Files
dafuweng-saiadmin6.x/saiadmin-artd/src/components/sai/sa-user/index.vue
2026-03-03 09:53:54 +08:00

403 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-select
v-model="selectedValue"
v-bind="$attrs"
:placeholder="placeholder"
:disabled="disabled"
:clearable="clearable"
:filterable="false"
:multiple="multiple"
:collapse-tags="collapseTags"
:collapse-tags-tooltip="collapseTagsTooltip"
:loading="loading"
popper-class="sa-user-select-popper"
@visible-change="handleVisibleChange"
@clear="handleClear"
>
<template #header>
<div class="sa-user-select-header" @click.stop>
<el-input
v-model="searchKeyword"
placeholder="搜索用户名、姓名、手机号"
clearable
@input="handleSearch"
@click.stop
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
</template>
<!-- 隐藏的选项用于显示已选中的用户 -->
<el-option
v-for="user in selectedUsers"
:key="user.id"
:value="user.id"
:label="user.username"
style="display: none"
/>
<!-- 使用 el-option 包装表格内容 -->
<el-option value="" disabled style="height: auto; padding: 0">
<div class="sa-user-select-table" @click.stop>
<el-table
ref="tableRef"
:data="userList"
@row-click="handleRowClick"
@selection-change="handleSelectionChange"
size="small"
v-loading="loading"
>
<el-table-column
v-if="multiple"
type="selection"
width="45"
:selectable="checkSelectable"
/>
<el-table-column prop="id" label="编号" align="center" width="80" />
<el-table-column prop="avatar" label="头像" width="60">
<template #default="{ row }">
<el-avatar :size="32" :src="row.avatar">
{{ row.username?.charAt(0) }}
</el-avatar>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" width="100" show-overflow-tooltip />
<el-table-column prop="realname" label="姓名" width="100" show-overflow-tooltip />
<el-table-column prop="phone" label="手机号" width="110" show-overflow-tooltip />
</el-table>
<div class="sa-user-select-pagination">
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.limit"
:total="pagination.total"
layout="total, prev, pager, next"
small
background
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</el-option>
<template #empty>
<el-empty description="暂无用户数据" :image-size="60" />
</template>
</el-select>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { getUserList } from '@/api/auth'
import { Search } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import type { TableInstance } from 'element-plus'
defineOptions({ name: 'SaUser', inheritAttrs: false })
interface UserItem {
id: number
username: string
email: string
phone: string
avatar?: string
status: string
[key: string]: any
}
interface Props {
/** 占位符 */
placeholder?: string
/** 是否禁用 */
disabled?: boolean
/** 是否可清空 */
clearable?: boolean
/** 是否可搜索 */
filterable?: boolean
/** 是否多选 */
multiple?: boolean
/** 多选时是否折叠标签 */
collapseTags?: boolean
/** 多选折叠时是否显示提示 */
collapseTagsTooltip?: boolean
/** 返回值类型:'id' 返回用户ID'object' 返回完整用户对象 */
valueType?: 'id' | 'object'
}
const props = withDefaults(defineProps<Props>(), {
placeholder: '请选择用户',
disabled: false,
clearable: true,
filterable: true,
multiple: false,
collapseTags: true,
collapseTagsTooltip: true,
valueType: 'id'
})
// 支持单选(number/object) 或 多选(Array)
const modelValue = defineModel<number | null | UserItem | Array<number | UserItem>>()
// 内部选中值
const selectedValue = ref<any>()
const searchKeyword = ref('')
const loading = ref(false)
const userList = ref<UserItem[]>([])
const tableRef = ref<TableInstance>()
// 缓存所有已选中的用户信息
const allSelectedUsers = ref<UserItem[]>([])
// 计算已选中的用户列表(用于显示)
const selectedUsers = computed(() => {
if (!selectedValue.value) return []
const selectedIds = props.multiple
? Array.isArray(selectedValue.value)
? selectedValue.value
: []
: [selectedValue.value]
// 从缓存中查找用户信息
return selectedIds
.map((id) => {
const cached = allSelectedUsers.value.find((u) => u.id === id)
if (cached) return cached
// 从当前列表中查找
const fromList = userList.value.find((u) => u.id === id)
if (fromList) {
// 添加到缓存
allSelectedUsers.value.push(fromList)
return fromList
}
// 如果都找不到,返回一个临时对象
return { id, username: `用户${id}`, email: '', phone: '', status: '1' }
})
.filter(Boolean)
})
// 分页参数
const pagination = ref({
page: 1,
limit: 5,
total: 0
})
// 获取用户列表
const fetchUserList = async () => {
loading.value = true
try {
const params: any = {
page: pagination.value.page,
limit: pagination.value.limit
}
// 添加搜索条件
if (searchKeyword.value) {
params.keyword = searchKeyword.value
}
const response = await getUserList(params)
if (response && response.data) {
userList.value = response.data || []
pagination.value.total = response.total || 0
}
} catch (error) {
console.error('获取用户列表失败:', error)
ElMessage.error('获取用户列表失败')
} finally {
loading.value = false
}
}
// 搜索防抖
let searchTimer: any = null
const handleSearch = () => {
if (searchTimer) clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
pagination.value.page = 1
fetchUserList()
}, 300)
}
// 下拉框显示/隐藏
const handleVisibleChange = (visible: boolean) => {
if (visible) {
// 打开时加载数据
fetchUserList()
}
}
// 清空选择
const handleClear = () => {
selectedValue.value = props.multiple ? [] : null
if (tableRef.value) {
if (props.multiple) {
tableRef.value.clearSelection()
} else {
tableRef.value.setCurrentRow(null)
}
}
}
// 单选 - 行点击
const handleRowClick = (row: UserItem) => {
if (!props.multiple) {
handleCurrentChange(row)
}
}
// 单选 - 当前行改变
const handleCurrentChange = (row: UserItem | undefined) => {
if (!props.multiple && row) {
// 添加到缓存
const existingIndex = allSelectedUsers.value.findIndex((u) => u.id === row.id)
if (existingIndex === -1) {
allSelectedUsers.value.push(row)
} else {
allSelectedUsers.value[existingIndex] = row
}
selectedValue.value = props.valueType === 'id' ? row.id : row
}
}
// 多选 - 选择改变
const handleSelectionChange = (selection: UserItem[]) => {
if (props.multiple) {
// 更新缓存
selection.forEach((row) => {
const existingIndex = allSelectedUsers.value.findIndex((u) => u.id === row.id)
if (existingIndex === -1) {
allSelectedUsers.value.push(row)
} else {
allSelectedUsers.value[existingIndex] = row
}
})
selectedValue.value = selection.map((item) => (props.valueType === 'id' ? item.id : item))
}
}
// 检查行是否可选
const checkSelectable = () => {
// 可以根据需要添加更多条件
return !props.disabled
}
// 分页改变
const handlePageChange = () => {
fetchUserList()
}
const handleSizeChange = () => {
pagination.value.page = 1
fetchUserList()
}
// 监听内部选中值变化,同步到 v-model
watch(
selectedValue,
(newVal) => {
modelValue.value = newVal
},
{ deep: true }
)
// 监听 v-model 变化,同步到内部选中值
watch(
() => modelValue.value,
(newVal) => {
selectedValue.value = newVal
},
{ immediate: true, deep: true }
)
// 组件挂载时初始化
onMounted(() => {
// 如果有初始值,加载数据
if (modelValue.value) {
fetchUserList()
}
})
</script>
<style scoped lang="scss">
.sa-user-select-header {
padding: 8px 12px;
border-bottom: 1px solid var(--el-border-color-light);
}
.sa-user-select-table {
min-height: 480px;
max-height: 600px;
display: flex;
flex-direction: column;
:deep(.el-table) {
.el-table__header-wrapper {
th {
background-color: var(--el-fill-color-light);
}
}
.el-table__row {
cursor: pointer;
&:hover {
background-color: var(--el-fill-color-light);
}
}
}
}
.sa-user-select-pagination {
padding: 8px 12px;
border-top: 1px solid var(--el-border-color-light);
background-color: var(--el-fill-color-blank);
display: flex;
justify-content: center;
}
</style>
<style lang="scss">
// 全局样式,不使用 scoped
.sa-user-select-popper {
max-width: 90vw !important;
.el-select-dropdown__item {
height: auto !important;
min-height: 320px !important;
max-height: 360px !important;
padding: 0 !important;
line-height: normal !important;
&.is-disabled {
cursor: default;
background-color: transparent !important;
}
}
.el-select-dropdown__wrap {
max-height: 340px !important;
}
// 确保下拉框列表容器也不限制高度
.el-select-dropdown__list {
padding: 0 !important;
}
// 确保滚动容器正确显示
.el-scrollbar__view {
padding: 0 !important;
}
}
</style>