mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-04 23:18:44 +08:00
优化多个组件的样式和结构,简化了主题切换器、快捷栏、用户通知和用户个人资料的布局,提升了用户体验。同时,移除了不必要的样式,确保组件在不同主题下的显示效果一致。
This commit is contained in:
@@ -195,36 +195,31 @@ onMounted(() => {
|
|||||||
<VIcon :icon="getThemeIcon" />
|
<VIcon :icon="getThemeIcon" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
<VList class="theme-switcher-list pt-0 overflow-hidden">
|
<VList class="pt-0">
|
||||||
<VCardItem class="theme-switcher-header">
|
<VCardItem class="py-3">
|
||||||
<VCardTitle class="font-weight-medium text-primary">主题选择</VCardTitle>
|
<VCardTitle>主题选择</VCardTitle>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<div class="theme-switcher-options px-2">
|
<VDivider class="mb-2" />
|
||||||
|
<div class="px-2">
|
||||||
<VListItem
|
<VListItem
|
||||||
v-for="theme in props.themes"
|
v-for="theme in props.themes"
|
||||||
:key="theme.name"
|
:key="theme.name"
|
||||||
@click="changeTheme(theme.name)"
|
@click="changeTheme(theme.name)"
|
||||||
class="theme-option"
|
:active="currentThemeName === theme.name"
|
||||||
:class="{ 'theme-option-active': currentThemeName === theme.name }"
|
class="mb-1"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<div class="theme-icon-wrapper">
|
<VIcon :icon="theme.icon" />
|
||||||
<VIcon :icon="theme.icon" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>{{ theme.title }}</VListItemTitle>
|
<VListItemTitle>{{ theme.title }}</VListItemTitle>
|
||||||
<template #append v-if="currentThemeName === theme.name">
|
<template #append v-if="currentThemeName === theme.name">
|
||||||
<VIcon icon="mdi-check" color="primary" size="small" />
|
<VIcon icon="mdi-check" color="primary" size="small" />
|
||||||
</template>
|
</template>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
|
|
||||||
<VDivider class="my-2" />
|
<VDivider class="my-2" />
|
||||||
|
<VListItem @click="cssDialog = true">
|
||||||
<VListItem @click="cssDialog = true" class="theme-option custom-theme-option">
|
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<div class="theme-icon-wrapper custom-theme-icon">
|
<VIcon icon="mdi-palette" />
|
||||||
<VIcon icon="mdi-palette" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>自定义主题</VListItemTitle>
|
<VListItemTitle>自定义主题</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
@@ -242,12 +237,7 @@ onMounted(() => {
|
|||||||
<VDialogCloseBtn @click="cssDialog = false" />
|
<VDialogCloseBtn @click="cssDialog = false" />
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VAceEditor
|
<VAceEditor v-model:value="customCSS" lang="css" :theme="editorTheme" class="w-full min-h-[30rem]" />
|
||||||
v-model:value="customCSS"
|
|
||||||
lang="css"
|
|
||||||
:theme="editorTheme"
|
|
||||||
style="block-size: 100%; min-block-size: 30rem"
|
|
||||||
/>
|
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText class="text-center">
|
<VCardText class="text-center">
|
||||||
<VBtn @click="saveCustomCSS" class="w-1/2">
|
<VBtn @click="saveCustomCSS" class="w-1/2">
|
||||||
@@ -260,73 +250,3 @@ onMounted(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.theme-switcher-header {
|
|
||||||
background: linear-gradient(to right, rgba(var(--v-theme-primary), 0.04), rgba(var(--v-theme-primary), 0.01));
|
|
||||||
border-block-end: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
|
||||||
padding-block: 12px;
|
|
||||||
padding-inline: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-switcher-options {
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-option {
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-block: 4px;
|
|
||||||
margin-inline: 0;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.04);
|
|
||||||
transform: translateX(4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.theme-option-active {
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-icon-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.08);
|
|
||||||
block-size: 36px;
|
|
||||||
inline-size: 36px;
|
|
||||||
margin-inline-end: 12px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
.v-icon {
|
|
||||||
color: rgba(var(--v-theme-primary), 0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-theme-icon {
|
|
||||||
background: linear-gradient(135deg, rgba(var(--v-theme-primary), 0.15), rgba(var(--v-theme-info), 0.15));
|
|
||||||
|
|
||||||
.v-icon {
|
|
||||||
color: rgba(var(--v-theme-primary), 0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theme transition
|
|
||||||
.app-copy {
|
|
||||||
position: fixed !important;
|
|
||||||
z-index: -1 !important;
|
|
||||||
overflow: clip !important;
|
|
||||||
contain: size style !important;
|
|
||||||
pointer-events: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-transition {
|
|
||||||
--clip-size: 0;
|
|
||||||
--clip-pos: 0 0;
|
|
||||||
|
|
||||||
clip-path: circle(var(--clip-size) at var(--clip-pos));
|
|
||||||
transition: clip-path 0.35s ease-out;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const currentPath = computed(() => route.path)
|
|||||||
:color="moreActiveState ? 'primary' : ''"
|
:color="moreActiveState ? 'primary' : ''"
|
||||||
/>
|
/>
|
||||||
<VMenu v-model="moreMenuDialog" close-on-content-click activator="parent" scrim>
|
<VMenu v-model="moreMenuDialog" close-on-content-click activator="parent" scrim>
|
||||||
<VList class="font-bold" lines="one">
|
<VList lines="one">
|
||||||
<VListSubheader class="bg-transparent"> 更多 </VListSubheader>
|
<VListSubheader class="bg-transparent"> 更多 </VListSubheader>
|
||||||
<VListItem
|
<VListItem
|
||||||
class="pe-20 ps-5"
|
class="pe-20 ps-5"
|
||||||
@@ -73,7 +73,7 @@ const currentPath = computed(() => route.path)
|
|||||||
:base-color="currentPath === menu.to ? 'primary' : undefined"
|
:base-color="currentPath === menu.to ? 'primary' : undefined"
|
||||||
>
|
>
|
||||||
<VListItemTitle>
|
<VListItemTitle>
|
||||||
<span class="text-lg">{{ menu.title }}</span>
|
<span class="text-base">{{ menu.title }}</span>
|
||||||
</VListItemTitle>
|
</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
</VList>
|
</VList>
|
||||||
|
|||||||
@@ -45,6 +45,57 @@ const sendButtonDisabled = ref(false)
|
|||||||
// 聊天容器
|
// 聊天容器
|
||||||
const chatContainer = ref<HTMLElement>()
|
const chatContainer = ref<HTMLElement>()
|
||||||
|
|
||||||
|
// 定义捷径列表
|
||||||
|
const shortcuts = [
|
||||||
|
{
|
||||||
|
title: '识别',
|
||||||
|
subtitle: '名称识别测试',
|
||||||
|
icon: 'mdi-text-recognition',
|
||||||
|
dialog: 'nameTest',
|
||||||
|
dialogRef: nameTestDialog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '规则',
|
||||||
|
subtitle: '规则测试',
|
||||||
|
icon: 'mdi-filter-cog',
|
||||||
|
dialog: 'ruleTest',
|
||||||
|
dialogRef: ruleTestDialog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '日志',
|
||||||
|
subtitle: '实时日志',
|
||||||
|
icon: 'mdi-file-document',
|
||||||
|
dialog: 'logging',
|
||||||
|
dialogRef: loggingDialog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '网络',
|
||||||
|
subtitle: '网速连通性测试',
|
||||||
|
icon: 'mdi-network',
|
||||||
|
dialog: 'netTest',
|
||||||
|
dialogRef: netTestDialog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '系统',
|
||||||
|
subtitle: '健康检查',
|
||||||
|
icon: 'mdi-cog',
|
||||||
|
dialog: 'systemTest',
|
||||||
|
dialogRef: systemTestDialog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '消息',
|
||||||
|
subtitle: '消息中心',
|
||||||
|
icon: 'mdi-message',
|
||||||
|
dialog: 'message',
|
||||||
|
dialogRef: messageDialog,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 打开对话框
|
||||||
|
function openDialog(dialogRef) {
|
||||||
|
dialogRef.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 滚动到底部
|
// 滚动到底部
|
||||||
function scrollMessageToEnd() {
|
function scrollMessageToEnd() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -78,25 +129,9 @@ onMounted(() => {
|
|||||||
scrollMessageToEnd()
|
scrollMessageToEnd()
|
||||||
const shortcut = getQueryValue('shortcut')
|
const shortcut = getQueryValue('shortcut')
|
||||||
if (shortcut) {
|
if (shortcut) {
|
||||||
switch (shortcut) {
|
const found = shortcuts.find(item => item.dialog === shortcut)
|
||||||
case 'nameTest':
|
if (found) {
|
||||||
nameTestDialog.value = true
|
found.dialogRef.value = true
|
||||||
break
|
|
||||||
case 'netTest':
|
|
||||||
netTestDialog.value = true
|
|
||||||
break
|
|
||||||
case 'logging':
|
|
||||||
loggingDialog.value = true
|
|
||||||
break
|
|
||||||
case 'ruleTest':
|
|
||||||
ruleTestDialog.value = true
|
|
||||||
break
|
|
||||||
case 'systemTest':
|
|
||||||
systemTestDialog.value = true
|
|
||||||
break
|
|
||||||
case 'message':
|
|
||||||
messageDialog.value = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -110,7 +145,6 @@ onMounted(() => {
|
|||||||
max-height="560"
|
max-height="560"
|
||||||
location="top end"
|
location="top end"
|
||||||
origin="top end"
|
origin="top end"
|
||||||
transition="scale-transition"
|
|
||||||
close-on-content-click
|
close-on-content-click
|
||||||
close-on-back
|
close-on-back
|
||||||
scrim
|
scrim
|
||||||
@@ -122,81 +156,35 @@ onMounted(() => {
|
|||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
<!-- Menu Content -->
|
<!-- Menu Content -->
|
||||||
<VCard class="shortcut-menu-card">
|
<VCard class="overflow-hidden">
|
||||||
<VCardItem class="shortcut-header border-b">
|
<VCardItem class="py-3">
|
||||||
<VCardTitle class="font-weight-medium text-primary">捷径</VCardTitle>
|
<VCardTitle>捷径</VCardTitle>
|
||||||
<template #append>
|
<template #append>
|
||||||
<IconBtn @click="appsMenu = false" class="shortcut-close-btn">
|
<IconBtn @click="appsMenu = false">
|
||||||
<VIcon icon="mdi-close" />
|
<VIcon icon="mdi-close" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<div class="ps ps--active-y shortcut-menu-container">
|
<VDivider />
|
||||||
<div class="shortcut-grid">
|
<div class="pa-3">
|
||||||
<!-- 识别 -->
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div class="shortcut-item" @click="nameTestDialog = true">
|
<!-- 循环渲染快捷方式 -->
|
||||||
<div class="shortcut-icon-wrapper">
|
<div v-for="(item, index) in shortcuts" :key="index">
|
||||||
<VIcon icon="mdi-text-recognition" size="24" />
|
<VCard
|
||||||
</div>
|
flat
|
||||||
<div class="shortcut-text">
|
variant="outlined"
|
||||||
<div class="shortcut-title">识别</div>
|
class="pa-4 d-flex align-center rounded-lg cursor-pointer transition-transform duration-300 hover:-translate-y-1"
|
||||||
<div class="shortcut-subtitle">名称识别测试</div>
|
hover
|
||||||
</div>
|
@click="openDialog(item.dialogRef)"
|
||||||
</div>
|
>
|
||||||
|
<VAvatar variant="tonal" size="48" class="me-4" rounded="lg">
|
||||||
<!-- 规则 -->
|
<VIcon :icon="item.icon" size="24" />
|
||||||
<div class="shortcut-item" @click="ruleTestDialog = true">
|
</VAvatar>
|
||||||
<div class="shortcut-icon-wrapper">
|
<div>
|
||||||
<VIcon icon="mdi-filter-cog" size="24" />
|
<div class="text-body-1 text-high-emphasis font-weight-medium">{{ item.title }}</div>
|
||||||
</div>
|
<div class="text-caption text-medium-emphasis">{{ item.subtitle }}</div>
|
||||||
<div class="shortcut-text">
|
</div>
|
||||||
<div class="shortcut-title">规则</div>
|
</VCard>
|
||||||
<div class="shortcut-subtitle">规则测试</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 日志 -->
|
|
||||||
<div class="shortcut-item" @click="loggingDialog = true">
|
|
||||||
<div class="shortcut-icon-wrapper">
|
|
||||||
<VIcon icon="mdi-file-document" size="24" />
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-text">
|
|
||||||
<div class="shortcut-title">日志</div>
|
|
||||||
<div class="shortcut-subtitle">实时日志</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 网络 -->
|
|
||||||
<div class="shortcut-item" @click="netTestDialog = true">
|
|
||||||
<div class="shortcut-icon-wrapper">
|
|
||||||
<VIcon icon="mdi-network" size="24" />
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-text">
|
|
||||||
<div class="shortcut-title">网络</div>
|
|
||||||
<div class="shortcut-subtitle">网速连通性测试</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 系统 -->
|
|
||||||
<div class="shortcut-item" @click="systemTestDialog = true">
|
|
||||||
<div class="shortcut-icon-wrapper">
|
|
||||||
<VIcon icon="mdi-cog" size="24" />
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-text">
|
|
||||||
<div class="shortcut-title">系统</div>
|
|
||||||
<div class="shortcut-subtitle">健康检查</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 消息 -->
|
|
||||||
<div class="shortcut-item" @click="messageDialog = true">
|
|
||||||
<div class="shortcut-icon-wrapper">
|
|
||||||
<VIcon icon="mdi-message" size="24" />
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-text">
|
|
||||||
<div class="shortcut-title">消息</div>
|
|
||||||
<div class="shortcut-subtitle">消息中心</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -245,16 +233,14 @@ onMounted(() => {
|
|||||||
<VCard>
|
<VCard>
|
||||||
<VDialogCloseBtn @click="loggingDialog = false" />
|
<VDialogCloseBtn @click="loggingDialog = false" />
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle class="inline-flex">
|
<VCardTitle class="d-inline-flex">
|
||||||
<VIcon icon="mdi-file-document" class="me-2" />
|
<VIcon icon="mdi-file-document" class="me-2" />
|
||||||
实时日志
|
实时日志
|
||||||
<a class="mx-2 inline-flex items-center justify-center" :href="allLoggingUrl()" target="_blank">
|
<a class="mx-2 d-inline-flex align-center" :href="allLoggingUrl()" target="_blank">
|
||||||
<div
|
<VChip color="grey-darken-1" size="small" class="ml-2">
|
||||||
class="inline-flex cursor-pointer items-center rounded-full bg-gray-600 px-2 text-sm text-gray-200 ring-1 ring-gray-500 transition hover:bg-gray-700"
|
<VIcon icon="mdi-open-in-new" size="small" start />
|
||||||
>
|
在新窗口中打开
|
||||||
<VIcon icon="mdi-open-in-new" />
|
</VChip>
|
||||||
<span class="ms-1">在新窗口中打开</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
</VCardTitle>
|
</VCardTitle>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
@@ -333,113 +319,3 @@ onMounted(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.shortcut-menu-card {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-header {
|
|
||||||
background: linear-gradient(to right, rgba(var(--v-theme-primary), 0.04), rgba(var(--v-theme-primary), 0.01));
|
|
||||||
border-block-end: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
|
||||||
padding-block: 12px;
|
|
||||||
padding-inline: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-close-btn {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-menu-container {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 16px;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-icon-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 12px;
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.08);
|
|
||||||
block-size: 48px;
|
|
||||||
inline-size: 48px;
|
|
||||||
margin-inline-end: 16px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
.v-icon {
|
|
||||||
color: rgba(var(--v-theme-primary), 1);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-item {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
overflow: hidden;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.05);
|
|
||||||
border-radius: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
z-index: -1;
|
|
||||||
background: linear-gradient(135deg, rgba(var(--v-theme-primary), 0.08) 0%, rgba(var(--v-theme-primary), 0) 60%);
|
|
||||||
content: '';
|
|
||||||
inset: 0;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: rgba(var(--v-theme-primary), 0.15);
|
|
||||||
transform: translateY(-4px);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-icon-wrapper {
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.12);
|
|
||||||
transform: scale(1.1);
|
|
||||||
|
|
||||||
.v-icon {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
box-shadow: 0 3px 10px rgba(var(--v-theme-on-surface), 0.08);
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-text {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-title {
|
|
||||||
color: rgba(var(--v-theme-on-surface), 0.95);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-block-end: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-subtitle {
|
|
||||||
color: rgba(var(--v-theme-on-surface), 0.7);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -41,7 +41,14 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VMenu v-model="appsMenu" width="400" transition="scale-transition" close-on-content-click class="notification-menu" scrim>
|
<VMenu
|
||||||
|
v-model="appsMenu"
|
||||||
|
width="400"
|
||||||
|
transition="scale-transition"
|
||||||
|
close-on-content-click
|
||||||
|
class="notification-menu"
|
||||||
|
scrim
|
||||||
|
>
|
||||||
<!-- Menu Activator -->
|
<!-- Menu Activator -->
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<VBadge v-if="hasNewMessage" dot color="error" :offset-x="5" :offset-y="5" v-bind="props">
|
<VBadge v-if="hasNewMessage" dot color="error" :offset-x="5" :offset-y="5" v-bind="props">
|
||||||
@@ -55,13 +62,12 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
<!-- Menu Content -->
|
<!-- Menu Content -->
|
||||||
<VCard>
|
<VCard>
|
||||||
<VCardItem class="notification-header">
|
<VCardItem class="py-3">
|
||||||
<VCardTitle class="font-weight-medium text-primary">通知中心</VCardTitle>
|
<VCardTitle>通知中心</VCardTitle>
|
||||||
<template #append>
|
<template #append>
|
||||||
<VTooltip text="设为已读">
|
<VTooltip text="设为已读">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<IconBtn
|
<IconBtn
|
||||||
class="mark-read-btn"
|
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
@@ -76,89 +82,33 @@ onBeforeUnmount(() => {
|
|||||||
</VTooltip>
|
</VTooltip>
|
||||||
</template>
|
</template>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<div v-if="notificationList.length > 0" class="notification-list">
|
<VDivider />
|
||||||
<VListItem v-for="(item, i) in notificationList" :key="i" lines="two" class="notification-item">
|
<div v-if="notificationList.length > 0">
|
||||||
|
<VListItem v-for="(item, i) in notificationList" :key="i" lines="two" class="mb-1">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VAvatar rounded class="notification-avatar">
|
<VAvatar rounded>
|
||||||
<VIcon v-if="item.type === 'user'" icon="mdi-account-alert" size="large"></VIcon>
|
<VIcon v-if="item.type === 'user'" icon="mdi-account-alert" size="large"></VIcon>
|
||||||
<VIcon v-else-if="item.type === 'plugin'" icon="mdi-robot" size="large"></VIcon>
|
<VIcon v-else-if="item.type === 'plugin'" icon="mdi-robot" size="large"></VIcon>
|
||||||
<VIcon v-else icon="mdi-laptop" size="large"></VIcon>
|
<VIcon v-else icon="mdi-laptop" size="large"></VIcon>
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
</template>
|
</template>
|
||||||
<div class="notification-content">
|
<div>
|
||||||
<div class="notification-title overflow-visiable break-words whitespace-break-spaces">
|
<div class="text-body-1 text-high-emphasis break-words whitespace-break-spaces">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</div>
|
</div>
|
||||||
<div class="notification-text">{{ item.text }}</div>
|
<div class="text-caption mt-1.5">
|
||||||
<div class="notification-time">{{ formatDateDifference(item.date) }}</div>
|
{{ item.text }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-primary mt-1.5">
|
||||||
|
{{ formatDateDifference(item.date) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="no-notification">
|
<div v-else class="py-8 text-center">
|
||||||
<div class="text-center">
|
<VIcon icon="mdi-bell-sleep-outline" size="40" class="mb-3" />
|
||||||
<VIcon icon="mdi-bell-sleep-outline" size="40" class="mb-3 text-primary" />
|
<div>暂无通知</div>
|
||||||
<div>暂无通知</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VMenu>
|
</VMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.notification-header {
|
|
||||||
background: linear-gradient(to right, rgba(var(--v-theme-primary), 0.04), rgba(var(--v-theme-primary), 0.01));
|
|
||||||
border-block-end: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
|
||||||
padding-block: 12px;
|
|
||||||
padding-inline: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-list {
|
|
||||||
padding: 8px;
|
|
||||||
max-block-size: 500px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-item {
|
|
||||||
.notification-avatar {
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-title {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-text {
|
|
||||||
color: rgba(var(--v-theme-on-surface), 0.75);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin-block-start: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-time {
|
|
||||||
color: rgba(var(--v-theme-primary), 0.8);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin-block-start: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-notification {
|
|
||||||
color: rgba(var(--v-theme-on-surface), 0.6);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
padding-block: 30px;
|
|
||||||
padding-inline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mark-read-btn {
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-menu .v-overlay__content {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -86,59 +86,52 @@ const userLevel = computed(() => userStore.level)
|
|||||||
<VImg :src="avatar" />
|
<VImg :src="avatar" />
|
||||||
|
|
||||||
<VMenu activator="parent" width="230" location="bottom end" offset="14px" class="user-menu" scrim>
|
<VMenu activator="parent" width="230" location="bottom end" offset="14px" class="user-menu" scrim>
|
||||||
<VList class="overflow-hidden pt-0">
|
<VList class="pt-0">
|
||||||
<!-- 👉 User Avatar & Name -->
|
<!-- 👉 User Avatar & Name -->
|
||||||
<div class="user-profile-header px-2 py-4 mb-2">
|
<VListItem class="py-4" bg-color="primary" bg-opacity="0.05">
|
||||||
<div class="d-flex align-center">
|
<template #prepend>
|
||||||
<VAvatar size="60" class="user-avatar" color="primary" rounded="sm">
|
<VAvatar size="60" color="primary" rounded="sm" class="border-2 border-opacity-10">
|
||||||
<VImg :src="avatar" />
|
<VImg :src="avatar" />
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
<div class="ms-4">
|
</template>
|
||||||
<div class="user-role">
|
<div>
|
||||||
{{ superUser ? '管理员' : '普通用户' }}
|
<span class="text-primary text-sm font-medium d-block">
|
||||||
</div>
|
{{ superUser ? '管理员' : '普通用户' }}
|
||||||
<div class="user-name">
|
</span>
|
||||||
{{ userName }}
|
<span class="text-high-emphasis text-lg font-weight-bold">
|
||||||
</div>
|
{{ userName }}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</VListItem>
|
||||||
|
<VDivider class="mb-2" />
|
||||||
<div class="px-2">
|
<div class="px-2">
|
||||||
<!-- 👉 Profile -->
|
<!-- 👉 Profile -->
|
||||||
<VListItem link @click="router.push('/profile')" class="user-menu-item mb-1">
|
<VListItem link @click="router.push('/profile')" class="mb-1 rounded-lg" hover>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<div class="user-menu-icon">
|
<VIcon icon="mdi-account-outline" />
|
||||||
<VIcon icon="mdi-account-outline" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>个人信息</VListItemTitle>
|
<VListItemTitle>个人信息</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
|
|
||||||
<VListItem link @click="router.push('/apps')" class="user-menu-item mb-1">
|
<VListItem link @click="router.push('/apps')" class="mb-1 rounded-lg" hover>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<div class="user-menu-icon">
|
<VIcon icon="mdi-view-grid-outline" />
|
||||||
<VIcon icon="mdi-view-grid-outline" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>功能视图</VListItemTitle>
|
<VListItemTitle>功能视图</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
|
|
||||||
<!-- 👉 Site Auth -->
|
<!-- 👉 Site Auth -->
|
||||||
<VListItem v-if="userLevel < 2 && superUser" link @click="showSiteAuthDialog" class="user-menu-item mb-1">
|
<VListItem v-if="userLevel < 2 && superUser" link @click="showSiteAuthDialog" class="mb-1 rounded-lg" hover>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<div class="user-menu-icon">
|
<VIcon icon="mdi-lock-check-outline" />
|
||||||
<VIcon icon="mdi-lock-check-outline" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>用户认证</VListItemTitle>
|
<VListItemTitle>用户认证</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
|
|
||||||
<!-- 👉 FAQ -->
|
<!-- 👉 FAQ -->
|
||||||
<VListItem href="https://wiki.movie-pilot.org" target="_blank" class="user-menu-item mb-1">
|
<VListItem href="https://wiki.movie-pilot.org" target="_blank" class="mb-1 rounded-lg" hover>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<div class="user-menu-icon">
|
<VIcon icon="mdi-help-circle-outline" />
|
||||||
<VIcon icon="mdi-help-circle-outline" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>帮助文档</VListItemTitle>
|
<VListItemTitle>帮助文档</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
@@ -147,19 +140,19 @@ const userLevel = computed(() => userStore.level)
|
|||||||
<VDivider v-if="superUser" class="my-3" />
|
<VDivider v-if="superUser" class="my-3" />
|
||||||
|
|
||||||
<!-- 👉 restart -->
|
<!-- 👉 restart -->
|
||||||
<VListItem v-if="superUser" @click="showRestartDialog" class="user-menu-item mb-1">
|
<VListItem v-if="superUser" @click="showRestartDialog" class="mb-1 rounded-lg" hover>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<div class="user-menu-icon restart-icon">
|
<VIcon icon="mdi-restart" />
|
||||||
<VIcon icon="mdi-restart" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>重启</VListItemTitle>
|
<VListItemTitle>重启</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
</div>
|
</div>
|
||||||
<!-- 👉 Logout -->
|
<!-- 👉 Logout -->
|
||||||
<div class="px-2 mt-3 mb-2">
|
<div class="px-2 mt-3 mb-2">
|
||||||
<VBtn color="error" block class="logout-btn" @click="logout">
|
<VBtn color="error" block class="py-3 rounded-lg" elevation="2" @click="logout">
|
||||||
<template #prepend> <VIcon icon="mdi-logout" /> </template>
|
<template #prepend>
|
||||||
|
<VIcon icon="mdi-logout" />
|
||||||
|
</template>
|
||||||
退出登录
|
退出登录
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,94 +168,21 @@ const userLevel = computed(() => userStore.level)
|
|||||||
<VDialog v-if="restartDialog" v-model="restartDialog" max-width="25rem">
|
<VDialog v-if="restartDialog" v-model="restartDialog" max-width="25rem">
|
||||||
<VCard>
|
<VCard>
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<div class="flex items-center justify-center mt-3">
|
<div class="d-flex align-center justify-center mt-3">
|
||||||
<VAvatar color="warning" variant="text" size="x-large">
|
<VAvatar color="warning" variant="text" size="x-large">
|
||||||
<VIcon size="x-large" icon="mdi-alert" />
|
<VIcon size="x-large" icon="mdi-alert" />
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
<div class="ms-3">
|
<div class="ms-3">
|
||||||
<p class="font-bold text-xl text-high-emphasis">确认重启系统吗?</p>
|
<p class="font-weight-bold text-xl text-high-emphasis">确认重启系统吗?</p>
|
||||||
<p>重启后,您将被注销并需要重新登录。</p>
|
<p>重启后,您将被注销并需要重新登录。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VCardActions class="mx-auto">
|
<VCardActions class="mx-auto">
|
||||||
<VBtn variant="elevated" color="error" @click="restart" prepend-icon="mdi-restart" class="px-5"> 确定 </VBtn>
|
|
||||||
<VBtn variant="tonal" color="secondary" class="px-5" @click="restartDialog = false">取消</VBtn>
|
<VBtn variant="tonal" color="secondary" class="px-5" @click="restartDialog = false">取消</VBtn>
|
||||||
|
<VBtn variant="elevated" color="error" @click="restart" prepend-icon="mdi-restart" class="px-5"> 确定 </VBtn>
|
||||||
</VCardActions>
|
</VCardActions>
|
||||||
<VDialogCloseBtn @click="restartDialog = false" />
|
<VDialogCloseBtn @click="restartDialog = false" />
|
||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.user-profile-header {
|
|
||||||
background: linear-gradient(135deg, rgba(var(--v-theme-primary), 0.05), rgba(var(--v-theme-primary), 0.02));
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-role {
|
|
||||||
color: rgba(var(--v-theme-primary), 0.9);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-block-end: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-name {
|
|
||||||
color: rgba(var(--v-theme-on-surface), 0.9);
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-avatar {
|
|
||||||
border: 2px solid rgba(var(--v-theme-on-surface), 0.1);
|
|
||||||
box-shadow: 0 4px 12px rgba(var(--v-theme-primary), 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-menu-item {
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-block: 4px;
|
|
||||||
margin-inline: 0;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.06);
|
|
||||||
transform: translateX(4px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-menu-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.08);
|
|
||||||
block-size: 36px;
|
|
||||||
inline-size: 36px;
|
|
||||||
margin-inline-end: 12px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
.v-icon {
|
|
||||||
color: rgba(var(--v-theme-primary), 0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.restart-icon {
|
|
||||||
background-color: rgba(var(--v-theme-error), 0.1);
|
|
||||||
|
|
||||||
.v-icon {
|
|
||||||
color: rgba(var(--v-theme-error), 0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-btn {
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 4px 12px rgba(var(--v-theme-error), 0.2);
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-menu .v-overlay__content {
|
|
||||||
border-radius: 8px !important;
|
|
||||||
box-shadow: 0 4px 12px rgba(var(--v-theme-on-surface), 0.08) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export default {
|
|||||||
VBtn: {
|
VBtn: {
|
||||||
// set v-btn default color to primary
|
// set v-btn default color to primary
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
|
elevation: 0,
|
||||||
},
|
},
|
||||||
VCard: {
|
VCard: {
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user