疯装qms项目中会用的 会签组件 !

发布时间:2024年01月23日

最近做的一个qms项目,多个地方需要选择会签人员,然后希望实现的效果图如下,这是我们产品经理画的原型图
在这里插入图片描述
这是我实现的效果图
在这里插入图片描述

会签组件效果

实现思路如下
点击外面的框,显示一个弹窗,弹窗左侧显示部门中间显示待选人员列表默认展示所有人员),右侧展示已选人员列表点击部门待选人员列表展示该部门下的人员点击后的待选人员待选人员列表删除,同时在已选人员列表新增,每个部门的人员最少并且只允许选中一个

代码如下
countersign.vue

<template>
  <div>
    <div @click="selectClick()" style="cursor: pointer;">
      <a-input v-model:value="selectName"  disabled style="pointer-events:none;width:300px;" placeholder="请选择" ref="searchInput" autocomplete="off">
      </a-input>
    </div>
    <a-modal v-model:visible="visible" @ok="handleOk" @cancel="handleCancel" width="700px" height="400px">
      <template #title>
        <div style="font-weight: 600; font-size: 16px">人员</div>
      </template>
      <div class="tree-container">
        <div>
          <div class="tree-title">单位组织机构</div>
          <div class="tree-section01">
            <div class="section01-left">
              <a-tree
                :tree-data="treeData"
                :selectedKeys="selectedKeys"
                @select="selectOrgTreeNode"
              >
              </a-tree>
            </div>
            <div class="section01-right">
              <a-divider  type="vertical"  style="height: 100%;"/>
            </div>
          </div>
        </div>
        <div>
          <div class="tree-title">待选人员</div>
          <div class="tree-section02">
            <div class="section-left">
              <a-input-search v-model:value="searchValue" style="margin-bottom: 8px" placeholder="搜索" @search="onSearch"/>
              <div v-if="userLoading">
                <div @click="changeTag(tag)"  v-for="(tag,index) in selectedUserData" :key="index" :style="tag.isEdit?{height:'30px',cursor: 'pointer'}:{height:'30px',cursor: 'not-allowed'}">
                  {{ tag.label }}<span v-show="tag.departName">{{tag.departName}}</span> 
                </div>
              </div>
              <div v-else>
                加载中
              </div>
            </div>
            <div class="section-right">
              <a-divider type="vertical" style="height: 100%;"/>
            </div>
          </div>
        </div>
        <div>
          <div class="tree-title">已选人员</div>
          <div class="tree-section03">
            <div class="tag-container">
              <div @click="closeTag(tag)"  v-for="(tag,index) in tags" :key="index" style="height:30px;cursor: pointer;">
                {{ tag.label }}<span v-show="tag.departName">{{tag.departName}}</span> 
              </div>
            </div>
          </div>
        </div>
      </div>
    </a-modal>
  </div>
</template>
 
