diff --git a/src/api/crm/business/index.ts b/src/api/crm/business/index.ts
index 41ec35d8..810ec6e9 100644
--- a/src/api/crm/business/index.ts
+++ b/src/api/crm/business/index.ts
@@ -1,10 +1,3 @@
-/*
- * @Author: zyna
- * @Date: 2023-12-02 13:08:56
- * @LastEditTime: 2023-12-17 16:28:20
- * @FilePath: \yudao-ui-admin-vue3\src\api\crm\business\index.ts
- * @Description: 
- */
 import request from '@/config/axios'
 
 export interface BusinessVO {
@@ -43,6 +36,11 @@ export const getBusiness = async (id: number) => {
   return await request.get({ url: `/crm/business/get?id=` + id })
 }
 
+// 获得 CRM 商机列表(精简)
+export const getSimpleBusinessList = async () => {
+  return await request.get({ url: `/crm/business/simple-all-list` })
+}
+
 // 新增 CRM 商机
 export const createBusiness = async (data: BusinessVO) => {
   return await request.post({ url: `/crm/business/create`, data })
@@ -63,7 +61,12 @@ export const exportBusiness = async (params) => {
   return await request.download({ url: `/crm/business/export-excel`, params })
 }
 
-//联系人关联商机列表
+// 联系人关联商机列表
 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(',') } })
+}
diff --git a/src/api/crm/contact/index.ts b/src/api/crm/contact/index.ts
index 44eebe80..4144c931 100644
--- a/src/api/crm/contact/index.ts
+++ b/src/api/crm/contact/index.ts
@@ -71,6 +71,11 @@ export const getSimpleContactList = async () => {
   return await request.get({ url: `/crm/contact/simple-all-list` })
 }
 
