|
|
|
|
@ -258,31 +258,64 @@
|
|
|
|
|
<u-popup v-model="deptModal.isShow" mode="bottom" border-radius="16" height="980" :mask-close-able="true" @close="deptModal.isShow = false">
|
|
|
|
|
<div class="dept-picker-wrap">
|
|
|
|
|
<div class="g_fs_18 g_c_3 g_flex_row_center g_pt_16 g_pb_16">选择部门</div>
|
|
|
|
|
<div class="dept-columns-scroll" style="overflow-x: auto;width: 100vw;">
|
|
|
|
|
<div class="dept-columns g_flex_row_start">
|
|
|
|
|
<div class="dept-column" v-for="(column, colIndex) in deptColumns" :key="colIndex" :id="'dept-column-' + colIndex">
|
|
|
|
|
<div :style="{ paddingTop: (columnPaddingTops[colIndex] || 0) + 'px' }">
|
|
|
|
|
<div
|
|
|
|
|
class="g_flex_row_between g_pt_16 g_pb_16"
|
|
|
|
|
style="border-bottom: 1px solid #ddd;"
|
|
|
|
|
v-for="(dept, deptIndex) in column"
|
|
|
|
|
:key="dept.id"
|
|
|
|
|
:id="'dept-item-' + colIndex + '-' + deptIndex"
|
|
|
|
|
@click="handleDeptClick(dept, colIndex, deptIndex)"
|
|
|
|
|
>
|
|
|
|
|
<div class="g_ell_1 g_flex_1" :style="{ color: '#333', fontSize: '14px', flex: 1, marginRight: '8px',padding:'0 4px' }">
|
|
|
|
|
{{ dept.teamName }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="g_flex_column_center g_flex_none" style="margin-right: 4px;">
|
|
|
|
|
<i
|
|
|
|
|
class="iconfont icon-a-duigoubeifen2"
|
|
|
|
|
style="font-size: 24px"
|
|
|
|
|
:style="{
|
|
|
|
|
color: isDeptSelected(dept.id) ? '#6A81FF' : '#fff',
|
|
|
|
|
}"
|
|
|
|
|
></i>
|
|
|
|
|
<!-- 面包屑导航 -->
|
|
|
|
|
<div class="dept-breadcrumb g_flex_row_start g_pl_12 g_pr_12 g_pb_8" v-if="deptBreadcrumb.length > 0">
|
|
|
|
|
<div class="g_flex_row_start g_fs_14" style="flex-wrap: wrap;">
|
|
|
|
|
<div
|
|
|
|
|
class="g_flex_row_start"
|
|
|
|
|
v-for="(crumb, index) in deptBreadcrumb"
|
|
|
|
|
:key="index"
|
|
|
|
|
@click="navigateToDeptLevel(index)"
|
|
|
|
|
>
|
|
|
|
|
<div :style="{ color: index === deptBreadcrumb.length - 1 ? '#333' : '#6A81FF' }">{{ crumb.teamName }}</div>
|
|
|
|
|
<div class="g_c_9 g_ml_4 g_mr_4" v-if="index < deptBreadcrumb.length - 1">/</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 部门列表 -->
|
|
|
|
|
<scroll-view scroll-y="true" class="dept-list-scroll">
|
|
|
|
|
<div>
|
|
|
|
|
<div
|
|
|
|
|
class="dept-list-item"
|
|
|
|
|
v-for="(dept, deptIndex) in currentDeptList"
|
|
|
|
|
:key="dept.id"
|
|
|
|
|
>
|
|
|
|
|
<!-- 复选框区域:点击选中 -->
|
|
|
|
|
<div class="dept-checkbox-area" @click="toggleDeptSelect(dept)">
|
|
|
|
|
<i
|
|
|
|
|
class="iconfont"
|
|
|
|
|
:class="isDeptSelected(dept.id) ? 'icon-check-circle-fill' : 'icon-yuanxingweixuanzhong'"
|
|
|
|
|
:style="{ fontSize: '20px', color: isDeptSelected(dept.id) ? '#6A81FF' : '#cccccc' }"
|
|
|
|
|
></i>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 内容区域:点击进入子级 -->
|
|
|
|
|
<div class="dept-content-area" @click="enterDeptLevel(dept)">
|
|
|
|
|
<div class="dept-folder-icon g_flex_none">
|
|
|
|
|
<div class="g_flex_c dept-folder-bg">
|
|
|
|
|
<i class="iconfont icon-folder-fill"></i>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="g_ell_1 dept-content-name">{{ dept.teamName }}</div>
|
|
|
|
|
<i class="iconfont icon-gengduo11 dept-content-arrow" v-if="dept.childs && dept.childs.length > 0"></i>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="currentDeptList.length === 0" class="g_text_c g_c_9 g_pt_40 g_pb_40">
|
|
|
|
|
暂无数据
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
<!-- 已选部门 -->
|
|
|
|
|
<div class="dept-selected-wrap" v-if="selectedDeptIds.length > 0">
|
|
|
|
|
<div class="dept-selected-scroll">
|
|
|
|
|
<div class="g_flex_row_start" style="flex-wrap: wrap;">
|
|
|
|
|
<div
|
|
|
|
|
class="dept-selected-tag g_flex_row_center g_fs_13"
|
|
|
|
|
v-for="(id, sIndex) in selectedDeptIds"
|
|
|
|
|
:key="id"
|
|
|
|
|
@click="removeSelectedDept(sIndex)"
|
|
|
|
|
>
|
|
|
|
|
{{ selectedDeptNames[sIndex] }}
|
|
|
|
|
<i class="iconfont icon-qingchu1 g_ml_4" style="font-size: 12px"></i>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -377,12 +410,11 @@ export default {
|
|
|
|
|
deptModal: {
|
|
|
|
|
isShow: false,
|
|
|
|
|
},
|
|
|
|
|
deptColumns: [], // 级联列:[[{id,teamName,childs}], [...]]
|
|
|
|
|
expandedDeptIds: [], // 当前展开路径上的部门id集合,用于级联展示
|
|
|
|
|
currentDeptList: [], // 当前层级的部门列表
|
|
|
|
|
deptBreadcrumb: [], // 部门导航面包屑(路径上的各级节点)
|
|
|
|
|
selectedDeptIds: [], // 选中的部门id集合
|
|
|
|
|
selectedDeptNames: [], // 选中的部门名称集合
|
|
|
|
|
currentDeptItem: null, // 当前编辑的表单项
|
|
|
|
|
columnPaddingTops: {}, // 每列的内边距顶部偏移,用于子级列和父级节点垂直对齐
|
|
|
|
|
roleModal: {
|
|
|
|
|
isShow: false,
|
|
|
|
|
},
|
|
|
|
|
@ -827,54 +859,13 @@ export default {
|
|
|
|
|
this.selectedDeptIds = [];
|
|
|
|
|
this.selectedDeptNames = [];
|
|
|
|
|
}
|
|
|
|
|
// 根据已选中的部门初始化展开路径
|
|
|
|
|
this.expandedDeptIds = this.buildExpandedPath(this.deptList || [], item.deptIds || []);
|
|
|
|
|
this.columnPaddingTops = {};
|
|
|
|
|
this.deptColumns = this.buildCascadingColumns(this.deptList || []);
|
|
|
|
|
// 初始化为根级列表,面包屑为空
|
|
|
|
|
this.currentDeptList = [].concat(this.deptList || []);
|
|
|
|
|
this.deptBreadcrumb = [];
|
|
|
|
|
this.deptModal.isShow = true;
|
|
|
|
|
},
|
|
|
|
|
// 根据已选中的部门id,构建展开路径
|
|
|
|
|
buildExpandedPath(list, selectedIds) {
|
|
|
|
|
if (!list || list.length === 0 || !selectedIds || selectedIds.length === 0) return [];
|
|
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
|
|
|
let item = list[i];
|
|
|
|
|
if (selectedIds.indexOf(item.id) > -1) {
|
|
|
|
|
// 找到了选中的节点,返回包含该节点的路径
|
|
|
|
|
return [item.id];
|
|
|
|
|
}
|
|
|
|
|
if (item.childs && item.childs.length > 0) {
|
|
|
|
|
let childPath = this.buildExpandedPath(item.childs, selectedIds);
|
|
|
|
|
if (childPath.length > 0) {
|
|
|
|
|
return [item.id].concat(childPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
},
|
|
|
|
|
// 根据展开路径构建级联列
|
|
|
|
|
buildCascadingColumns(list) {
|
|
|
|
|
if (!list || list.length === 0) return [];
|
|
|
|
|
let columns = [list];
|
|
|
|
|
let currentList = list;
|
|
|
|
|
for (let i = 0; i < this.expandedDeptIds.length; i++) {
|
|
|
|
|
let expandedId = this.expandedDeptIds[i];
|
|
|
|
|
let found = currentList.find(item => item.id === expandedId);
|
|
|
|
|
if (found && found.childs && found.childs.length > 0) {
|
|
|
|
|
columns.push(found.childs);
|
|
|
|
|
currentList = found.childs;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return columns;
|
|
|
|
|
},
|
|
|
|
|
// 判断某节点是否在当前展开路径上
|
|
|
|
|
isParentExpanded(dept, colIndex) {
|
|
|
|
|
return this.expandedDeptIds[colIndex] === dept.id;
|
|
|
|
|
},
|
|
|
|
|
// 点击部门项:切换选中 + 展开/收起下一级
|
|
|
|
|
handleDeptClick(dept, colIndex, deptIndex) {
|
|
|
|
|
// 切换选中状态
|
|
|
|
|
// 切换部门选中状态(点击复选框/名称区域)
|
|
|
|
|
toggleDeptSelect(dept) {
|
|
|
|
|
let idx = this.selectedDeptIds.indexOf(dept.id);
|
|
|
|
|
if (idx > -1) {
|
|
|
|
|
this.selectedDeptIds.splice(idx, 1);
|
|
|
|
|
@ -883,55 +874,37 @@ export default {
|
|
|
|
|
this.selectedDeptIds.push(dept.id);
|
|
|
|
|
this.selectedDeptNames.push(dept.teamName);
|
|
|
|
|
}
|
|
|
|
|
// 处理展开/收起:点击有子级的节点时,展开或收起其子级
|
|
|
|
|
if (dept.childs && dept.childs.length > 0) {
|
|
|
|
|
if (this.expandedDeptIds[colIndex] === dept.id) {
|
|
|
|
|
// 已展开,则收起该节点及其后续展开
|
|
|
|
|
this.expandedDeptIds = this.expandedDeptIds.slice(0, colIndex);
|
|
|
|
|
// 清除后续列的 padding-top
|
|
|
|
|
this.clearColumnPaddingTops(colIndex);
|
|
|
|
|
} else {
|
|
|
|
|
// 未展开或展开了其他节点,则设置当前节点为展开,并截断后续
|
|
|
|
|
this.expandedDeptIds = this.expandedDeptIds.slice(0, colIndex);
|
|
|
|
|
this.expandedDeptIds.push(dept.id);
|
|
|
|
|
}
|
|
|
|
|
this.deptColumns = this.buildCascadingColumns(this.deptList || []);
|
|
|
|
|
// 下一帧滚动新展开的子级列,使其顶部与当前点击项对齐
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.alignColumnToParent(colIndex, deptIndex);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 清除指定列之后所有列的 padding-top
|
|
|
|
|
clearColumnPaddingTops(colIndex) {
|
|
|
|
|
let newPaddingTops = {};
|
|
|
|
|
Object.keys(this.columnPaddingTops).forEach(key => {
|
|
|
|
|
if (Number(key) <= colIndex) {
|
|
|
|
|
newPaddingTops[key] = this.columnPaddingTops[key];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.columnPaddingTops = newPaddingTops;
|
|
|
|
|
// 进入子级部门列表(点击右箭头)
|
|
|
|
|
enterDeptLevel(dept) {
|
|
|
|
|
if (!dept.childs || dept.childs.length === 0) return;
|
|
|
|
|
this.deptBreadcrumb.push(dept);
|
|
|
|
|
this.currentDeptList = [].concat(dept.childs);
|
|
|
|
|
},
|
|
|
|
|
// 滚动子级列,使其顶部与父级节点垂直对齐
|
|
|
|
|
alignColumnToParent(colIndex, deptIndex) {
|
|
|
|
|
let that = this;
|
|
|
|
|
let parentItemId = '#dept-item-' + colIndex + '-' + deptIndex;
|
|
|
|
|
let childColumnId = '#dept-column-' + (colIndex + 1);
|
|
|
|
|
let firstColumnId = '#dept-column-0';
|
|
|
|
|
uni.createSelectorQuery().in(that).select(parentItemId).boundingClientRect(function(parentRect) {
|
|
|
|
|
if (!parentRect) return;
|
|
|
|
|
uni.createSelectorQuery().in(that).select(firstColumnId).boundingClientRect(function(firstRect) {
|
|
|
|
|
if (!firstRect) return;
|
|
|
|
|
// 计算父级节点相对于第一列顶部的偏移量
|
|
|
|
|
let offset = parentRect.top - firstRect.top;
|
|
|
|
|
if (offset > 0) {
|
|
|
|
|
// 给子级列设置相同的 padding-top,使子级列第一项与父级节点垂直对齐
|
|
|
|
|
let newPaddingTops = Object.assign({}, that.columnPaddingTops);
|
|
|
|
|
newPaddingTops[colIndex + 1] = offset;
|
|
|
|
|
that.columnPaddingTops = newPaddingTops;
|
|
|
|
|
}
|
|
|
|
|
}).exec();
|
|
|
|
|
}).exec();
|
|
|
|
|
// 通过面包屑导航到指定层级
|
|
|
|
|
navigateToDeptLevel(index) {
|
|
|
|
|
// 点击最后一项(当前层级)不做处理
|
|
|
|
|
if (index === this.deptBreadcrumb.length - 1) return;
|
|
|
|
|
// 截断面包屑到目标层级
|
|
|
|
|
this.deptBreadcrumb = this.deptBreadcrumb.slice(0, index + 1);
|
|
|
|
|
let targetDept = this.deptBreadcrumb[this.deptBreadcrumb.length - 1];
|
|
|
|
|
this.currentDeptList = [].concat(targetDept.childs || []);
|
|
|
|
|
},
|
|
|
|
|
// 返回上一级
|
|
|
|
|
backToParentDept() {
|
|
|
|
|
if (this.deptBreadcrumb.length === 0) return;
|
|
|
|
|
this.deptBreadcrumb.pop();
|
|
|
|
|
if (this.deptBreadcrumb.length === 0) {
|
|
|
|
|
this.currentDeptList = [].concat(this.deptList || []);
|
|
|
|
|
} else {
|
|
|
|
|
let parentDept = this.deptBreadcrumb[this.deptBreadcrumb.length - 1];
|
|
|
|
|
this.currentDeptList = [].concat(parentDept.childs || []);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 移除已选部门标签
|
|
|
|
|
removeSelectedDept(index) {
|
|
|
|
|
this.selectedDeptIds.splice(index, 1);
|
|
|
|
|
this.selectedDeptNames.splice(index, 1);
|
|
|
|
|
},
|
|
|
|
|
// 提交部门选择
|
|
|
|
|
submitDept() {
|
|
|
|
|
@ -1071,6 +1044,85 @@ export default {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
.dept-breadcrumb {
|
|
|
|
|
width: 100%;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
border-bottom: 1px solid #f5f5f5;
|
|
|
|
|
}
|
|
|
|
|
.dept-list-scroll {
|
|
|
|
|
flex: 1;
|
|
|
|
|
width: 100%;
|
|
|
|
|
-webkit-overflow-scrolling: touch;
|
|
|
|
|
}
|
|
|
|
|
.dept-list-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
height: 56px;
|
|
|
|
|
}
|
|
|
|
|
.dept-checkbox-area {
|
|
|
|
|
width: 56px;
|
|
|
|
|
height: 56px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
.dept-content-area {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
align-items: center;
|
|
|
|
|
border-bottom: 1px solid #f5f5f5;
|
|
|
|
|
padding-right: 12px;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
.dept-folder-icon {
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
}
|
|
|
|
|
.dept-folder-bg {
|
|
|
|
|
width: 40px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
background-color: rgba(2, 122, 255, 0.1);
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
}
|
|
|
|
|
.dept-folder-bg .iconfont {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
}
|
|
|
|
|
.dept-content-name {
|
|
|
|
|
color: #333;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
.dept-content-arrow {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
color: #cccccc;
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
.dept-selected-wrap {
|
|
|
|
|
width: 100%;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
background-color: #fafafa;
|
|
|
|
|
border-top: 1px solid #f0f0f0;
|
|
|
|
|
padding: 12rpx 24rpx;
|
|
|
|
|
}
|
|
|
|
|
.dept-selected-scroll {
|
|
|
|
|
max-height: 160rpx;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
-webkit-overflow-scrolling: touch;
|
|
|
|
|
}
|
|
|
|
|
.dept-selected-tag {
|
|
|
|
|
background-color: #f0f2ff;
|
|
|
|
|
color: #6A81FF;
|
|
|
|
|
padding: 6rpx 16rpx;
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
margin-right: 12rpx;
|
|
|
|
|
margin-bottom: 12rpx;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
.dept-columns-scroll {
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
@ -1094,11 +1146,7 @@ export default {
|
|
|
|
|
min-width: 100%;
|
|
|
|
|
}
|
|
|
|
|
.dept-column {
|
|
|
|
|
width: 30vw;
|
|
|
|
|
min-width: 180px;
|
|
|
|
|
max-width: 30vw;
|
|
|
|
|
height: 100%;
|
|
|
|
|
border-right: 1px solid #ddd;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
flex-grow: 0;
|
|
|
|
|
|