mobile-workflow/components/OrgPicker.vue
2024-04-30 00:30:46 +08:00

509 lines
12 KiB
Vue

<template>
<uni-popup ref="orgPickerPopup" class="uni-popup" style="touch-action:none" :catchtouchmove="true"
@close="isShowPicker.value = false">
<uni-nav-bar v-if="showNav" statusBar :title="title" color="#fff" backgroundColor="#4C87F3"></uni-nav-bar>
<view class="w-orgPicker-popup" :style="{height: wheight + 'px'}">
<view style="padding: 0 0 16rpx 0; background-color: white;">
<view class="search">
<uni-search-bar v-model="searchValue" bgColor="#EEEEEE" radius="5" placeholder="搜索"
clearButton="auto" cancelButton="none" />
<!-- :readonly="type === 'dept' || type === 'role'" -->
</view>
<view style="padding: 6rpx 16rpx;">
<scroll-view scroll-x>
<uni-breadcrumb separator=">">
<uni-breadcrumb-item v-for="(org, index) in orgPaths" :key="index"
@tap="toDept(org.id, index)">
<view
:style="{color: index + 1 === orgPaths.length ? '#989996':'#1E90FD','font-size': '32rpx'}">
{{org.name}}
</view>
</uni-breadcrumb-item>
</uni-breadcrumb>
</scroll-view>
</view>
</view>
<scroll-view v-if="orgList.length > 0" class="w-org-list" scroll-y :style="{height: contentHeight + 'px'}">
<uni-list>
<uni-list-item clickable v-for="(org, i) in orgDatas" class="w-org-item"
:key="`${org.type}_${org.id}`" :showArrow="props.type === 'user' && org.type !== 'user'"
@click="selectChange(org)">
<template v-slot:header>
<view style="display: flex; align-items: center;">
<radio v-if="!(props.type === 'user' && org.type === 'dept')" :value="org.id"
:checked="org.selected" @click.stop="selectChange(org)"
style="transform:scale(0.9)" />
<view class="w-org-avatar">
<avatar v-if="org.type === 'user'" :name="org.name" :src="org.avatar"
:showName="false">
</avatar>
<image class="w-dept-img" lazy-load mode="aspectFit" v-else
src="/static/image/dept.png"></image>
</view>
</view>
</template>
<template v-slot:body>
<view style="flex: 1; display: flex; align-items: center;">
<text style="display: flex; align-items: center; font-size: 32rpx">{{org.name}}</text>
<view v-if="org.isLeader"
style="display: flex; align-items: center; margin-left: 16rpx;">
<uni-tag style="font-weight: 400;" type="warning" size="mini" text="部门负责人"
inverted></uni-tag>
</view>
</view>
</template>
<template v-slot:footer v-if="props.type !== 'user' && org.type !== 'user'">
<view @click.stop="toInnerOrg(org)" :class="{'w-org-next': true, 'w-org-dis': false}">
<view>下级</view>
<uni-icons type="redo" :size="20" color="#4478F7"></uni-icons>
</view>
</template>
</uni-list-item>
</uni-list>
</scroll-view>
<view v-else :style="{width: '100%', height: contentHeight + 'px'}">
<image mode="aspectFit" style="width: 100%;" src="/static/image/noUser.png"></image>
</view>
<view class="w-orgPicker-options">
<label v-show="props.multiple">
<radio :checked="false" style="transform: scale(0.8);" />
全选
</label>
<view @click="showSelected">
<text>已选 [{{selectedMap.size}}] 项 </text>
<uni-icons type="down" color="#4478F7"></uni-icons>
</view>
<view>
<button class="w-button" style="margin-right: 10px;" type="default" size="mini"
@click="close">取消</button>
<button class="w-button" type="primary" size="mini" @click="selectOk">确认</button>
</view>
</view>
</view>
</uni-popup>
<uni-popup ref="orgPickerSelectedPopup" type="bottom">
<view class="w-orgPicker-selected">
<view class="w-selected" v-for="org in selected" :key="`${org.type}_${org.id}`">
<view>
<avatar :type="org.type" v-if="org.type==='user'" :closeable="org.enableEdit" :src="org.avatar"
:size="25" :name="org.name" :showName="false"></avatar>
</view>
<view>{{org.name}}</view>
<button size="mini" @click="removeSelected(org)">移除</button>
</view>
</view>
</uni-popup>
</template>
<script setup>
import {
onMounted,
computed,
ref,
watch,
nextTick
} from "vue";
import {
onBackPress
} from "@dcloudio/uni-app";
import Avatar from '@/components/Avatar.vue'
import {
getOrgTree,
getUserByName,
getDeptByName,
getEnterpriseByName
} from '@/api/org.js'
const props = defineProps({
title: {
default: '请选择',
type: String
},
type: {
default: 'org', //org选择部门/人员 user-选人 dept-选部门 role-选角色
type: String
},
multiple: { //是否多选
default: false,
type: Boolean
},
selected: {
default: () => {
return []
},
type: Array
},
position: {
type: String,
default: 'right'
},
showNav: Boolean
})
const orgPickerPopup = ref()
const orgPickerSelectedPopup = ref()
const wheight = uni.getSystemInfoSync().windowHeight
const selectAll = ref(false)
const isShowPicker = ref(false)
//已选中的用户/角色/部门
const selectedMap = ref(null)
selectedMap.value = new Map()
//当前展示的部门id
const currentDeptId = ref(0)
const searchValue = ref('')
//搜索结果
const searchUsers = ref([])
//主数据
const orgList = ref([])
//部门路径
const orgPaths = ref([{
name: '组织',
id: 0
}])
const contentHeight = computed(() => {
const baseH = uni.getSystemInfoSync().windowHeight - 168
//减去导航栏高度
// #ifdef H5
return baseH - (props.showNav ? 44 : 0)
// #endif
// #ifndef H5
return baseH - (props.showNav ? 88 : 0)
// #endif
})
const selected = computed(() => {
const temp = []
selectedMap.value.forEach(v => temp.push({
id: v.id,
name: v.name,
avatar: v.avatar,
type: v.type
}))
return temp
})
const selectCount = computed(() => {
return selectedMap.value.size
})
const isSearchMode = ref(false)
const orgDatas = computed(() => {
if (searchValue.value.trim() !== '') {
return searchUsers.value
}
return orgList.value
})
const emit = defineEmits(['ok'])
//监听返回按钮
onBackPress(() => backToParent())
onMounted(() => {
if (props.type == "dept") {
orgPaths.value = [{
name: '部门',
id: 0
}]
}
})
function toDept(orgId, i) {
currentDeptId.value = orgId
orgPaths.value.length = i + 1
getOrgList()
}
function showPicker() {
//加载选中数据
(props.selected || []).forEach(org => {
const _org = Object.assign({}, org)
_org.selected = true
selectedMap.value.set(org.id + org.type, _org)
})
isShowPicker.value = true
}
function backToParent() {
if (orgPaths.value.length > 1) {
orgPaths.value.length--
currentDeptId.value = orgPaths.value[orgPaths.value.length - 1].id
getOrgList()
return true;
} else if (orgPickerPopup.value.close instanceof Function) {
orgPickerPopup.value.close()
return false;
}
}
function toInnerOrg(org) {
console.log(org);
if (org.type === 'dept') {
currentDeptId.value = org.id
orgPaths.value.push({
name: org.name,
id: org.id
})
getOrgList()
} else if (org.type === 'enterprise') {
currentDeptId.value = org.id
orgPaths.value.push({
name: org.name,
id: org.id
})
getOrgList()
}
}
function getOrgList() {
const loginUser = JSON.parse(uni.getStorageSync('loginUser'))
getOrgTree({
deptId: currentDeptId.value + "P" + loginUser.sn,
type: props.type
}).then(rsp => {
orgList.value = rsp.data
loadSelected(orgList.value)
}).catch(err => {
})
}
function searchUser() {
searchUsers.value.length = 0
console.log(props.type, "type");
let request = null;
let name = ""
switch (props.type) {
case "user":
name = "userName"
request = getUserByName
break;
case "enterprise":
name = "enterpriseName";
request = getEnterpriseByName
break;
case "dept":
name = "deptName";
request = getDeptByName
break;
}
request({
[name]: searchValue.value.trim()
}).then(rsp => {
searchUsers.value = rsp.data.map(org => {
org.selected = selectedMap.value.has(org.id + org.type)
return org
})
}).catch(err => {
})
}
/**
* 打开组织架构选择器
* @param {Object} rootDeptId 根部门ID
*/
function show(rootDeptId = 0) {
currentDeptId.value = rootDeptId
orgPaths.value[0].id = rootDeptId
getOrgList()
orgPickerPopup.value.open(props.position)
showPicker()
}
function close() {
selectedMap.value.clear()
orgPickerPopup.value.close()
}
/**
* 确认选中
*/
function selectOk() {
if (selected.value.length === 0) {
uni.showToast({
icon: 'none',
title: '无选中项😥'
})
} else {
emit('ok', selected.value)
close()
}
}
function selectChange(org) {
console.log(org, "org");
if (org.type === 'dept' && props.type === 'user') {
//选用户时不触发部门,直接进入子部门
toInnerOrg(org)
return;
}
const orgId = org.id + org.type
if (selectedMap.value.has(orgId)) {
//取消选中
org.selected = false
selectedMap.value.delete(orgId)
} else {
//选中
if (!props.multiple) {
//单选的话清除之前选的
selectedMap.value.forEach(v => v.selected = false)
// 清空之前选中的
orgDatas.value.forEach(v => v.selected = false)
org.selected = true
selectedMap.value.clear()
}
selectedMap.value.set(orgId, org)
console.log(selectedMap.value, "selectedMap.value", props);
}
}
//加载选中数据状态
function loadSelected(orgs) {
orgs.forEach(org => {
const orgId = org.id + org.type
org.selected = selectedMap.value.has(org.id + org.type)
})
}
function showSelected() {
if (selectedMap.value.size === 0) {
uni.showToast({
icon: 'none',
title: '没有选中的项'
})
} else {
orgPickerSelectedPopup.value.open()
}
}
function removeSelected(org) {
const orgId = org.id + org.type
selectedMap.value.get(orgId).selected = false
selectedMap.value.delete(orgId)
if (selectedMap.value.size === 0) {
orgPickerSelectedPopup.value.close()
}
}
watch(searchValue, () => {
if (searchValue.value.trim() !== '') {
searchUser()
}
})
defineExpose({
show,
close
})
</script>
<style lang="less" scoped>
page {
background-color: #F4F5F7;
}
.w-org-paths {
background-color: white;
}
:deep(.w-org-list) {
font-size: 32rpx;
margin-top: 26rpx;
.w-org-item {
.uni-list-item__container {
align-items: center;
}
}
}
.w-org-avatar {
display: flex;
justify-content: center;
align-items: center;
.w-dept-img {
width: 51rpx;
height: 51rpx;
padding: 13rpx;
border-radius: 10px;
background-color: #c4d7ff;
}
margin-right: 26rpx;
}
.search {
.search-input {
font-size: 32rpx;
background-color: #F4F5F7;
border-radius: 13rpx;
}
}
.w-orgPicker-popup {
width: 100vw;
height: 100vh;
background-color: #F4F5F7;
}
.w-org-next {
color: #4478F7;
display: flex;
align-items: center;
}
.w-orgPicker-options {
height: 64rpx;
padding: 32rpx;
background-color: #F1F1F1;
display: flex;
align-items: center;
&>label {
display: flex;
align-items: center;
}
&>view:nth-child(2) {
flex: 1;
color: #4478F7;
margin-left: 10px;
}
}
.w-orgPicker-selected {
padding: 32rpx;
background-color: white;
.w-selected {
display: flex;
align-items: center;
padding: 5px;
background-color: #F1F1F1;
border-radius: 5px;
margin: 2px 0;
&>view:first-child {
margin-right: 5px;
image {
width: 20px;
height: 20px;
}
}
&>view:nth-child(2) {
flex: 1;
}
}
}
</style>