+// 获得 CRM 联系人列表
+export const getContactListByIds = async (val: number[]) => {
+  return await request.get({ url: '/crm/contact/list-by-ids', params: { ids: val.join(',') } })
+}
+
 // 批量新增联系人商机关联
 export const createContactBusinessList = async (data: ContactBusinessReqVO) => {
   return await request.post({ url: `/crm/contact/create-business-list`, data })
@@ -84,4 +89,4 @@ export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => {
 // 查询联系人操作日志
 export const getOperateLogPage = async (params: any) => {
   return await request.get({ url: '/crm/contact/operate-log-page', params })
-}
\ No newline at end of file
+}
diff --git a/src/api/crm/customer/index.ts b/src/api/crm/customer/index.ts
index 5ec8a3ea..4542c86a 100644
--- a/src/api/crm/customer/index.ts
+++ b/src/api/crm/customer/index.ts
@@ -64,8 +64,8 @@ export const exportCustomer = async (params: any) => {
 }
 
 // 客户列表
-export const queryAllList = async () => {
-  return await request.get({ url: `/crm/customer/query-all-list` })
+export const getSimpleCustomerList = async () => {
+  return await request.get({ url: `/crm/customer/list-all-simple` })
 }
 
 // 查询客户操作日志
@@ -75,18 +75,28 @@ export const getOperateLogPage = async (id: number) => {
 
 // ======================= 业务操作 =======================
 
+export interface TransferReqVO {
+  id: number | undefined // 客户编号
+  newOwnerUserId: number | undefined // 新负责人的用户编号
+  oldOwnerPermissionLevel: number | undefined // 老负责人加入团队后的权限级别
+}
+
+// 客户转移
+export const transfer = async (data: TransferReqVO) => {
+  return await request.put({ url: '/crm/customer/transfer', data })
+}
+
 // 锁定/解锁客户
 export const lockCustomer = async (id: number, lockStatus: boolean) => {
   return await request.put({ url: `/crm/customer/lock`, data: { id, lockStatus } })
 }
 
-// TODO @puhui999:方法名,改成和后端一致哈
 // 领取公海客户
-export const receive = async (ids: any[]) => {
+export const receiveCustomer = async (ids: any[]) => {
   return await request.put({ url: '/crm/customer/receive', params: { ids: ids.join(',') } })
 }
 
 // 客户放入公海
-export const putPool = async (id: number) => {
+export const putCustomerPool = async (id: number) => {
   return await request.put({ url: `/crm/customer/put-pool?id=${id}` })
 }
diff --git a/src/api/crm/followup/index.ts b/src/api/crm/followup/index.ts
new file mode 100644
index 00000000..f6b66105
--- /dev/null
+++ b/src/api/crm/followup/index.ts
@@ -0,0 +1,46 @@
+import request from '@/config/axios'
+
+// 跟进记录 VO
+export interface FollowUpRecordVO {
+  id: number // 编号
+  bizType: number // 数据类型
+  bizId: number // 数据编号
+  type: number // 跟进类型
+  content: string // 跟进内容
+  nextTime: Date // 下次联系时间
+  businessIds: number[] // 关联的商机编号数组
+  contactIds: number[] // 关联的联系人编号数组
+}
+
+// 跟进记录 API
+export const FollowUpRecordApi = {
+  // 查询跟进记录分页
+  getFollowUpRecordPage: async (params: any) => {
+    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 })
+  }
+}
diff --git a/src/api/crm/message/index.ts b/src/api/crm/message/index.ts
index fcd5fbd7..098729eb 100644
--- a/src/api/crm/message/index.ts
+++ b/src/api/crm/message/index.ts
@@ -34,6 +34,7 @@ export interface CustomerVO {
 }
 
 // 查询客户列表
+// TODO @芋艿:看看是不是后续融合到 getCustomerPage 里;
 export const getTodayCustomerPage = async (params) => {
   return await request.get({ url: `/crm/message/todayCustomer`, params })
 }
diff --git a/src/components/OperateLogV2/src/OperateLogV2.vue b/src/components/OperateLogV2/src/OperateLogV2.vue
index ae8aad40..b766fb44 100644
--- a/src/components/OperateLogV2/src/OperateLogV2.vue
+++ b/src/components/OperateLogV2/src/OperateLogV2.vue
@@ -1,6 +1,6 @@
+<!-- 某个记录的操作日志列表,目前主要用于 CRM 客户、商机等详情界面 -->
 <template>
-  <!-- TODO @puhui999:左边不用有空隙哈 -->
-  <div class="p-20px">
+  <div class="pt-20px">
     <el-timeline>
       <el-timeline-item
         v-for="(log, index) in logList"
@@ -58,7 +58,7 @@ const getUserTypeColor = (type: number) => {
 <style lang="scss" scoped>
 // 时间线样式调整
 :deep(.el-timeline) {
-  margin: 10px 0 0 160px;
+  margin: 10px 0 0 110px;
 
   .el-timeline-item__wrapper {
     position: relative;
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index 35320edb..7e805159 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -537,6 +537,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
           noCache: true,
           hidden: true
         },
+        // TODO @db52:后面搞,搞成菜单
         component: () => import('@/views/crm/message/index.vue')
       }
     ]
diff --git a/src/utils/dict.ts b/src/utils/dict.ts
index 8b543d15..2c8e47ff 100644
--- a/src/utils/dict.ts
+++ b/src/utils/dict.ts
@@ -162,9 +162,7 @@ export enum DICT_TYPE {
   MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型
 
   // ========== MALL - 商品模块 ==========
-  PRODUCT_UNIT = 'product_unit', // 商品单位
   PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态
-  PROMOTION_TYPE_ENUM = 'promotion_type_enum', // 营销类型枚举
 
   // ========== MALL - 交易模块 ==========
   EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式
@@ -204,5 +202,6 @@ export enum DICT_TYPE {
   CRM_CUSTOMER_SOURCE = 'crm_customer_source',
   CRM_PRODUCT_STATUS = 'crm_product_status',
   CRM_PERMISSION_LEVEL = 'crm_permission_level', // CRM 数据权限的级别
-  CRM_PRODUCT_UNIT = 'crm_product_unit' // 产品单位
+  CRM_PRODUCT_UNIT = 'crm_product_unit', // 产品单位
+  CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type' // 跟进方式
 }
diff --git a/src/views/crm/contact/ContactForm.vue b/src/views/crm/contact/ContactForm.vue
index fc691a21..f8c35af6 100644
--- a/src/views/crm/contact/ContactForm.vue
+++ b/src/views/crm/contact/ContactForm.vue
@@ -1,25 +1,25 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible" :width="820">
+  <Dialog v-model="dialogVisible" :title="dialogTitle" :width="820">
     <el-form
       ref="formRef"
+      v-loading="formLoading"
       :model="formData"
       :rules="formRules"
       label-width="110px"
-      v-loading="formLoading"
     >
       <el-row :gutter="20">
         <el-col :span="12">
           <el-form-item label="姓名" prop="name">
-            <el-input input-style="width:190px;" v-model="formData.name" placeholder="请输入姓名" />
+            <el-input v-model="formData.name" input-style="width:190px;" placeholder="请输入姓名" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="负责人" prop="ownerUserId">
             <el-select
               v-model="formData.ownerUserId"
+              lable-key="nickname"
               placeholder="请选择负责人"
               value-key="id"
-              lable-key="nickname"
             >
               <el-option
                 v-for="item in userList"
@@ -36,9 +36,9 @@
           <el-form-item label="客户名称" prop="customerName">
             <el-select
               v-model="formData.customerId"
+              lable-key="name"
               placeholder="请选择客户"
               value-key="id"
-              lable-key="name"
             >
               <el-option
                 v-for="item in customerList"
@@ -66,8 +66,8 @@
         <el-col :span="12">
           <el-form-item label="手机号" prop="mobile">
             <el-input
-              input-style="width:190px;"
               v-model="formData.mobile"
+              input-style="width:190px;"
               placeholder="请输入手机号"
             />
           </el-form-item>
@@ -82,8 +82,8 @@
         <el-col :span="12">
           <el-form-item label="邮箱" prop="email">
             <el-input
-              input-style="width:190px;"
               v-model="formData.email"
+              input-style="width:190px;"
               placeholder="请输入邮箱"
             />
           </el-form-item>
@@ -98,8 +98,8 @@
         <el-col :span="12">
           <el-form-item label="微信" prop="wechat">
             <el-input
-              input-style="width:190px;"
               v-model="formData.wechat"
+              input-style="width:190px;"
               placeholder="请输入微信"
             />
           </el-form-item>
@@ -108,9 +108,9 @@
           <el-form-item label="下次联系时间" prop="contactNextTime">
             <el-date-picker
               v-model="formData.contactNextTime"
+              placeholder="选择下次联系时间"
               type="datetime"
               value-format="x"
-              placeholder="选择下次联系时间"
             />
           </el-form-item>
         </el-col>
@@ -129,8 +129,8 @@
         <el-col :span="12">
           <el-form-item label="地址" prop="detailAddress">
             <el-input
-              input-style="width:190px;"
               v-model="formData.detailAddress"
+              input-style="width:190px;"
               placeholder="请输入地址"
             />
           </el-form-item>
@@ -143,16 +143,16 @@
               <el-option
                 v-for="item in allContactList"
                 :key="item.id"
+                :disabled="item.id == formData.id"
                 :label="item.name"
                 :value="item.id"
-                :disabled="item.id == formData.id"
               />
             </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="职位" prop="post">
-            <el-input input-style="width:190px;" v-model="formData.post" placeholder="请输入职位" />
+            <el-input v-model="formData.post" input-style="width:190px;" placeholder="请输入职位" />
           </el-form-item>
         </el-col>
       </el-row>
@@ -180,14 +180,14 @@
       </el-row>
     </el-form>
     <template #footer>
-      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
       <el-button @click="dialogVisible = false">取 消</el-button>
     </template>
   </Dialog>
 </template>
-<script setup lang="ts">
+<script lang="ts" setup>
 import * as ContactApi from '@/api/crm/contact'
-import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict'
+import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
 import * as UserApi from '@/api/system/user'
 import * as CustomerApi from '@/api/crm/customer'
 import * as AreaApi from '@/api/system/area'
@@ -242,7 +242,7 @@ const open = async (type: string, id?: number) => {
   resetForm()
   allContactList.value = await ContactApi.getSimpleContactList()
   userList.value = await UserApi.getSimpleUserList()
-  customerList.value = await CustomerApi.queryAllList()
+  customerList.value = await CustomerApi.getSimpleCustomerList()
   areaList.value = await AreaApi.getAreaTree()
   // 修改时,设置数据
   if (id) {
diff --git a/src/views/crm/contact/index.vue b/src/views/crm/contact/index.vue
index fc467ea1..c1c7f347 100644
--- a/src/views/crm/contact/index.vue
+++ b/src/views/crm/contact/index.vue
@@ -2,21 +2,21 @@
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
-      class="-mb-15px"
-      :model="queryParams"
       ref="queryFormRef"
       :inline="true"
+      :model="queryParams"
+      class="-mb-15px"
       label-width="68px"
     >
       <el-form-item label="客户" prop="customerId">
         <el-select
           v-model="queryParams.customerId"
           class="!w-240px"
+          clearable
+          lable-key="name"
           placeholder="请选择客户"
           value-key="id"
-          lable-key="name"
           @keyup.enter="handleQuery"
-          clearable
         >
           <el-option
             v-for="item in customerList"
@@ -30,8 +30,8 @@
         <el-input
           v-model="queryParams.name"
           class="!w-240px"
-          placeholder="请输入姓名"
           clearable
+          placeholder="请输入姓名"
           @keyup.enter="handleQuery"
         />
       </el-form-item>
@@ -39,8 +39,8 @@
         <el-input
           v-model="queryParams.mobile"
           class="!w-240px"
-          placeholder="请输入手机号"
           clearable
+          placeholder="请输入手机号"
           @keyup.enter="handleQuery"
         />
       </el-form-item>
@@ -48,8 +48,8 @@
         <el-input
           v-model="queryParams.telephone"
           class="!w-240px"
-          placeholder="请输入电话"
           clearable
+          placeholder="请输入电话"
           @keyup.enter="handleQuery"
         />
       </el-form-item>
@@ -57,8 +57,8 @@
         <el-input
           v-model="queryParams.qq"
           class="!w-240px"
-          placeholder="请输入QQ"
           clearable
+          placeholder="请输入QQ"
           @keyup.enter="handleQuery"
         />
       </el-form-item>
@@ -66,8 +66,8 @@
         <el-input
           v-model="queryParams.wechat"
           class="!w-240px"
-          placeholder="请输入微信"
           clearable
+          placeholder="请输入微信"
           @keyup.enter="handleQuery"
         />
       </el-form-item>
@@ -75,25 +75,33 @@
         <el-input
           v-model="queryParams.email"
           class="!w-240px"
-          placeholder="请输入电子邮箱"
           clearable
+          placeholder="请输入电子邮箱"
           @keyup.enter="handleQuery"
         />
       </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 @click="handleQuery">
+          <Icon class="mr-5px" icon="ep:search" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          重置
+        </el-button>
+        <el-button v-hasPermi="['crm:contact:create']" type="primary" @click="openForm('create')">
+          <Icon class="mr-5px" icon="ep:plus" />
+          新增
         </el-button>
         <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
           v-hasPermi="['crm:contact:export']"
+          :loading="exportLoading"
+          plain
+          type="success"
+          @click="handleExport"
         >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
+          <Icon class="mr-5px" icon="ep:download" />
+          导出
         </el-button>
       </el-form-item>
     </el-form>
@@ -101,30 +109,30 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="姓名" fixed="left" align="center" prop="name" width="140">
+    <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
+      <el-table-column align="center" fixed="left" label="姓名" prop="name" width="140">
         <template #default="scope">
-          <el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
+          <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
             {{ scope.row.name }}
           </el-link>
         </template>
       </el-table-column>
-      <el-table-column label="客户名称" fixed="left" align="center" prop="customerName" width="120">
+      <el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="120">
         <template #default="scope">
           <el-link
-            type="primary"
             :underline="false"
+            type="primary"
             @click="openCustomerDetail(scope.row.customerId)"
           >
             {{ scope.row.customerName }}
           </el-link>
         </template>
       </el-table-column>
-      <el-table-column label="手机" align="center" prop="mobile" width="120" />
-      <el-table-column label="电话" align="center" prop="telephone" width="120" />
-      <el-table-column label="邮箱" align="center" prop="email" width="120" />
-      <el-table-column label="职位" align="center" prop="post" width="120" />
-      <el-table-column label="地址" align="center" prop="detailAddress" width="120" />
+      <el-table-column align="center" label="手机" prop="mobile" width="120" />
+      <el-table-column align="center" label="电话" prop="telephone" width="120" />
+      <el-table-column align="center" label="邮箱" prop="email" width="120" />
+      <el-table-column align="center" label="职位" prop="post" width="120" />
+      <el-table-column align="center" label="地址" prop="detailAddress" width="120" />
       <el-table-column
         :formatter="dateFormatter"
         align="center"
@@ -132,56 +140,56 @@
         prop="contactNextTime"
         width="180px"
       />
-      <el-table-column label="备注" align="center" prop="remark" />
-      <el-table-column label="关键决策人" align="center" prop="master" width="100">
+      <el-table-column align="center" label="备注" prop="remark" />
+      <el-table-column align="center" label="关键决策人" prop="master" width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
         </template>
       </el-table-column>
-      <el-table-column label="直属上级" align="center" prop="parentName" width="140" />
+      <el-table-column align="center" label="直属上级" prop="parentName" width="140" />
       <el-table-column
-        label="最后跟进时间"
-        align="center"
-        prop="contactLastTime"
         :formatter="dateFormatter"
+        align="center"
+        label="最后跟进时间"
+        prop="contactLastTime"
         width="180px"
       />
-      <el-table-column label="性别" align="center" prop="sex">
+      <el-table-column align="center" label="性别" 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="ownerUserName" width="120" />
-      <el-table-column label="创建人" align="center" prop="creatorName" width="120" />
+      <el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
+      <el-table-column align="center" label="创建人" prop="creatorName" width="120" />
       <el-table-column
+        :formatter="dateFormatter"
+        align="center"
         label="更新时间"
-        align="center"
         prop="updateTime"
-        :formatter="dateFormatter"
         width="180px"
       />
       <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
         :formatter="dateFormatter"
+        align="center"
+        label="创建时间"
+        prop="createTime"
         width="180px"
       />
-      <el-table-column label="操作" align="center" fixed="right" width="200">
+      <el-table-column align="center" fixed="right" label="操作" width="200">
         <template #default="scope">
           <el-button
+            v-hasPermi="['crm:contact:update']"
             link
             type="primary"
             @click="openForm('update', scope.row.id)"
-            v-hasPermi="['crm:contact:update']"
           >
             编辑
           </el-button>
           <el-button
+            v-hasPermi="['crm:contact:delete']"
             link
             type="danger"
             @click="handleDelete(scope.row.id)"
-            v-hasPermi="['crm:contact:delete']"
           >
             删除
           </el-button>
@@ -190,9 +198,9 @@
     </el-table>
     <!-- 分页 -->
     <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
       v-model:limit="queryParams.pageSize"
+      v-model:page="queryParams.pageNo"
+      :total="total"
       @pagination="getList"
     />
   </ContentWrap>
@@ -201,7 +209,7 @@
   <ContactForm ref="formRef" @success="getList" />
 </template>
 
-<script setup lang="ts">
+<script lang="ts" setup>
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import * as ContactApi from '@/api/crm/contact'
@@ -295,6 +303,7 @@ const { push } = useRouter()
 const openDetail = (id: number) => {
   push({ name: 'CrmContactDetail', params: { id } })
 }
+
 /** 打开客户详情 */
 const openCustomerDetail = (id: number) => {
   push({ name: 'CrmCustomerDetail', params: { id } })
@@ -303,6 +312,6 @@ const openCustomerDetail = (id: number) => {
 /** 初始化 **/
 onMounted(async () => {
   await getList()
-  customerList.value = await CustomerApi.queryAllList()
+  customerList.value = await CustomerApi.getSimpleCustomerList()
 })
 </script>
diff --git a/src/views/crm/customer/detail/index.vue b/src/views/crm/customer/detail/index.vue
index 15472124..41b9a0be 100644
--- a/src/views/crm/customer/detail/index.vue
+++ b/src/views/crm/customer/detail/index.vue
@@ -1,33 +1,57 @@
 <template>
   <CustomerDetailsHeader :customer="customer" :loading="loading">
-    <!-- @puhui999:返回是不是可以去掉哈,貌似用途可能不大 -->
-    <el-button @click="close">返回</el-button>
-    <!-- TODO puhui999: 按钮数据权限收尾统一完善,需要按权限分级和客户状态来动态显示匹配的按钮 -->
-    <el-button v-hasPermi="['crm:customer:update']" type="primary" @click="openForm">
+    <el-button
+      v-if="permissionListRef?.validateWrite"
+      v-hasPermi="['crm:customer:update']"
+      type="primary"
+      @click="openForm"
+    >
       编辑
     </el-button>
-    <!-- TODO @puhui999:转移的操作接入 -->
-    <el-button type="primary" @click="transfer">转移</el-button>
-    <!-- TODO @puhui999:修改成交状态的接入 -->
-    <el-button>更改成交状态</el-button>
-    <el-button v-if="customer.lockStatus" @click="handleUnlock">解锁</el-button>
-    <el-button v-if="!customer.lockStatus" @click="handleLock">锁定</el-button>
-    <el-button v-if="!customer.ownerUserId" type="primary" @click="receive">领取客户</el-button>
-    <el-button v-if="customer.ownerUserId" @click="putPool">客户放入公海</el-button>
+    <el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer">
+      转移
+    </el-button>
+    <el-button v-if="permissionListRef?.validateWrite">更改成交状态</el-button>
+    <el-button
+      v-if="customer.lockStatus && permissionListRef?.validateOwnerUser"
+      @click="handleUnlock"
+    >
+      解锁
+    </el-button>
+    <el-button
+      v-if="!customer.lockStatus && permissionListRef?.validateOwnerUser"
+      @click="handleLock"
+    >
+      锁定
+    </el-button>
+    <el-button v-if="!customer.ownerUserId" type="primary" @click="handleReceive">
+      领取客户
+    </el-button>
+    <el-button
+      v-if="customer.ownerUserId && permissionListRef?.validateOwnerUser"
+      @click="handlePutPool"
+    >
+      客户放入公海
+    </el-button>
   </CustomerDetailsHeader>
   <el-col>
     <el-tabs>
-      <el-tab-pane label="详细资料">
-        <CustomerDetailsInfo :customer="customer" />
+      <el-tab-pane label="跟进记录">
+        <FollowUpList :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
       </el-tab-pane>
-      <el-tab-pane label="操作日志">
-        <OperateLogV2 :log-list="logList" />
+      <el-tab-pane label="基本信息">
+        <CustomerDetailsInfo :customer="customer" />
       </el-tab-pane>
       <el-tab-pane label="联系人" lazy>
         <ContactList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
       </el-tab-pane>
       <el-tab-pane label="团队成员">
-        <PermissionList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
+        <PermissionList
+          ref="permissionListRef"
+          :biz-id="customer.id!"
+          :biz-type="BizTypeEnum.CRM_CUSTOMER"
+          :show-action="!permissionListRef?.isPool || false"
+        />
       </el-tab-pane>
       <el-tab-pane label="商机" lazy>
         <BusinessList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
@@ -39,12 +63,16 @@
         <ReceivablePlanList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
         <ReceivableList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
       </el-tab-pane>
+      <el-tab-pane label="操作日志">
+        <OperateLogV2 :log-list="logList" />
+      </el-tab-pane>
       <el-tab-pane label="回访" lazy>TODO 待开发</el-tab-pane>
     </el-tabs>
   </el-col>
 
   <!-- 表单弹窗:添加/修改 -->
   <CustomerForm ref="formRef" @success="getCustomer" />
+  <CrmTransferForm ref="crmTransferFormRef" @success="close" />
 </template>
 <script lang="ts" setup>
 import { useTagsViewStore } from '@/store/modules/tagsView'
@@ -58,6 +86,8 @@ import BusinessList from '@/views/crm/business/components/BusinessList.vue' // 
 import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue' // 回款列表
 import ReceivablePlanList from '@/views/crm/receivable/plan/components/ReceivablePlanList.vue' // 回款计划列表
 import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
+import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
+import FollowUpList from '@/views/crm/followup/index.vue'
 import { BizTypeEnum } from '@/api/crm/permission'
 import type { OperateLogV2VO } from '@/api/system/operatelog'
 
@@ -67,7 +97,9 @@ const customerId = ref(0) // 客户编号
 const loading = ref(true) // 加载中
 const message = useMessage() // 消息弹窗
 const { delView } = useTagsViewStore() // 视图操作
-const { currentRoute, push } = useRouter() // 路由
+const { currentRoute } = useRouter() // 路由
+
+const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
 
 /** 获取详情 */
 const customer = ref<CustomerApi.CustomerVO>({} as CustomerApi.CustomerVO) // 客户详情
@@ -88,7 +120,10 @@ const openForm = () => {
 }
 
 /** 客户转移 */
-const transfer = () => {}
+const crmTransferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 客户转移表单 ref
+const transfer = () => {
+  crmTransferFormRef.value?.open('客户转移', customerId.value, CustomerApi.transfer)
+}
 
 /** 锁定客户 */
 const handleLock = async () => {
@@ -106,19 +141,18 @@ const handleUnlock = async () => {
   await getCustomer()
 }
 
-// TODO @puhui999:下面两个方法的命名,也用 handleXXX 风格哈
 /** 领取客户 */
-const receive = async () => {
+const handleReceive = async () => {
   await message.confirm(`确定领取客户【${customer.value.name}】 吗?`)
-  await CustomerApi.receive([unref(customerId.value)])
+  await CustomerApi.receiveCustomer([unref(customerId.value)])
   message.success(`领取客户【${customer.value.name}】成功`)
   await getCustomer()
 }
 
 /** 客户放入公海 */
-const putPool = async () => {
+const handlePutPool = async () => {
   await message.confirm(`确定将客户【${customer.value.name}】放入公海吗?`)
-  await CustomerApi.putPool(unref(customerId.value))
+  await CustomerApi.putCustomerPool(unref(customerId.value))
   message.success(`客户【${customer.value.name}】放入公海成功`)
   close()
 }
@@ -135,15 +169,13 @@ const getOperateLog = async () => {
 
 const close = () => {
   delView(unref(currentRoute))
-  // TODO 先返回到客户列表
-  push({ name: 'CrmCustomer' })
 }
 
 /** 初始化 */
 const { params } = useRoute()
 onMounted(() => {
   if (!params.id) {
-    ElMessage.warning('参数错误,客户不能为空!')
+    message.warning('参数错误,客户不能为空!')
     close()
     return
   }
diff --git a/src/views/crm/customer/index.vue b/src/views/crm/customer/index.vue
index 3ca7b022..b2cee2f7 100644
--- a/src/views/crm/customer/index.vue
+++ b/src/views/crm/customer/index.vue
@@ -100,14 +100,10 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <!-- TODO @puhui999:是不是就 3 重呀,我负责的,我参与的,我下属的 -->
     <el-tabs v-model="activeName" @tab-click="handleClick">
-      <el-tab-pane label="客户列表" name="1" />
-      <el-tab-pane label="我负责的" name="2" />
-      <el-tab-pane label="我关注的" name="3" />
-      <el-tab-pane label="我参与的" name="4" />
-      <el-tab-pane label="下属负责的" name="5" />
-      <el-tab-pane label="客户公海" name="6" />
+      <el-tab-pane label="我负责的" name="1" />
+      <el-tab-pane label="我参与的" name="2" />
+      <el-tab-pane label="下属负责的" name="3" />
     </el-tabs>
     <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
       <el-table-column align="center" label="编号" prop="id" />
@@ -149,7 +145,9 @@
           <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
         </template>
       </el-table-column>
-      <!-- TODO @puhui999:距进入公海天数 -->
+      <el-table-column align="center" label="距离进入公海" prop="poolDay">
+        <template #default="scope"> {{ scope.row.poolDay }} 天</template>
+      </el-table-column>
       <el-table-column
         :formatter="dateFormatter"
         align="center"
@@ -251,43 +249,27 @@ const activeName = ref('1') // 列表 tab
 
 enum CrmSceneTypeEnum {
   OWNER = 1,
-  FOLLOW = 2,
-  INVOLVED = 3,
-  SUBORDINATE = 4
+  INVOLVED = 2,
+  SUBORDINATE = 3
 }
 
 const handleClick = (tab: TabsPaneContext) => {
   switch (tab.paneName) {
     case '1':
-      resetQuery()
-      break
-    case '2':
       resetQuery(() => {
         queryParams.value.sceneType = CrmSceneTypeEnum.OWNER
       })
       break
-    case '3':
-      resetQuery(() => {
-        queryParams.value.sceneType = CrmSceneTypeEnum.FOLLOW
-      })
-      break
-    // TODO @puhui999:这个貌似报错?
-    case '4':
+    case '2':
       resetQuery(() => {
         queryParams.value.sceneType = CrmSceneTypeEnum.INVOLVED
       })
       break
-    case '5':
+    case '3':
       resetQuery(() => {
         queryParams.value.sceneType = CrmSceneTypeEnum.SUBORDINATE
       })
       break
-    // TODO @puhui999:公海单独一个菜单哈。
-    case '6':
-      resetQuery(() => {
-        queryParams.value.pool = true
-      })
-      break
   }
 }
 
diff --git a/src/views/crm/customer/pool/index.vue b/src/views/crm/customer/pool/index.vue
new file mode 100644
index 00000000..c7b13a04
--- /dev/null
+++ b/src/views/crm/customer/pool/index.vue
@@ -0,0 +1,279 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      ref="queryFormRef"
+      :inline="true"
+      :model="queryParams"
+      class="-mb-15px"
+      label-width="68px"
+    >
+      <el-form-item label="客户名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          class="!w-240px"
+          clearable
+          placeholder="请输入客户名称"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="手机" prop="mobile">
+        <el-input
+          v-model="queryParams.mobile"
+          class="!w-240px"
+          clearable
+          placeholder="请输入手机"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="所属行业" prop="industryId">
+        <el-select
+          v-model="queryParams.industryId"
+          class="!w-240px"
+          clearable
+          placeholder="请选择所属行业"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="客户等级" prop="level">
+        <el-select
+          v-model="queryParams.level"
+          class="!w-240px"
+          clearable
+          placeholder="请选择客户等级"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="客户来源" prop="source">
+        <el-select
+          v-model="queryParams.source"
+          class="!w-240px"
+          clearable
+          placeholder="请选择客户来源"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon class="mr-5px" icon="ep:search" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery(undefined)">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          重置
+        </el-button>
+        <el-button
+          v-hasPermi="['crm:customer:export']"
+          :loading="exportLoading"
+          plain
+          type="success"
+          @click="handleExport"
+        >
+          <Icon class="mr-5px" icon="ep:download" />
+          导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
+      <el-table-column align="center" label="编号" prop="id" />
+      <el-table-column align="center" label="客户名称" prop="name" width="160">
+        <template #default="scope">
+          <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
+            {{ scope.row.name }}
+          </el-link>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="手机" prop="mobile" width="120" />
+      <el-table-column align="center" label="电话" prop="telephone" width="120" />
+      <el-table-column align="center" label="客户来源" 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 align="center" label="所属行业" prop="industryId" width="120">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="客户等级" 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-column align="center" label="网址" prop="website" width="200" />
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="下次联系时间"
+        prop="contactNextTime"
+        width="180px"
+      />
+      <el-table-column align="center" label="备注" prop="remark" width="200" />
+      <el-table-column align="center" label="成交状态" prop="dealStatus">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="距离进入公海" prop="poolDay">
+        <template #default="scope"> {{ scope.row.poolDay }}天</template>
+      </el-table-column>
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="最后跟进时间"
+        prop="contactLastTime"
+        width="180px"
+      />
+      <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="100px" />
+      <el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
+      <el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
+      <el-table-column align="center" fixed="right" label="操作" min-width="150">
+        <template #default="scope">
+          <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
+            详情
+          </el-link>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      v-model:limit="queryParams.pageSize"
+      v-model:page="queryParams.pageNo"
+      :total="total"
+      @pagination="getList"
+    />
+  </ContentWrap>
+</template>
+
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import * as CustomerApi from '@/api/crm/customer'
+
+defineOptions({ name: 'CrmCustomerPool' })
+
+const message = useMessage() // 消息弹窗
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = ref({
+  pageNo: 1,
+  pageSize: 10,
+  name: '',
+  mobile: '',
+  industryId: undefined,
+  level: undefined,
+  source: undefined,
+  sceneType: undefined,
+  pool: true
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await CustomerApi.getCustomerPage(queryParams.value)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.value = {
+    pageNo: 1,
+    pageSize: 10,
+    name: '',
+    mobile: '',
+    industryId: undefined,
+    level: undefined,
+    source: undefined,
+    sceneType: undefined,
+    pool: true
+  }
+  handleQuery()
+}
+
+/** 打开客户详情 */
+const { currentRoute, push } = useRouter()
+const openDetail = (id: number) => {
+  push({ name: 'CrmCustomerDetail', params: { id } })
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await CustomerApi.exportCustomer(queryParams.value)
+    download.excel(data, '客户.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 监听路由变化更新列表 */
+watch(
+  () => currentRoute.value,
+  () => {
+    getList()
+  }
+)
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>
diff --git a/src/views/crm/followup/FollowUpRecordForm.vue b/src/views/crm/followup/FollowUpRecordForm.vue
new file mode 100644
index 00000000..ea75fdb9
--- /dev/null
+++ b/src/views/crm/followup/FollowUpRecordForm.vue
@@ -0,0 +1,167 @@
+<!-- 跟进记录的添加表单弹窗 -->
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+    >
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="跟进类型" prop="type">
+            <el-select v-model="formData.type" placeholder="请选择跟进类型">
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="下次联系时间" prop="nextTime">
+            <el-date-picker
+              v-model="formData.nextTime"
+              placeholder="选择下次联系时间"
+              type="date"
+              value-format="x"
+            />
+          </el-form-item>
+        </el-col>
+        <!-- TODO @puhui999:不搞富文本哈;然后加个附件、图片两个 form-item 哈 -->
+        <el-col :span="24">
+          <el-form-item label="跟进内容" prop="content">
+            <Editor v-model="formData.content" height="300px" />
+          </el-form-item>
+        </el-col>
+        <!-- TODO @puhui999:因为不考虑编辑的情况,是不是关联要是个弹窗选择哈? -->
+        <el-col :span="24">
+          <el-form-item label="关联联系人" prop="contactIds">
+            <el-select v-model="formData.contactIds" multiple placeholder="请选择">
+              <el-option
+                v-for="item in allContactList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+            <contact-list v-model:contactIds="formData.contactIds" />
+          </el-form-item>
+          <!--          <el-form-item label="关联联系人" prop="contactIds">-->
+          <!--            <el-button @click="handleAddContact">-->
+          <!--              <Icon class="mr-5px" icon="ep:plus" />-->
+          <!--              选择添加联系人-->
+          <!--            </el-button>-->
+          <!--            <contact-list v-model:contactIds="formData.contactIds" />-->
+          <!--          </el-form-item>-->
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="关联商机" prop="businessIds">
+            <el-select v-model="formData.businessIds" multiple placeholder="请选择">
+              <el-option
+                v-for="item in allBusinessList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+            <business-list v-model:businessIds="formData.businessIds" />
+          </el-form-item>
+          <!--          <el-form-item label="关联商机" prop="businessIds">-->
+          <!--            <el-button @click="handleAddBusiness">-->
+          <!--              <Icon class="mr-5px" icon="ep:plus" />-->
+          <!--              选择添加商机-->
+          <!--            </el-button>-->
+          <!--            <business-list v-model:businessIds="formData.businessIds" />-->
+          <!--          </el-form-item>-->
+        </el-col>
+      </el-row>
+    </el-form>
+    <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 { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
+import { BusinessList, ContactList } from './components'
+import * as ContactApi from '@/api/crm/contact'
+import * as BusinessApi from '@/api/crm/business'
+
+defineOptions({ name: 'FollowUpRecordForm' })
+
+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<FollowUpRecordVO>({} as FollowUpRecordVO)
+const formRules = reactive({
+  type: [{ required: true, message: '跟进类型不能为空', trigger: 'change' }],
+  content: [{ required: true, message: '跟进内容不能为空', trigger: 'blur' }],
+  nextTime: [{ required: true, message: '下次联系时间不能为空', trigger: 'blur' }]
+})
+
+const formRef = ref() // 表单 Ref
+const allContactList = ref<ContactApi.ContactVO[]>([]) // 所有联系人列表
+const allBusinessList = ref<BusinessApi.BusinessVO[]>([]) // 所有商家列表
+
+/** 打开弹窗 */
+const open = async (bizType: number, bizId: number, type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  formData.value.bizType = bizType
+  formData.value.bizId = bizId
+  allContactList.value = await ContactApi.getSimpleContactList()
+  allBusinessList.value = await BusinessApi.getSimpleBusinessList()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await FollowUpRecordApi.getFollowUpRecord(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  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'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formRef.value?.resetFields()
+  formData.value = {} as FollowUpRecordVO
+}
+</script>
diff --git a/src/views/crm/followup/components/BusinessList.vue b/src/views/crm/followup/components/BusinessList.vue
new file mode 100644
index 00000000..49511194
--- /dev/null
+++ b/src/views/crm/followup/components/BusinessList.vue
@@ -0,0 +1,71 @@
+<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(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>
diff --git a/src/views/crm/followup/components/BusinessListSelectForm.vue b/src/views/crm/followup/components/BusinessListSelectForm.vue
new file mode 100644
index 00000000..2ff6ab4a
--- /dev/null
+++ b/src/views/crm/followup/components/BusinessListSelectForm.vue
@@ -0,0 +1,79 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%">
+    <el-row>
+      <el-col :span="12">
+        <el-form-item label="跟进类型" prop="type">
+          <el-select v-model="formData.type" placeholder="请选择跟进类型">
+            <el-option
+              v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="下次联系时间" prop="nextTime">
+          <el-date-picker
+            v-model="formData.nextTime"
+            placeholder="选择下次联系时间"
+            type="date"
+            value-format="x"
+          />
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <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>
+/** 跟进记录 表单 */
+defineOptions({ name: 'BusinessListSelectForm' })
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref([])
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await FollowUpRecordApi.getFollowUpRecord(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/crm/followup/components/ContactList.vue b/src/views/crm/followup/components/ContactList.vue
new file mode 100644
index 00000000..2cd7ccf0
--- /dev/null
+++ b/src/views/crm/followup/components/ContactList.vue
@@ -0,0 +1,97 @@
+<template>
+  <el-table :data="list" :show-overflow-tooltip="true" :stripe="true" height="200">
+    <el-table-column align="center" fixed="left" label="姓名" prop="name" width="140" />
+    <el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="120" />
+    <el-table-column align="center" label="手机" prop="mobile" width="120" />
+    <el-table-column align="center" label="电话" prop="telephone" width="120" />
+    <el-table-column align="center" label="邮箱" prop="email" width="120" />
+    <el-table-column align="center" label="职位" prop="post" width="120" />
+    <el-table-column align="center" label="地址" prop="detailAddress" width="120" />
+    <el-table-column
+      :formatter="dateFormatter"
+      align="center"
+      label="下次联系时间"
+      prop="contactNextTime"
+      width="180px"
+    />
+    <el-table-column align="center" label="关键决策人" prop="master" width="100">
+      <template #default="scope">
+        <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
+      </template>
+    </el-table-column>
+    <el-table-column align="center" label="直属上级" prop="parentName" width="140" />
+    <el-table-column
+      :formatter="dateFormatter"
+      align="center"
+      label="最后跟进时间"
+      prop="contactLastTime"
+      width="180px"
+    />
+    <el-table-column align="center" label="性别" prop="sex">
+      <template #default="scope">
+        <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
+      </template>
+    </el-table-column>
+    <el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
+    <el-table-column align="center" label="创建人" prop="creatorName" width="120" />
+    <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="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 { DICT_TYPE } from '@/utils/dict'
+import * as ContactApi from '@/api/crm/contact'
+
+defineOptions({ name: 'ContactList' })
+const props = withDefaults(defineProps<{ contactIds: number[] }>(), {
+  contactIds: () => []
+})
+const list = ref<ContactApi.ContactVO[]>([] as ContactApi.ContactVO[])
+const getContactList = async () => {
+  list.value = (await ContactApi.getContactListByIds(
+    props.contactIds
+  )) as unknown as ContactApi.ContactVO[]
+}
+watch(
+  () => props.contactIds,
+  (val) => {
+    if (!val || val.length === 0) {
+      return
+    }
+    getContactList()
+  }
+)
+const emits = defineEmits<{
+  (e: 'update:contactIds', contactIds: 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:contactIds',
+    list.value.map((item) => item.id)
+  )
+}
+</script>
diff --git a/src/views/crm/followup/components/index.ts b/src/views/crm/followup/components/index.ts
new file mode 100644
index 00000000..b3e7f257
--- /dev/null
+++ b/src/views/crm/followup/components/index.ts
@@ -0,0 +1,4 @@
+import BusinessList from './BusinessList.vue'
+import ContactList from './ContactList.vue'
+
+export { BusinessList, ContactList }
diff --git a/src/views/crm/followup/index.vue b/src/views/crm/followup/index.vue
new file mode 100644
index 00000000..5960d1d9
--- /dev/null
+++ b/src/views/crm/followup/index.vue
@@ -0,0 +1,137 @@
+<!-- 某个记录的跟进记录列表,目前主要用于 CRM 客户、商机等详情界面 -->
+<template>
+  <!-- 操作栏 -->
+  <el-row class="mb-10px" justify="end">
+    <el-button @click="openForm('create')">
+      <Icon class="mr-5px" icon="ep:edit" />
+      写跟进
+    </el-button>
+  </el-row>
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
+      <el-table-column align="center" label="编号" prop="id" />
+      <el-table-column align="center" label="跟进人" prop="creatorName" />
+      <el-table-column align="center" label="跟进类型" prop="type">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="scope.row.type" />
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="跟进内容" prop="content" />
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="下次联系时间"
+        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="操作">
+        <template #default="scope">
+          <el-button
+            v-hasPermi="['crm:follow-up-record:update']"
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+          >
+            编辑
+          </el-button>
+          <el-button
+            v-hasPermi="['crm:follow-up-record:delete']"
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      v-model:limit="queryParams.pageSize"
+      v-model:page="queryParams.pageNo"
+      :total="total"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <FollowUpRecordForm ref="formRef" @success="getList" />
+</template>
+
+<script lang="ts" setup>
+import { dateFormatter } from '@/utils/formatTime'
+import { DICT_TYPE } from '@/utils/dict'
+import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
+import FollowUpRecordForm from './FollowUpRecordForm.vue'
+
+/** 跟进记录列表 */
+defineOptions({ name: 'FollowUpRecord' })
+const props = defineProps<{
+  bizType: number
+  bizId: number
+}>()
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<FollowUpRecordVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  bizType: 0,
+  bizId: 0
+})
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await FollowUpRecordApi.getFollowUpRecordPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 添加/修改操作 */
+const formRef = ref<InstanceType<typeof FollowUpRecordForm>>()
+const openForm = (type: string, id?: number) => {
+  formRef.value?.open(props.bizType, props.bizId, type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await FollowUpRecordApi.deleteFollowUpRecord(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+watch(
+  () => props.bizId,
+  () => {
+    queryParams.bizType = props.bizType
+    queryParams.bizId = props.bizId
+    getList()
+  }
+)
+</script>
diff --git a/src/views/crm/message/index.vue b/src/views/crm/message/index.vue
index 1f809524..1dd972aa 100644
--- a/src/views/crm/message/index.vue
+++ b/src/views/crm/message/index.vue
@@ -20,7 +20,7 @@
     </el-col>
   </el-row>
 </template>
-
+<!-- @dbh52:模块改成 backlog 会更合适,待办事项 -->
 <script lang="ts" setup>
 import TodayCustomer from './tables/TodayCustomer.vue'
 import FollowLeads from './tables/FollowLeads.vue'
diff --git a/src/views/crm/message/tables/TodayCustomer.vue b/src/views/crm/message/tables/TodayCustomer.vue
index df70e599..f4b59be1 100644
--- a/src/views/crm/message/tables/TodayCustomer.vue
+++ b/src/views/crm/message/tables/TodayCustomer.vue
@@ -125,11 +125,12 @@ import { DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import * as MessageApi from '@/api/crm/message'
 
-const title = ref('今日需联系客户')
+const title = ref('今日需联系客户') // TODO @dbh52:这个不用枚举一个变量哈;
 const loading = ref(true) // 列表的加载中
 const total = ref(0) // 列表的总页数
 const list = ref([]) // 列表的数据
 const queryParams = ref<{
+  // TODO @dbh52:这个 ref 类型定义可以去掉哈。之前定义的原因,是因为 idea 报错了;默认 idea 可以推导出类型
   pageNo: number
   pageSize: number
   contactStatus: number | undefined
@@ -149,6 +150,7 @@ const CONTACT_STATUS = [
 ]
 
 const SCENE_TYPES = [
+  // TODO 芋艿:貌似可以搞成全局枚举
   { label: '我负责的', value: 1 },
   { label: '我跟进的', value: 2 },
   { label: '我参与的', value: 3 },
@@ -182,6 +184,7 @@ const resetQuery = (func: Function | undefined = undefined) => {
     contactStatus: 1,
     sceneType: 1
   }
+  // TODO @dbh52:这里的 func 是不是可以去掉哈;
   func && func()
   handleQuery()
 }
diff --git a/src/views/crm/permission/components/PermissionList.vue b/src/views/crm/permission/components/PermissionList.vue
index f25f6598..b098f261 100644
--- a/src/views/crm/permission/components/PermissionList.vue
+++ b/src/views/crm/permission/components/PermissionList.vue
@@ -1,103 +1,112 @@
 <template>
   <!-- 操作栏 -->
-  <el-row justify="end">
-    <el-button @click="openForm">
-      <Icon class="mr-5px" icon="fluent:people-team-add-20-filled" />
-      添加团队成员
+  <el-row v-if="showAction" justify="end">
+    <el-button v-if="validateOwnerUser" type="primary" @click="openForm">
+      <Icon class="mr-5px" icon="ep:plus" />
+      新增
     </el-button>
-    <el-button @click="handleUpdate">
+    <el-button v-if="validateOwnerUser" @click="handleUpdate">
       <Icon class="mr-5px" icon="ep:edit" />
       编辑
     </el-button>
-    <el-button @click="handleDelete">
+    <el-button v-if="validateOwnerUser" @click="handleDelete">
       <Icon class="mr-5px" icon="ep:delete" />
       移除
     </el-button>
-    <el-button type="danger" @click="handleQuit"> 退出团队</el-button>
+    <el-button v-if="!validateOwnerUser && list.length > 0" type="danger" @click="handleQuit">
+      退出团队
+    </el-button>
   </el-row>
-
-  <!-- 列表 -->
-  <ContentWrap class="mt-10px">
-    <el-table
-      v-loading="loading"
-      :data="list"
-      :show-overflow-tooltip="true"
-      :stripe="true"
-      @selection-change="handleSelectionChange"
-    >
-      <!-- TODO @puhui999:负责人不允许选中 -->
-      <el-table-column type="selection" width="55" />
-      <el-table-column align="center" label="姓名" prop="nickname" />
-      <el-table-column align="center" label="部门" prop="deptName" />
-      <el-table-column align="center" label="岗位" prop="postNames" />
-      <el-table-column align="center" label="权限" prop="level">
-        <template #default="{ row }">
-          <dict-tag :type="DICT_TYPE.CRM_PERMISSION_LEVEL" :value="row.level" />
-        </template>
-      </el-table-column>
-      <el-table-column
-        :formatter="dateFormatter"
-        align="center"
-        label="加入时间"
-        prop="createTime"
-      />
-    </el-table>
-  </ContentWrap>
+  <!-- 团队成员展示 -->
+  <el-table
+    ref="elTableRef"
+    v-loading="loading"
+    :data="list"
+    :show-overflow-tooltip="true"
+    :stripe="true"
+    class="mt-20px"
+    @selection-change="handleSelectionChange"
+  >
+    <el-table-column type="selection" width="55" />
+    <el-table-column align="center" label="姓名" prop="nickname" />
+    <el-table-column align="center" label="部门" prop="deptName" />
+    <el-table-column align="center" label="岗位" prop="postNames" />
+    <el-table-column align="center" label="权限级别" prop="level">
+      <template #default="{ row }">
+        <dict-tag :type="DICT_TYPE.CRM_PERMISSION_LEVEL" :value="row.level" />
+      </template>
+    </el-table-column>
+    <el-table-column :formatter="dateFormatter" align="center" label="加入时间" prop="createTime" />
+  </el-table>
 
   <!-- 表单弹窗:添加/修改 -->
   <CrmPermissionForm ref="formRef" @success="getList" />
 </template>
 <script lang="ts" setup>
 import { dateFormatter } from '@/utils/formatTime'
-import { DICT_TYPE } from '@/utils/dict'
+import { ElTable } from 'element-plus'
 import * as PermissionApi from '@/api/crm/permission'
-import { PermissionLevelEnum } from '@/api/crm/permission'
 import { useUserStoreWithOut } from '@/store/modules/user'
 import CrmPermissionForm from './PermissionForm.vue'
+import { DICT_TYPE } from '@/utils/dict'
 
 defineOptions({ name: 'CrmPermissionList' })
-const props = defineProps<{
-  bizType: number // 业务类型
-  bizId: number // 业务编号
-}>()
 
 const message = useMessage() // 消息
+
+const props = defineProps<{
+  bizType: number // 模块类型
+  bizId: number | undefined // 模块数据编号
+  showAction: boolean //是否展示操作按钮
+}>()
 const loading = ref(true) // 列表的加载中
 const list = ref<PermissionApi.PermissionVO[]>([]) // 列表的数据
+const formData = ref({
+  ownerUserId: 0
+})
+const userStore = useUserStoreWithOut() // 用户信息缓存
 
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
   try {
-    list.value = await PermissionApi.getPermissionList({
+    const data = await PermissionApi.getPermissionList({
       bizType: props.bizType,
       bizId: props.bizId
     })
+    list.value = data
+    const permission = list.value.find(
+      (item) =>
+        item.userId === userStore.getUser.id &&
+        item.level === PermissionApi.PermissionLevelEnum.OWNER
+    )
+    if (permission) {
+      formData.value.ownerUserId = userStore.getUser.id
+    }
   } finally {
     loading.value = false
   }
 }
-
-/** 选中团队成员 */
 const multipleSelection = ref<PermissionApi.PermissionVO[]>([]) // 选择的团队成员
+const elTableRef = ref<InstanceType<typeof ElTable>>()
 const handleSelectionChange = (val: PermissionApi.PermissionVO[]) => {
+  if (val.findIndex((item) => item.level === PermissionApi.PermissionLevelEnum.OWNER) !== -1) {
+    message.warning('不能选择负责人!')
+    elTableRef.value?.clearSelection()
+    return
+  }
   multipleSelection.value = val
 }
 
-/** 添加团队成员 */
-const formRef = ref<InstanceType<typeof CrmPermissionForm>>() // 权限表单 Ref
-const openForm = () => {
-  formRef.value?.open('create', props.bizType, props.bizId)
-}
-
 /** 编辑团队成员 */
+const formRef = ref<InstanceType<typeof CrmPermissionForm>>() // 权限表单 Ref
 const handleUpdate = () => {
   if (multipleSelection.value?.length === 0) {
     message.warning('请先选择团队成员后操作!')
     return
   }
-  const ids = multipleSelection.value?.map((item) => item.id) as number[]
-  formRef.value?.open('update', props.bizType, props.bizId, ids)
+  const ids = multipleSelection.value?.map((item) => item.id) as unknown as number[]
+  formRef.value?.open('update', props.bizType, props.bizId!, ids)
 }
 
 /** 移除团队成员 */
@@ -106,36 +115,75 @@ const handleDelete = async () => {
     message.warning('请先选择团队成员后操作!')
     return
   }
-  await message.delConfirm('是否删除选择的团队成员?')
+  await message.delConfirm()
   const ids = multipleSelection.value?.map((item) => item.id)
   await PermissionApi.deletePermissionBatch({
     bizType: props.bizType,
     bizId: props.bizId,
     ids
   })
-  message.success('删除成功')
 }
 
+/** 添加团队成员 */
+const openForm = () => {
+  formRef.value?.open('create', props.bizType, props.bizId!)
+}
+
+// 校验负责人权限和编辑权限
+const validateOwnerUser = ref(false)
+const validateWrite = ref(false)
+const isPool = ref(false)
+watch(
+  list,
+  (newArr) => {
+    isPool.value = false
+    if (newArr?.length > 0) {
+      isPool.value = !list.value.some(
+        (item) => item.level === PermissionApi.PermissionLevelEnum.OWNER
+      )
+      validateOwnerUser.value = false
+      validateWrite.value = false
+      const userId = userStore.getUser?.id
+      list.value
+        .filter((item) => item.userId === userId)
+        .forEach((item) => {
+          if (item.level === PermissionApi.PermissionLevelEnum.OWNER) {
+            validateOwnerUser.value = true
+            validateWrite.value = true
+          } else if (item.level === PermissionApi.PermissionLevelEnum.WRITE) {
+            validateWrite.value = true
+          }
+        })
+    } else {
+      isPool.value = true
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+defineExpose({ openForm, validateOwnerUser, validateWrite, isPool })
 /** 退出团队 */
-const userStore = useUserStoreWithOut() // 用户信息缓存
 const handleQuit = async () => {
   const permission = list.value.find(
-    (item) => item.userId === userStore.getUser.id && item.level === PermissionLevelEnum.OWNER
+    (item) =>
+      item.userId === userStore.getUser.id && item.level === PermissionApi.PermissionLevelEnum.OWNER
   )
   if (permission) {
     message.warning('负责人不能退出团队!')
     return
   }
-  await message.confirm('确认退出团队吗?')
   const userPermission = list.value.find((item) => item.userId === userStore.getUser.id)
-  await PermissionApi.deleteSelfPermission(userPermission?.id)
+  if (userPermission) {
+    await PermissionApi.deleteSelfPermission(userPermission.id!)
+  }
 }
 
-/** 监听打开的 bizId + bizType,从而加载最新的列表 */
 watch(
-  () => [props.bizId, props.bizType],
-  (val) => {
-    if (!val[0]) {
+  () => props.bizId,
+  (bizId) => {
+    if (!bizId) {
       return
     }
     getList()
diff --git a/src/views/crm/permission/components/TransferForm.vue b/src/views/crm/permission/components/TransferForm.vue
new file mode 100644
index 00000000..c9f5eed1
--- /dev/null
+++ b/src/views/crm/permission/components/TransferForm.vue
@@ -0,0 +1,123 @@
+<!-- 转移数据的表单弹窗,目前主要用于 CRM 客户、商机等详情界面 -->
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="30%">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="150px"
+    >
+      <el-form-item label="选择新负责人" prop="newOwnerUserId">
+        <el-select v-model="formData.newOwnerUserId">
+          <el-option
+            v-for="item in userOptions"
+            :key="item.id"
+            :label="item.nickname"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="老负责人">
+        <el-radio-group
+          v-model="oldOwnerHandler"
+          @change="formData.oldOwnerPermissionLevel = undefined"
+        >
+          <el-radio :label="false" size="large">移除</el-radio>
+          <el-radio :label="true" size="large">加入团队</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item v-if="oldOwnerHandler" label="老负责人权限级别" prop="oldOwnerPermissionLevel">
+        <el-radio-group v-model="formData.oldOwnerPermissionLevel">
+          <template
+            v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PERMISSION_LEVEL)"
+            :key="dict.value"
+          >
+            <el-radio v-if="dict.value != PermissionLevelEnum.OWNER" :label="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </template>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <!-- TODO 转移客户时,需要额外有【联系人】【商机】【合同】的 checkbox 选择 -->
+    <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 UserApi from '@/api/system/user'
+import type { TransferReqVO } from '@/api/crm/customer'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { PermissionLevelEnum } from '@/api/crm/permission'
+
+defineOptions({ name: 'CrmTransferForm' })
+
+const message = useMessage() // 消息弹窗
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
+const oldOwnerHandler = ref(false) // 老负责人的处理方式
+const formData = ref<TransferReqVO>({
+  id: undefined, // 客户编号
+  newOwnerUserId: undefined, // 新负责人的用户编号
+  oldOwnerPermissionLevel: undefined // 老负责人加入团队后的权限级别
+})
+const formRules = reactive({
+  newOwnerUserId: [{ required: true, message: '新负责人不能为空', trigger: 'blur' }],
+  oldOwnerPermissionLevel: [
+    { required: true, message: '老负责人加入团队后的权限级别不能为空', trigger: 'blur' }
+  ]
+})
+const formRef = ref() // 表单 Ref
+const transferFuncRef = ref<Function>(() => {}) // 转移所需回调
+
+/** 打开弹窗 */
+const open = async (title: string, bizId: number, transferFunc: Function) => {
+  dialogVisible.value = true
+  dialogTitle.value = title
+  transferFuncRef.value = transferFunc
+  resetForm()
+  formData.value.id = bizId
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value
+    await transferFuncRef.value(unref(data))
+    message.success(dialogTitle.value + '成功')
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formRef.value?.resetFields()
+  formData.value = {
+    id: undefined, // 客户编号
+    newOwnerUserId: undefined, // 新负责人的用户编号
+    oldOwnerPermissionLevel: undefined // 老负责人加入团队后的权限级别
+  }
+}
+onMounted(async () => {
+  // 获得用户列表
+  // TODO 芋艿:用户列表的选择组件
+  userOptions.value = await UserApi.getSimpleUserList()
+})
+</script>
diff --git a/src/views/crm/product/ProductForm.vue b/src/views/crm/product/ProductForm.vue
index e12af4c8..76ca7778 100644
--- a/src/views/crm/product/ProductForm.vue
+++ b/src/views/crm/product/ProductForm.vue
@@ -47,7 +47,7 @@
           <el-form-item label="产品单位" prop="unit">
             <el-select v-model="formData.unit" class="w-1/1" placeholder="请选择单位">
               <el-option
-                v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
+                v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PRODUCT_UNIT)"
                 :key="dict.value"
                 :label="dict.label"
                 :value="dict.value"
diff --git a/src/views/crm/product/detail/ProductDetailsHeader.vue b/src/views/crm/product/detail/ProductDetailsHeader.vue
index 356f1dbc..34c94da8 100644
--- a/src/views/crm/product/detail/ProductDetailsHeader.vue
+++ b/src/views/crm/product/detail/ProductDetailsHeader.vue
@@ -22,7 +22,7 @@
         {{ productCategoryList?.find((c) => c.id === product.categoryId)?.name }}
       </el-descriptions-item>
       <el-descriptions-item label="产品单位">
-        <dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="product.unit" />
+        <dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="product.unit" />
       </el-descriptions-item>
       <el-descriptions-item label="产品价格">{{ fenToYuan(product.price) }}元</el-descriptions-item>
       <el-descriptions-item label="产品编码">{{ product.no }}</el-descriptions-item>
diff --git a/src/views/crm/product/detail/ProductDetailsInfo.vue b/src/views/crm/product/detail/ProductDetailsInfo.vue
index 85046b85..ba9e89af 100644
--- a/src/views/crm/product/detail/ProductDetailsInfo.vue
+++ b/src/views/crm/product/detail/ProductDetailsInfo.vue
@@ -14,10 +14,10 @@
             {{ productCategoryList?.find((c) => c.id === product.categoryId)?.name }}
           </el-descriptions-item>
           <el-descriptions-item label="是否上下架">
-            <dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="product.status"/>
+            <dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="product.status" />
           </el-descriptions-item>
           <el-descriptions-item label="单位">
-            <dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="product.unit"/>
+            <dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="product.unit" />
           </el-descriptions-item>
         </el-descriptions>
       </el-collapse-item>
@@ -25,12 +25,12 @@
   </ContentWrap>
 </template>
 <script setup lang="ts">
-import {DICT_TYPE} from '@/utils/dict'
+import { DICT_TYPE } from '@/utils/dict'
 import * as ProductApi from '@/api/crm/product'
-import {fenToYuan} from '@/utils'
+import { fenToYuan } from '@/utils'
 import * as ProductCategoryApi from '@/api/crm/product/productCategory'
 
-const {product} = defineProps<{
+const { product } = defineProps<{
   product: ProductApi.ProductVO
 }>()
 
diff --git a/src/views/crm/product/index.vue b/src/views/crm/product/index.vue
index 5100d7f4..ca9c6cda 100644
--- a/src/views/crm/product/index.vue
+++ b/src/views/crm/product/index.vue
@@ -60,7 +60,7 @@
       <el-table-column label="产品类型" align="center" prop="categoryName" width="160" />
       <el-table-column label="产品单位" align="center" prop="unit">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="scope.row.unit" />
+          <dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" />
         </template>
       </el-table-column>
       <el-table-column label="产品编码" align="center" prop="no" />