Browse Source

!322 crm-团队成员:完善团队成员通用组件封装
Merge pull request !322 from puhui999/dev-to-dev

芋道源码 1 year ago
parent
commit
2e01944df9

+ 48 - 0
src/api/crm/permission/index.ts

@@ -0,0 +1,48 @@
+import request from '@/config/axios'
+
+export interface PermissionVO {
+  id?: number // 数据权限编号
+  userId: number | undefined // 用户编号
+  bizType: number | undefined // Crm 类型
+  bizId: number | undefined // Crm 类型数据编号
+  level: number | undefined // 权限级别
+  deptName?: string // 部门名称
+  nickname?: string // 用户昵称
+  postNames?: string // 岗位名称数组
+  createTime?: Date
+}
+
+// 查询团队成员列表
+export const getPermissionList = async (params) => {
+  return await request.get({ url: `/crm/permission/list`, params })
+}
+
+// 新增团队成员
+export const createPermission = async (data: PermissionVO) => {
+  return await request.post({ url: `/crm/permission/add`, data })
+}
+
+// 修改团队成员权限级别
+export const updatePermission = async (data) => {
+  return await request.put({ url: `/crm/permission/update`, data })
+}
+
+// 删除团队成员
+export const deletePermission = async (params) => {
+  return await request.delete({ url: '/crm/permission/delete', params })
+}
+
+// 退出团队
+export const quitTeam = async (id) => {
+  return await request.delete({ url: '/crm/permission/quit-team?id=' + id })
+}
+
+// 领取公海数据
+export const receive = async (data: { bizType: number; bizId: number }) => {
+  return await request.put({ url: `/crm/permission/receive`, data })
+}
+
+// 数据放入公海
+export const putPool = async (data: { bizType: number; bizId: number }) => {
+  return await request.put({ url: `/crm/permission/put-pool`, data })
+}

+ 4 - 0
src/config/axios/service.ts

