mirror of https://github.com/jeecgboot/jeecg-boot
新增一个UI组件,支持通过部门选人,更加便捷
parent
cffba084fc
commit
3b34276cf8
|
@ -27,6 +27,7 @@ export { default as JDictSelectTag } from './src/jeecg/components/JDictSelectTag
|
|||
export { default as JTreeSelect } from './src/jeecg/components/JTreeSelect.vue';
|
||||
export { default as JSearchSelect } from './src/jeecg/components/JSearchSelect.vue';
|
||||
export { default as JSelectUserByDept } from './src/jeecg/components/JSelectUserByDept.vue';
|
||||
export { default as JSelectUserByDepartment } from './src/jeecg/components/JSelectUserByDepartment.vue';
|
||||
export { default as JEditor } from './src/jeecg/components/JEditor.vue';
|
||||
export { default as JImageUpload } from './src/jeecg/components/JImageUpload.vue';
|
||||
// Jeecg自定义校验
|
||||
|
|
|
@ -64,6 +64,7 @@ import JInput from './jeecg/components/JInput.vue';
|
|||
import JTreeSelect from './jeecg/components/JTreeSelect.vue';
|
||||
import JEllipsis from './jeecg/components/JEllipsis.vue';
|
||||
import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
|
||||
import JSelectUserByDepartment from './jeecg/components/JSelectUserByDepartment.vue';
|
||||
import JUpload from './jeecg/components/JUpload/JUpload.vue';
|
||||
import JSearchSelect from './jeecg/components/JSearchSelect.vue';
|
||||
import JAddInput from './jeecg/components/JAddInput.vue';
|
||||
|
@ -159,6 +160,7 @@ componentMap.set('JInput', JInput);
|
|||
componentMap.set('JTreeSelect', JTreeSelect);
|
||||
componentMap.set('JEllipsis', JEllipsis);
|
||||
componentMap.set('JSelectUserByDept', JSelectUserByDept);
|
||||
componentMap.set('JSelectUserByDepartment', JSelectUserByDepartment);
|
||||
componentMap.set('JUpload', JUpload);
|
||||
componentMap.set('JSearchSelect', JSearchSelect);
|
||||
componentMap.set('JAddInput', JAddInput);
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
<!--用户选择组件-->
|
||||
<template>
|
||||
<div>
|
||||
<JSelectBiz @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs" @change="handleSelectChange"></JSelectBiz>
|
||||
<JSelectUserByDepartmentModal
|
||||
v-if="modalShow"
|
||||
:selectedUser="selectOptions"
|
||||
:modalTitle="modalTitle"
|
||||
:rowKey="rowKey"
|
||||
:labelKey="labelKey"
|
||||
:isRadioSelection="isRadioSelection"
|
||||
:params="params"
|
||||
@register="regModal"
|
||||
@change="setValue"
|
||||
@close="() => (modalShow = false)"
|
||||
v-bind="attrs"
|
||||
></JSelectUserByDepartmentModal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, provide } from 'vue';
|
||||
import JSelectUserByDepartmentModal from './modal/JSelectUserByDepartmentModal.vue';
|
||||
import JSelectBiz from './base/JSelectBiz.vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { SelectValue } from 'ant-design-vue/es/select';
|
||||
import { isArray, isString, isObject } from '/@/utils/is';
|
||||
import { getTableList as getTableListOrigin } from '/@/api/common/api';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
defineOptions({ name: 'JSelectUserByDepartment' });
|
||||
const props = defineProps({
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '部门用户选择',
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'realname',
|
||||
},
|
||||
//查询参数
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isRadioSelection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['options-change', 'change', 'update:value']);
|
||||
const { createMessage } = useMessage();
|
||||
//注册model
|
||||
const [regModal, { openModal }] = useModal();
|
||||
// 是否显示弹窗
|
||||
const modalShow = ref(false);
|
||||
//下拉框选项值
|
||||
const selectOptions: any = ref<SelectValue>([]);
|
||||
//下拉框选中值
|
||||
let selectValues: any = reactive<object>({
|
||||
value: [],
|
||||
change: false,
|
||||
});
|
||||
// 是否正在加载回显数据
|
||||
const loadingEcho = ref<boolean>(false);
|
||||
//下发 selectOptions,xxxBiz组件接收
|
||||
provide('selectOptions', selectOptions);
|
||||
//下发 selectValues,xxxBiz组件接收
|
||||
provide('selectValues', selectValues);
|
||||
//下发 loadingEcho,xxxBiz组件接收
|
||||
provide('loadingEcho', loadingEcho);
|
||||
|
||||
const attrs: any = useAttrs();
|
||||
|
||||
// 打开弹窗
|
||||
function handleOpen() {
|
||||
modalShow.value = true;
|
||||
setTimeout(() => {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
const handleSelectChange = (data) => {
|
||||
selectOptions.value = selectOptions.value.filter((item) => data.includes(item[props.rowKey]));
|
||||
setValue(selectOptions.value);
|
||||
};
|
||||
// 设置下拉框的值
|
||||
const setValue = (data) => {
|
||||
selectOptions.value = data.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
label: item[props.labelKey],
|
||||
value: item[props.rowKey],
|
||||
};
|
||||
});
|
||||
selectValues.value = data.map((item) => item[props.rowKey]);
|
||||
// 更新value
|
||||
emit('update:value', selectValues.value);
|
||||
// 触发change事件(不转是因为basicForm提交时会自动将字符串转化为数组)
|
||||
emit('change', selectValues.value);
|
||||
// 触发options-change事件
|
||||
emit('options-change', selectOptions.value);
|
||||
};
|
||||
// 翻译
|
||||
const transform = () => {
|
||||
let value = props.value;
|
||||
let len;
|
||||
if (isArray(value) || isString(value)) {
|
||||
if (isArray(value)) {
|
||||
len = value.length;
|
||||
value = value.join(',');
|
||||
} else {
|
||||
len = value.split(',').length;
|
||||
}
|
||||
value = value.trim();
|
||||
if (value) {
|
||||
// 如果value的值在selectedUser中存在,则不请求翻译
|
||||
let isNotRequestTransform = false;
|
||||
isNotRequestTransform = value.split(',').every((value) => !!selectOptions.value.find((item) => item[props.rowKey] === value));
|
||||
if (isNotRequestTransform) {
|
||||
selectValues.value = value.split(',')
|
||||
return;
|
||||
}
|
||||
const params = { isMultiTranslate: true, pageSize: len, [props.rowKey]: value };
|
||||
if (isObject(attrs.params)) {
|
||||
Object.assign(params, attrs.params);
|
||||
}
|
||||
getTableListOrigin(params).then((result: any) => {
|
||||
const records = result.records ?? [];
|
||||
selectValues.value = records.map((item) => item[props.rowKey]);
|
||||
selectOptions.value = records.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
label: item[props.labelKey],
|
||||
value: item[props.rowKey],
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
selectValues.value = [];
|
||||
}
|
||||
};
|
||||
// 监听value变化
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
transform();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.j-select-row {
|
||||
@width: 82px;
|
||||
.left {
|
||||
width: calc(100% - @width - 8px);
|
||||
}
|
||||
.right {
|
||||
width: @width;
|
||||
}
|
||||
.full {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.ant-select-search__field) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,833 @@
|
|||
<template>
|
||||
<BasicModal
|
||||
wrapClassName="JSelectUserByDepartmentModal"
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
:title="modalTitle"
|
||||
width="800px"
|
||||
@ok="handleOk"
|
||||
destroyOnClose
|
||||
@visible-change="visibleChange"
|
||||
>
|
||||
<div class="j-select-user-by-dept">
|
||||
<div class="modal-content">
|
||||
<!-- 左侧搜索和组织列表 -->
|
||||
<div class="left-content">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-box">
|
||||
<a-input v-model:value.trim="searchText" placeholder="搜索" @change="handleSearch" @pressEnter="handleSearch" allowClear />
|
||||
</div>
|
||||
<!-- 组织架构 -->
|
||||
<div class="tree-box">
|
||||
<template v-if="searchText.length">
|
||||
<template v-if="searchResult.depart.length || searchResult.user.length">
|
||||
<div class="search-result">
|
||||
<template v-if="searchResult.user.length">
|
||||
<div class="search-user">
|
||||
<p class="search-user-title">人员</p>
|
||||
<template v-for="item in searchResult.user" :key="item.id">
|
||||
<div class="search-user-item" @click="handleSearchUserCheck(item)">
|
||||
<a-checkbox v-model:checked="item.checked" />
|
||||
<div class="right">
|
||||
<div class="search-user-item-circle">
|
||||
<img v-if="item.avatar" :src="getFileAccessHttpUrl(item.avatar)" alt="avatar" />
|
||||
</div>
|
||||
<div class="search-user-item-info">
|
||||
<div class="search-user-item-name">{{ item.realname }}</div>
|
||||
<div class="search-user-item-org">{{ item.orgCodeTxt }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="searchResult.depart.length">
|
||||
<div class="search-depart">
|
||||
<p class="search-depart-title">部门</p>
|
||||
<template v-for="item in searchResult.depart" :key="item.id">
|
||||
<div class="search-depart-item" @click="handleSearchDepartClick(item)">
|
||||
<a-checkbox v-model:checked="item.checked" @click.stop @change="($event) => handleSearchDepartCheck($event, item)" />
|
||||
<div class="search-depart-item-name">{{ item.departName }}</div>
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="no-data">
|
||||
<a-empty description="暂无数据" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-breadcrumb v-if="breadcrumb.length">
|
||||
<a-breadcrumb-item @click="handleBreadcrumbClick()">
|
||||
<HomeOutlined />
|
||||
</a-breadcrumb-item>
|
||||
<template v-for="item in breadcrumb" :key="item?.id">
|
||||
<a-breadcrumb-item @click="handleBreadcrumbClick(item)">
|
||||
<span>{{ item.departName }}</span>
|
||||
</a-breadcrumb-item>
|
||||
</template>
|
||||
</a-breadcrumb>
|
||||
<div v-if="currentDepartUsers.length">
|
||||
<!-- 当前部门用户树 -->
|
||||
<div class="depart-users-tree">
|
||||
<div v-if="!currentDepartTree.length" class="allChecked">
|
||||
<a-checkbox v-model:checked="currentDepartAllUsers" @change="handleAllUsers">全选</a-checkbox>
|
||||
</div>
|
||||
<template v-for="item in currentDepartUsers" :key="item.id">
|
||||
<div class="depart-users-tree-item" @click="handleDepartUsersTreeCheck(item)">
|
||||
<a-checkbox v-model:checked="item.checked" />
|
||||
<div class="right">
|
||||
<div class="depart-users-tree-item-circle">
|
||||
<img v-if="item.avatar" :src="getFileAccessHttpUrl(item.avatar)" alt="avatar" />
|
||||
</div>
|
||||
<div class="depart-users-tree-item-name">{{ item.realname }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 部门树 -->
|
||||
<div v-if="currentDepartTree.length" class="depart-tree">
|
||||
<template v-for="item in currentDepartTree" :key="item.id">
|
||||
<div class="depart-tree-item" @click="handleDepartTreeClick(item)">
|
||||
<a-checkbox v-model:checked="item.checked" @click.stop @change="($event) => handleDepartTreeCheck($event, item)" />
|
||||
<div class="depart-tree-item-name">{{ item.departName }}</div>
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="currentDepartTree.length === 0 && currentDepartUsers.length === 0" class="no-data">
|
||||
<a-empty description="暂无数据" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧已选人员展示 -->
|
||||
<div class="right-content">
|
||||
<div class="selected-header"> 已选人员:{{ selectedUsers.length }}人 </div>
|
||||
<div class="selected-users">
|
||||
<div class="content">
|
||||
<div v-for="user in selectedUsers" :key="user.id" class="user-avatar" @click="handleDelUser(user)">
|
||||
<div class="avatar-circle">
|
||||
<img v-if="user.avatar" :src="getFileAccessHttpUrl(user.avatar)" alt="avatar" />
|
||||
<div class="mask">
|
||||
<CloseOutlined></CloseOutlined>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-name">{{ user.realname }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { RightOutlined, HomeOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { queryTreeList, getTableList as getTableListOrigin } from '/@/api/common/api';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
defineOptions({ name: 'JSelectUserByDepartmentModal' });
|
||||
const props = defineProps({
|
||||
// ...selectProps,
|
||||
//回传value字段名
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
//回传文本字段名
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'name',
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '部门用户选择',
|
||||
},
|
||||
selectedUser: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
//查询参数
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
//最大选择数量
|
||||
maxSelectCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// 是否单选
|
||||
isRadioSelection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close', 'register', 'change']);
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
const { createMessage } = useMessage();
|
||||
// 搜索文本
|
||||
const searchText = ref('');
|
||||
const breadcrumb = ref<any[]>([]);
|
||||
// 部门树(整颗树)
|
||||
const departTree = ref([]);
|
||||
// 当前部门树
|
||||
const currentDepartTree = ref<any[]>([]);
|
||||
// 选中的部门节点
|
||||
const checkedDepartIds = ref<string[]>([]);
|
||||
// 当前部门用户
|
||||
const currentDepartUsers = ref([]);
|
||||
// 已选用户
|
||||
const selectedUsers = ref<any[]>([]);
|
||||
// 全选
|
||||
const currentDepartAllUsers = ref(false);
|
||||
// 搜索结构
|
||||
const searchResult: any = reactive({
|
||||
depart: [],
|
||||
user: [],
|
||||
});
|
||||
// 映射部门和人员的关系
|
||||
const cacheDepartUser = {};
|
||||
//注册弹框
|
||||
const [register, { closeModal }] = useModalInner(async (data) => {
|
||||
// 初始化
|
||||
if (props.selectedUser.length) {
|
||||
// 编辑时,传进来已选中的数据
|
||||
selectedUsers.value = props.selectedUser;
|
||||
}
|
||||
getQueryTreeList();
|
||||
});
|
||||
const visibleChange = (visible) => {
|
||||
if (visible === false) {
|
||||
setTimeout(() => {
|
||||
emit('close');
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
const handleOk = () => {
|
||||
if (selectedUsers.value.length == 0) {
|
||||
createMessage.warning('请选择人员');
|
||||
return;
|
||||
}
|
||||
if (props.isRadioSelection && selectedUsers.value.length > 1) {
|
||||
createMessage.warning('只允许选择一个用户');
|
||||
return;
|
||||
}
|
||||
if (props.maxSelectCount && selectedUsers.value.length > props.maxSelectCount) {
|
||||
createMessage.warning(`最多只能选择${props.maxSelectCount}个用户`);
|
||||
return;
|
||||
}
|
||||
emit('change', selectedUsers.value);
|
||||
closeModal();
|
||||
};
|
||||
// 搜索人员/部门
|
||||
const handleSearch = () => {
|
||||
if (searchText.value) {
|
||||
defHttp
|
||||
.get({
|
||||
url: `/sys/user/listAll`,
|
||||
params: {
|
||||
column: 'createTime',
|
||||
order: 'desc',
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
realname: `*${searchText.value}*`,
|
||||
},
|
||||
})
|
||||
.then((result: any) => {
|
||||
result.records?.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id == item.id);
|
||||
if (findItem) {
|
||||
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false;
|
||||
}
|
||||
});
|
||||
searchResult.user = result.records ?? [];
|
||||
});
|
||||
searchResult.depart = getDepartByName(searchText.value) ?? [];
|
||||
} else {
|
||||
searchResult.user = [];
|
||||
searchResult.depart = [];
|
||||
}
|
||||
};
|
||||
// 面包屑
|
||||
const handleBreadcrumbClick = (item?) => {
|
||||
// 先清空
|
||||
currentDepartUsers.value = [];
|
||||
if (item) {
|
||||
const findIndex = breadcrumb.value.findIndex((o) => o.id === item.id);
|
||||
if (findIndex != -1) {
|
||||
breadcrumb.value = breadcrumb.value.filter((item, index) => {
|
||||
console.log(item);
|
||||
return index <= findIndex;
|
||||
});
|
||||
}
|
||||
const data = getDepartTreeNodeById(item.id, departTree.value);
|
||||
currentDepartTree.value = data.children;
|
||||
} else {
|
||||
// 根节点
|
||||
currentDepartTree.value = departTree.value;
|
||||
breadcrumb.value = [];
|
||||
}
|
||||
};
|
||||
// 点击部门树复选框触发
|
||||
const handleDepartTreeCheck = (e, item) => {
|
||||
const { target } = e;
|
||||
if (target.checked) {
|
||||
// 选中
|
||||
getUsersByDeptId(item['id']).then((users) => {
|
||||
addUsers(users);
|
||||
});
|
||||
checkedDepartIds.value.push((item as any).id);
|
||||
// 检查父节点下所有子节点是否选中
|
||||
const parentItem = getDepartTreeParentById(item.id);
|
||||
if (parentItem?.children) {
|
||||
const isChildAllChecked = parentItem.children.every((item) => item.checked);
|
||||
if (isChildAllChecked) {
|
||||
parentItem.checked = true;
|
||||
} else {
|
||||
parentItem.checked = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 取消选中
|
||||
const findIndex = checkedDepartIds.value.findIndex((o: any) => o.id === item.id);
|
||||
if (findIndex != -1) {
|
||||
checkedDepartIds.value.splice(findIndex, 1);
|
||||
}
|
||||
// 如果父节点是选中,则需要取消
|
||||
const parentItem = getDepartTreeParentById(item.id);
|
||||
if (parentItem) {
|
||||
parentItem.checked = false;
|
||||
}
|
||||
getUsersByDeptId(item['id']).then((users) => {
|
||||
users.forEach((item) => {
|
||||
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id);
|
||||
if (findIndex != -1) {
|
||||
selectedUsers.value.splice(findIndex, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
// 点击部门树节点触发
|
||||
const handleDepartTreeClick = (item) => {
|
||||
breadcrumb.value = [...breadcrumb.value, item];
|
||||
if (item.children) {
|
||||
// 有子节点,则显示部门
|
||||
if (item.checked) {
|
||||
// 父节点勾选,则子节点全部勾选
|
||||
item.children.forEach((item) => {
|
||||
item.checked = true;
|
||||
});
|
||||
}
|
||||
currentDepartTree.value = item.children;
|
||||
defHttp
|
||||
.get({
|
||||
url: '/sys/sysDepart/getUsersByDepartId',
|
||||
params: {
|
||||
id: item['id'],
|
||||
},
|
||||
})
|
||||
.then((res: any) => {
|
||||
const result = res ?? [];
|
||||
if (item.checked) {
|
||||
// 父节点勾选,则默认勾选
|
||||
result.forEach((item) => {
|
||||
item.checked = true;
|
||||
});
|
||||
}
|
||||
// 右侧勾选了,则默认勾选(用户存在多部门,在别的部门被选中了)
|
||||
if (selectedUsers.value.length) {
|
||||
result.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id === item.id);
|
||||
if (findItem) {
|
||||
// 说明在selectedUsers中被找到
|
||||
item.checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
currentDepartUsers.value = result;
|
||||
});
|
||||
} else {
|
||||
// 没有子节点,则显示用户
|
||||
currentDepartTree.value = [];
|
||||
getTableList({
|
||||
departId: item['id'],
|
||||
}).then((res: any) => {
|
||||
if (res?.records) {
|
||||
let checked = true;
|
||||
res.records.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id == item.id);
|
||||
if (findItem) {
|
||||
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false;
|
||||
checked = false;
|
||||
}
|
||||
});
|
||||
currentDepartAllUsers.value = checked;
|
||||
currentDepartUsers.value = res.records;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// 点击部门用户树复选框触发
|
||||
const handleDepartUsersTreeCheck = (item) => {
|
||||
item.checked = !item.checked;
|
||||
if (item.checked) {
|
||||
addUsers(item);
|
||||
} else {
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id);
|
||||
}
|
||||
if (item.checked == false) {
|
||||
// 有一个是false,则全选false
|
||||
currentDepartAllUsers.value = false;
|
||||
}
|
||||
};
|
||||
// 全选
|
||||
const handleAllUsers = ({ target }) => {
|
||||
const { checked } = target;
|
||||
if (checked) {
|
||||
currentDepartUsers.value.forEach((item: any) => (item.checked = true));
|
||||
addUsers(currentDepartUsers.value);
|
||||
} else {
|
||||
currentDepartUsers.value.forEach((item: any) => (item.checked = false));
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => {
|
||||
const userId = user.id;
|
||||
const findItem = currentDepartUsers.value.find((item: any) => item.id === userId);
|
||||
if (findItem) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// 删除人员
|
||||
const handleDelUser = (item) => {
|
||||
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id);
|
||||
if (findIndex != -1) {
|
||||
selectedUsers.value.splice(findIndex, 1);
|
||||
}
|
||||
const findItem: any = currentDepartUsers.value.find((user: any) => user.id === item.id);
|
||||
if (findItem) {
|
||||
findItem.checked = false;
|
||||
currentDepartAllUsers.value = false;
|
||||
}
|
||||
};
|
||||
// 点击搜索用户复选框
|
||||
const handleSearchUserCheck = (item) => {
|
||||
item.checked = !item.checked;
|
||||
if (item.checked) {
|
||||
addUsers(item);
|
||||
} else {
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id);
|
||||
}
|
||||
};
|
||||
// 点击搜索部门复选框
|
||||
const handleSearchDepartCheck = (e, item) => {
|
||||
handleDepartTreeCheck(e, item);
|
||||
};
|
||||
// 点击搜索部门
|
||||
const handleSearchDepartClick = (item) => {
|
||||
searchResult.depart = [];
|
||||
searchResult.user = [];
|
||||
breadcrumb.value = getPathToNodeById(item.id);
|
||||
handleDepartTreeClick(item);
|
||||
};
|
||||
// 添加人员到右侧
|
||||
const addUsers = (users) => {
|
||||
let newUsers: any = [];
|
||||
if (isArray(users)) {
|
||||
// selectedUsers里面没有才添加(防止重复)
|
||||
newUsers = users.filter((user: any) => !selectedUsers.value.find((item) => item.id === user.id));
|
||||
} else {
|
||||
if (!selectedUsers.value.find((user) => user.id === users.id)) {
|
||||
// selectedUsers里面没有才添加(防止重复)
|
||||
newUsers = [users];
|
||||
}
|
||||
}
|
||||
selectedUsers.value = [...selectedUsers.value, ...newUsers];
|
||||
const result = currentDepartUsers.value.every((item: any) => !!item.checked);
|
||||
currentDepartAllUsers.value = result;
|
||||
};
|
||||
// 解析参数
|
||||
const parseParams = (params) => {
|
||||
if (props?.params) {
|
||||
return {
|
||||
...params,
|
||||
...props.params,
|
||||
};
|
||||
}
|
||||
return params;
|
||||
};
|
||||
const getQueryTreeList = (params?) => {
|
||||
params = parseParams(params);
|
||||
queryTreeList({ ...params }).then((res) => {
|
||||
if (res) {
|
||||
departTree.value = res;
|
||||
currentDepartTree.value = res;
|
||||
}
|
||||
});
|
||||
};
|
||||
// 根据部门id获取用户
|
||||
const getTableList = (params) => {
|
||||
params = parseParams(params);
|
||||
return getTableListOrigin({ ...params });
|
||||
};
|
||||
const getUsersByDeptId = (id) => {
|
||||
return new Promise<any[]>((resolve) => {
|
||||
if (cacheDepartUser[id]) {
|
||||
resolve(cacheDepartUser[id]);
|
||||
} else {
|
||||
getTableList({
|
||||
departId: id,
|
||||
}).then((res: any) => {
|
||||
cacheDepartUser[id] = res.records ?? [];
|
||||
if (res?.records?.length) {
|
||||
resolve(res.records ?? []);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// 根据id获取根节点到当前节点路径
|
||||
const getPathToNodeById = (id: string, tree = departTree.value, path = []): any[] => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return [...path];
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const foundPath = getPathToNodeById(id, (node as any).children, [...path, node]);
|
||||
if (foundPath.length) {
|
||||
return foundPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
// 根据id获取部门树父节点数据
|
||||
const getDepartTreeParentById = (id: string, tree = departTree.value, parent = null): any => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return parent;
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const found = getDepartTreeParentById(id, (node as any).children, node);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
// 通过名称搜索部门支持模糊
|
||||
const getDepartByName = (name: string, tree = departTree.value): any[] => {
|
||||
const result: any[] = [];
|
||||
const search = (nodes: any[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.departName?.toLowerCase().includes(name.toLowerCase())) {
|
||||
result.push(node);
|
||||
}
|
||||
if (node.children?.length) {
|
||||
search(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
search(tree);
|
||||
return result;
|
||||
};
|
||||
// 根据id获取部门树当前节点数据
|
||||
const getDepartTreeNodeById = (id: string, tree = departTree.value): any => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return node;
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const found = getDepartTreeNodeById(id, (node as any).children);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.JSelectUserByDepartmentModal {
|
||||
.scroll-container {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
.j-select-user-by-dept {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.modal-content {
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
height: 400px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.left-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
.search-box {
|
||||
margin: 0 16px 16px 16px;
|
||||
}
|
||||
:deep(.ant-breadcrumb) {
|
||||
font-size: 12px;
|
||||
margin-left: 16px;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
li {
|
||||
.ant-breadcrumb-link {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
.ant-breadcrumb-link {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tree-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
.no-data {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
.depart-tree {
|
||||
.depart-tree-item {
|
||||
padding: 0 16px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
}
|
||||
.depart-tree-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.depart-users-tree {
|
||||
.allChecked {
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
padding-top: 12px;
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.depart-users-tree-item {
|
||||
line-height: 50px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.depart-users-tree-item-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #aaa;
|
||||
overflow: hidden;
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.depart-users-tree-item-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.search-depart {
|
||||
margin-bottom: 8px;
|
||||
.search-depart-title {
|
||||
padding-left: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.search-depart-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.search-depart-item-name {
|
||||
margin-left: 8px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-user {
|
||||
margin-bottom: 8px;
|
||||
.search-user-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.search-user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.search-user-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.search-user-item-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background-color: #aaa;
|
||||
}
|
||||
.search-user-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search-user-item-org {
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right-content {
|
||||
width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 16px;
|
||||
.selected-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.selected-users {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
.user-avatar {
|
||||
text-align: center;
|
||||
width: 70px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.avatar-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
margin: 0 auto 8px;
|
||||
position: relative;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
&:hover {
|
||||
.mask {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.mask {
|
||||
opacity: 0;
|
||||
transition: opacity;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
.user-name {
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -139,6 +139,7 @@ export type ComponentType =
|
|||
| 'JTreeSelect'
|
||||
| 'JEllipsis'
|
||||
| 'JSelectUserByDept'
|
||||
| 'JSelectUserByDepartment'
|
||||
| 'JUpload'
|
||||
| 'JSearchSelect'
|
||||
| 'JAddInput'
|
||||
|
|
|
@ -258,6 +258,20 @@ export const schemas: FormSchema[] = [
|
|||
label: '选中用户',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
field: 'user4',
|
||||
component: 'JSelectUserByDepartment',
|
||||
label: '部门选择用户',
|
||||
helpMessage: ['component模式'],
|
||||
defaultValue: '',
|
||||
componentProps: {
|
||||
labelKey: 'realname',
|
||||
rowKey: 'username',
|
||||
},
|
||||
colProps: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'role2',
|
||||
component: 'JSelectRole',
|
||||
|
|
|
@ -144,7 +144,7 @@ export const formSchema: FormSchema[] = [
|
|||
{
|
||||
field: 'userIds',
|
||||
label: '指定用户',
|
||||
component: 'JSelectUser',
|
||||
component: 'JSelectUserByDepartment',
|
||||
required: true,
|
||||
componentProps: {
|
||||
rowKey: 'id',
|
||||
|
|
Loading…
Reference in New Issue