新增一个UI组件,支持通过部门选人,更加便捷

pull/8257/head^2
JEECG 2025-05-04 16:34:12 +08:00
parent cffba084fc
commit 3b34276cf8
7 changed files with 1028 additions and 1 deletions

View File

@ -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

View File

@ -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);

View File

@ -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);
// changebasicForm
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) {
// valueselectedUser
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>

View File

@ -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) {
// falsefalse
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>

View File

@ -139,6 +139,7 @@ export type ComponentType =
| 'JTreeSelect'
| 'JEllipsis'
| 'JSelectUserByDept'
| 'JSelectUserByDepartment'
| 'JUpload'
| 'JSearchSelect'
| 'JAddInput'

View File

@ -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',

View File

@ -144,7 +144,7 @@ export const formSchema: FormSchema[] = [
{
field: 'userIds',
label: '指定用户',
component: 'JSelectUser',
component: 'JSelectUserByDepartment',
required: true,
componentProps: {
rowKey: 'id',