fix: 消息中心纯文本换行丢失、登录页优化、底部导航液态玻璃效果

This commit is contained in:
jxxghp
2026-04-07 13:10:19 +08:00
parent fe22403e66
commit e2e239f6d9
7 changed files with 176 additions and 13 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "moviepilot",
"version": "2.9.25",
"version": "2.9.26",
"private": true,
"type": "module",
"bin": "dist/service.js",

View File

@@ -24,6 +24,7 @@ const imageLoadError = ref(false)
// 初始化 markdown-it
const md = new MarkdownIt({
html: true,
breaks: true,
linkify: true,
typographer: true,
})

View File

@@ -171,7 +171,7 @@ const showDynamicButton = computed(() => {
<Teleport v-if="appMode && showNav" to="body">
<div class="footer-nav-container">
<TransitionGroup name="footer-nav" tag="div" class="footer-nav-group">
<VCard key="main-nav" elevation="3" class="footer-nav-card border" rounded="pill">
<VCard key="main-nav" :elevation="0" class="footer-nav-card" rounded="pill">
<VCardText class="footer-card-content">
<!-- 添加指示器 -->
<div ref="indicator" class="nav-indicator"></div>
@@ -217,8 +217,8 @@ const showDynamicButton = computed(() => {
<VCard
v-if="showDynamicButton"
key="dynamic-btn"
elevation="3"
class="footer-nav-card dynamic-btn-card border"
:elevation="0"
class="footer-nav-card dynamic-btn-card"
rounded="pill"
>
<VCardText class="footer-card-content">
@@ -260,23 +260,120 @@ const showDynamicButton = computed(() => {
// 按钮卡片之间的间距
> .v-card + .v-card {
margin-inline-start: 2px; // 减少间距
margin-inline-start: 4px;
}
}
.footer-nav-card {
position: relative;
overflow: hidden;
backdrop-filter: blur(24px);
background-color: rgba(var(--v-theme-surface), 0.6);
pointer-events: auto;
transition: all 0.5s cubic-bezier(0.25, 1, 0.5, 1);
will-change: transform, max-width, opacity;
// ===== iOS 26 Liquid Glass 效果 =====
// 强力毛玻璃背景模糊
backdrop-filter: blur(40px) saturate(1.8) brightness(1.05);
-webkit-backdrop-filter: blur(40px) saturate(1.8) brightness(1.05);
// 半透明玻璃底色 - 浅色模式
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.45) 0%,
rgba(255, 255, 255, 0.25) 40%,
rgba(255, 255, 255, 0.3) 100%
);
// 多层内发光边框 - 模拟玻璃边缘折射
border: 0.5px solid rgba(255, 255, 255, 0.5) !important;
box-shadow:
// 外层柔和投影
0 8px 32px rgba(0, 0, 0, 0.08),
0 2px 12px rgba(0, 0, 0, 0.04),
// 内发光 - 上边缘高光
inset 0 1px 1px rgba(255, 255, 255, 0.6),
// 内发光 - 整体光泽
inset 0 0 20px rgba(255, 255, 255, 0.1);
// 顶部光泽反射条
&::before {
content: '';
position: absolute;
inset-block-start: 0;
inset-inline-start: 10%;
z-index: 1;
border-radius: 0 0 50% 50%;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.5) 0%,
rgba(255, 255, 255, 0) 100%
);
block-size: 40%;
inline-size: 80%;
pointer-events: none;
}
// 底部微光
&::after {
content: '';
position: absolute;
inset-block-end: 0;
inset-inline-start: 5%;
z-index: 1;
border-radius: 50% 50% 0 0;
background: linear-gradient(
0deg,
rgba(255, 255, 255, 0.08) 0%,
rgba(255, 255, 255, 0) 100%
);
block-size: 30%;
inline-size: 90%;
pointer-events: none;
}
// ===== 暗色模式适配 =====
.v-theme--dark & {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.12) 0%,
rgba(255, 255, 255, 0.05) 40%,
rgba(255, 255, 255, 0.08) 100%
);
border: 0.5px solid rgba(255, 255, 255, 0.15) !important;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
0 2px 12px rgba(0, 0, 0, 0.2),
inset 0 1px 1px rgba(255, 255, 255, 0.15),
inset 0 0 20px rgba(255, 255, 255, 0.03);
&::before {
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.12) 0%,
rgba(255, 255, 255, 0) 100%
);
}
&::after {
background: linear-gradient(
0deg,
rgba(255, 255, 255, 0.03) 0%,
rgba(255, 255, 255, 0) 100%
);
}
}
// 透明主题下的特殊样式
.v-theme--transparent & {
backdrop-filter: blur(var(--transparent-blur-heavy, 16px));
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity-heavy, 0.5));
backdrop-filter: blur(40px) saturate(1.8) brightness(1.05);
-webkit-backdrop-filter: blur(40px) saturate(1.8) brightness(1.05);
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.2) 0%,
rgba(255, 255, 255, 0.08) 40%,
rgba(255, 255, 255, 0.12) 100%
);
}
.v-btn-toggle {
@@ -287,6 +384,7 @@ const showDynamicButton = computed(() => {
.footer-card-content {
position: relative;
z-index: 2;
padding-block: 4px;
padding-inline: 6px;
}
@@ -309,12 +407,46 @@ const showDynamicButton = computed(() => {
justify-content: center;
background-color: transparent;
block-size: 48px;
transition: all 0.35s cubic-bezier(0.25, 1, 0.5, 1);
&.v-btn--active {
background-color: transparent;
box-shadow: none;
}
// 选中态 - 液态玻璃胶囊高亮
&.footer-nav-btn-active {
&::before {
content: '';
position: absolute;
inset: 4px 2px;
z-index: -1;
border: 0.5px solid rgba(255, 255, 255, 0.35);
border-radius: 16px;
background: linear-gradient(
180deg,
rgba(var(--v-theme-primary), 0.18) 0%,
rgba(var(--v-theme-primary), 0.08) 100%
);
box-shadow:
0 2px 8px rgba(var(--v-theme-primary), 0.15),
inset 0 1px 1px rgba(255, 255, 255, 0.3);
transition: all 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.v-theme--dark &::before {
border-color: rgba(255, 255, 255, 0.12);
background: linear-gradient(
180deg,
rgba(var(--v-theme-primary), 0.25) 0%,
rgba(var(--v-theme-primary), 0.1) 100%
);
box-shadow:
0 2px 8px rgba(var(--v-theme-primary), 0.2),
inset 0 1px 1px rgba(255, 255, 255, 0.1);
}
}
.btn-content {
display: flex;
flex-direction: column;

View File

@@ -258,6 +258,7 @@ export default {
serverError: 'Login failed, server error!',
loginFailed: 'Login Failed',
secondaryVerification: 'Secondary Verification',
orDivider: 'OR',
loginWithPasskey: 'Login with Passkey',
loginWithOtp: 'Login with OTP',
orUsePasskey: 'Or use Passkey for verification',

View File

@@ -257,6 +257,7 @@ export default {
serverError: '登录失败,服务器错误!',
loginFailed: '登录失败',
secondaryVerification: '二次验证',
orDivider: '或',
loginWithPasskey: '使用通行密钥登录',
loginWithOtp: '使用验证码登录',
orUsePasskey: '或使用通行密钥进行验证',

View File

@@ -257,6 +257,7 @@ export default {
noPermission: '登錄失敗,您沒有任何功能權限,請聯繫管理員!',
loginFailed: '登錄失敗',
secondaryVerification: '二次驗證',
orDivider: '或',
loginWithPasskey: '使用通行密鑰登錄',
loginWithOtp: '使用驗證碼登錄',
orUsePasskey: '或使用通行密鑰進行驗證',

View File

@@ -610,15 +610,21 @@ onUnmounted(() => {
</VCol>
<VCol cols="12">
<!-- login button -->
<VBtn block type="submit" prepend-icon="mdi-login" :loading="loading">
<VBtn block type="submit" prepend-icon="mdi-login" :loading="loading" size="large">
{{ t('login.login') }}
</VBtn>
<!-- or divider -->
<div class="or-divider my-4">
<span class="or-divider-text">{{ t('login.orDivider') }}</span>
</div>
<!-- passkey login button -->
<VBtn
block
variant="tonal"
variant="outlined"
color="success"
class="mt-3 passkey-btn"
class="passkey-btn"
prepend-icon="material-symbols:passkey"
:loading="passkeyLoading"
@click="loginWithPassKey(false)"
@@ -718,8 +724,29 @@ onUnmounted(() => {
background: rgba(var(--v-theme-surface), 0.7) !important;
}
.or-divider {
position: relative;
display: flex;
align-items: center;
text-align: center;
&::before,
&::after {
content: '';
flex: 1;
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
.or-divider-text {
padding-inline: 12px;
font-size: 0.8125rem;
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
white-space: nowrap;
}
}
.v-theme--light {
.passkey-btn.v-btn--variant-tonal {
.passkey-btn.v-btn--variant-outlined {
color: rgb(86, 170, 0) !important;
}
}