Files
art-design/src/views/system/user/index.vue
2026-01-10 10:04:08 +08:00

262 lines
7.3 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.
<!-- 用户管理页面 -->
<!-- art-full-height 自动计算出页面剩余高度 -->
<!-- art-table-card 一个符合系统样式的 class同时自动撑满剩余高度 -->
<!-- 更多 useTable 使用示例请移步至 功能示例 下面的高级表格示例或者查看官方文档 -->
<!-- useTable 文档https://www.artd.pro/docs/zh/guide/hooks/use-table.html -->
<template>
<div class="user-page art-full-height">
<!-- 搜索栏 -->
<UserSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams"></UserSearch>
<ElCard class="art-table-card" shadow="never">
<!-- 表格头部 -->
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
<ElSpace wrap>
<ElButton @click="showDialog('add')" v-ripple>新增用户</ElButton>
</ElSpace>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
:loading="loading"
:data="data"
:columns="columns"
:pagination="pagination"
@selection-change="handleSelectionChange"
@pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange"
>
</ArtTable>
<!-- 用户弹窗 -->
<UserDialog
v-model:visible="dialogVisible"
:type="dialogType"
:user-data="currentUserData"
@submit="handleDialogSubmit"
/>
</ElCard>
</div>
</template>
<script setup lang="ts">
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
import { ACCOUNT_TABLE_DATA } from '@/mock/temp/formData'
import { useTable } from '@/hooks/core/useTable'
import { fetchGetUserList } from '@/api/system-manage'
import UserSearch from './modules/user-search.vue'
import UserDialog from './modules/user-dialog.vue'
import { ElTag, ElMessageBox, ElImage } from 'element-plus'
import { DialogType } from '@/types'
defineOptions({ name: 'User' })
type UserListItem = Api.SystemManage.UserListItem
// 弹窗相关
const dialogType = ref<DialogType>('add')
const dialogVisible = ref(false)
const currentUserData = ref<Partial<UserListItem>>({})
// 选中行
const selectedRows = ref<UserListItem[]>([])
// 搜索表单
const searchForm = ref({
userName: undefined,
userGender: undefined,
userPhone: undefined,
userEmail: undefined,
status: '1'
})
// 用户状态配置
const USER_STATUS_CONFIG = {
'1': { type: 'success' as const, text: '在线' },
'2': { type: 'info' as const, text: '离线' },
'3': { type: 'warning' as const, text: '异常' },
'4': { type: 'danger' as const, text: '注销' }
} as const
/**
* 获取用户状态配置
*/
const getUserStatusConfig = (status: string) => {
return (
USER_STATUS_CONFIG[status as keyof typeof USER_STATUS_CONFIG] || {
type: 'info' as const,
text: '未知'
}
)
}
const {
columns,
columnChecks,
data,
loading,
pagination,
getData,
searchParams,
resetSearchParams,
handleSizeChange,
handleCurrentChange,
refreshData
} = useTable({
// 核心配置
core: {
apiFn: fetchGetUserList,
apiParams: {
current: 1,
size: 20,
...searchForm.value
},
// 自定义分页字段映射,未设置时将使用全局配置 tableConfig.ts 中的 paginationKey
// paginationKey: {
// current: 'pageNum',
// size: 'pageSize'
// },
columnsFactory: () => [
{ type: 'selection' }, // 勾选列
{ type: 'index', width: 60, label: '序号' }, // 序号
{
prop: 'userInfo',
label: '用户名',
width: 280,
// visible: false, // 默认是否显示列
formatter: (row) => {
return h('div', { class: 'user flex-c' }, [
h(ElImage, {
class: 'size-9.5 rounded-md',
src: row.avatar,
previewSrcList: [row.avatar],
// 图片预览是否插入至 body 元素上,用于解决表格内部图片预览样式异常
previewTeleported: true
}),
h('div', { class: 'ml-2' }, [
h('p', { class: 'user-name' }, row.userName),
h('p', { class: 'email' }, row.userEmail)
])
])
}
},
{
prop: 'userGender',
label: '性别',
sortable: true,
formatter: (row) => row.userGender
},
{ prop: 'userPhone', label: '手机号' },
{
prop: 'status',
label: '状态',
formatter: (row) => {
const statusConfig = getUserStatusConfig(row.status)
return h(ElTag, { type: statusConfig.type }, () => statusConfig.text)
}
},
{
prop: 'createTime',
label: '创建日期',
sortable: true
},
{
prop: 'operation',
label: '操作',
width: 120,
fixed: 'right', // 固定列
formatter: (row) =>
h('div', [
h(ArtButtonTable, {
type: 'edit',
onClick: () => showDialog('edit', row)
}),
h(ArtButtonTable, {
type: 'delete',
onClick: () => deleteUser(row)
})
])
}
]
},
// 数据处理
transform: {
// 数据转换器 - 替换头像
dataTransformer: (records) => {
// 类型守卫检查
if (!Array.isArray(records)) {
console.warn('数据转换器: 期望数组类型,实际收到:', typeof records)
return []
}
// 使用本地头像替换接口返回的头像
return records.map((item, index: number) => {
return {
...item,
avatar: ACCOUNT_TABLE_DATA[index % ACCOUNT_TABLE_DATA.length].avatar
}
})
}
}
})
/**
* 搜索处理
* @param params 参数
*/
const handleSearch = (params: Record<string, any>) => {
console.log(params)
// 搜索参数赋值
Object.assign(searchParams, params)
getData()
}
/**
* 显示用户弹窗
*/
const showDialog = (type: DialogType, row?: UserListItem): void => {
console.log('打开弹窗:', { type, row })
dialogType.value = type
currentUserData.value = row || {}
nextTick(() => {
dialogVisible.value = true
})
}
/**
* 删除用户
*/
const deleteUser = (row: UserListItem): void => {
console.log('删除用户:', row)
ElMessageBox.confirm(`确定要注销该用户吗?`, '注销用户', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(() => {
ElMessage.success('注销成功')
})
}
/**
* 处理弹窗提交事件
*/
const handleDialogSubmit = async () => {
try {
dialogVisible.value = false
currentUserData.value = {}
} catch (error) {
console.error('提交失败:', error)
}
}
/**
* 处理表格行选择变化
*/
const handleSelectionChange = (selection: UserListItem[]): void => {
selectedRows.value = selection
console.log('选中行数据:', selectedRows.value)
}
</script>