|
@@ -0,0 +1,362 @@
|
|
|
+<template>
|
|
|
+ <div class="le-select-user">
|
|
|
+ <el-input :model-value="modelValue" style="display: none" />
|
|
|
+ <el-tooltip v-if="!disabled" content="选择人员" placement="left">
|
|
|
+ <el-button style="width: 32px" @click="selectHandler">
|
|
|
+ <svg-icon style="font-size: 18px" icon-class="flow-user-add" />
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <FlowNodeAvatar v-for="(item, i) of modelValue" :key="i" :name="item.name" />
|
|
|
+ <el-dialog v-model="dialogVisible" class="le-dialog" title="选择人员" :width="680" destroy-on-close append-to-body>
|
|
|
+ <div class="sc-user-select sc-user-select-role">
|
|
|
+ <div class="sc-user-select__left">
|
|
|
+ <div class="sc-user-select__search">
|
|
|
+ <el-input v-model="searchParams.username" prefix-icon="Search" placeholder="搜索成员" @keyup.enter="searchHandler">
|
|
|
+ <template #append>
|
|
|
+ <el-button icon="Search" @click="searchHandler"></el-button>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sc-user-select__select">
|
|
|
+ <div v-loading="groupData.loading" class="sc-user-select__tree">
|
|
|
+ <el-scrollbar>
|
|
|
+ <el-tree
|
|
|
+ ref="groupRef"
|
|
|
+ class="menu"
|
|
|
+ :data="groupData.list"
|
|
|
+ :node-key="groupProps.key"
|
|
|
+ :props="groupProps"
|
|
|
+ highlight-current
|
|
|
+ :expand-on-click-node="false"
|
|
|
+ :current-node-key="searchParams.departmentId"
|
|
|
+ @node-click="groupClick"
|
|
|
+ />
|
|
|
+ </el-scrollbar>
|
|
|
+ </div>
|
|
|
+ <div v-loading="dataInfo.loading" class="sc-user-select__user">
|
|
|
+ <div class="sc-user-select__user__list">
|
|
|
+ <el-scrollbar ref="userScrollbar">
|
|
|
+ <el-tree
|
|
|
+ ref="treeRef"
|
|
|
+ class="menu"
|
|
|
+ :data="dataInfo.list"
|
|
|
+ :node-key="userProps.key"
|
|
|
+ :props="userProps"
|
|
|
+ :default-checked-keys="selectedIds"
|
|
|
+ show-checkbox
|
|
|
+ check-on-click-node
|
|
|
+ @check-change="checkChange"
|
|
|
+ ></el-tree>
|
|
|
+ </el-scrollbar>
|
|
|
+ </div>
|
|
|
+ <footer>
|
|
|
+ <el-pagination
|
|
|
+ v-model:currentPage="searchParams.currentPage"
|
|
|
+ background
|
|
|
+ layout="prev,next"
|
|
|
+ small
|
|
|
+ :total="dataInfo.total"
|
|
|
+ :page-size="searchParams.pageSize"
|
|
|
+ @current-change="queryData()"
|
|
|
+ ></el-pagination>
|
|
|
+ </footer>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="sc-user-select__toicon">
|
|
|
+ <el-icon><ArrowRight /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="sc-user-select__selected">
|
|
|
+ <header>已选 ({{ selected.length }})</header>
|
|
|
+ <ul style="padding: 0; margin: 0">
|
|
|
+ <el-scrollbar>
|
|
|
+ <li v-for="(item, index) in selected" :key="item.id">
|
|
|
+ <span class="name">
|
|
|
+ <label>{{ item.name || '-' }}</label>
|
|
|
+ </span>
|
|
|
+ <span class="delete">
|
|
|
+ <el-button type="danger" icon="Delete" circle size="small" @click="removeItem(index)" />
|
|
|
+ </span>
|
|
|
+ </li>
|
|
|
+ </el-scrollbar>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submit">确认</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script lang="tsx" setup>
|
|
|
+import { watch, unref, ref, computed, PropType } from 'vue'
|
|
|
+import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
|
|
|
+import user from '@/api/system/user.ts'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import department from '@/api/system/department.ts'
|
|
|
+
|
|
|
+defineOptions({ name: 'LeSelectRole' })
|
|
|
+const emit = defineEmits(['update:modelValue', 'change', 'input', 'update:visible', 'getCurRow'])
|
|
|
+type Item = { name: string; id: string; [key: string]: any }
|
|
|
+const props = defineProps({
|
|
|
+ modelValue: {
|
|
|
+ type: Array as PropType<Item[]>,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ min: { type: Number, default: 0 },
|
|
|
+ max: { type: Number, default: 999999 },
|
|
|
+ disabled: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ }
|
|
|
+ // params: {
|
|
|
+ // type: Object
|
|
|
+ // }
|
|
|
+})
|
|
|
+const groupRef = ref()
|
|
|
+const treeRef = ref()
|
|
|
+const userScrollbar = ref()
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const dataInfo = ref({ list: [], loading: false, total: 0 })
|
|
|
+const groupProps = {
|
|
|
+ key: 'id',
|
|
|
+ label: 'name',
|
|
|
+ children: 'children'
|
|
|
+}
|
|
|
+const userProps = {
|
|
|
+ key: 'id',
|
|
|
+ label: 'realName'
|
|
|
+}
|
|
|
+
|
|
|
+// 命中的数据
|
|
|
+const selected = ref<Item[]>([])
|
|
|
+const selectedIds = computed(() => {
|
|
|
+ return selected.value.map(v => v.id)
|
|
|
+})
|
|
|
+watch(
|
|
|
+ () => props.modelValue,
|
|
|
+ vals => {
|
|
|
+ selected.value = JSON.parse(JSON.stringify(vals || []))
|
|
|
+ },
|
|
|
+ {
|
|
|
+ immediate: true,
|
|
|
+ deep: true
|
|
|
+ }
|
|
|
+)
|
|
|
+const checkChange = (data, checked) => {
|
|
|
+ if (checked) {
|
|
|
+ selected.value.push({
|
|
|
+ id: data.id,
|
|
|
+ name: data.realName
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ const idx = selected.value.findIndex(v => v.id === data.id)
|
|
|
+ if (idx >= 0) {
|
|
|
+ selected.value.splice(idx, 1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+const removeItem = (index: number) => {
|
|
|
+ selected.value.splice(index, 1)
|
|
|
+ treeRef.value?.setCheckedKeys(selectedIds.value)
|
|
|
+}
|
|
|
+const selectHandler = () => {
|
|
|
+ dialogVisible.value = true
|
|
|
+ selected.value = JSON.parse(JSON.stringify(props.modelValue || []))
|
|
|
+ const _group = groupData.value
|
|
|
+ if (!_group.loading && !_group.list.length) {
|
|
|
+ queryGroupData()
|
|
|
+ }
|
|
|
+ const _info = dataInfo.value
|
|
|
+ if (!_info.loading && !_info.list.length || searchParams.value.username || searchParams.value.departmentId) {
|
|
|
+ queryData(true)
|
|
|
+ }
|
|
|
+}
|
|
|
+const searchParams = ref({
|
|
|
+ username: undefined,
|
|
|
+ departmentId: undefined,
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 20
|
|
|
+})
|
|
|
+const groupData = ref({ list: [], loading: false })
|
|
|
+const groupClick = (data: any) => {
|
|
|
+ Object.assign(searchParams.value, { departmentId: data.id, currentPage: 1 })
|
|
|
+ queryData()
|
|
|
+}
|
|
|
+const queryGroupData = () => {
|
|
|
+ groupData.value.loading = true
|
|
|
+ department
|
|
|
+ .departmentPageApi({ page: 1, pageSize: 9999 })
|
|
|
+ .then((res: any) => {
|
|
|
+ const list = res || []
|
|
|
+ list.unshift({ id: '', name: '所有' })
|
|
|
+ groupData.value = {
|
|
|
+ list,
|
|
|
+ loading: false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ groupData.value.loading = false
|
|
|
+ })
|
|
|
+}
|
|
|
+const queryData = (init = false) => {
|
|
|
+ if (init) {
|
|
|
+ searchParams.value.username = undefined
|
|
|
+ searchParams.value.departmentId = undefined
|
|
|
+ }
|
|
|
+ dataInfo.value.loading = true
|
|
|
+ const _search = searchParams.value
|
|
|
+ const params = {
|
|
|
+ data: {
|
|
|
+ username: _search.username,
|
|
|
+ departmentId: _search.departmentId
|
|
|
+ },
|
|
|
+ page: _search.currentPage,
|
|
|
+ pageSize: _search.pageSize
|
|
|
+ }
|
|
|
+ user
|
|
|
+ .userPageApi(params)
|
|
|
+ .then((res: any) => {
|
|
|
+ dataInfo.value = {
|
|
|
+ list: res.records || [],
|
|
|
+ total: res.total,
|
|
|
+ loading: false
|
|
|
+ }
|
|
|
+ // 滚动到顶部
|
|
|
+ userScrollbar.value?.setScrollTop(0)
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ dataInfo.value.loading = false
|
|
|
+ })
|
|
|
+}
|
|
|
+const searchHandler = () => {
|
|
|
+ const _id = (searchParams.value.departmentId = '')
|
|
|
+ groupRef.value.setCurrentKey(_id)
|
|
|
+ searchParams.value.currentPage = 1
|
|
|
+ queryData()
|
|
|
+}
|
|
|
+const submit = () => {
|
|
|
+ // props.modelValue
|
|
|
+ const _selected = selected.value
|
|
|
+ if (!Array.isArray(_selected)) return ElMessage.warning('请选择数据')
|
|
|
+ // 最小限制
|
|
|
+ if (_selected.length < props.min) return ElMessage.warning(`选中的数据个数不能小于${props.min}条`)
|
|
|
+ // 最大限制
|
|
|
+ if (_selected.length > props.max) return ElMessage.warning(`选中的数据个数不能大于${props.max}条`)
|
|
|
+ dialogVisible.value = false
|
|
|
+ emit('update:modelValue', _selected)
|
|
|
+ emit('change', _selected)
|
|
|
+ // emit('input', _selected)
|
|
|
+}
|
|
|
+// const { prefixIcon, suffixIcon, prop, controlsPosition, t_placeholder, size, placeholder, max = 999999999999999, ...local_props } = ctx.attrs
|
|
|
+</script>
|
|
|
+<style scoped lang="scss">
|
|
|
+.le-select-user {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ min-height: 32px;
|
|
|
+}
|
|
|
+.sc-user-select {
|
|
|
+ display: flex;
|
|
|
+ .sc-user-select__left {
|
|
|
+ //width: 200px;
|
|
|
+ width: 400px;
|
|
|
+ }
|
|
|
+ .sc-user-select__search {
|
|
|
+ padding-bottom: 10px;
|
|
|
+ }
|
|
|
+ .sc-user-select__tree {
|
|
|
+ width: 200px;
|
|
|
+ height: 300px;
|
|
|
+ border-right: 1px solid var(--el-border-color-light);
|
|
|
+ //border: none;
|
|
|
+ //height: 343px;
|
|
|
+ }
|
|
|
+ .sc-user-select__user {
|
|
|
+ width: 200px;
|
|
|
+ height: 300px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+ .sc-user-select__user__list {
|
|
|
+ flex: 1;
|
|
|
+ overflow: auto;
|
|
|
+ }
|
|
|
+ .sc-user-select__user footer {
|
|
|
+ height: 36px;
|
|
|
+ padding-top: 5px;
|
|
|
+ border-top: 1px solid var(--el-border-color-light);
|
|
|
+ }
|
|
|
+
|
|
|
+ .sc-user-select__toicon {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ margin: 0 10px;
|
|
|
+ }
|
|
|
+ .sc-user-select__toicon i {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ background: #ccc;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 20px;
|
|
|
+ border-radius: 50%;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ .sc-user-select__select {
|
|
|
+ display: flex;
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+ background: var(--el-color-white);
|
|
|
+ }
|
|
|
+ .sc-user-select__selected {
|
|
|
+ height: 345px;
|
|
|
+ width: 200px;
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+ background: var(--el-color-white);
|
|
|
+ header {
|
|
|
+ height: 43px;
|
|
|
+ line-height: 43px;
|
|
|
+ border-bottom: 1px solid var(--el-border-color-light);
|
|
|
+ padding: 0 15px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ ul {
|
|
|
+ height: 300px;
|
|
|
+ overflow: auto;
|
|
|
+ }
|
|
|
+ li {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 5px 5px 5px 15px;
|
|
|
+ height: 38px;
|
|
|
+ .name {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ .el-avatar {
|
|
|
+ background: var(--el-color-primary);
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+ label {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .delete {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ &:hover {
|
|
|
+ background: var(--el-color-primary-light-9);
|
|
|
+ .delete {
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|