<script lang="ts" setup>
|
import type { ScrollActionType } from '@jnpf/ui';
|
|
import { nextTick, reactive, ref, toRefs, watch } from 'vue';
|
|
import { useGlobSetting, useMessage } from '@jnpf/hooks';
|
import { ScrollContainer } from '@jnpf/ui';
|
import { BasicModal, useModalInner } from '@jnpf/ui/modal';
|
|
import { useDebounceFn } from '@vueuse/core';
|
import { Avatar, Checkbox, Input, Radio, TabPane, Tabs, Tag } from 'ant-design-vue';
|
|
import { $t } from '#/locales';
|
|
interface State {
|
activeKey: string;
|
finish: boolean;
|
lastList: any[];
|
loading: boolean;
|
listQuery: any;
|
selectedData: any[];
|
selectedIds: any[];
|
}
|
|
const props = defineProps({
|
multiple: { default: true, type: Boolean },
|
required: { default: false, type: Boolean },
|
api: { type: Function },
|
query: { type: Object, default: () => ({}) },
|
modalTitle: { default: '选择用户', type: String },
|
});
|
const emit = defineEmits(['register', 'confirm']);
|
|
const [registerModal, { changeLoading, changeOkLoading, closeModal }] = useModalInner(init);
|
const globSetting = useGlobSetting();
|
const { createMessage } = useMessage();
|
const apiUrl = ref(globSetting.apiURL);
|
const infiniteBody = ref<Nullable<ScrollActionType>>(null);
|
const state = reactive<State>({
|
activeKey: '',
|
finish: false,
|
lastList: [],
|
loading: false,
|
listQuery: {
|
currentPage: 1,
|
hasPage: 0,
|
keyword: '',
|
pageSize: 20,
|
},
|
selectedData: [],
|
selectedIds: [],
|
});
|
const { activeKey, lastList, loading, listQuery, selectedData, selectedIds } = toRefs(state);
|
const debounceHandleSearch = useDebounceFn(handleSearch, 300);
|
|
watch(
|
() => state.selectedData,
|
(val) => {
|
state.selectedIds = val.map((o) => o.id);
|
},
|
{ deep: true },
|
);
|
|
function init() {
|
changeLoading(true);
|
state.activeKey = '1';
|
state.listQuery.keyword = '';
|
state.selectedData = [];
|
initData();
|
nextTick(() => {
|
bindScroll();
|
});
|
changeLoading(false);
|
}
|
function onTabChange() {}
|
function handleSearch(e) {
|
const value = e.target.value;
|
if (state.loading) return;
|
state.listQuery.keyword = value || '';
|
nextTick(() => {
|
bindScroll();
|
});
|
initData();
|
}
|
function handleNodeClick(data) {
|
const index = state.selectedData.findIndex((o) => o.id === data.id);
|
if (index !== -1) return state.selectedData.splice(index, 1);
|
props.multiple ? state.selectedData.push(data) : (state.selectedData = [data]);
|
}
|
function removeAll() {
|
state.selectedData = [];
|
}
|
function removeData(index: number) {
|
state.selectedData.splice(index, 1);
|
}
|
function handleSubmit() {
|
if (props.required && !state.selectedData.length) {
|
createMessage.warning('至少选择一个用户');
|
return;
|
}
|
changeOkLoading(true);
|
emit('confirm', state.selectedData);
|
closeModal();
|
}
|
function bindScroll() {
|
const bodyRef = infiniteBody.value;
|
const vBody = bodyRef?.getScrollWrap();
|
vBody?.addEventListener('scroll', () => {
|
if (vBody.scrollTop > 0 && vBody.scrollHeight - vBody.clientHeight - vBody.scrollTop <= 200 && !state.loading && !state.finish) {
|
state.listQuery.currentPage += 1;
|
getLastList();
|
}
|
});
|
}
|
function getLastList() {
|
if (!props.api) return;
|
state.loading = true;
|
const query = { ...state.listQuery, ...props.query };
|
props
|
.api(query)
|
.then((res) => {
|
state.finish = res.data.list.length < state.listQuery.pageSize;
|
state.lastList = [...state.lastList, ...res.data.list];
|
state.loading = false;
|
})
|
.catch(() => {
|
state.loading = false;
|
});
|
}
|
async function initData() {
|
state.listQuery.currentPage = 1;
|
state.finish = false;
|
state.lastList = [];
|
getLastList();
|
}
|
</script>
|
<template>
|
<BasicModal v-bind="$attrs" @register="registerModal" :title="modalTitle" :width="800" @ok="handleSubmit" destroy-on-close class="common-select-modal">
|
<template #insertFooter v-if="multiple">
|
<div class="float-left">
|
<span class="mr-[10px]">{{ $t('component.jnpf.common.selected') }}({{ selectedData.length }})</span>
|
<span class="remove-all-btn" @click="removeAll">{{ $t('component.jnpf.common.clearAll') }}</span>
|
</div>
|
</template>
|
<div class="common-select-modal-main">
|
<div class="selected-pane">
|
<Tag v-for="(item, i) in selectedData" :key="item.id" :closable="true" class="selected-pane-tag" @close="removeData(i)">
|
{{ item.fullName }}
|
</Tag>
|
</div>
|
<div class="select-pane">
|
<div class="select-pane-tool">
|
<Tabs v-model:active-key="activeKey" :tab-bar-gutter="20" class="select-pane-tool-tabs" @change="onTabChange">
|
<TabPane key="1" tab="用户" />
|
<template #rightExtra>
|
<Input v-model:value="listQuery.keyword" :placeholder="$t('common.enterKeyword')" allow-clear @change="debounceHandleSearch" />
|
</template>
|
</Tabs>
|
<div class="select-pane-main">
|
<ScrollContainer class="select-pane-list select-pane-item" ref="infiniteBody" v-loading="loading && listQuery.currentPage === 1">
|
<div v-for="(item, i) in lastList" :key="i" class="select-pane-list__item" @click="handleNodeClick(item)">
|
<div class="select-pane-list__item-title">
|
<Avatar :size="26" :src="apiUrl + item.headIcon" class="select-pane-list__item--headIcon" v-if="item.headIcon" />
|
<i :class="item.icon" class="mr-[6px]" v-if="item.icon && !item.headIcon"></i>
|
<span :title="item.fullName">{{ item.fullName }}</span>
|
</div>
|
<div class="select-pane-list__item-action">
|
<Checkbox :checked="selectedIds.includes(item.id)" v-if="multiple" />
|
<Radio :checked="selectedIds.includes(item.id)" v-else />
|
</div>
|
</div>
|
<jnpf-empty v-if="lastList.length === 0" />
|
</ScrollContainer>
|
</div>
|
</div>
|
</div>
|
</div>
|
</BasicModal>
|
</template>
|