<!-- 用户管理页面 -->
|
<template>
|
<div class="user-page art-full-height">
|
<UserSearch
|
v-model="searchForm"
|
:dept-tree-options="deptTreeOptions"
|
:role-options="roleOptions"
|
@search="handleSearch"
|
@reset="handleReset"
|
/>
|
|
<ElCard class="art-table-card">
|
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
<template #left>
|
<ElSpace wrap>
|
<ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>新增用户</ElButton>
|
</ElSpace>
|
</template>
|
</ArtTableHeader>
|
|
<ArtTable
|
:loading="loading"
|
:data="data"
|
:columns="columns"
|
:pagination="pagination"
|
@pagination:size-change="handleSizeChange"
|
@pagination:current-change="handleCurrentChange"
|
/>
|
|
<UserDialog
|
v-model:visible="dialogVisible"
|
:type="dialogType"
|
:user-data="currentUserData"
|
:role-options="roleOptions"
|
:dept-tree-options="deptTreeOptions"
|
@submit="handleDialogSubmit"
|
/>
|
|
<UserDetailDrawer
|
v-model:visible="detailDrawerVisible"
|
:loading="detailLoading"
|
:user-data="detailUserData"
|
/>
|
</ElCard>
|
</div>
|
</template>
|
|
<script setup>
|
import request from '@/utils/http'
|
import {
|
fetchDeleteUser,
|
fetchGetDeptTree,
|
fetchGetRoleOptions,
|
fetchGetUserDetail,
|
fetchResetUserPassword,
|
fetchSaveUser,
|
fetchUpdateUser,
|
fetchUpdateUserStatus
|
} from '@/api/system-manage'
|
import { useTable } from '@/hooks/core/useTable'
|
import { useAuth } from '@/hooks/core/useAuth'
|
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
|
import { ElMessage, ElMessageBox, ElSwitch, ElTag } from 'element-plus'
|
import UserSearch from './modules/user-search.vue'
|
import UserDialog from './modules/user-dialog.vue'
|
import UserDetailDrawer from './modules/user-detail-drawer.vue'
|
import {
|
buildUserDialogModel,
|
buildUserPageQueryParams,
|
buildUserSavePayload,
|
buildUserSearchParams,
|
createUserSearchState,
|
getUserPaginationKey,
|
getUserStatusMeta,
|
mergeUserDetailRecord,
|
normalizeDeptTreeOptions,
|
normalizeRoleOptions,
|
normalizeUserListRow,
|
formatUserRoleNames
|
} from './userPage.helpers'
|
|
defineOptions({ name: 'User' })
|
|
const searchForm = ref(createUserSearchState())
|
const dialogType = ref('add')
|
const dialogVisible = ref(false)
|
const currentUserData = ref(buildUserDialogModel())
|
const detailDrawerVisible = ref(false)
|
const detailLoading = ref(false)
|
const detailUserData = ref({})
|
const roleOptions = ref([])
|
const deptTreeOptions = ref([])
|
const RESET_PASSWORD = '123456'
|
const { hasAuth } = useAuth()
|
|
const fetchUserPage = (params = {}) => {
|
return request.post({
|
url: '/user/page',
|
params: buildUserPageQueryParams(params)
|
})
|
}
|
|
const {
|
columns,
|
columnChecks,
|
data,
|
loading,
|
pagination,
|
getData,
|
replaceSearchParams,
|
resetSearchParams,
|
handleSizeChange,
|
handleCurrentChange,
|
refreshData,
|
refreshCreate,
|
refreshUpdate,
|
refreshRemove
|
} = useTable({
|
core: {
|
apiFn: fetchUserPage,
|
apiParams: buildUserPageQueryParams(searchForm.value),
|
paginationKey: getUserPaginationKey(),
|
columnsFactory: () => [
|
{
|
prop: 'username',
|
label: '用户名',
|
minWidth: 140,
|
showOverflowTooltip: true
|
},
|
{
|
prop: 'nickname',
|
label: '昵称',
|
minWidth: 120,
|
showOverflowTooltip: true
|
},
|
{
|
prop: 'deptLabel',
|
label: '部门',
|
minWidth: 140,
|
showOverflowTooltip: true
|
},
|
{
|
prop: 'phone',
|
label: '手机号',
|
minWidth: 130
|
},
|
{
|
prop: 'email',
|
label: '邮箱',
|
minWidth: 180,
|
showOverflowTooltip: true
|
},
|
{
|
prop: 'roleNames',
|
label: '角色',
|
minWidth: 180,
|
showOverflowTooltip: true,
|
formatter: (row) => formatUserRoleNames(row.roles) || row.roleNames || '-'
|
},
|
{
|
prop: 'status',
|
label: '状态',
|
width: 180,
|
formatter: (row) => {
|
const statusMeta = getUserStatusMeta(row.statusBool ?? row.status)
|
if (!hasAuth('edit')) {
|
return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
|
}
|
return h('div', { class: 'flex items-center gap-2' }, [
|
h(ElSwitch, {
|
modelValue: row.statusBool ?? statusMeta.bool,
|
loading: row._statusLoading,
|
'onUpdate:modelValue': (value) => handleStatusChange(row, value)
|
}),
|
h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
|
])
|
}
|
},
|
{
|
prop: 'updateTimeText',
|
label: '更新时间',
|
minWidth: 180,
|
sortable: true,
|
formatter: (row) => row.updateTimeText || '-'
|
},
|
{
|
prop: 'createTimeText',
|
label: '创建时间',
|
minWidth: 180,
|
sortable: true,
|
formatter: (row) => row.createTimeText || '-'
|
},
|
{
|
prop: 'operation',
|
label: '操作',
|
width: 220,
|
fixed: 'right',
|
formatter: (row) => {
|
const buttons = []
|
|
if (hasAuth('query')) {
|
buttons.push(
|
h(ArtButtonTable, {
|
type: 'view',
|
onClick: () => openDetail(row)
|
})
|
)
|
}
|
|
if (hasAuth('edit')) {
|
buttons.push(
|
h(ArtButtonTable, {
|
type: 'edit',
|
onClick: () => openEditDialog(row)
|
}),
|
h(ArtButtonTable, {
|
icon: 'ri:key-2-line',
|
iconClass: 'bg-warning/12 text-warning',
|
onClick: () => handleResetPassword(row)
|
})
|
)
|
}
|
|
if (hasAuth('delete')) {
|
buttons.push(
|
h(ArtButtonTable, {
|
type: 'delete',
|
onClick: () => handleDelete(row)
|
})
|
)
|
}
|
|
return h('div', buttons)
|
}
|
}
|
]
|
},
|
transform: {
|
dataTransformer: (records) => {
|
if (!Array.isArray(records)) {
|
return []
|
}
|
return records.map((item) => normalizeUserListRow(item))
|
}
|
}
|
})
|
|
const loadLookups = async () => {
|
try {
|
const [roles, depts] = await Promise.all([fetchGetRoleOptions({}), fetchGetDeptTree({})])
|
roleOptions.value = normalizeRoleOptions(roles)
|
deptTreeOptions.value = normalizeDeptTreeOptions(depts)
|
} catch (error) {
|
console.error('加载用户页字典失败', error)
|
}
|
}
|
|
onMounted(() => {
|
loadLookups()
|
})
|
|
const handleSearch = (params) => {
|
replaceSearchParams(buildUserSearchParams(params))
|
getData()
|
}
|
|
const handleReset = () => {
|
Object.assign(searchForm.value, createUserSearchState())
|
resetSearchParams()
|
}
|
|
const showDialog = (type, row) => {
|
dialogType.value = type
|
currentUserData.value = type === 'edit' ? buildUserDialogModel(row) : buildUserDialogModel()
|
dialogVisible.value = true
|
}
|
|
const loadUserDetail = async (id) => {
|
detailLoading.value = true
|
try {
|
return await fetchGetUserDetail(id)
|
} finally {
|
detailLoading.value = false
|
}
|
}
|
|
const openEditDialog = async (row) => {
|
try {
|
const detail = await loadUserDetail(row.id)
|
currentUserData.value = buildUserDialogModel(mergeUserDetailRecord(detail, row))
|
dialogType.value = 'edit'
|
dialogVisible.value = true
|
} catch (error) {
|
ElMessage.error(error?.message || '获取用户详情失败')
|
}
|
}
|
|
const openDetail = async (row) => {
|
detailDrawerVisible.value = true
|
try {
|
detailUserData.value = mergeUserDetailRecord(await loadUserDetail(row.id), row)
|
} catch (error) {
|
detailDrawerVisible.value = false
|
detailUserData.value = {}
|
ElMessage.error(error?.message || '获取用户详情失败')
|
}
|
}
|
|
const handleDialogSubmit = async (formData) => {
|
const payload = buildUserSavePayload(formData)
|
try {
|
if (dialogType.value === 'edit') {
|
await fetchUpdateUser(payload)
|
ElMessage.success('修改成功')
|
dialogVisible.value = false
|
currentUserData.value = buildUserDialogModel()
|
await refreshUpdate()
|
return
|
}
|
await fetchSaveUser(payload)
|
ElMessage.success('新增成功')
|
dialogVisible.value = false
|
currentUserData.value = buildUserDialogModel()
|
await refreshCreate()
|
} catch (error) {
|
ElMessage.error(error?.message || '提交失败')
|
}
|
}
|
|
const handleDelete = async (row) => {
|
try {
|
await ElMessageBox.confirm(`确定要删除用户「${row.username || row.nickname || row.id}」吗?`, '删除确认', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
await fetchDeleteUser(row.id)
|
ElMessage.success('删除成功')
|
await refreshRemove()
|
} catch (error) {
|
if (error !== 'cancel') {
|
ElMessage.error(error?.message || '删除失败')
|
}
|
}
|
}
|
|
const handleResetPassword = async (row) => {
|
try {
|
await ElMessageBox.confirm(
|
`确定将用户「${row.username || row.nickname || row.id}」的密码重置为 ${RESET_PASSWORD} 吗?`,
|
'重置密码',
|
{
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}
|
)
|
await fetchResetUserPassword({
|
id: row.id,
|
password: RESET_PASSWORD
|
})
|
ElMessage.success(`密码已重置为 ${RESET_PASSWORD}`)
|
await refreshUpdate()
|
} catch (error) {
|
if (error !== 'cancel') {
|
ElMessage.error(error?.message || '重置密码失败')
|
}
|
}
|
}
|
|
const handleStatusChange = async (row, checked) => {
|
const previousStatus = row.status
|
const previousStatusBool = row.statusBool
|
const nextStatus = checked ? 1 : 0
|
row._statusLoading = true
|
row.status = nextStatus
|
row.statusBool = checked
|
|
try {
|
await fetchUpdateUserStatus({
|
id: row.id,
|
status: nextStatus
|
})
|
ElMessage.success('状态已更新')
|
await refreshUpdate()
|
} catch (error) {
|
row.status = previousStatus
|
row.statusBool = previousStatusBool
|
ElMessage.error(error?.message || '状态更新失败')
|
} finally {
|
row._statusLoading = false
|
}
|
}
|
</script>
|