x
This commit is contained in:
@@ -5,100 +5,86 @@
|
||||
<h1>用户管理</h1>
|
||||
<p>管理系统用户和权限</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="openUserModal()">+ 添加用户</button>
|
||||
<t-button theme="primary" @click="openUserModal()">+ 添加用户</t-button>
|
||||
</div>
|
||||
|
||||
<div v-if="state.loading" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<t-loading :loading="state.loading" size="large">
|
||||
<t-table :data="state.users" row-key="id" stripe>
|
||||
<t-table-column colKey="username" title="用户名" />
|
||||
<t-table-column colKey="email" title="邮箱" />
|
||||
<t-table-column colKey="full_name" title="姓名">
|
||||
<template #cell="{ row }">{{ row.full_name || '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="is_active" title="状态">
|
||||
<template #cell="{ row }">
|
||||
<t-tag :theme="row.is_active ? 'success' : 'danger'">
|
||||
{{ row.is_active ? '正常' : '禁用' }}
|
||||
</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="roles" title="角色">
|
||||
<template #cell="{ row }">
|
||||
<t-tag v-for="role in row.roles" :key="role" theme="primary" variant="light" style="margin-right: 4px;">
|
||||
{{ role }}
|
||||
</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="created_at" title="注册时间">
|
||||
<template #cell="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="actions" title="操作">
|
||||
<template #cell="{ row }">
|
||||
<t-space :size="4">
|
||||
<t-button theme="primary" size="small" @click="openUserModal(row)">编辑</t-button>
|
||||
<t-button theme="warning" size="small" @click="resetPassword(row)">重置密码</t-button>
|
||||
<t-button v-if="row.id !== storeUser?.id" theme="danger" size="small" @click="deleteUser(row)">删除</t-button>
|
||||
</t-space>
|
||||
</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.users.length === 0" description="暂无用户数据" />
|
||||
</t-loading>
|
||||
|
||||
<div v-else class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>邮箱</th>
|
||||
<th>姓名</th>
|
||||
<th>状态</th>
|
||||
<th>角色</th>
|
||||
<th>注册时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in state.users" :key="user.id">
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{{ user.full_name || '-' }}</td>
|
||||
<td>
|
||||
<span :class="['badge', user.is_active ? 'badge-success' : 'badge-error']">
|
||||
{{ user.is_active ? '正常' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="role in (user as any).roles" :key="role" class="badge badge-info" style="margin-right: 4px;">
|
||||
{{ role }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatDateTime(user.created_at) }}</td>
|
||||
<td>
|
||||
<div class="action-buttons" style="display:flex; gap:4px;">
|
||||
<button class="btn btn-sm" style="background:var(--primary-500);color:white;" @click="openUserModal(user)">编辑</button>
|
||||
<button class="btn btn-sm" style="background:#eab308;color:white;" @click="resetPassword(user)">重置密码</button>
|
||||
<button v-if="(user as any).id !== storeUser?.id" class="btn btn-sm" style="background:var(--error);color:white;" @click="deleteUser(user)">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="state.showUserModal" class="modal-overlay" @click.self="state.showUserModal = false">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>{{ state.editingUser ? '编辑用户' : '添加用户' }}</h2>
|
||||
<button class="modal-close" @click="state.showUserModal = false">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">用户名</label>
|
||||
<input v-model="state.userForm.username" type="text" class="form-input" :disabled="!!state.editingUser" />
|
||||
<t-dialog
|
||||
v-model:visible="state.showUserModal"
|
||||
:header="state.editingUser ? '编辑用户' : '添加用户'"
|
||||
:close-on-overlay-click="true"
|
||||
width="520px"
|
||||
>
|
||||
<t-form label-width="80px">
|
||||
<t-form-item label="用户名">
|
||||
<t-input v-model="state.userForm.username" :disabled="!!state.editingUser" />
|
||||
</t-form-item>
|
||||
<t-form-item label="邮箱">
|
||||
<t-input v-model="state.userForm.email" type="email" />
|
||||
</t-form-item>
|
||||
<t-form-item v-if="!state.editingUser" label="密码">
|
||||
<t-input v-model="state.userForm.password" type="password" />
|
||||
</t-form-item>
|
||||
<t-form-item label="姓名">
|
||||
<t-input v-model="state.userForm.full_name" />
|
||||
</t-form-item>
|
||||
<t-form-item label="角色">
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label v-for="role in state.roles" :key="role.id" style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
||||
<input type="checkbox" :value="role.id" v-model="state.userForm.role_ids" />
|
||||
{{ role.name }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">邮箱</label>
|
||||
<input v-model="state.userForm.email" type="email" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group" v-if="!state.editingUser">
|
||||
<label class="form-label">密码</label>
|
||||
<input v-model="state.userForm.password" type="password" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">姓名</label>
|
||||
<input v-model="state.userForm.full_name" type="text" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">角色</label>
|
||||
<div class="checkbox-group" style="flex-direction: column; gap: 8px;">
|
||||
<label v-for="role in state.roles" :key="(role as any).id" class="checkbox-label">
|
||||
<input type="checkbox" :value="(role as any).id" v-model="state.userForm.role_ids" class="checkbox-input" />
|
||||
{{ (role as any).name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="state.showUserModal = false">取消</button>
|
||||
<button class="btn btn-primary" @click="saveUser">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<t-button theme="default" @click="state.showUserModal = false">取消</t-button>
|
||||
<t-button theme="primary" @click="saveUser">保存</t-button>
|
||||
</template>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { DialogPlugin } from 'tdesign-vue-next'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { addNotification } from '@/shared/notification'
|
||||
@@ -139,6 +125,21 @@ const state = reactive({
|
||||
},
|
||||
})
|
||||
|
||||
function confirmDialog(header: string, body: string, theme: string, confirmText: string, cancelText: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const dlg = DialogPlugin.confirm({
|
||||
header,
|
||||
body,
|
||||
theme: theme as any,
|
||||
confirmBtn: confirmText,
|
||||
cancelBtn: cancelText,
|
||||
onConfirm: () => { dlg.hide(); resolve(true) },
|
||||
onCancel: () => { dlg.hide(); resolve(false) },
|
||||
onClose: () => { resolve(false) },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
state.users = await apiRequest<UserItem[]>('/api/auth/users')
|
||||
@@ -217,7 +218,7 @@ const saveUser = async () => {
|
||||
}
|
||||
|
||||
const deleteUser = async (user: UserItem) => {
|
||||
if (!confirm(`确定要删除用户 ${user.username} 吗?`)) return
|
||||
if (!await confirmDialog('删除确认', `确定要删除用户 ${user.username} 吗?`, 'warning', '删除', '取消')) return
|
||||
|
||||
try {
|
||||
await apiRequest(`/api/auth/users/${user.id}`, { method: 'DELETE' })
|
||||
|
||||
Reference in New Issue
Block a user