<script setup>
import {onMounted, ref,defineEmits,defineProps, onBeforeUnmount, watch, nextTick} from 'vue';
import {defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import qs from 'qs'
const { createMessage } = useMessage();
const visible = ref(false);
const emits = defineEmits(['check']);
const props = defineProps({
  checked: {
    type: Array,
    default: [],
  }, //字段
});
const checkUser=[];
checkUser.value=props.checked;
// 组织树数据源
const treeData = ref([]);
// 所有用户数据
const userDataOptions = ref([]);
const selectedUserData = ref([]);
// 全局selectedKeys
const selectedKeys = ref([]);
const tags = ref([])
const selectName=ref('');//选中返回值
const departList=ref([]);//部门列表
const userList=ref([]);//用户列表
const userLoading=ref(false);//加载用户数据
onMounted(() => {
  // console.log(checkUser.value,'回显值')
  getDepartList();
  getAllUserList();
});

function getComponentData(url,jsonData,carNo){
  return new Promise((resolve,reject)=>{
    getAction(url,{ trainCode: carNo}).then(res => {
      if (res.success) {
        resolve(JSON.parse(res.result))
      } else {
        resolve(jsonData);
      }
    })
  })
}

//获取部门列表
const pid=ref('');
async function getDepartList(){
  departList.value=[];
  pid.value = 'c6d7cb4deeac411cb3384b1b31278596';//眉山制动id
  let url = '/sys/sysDepart/queryDepartTreeSync';
  let params = { pid: pid.value, };
  //对获取到的部门列表进行筛选,只获取其中四个部门
  // ('1744927552985948161','1744927780283670530','1744927844313915394','1744927935921709057')
  await defHttp.get({ url, params }, { isTransformResponse: false }).then((res) => {
    if (res.success) {
      for(let i=0;i<res.result.length;i++){
        if(res.result[i].id=='1744927552985948161'||res.result[i].id=='1744927780283670530'||res.result[i].id=='1744927844313915394'||res.result[i].id=='1744927935921709057'){
          departList.value.push(res.result[i]);
        }
      }
      // console.log(departList.value,'部门')
    } else {
      createMessage.error(res.message);
    }
  });
  queryData();
}
//判断一个数组是否含有另一个数组中的值
function getIncludes(arr1, arr2) {
  return arr1.filter((item) => {
	  return arr2.includes(item)
  })
}


//点击部门获取该部门下的人员列表
async function getUserList(departIds){
  userLoading.value=false;
  selectedUserData.value=[];
  userList.value=[];
  let url = '/sys/user/listAll';
  // let url = '/subassembly/listAll';
  let params = { departIds:departIds,pageNo:1,pageSize:200};
  // params=qs.stringify(params)
  userDataOptions.value = [];
  await defHttp.get({ url, params,paramsSerializer: params => {
            return qs.stringify(params, { indices: false })
          }, }, { isTransformResponse: false}).then((res) => {
    if (res.success) {
      userLoading.value=true;
      userList.value=res.result.records;
      
      // 对获取到的数据进行排序,防止后面顺序错乱
      userList.value.forEach((user,i) => {
        let option = {
          label: user.realname,
          value: user.username,
          departName:user.departIds_dictText,
          isEdit:true,
          px:i
        }
        userDataOptions.value.push(option)
      })
      sessionStorage.setItem("inData",JSON.stringify(userDataOptions.value));
      selectedUserData.value = userDataOptions.value.filter(item => !tags.value.some(obj => obj.value === item.value));
      // console.log(userDataOptions.value,'userDataOptions用户')
      // console.log(userList.value,'用户')
    } else {
      createMessage.error(res.message);
    }
  });
}
//数据回显时,获取所有人员列表
async function getAllUserList(){
  userLoading.value=false;
  selectedUserData.value=[];
  userList.value=[];
  // let url = '/sys/user/listAll';
  let url='/subassembly/listAll';
  let params = { order: 'desc'};
  userDataOptions.value = [];
  await defHttp.get({ url, params }, { isTransformResponse: false,timeout: 60000 }).then((res) => {
    if (res.success) {
      userLoading.value=true;
      userList.value=res.result;
      
      // 对获取到的数据进行排序,防止后面顺序错乱
      userList.value.forEach((user,i) => {
        let option = {
          label: user.realname,
          value: user.username,
          departName:user.orgName,
          isEdit:true,
          px:i
        }
        userDataOptions.value.push(option)
      })
      let newArr=[];
      //选中人员回显;
      for(let i=0;i<userDataOptions.value.length;i++){
        for(let j=0;j<checkUser.value.length;j++){
          if(userDataOptions.value[i].value==checkUser.value[j]){
            newArr.push(userDataOptions.value[i])
          }
        }
      }
      // console.log(newArr,'newArr')
      if(tags.value.length>0){
        for(let i=0;i<newArr.length;i++){
          for(let j=0;j<tags.value.length;j++){
            if(newArr[i].value!=tags.value[j].value){
              tags.value.push(newArr[i])
            }
          }
        }
      }else{
        tags.value=newArr;
      }
      sessionStorage.setItem("tagsData",JSON.stringify(tags.value));
      // console.log(tags.value,'tags')
      selectName.value=tags.value.map(item=>{
        return item.label
      });
      sessionStorage.setItem("inData",JSON.stringify(userDataOptions.value));
      selectedUserData.value = userDataOptions.value.filter(item => !tags.value.some(obj => obj.value === item.value));
      // console.log(userDataOptions.value,'userDataOptions用户')
      // console.log(userList.value,'用户')
    } else {
      createMessage.error(res.message);
    }
  });
}
// 组织架构树搜索
const searchValue = ref('');
const onSearch = (searchInputValue) => {
  let newArr = [];
  let inData = JSON.parse(sessionStorage.getItem('inData'));
  if (inData) {
    if (searchValue.value) {
      for (let i = 0; i < inData.length; i++) {
        if (inData[i].label.indexOf(searchValue.value)!=-1) {
          newArr.push(inData[i]);
        }
      }
    } else {
      newArr = inData;
    }
    selectedUserData.value = newArr.filter(item => !tags.value.some(obj => obj.value === item.value));
  }
};
 
function selectClick(){
  visible.value=true;
  // if(visible.value){
  //   getAllUserList();
  // }
}
 
// 点击左侧组织架构树根据组织code获取所属用户
function selectOrgTreeNode(departIds, e){
  getUserList(departIds);
  userDataOptions.value = [];
  // 对获取到的数据进行排序,防止后面顺序错乱
  userList.value.forEach((user,i) => {
    let option = {
      label: user.realname,
      value: user.username,
      px:i
    }
    userDataOptions.value.push(option)
  })
  sessionStorage.setItem("inData",JSON.stringify(userDataOptions.value));
  selectedUserData.value = userDataOptions.value.filter(item => !tags.value.some(obj => obj.value === item.value));
  // console.log(selectedUserData.value,'selectedUserData')
  // console.log(userDataOptions.value,'userDataOptions点击部门')
}
 
//已选择人员点击删除,同时待选择人员添加
const closeTag = (tag) => {
  let isIn=false;
  // console.log(tag,'tag点击')
  let index='';
  for(let i=0;i<tags.value.length;i++){
    if(tag.value==tags.value[i].value){
      index=i;
    }
  }
  for(let i=0;i<selectedUserData.value.length;i++){
    if(selectedUserData.value[i].departName==tag.departName){
      isIn=true;
    }
  }
  searchValue.value='';
  if(isIn){
    selectedUserData.value.push(tag);
    selectedUserData.value=selectedUserData.value.sort(function(a,b){
      return a.px-b.px
    });
    tags.value.splice(index,1);
  }else{
    for(let i=0;i<tags.value.length;i++){
      if(tag.value==tags.value[i].value){
        index=i;
      }
    }
    tags.value.splice(index,1);
  }
}
//待选择人员点击删除,同时已选择人员添加
const changeTag= (tag) => {
  searchValue.value='';
  //判断待选择人员是否在已选择人员中,在的话isIn为true,不在等于为false
  let isIn=false;
  for(let i=0;i<tags.value.length;i++){
    if(tags.value[i].departName==tag.departName){
      isIn=true;
    }
  }
  // console.log('isIn',isIn)
  if(isIn){
    selectedUserData.value.map(item=>{
      item.isEdit=false
    })
  }else{
    tags.value.push(tag);
    tags.value=tags.value.sort(function(a,b){
      return a.px-b.px
    });
    selectedUserData.value = userDataOptions.value.filter(item => !tags.value.some(obj => obj.value === item.value));
  }
  // console.log(selectedUserData.value,'selectedUserData')
}

// 左侧组织架构树
const queryData = async () => {
  let officeUserData = [];
  officeUserData=departList.value;
  localStorage.setItem('officeUserData', JSON.stringify(officeUserData));
  recursive(officeUserData);
}
 
// 根据数据源构建架构树
const recursive = (newList) => {
  // console.log(newList,'newList')
  newList.map((item => {
    item.title = item.departName;
    item.key = item.id;
    // if (item.children !== undefined) {
    //   recursive(item.children)
    // }
  }))
  treeData.value = newList;
};

const handleCancel = (e) => {
  visible.value = false;
  //选中人员回显;
  tags.value=JSON.parse(sessionStorage.getItem("tagsData"));
}
const handleOk = (e) => {
  // console.log(tags.value,'选中值')
  if(tags.value.length==4){
    visible.value = false;
    selectName.value=tags.value.map(item=>{
      return item.label
    });
    emits('check', tags.value); //把列表选中的值传过去
  }else{
    createMessage.warning('每个部门至少选择一个人员~');
  }
};
 
onBeforeUnmount(() => {
})

 
</script>
<style lang="less" scoped>
 
.tree-container {
  display: flex;
  flex-direction: row;
  min-height: 400px;
  max-height: 650px;
  box-sizing: border-box;
 
  .tree-section01 {
    overflow-y: scroll;
    // margin-right: 20px;
    padding-left:5px;
    display: flex;
    max-height: 400px;
  }
 
  .tree-section02 {
    display: flex;
    overflow-y: scroll;
    max-height: 400px;
    // margin-right: 20px;
    // padding-top: 35px;
    padding-left:5px;
    flex: 3;
 
    & :deep(.ant-checkbox-group) {
      display: flex !important;
      flex-direction: column !important;
    }
  }
 
  .tree-section03 {
    padding-left:5px;
    // padding-top: 30px;
    flex: 3;
    max-height: 400px;

  }
 
  .section-left {
    width: 100%;
  }
 
  .tree-title {
    font-size: 14px;
    height: 22px;
    line-height: 22px;
    font-weight: 600;
    color: rgba(21, 22, 24, 0.92);
    margin-left:10px;
  }
 
  .tag-container {
    .tag-content {
      width: 100%;
      display: flex;
      flex-wrap: wrap;
 
      .tag-title {
        font-size: 14px;
        height: 22px;
        line-height: 22px;
        font-weight: 600;
        color: red;
      }
 
      .tag-item {
        margin: 2px 2px;
      }
    }
  }
}
</style>

调用countersign组件

<template>
    <a-card :bordered="false">
      <a-tabs defaultActiveKey="1">
        <a-tab-pane tab="基础示例" key="1">
          <a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
            <a-row :gutter="24">
              <a-col :span="8">
                <a-form-item label="label名称:"> 
                  <countersign :checked='checkUser' @check='getCheckUser'></countersign>
                </a-form-item>
              </a-col>
            </a-row>
          </a-form>
        </a-tab-pane>
      </a-tabs>
    </a-card>
</template>

<script setup>
import {onMounted, ref,defineEmits,defineProps, onBeforeUnmount, watch, nextTick} from 'vue';
import countersign from '/@/components/countersign.vue';
const checkUser=ref(['010800007567']);//传过去的人员账号
const checkedUser=ref([]);//回显的数据
function getCheckUser(val){
  console.log(val,'点击确定返回数据')
  if(val){
    checkedUser.value=val.map(item=>{
      return item.value
    })
  }
  console.log(checkedUser.value,'选中人员账号')
}
</script>

文章来源:https://blog.csdn.net/blue__k/article/details/135765088
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。