Jelajahi Sumber

提交客户管家组织架构(基本的添加部门、人员操作)

Lijy 1 tahun lalu
induk
melakukan
d940665431

+ 1 - 3
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/login.vue

@@ -88,9 +88,7 @@ const login = (formEl: FormInstance | undefined) => {
         loginLoading.value = false;
         router.push(res.data?.moduleList[0].path);
       }, 1000)
-      loginLoading.value = false;
-    }).catch(err => {
-      //console.log(err)
+    }).catch(_err => {
       loginLoading.value = false;
     })
     return

+ 9 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/api.ts

@@ -0,0 +1,9 @@
+export const MOD = "/team"
+export const GET_DATA_LIST = '/user/getEmployeeList'
+export const GET_ROUTELIST = '/permission/getFrontRoleList'
+export const GET_DEPTLIST = '/department/list'
+export const GET_USERLIST = '/user/getSimpleActiveUserList'
+export const GET_ADDDEPT = '/department/add'
+export const DETELE_DEPT = '/department/delete'
+export const ADD_USER = '/user/insertUser'
+export const GET_USERINFO = '/user/getUserInfo'

+ 444 - 3
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/index.vue

@@ -1,11 +1,452 @@
 <template>
-  <div>
-    团队
+  <div class="h-full flex flex-col teamstyle">
+    <!-- 头部 -->
+    <div class="bg-white flex justify-between team-header">
+      <div class="flex items-center">
+        <el-link type="primary" class="text-nowrap mr-20" :icon="CirclePlusFilled"
+          @click="dialogFromCli('addDeptDialogVisible')">创建部门</el-link>
+        <el-link class="text-nowrap textFont textFont mr-10" type="primary" :icon="Edit"
+          @click="updateDepartment('addDeptDialogVisible')">{{ deptListItem.label || '全部人员' }}</el-link>
+        <span class="textSpan">共 0 人</span>
+      </div>
+      <div class="teamForm flex items-center">
+        <el-input v-model="teamForm.keyword" style="max-width: 650px" size="default" placeholder="请输入姓名搜索" class="mr-6">
+          <template #prepend>
+            <el-select v-model="teamForm.matchingType" style="width: 80px">
+              <el-option label="姓名" :value="0" />
+              <el-option label="电话" :value="1" />
+              <el-option label="工号" :value="2" />
+            </el-select>
+          </template>
+          <template #append>
+            <el-button :icon="Search" />
+          </template>
+        </el-input>
+
+        <div class="formItem mr-6 flex items-center">
+          <div class="text-nowrap">状态:</div>
+          <el-select v-model="teamForm.status" placeholder="请选择" size="default" style="width: 100px">
+            <el-option v-for="item in stateOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </div>
+        <div class="formItem mr-6 flex items-center">
+          <div class="text-nowrap">角色:</div>
+          <el-select v-model="teamForm.roleId" placeholder="请选择" size="default" style="width: 150px">
+            <el-option v-for="item in roleList" :key="item.id" :label="item.rolename" :value="item.id" />
+          </el-select>
+        </div>
+
+        <el-dropdown>
+          <el-button type="primary">
+            更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
+          </el-button>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item @click="addPersone(false)">添加人员</el-dropdown-item>
+              <el-dropdown-item>导出人员</el-dropdown-item>
+              <el-dropdown-item>批量导入</el-dropdown-item>
+              <el-dropdown-item>导入薪资</el-dropdown-item>
+              <el-dropdown-item>自定义配置</el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+    </div>
+    <!-- 内容 -->
+    <div class="flex-1 flex">
+      <div class="p-5 w-80 pr-0">
+        <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+          <div class="flex-1 overflow-y-auto const-left">
+            <el-tree style="max-width: 600px" :data="deptList" :props="treeProps" @node-click="treeNode">
+              <template #default="{ node, data }">
+                <div class="flex justify-between treeContent">
+                  <div class="custom-tree-node">
+                    <div class="treeLabel">{{ node.label }}</div>
+                    <div class="treeIcon" v-if="data.id > 0">
+                      <el-link type="primary" :icon="CirclePlus" :underline="false"
+                        @click.stop="dialogFromCli('addDeptDialogVisible', data, true)"></el-link>
+                      <el-link type="primary" :icon="Delete" :underline="false" style="margin-left: 6px;"
+                        @click.stop="deteleDept(data)"></el-link>
+                    </div>
+                  </div>
+                </div>
+              </template>
+            </el-tree>
+          </div>
+        </div>
+      </div>
+      <div class="flex-1 p-5 overflow-auto">
+        <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+          <div class="flex-1 p-3">
+            <el-table ref="multipleTableRef" :data="tableData" v-loading="loadingFrom.tableLoading"
+              style="width: 100%;height: 100%;">
+              <el-table-column type="selection" width="55" />
+              <el-table-column label="姓名" property="name" width="150"></el-table-column>
+              <el-table-column label="手机" property="phone" ></el-table-column>
+              <el-table-column label="工号" property="jobNumber"></el-table-column>
+              <el-table-column label="部门" property="departmentName"></el-table-column>
+              <el-table-column label="角色" property="roleName"></el-table-column>
+              <el-table-column label="创建时间" property="createTime"></el-table-column>
+              <el-table-column label="操作" width="150" fixed="right">
+                <template #default="scope">
+                  <el-button :size="'small'">重置</el-button>
+                  <el-button type="primary" :size="'small'" @click="addPersone(scope.row)">编辑</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+          <div class="flex items-center justify-between p-3 bg-slate-100">
+            <div class="flex">
+              <el-button size="default">取消</el-button>
+              <el-dropdown class="ml-3">
+                <el-button type="primary">
+                  更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
+                </el-button>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item>批量修改部门</el-dropdown-item>
+                    <el-dropdown-item>批量修改角色</el-dropdown-item>
+                    <el-dropdown-item>修正工时所属部门</el-dropdown-item>
+                    <el-dropdown-item>批量启用员工</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </div>
+            <div class="pr-4">
+              <el-pagination layout="total, prev, pager, next, sizes" :total="totalTable" :hide-on-single-page="true" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 新增部门 -->
+    <el-dialog v-model="dialogFrom.addDeptDialogVisible" :title="deptListItem.label || '创建部门'" width="500"
+      :before-close="handleClose">
+      <div>
+        <el-form ref="deptRuleFormRef" style="max-width: 500px" :model="deptForm" :rules="deptRules" label-width="auto"
+          size="large" status-icon>
+          <el-form-item label="部门名称" prop="name">
+            <el-input v-model="deptForm.name" placeholder="请输入部门名称" clearable />
+          </el-form-item>
+          <el-form-item label="主要负责人">
+            <el-select v-model="deptForm.managerId" placeholder="请选择" style="width: 100%" clearable>
+              <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="其他负责人">
+            <el-select v-model="deptForm.otherManagerIds" placeholder="请选择" style="width: 100%" multiple clearable>
+              <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogFrom.addDeptDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="createDepartment(deptRuleFormRef)"
+            v-bind:loading="loadingFrom.deptDialogVisibleLoading">
+            确定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 新增人员 -->
+    <AddPersonnelModal :data="{
+      addPersonnelDialogVisible: dialogFrom.addPersonnelDialogVisible,
+      deptList: deptListUntreated,
+      roleList: roleList,
+      personnelFromData: personnelFromData
+    }" @closeModal="closeModal" @personnelModalConfirm="personnelModalConfirm" />
   </div>
 </template>
 
 <script lang="ts" setup>
