From b27eeea6c0651688882926c2b0988a5d7bc5faaf Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 7 Jul 2026 08:54:09 +0800 Subject: [PATCH] Improve frontend dialog and layout behavior --- src/@layouts/types.d.ts | 5 +- src/components/dialog/SearchBarDialog.vue | 1 + src/components/dialog/UserAddEditDialog.vue | 575 ++++++++++++++++-- src/components/filter/TorrentFilterBar.vue | 35 +- src/composables/useDynamicButton.ts | 14 +- src/composables/useDynamicHeaderTab.ts | 10 +- .../default/components/DefaultLayout.vue | 26 +- src/layouts/default/components/Footer.vue | 1 + src/locales/en-US.ts | 38 ++ src/locales/zh-CN.ts | 38 ++ src/locales/zh-TW.ts | 38 ++ src/router/i18n-menu.ts | 12 + src/router/index.ts | 45 +- src/utils/permission.ts | 212 ++++++- src/utils/pluginSidebarNav.ts | 3 +- 15 files changed, 940 insertions(+), 113 deletions(-) diff --git a/src/@layouts/types.d.ts b/src/@layouts/types.d.ts index e8425b60..4d23a1f9 100644 --- a/src/@layouts/types.d.ts +++ b/src/@layouts/types.d.ts @@ -1,6 +1,6 @@ import type { Component, Ref, VNode } from 'vue' import type { RouteLocationRaw } from 'vue-router' -import type { UserPermissionKey } from '@/utils/permission' +import type { UserPermissionFeatureKey, UserPermissionKey } from '@/utils/permission' import { ContentWidth, FooterType, NavbarType } from './enums' export interface UserConfig { @@ -124,6 +124,7 @@ export interface NavLink extends NavLinkProps, Partial { badgeClass?: string disable?: boolean permission?: UserPermissionKey + feature?: UserPermissionFeatureKey } export interface NavMenuTabItem { @@ -131,6 +132,8 @@ export interface NavMenuTabItem { icon?: string tab: string description?: string + permission?: UserPermissionKey + feature?: UserPermissionFeatureKey } export interface NavMenu extends NavLink { diff --git a/src/components/dialog/SearchBarDialog.vue b/src/components/dialog/SearchBarDialog.vue index 02b3ab3d..0c6c349c 100644 --- a/src/components/dialog/SearchBarDialog.vue +++ b/src/components/dialog/SearchBarDialog.vue @@ -153,6 +153,7 @@ function getMenus(): NavMenu[] { header: item.header, admin: item.admin, permission: item.permission, + feature: item.feature, }), ) // 设置标签页 diff --git a/src/components/dialog/UserAddEditDialog.vue b/src/components/dialog/UserAddEditDialog.vue index 4973e30b..056b4940 100644 --- a/src/components/dialog/UserAddEditDialog.vue +++ b/src/components/dialog/UserAddEditDialog.vue @@ -7,6 +7,14 @@ import { useDisplay } from 'vuetify' import avatar1 from '@images/avatars/avatar-1.png' import { useUserStore } from '@/stores' import { useI18n } from 'vue-i18n' +import { + USER_PERMISSION_FEATURES, + buildDefaultFeaturePermissions, + normalizeUserPermissions, + type UserPermissionCategoryKey, + type UserPermissionFeatureKey, + type UserPermissions, +} from '@/utils/permission' // 多语言支持 const { t } = useI18n() @@ -65,14 +73,6 @@ interface ExtendedUser extends User { nickname?: string } -// 权限类型定义 -interface UserPermissions { - discovery: boolean // 发现权限 - search: boolean // 搜索权限 - subscribe: boolean // 订阅权限 - manage: boolean // 管理权限 -} - // 用户编辑表单数据 const userForm = ref({ id: 0, @@ -88,6 +88,7 @@ const userForm = ref({ search: true, subscribe: true, manage: false, + features: buildDefaultFeaturePermissions(), }, settings: { wechat_userid: null, @@ -127,34 +128,115 @@ const permissionOptions = [ description: t('dialog.userAddEdit.permissions.manageDesc'), icon: 'mdi-cog-outline', }, -] +] as const + +const activePermissionCategory = ref('discovery') // 权限状态计算属性 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, - } + return normalizeUserPermissions(userForm.value.permissions as Partial) }, set: (value: UserPermissions) => { userForm.value.permissions = value }, }) -// 切换权限状态 -function togglePermission(key: keyof UserPermissions) { +const permissionFeatureOptions = computed(() => + USER_PERMISSION_FEATURES.map(feature => ({ + ...feature, + title: t(feature.titleKey), + description: t(feature.descriptionKey), + })), +) + +const activePermissionOption = computed(() => + permissionOptions.find(option => option.key === activePermissionCategory.value) ?? permissionOptions[0], +) + +const activePermissionFeatures = computed(() => + permissionFeatureOptions.value.filter(feature => feature.permission === activePermissionCategory.value), +) + +const enabledCategoryCount = computed( + () => permissionOptions.filter(option => userPermissions.value[option.key]).length, +) + +const enabledFeatureCount = computed( + () => + permissionFeatureOptions.value.filter( + feature => userPermissions.value[feature.permission] && isFeatureEnabled(feature.key), + ).length, +) + +/** 切换当前查看的权限分类。 */ +function selectPermissionCategory(key: UserPermissionCategoryKey) { + activePermissionCategory.value = key +} + +/** 切换权限分类启用状态,并聚焦到该分类的功能列表。 */ +function togglePermission(key: UserPermissionCategoryKey) { const currentPermissions = userPermissions.value userPermissions.value = { ...currentPermissions, [key]: !currentPermissions[key], } + activePermissionCategory.value = key } -// 更新头像 +/** 判断功能项是否已启用,缺省功能按启用处理以兼容旧权限数据。 */ +function isFeatureEnabled(key: UserPermissionFeatureKey) { + return userPermissions.value.features?.[key] !== false +} + +/** 切换单个功能项的启用状态。 */ +function togglePermissionFeature(key: UserPermissionFeatureKey) { + userPermissions.value = { + ...userPermissions.value, + features: { + ...(userPermissions.value.features ?? {}), + [key]: !isFeatureEnabled(key), + }, + } +} + +/** 统计指定分类下已经勾选的功能数量。 */ +function getEnabledFeatureCount(permission: UserPermissionCategoryKey) { + return permissionFeatureOptions.value.filter( + feature => feature.permission === permission && isFeatureEnabled(feature.key), + ).length +} + +/** 返回指定分类下的功能总数。 */ +function getFeatureTotalCount(permission: UserPermissionCategoryKey) { + return permissionFeatureOptions.value.filter(feature => feature.permission === permission).length +} + +/** 判断指定分类是否处于功能部分勾选状态。 */ +function isPermissionPartiallySelected(permission: UserPermissionCategoryKey) { + const enabledCount = getEnabledFeatureCount(permission) + const totalCount = getFeatureTotalCount(permission) + return enabledCount > 0 && enabledCount < totalCount +} + +/** 批量设置当前分类下的所有功能项。 */ +function setCategoryFeatures(permission: UserPermissionCategoryKey, enabled: boolean) { + const nextFeatures = { ...(userPermissions.value.features ?? {}) } + for (const feature of permissionFeatureOptions.value.filter(item => item.permission === permission)) { + nextFeatures[feature.key] = enabled + } + userPermissions.value = { + ...userPermissions.value, + features: nextFeatures, + } +} + +/** 将当前分类下的功能项恢复为默认全选状态。 */ +function resetCategoryFeatures(permission: UserPermissionCategoryKey) { + setCategoryFeatures(permission, true) +} + +/** 校验并读取用户上传的新头像。 */ function changeAvatar(file: Event) { const fileReader = new FileReader() const { files } = file.target as HTMLInputElement @@ -182,19 +264,19 @@ function changeAvatar(file: Event) { } } -// 重置默认头像 +/** 将头像恢复为系统默认头像。 */ function resetDefaultAvatar() { currentAvatar.value = avatar1 $toast.success(t('dialog.userAddEdit.resetAvatarSuccess')) } -// 还原当前头像 +/** 还原为用户当前已保存的头像。 */ function restoreCurrentAvatar() { currentAvatar.value = userForm.value.avatar $toast.success(t('dialog.userAddEdit.restoreAvatarSuccess')) } -// 查询用户信息 +/** 从接口查询当前编辑用户的信息并回填表单。 */ async function fetchUserInfo() { try { userForm.value = await api.get(`user/${props.username}`) @@ -210,7 +292,7 @@ async function fetchUserInfo() { } } -// 调用API 新增用户 +/** 调用接口创建新用户。 */ async function addUser() { if (isAdding.value) { $toast.error(t('dialog.userAddEdit.creatingUser', { name: userForm.value.name })) @@ -256,7 +338,7 @@ async function addUser() { isAdding.value = false } -// 调用API更新用户信息 +/** 调用接口更新当前用户信息。 */ async function updateUser() { if (isUpdating.value) { $toast.error(t('dialog.userAddEdit.updatingUser', { name: userForm.value.name })) @@ -367,7 +449,7 @@ onMounted(() => {