mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-07 08:40:46 +08:00
- 新增路由 plugin-app 与壳页,按 nav_key 尝试 AppPage{Pascal}/AppPage/Page
- DefaultLayout 与 appcenter 合并插件侧栏项;plugin/sidebar_nav 经 Pinia 去重缓存
- 工具 pluginSidebarNav、联邦 loader 与文档/示例更新;登出时清空侧栏缓存
Made-with: Cursor
133 lines
3.6 KiB
Vue
133 lines
3.6 KiB
Vue
<script setup lang="ts">
|
||
import { NavMenu } from '@/@layouts/types'
|
||
import { getNavMenus } from '@/router/i18n-menu'
|
||
import { usePluginSidebarNavStore, useUserStore } from '@/stores'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { filterPluginSidebarNavEntries } from '@/utils/pluginSidebarNav'
|
||
import { filterMenusByPermission } from '@/utils/permission'
|
||
|
||
// 国际化
|
||
const { t } = useI18n()
|
||
|
||
const userStore = useUserStore()
|
||
const pluginSidebarNavStore = usePluginSidebarNavStore()
|
||
|
||
// 获取用户权限信息
|
||
const userPermissions = computed(() => ({
|
||
is_superuser: userStore.superUser,
|
||
...userStore.permissions,
|
||
}))
|
||
|
||
// 应用分组(以header分组)
|
||
const appGroups = ref<Record<string, NavMenu[]>>({})
|
||
|
||
// 根据header属性对应用进行分类(含插件侧栏项,与桌面端侧栏一致)
|
||
async function categorizeApps() {
|
||
const allMenus = getNavMenus(t)
|
||
const filteredMenus = filterMenusByPermission(allMenus, userPermissions.value)
|
||
let menus = filteredMenus.filter((item: NavMenu) => !item.footer)
|
||
|
||
await pluginSidebarNavStore.ensureSidebarNav()
|
||
if (pluginSidebarNavStore.items.length > 0) {
|
||
const pluginNavMenus = filterPluginSidebarNavEntries(
|
||
pluginSidebarNavStore.items,
|
||
t,
|
||
userPermissions.value,
|
||
).map(e => e.navMenu)
|
||
menus = [...menus, ...pluginNavMenus]
|
||
}
|
||
|
||
const groupedMenus: Record<string, NavMenu[]> = {}
|
||
|
||
menus.forEach(menu => {
|
||
const header = menu.header || t('appcenter.others')
|
||
if (!groupedMenus[header]) {
|
||
groupedMenus[header] = []
|
||
}
|
||
groupedMenus[header].push(menu)
|
||
})
|
||
|
||
appGroups.value = groupedMenus
|
||
}
|
||
|
||
onMounted(() => {
|
||
categorizeApps()
|
||
})
|
||
</script>
|
||
<template>
|
||
<div class="app-settings-container">
|
||
<VContainer>
|
||
<!-- 遍历所有分组 -->
|
||
<div v-for="(apps, header) in appGroups" :key="header" class="mb-3">
|
||
<VListSubheader class="ps-1">
|
||
{{ header }}
|
||
</VListSubheader>
|
||
<!-- 分组内容 - 使用卡片包装 -->
|
||
<VCard variant="flat" class="settings-section-card">
|
||
<VList lines="one" class="settings-list">
|
||
<VListItem
|
||
v-for="(app, appIndex) in apps"
|
||
:key="`${header}-${appIndex}-${String(app.to)}`"
|
||
:to="app.to || ''"
|
||
color="primary"
|
||
class="settings-list-item"
|
||
rounded="0"
|
||
>
|
||
<template #prepend>
|
||
<VAvatar size="42" color="primary" variant="text" class="me-3">
|
||
<VIcon :icon="app.icon as string" size="24"></VIcon>
|
||
</VAvatar>
|
||
</template>
|
||
|
||
<VListItemTitle class="font-weight-medium">
|
||
{{ app.full_title || app.title }}
|
||
</VListItemTitle>
|
||
|
||
<VListItemSubtitle v-if="app.description">
|
||
{{ app.description }}
|
||
</VListItemSubtitle>
|
||
|
||
<template #append>
|
||
<VIcon icon="mdi-chevron-right"></VIcon>
|
||
</template>
|
||
</VListItem>
|
||
</VList>
|
||
</VCard>
|
||
</div>
|
||
</VContainer>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.app-settings-container {
|
||
margin-block: 0;
|
||
margin-inline: auto;
|
||
max-inline-size: 960px;
|
||
}
|
||
|
||
.settings-section-card {
|
||
overflow: hidden;
|
||
backdrop-filter: blur(10px);
|
||
background-color: rgb(var(--v-theme-surface));
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 8%);
|
||
}
|
||
|
||
.settings-list {
|
||
padding: 0;
|
||
}
|
||
|
||
.settings-list-item {
|
||
padding-block: 8px;
|
||
padding-inline: 12px;
|
||
transition: background-color 0.2s;
|
||
|
||
&:not(:last-child) {
|
||
border-block-end: 1px solid rgba(var(--v-border-color), 0.12);
|
||
}
|
||
|
||
&:hover {
|
||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||
}
|
||
}
|
||
</style>
|