+import { ref, reactive, onMounted, onBeforeMount, inject } from 'vue';
+import { dayjs } from 'element-plus'
+import { Search, CirclePlusFilled, Edit, CirclePlus, Delete } from '@element-plus/icons-vue'
+import { FormInstance, FormRules, ElMessageBox } from 'element-plus'
+import { useStore } from '@/store/index'
+import { GET_DATA_LIST, DETELE_DEPT, MOD, GET_USERINFO, GET_ROUTELIST, GET_DEPTLIST, GET_USERLIST, GET_ADDDEPT, ADD_USER } from './api'
+import { post } from "@/utils/request";
+import { getFromValue, updateDepTreeData, resetFromValue } from '@/utils/tools'
+
+// 导入页面
+import AddPersonnelModal from './module/AddPersonnelModal.vue'
+
+const { getFunctionList, getUserInfoVal } = useStore()
+const globalPopup = inject<GlobalPopup>('globalPopup')
+
+// 定义类型
+interface deptRuleForm { // 部门表单类型
+  name: string,
+  id: string | number,
+  parentId: string | number,
+  managerId: string | number,
+  otherManagerIds: string[] | number[],
+}
+
+// 固定数据
+const stateOptions = [{ value: '3', label: '全部' }, { value: '1', label: '在职' }, { value: '0', label: '停用' }]
+
+// 定义变量
+const pagePermission: any = ref([]) // 功能权限
+const loadingFrom = reactive({ // 所有加载状态
+  tableLoading: false,
+  deptDialogVisibleLoading: false
+})
+const dialogFrom: any = reactive({ // 所有弹窗状态
+  addDeptDialogVisible: false,
+  addPersonnelDialogVisible: false
+});
+const totalTable = ref(0) // 表格总数
+const tableData: any = ref([]) // 表格数据
+const roleList: any = ref([]) // 角色列表
+const userList: any = ref([]) // 用户列表
+const deptList: any = ref([]) // 部门数据
+const deptListUntreated: any = ref([]) // 部门数据(未处理)
+const deptListItem: any = ref({}) // 选中的部门数据
+const personnelFromData = ref({}) // 人员表单数据
+const teamForm = reactive({ // 筛选条件表单
+  matchingType: 0,
+  keyword: '',
+  status: '3',
+  pageIndex: 1,
+  pageSize: 20,
+  roleId: '',
+  onlyDirect: '',
+  departmentId: '-1',
+});
+const deptRuleFormRef = ref<FormInstance>() // 表单实例
+const deptForm = reactive<deptRuleForm>({ // 部门表单
+  name: '',
+  id: '',
+  parentId: '',
+  managerId: '',
+  otherManagerIds: [],
+})
+const treeProps = { // 部门树配置
+  children: 'children',
+  label: 'label',
+}
+
+// 定义校验规则
+const deptRules = reactive<FormRules<typeof deptForm>>({ // 部门表单校验规则
+  name: [{ required: true, trigger: 'blur', message: '请输入部门名称' }]
+})
+
+// 定义方法
+function addPersone(item: any) {
+  console.log(item)
+  if(!item) {
+    dialogFrom.addPersonnelDialogVisible = true
+    return
+  } 
+  post(GET_USERINFO, { userId: item.id }).then(res => {
+    const { id, name, phone, jobNumber, roleId, departmentCascade, inductionDate } = res.data
+    let newData = { id, name, phone, jobNumber, roleId, departmentId: 
+      departmentCascade && departmentCascade.split(',').map(Number).reverse(), 
+      inductionDate 
+    }
+    personnelFromData.value = newData
+    dialogFrom.addPersonnelDialogVisible = true
+  })
+}
+
+async function personnelModalConfirm(data: any, modelType: string) {
+  post(ADD_USER, { ...data }).then(res => {
+    if (res.code != 'ok') {
+      dialogFrom[modelType] = false
+      globalPopup?.showError(res.msg)
+      return
+    }
+    dialogFrom[modelType] = false
+    globalPopup?.showSuccess('添加成功')
+    getTableData()
+  }).catch(_err => {
+    dialogFrom[modelType] = false
+  })
+}
+
+function createDepartment(formEl: FormInstance | undefined) {
+  if (!formEl) return
+  let data = getFromValue(deptForm)
+  loadingFrom.deptDialogVisibleLoading = true
+  post(GET_ADDDEPT, { ...deptForm, otherManagerIds: data.otherManagerIds && data.otherManagerIds.join(',') }).then(res => {
+    if (res.code != 'ok') {
+      loadingFrom.deptDialogVisibleLoading = false
+      globalPopup?.showError(res.msg)
+      return
+    }
+    loadingFrom.deptDialogVisibleLoading = false
+    globalPopup?.showSuccess('创建成功')
+    getDeptList()
+    dialogFrom.addDeptDialogVisible = false
+  }).catch(_err => {
+    loadingFrom.deptDialogVisibleLoading = false
+  })
+}
+
+function updateDepartment(type: string) {
+  if (!deptListItem.value.id || deptListItem.value.id <= 0) return
+  const { id, label, parentId, managerId, otherManagerIds } = deptListItem.value
+  console.log(deptListItem.value)
+  let data = { id, name: label, parentId, managerId, otherManagerIds }
+  Object.assign(deptForm, data)
+  dialogFrom[type] = true
+}
+
+function treeNode(item: any) {
+  deptListItem.value = item
+  teamForm.departmentId = item.id
+  getTableData()
+}
+
+function deteleDept(data: any) {
+  console.log(data)
+  ElMessageBox.confirm(
+    `确定删除【${data.label}】部门吗?`, '',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      post(DETELE_DEPT, { id: data.id }).then(res => {
+        if (res.code != 'ok') {
+          globalPopup?.showError(res.msg)
+          return
+        }
+        globalPopup?.showSuccess('删除成功')
+        getDeptList()
+      })
+    })
+}
 
