@@ -0,0 +1,132 @@ | |||
<template> | |||
<div class="people-item" :title="user.cnName"> | |||
<div class="people-wrap" :class="{'rt-0': isShowManagerMark}"> | |||
<div class="people"> | |||
<img v-if="Object.keys(user).length > 0" | |||
class="proj-avatarface" | |||
:class="{'manager-mark': isShowManagerMark }" | |||
:src="user.headImgUrl | resolveAvator" alt | |||
/> | |||
<div v-else-if="!isShowEmptyAvatar" class="add-btn"> | |||
<i class="el-icon-plus proj-avatarface"></i> | |||
</div> | |||
<img v-else src="/static/img/暂无2.svg" alt="" class="proj-avatarface empty"> | |||
</div> | |||
<div class="people-name" :class="{'rt-2': isEnterEdit && isShowManagerMark, 'rt-4': isEnterEdit && !isShowManagerMark}">{{user.cnName}}</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return { | |||
} | |||
}, | |||
computed: { | |||
}, | |||
props: { | |||
user: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
isShowManagerMark: {//是否显示 圆圈边框 | |||
type: Boolean, | |||
default: false, | |||
}, | |||
isEnterEdit: {//控制是展示还是编辑 | |||
type: Boolean, | |||
default: false, | |||
}, | |||
isShowEmptyAvatar: {//是否显示暂无 | |||
type: Boolean, | |||
defualt: false | |||
} | |||
}, | |||
methods: { | |||
}, | |||
} | |||
</script> | |||
<style scoped> | |||
.rt-2 { | |||
top: 2px !important; | |||
} | |||
.rt-0 { | |||
top: 0 !important; | |||
} | |||
.rt-4 { | |||
top: 4px !important; | |||
} | |||
.empty { | |||
background-color: #eaeaea; | |||
} | |||
.people-item { | |||
display: flex; | |||
position: relative; | |||
z-index: 9; | |||
box-sizing: border-box; | |||
text-align: center; | |||
width: 64px; | |||
padding: 0 0 8px 0; | |||
} | |||
.people-item .people-wrap { | |||
position: relative; | |||
top: 4px; | |||
border-radius: 50%; | |||
margin: 0 auto; | |||
width: 100%; | |||
} | |||
.people-item .people { | |||
border-radius: 50%; | |||
} | |||
.active-manager-mark .people-name { | |||
top:-2px; | |||
} | |||
/* 加号选择框 */ | |||
.people-item .el-icon-plus { | |||
display: block; | |||
text-align: center; | |||
height: 48px; | |||
line-height: 48px; | |||
font-size: 24px; | |||
color: #999B9D; | |||
} | |||
.proj-avatarface { | |||
vertical-align: bottom; | |||
width: 48px; | |||
height: 48px; | |||
border-radius: 50%; | |||
overflow: hidden; | |||
background-color: rgba(246, 246, 246, 1); | |||
} | |||
.people-name { | |||
position: relative; | |||
width: 100%; | |||
overflow: hidden; | |||
white-space: nowrap; | |||
text-overflow: ellipsis; | |||
height: 16px; | |||
line-height: 16px; | |||
color: rgba(17, 19, 21, 100); | |||
font-size: 12px; | |||
text-align: center; | |||
font-family: PingFangSC-Regular; | |||
margin: 8px auto 0; | |||
} | |||
.add-btn { | |||
cursor: pointer; | |||
margin: 0 4px; | |||
} | |||
</style> |
@@ -0,0 +1,305 @@ | |||
<template> | |||
<div class="member-list"> | |||
<div class="top-title">选择成员</div> | |||
<div class="search-bar"> | |||
<i class="search-icon"></i> | |||
<input placeholder="搜索功能正在内测中" class="search-input" disabled/> | |||
</div> | |||
<!-- 下面按企业 / 部门 显示人员 --> | |||
<div class="hori-line"></div> | |||
<div class="content-wrap"> | |||
<!-- 折叠面板 --> | |||
<div class="people-wrap"> | |||
<el-collapse v-model="activeCompanyName"> | |||
<!-- 添加协作群与工作 --> | |||
<el-collapse-item | |||
v-for="topNode in treeList" | |||
:key="topNode.id" | |||
:name="topNode.id" | |||
> | |||
<template slot="title"> | |||
<span class="company-title">{{ topNode.label }}</span> | |||
</template> | |||
<el-collapse v-model="activeDeptNameList" v-if="topNode.hasChildren"> | |||
<!-- 二级折叠面板 --> | |||
<el-collapse-item v-for="deptNode in topNode.children" v-show="deptNode.hasChildren" :name="deptNode.id" :key="deptNode.id"> | |||
<template slot="title" class="dept-entry"> | |||
<span class="dept-name">{{ deptNode.label }}</span> | |||
<div class="check-all-wrap" | |||
@click.stop="toggleAllDept(deptNode)" | |||
> | |||
<div class="checkbox" :class="{checked: deptNodeSelectedMap[deptNode.id]}"/><span>全选</span> | |||
</div> | |||
</template> | |||
<div class="user-info-item" v-for="memberNode in deptNode.children" :label="memberNode.label" :key="memberNode.id" | |||
@click.stop="toggleMember(memberNode)" | |||
> | |||
<img :src="memberNode.data.headImgUrl | resolveAvator" alt="" class="avatar-img" /> | |||
<span class="user-info-name">{{memberNode.label}}</span> | |||
<div class="checkbox" :class="{checked: isSelected(memberNode.id)}" /> | |||
</div> | |||
</el-collapse-item> | |||
</el-collapse> | |||
<div class="empty-people-tips" v-else>暂未添加人员</div> | |||
</el-collapse-item> | |||
<!-- v-if="linkCompanyList.length" --> | |||
</el-collapse> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
// 选择人员 人员列表组件 | |||
export default { | |||
data() { | |||
// console.log(this.treeList); | |||
const firstTopNode = this.treeList[0]; | |||
const activeCompanyName = firstTopNode ? [firstTopNode.id]:[]; | |||
const activeDeptNameList = firstTopNode ? firstTopNode.mapChildren(deptNode => deptNode.id): []; | |||
this.clacAllSelected(); | |||
return { | |||
ischeckAll: false, | |||
activeCompanyName, | |||
activeDeptNameList, | |||
} | |||
}, | |||
methods: { | |||
isSelected(id) { | |||
return !!this.selectedKeys.find(key => key === id); | |||
}, | |||
isChildrenAllSelected(deptNode) { | |||
const selectedKeys = this.selectedKeys; | |||
return deptNode.everyChildren(node => selectedKeys.find(key => key === node.id)); | |||
}, | |||
toggleAllDept(deptNode) { | |||
const targetChecked = !this.deptNodeSelectedMap[deptNode.id]; | |||
this.$emit('select', deptNode.children, targetChecked); | |||
}, | |||
toggleMember(memberNode) { | |||
const targetChecked = !this.isSelected(memberNode.id); | |||
this.$emit('select', [memberNode], targetChecked); | |||
}, | |||
clacAllSelected() { | |||
const hash = {}; | |||
this.treeList.forEach(topNode => { | |||
topNode.mapChildren(deptNode => { | |||
const flag = this.isChildrenAllSelected(deptNode); | |||
if(flag) { hash[deptNode.id] = true; } | |||
}) | |||
}); | |||
this.deptNodeSelectedMap = hash; | |||
}, | |||
}, | |||
props: { | |||
treeList: { | |||
type: Array, | |||
default() { | |||
return [] | |||
} | |||
}, | |||
selectedKeys: { | |||
type: Array, | |||
default() { | |||
return [] | |||
} | |||
}, | |||
}, | |||
watch: { | |||
selectedKeys: { | |||
immediate: true, | |||
handler(val) { | |||
this.clacAllSelected(); | |||
} | |||
}, | |||
treeList: { | |||
immediate: true, | |||
handler(val) { | |||
this.clacAllSelected(); | |||
} | |||
}, | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
/* 水平的线 */ | |||
.hori-line { | |||
border: 0.5px solid #eaeaea; | |||
margin: 13px 0 0 0; | |||
} | |||
.member-list { | |||
width: 100%; | |||
min-width: 375px; | |||
max-width: 375px; | |||
background-color: #fcfcfc; | |||
border-top-left-radius: 8px; | |||
border-top-right-radius: 8px; | |||
box-shadow: 0px 4px 10px 1px rgba(0, 0, 0, 0.1), 0px 1px 3px 0px rgba(0, 0, 0, 0.1); | |||
.top-title { | |||
height: 50px; | |||
line-height: 50px; | |||
padding: 0 0 0 24px; | |||
color: rgba(12, 13, 16, 100); | |||
font-size: 20px; | |||
text-align: left; | |||
font-family: PingFangSC-Medium; | |||
} | |||
.search-bar { | |||
position: relative; | |||
margin: 6px 15px 7px; | |||
padding: 0 5px; | |||
.search-icon { | |||
position: absolute; | |||
left: 10px; | |||
width: 22px; | |||
height:22px; | |||
background: url('/static/img/搜索.svg') center/100%; | |||
margin: 4px 0; | |||
} | |||
.search-input { | |||
box-sizing: border-box; | |||
outline: none; | |||
width:100%; | |||
height: 30px; | |||
padding: 0 0 0 30px; | |||
border-radius: 8px; | |||
background-color: rgba(233, 233, 235, 1); | |||
border: 0.5px solid rgba(174, 174, 174, 1); | |||
cursor: not-allowed; | |||
} | |||
} | |||
.content-wrap { | |||
// 右侧滚动条有8px的宽度 | |||
padding: 0 0 0 8px; | |||
overflow-y: scroll; | |||
height: calc(100vh - 177px); | |||
/* 我的企业文字样式 */ | |||
.company-title { | |||
margin: 12px 0 10px 16px; | |||
height: 22px; | |||
line-height: 22px; | |||
color: rgba(50, 50, 60, 100); | |||
font-size: 18px; | |||
text-align: left; | |||
font-family: PingFangSC-Regular; | |||
} | |||
.dept-name { | |||
margin: 0 0 0 16px; | |||
flex: 1; | |||
} | |||
} | |||
.people-wrap { | |||
padding: 0 0 40px 0; | |||
} | |||
} | |||
.search-input::-webkit-input-placeholder { | |||
color: rgba(0, 0, 0, 0.56); | |||
font-size: 14px; | |||
font-family:'Courier New', Courier, monospace; | |||
color: rgba(0, 0, 0, 0.56); | |||
height: 20px !important; | |||
line-height: 20px !important; | |||
} | |||
.user-info-item:hover { | |||
background-color: rgba(50, 50, 60, 0.1) | |||
} | |||
.user-info-item { | |||
display: flex; | |||
padding:0 16px; | |||
height: 44px; | |||
line-height: 44px; | |||
align-items: center; | |||
cursor: pointer; | |||
border-radius: 8px; | |||
.avatar-img { | |||
width: 32px; | |||
height:32px; | |||
border-radius: 50%; | |||
margin: 0 13px 0 0; | |||
} | |||
.user-info-name { | |||
flex: 1; | |||
font-size: 14px; | |||
font-family: PingFangSC-Regular; | |||
} | |||
} | |||
</style> | |||
<style lang="scss"> | |||
.member-list .el-collapse-item__wrap { | |||
border-bottom: none !important; | |||
} | |||
.dept-select-all-btn .el-checkbox__label { | |||
padding: 0 0 0 6px !important; | |||
margin: 0 8px 0 0; | |||
} | |||
.member-list .el-collapse { | |||
border: none !important; | |||
} | |||
.member-list .el-collapse-item__arrow { | |||
position: relative; | |||
left: 2px; | |||
} | |||
.el-checkbox.dept-select-all-btn { | |||
/* //margin: 0 0 0 auto; */ | |||
position: absolute; | |||
right: 15px; | |||
} | |||
.member-list .el-collapse-item__header { | |||
height: 44px !important; | |||
line-height: 44px !important; | |||
border: none !important; | |||
position: relative; | |||
} | |||
.user-info-item .el-checkbox__inner { | |||
width: 20px; | |||
height: 20px; | |||
color: #cbcbce; | |||
} | |||
.member-list .el-collapse-item__header { | |||
margin: 0 4px 0 0 !important; | |||
} | |||
.check-all-wrap { | |||
display: flex; | |||
align-items: center; | |||
margin: 0 10px 0 0; | |||
.checkbox { | |||
width: 10px; | |||
height: 10px; | |||
margin: 0 6px 0 0; | |||
} | |||
} | |||
.checkbox{ | |||
float: right; | |||
width: 20px; | |||
height: 20px; | |||
border: 1px solid rgba(203, 203, 206, 1); | |||
border-radius: 50%; | |||
&.checked{ | |||
background-color: #7850ff | |||
} | |||
} | |||
.member-list .app-header-content { | |||
background-color: #f8f8f8; | |||
} | |||
.empty-people-tips { | |||
font-size: 16px; | |||
text-align: center; | |||
margin: 20px 0; | |||
} | |||
</style> |
@@ -1,33 +0,0 @@ | |||
<!-- | |||
选择成员弹窗 | |||
--> | |||
<template> | |||
<div class="dialog members"> | |||
<h3>选择成员</h3> | |||
<el-input placeholder="搜索职员"> | |||
<el-button slot="prepend" icon="el-icon-search" /> | |||
</el-input> | |||
</div> | |||
</template> | |||
<style scoped> | |||
.dialog { | |||
position: absolute; | |||
background-color: rgba(252, 252, 252, 1); | |||
box-shadow: 0px 3px 11px 1px rgba(0, 0, 0, 0.06); | |||
border-radius: 8px 8px 0 0; | |||
} | |||
.members { | |||
left: 868px; | |||
top: 48px; | |||
width: 375px; | |||
height: 976px; | |||
} | |||
.members h3 { | |||
margin-top: 16px; | |||
margin-bottom: 28px; | |||
line-height: 1em; | |||
padding: 0 24px; | |||
} | |||
</style> |
@@ -0,0 +1,387 @@ | |||
<template> | |||
<div> | |||
<div v-for="folder in folderList" :key="folder.id"> | |||
<div> | |||
<div | |||
@click.stop="activeFolderClick(folder)" | |||
class="folder-wrap folder-hover" | |||
ref="activeFolder" | |||
:class="{ | |||
'active-folder': folder.id == activeFolderId && isEnterEdit, | |||
'folder-click': !isEnterEdit, | |||
}" | |||
> | |||
<div | |||
class="folder-name" | |||
:class="{ 'opacity-5': !isAllowedAssignedStaffFolder }" | |||
> | |||
<span>{{ folder.folderName }}</span> | |||
<span v-if="!isProjManager" class="user-perm-text"></span> | |||
</div> | |||
<div | |||
v-if="!isEnterEdit || activeFolderId !== folder.id" | |||
class="clear-fix border-box" | |||
:class="{ | |||
'mb-36': folder.id === activeFolderId && isEnterEdit, | |||
'ml-4': !isEnterEdit, | |||
'adjust-position': isEnterEdit, | |||
}" | |||
> | |||
<div class="fl"> | |||
<div v-if="folder.manageUser && folder.manageUser.length == 0"> | |||
<div | |||
class="use-relative" | |||
v-if="isAllowedAssignedFolder(folder)" | |||
> | |||
<people-item | |||
v-if="!isEnterEdit" | |||
:class="{ 'mr-8': !isEnterEdit }" | |||
@click.native.stop=" | |||
aloneAddUserForFolder(folder), | |||
enterEditFolderClick(folder) | |||
" | |||
/> | |||
<people-item v-else /> | |||
</div> | |||
<div class="notclick" v-else> | |||
<people-item | |||
:isShowEmptyAvatar="true" | |||
:class="{ 'mr-8': !isEnterEdit }" | |||
/> | |||
</div> | |||
</div> | |||
<div class="use-flex" v-else> | |||
<div v-for="(user, index) in folder.manageUser" :key="user.id"> | |||
<div class="use-relative"> | |||
<people-item | |||
:user="user" | |||
:isShowManagerMark="isEnterEdit" | |||
:class="{ | |||
notclick: !isAllowedAssignedStaffFolder(folder), | |||
'mr-8': !isEnterEdit, | |||
}" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 竖线 --> | |||
<div class="vertical-line" v-show="!isEnterEdit" /> | |||
<div | |||
class="use-flex" | |||
:class="{ 'mb-6': isEnterEdit && activeFolderId !== folder.id }" | |||
> | |||
<div | |||
v-for="user in listUserComputed(folder.listUser)" | |||
:key="user.id" | |||
:class="{ | |||
notclick: !( | |||
isAllowedAssignedStaffFolder(folder) || isStaff(folder) | |||
), | |||
}" | |||
> | |||
<people-item :user="user" :isEnterEdit="isEnterEdit" /> | |||
</div> | |||
<div | |||
class="add-btn" | |||
v-if=" | |||
folder.manageUser && | |||
folder.manageUser.length > 0 && | |||
isAllowedAssignedStaffFolder(folder) | |||
" | |||
> | |||
<people-item | |||
v-if="!isEnterEdit" | |||
@click.native.stop=" | |||
aloneAddUserOnlyStaff(folder), enterEditFolderClick(folder) | |||
" | |||
/> | |||
</div> | |||
<div class="stand-place" v-if="isNest(folder.nodeId)" /> | |||
<!-- 嵌套模板入口 --> | |||
<div | |||
class="add_nest" | |||
v-if="isNest(folder.nodeId) && !isEnterEdit" | |||
> | |||
<div class="add_nest_box" @click.stop="enterNestTemp(folder)"> | |||
<i class="el-icon-plus"></i> | |||
<p>嵌套模板</p> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
v-if="isEnterEdit && activeFolderId == folder.id" | |||
class="use-relative ml-4 mb-36 use-flex border-box" | |||
:class="{ 'adjust-margin': folder.id === lastFolderId }" | |||
> | |||
<temp-selected-user-list | |||
:selectedUserList="selectedUser" | |||
@changeManagerClick="changeManagerClick" | |||
@setFirstPeopleToManager="setFirstPeopleToManager" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<template v-if="folder.listChildren && folder.listChildren.length > 0"> | |||
<plain-folder-list | |||
:folderList="folder.listChildren" | |||
:id="id" | |||
:lastFolderId="lastFolderId" | |||
:allManager="allManager" | |||
:allManagerChild="allManagerChild" | |||
:aloneAddUserForFolder="aloneAddUserForFolder" | |||
:aloneAddUserOnlyStaff="aloneAddUserOnlyStaff" | |||
:allChildrenLists="allChildrenLists" | |||
:isEnterEdit="isEnterEdit" | |||
:activeFolderId="activeFolderId" | |||
:selectedUser="selectedUser" | |||
:isProjManager="isProjManager" | |||
:nestNodeList="nestNodeList" | |||
/> | |||
</template> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import PeopleItem from "@/components/people-item"; | |||
import TempSelectedUserList from "./temp-selected-user-list"; | |||
export default { | |||
name: "PlainFolderList", | |||
components: { | |||
PeopleItem, | |||
TempSelectedUserList, | |||
}, | |||
props: { | |||
id: String, //用户的id, | |||
lastFolderId: String, | |||
allManager: Object, | |||
allManagerChild: Array, | |||
folderList: Array, | |||
aloneAddUserForFolder: Function, | |||
aloneAddUserOnlyStaff: Function, | |||
allChildrenLists: Array, | |||
isEnterEdit: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
nestNodeList: Array, | |||
isProjManager: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
activeFolderId: { | |||
type: String, | |||
defualt: "", | |||
}, | |||
selectedUser: { | |||
type: Array, | |||
default() { | |||
return []; | |||
}, | |||
}, | |||
}, | |||
data() { | |||
return { | |||
showAll: false, | |||
exsitNest: false, | |||
}; | |||
}, | |||
methods: { | |||
//判断是否存在可嵌套节点 | |||
isNest(id) { | |||
let len = this.nestNodeList.length; | |||
for (let i = 0; i < len; i++) { | |||
if (this.nestNodeList[i].ParentNodeId == id) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
}, | |||
enterNestTemp(folder) { | |||
this.$bus.$emit("enterNestTemp", folder); | |||
//this.$bus.$emit('enterNestTemp'); | |||
}, | |||
// 设置第一个选择的人员为节点负责人 | |||
setFirstPeopleToManager(user) { | |||
this.$bus.$emit("setFirstPeopleToManager", user); | |||
}, | |||
// 切换工作负责人 | |||
changeManagerClick(user) { | |||
this.$bus.$emit("changeManagerClick", user); | |||
}, | |||
// 点击进入文件夹编辑 | |||
activeFolderClick(folder) { | |||
if (this.isEnterEdit && this.isAllowedAssignedStaffFolder(folder)) { | |||
this.$bus.$emit("activeFolderClick", folder); | |||
} | |||
}, | |||
// 点击了添加职员的按钮 进入编辑状态 | |||
// 因为这里用的是递归组件 所以就不用父子组件通信 用事件总线 | |||
enterEditFolderClick(folder) { | |||
if (!this.isEnterEdit) { | |||
this.$bus.$emit("enterEditFolderClick", folder); | |||
} | |||
}, | |||
showAllClick() { | |||
this.showAll = true; | |||
}, | |||
toggleFolderExpand(folder) { | |||
folder.expanded = !folder.expanded; | |||
this.$forceUpdate(); | |||
}, | |||
isAllowedAssignedFolder(folder) { | |||
return ( | |||
this.allManager.id == this.id || //总负责人 | |||
(this.allManagerChild.length > 0 && | |||
this.allManagerChild.find((item) => item.id == this.id)) || //是不是负责人 | |||
(this.allChildrenLists.length > 0 && | |||
this.allChildrenLists.includes(folder)) | |||
); //它的所有子节点 | |||
//||folder.manageUser && folder.manageUser[0].id == this.id | |||
}, | |||
isAllowedAssignedStaffFolder(folder) { | |||
return ( | |||
(folder.manageUser && | |||
folder.manageUser.length > 0 && | |||
folder.manageUser[0].id == this.id) || | |||
this.isAllowedAssignedFolder(folder) | |||
); | |||
}, | |||
isStaff(folder) { | |||
return folder.listUser.some((item) => { | |||
return item.id === this.id; | |||
}); | |||
}, | |||
}, | |||
computed: { | |||
listUserComputed: function () { | |||
var thisApp = this; | |||
return function (listUsers) { | |||
return listUsers.filter(function (user) { | |||
return user.folderPerm != 2; | |||
}); | |||
}; | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
.stand-place { | |||
width: 64px; | |||
height: 72px; | |||
} | |||
.add_nest { | |||
position: absolute; | |||
right: 10px; | |||
bottom: -6px; | |||
z-index: 9; | |||
width: 64px; | |||
height: 80px; | |||
box-sizing: border-box; | |||
.add_nest_box { | |||
cursor: pointer; | |||
width: 64px; | |||
height: 64px; | |||
box-sizing: border-box; | |||
border: 1px solid #7850ff; | |||
border-radius: 8px; | |||
.el-icon-plus { | |||
font-size: 24px; | |||
color: #7850ff; | |||
} | |||
p { | |||
color: #7850ff; | |||
font-size: 14px; | |||
text-align: center; | |||
margin-top: 10px; | |||
} | |||
} | |||
} | |||
.opacity-5 { | |||
opacity: 0.5; | |||
} | |||
.user-perm-text { | |||
margin: 0 0 0 8px; | |||
} | |||
.notclick { | |||
cursor: not-allowed !important; | |||
opacity: 0.5; | |||
} | |||
.notclick .avtarface { | |||
cursor: not-allowed; | |||
} | |||
.allopacity { | |||
opacity: 1; | |||
} | |||
.noManagerText { | |||
font-size: 0; | |||
} | |||
.active-folder .people-item { | |||
cursor: pointer; | |||
} | |||
.folder-name { | |||
color: #32323c; | |||
height: 20px; | |||
line-height: 20px; | |||
margin: 0 0 6px 12px; | |||
color: rgba(50, 50, 60, 100); | |||
font-size: 14px; | |||
text-align: left; | |||
font-family: PingFangSC-Regular; | |||
} | |||
.adjust-position { | |||
position: relative; | |||
left: 4px; | |||
} | |||
.folder-wrap { | |||
position: relative; | |||
padding: 6px 0 0 0; | |||
margin: 12px 0 0 0; | |||
border-radius: 8px; | |||
background-color: #fff; | |||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.02); | |||
} | |||
/* 编辑按钮 */ | |||
.edit-btn { | |||
position: absolute; | |||
top: 4px; | |||
right: 4px; | |||
width: 64px; | |||
height: 24px; | |||
line-height: 24px; | |||
font-size: 14px; | |||
text-align: center; | |||
font-family: PingFangSC-Regular; | |||
color: #979797; | |||
border-radius: 4px; | |||
background-color: rgba(255, 255, 255, 1); | |||
border: 0.5px solid rgba(151, 151, 151, 1); | |||
cursor: pointer; | |||
} | |||
// 最后一个元素不显示32px的间距 因为这样会和原先设置的底部28px的留白重复 | |||
.adjust-margin { | |||
margin-bottom: 0; | |||
} | |||
.mb-6 { | |||
margin: 0 0 6px 0; | |||
} | |||
</style> |
@@ -0,0 +1,130 @@ | |||
<!-- 用来编辑人员时 存储编辑的状态 --> | |||
<template> | |||
<div class="temp-selected-user-list" :style="{height: showAdjustHeight}"> | |||
<div class="use-flex" ref="selectedUserList"> | |||
<div v-for="(user, index) in selectedUserList" :key="user.id" class="use-relative"> | |||
<people-item @click.native.stop="changeManagerClick(user)" | |||
:class="{'active-manager-mark': user.folderPerm == 2}" | |||
:user="user"/> | |||
<div v-if="user.folderPerm == 2" class="active-manager-mark-bottom-name">工作负责人</div> | |||
</div> | |||
<div v-if="selectedUserList.length == 0" class="use-relative" @click.stop="openPersonnelListClick"> | |||
<people-item class="active-manager-mark"/> | |||
<div class="active-manager-mark-bottom-name">工作负责人</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import PeopleItem from '@/components/people-item'; | |||
export default { | |||
data() { | |||
return { | |||
selectedUserListEl: null,//渲染选中人员的dom元素 | |||
selectedUserListElWidth: 0, | |||
selectedUserListElHeight: 0, | |||
renderHeight: 0, | |||
isAdjustHeight: false, | |||
} | |||
}, | |||
components: { | |||
PeopleItem | |||
}, | |||
props: { | |||
selectedUserList: {//选择的人员列表 利用folderPerm是否为2来区分是否是普通人员 | |||
type: Array, | |||
default() { | |||
return [] | |||
} | |||
}, | |||
}, | |||
mounted() { | |||
document.body.addEventListener('resize',()=> { | |||
this.adjustHeightOperate(); | |||
}) | |||
this.selectedUserListEl = this.$refs.selectedUserList; | |||
if(this.selectedUserListEl) { | |||
this.selectedUserListElWidth = this.selectedUserListEl.clientWidth; | |||
this.selectedUserListElHeight = this.selectedUserListEl.clientHeight; | |||
this.adjustHeightOperate(); | |||
} | |||
}, | |||
computed: { | |||
selectedUserListLength() { | |||
return this.selectedUserList.length; | |||
}, | |||
showAdjustHeight() { | |||
return this.isAdjustHeight ? `${this.selectedUserListElHeight - 31}px` : `auto`; | |||
} | |||
}, | |||
watch: { | |||
selectedUserList: { | |||
immediate: true, | |||
handler(val) { | |||
if(val.length > 0) { | |||
let isExistsManager = val.find(user => user.folderPerm && user.folderPerm == 2); | |||
if(!isExistsManager) { | |||
val[0].folderPerm = 2; | |||
this.$emit('setFirstPeopleToManager', val[0]); | |||
} | |||
} | |||
} | |||
}, | |||
selectedUserListLength(val) { | |||
setTimeout(()=> { | |||
this.selectedUserListElWidth = this.selectedUserListEl.clientWidth; | |||
this.selectedUserListElHeight = this.selectedUserListEl.clientHeight; | |||
this.adjustHeightOperate(); | |||
}, 50) | |||
} | |||
}, | |||
methods: { | |||
// 工作负责人为空的时候 点击 -》 展开右侧的人员列表 | |||
openPersonnelListClick() { | |||
this.$bus.$emit('openPersonnelListClick') | |||
}, | |||
// 如果点击到最后一行的话 要动态改变盒子的高度 | |||
changeManagerClick(user) { | |||
this.$emit('changeManagerClick', user); | |||
this.adjustHeightOperate(); | |||
}, | |||
adjustHeightOperate() { | |||
this.isAdjustHeight = false; | |||
const num = parseInt(this.selectedUserListElWidth / 64);//一行排列了几个人 | |||
const totalNum = this.selectedUserListLength;//总人数 | |||
const row = (totalNum / num);//行数 | |||
let currentUserIndex = 0; | |||
if(row <= 1) { | |||
// 只有一行 | |||
this.isAdjustHeight = true; | |||
this.renderHeight = this.selectedUserListElHeight - 31; | |||
return; | |||
} | |||
// 获取当前选中的人员的索引 | |||
for(let i = 0; i < totalNum; i++) { | |||
if(this.selectedUserList[i].folderPerm == 2) { | |||
currentUserIndex = i; | |||
break; | |||
} | |||
} | |||
// 当前选中的负责人位于最后一行以及不满一行的时候 那么需要调整高度 | |||
if(parseInt(row)*num < currentUserIndex+1 && currentUserIndex+1 <= (parseInt(row)+1)*num) { | |||
this.isAdjustHeight = true; | |||
this.renderHeight = this.selectedUserListElHeight - 31; | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
</style> |
@@ -0,0 +1,102 @@ | |||
import { fetchApi, wrapErrorHint } from '@/utils/request'; | |||
import { firstCharToLowerCase } from '@/utils/tool'; | |||
import { fetchDeptList, getUserListByNode } from '@/services/user.js'; | |||
export async function fetchMemberTree(companyId) { | |||
const [{ list: mainMemberList }, deptList, otherCompanyMemberListRes] = await Promise.all([ | |||
getUserListByNode({ id: companyId, nodeType: 'company' }, 1, 10000), | |||
fetchDeptList(companyId), | |||
fetchApi('user/queryAllLinkCompanyRoleUserByCompanyId', { companyId }), | |||
]); | |||
const mainCompanyNode = new Node(companyId, '我的企业', null, 'company', {}); | |||
const emptyDeptNode = new Node('emptyDeptNode', sessionStorage.CompanyName || '', companyId, 'dept', {}); | |||
const nodeMap = { [emptyDeptNode.id]: emptyDeptNode }; | |||
const memberMap = {}; | |||
deptList.forEach(dept => { | |||
const deptNode = new Node(dept.id, dept.label, companyId, 'dept', dept.data); | |||
nodeMap[deptNode.id] = deptNode; | |||
mainCompanyNode.append(deptNode); | |||
}); | |||
mainMemberList.forEach((member) => { | |||
const parentNode = nodeMap[member.deptId] || emptyDeptNode; | |||
const memberNode = new Node(member.id, member.cnName, parentNode.id, 'user', member); | |||
memberMap[memberNode.id] = memberNode; | |||
parentNode.append(memberNode); | |||
}); | |||
if(emptyDeptNode.hasChildren) { mainCompanyNode.append(emptyDeptNode); } | |||
const linkCompanyParentNode = new Node('linkCompanyParentNode', '互链企业', null, 'empty', {}); | |||
const linkCompanyList = otherCompanyMemberListRes.Data || []; | |||
linkCompanyList.forEach((data) => { | |||
const { CompanyId, CompanyName, Users } = data; | |||
const linkCompanyNode = new Node(CompanyId, CompanyName, linkCompanyParentNode.id, 'company', { companyId: CompanyId, companyName: CompanyName }); | |||
Users.forEach(upperMember => { | |||
const member = firstCharToLowerCase(upperMember); | |||
const memberNode = new Node(member.id, member.cnName, linkCompanyNode.id, 'user', member); | |||
memberMap[memberNode.id] = memberNode; | |||
linkCompanyNode.append(memberNode); | |||
}); | |||
linkCompanyNode.sortChildren(); | |||
nodeMap[linkCompanyNode.id] = linkCompanyNode; | |||
linkCompanyParentNode.append(linkCompanyNode); | |||
}); | |||
const outputList = [mainCompanyNode]; | |||
if(linkCompanyParentNode.hasChildren) { outputList.push(linkCompanyParentNode); } | |||
// 排序 | |||
mainCompanyNode.children?.forEach(deptNode=>{ | |||
deptNode.sortChildren(); | |||
}); | |||
return { | |||
treeList: outputList, | |||
memberMap, | |||
} | |||
} | |||
// function plainTreeNodes(nodesList, userList, outputList = []) { | |||
// (nodesList || []).forEach(node => { | |||
// // node.id | |||
// const data = node.data; | |||
// data.listUser = userList.filter(user => user.deptId === node.id); | |||
// outputList.push(data); | |||
// if(node.children && node.children.length) { | |||
// plainTreeNodes(node.children, userList, outputList); | |||
// } | |||
// }); | |||
// return outputList; | |||
// } | |||
const sortFunc = (nodeA, nodeB) => nodeA.label.localeCompare(nodeB.label); | |||
class Node { | |||
// type: 'company' | 'dept' | 'user' | 'empty' | |||
constructor(id, label, parentId, type, data) { | |||
Object.assign(this, { id, label, parentId, type, data }); | |||
} | |||
append(childNode) { | |||
if(!this.children) { this.children = []; } | |||
this.children.push(childNode); | |||
} | |||
sortChildren(f = sortFunc) { | |||
if(!this.hasChildren) { return this; } | |||
this.children.sort(f); | |||
return this; | |||
} | |||
mapChildren(f) { | |||
if(!this.hasChildren) { return []; } | |||
return this.children.map(f); | |||
} | |||
everyChildren(f) { | |||
if(!this.hasChildren) { return false; } | |||
return this.children.every(f); | |||
} | |||
get hasChildren() { | |||
return this.children && !!this.children.length; | |||
} | |||
} |