@@ -217,6 +217,10 @@ const refreshToken = async () => {
 const handleAuthorized = () => {
   const { t } = useI18n()
   if (!isRelogin.show) {
+    // 如果已经到重新登录页面则不进行弹窗提示
+    if (window.location.href.includes('login?redirect=')) {
+      return
+    }
     isRelogin.show = true
     ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
       showCancelButton: false,

+ 112 - 0
src/views/crm/components/CrmPermissionForm.vue

@@ -0,0 +1,112 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="30%">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+    >
+      <el-form-item v-if="formType === 'create'" label="选择人员" prop="userId">
+        <el-select v-model="formData.userId">
+          <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="权限级别" prop="level">
+        <el-radio-group v-model="formData.level">
+          <el-radio :label="CrmPermissionLevelEnum.READ">只读</el-radio>
+          <el-radio :label="CrmPermissionLevelEnum.WRITE">读写</el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </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 * as UserApi from '@/api/system/user'
+import * as PermissionApi from '@/api/crm/permission'
+import { CrmPermissionLevelEnum } from './index'
+
+defineOptions({ name: 'CrmPermissionForm' })
+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 userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
+const formData = ref<PermissionApi.PermissionVO & { ids?: number[] }>({
+  userId: undefined, // 用户编号
+  bizType: undefined, // Crm 类型
+  bizId: undefined, // Crm 类型数据编号
+  level: undefined // 权限级别
+})
+const formRules = reactive({
+  userId: [{ required: true, message: '人员不能为空', trigger: 'blur' }],
+  level: [{ required: true, message: '权限级别不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: 'create' | 'update', bizType: number, bizId: number, ids?: number[]) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type) + '团队成员'
+  formType.value = type
+  resetForm(bizType, bizId)
+  // 修改时,设置数据
+  if (ids) {
+    formData.value.ids = ids
+  }
+}
+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
+    if (formType.value === 'create') {
+      await PermissionApi.createPermission(unref(data))
+      message.success(t('common.createSuccess'))
+    } else {
+      await PermissionApi.updatePermission(unref(data))
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = (bizType: number, bizId: number) => {
+  formRef.value?.resetFields()
+  formData.value = {
+    userId: undefined, // 用户编号
+    bizType, // Crm 类型
+    bizId, // Crm 类型数据编号
+    level: undefined // 权限级别
+  }
+}
+onMounted(async () => {
+  // 获得用户列表
+  userOptions.value = await UserApi.getSimpleUserList()
+})
+</script>

+ 147 - 0
src/views/crm/components/CrmTeamList.vue

@@ -0,0 +1,147 @@
+<template>
+  <!-- 操作栏 -->
+  <el-row justify="end">
+    <el-button type="primary" @click="handleAdd">
+      <Icon class="mr-5px" icon="ep:plus" />
+      新增
+    </el-button>
+    <el-button @click="handleEdit">
+      <Icon class="mr-5px" icon="ep:edit" />
+      编辑
+    </el-button>
+    <el-button @click="handleRemove">
+      <Icon class="mr-5px" icon="ep:delete" />
+      移除
+    </el-button>
+    <el-button type="danger" @click="handleQuit"> 退出团队</el-button>
+  </el-row>
+  <!--  团队成员展示 -->
+  <el-table
+    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 }">
+        <el-tag>{{ getLevelName(row.level) }}</el-tag>
+      </template>
+    </el-table-column>
+    <el-table-column :formatter="dateFormatter" align="center" label="加入时间" prop="createTime" />
+  </el-table>
+  <CrmPermissionForm ref="crmPermissionFormRef" />
+</template>
+<script lang="ts" setup>
+import { dateFormatter } from '@/utils/formatTime'
+import { ElTable } from 'element-plus'
+import * as PermissionApi from '@/api/crm/permission'
+import { useUserStoreWithOut } from '@/store/modules/user'
+import CrmPermissionForm from './CrmPermissionForm.vue'
+import { CrmPermissionLevelEnum } from './index'
+
+defineOptions({ name: 'CrmTeam' })
+const props = defineProps<{
+  bizType: number
+  bizId: number
+}>()
+const loading = ref(true) // 列表的加载中
+const list = ref<PermissionApi.PermissionVO[]>([
+  // TODO 测试数据
+  {
+    id: 1, // 数据权限编号
+    userId: 1, // 用户编号
+    bizType: 1, // Crm 类型
+    bizId: 1, // Crm 类型数据编号
+    level: 1, // 权限级别
+    deptName: '研发部门', // 部门名称
+    nickname: '芋道源码', // 用户昵称
+    postNames: '全栈开发工程师', // 岗位名称数组
+    createTime: new Date()
+  }
+]) // 列表的数据
+const getList = async () => {
+  loading.value = true
+  try {
+    const res = await PermissionApi.getPermissionList({
+      bizType: props.bizType,
+      bizId: props.bizId
+    })
+    list.value = res
+  } finally {
+    loading.value = false
+  }
+}
+/**
+ * 获得权限级别名称
+ * @param level 权限级别
+ */
+const getLevelName = computed(() => (level: number) => {
+  switch (level) {
+    case CrmPermissionLevelEnum.OWNER:
+      return '负责人'
+    case CrmPermissionLevelEnum.READ:
+      return '只读'
+    case CrmPermissionLevelEnum.WRITE:
+      return '读写'
+    default:
+      break
+  }
+})
+const multipleSelection = ref<PermissionApi.PermissionVO[]>([])
+const handleSelectionChange = (val: PermissionApi.PermissionVO[]) => {
+  multipleSelection.value = val
+}
+const message = useMessage()
+const crmPermissionFormRef = ref<InstanceType<typeof CrmPermissionForm>>()
+const handleEdit = () => {
+  if (multipleSelection.value?.length === 0) {
+    message.warning('请先选择团队成员后操作!')
+    return
+  }
+  const ids = multipleSelection.value?.map((item) => item.id)
+  crmPermissionFormRef.value?.open('update', props.bizType, props.bizId, ids)
+}
+const handleRemove = async () => {
+  if (multipleSelection.value?.length === 0) {
+    message.warning('请先选择团队成员后操作!')
+    return
+  }
+  await message.delConfirm()
+  const ids = multipleSelection.value?.map((item) => item.id)
+  await PermissionApi.deletePermission({
+    bizType: props.bizType,
+    bizId: props.bizId,
+    ids
+  })
+}
+const handleAdd = () => {
+  crmPermissionFormRef.value?.open('create', props.bizType, props.bizId)
+}
+
+const userStore = useUserStoreWithOut()
+const handleQuit = async () => {
+  const permission = list.value.find(
+    (item) => item.userId === userStore.getUser.id && item.level === CrmPermissionLevelEnum.OWNER
+  )
+  if (permission) {
+    message.warning('负责人不能退出团队!')
+    return
+  }
+  const userPermission = list.value.find((item) => item.userId === userStore.getUser.id)
+  await PermissionApi.quitTeam(userPermission?.id)
+}
+
+watch(
+  () => props.bizId,
+  () => {
+    getList()
+  },
+  { immediate: true, deep: true }
+)
+</script>

+ 17 - 0
src/views/crm/components/index.ts

@@ -0,0 +1,17 @@
+import CrmTeam from './CrmTeamList.vue'
+
+enum CrmBizTypeEnum {
+  CRM_LEADS = 1, // 线索
+  CRM_CUSTOMER = 2, // 客户
+  CRM_CONTACTS = 3, // 联系人
+  CRM_BUSINESS = 5, // 商机
+  CRM_CONTRACT = 6 // 合同
+}
+
+enum CrmPermissionLevelEnum {
+  OWNER = 1, // 负责人
+  READ = 2, // 读
+  WRITE = 3 // 写
+}
+
+export { CrmTeam, CrmBizTypeEnum, CrmPermissionLevelEnum }

+ 57 - 46
src/views/crm/customer/index.vue

@@ -2,36 +2,36 @@
   <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="name">
         <el-input
           v-model="queryParams.name"
-          placeholder="请输入客户名称"
+          class="!w-240px"
           clearable
+          placeholder="请输入客户名称"
           @keyup.enter="handleQuery"
-          class="!w-240px"
         />
       </el-form-item>
       <el-form-item label="手机" prop="mobile">
         <el-input
           v-model="queryParams.mobile"
-          placeholder="请输入手机"
+          class="!w-240px"
           clearable
+          placeholder="请输入手机"
           @keyup.enter="handleQuery"
-          class="!w-240px"
         />
       </el-form-item>
       <el-form-item label="所属行业" prop="industryId">
         <el-select
           v-model="queryParams.industryId"
-          placeholder="请选择所属行业"
-          clearable
           class="!w-240px"
+          clearable
+          placeholder="请选择所属行业"
         >
           <el-option
             v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
@@ -44,9 +44,9 @@
       <el-form-item label="客户等级" prop="level">
         <el-select
           v-model="queryParams.level"
-          placeholder="请选择客户等级"
-          clearable
           class="!w-240px"
+          clearable
+          placeholder="请选择客户等级"
         >
           <el-option
             v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
@@ -59,9 +59,9 @@
       <el-form-item label="客户来源" prop="source">
         <el-select
           v-model="queryParams.source"
-          placeholder="请选择客户来源"
-          clearable
           class="!w-240px"
+          clearable
+          placeholder="请选择客户来源"
         >
           <el-option
             v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
@@ -72,19 +72,27 @@
         </el-select>
       </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:customer: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:customer:create']" type="primary" @click="openForm('create')">
+          <Icon class="mr-5px" icon="ep:plus" />
+          新增
         </el-button>
         <el-button
-          type="success"
+          v-hasPermi="['crm:customer:export']"
+          :loading="exportLoading"
           plain
+          type="success"
           @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['crm:customer:export']"
         >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
+          <Icon class="mr-5px" icon="ep:download" />
+          导出
         </el-button>
       </el-form-item>
     </el-form>
@@ -92,77 +100,77 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="客户名称" align="center" prop="name" width="160" />
-      <el-table-column label="所属行业" align="center" prop="industryId" width="120">
+    <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" />
+      <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 label="客户来源" align="center" prop="source" width="100">
+      <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 label="客户等级" align="center" prop="level" width="120">
+      <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 label="手机" align="center" prop="mobile" width="120" />
-      <el-table-column label="详细地址" align="center" prop="detailAddress" width="200" />
-      <el-table-column label="负责人" align="center" prop="ownerUserName" />
-      <el-table-column label="所属部门" align="center" prop="ownerUserDept" />
-      <el-table-column label="创建人" align="center" prop="creatorName" />
+      <el-table-column align="center" label="手机" prop="mobile" width="120" />
+      <el-table-column align="center" label="详细地址" prop="detailAddress" width="200" />
+      <el-table-column align="center" label="负责人" prop="ownerUserName" />
+      <el-table-column align="center" label="所属部门" prop="ownerUserDept" />
+      <el-table-column align="center" label="创建人" prop="creatorName" />
       <el-table-column
-        label="创建时间"
+        :formatter="dateFormatter"
         align="center"
+        label="创建时间"
         prop="createTime"
-        :formatter="dateFormatter"
         width="180px"
       />
-      <el-table-column label="成交状态" align="center" prop="dealStatus">
+      <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
-        label="下次联系时间"
+        :formatter="dateFormatter"
         align="center"
+        label="下次联系时间"
         prop="contactNextTime"
-        :formatter="dateFormatter"
         width="180px"
       />
       <el-table-column
-        label="最后跟进时间"
+        :formatter="dateFormatter"
         align="center"
+        label="最后跟进时间"
         prop="contactLastTime"
-        :formatter="dateFormatter"
         width="180px"
       />
-      <el-table-column label="锁定状态" align="center" prop="lockStatus">
+      <el-table-column align="center" label="锁定状态" prop="lockStatus">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.lockStatus" />
         </template>
       </el-table-column>
       <!--  TODO @wanwan 距进入公海天数    -->
-      <el-table-column label="操作" align="center" min-width="150" fixed="right">
+      <el-table-column align="center" fixed="right" label="操作" min-width="150">
         <template #default="scope">
           <el-button link type="primary" @click="openDetail(scope.row.id)">详情</el-button>
           <el-button
+            v-hasPermi="['crm:customer:update']"
             link
             type="primary"
             @click="openForm('update', scope.row.id)"
-            v-hasPermi="['crm:customer:update']"
           >
             编辑
           </el-button>
           <el-button
+            v-hasPermi="['crm:customer:delete']"
             link
             type="danger"
             @click="handleDelete(scope.row.id)"
-            v-hasPermi="['crm:customer:delete']"
           >
             删除
           </el-button>
@@ -171,23 +179,26 @@
     </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>
+  <!-- TODO 方便查看效果 -->
+  <CrmTeam :biz-id="1" :biz-type="CrmBizTypeEnum.CRM_CUSTOMER" />
 
   <!-- 表单弹窗:添加/修改 -->
   <CustomerForm ref="formRef" @success="getList" />
 </template>
 
-<script setup lang="ts">
-import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
+<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'
 import CustomerForm from './CustomerForm.vue'
+import { CrmBizTypeEnum, CrmTeam } from '@/views/crm/components'
 
 defineOptions({ name: 'CrmCustomer' })