Browse Source

提交代码

Lijy 6 tháng trước cách đây
mục cha
commit
9474bf1a32

+ 171 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/departmentSelection/departmentSelection.vue

@@ -0,0 +1,171 @@
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject, watchEffect, computed } from 'vue';
+import type { CascaderProps, CascaderNode } from 'element-plus'
+import { cloneDeep, debounce } from 'lodash';
+import { post } from "@/utils/request";
+import { useStore } from '@/store/index'
+import { Emits } from '../type';
+import { storeToRefs } from 'pinia';
+import { updateDepTreeData, generateUniqueId } from '@/utils/tools'
+
+const emit = defineEmits<Emits>();
+const props = defineProps({
+  modelValue: { type: [String, Number, Array, Object, Boolean], required: true },
+  size: { type: String as () => assemblySize, required: true, default: () => 'small' },
+  placeholder: { type: String, required: false, default: () => '请选择' },
+  multiple: { type: Boolean, required: false, default: false },
+  disabled: { type: Boolean, required: false, default: false },
+  clearable: { type: Boolean, required: false, default: true },
+  options: { type: Array as () => any, required: false, default: () => [] },
+  width: { type: String, required: false, default: () => '100%' },
+  isAddChineseCharacters: { type: Boolean, required: false, default: false },
+  props: {
+    type: Object as () => CascaderProps, required: false, default: () => {
+      return {
+        value: 'id',
+        expandTrigger: 'hover' as const,
+        checkStrictly: true
+      }
+    }
+  }
+})
+const { departmentList, userInfo } = storeToRefs(useStore());
+const { setValue } = useStore()
+const departmentVal = ref(props.modelValue); // 响应式绑定 v-model 的值
+const departmentValTwo = ref(props.modelValue);
+const departmentProps = ref<CascaderProps>({})
+const departmantArray = ref<any>([]);
+const visibleFlag = ref<boolean>(false);
+const deptRef = ref<any>(null)
+
+const deptLabel = computed(() => {
+  if (Array.isArray(departmentVal.value)) {
+    const deptId = departmentVal.value[departmentVal.value.length - 1]
+    return findLabelById(departmantArray.value, deptId)
+  }
+  return ''
+})
+
+async function filterMethod(node: CascaderNode, keyword: string) {
+  const { userNameNeedTranslate } = userInfo.value
+  if(userNameNeedTranslate == 0) {
+    return [keyword].includes(node.label)
+  }
+  const { data = [] } = await post(`/department/listAllMemb`, { keyword, cursor: '2' })
+  const keywordList = data.flatMap(extractLabels)
+  return [...keywordList].includes(node.label)
+}
+
+function getDeptList(keyword: string = '') {
+  post(`/department/listAllMemb`, { keyword }).then(res => {
+    const deptList = updateDepTreeData(res.data, props.isAddChineseCharacters)
+    departmantArray.value = deptList
+    if (!keyword) {
+      setValue((deptList || []), 'departmentList')
+    }
+  })
+}
+
+function visibleChange(visible: boolean) { // 下拉框出现/隐藏时触发
+  if (!props.multiple) {
+    setTimeout(() => {
+      departmentVal.value = visible ? [] : cloneDeep(departmentValTwo.value)
+    }, 10)
+  }
+  visibleFlag.value = visible
+}
+
+function updateValue(val: any) { // 值改变的时候触发
+  if (!props.multiple) {
+    deptRef.value.$refs.input.value = '';
+    deptRef.value.togglePopperVisible()
+  }
+  departmentValTwo.value = cloneDeep(val)
+  emit('update:modelValue', departmentVal.value)
+  emit('change', val)
+}
+
+function findLabelById(tree: any, id: any) {
+  for (let node of tree) {
+    if (node.id == id) {
+      return node.label;
+    }
+    if (node.children && node.children.length > 0) {
+      const result: any = findLabelById(node.children, id);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return null;
+}
+
+function extractLabels(node: any) {
+  let labels: any[] = [];
+  if (node.label) {
+    labels.push(node.label);
+  }
+  if (node.children && Array.isArray(node.children)) {
+    node.children.forEach((child: any) => {
+      labels = labels.concat(extractLabels(child));
+    });
+  }
+  return labels;
+}
+
+onMounted(() => {
+  departmentProps.value = props.props
+  if (departmentList.value.length == 0) {
+    if (props.options.length > 0) {
+      departmantArray.value = props.options
+    } else {
+      getDeptList()
+    }
+  } else {
+    departmantArray.value = departmentList.value
+  }
+})
+</script>
+
+<template>
+  <div class="departmentSelectionDiv" :style="`width: ${width}`">
+    <el-cascader v-model="departmentVal" :ref="'deptRef'" :options="departmantArray" :props="departmentProps"
+      :size="size" :clearable="clearable" :placeholder="placeholder" filterable :show-all-levels="false"
+      :class="`departmentSelection ${!multiple && !visibleFlag ? 'clearColor' : ''}`" :style="`width: ${width}`"
+      :filter-method="filterMethod" @visible-change="visibleChange" @change="updateValue">
+      <template #default="{ node, data }">
+        <span>
+          <TextTranslation translationTypes="departmentName" :translationValue="data.label"></TextTranslation>
+        </span>
+      </template>
+    </el-cascader>
+    <!-- 覆盖 -->
+    <div class="coverDept" v-if="!visibleFlag">
+      {{ deptLabel }}
+    </div>
+  </div>
+</template>
+
+<style>
+.clearColor .el-input .el-input__inner {
+  color: transparent !important;
+}
+</style>
+
+<style lang="scss" scoped>
+.departmentSelectionDiv {
+  position: relative;
+  display: inline-block;
+
+  .coverDept {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    left: 6px;
+    background: #fff;
+    color: #303133;
+    font-size: 12px;
+    height: calc(100% - 4px);
+  }
+}
+</style>

+ 0 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/personnelSearch/data.ts


+ 7 - 8
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/personnelSearch/personnelSearch.vue

@@ -2,7 +2,7 @@
 import { ref, reactive, onMounted, inject, watchEffect, computed } from 'vue';
 import { debounce } from 'lodash';
 import { storeToRefs } from 'pinia';
-import { Emits, optionsType } from './type';
+import { Emits, optionsType } from '../type';
 import { useStore } from '@/store/index'
 import { generateUniqueId } from '@/utils/tools'
 import { post, get, uploadFile } from "@/utils/request";
@@ -96,7 +96,11 @@ function updateValue(val: any) { // 值改变的时候触发
 
 onMounted(() => {
   if (personnelList.value.length == 0) {
-    getUserList()
+    if (props.options.length > 0) {
+      personnelArray.value = props.options
+    } else {
+      getUserList()
+    }
   } else {
     personnelArray.value = personnelList.value
   }
@@ -181,7 +185,6 @@ onMounted(() => {
       </div>
     </template>
   </el-select-v2>
-  <span class="aabbcc">aabbcc</span>
 </template>
 
 <style lang="scss" scoped>
@@ -195,7 +198,7 @@ onMounted(() => {
       display: none;
     }
   }
-  
+
   :deep(.el-select__input) {
     margin-left: 0;
   }
@@ -204,8 +207,4 @@ onMounted(() => {
 .setUpInput :deep(.el-input__inner) {
   color: #fff !important;
 }
-
-.aabbcc {
-  color: $modena;
-}
 </style>

+ 234 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/treeSelect/treeSelect.vue

@@ -0,0 +1,234 @@
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject, watchEffect, computed } from 'vue';
+import type { CascaderProps, CascaderNode } from 'element-plus'
+import { cloneDeep, debounce } from 'lodash';
+import { post } from "@/utils/request";
+import { useStore } from '@/store/index'
+import { Emits } from '../type';
+import { storeToRefs } from 'pinia';
+import { updateDepTreeData, generateUniqueId } from '@/utils/tools'
+const emit = defineEmits<Emits>();
+
+const props = defineProps({
+  modelValue: { type: [String, Number, Array, Object, Boolean], required: true },
+  size: { type: String as () => assemblySize, required: true, default: () => 'small' },
+  placeholder: { type: String, required: false, default: () => '请选择' },
+  multiple: { type: Boolean, required: false, default: false }, // 多选
+  anyLevel: { type: Boolean, required: false, default: true }, // 选择任意一级
+  checkStrictly: { type: Boolean, required: false, default: false }, // 是否父子级相互不关联
+  disabled: { type: Boolean, required: false, default: false },
+  clearable: { type: Boolean, required: false, default: true },
+  options: { type: Array as () => any, required: false, default: () => [] },
+  width: { type: String, required: false, default: () => '100%' },
+  isAddChineseCharacters: { type: Boolean, required: false, default: false },
+  props: {
+    type: Object as () => CascaderProps, required: false, default: () => {
+      return {
+        value: 'id',
+        expandTrigger: 'hover' as const,
+        checkStrictly: true
+      }
+    }
+  }
+})
+
+const { departmentList, userInfo } = storeToRefs(useStore());
+const { setValue } = useStore()
+const treeSelectVal = ref(props.modelValue); // 响应式绑定 v-model 的值
+const treeSelectArray = ref<any>([]);
+const visibleFlag = ref(false);
+const selectLoading = ref(false);
+const treeSelectRef = ref();
+const searchCriteria = ref<string[]>([])
+
+const getSelectedLabel = computed(() => {
+  console.log(treeSelectVal.value)
+  if (!props.multiple) {
+    return treeSelectVal.value ? findLabelById(treeSelectArray.value, treeSelectVal.value) : props.placeholder
+  }
+
+  if (props.multiple) {
+    if (Array.isArray(treeSelectVal.value)) {
+      if (treeSelectVal.value.length <= 0) {
+        return props.placeholder
+      }
+
+      return treeSelectVal.value ? findLabelById(treeSelectArray.value, treeSelectVal.value[0]) : props.placeholder
+    }
+  }
+
+  return props.placeholder
+})
+
+const filterMethod = debounce(filterMethods, 500)
+async function filterMethods(val: string) {
+  const { userNameNeedTranslate } = userInfo.value
+  if (val == '') {
+    treeSelectArray.value = departmentList.value
+    selectLoading.value = false
+    searchCriteria.value = []
+    treeSelectRef.value.filter()
+    return
+  }
+  if (userNameNeedTranslate == 0) {
+    searchCriteria.value = [val]
+    setTimeout(() => {
+      treeSelectRef.value.filter()
+    }, 10)
+    return
+  }
+  if (userNameNeedTranslate == 1) {
+    selectLoading.value = true
+    const { data = [] } = await post(`/department/listAllMemb`, { keyword: val })
+    const keywordList = data.flatMap(extractLabels)
+    searchCriteria.value = keywordList
+    console.log(searchCriteria.value)
+    setTimeout(() => {
+      selectLoading.value = false
+      treeSelectRef.value.filter()
+    }, 100)
+  }
+}
+
+function filterNode(_value: string, data: Tree) {
+  const { userNameNeedTranslate } = userInfo.value
+  if (searchCriteria.value.length == 0) {
+    return true
+  }
+  if (userNameNeedTranslate == 0) {
+    return data.label.indexOf(searchCriteria.value[0]) > -1
+  }
+  if (userNameNeedTranslate == 0) {
+    return [...searchCriteria.value].includes(data.label)
+  }
+}
+
+function tagClose(_evt: MouseEvent) {
+  if (Array.isArray(treeSelectVal.value)) {
+    treeSelectVal.value.shift()
+    updateValue(treeSelectVal.value)
+  }
+}
+
+function getDeptList(keyword: string = '') {
+  post(`/department/listAllMemb`, { keyword }).then(res => {
+    const deptList = updateDepTreeData(res.data, props.isAddChineseCharacters)
+    treeSelectArray.value = deptList
+    if (!keyword) {
+      setValue((deptList || []), 'departmentList')
+    }
+  })
+}
+
+function visibleChange(visible: boolean) { // 下拉框出现/隐藏时触发
+  visibleFlag.value = visible
+}
+
+function findLabelById(tree: any, id: any) {
+  for (let node of tree) {
+    if (node.id == id) {
+      return node.label;
+    }
+    if (node.children && node.children.length > 0) {
+      const result: any = findLabelById(node.children, id);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return null;
+}
+
+function extractLabels(node: any) {
+  let labels: any[] = [];
+  if (node.label) {
+    labels.push(node.label);
+  }
+  if (node.children && Array.isArray(node.children)) {
+    node.children.forEach((child: any) => {
+      labels = labels.concat(extractLabels(child));
+    });
+  }
+  return labels;
+}
+
+function updateValue(val: any) { // 值改变的时候触发
+  emit('update:modelValue', treeSelectVal.value)
+  emit('change', val)
+}
+
+onMounted(() => {
+  if (departmentList.value.length == 0) {
+    if (props.options.length > 0) {
+      treeSelectArray.value = props.options
+    } else {
+      getDeptList()
+    }
+  } else {
+    treeSelectArray.value = departmentList.value
+  }
+})
+</script>
+
+<template>
+  <el-tree-select v-model="treeSelectVal" ref="treeSelectRef" :data="treeSelectArray" :size="size"
+    :loading="selectLoading" :clearable="clearable" :placeholder="placeholder" :show-checkbox="anyLevel"
+    :multiple="multiple" node-key="id" :render-after-expand="true" :check-strictly="checkStrictly" filterable
+    collapse-tags :style="`width: ${width}`" :class="`custom-select ${!visibleFlag ? 'setUpInput' : ''}`"
+    @change="updateValue" @visible-change="visibleChange" :filter-method="filterMethod"
+    :filter-node-method="filterNode">
+    <!-- 单选 -->
+    <template #prefix v-if="!multiple">
+      <div style="height: 100%;display: flex;align-items: center;">
+        <div v-if="!visibleFlag" class="selectSingleChoice">
+          <template v-if="getSelectedLabel == placeholder">
+            {{ placeholder }}
+          </template>
+          <template v-else>
+            <span style="color: #303133;">
+              <TextTranslation translationTypes="departmentName" :translationValue="getSelectedLabel"></TextTranslation>
+            </span>
+          </template>
+        </div>
+      </div>
+    </template>
+    <!-- 多选 -->
+    <template #tag v-if="multiple">
+      <template v-if="Array.isArray(treeSelectVal) && treeSelectVal.length > 0">
+        <el-tag type="info" :size="size" closable @close="tagClose">
+          <TextTranslation translationTypes="departmentName" :translationValue="getSelectedLabel"></TextTranslation>
+        </el-tag>
+        <el-tag type="info" :size="size" v-if="treeSelectVal.length > 1">+{{ treeSelectVal.length }}</el-tag>
+      </template>
+      <template v-else>
+        <span style="color: #A8ABB2">{{ placeholder }}</span>
+      </template>
+    </template>
+    <!-- 主要内容 -->
+    <template #default="{ node, data }">
+      <TextTranslation translationTypes="departmentName" :translationValue="node.label"></TextTranslation>
+    </template>
+  </el-tree-select>
+</template>
+
+<style lang="scss" scoped>
+.custom-select {
+  :deep(.el-select__placeholder.is-transparent) {
+    color: transparent !important;
+  }
+
+  :deep(.el-select__placeholder) {
+    span {
+      display: none;
+    }
+  }
+
+  :deep(.el-select__input) {
+    margin-left: 0;
+  }
+}
+
+.setUpInput :deep(.el-input__inner) {
+  color: #fff !important;
+}
+</style>

fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/personnelSearch/type.d.ts → fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/type.d.ts


+ 6 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/index.vue

@@ -7,6 +7,8 @@ import Echarts from '@/components/ReEcharts/index.vue';
 import { EChartsOption, use } from 'echarts';
 import { dayjs } from 'element-plus';
 import personnelSearch from '@/components/translationComponent/personnelSearch/personnelSearch.vue';
+import departmentSelection from '@/components/translationComponent/departmentSelection/departmentSelection.vue'
+import treeSelect from '@/components/translationComponent/treeSelect/treeSelect.vue'
 import {
   getSummaryData,
   getBulletinData,
@@ -19,6 +21,8 @@ import {
 
 const selectVal = ref<Array<string | number>>([])
 const selectVals = ref<string | number>('')
+const departmentVal = ref<any>('')
+const treeSelectVals = ref<any>('')
 const selectChange = (val: any) => {
   console.log(val, '<===== 当前的数据 selectVal')
   console.log(selectVal.value, '<===== 双向绑定的数据 selectVal')
@@ -194,6 +198,8 @@ watchEffect(() => {
         <div class="w-40">
           <personnel-search v-model="selectVal" :size="'small'" multiple placeholder="你好世界" @change="selectChange"></personnel-search>
           <personnel-search v-model="selectVals" :size="'small'" placeholder="你好世界" @change="selectChange2"></personnel-search>
+          <department-selection v-model="departmentVal" :size="'small'" placeholder="部门选择"></department-selection>
+          <tree-select v-model="treeSelectVals" :size="'small'" multiple placeholder="树形选择"></tree-select>
           <el-select
             ref="select1"
             size="small"

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

@@ -77,7 +77,7 @@
         <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col overflow-hidden pt-2 pl-2 pr-2">
           <div class="flex-1">
             <el-table ref="multipleTableRef" :data="tableData" v-loading="loadingFrom.tableLoading"
-              @selection-change="changeBatch" style="width: 100%;height: 100%;">
+              @selection-change="changeBatch" :style="`width: 100%;height: calc(100vh - 204px);`">
               <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>

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

@@ -2,7 +2,8 @@ type SotreState = {
   userInfo: any;
   routers: RouteRecordRaw[];
   asyncRoutesMark: boolean;
-  personnelList: any[]
+  personnelList: any[];
+  departmentList: any[];
 };
 type SoreGetters = {
   getRoutersList: () => RouteRecordRaw[];

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

@@ -11,6 +11,7 @@ export const useStore = defineStore<
     routers: [], // 返回的所有路由
     asyncRoutesMark: false, // 是否添加过路由
     personnelList: [], // 人员列表
+    departmentList: [], // 部门列表
   }),
   getters: {
     getRoutersList() {
@@ -50,6 +51,7 @@ export const useStore = defineStore<
       this.userInfo = {};
       this.routers = [];
       this.personnelList = [];
+      this.departmentList = [];
       this.asyncRoutesMark = false;
     },
   },