Files
YG_FT/frontend/src/views/system/UserSettingsView.vue

374 lines
12 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.
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
changeMyPassword,
deleteUser,
getUsers,
resetUserPassword,
updateUserAccess,
} from '@/api/modules/system'
import type { PermissionCode, SystemUser, UserStatus } from '@/types'
import { statusLabel, statusTagType } from '@/utils/status'
const loading = ref(false)
const users = ref<SystemUser[]>([])
// 权限码 -> 中文名(与路由模块一一对应)
const PERMISSION_LABELS: Record<PermissionCode, string> = {
dashboard: '服务看板',
'fine-tune': '模型训练',
'model-eval': '模型评测',
'model-inference': '模型推理',
'model-manage': '模型管理',
dataset: '数据集管理',
'data-process': '数据处理',
'data-convert': '数据转换',
compute: '计算资源',
hardware: '硬件监控',
logs: '日志中心',
'user-settings': '用户与权限',
}
const ALL_PERMISSIONS = Object.keys(PERMISSION_LABELS) as PermissionCode[]
async function loadUsers() {
loading.value = true
try {
users.value = await getUsers()
} finally {
loading.value = false
}
}
onMounted(loadUsers)
// 当前登录用户,用于禁止操作自身(避免误锁自己)
const currentUsername = ref<string>('')
try {
currentUsername.value = JSON.parse(localStorage.getItem('currentUser') || '{}').username || ''
} catch {
currentUsername.value = ''
}
function isSelf(row: SystemUser) {
return row.username === currentUsername.value
}
function asSystemUser(row: unknown): SystemUser {
return row as SystemUser
}
function userPermissions(row: unknown): PermissionCode[] {
return (asSystemUser(row).permissions || []) as PermissionCode[]
}
function permissionLabel(code: PermissionCode): string {
return PERMISSION_LABELS[code] || code
}
// ---------- 启停 ----------
async function toggleStatus(row: SystemUser, next: boolean) {
const nextStatus: UserStatus = next ? 'active' : 'disabled'
const prev = row.status
row.status = nextStatus
try {
await updateUserAccess(row.id, { status: nextStatus })
ElMessage.success(`${row.display_name}${next ? '启用' : '停用'}`)
await loadUsers()
} catch {
row.status = prev
ElMessage.error('状态更新失败')
}
}
// ---------- 重置密码 ----------
const pwdDialog = reactive({ visible: false, id: '', name: '', password: '', saving: false })
function openResetPwd(row: SystemUser) {
pwdDialog.id = row.id
pwdDialog.name = row.display_name
pwdDialog.password = 'Platform@123'
pwdDialog.visible = true
}
async function confirmResetPwd() {
if (!pwdDialog.password.trim()) {
ElMessage.warning('请输入新密码')
return
}
pwdDialog.saving = true
try {
await resetUserPassword(pwdDialog.id, pwdDialog.password.trim())
ElMessage.success(`已重置 ${pwdDialog.name} 的密码`)
pwdDialog.visible = false
} catch {
ElMessage.error('重置密码失败')
} finally {
pwdDialog.saving = false
}
}
// ---------- 用户自改密码 ----------
const myPwdDialog = reactive({ visible: false, oldPassword: '', newPassword: '', saving: false })
function openChangeMyPwd() {
myPwdDialog.oldPassword = ''
myPwdDialog.newPassword = ''
myPwdDialog.visible = true
}
async function confirmChangeMyPwd() {
if (!myPwdDialog.oldPassword.trim() || !myPwdDialog.newPassword.trim()) {
ElMessage.warning('请填写旧密码和新密码')
return
}
if (myPwdDialog.newPassword.length < 6) {
ElMessage.warning('新密码至少 6 位')
return
}
myPwdDialog.saving = true
try {
await changeMyPassword(myPwdDialog.oldPassword.trim(), myPwdDialog.newPassword.trim())
ElMessage.success('密码修改成功')
myPwdDialog.visible = false
} catch {
ElMessage.error('密码修改失败,请检查旧密码是否正确')
} finally {
myPwdDialog.saving = false
}
}
// ---------- 页面权限 ----------
const permDialog = reactive({
visible: false,
id: '',
name: '',
checked: [] as PermissionCode[],
saving: false,
})
function openPerms(row: SystemUser) {
permDialog.id = row.id
permDialog.name = row.display_name
// admin 用户强制全选且只读
if (row.role === 'admin' || row.protected) {
permDialog.checked = [...ALL_PERMISSIONS]
} else {
// 非 admin 用户去掉 user-settings
permDialog.checked = (row.permissions || []).filter((p) => p !== 'user-settings')
}
permDialog.visible = true
}
async function confirmPerms() {
if (permReadonly.value) {
permDialog.visible = false
return
}
// 双重保险:非 admin 用户不允许勾选 user-settings
if (!isTargetAdmin.value) {
permDialog.checked = permDialog.checked.filter((p) => p !== 'user-settings')
}
permDialog.saving = true
try {
await updateUserAccess(permDialog.id, { permissions: permDialog.checked })
ElMessage.success(`已更新 ${permDialog.name} 的页面权限`)
permDialog.visible = false
await loadUsers()
} catch {
ElMessage.error('权限更新失败')
} finally {
permDialog.saving = false
}
}
/** 权限列admin 用户全选且只读,非 admin 用户不显示 user-settings */
const isTargetAdmin = computed(() => {
const u = users.value.find((u) => u.id === permDialog.id)
return u?.role === 'admin' || u?.protected === true
})
const permColumns = computed(() => {
if (isTargetAdmin.value) return ALL_PERMISSIONS
// 非 admin 用户不能拥有 user-settings 权限
return ALL_PERMISSIONS.filter((c) => c !== 'user-settings')
})
const permReadonly = computed(() => isTargetAdmin.value)
// ---------- 删除 ----------
async function removeUser(row: SystemUser) {
try {
await ElMessageBox.confirm(
`确定删除用户 “${row.display_name}${row.username})” 吗?该操作不可恢复。`,
'删除用户',
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' },
)
} catch {
return
}
try {
await deleteUser(row.id)
ElMessage.success(`已删除 ${row.display_name}`)
await loadUsers()
} catch (err: any) {
const msg = err?.response?.data?.message || '删除失败'
ElMessage.error(msg)
}
}
</script>
<template>
<section class="user-settings" v-loading="loading">
<header class="page-header">
<div>
<h1>用户设置</h1>
<p>管理平台账号角色状态登录密码与页面权限</p>
</div>
<div>
<el-button @click="openChangeMyPwd">修改密码</el-button>
<el-button type="primary" @click="$router.push('/user-settings/create')">创建用户</el-button>
</div>
</header>
<el-table :data="users" border>
<el-table-column prop="username" label="账号" min-width="140" />
<el-table-column prop="display_name" label="显示名称" min-width="160" />
<el-table-column prop="role" label="角色" width="120" />
<el-table-column label="状态" width="130">
<template #default="{ row }">
<el-tag :type="statusTagType(asSystemUser(row).status)" size="small">{{ statusLabel(asSystemUser(row).status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="页面权限" min-width="160">
<template #default="{ row }">
<el-tag
v-for="p in userPermissions(row).slice(0, 3)"
:key="p"
size="small"
type="info"
class="perm-tag"
>{{ permissionLabel(p) }}</el-tag>
<span v-if="userPermissions(row).length > 3" class="perm-more">
+{{ userPermissions(row).length - 3 }}
</span>
<span v-if="!userPermissions(row).length" class="perm-more"></span>
</template>
</el-table-column>
<el-table-column prop="create_time" label="创建时间" min-width="180" />
<el-table-column label="操作" width="260" fixed="right">
<template #default="{ row }">
<el-switch
:model-value="asSystemUser(row).status === 'active'"
:disabled="asSystemUser(row).protected || isSelf(asSystemUser(row))"
@change="(v: any) => toggleStatus(asSystemUser(row), v)"
inline-prompt
active-text="启用"
inactive-text="停用"
/>
<el-button
link
type="primary"
:disabled="asSystemUser(row).protected"
@click="openResetPwd(asSystemUser(row))"
>重置密码</el-button>
<el-button
link
type="primary"
@click="openPerms(asSystemUser(row))"
>页面权限</el-button>
<el-button
link
type="danger"
:disabled="asSystemUser(row).protected || isSelf(asSystemUser(row))"
@click="removeUser(asSystemUser(row))"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 重置密码 -->
<el-dialog v-model="pwdDialog.visible" title="重置密码" width="420px">
<p class="dlg-tip"> <b>{{ pwdDialog.name }}</b> 设置新密码</p>
<el-input v-model="pwdDialog.password" placeholder="请输入新密码" show-password />
<template #footer>
<el-button @click="pwdDialog.visible = false">取消</el-button>
<el-button type="primary" :loading="pwdDialog.saving" @click="confirmResetPwd">确定重置</el-button>
</template>
</el-dialog>
<!-- 页面权限 -->
<el-dialog v-model="permDialog.visible" title="页面权限" width="540px">
<p class="dlg-tip">
<b>{{ permDialog.name }}</b> 分配可访问的页面模块
<el-tag v-if="permReadonly" type="warning" size="small" style="margin-left: 8px">管理员权限不可更改</el-tag>
</p>
<el-checkbox-group v-model="permDialog.checked" class="perm-group" :disabled="permReadonly">
<el-checkbox
v-for="code in permColumns"
:key="code"
:value="code"
:label="PERMISSION_LABELS[code]"
/>
</el-checkbox-group>
<template #footer>
<el-button @click="permDialog.visible = false">{{ permReadonly ? '关闭' : '取消' }}</el-button>
<el-button v-if="!permReadonly" type="primary" :loading="permDialog.saving" @click="confirmPerms">保存</el-button>
</template>
</el-dialog>
<!-- 修改自己的密码 -->
<el-dialog v-model="myPwdDialog.visible" title="修改密码" width="420px">
<el-form label-width="80px">
<el-form-item label="旧密码">
<el-input v-model="myPwdDialog.oldPassword" placeholder="请输入当前密码" show-password />
</el-form-item>
<el-form-item label="新密码">
<el-input v-model="myPwdDialog.newPassword" placeholder="至少 6 位" show-password />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="myPwdDialog.visible = false">取消</el-button>
<el-button type="primary" :loading="myPwdDialog.saving" @click="confirmChangeMyPwd">确认修改</el-button>
</template>
</el-dialog>
</section>
</template>
<style scoped>
.user-settings {
padding: 24px;
}
.page-header {
display: flex;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
.page-header h1 {
margin: 0;
font-size: 24px;
}
.page-header p {
margin: 8px 0 0;
color: #64748b;
}
.perm-tag {
margin-right: 4px;
margin-bottom: 2px;
}
.perm-more {
color: #94a3b8;
font-size: 12px;
}
.dlg-tip {
margin: 0 0 12px;
color: #475569;
}
.perm-group {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px 12px;
}
</style>