新增用户权限管理功能

This commit is contained in:
jxxghp
2025-06-10 23:25:59 +08:00
parent 1631951a24
commit eb143c28e3
10 changed files with 445 additions and 19 deletions

View File

@@ -183,6 +183,21 @@ onMounted(() => {
</VChip>
<VChip v-if="user.is_otp" size="x-small" color="info" variant="tonal" label>2FA</VChip>
</div>
<!-- 权限显示 -->
<div v-if="!user.is_superuser && user.permissions" class="d-flex flex-wrap gap-1 mt-1">
<VChip v-if="user.permissions.discovery" size="x-small" color="purple" variant="outlined" label>
{{ t('dialog.userAddEdit.permissions.discovery') }}
</VChip>
<VChip v-if="user.permissions.search" size="x-small" color="blue" variant="outlined" label>
{{ t('dialog.userAddEdit.permissions.search') }}
</VChip>
<VChip v-if="user.permissions.subscribe" size="x-small" color="green" variant="outlined" label>
{{ t('dialog.userAddEdit.permissions.subscribe') }}
</VChip>
<VChip v-if="user.permissions.manage" size="x-small" color="orange" variant="outlined" label>
{{ t('dialog.userAddEdit.permissions.manage') }}
</VChip>
</div>
</div>
<!-- 移动端订阅数据信息 -->
@@ -294,9 +309,10 @@ onMounted(() => {
z-index: 1;
display: flex;
align-items: center;
width: 100%;
top: 0;
padding: 8px 12px;
inline-size: 100%;
inset-block-start: 0;
padding-block: 8px;
padding-inline: 12px;
}
.admin-header {
@@ -326,10 +342,12 @@ onMounted(() => {
opacity: 0.6;
transform: scale(0.95);
}
70% {
opacity: 0.2;
transform: scale(1.05);
}
100% {
opacity: 0.6;
transform: scale(0.95);
@@ -340,19 +358,21 @@ onMounted(() => {
position: absolute;
z-index: 5;
animation: float 3s ease-in-out infinite;
top: -10px;
left: -6px;
transform: rotate(-25deg);
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 40%));
inset-block-start: -10px;
inset-inline-start: -6px;
transform: rotate(-25deg);
}
@keyframes float {
0% {
transform: rotate(-25deg) translateY(0);
}
50% {
transform: rotate(-25deg) translateY(-3px);
}
100% {
transform: rotate(-25deg) translateY(0);
}
@@ -368,6 +388,7 @@ onMounted(() => {
opacity: 0.9;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.2);

View File

@@ -65,6 +65,14 @@ interface ExtendedUser extends User {
nickname?: string
}
// 权限类型定义
interface UserPermissions {
discovery: boolean // 发现权限
search: boolean // 搜索权限
subscribe: boolean // 订阅权限
manage: boolean // 管理权限
}
// 用户编辑表单数据
const userForm = ref<ExtendedUser>({
id: 0,
@@ -75,7 +83,12 @@ const userForm = ref<ExtendedUser>({
is_superuser: false,
avatar: avatar1,
is_otp: false,
permissions: {},
permissions: {
discovery: true,
search: true,
subscribe: true,
manage: false,
},
settings: {
wechat_userid: null,
telegram_userid: null,
@@ -86,6 +99,59 @@ const userForm = ref<ExtendedUser>({
nickname: '', // 昵称字段
})
// 权限选项
const permissionOptions = [
{
key: 'discovery',
title: t('dialog.userAddEdit.permissions.discovery'),
description: t('dialog.userAddEdit.permissions.discoveryDesc'),
icon: 'mdi-star-outline',
},
{
key: 'search',
title: t('dialog.userAddEdit.permissions.search'),
description: t('dialog.userAddEdit.permissions.searchDesc'),
icon: 'mdi-magnify',
},
{
key: 'subscribe',
title: t('dialog.userAddEdit.permissions.subscribe'),
description: t('dialog.userAddEdit.permissions.subscribeDesc'),
icon: 'mdi-rss',
},
{
key: 'manage',
title: t('dialog.userAddEdit.permissions.manage'),
description: t('dialog.userAddEdit.permissions.manageDesc'),
icon: 'mdi-cog-outline',
},
]
// 权限状态计算属性
const userPermissions = computed({
get: () => {
const permissions = userForm.value.permissions as UserPermissions
return {
discovery: permissions?.discovery ?? true,
search: permissions?.search ?? true,
subscribe: permissions?.subscribe ?? true,
manage: permissions?.manage ?? false,
}
},
set: (value: UserPermissions) => {
userForm.value.permissions = value
},
})
// 切换权限状态
function togglePermission(key: keyof UserPermissions) {
const currentPermissions = userPermissions.value
userPermissions.value = {
...currentPermissions,
[key]: !currentPermissions[key],
}
}
// 更新头像
function changeAvatar(file: Event) {
const fileReader = new FileReader()
@@ -164,6 +230,10 @@ async function addUser() {
}
userForm.value.password = newPassword.value
}
// 设置权限数据
userForm.value.permissions = userPermissions.value
isAdding.value = true
startNProgress()
try {
@@ -216,8 +286,10 @@ async function updateUser() {
isUpdating.value = true
startNProgress()
try {
// 确保昵称保存,使用一个临时变量存储完整数据
// 确保昵称和权限保存,使用一个临时变量存储完整数据
const userData = { ...userForm.value }
// 确保权限数据正确传递
userData.permissions = userPermissions.value
const result: { [key: string]: any } = await api.put('user/', userData)
@@ -235,6 +307,10 @@ async function updateUser() {
if (oldAvatar !== currentAvatar.value && isCurrentUser.value) {
userStore.setAvatar(currentAvatar.value)
}
// 如果是当前登录用户,更新权限信息
if (isCurrentUser.value) {
userStore.setPermissions(userPermissions.value)
}
emit('save')
} else {
if (oldUserName !== currentUserName.value) {
@@ -473,6 +549,67 @@ onMounted(() => {
/>
</VCol>
</VRow>
<VDivider class="my-10" v-if="canControl">
<span>{{ t('dialog.userAddEdit.permissions.title') }}</span>
</VDivider>
<!-- 权限设置 -->
<div v-if="canControl" class="mt-4">
<div class="mb-4">
<VBtn
variant="outlined"
color="primary"
size="small"
@click="userPermissions = { discovery: true, search: true, subscribe: true, manage: false }"
class="me-2"
>
{{ t('dialog.userAddEdit.permissions.presetNormal') }}
</VBtn>
<VBtn
variant="outlined"
color="warning"
size="small"
@click="userPermissions = { discovery: true, search: true, subscribe: true, manage: true }"
>
{{ t('dialog.userAddEdit.permissions.presetAdmin') }}
</VBtn>
</div>
<VRow>
<VCol v-for="option in permissionOptions" :key="option.key" cols="12" md="6">
<VCard
:color="userPermissions[option.key as keyof UserPermissions] ? 'primary' : 'surface'"
:variant="userPermissions[option.key as keyof UserPermissions] ? 'tonal' : 'outlined'"
class="cursor-pointer transition-all"
@click="togglePermission(option.key as keyof UserPermissions)"
hover
>
<VCardText class="d-flex align-center pa-4">
<VAvatar
:color="userPermissions[option.key as keyof UserPermissions] ? 'primary' : 'surface-variant'"
size="40"
class="me-3"
>
<VIcon :icon="option.icon" />
</VAvatar>
<div class="flex-grow-1">
<div class="text-subtitle-1 font-weight-medium d-flex align-center">
{{ option.title }}
<VIcon
v-if="userPermissions[option.key as keyof UserPermissions]"
icon="mdi-check-circle"
color="primary"
size="small"
class="ms-2"
/>
</div>
<div class="text-caption text-medium-emphasis">
{{ option.description }}
</div>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</div>
</VForm>
</VCardText>
<VCardActions class="pt-3">