+function getTableData() {
+  loadingFrom.tableLoading = true
+  post(GET_DATA_LIST, { ...teamForm }).then(res => {
+    if (res.code != 'ok') {
+      loadingFrom.tableLoading = false
+      globalPopup?.showError(res.msg)
+      return
+    }
+    loadingFrom.tableLoading = false
+    totalTable.value = res.data.total
+    tableData.value = res.data.records
+  }).catch(_err => {
+    loadingFrom.tableLoading = false
+  })
+}
+
+function getRoleList() {
+  const companyId = getUserInfoVal('companyId') || ''
+  post(GET_ROUTELIST, { companyId }).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    roleList.value = res.data
+  })
+}
+
+function getDeptList() {
+  post(GET_DEPTLIST, {}).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    deptListUntreated.value = updateDepTreeData(res.data, false)
+    deptList.value = updateDepTreeData(res.data, true)
+  })
+}
+
+function getUserList() {
+  post(GET_USERLIST, {}).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    userList.value = res.data
+  })
+}
+
+function dialogFromCli(type: string, data: any = {}, flag: boolean = false) {
+  resetDialog()
+  if (flag) {
+    const { id } = data
+    deptForm.parentId = id
+  }
+  dialogFrom[type] = true
+}
+
+function resetDialog() {
+  let newDeptForm = resetFromValue(deptForm)
+  Object.assign(deptForm, newDeptForm)
+}
+
+function handleClose(done: any) {
+  done()
+}
+
+function closeModal(modelType: string) {
+  dialogFrom[modelType] = false
+}
+
+onBeforeMount(() => {
+  pagePermission.value = getFunctionList(MOD)
+})
+
+onMounted(() => {
+  getRoleList()
+  getUserList()
+  getTableData()
+  getDeptList()
+});
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.teamstyle {
+  .team-header {
+    padding: 0.75rem 1.25rem;
+  }
+
+  .textFont {
+    font-size: 20px;
+  }
+
+  .textSpan {
+    color: $fontGray;
+  }
+}
+
+.const-left {
+  padding: 0.75rem 0;
+
+  .treeContent {
+    width: 87%;
+
+    .custom-tree-node {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 100%;
+    }
+
+    .treeLabel {
+      width: 80%;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .treeIcon {
+      display: flex;
+      align-items: center;
+      justify-self: flex-end;
+    }
+  }
+}
+</style>

+ 107 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/module/AddPersonnelModal.vue

@@ -0,0 +1,107 @@
+<template>
+    <el-dialog v-model="data.addPersonnelDialogVisible" :title="'添加人员'" width="500" :before-close="handleClose">
+        <div>
+            <el-form ref="personnelRuleFormRef" style="max-width: 500px" :model="personnelFrom" :rules="personnelRules"
+                label-width="auto" size="large" status-icon>
+                <el-form-item label="姓名" prop="name">
+                    <el-input v-model="personnelFrom.name" placeholder="请输入姓名" clearable />
+                </el-form-item>
+                <el-form-item label="电话">
+                    <el-input v-model="personnelFrom.phone" placeholder="请输入电话" clearable />
+                </el-form-item>
+                <el-form-item label="工号">
+                    <el-input v-model="personnelFrom.jobNumber" placeholder="请输入工号" clearable />
+                </el-form-item>
+                <el-form-item label="部门">
+                    <el-cascader v-model="personnelFrom.departmentId" :options="data.deptList" placeholder="请选择部门" :props="{ checkStrictly: true }" clearable style="width: 100%" />
+                </el-form-item>
+                <el-form-item label="角色">
+                    <el-select v-model="personnelFrom.roleId" placeholder="请选择角色" size="large">
+                        <el-option v-for="item in data.roleList" :key="item.id" :label="item.rolename" :value="item.id" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="入职时间">
+                    <el-date-picker v-model="personnelFrom.inductionDate" type="date" placeholder="选择入职时间" value-format="YYYY-MM-DD" style="width: 100%" />
+                </el-form-item>
+            </el-form>
+        </div>
+        <template #footer>
+            <div class="dialog-footer">
+                <el-button @click="handleClose">取消</el-button>
+                <el-button type="primary" @click="addPersonel(personnelRuleFormRef)">
+                    确定
+                </el-button>
+            </div>
+        </template>
+    </el-dialog>
+</template>
+<script setup lang="ts">
+import { ref, watch, reactive } from 'vue'
+import { FormRules, FormInstance } from 'element-plus'
+import { getFromValue, resetFromValue } from '@/utils/tools'
+
+const emit = defineEmits(['closeModal', 'personnelModalConfirm']);
+// 定义类型
+interface Props {
+    data: {
+        addPersonnelDialogVisible: boolean,
+        deptList: any,
+        roleList: any,
+        personnelFromData: any
+    };
+}
+
+interface personnelFromType { // 类型定义
+    id: string | number,
+    name: string,
+    phone: string | number,
+    jobNumber: string,
+    roleId: string | number,
+    departmentId: string[] | number[],
+    inductionDate: string,
+}
+const data = ref<Props['data']>({
+    addPersonnelDialogVisible: false,
+    deptList: [],
+    roleList: [],
+    personnelFromData: {}
+})
+const personnelRuleFormRef = ref<FormInstance>() // 表单实例
+const personnelFrom = reactive<personnelFromType>({ // 填写的内容
+    id: '',
+    name: '',
+    phone: '',
+    jobNumber: '',
+    roleId: '',
+    departmentId: [],
+    inductionDate: '',
+});
+
+// 接收参数
+const props = defineProps<Props>();
+
+// 定义校验规则
+const personnelRules = reactive<FormRules<typeof personnelFrom>>({ // 部门表单校验规则
+    name: [{ required: true, trigger: 'blur', message: '请输入姓名' }]
+})
+
+// 定义方法
+function addPersonel(formEl: FormInstance | undefined) {
+    if (!formEl) return
+    let dataForm = getFromValue(personnelFrom)
+    const { departmentId } = dataForm
+    emit('personnelModalConfirm', { ...dataForm, departmentId: departmentId && departmentId[departmentId.length - 1]  }, 'addPersonnelDialogVisible')
+}
+
+// 监听 Props 的变化
+watch(() => props.data, (newValue) => {
+    data.value = newValue
+    Object.assign(personnelFrom, newValue.personnelFromData)
+});
+
+const handleClose = () => {
+    emit('closeModal', 'addPersonnelDialogVisible')
+}
+
+</script>
+<style scoped lang="scss"></style>

+ 12 - 9
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue

@@ -1,21 +1,24 @@
 <template>
-  <div class="bg-gray-200 h-full flex">
+  <div class="h-full flex">
     <div class="p-5 w-80 pr-0">
-      <div class="bg-white w-full h-full p-3 shadow-md rounded-md">
-        线索管理
+      <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+        <div class="flex-1 p-3 overflow-y-auto">
+
+        </div>
+        <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
+          <El-button class="w-full">重置</El-Button>
+          <El-button type="primary" class="w-full">搜索</El-Button>
+        </div>
       </div>
     </div>
-    <div class="flex-1 bg-gray-200 p-5">
+    <div class="flex-1 p-5">
       <div class="bg-white w-full h-full p-3 shadow-md rounded-md">222</div>
     </div>
   </div>
 </template>
 
-
-<script setup lang="ts">
+<script lang="ts" setup>
 
 </script>
 
-
-<style scoped></style>
-
+<style lang="scss" scoped></style>

+ 1 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/store/Store.d.ts

@@ -13,5 +13,6 @@ type SotreActions = {
   setValue(val: any, key: keyof SotreState): void;
   getRouterConfig(path: string): RouteRecordRaw | any;
   getFunctionList(path: string): any[];
+  getUserInfoVal(val: string): any;
   clearStore(): void;
 };

+ 4 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/store/index.ts

@@ -13,12 +13,11 @@ export const useStore = defineStore<
   }),
   getters: {
     getRoutersList() {
-      // 取值
       return this.routers;
     },
     getToken() {
       return this.userInfo?.id || "";
-    },
+    }
   },
   actions: {
     // 方法
@@ -41,6 +40,9 @@ export const useStore = defineStore<
       }
       return config.functionList || [];
     },
+    getUserInfoVal(val) {
+      return this.userInfo[val];
+    },
     clearStore() {
       localStorage.clear();
       sessionStorage.clear();

+ 5 - 3
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts

@@ -45,12 +45,14 @@ export function resetFromValue<T>(formData: T) {
   for (const key in formData) {
     result[key] = '';
   }
+  return result;
 }
 
 /**
- * 对部门数据进行二次处理
- * @param arr 部门数组
- * @returns {any[]}
+ * 更新部门数据
+ * @param arr 部门数据源
+ * @param flag 是否需要添加全部人员和未分配
+ * @returns 
  */
 export function updateDepTreeData(arr: any, flag: boolean = false) {
   const result = []; // 创建一个新数组来存储结果