add user management interface
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
<script setup lang="ts">
|
||||
import { api } from '@/utils/request'
|
||||
import { useUserManageStore, type UserRecord } from '@/stores/user-manage'
|
||||
import UserFormModal from '@/components/UserFormModal.vue'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
ReloadOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const store = useUserManageStore()
|
||||
|
||||
// Detail drawer
|
||||
const drawerVisible = ref(false)
|
||||
const drawerLoading = ref(false)
|
||||
const currentUser = ref<UserRecord | null>(null)
|
||||
|
||||
// Form modal
|
||||
const modalOpen = ref(false)
|
||||
const modalMode = ref<'create' | 'edit'>('create')
|
||||
const editingUser = ref<UserRecord | null>(null)
|
||||
|
||||
const columns = [
|
||||
{ title: 'ID', dataIndex: 'id', width: 80 },
|
||||
{ title: '用户名', dataIndex: 'username' },
|
||||
{ title: '邮箱', dataIndex: 'email' },
|
||||
{ title: '状态', dataIndex: 'status', width: 100 },
|
||||
{ title: '创建时间', dataIndex: 'created_at', width: 180 },
|
||||
{ title: '操作', key: 'action', width: 200 },
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
store.fetchUsers()
|
||||
})
|
||||
|
||||
function handleSearch() {
|
||||
store.pagination.page = 1
|
||||
store.fetchUsers()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
store.resetFilters()
|
||||
store.fetchUsers()
|
||||
}
|
||||
|
||||
function handlePageChange(page: number, pageSize: number) {
|
||||
store.pagination.page = page
|
||||
store.pagination.per_page = pageSize
|
||||
store.fetchUsers()
|
||||
}
|
||||
|
||||
async function handleViewDetail(record: UserRecord) {
|
||||
drawerVisible.value = true
|
||||
drawerLoading.value = true
|
||||
try {
|
||||
currentUser.value = await api.get<UserRecord>(`/api/v1/users/${record.id}`)
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : '获取详情失败'
|
||||
message.error(msg)
|
||||
} finally {
|
||||
drawerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
modalMode.value = 'create'
|
||||
editingUser.value = null
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
function handleEdit(record: UserRecord) {
|
||||
modalMode.value = 'edit'
|
||||
editingUser.value = record
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
async function handleToggleStatus(record: UserRecord) {
|
||||
const newStatus = record.status === 1 ? 0 : 1
|
||||
try {
|
||||
await api.patch(`/api/v1/users/${record.id}/status`, { status: newStatus })
|
||||
message.success('操作成功')
|
||||
store.fetchUsers()
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : '操作失败'
|
||||
message.error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
function handleModalSuccess() {
|
||||
store.fetchUsers()
|
||||
}
|
||||
|
||||
function formatTime(time: string) {
|
||||
return time ? time.replace('T', ' ').substring(0, 19) : '-'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold mb-4">用户管理</h2>
|
||||
|
||||
<!-- Filter area -->
|
||||
<a-card class="mb-4">
|
||||
<a-form layout="inline" @submit.prevent="handleSearch">
|
||||
<a-form-item label="用户名">
|
||||
<a-input
|
||||
v-model:value="store.filters.username"
|
||||
placeholder="请输入用户名"
|
||||
allow-clear
|
||||
@press-enter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱">
|
||||
<a-input
|
||||
v-model:value="store.filters.email"
|
||||
placeholder="请输入邮箱"
|
||||
allow-clear
|
||||
@press-enter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="store.filters.status"
|
||||
placeholder="全部"
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option :value="1">启用</a-select-option>
|
||||
<a-select-option :value="0">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" html-type="submit">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- Table -->
|
||||
<a-card>
|
||||
<div class="mb-4">
|
||||
<a-button type="primary" @click="handleCreate">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建用户
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="store.users"
|
||||
:loading="store.loading"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'status'">
|
||||
<a-tag :color="record.status === 1 ? 'green' : 'red'">
|
||||
{{ record.status === 1 ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'created_at'">
|
||||
{{ formatTime(record.created_at) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleViewDetail(record as UserRecord)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="handleEdit(record as UserRecord)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:title="`确定要${record.status === 1 ? '禁用' : '启用'}该用户吗?`"
|
||||
@confirm="handleToggleStatus(record as UserRecord)"
|
||||
>
|
||||
<a-button type="link" size="small" :danger="record.status === 1">
|
||||
{{ record.status === 1 ? '禁用' : '启用' }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<a-pagination
|
||||
:current="store.pagination.page"
|
||||
:page-size="store.pagination.per_page"
|
||||
:total="store.pagination.total"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:show-total="(total: number) => `共 ${total} 条`"
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- Detail drawer -->
|
||||
<a-drawer
|
||||
title="用户详情"
|
||||
:open="drawerVisible"
|
||||
:width="480"
|
||||
@close="drawerVisible = false"
|
||||
>
|
||||
<a-spin :spinning="drawerLoading">
|
||||
<a-descriptions v-if="currentUser" :column="1" bordered>
|
||||
<a-descriptions-item label="ID">{{ currentUser.id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="用户名">{{ currentUser.username }}</a-descriptions-item>
|
||||
<a-descriptions-item label="邮箱">{{ currentUser.email }}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="currentUser.status === 1 ? 'green' : 'red'">
|
||||
{{ currentUser.status === 1 ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="角色 ID">{{ currentUser.role_id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">
|
||||
{{ formatTime(currentUser.created_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">
|
||||
{{ formatTime(currentUser.updated_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="扩展信息">
|
||||
<pre class="m-0 text-xs">{{
|
||||
currentUser.ext ? JSON.stringify(currentUser.ext, null, 2) : '-'
|
||||
}}</pre>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</a-drawer>
|
||||
|
||||
<!-- Create/Edit modal -->
|
||||
<UserFormModal
|
||||
v-model:open="modalOpen"
|
||||
:mode="modalMode"
|
||||
:user-data="editingUser"
|
||||
@success="handleModalSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user