feat(site): 对齐参考站点UI与交互调整
构建并部署 BYW VoiceHub / Preflight 语法与构建检查 (push) Successful in 2m8s
构建并部署 BYW VoiceHub / 构建并推送容器镜像 (push) Successful in 9m11s
构建并部署 BYW VoiceHub / 部署到生产服务器 (push) Successful in 7s

- 页脚:响应耗时文字统一为「响应耗时 XXXms」,版本链接改为 GitHub 官方仓库,默认 ICP 备案号设置为粤ICP备2025450093号
- 顶部栏:恢复主题切换按钮与语言按钮(语言暂为占位提示),放置于用户区左侧
- 主题:修复首屏内联脚本默认主题白名单校验,避免 localStorage 中残留的 light/dark/System 等非法值生效,首屏默认改为 ClassicLight(仅允许 ClassicLight/ModernLight)
- 登录页:在密码输入框下方恢复「忘记密码?」入口,新增配套的找回密码引导页 /forgot-password
- 默认配置:system-settings 兜底记录与 SYSTEM_SETTINGS_DEFAULTS 新增 icpNumber 默认值
This commit is contained in:
2026-09-12 11:56:30 +08:00
parent 0c71328efd
commit a82ff6747e
7 changed files with 415 additions and 15 deletions
+269
View File
@@ -0,0 +1,269 @@
<template>
<div class="auth-layout">
<div class="auth-container">
<div class="form-section">
<div class="form-header">
<div class="logo-row">
<img
:src="brandLogoSrc"
alt="Brand Logo"
class="brand-logo-center"
width="96"
height="96"
decoding="async"
fetchpriority="high"
>
<div v-if="schoolLogoHomeUrl && schoolLogoHomeUrl.trim()" class="logo-divider" />
<img
v-if="schoolLogoHomeUrl && schoolLogoHomeUrl.trim()"
:src="schoolLogoHomeUrl"
alt="学校Logo"
class="school-logo"
width="160"
height="160"
decoding="async"
fetchpriority="high"
>
</div>
<h1 class="form-title">{{ siteTitle ? siteTitle + ' | VoiceHub' : 'VoiceHub' }}</h1>
<div class="header-divider" />
</div>
<div class="forgot-password-form">
<div class="page-intro">
<h2 class="page-heading">找回密码</h2>
<p class="page-subtitle">请联系管理员重置您的账号密码</p>
</div>
<div class="contact-card">
<div class="contact-card-icon">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
<div class="contact-card-body">
<h3>联系管理员</h3>
<p>为保障账号安全,系统暂未开放自助密码找回。请通过以下方式联系管理员进行密码重置:</p>
<ul class="contact-tips">
<li>站内联系:登录有管理员权限的账号</li>
<li>线下联系:前往广播站办公室或指导老师</li>
<li>邮件联系:如有公开的管理员邮箱</li>
</ul>
</div>
</div>
<div class="form-actions">
<NuxtLink to="/login" class="back-to-login-btn">
返回登录
</NuxtLink>
</div>
</div>
</div>
</div>
<SiteFooter />
</div>
</template>
<script setup>
import { onMounted, computed } from 'vue'
import logo from '~~/public/images/logo.png'
const { siteTitle, initSiteConfig, schoolLogoHomeUrl } = useSiteConfig()
const brandLogoSrc = computed(() => {
return logo
})
onMounted(async () => {
await initSiteConfig()
if (typeof document !== 'undefined' && siteTitle.value) {
document.title = `找回密码 | ${siteTitle.value}`
}
})
</script>
<style scoped>
.auth-layout {
min-height: 100vh;
background: var(--bg-primary);
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding: 20px;
}
.auth-container {
width: 100%;
max-width: 520px;
background: var(--bg-secondary);
border-radius: var(--radius-2xl);
border: 1px solid var(--border-primary);
box-shadow: var(--shadow-lg);
overflow: hidden;
margin: auto 0;
margin-bottom: 40px;
}
.form-section {
padding: 40px 32px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--bg-secondary);
}
.form-header {
text-align: center;
margin-bottom: 24px;
width: 100%;
}
.logo-row {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
margin-bottom: 12px;
}
.logo-divider {
width: 1px;
height: 96px;
background: var(--border-secondary);
}
.brand-logo-center {
width: 96px;
height: 96px;
object-fit: contain;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.15));
}
.school-logo {
width: 160px;
height: 160px;
object-fit: contain;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.15));
}
.form-title {
font-size: 24px;
font-weight: var(--font-bold);
margin: 0;
color: var(--text-primary);
}
.header-divider {
height: 1px;
background: var(--border-secondary);
margin: 14px auto 0;
width: 100%;
}
.forgot-password-form {
width: 100%;
max-width: 400px;
}
.page-intro {
text-align: center;
margin-bottom: 28px;
}
.page-heading {
font-size: 28px;
font-weight: var(--font-bold);
color: var(--text-primary);
margin: 0 0 8px 0;
}
.page-subtitle {
font-size: 14px;
color: var(--text-tertiary);
margin: 0;
}
.contact-card {
display: flex;
gap: 16px;
padding: 24px;
background: var(--bg-tertiary);
border: 1px solid var(--border-secondary);
border-radius: var(--radius-lg);
margin-bottom: 28px;
}
.contact-card-icon {
width: 40px;
height: 40px;
border-radius: 12px;
background: rgba(59, 130, 246, 0.12);
color: var(--primary);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.contact-card-icon svg {
width: 20px;
height: 20px;
}
.contact-card-body h3 {
font-size: 16px;
font-weight: var(--font-semibold);
color: var(--text-primary);
margin: 0 0 8px 0;
}
.contact-card-body p {
font-size: 14px;
color: var(--text-secondary);
margin: 0 0 12px 0;
line-height: 1.6;
}
.contact-tips {
margin: 0;
padding-left: 18px;
color: var(--text-secondary);
font-size: 13px;
line-height: 1.9;
}
.form-actions {
display: flex;
flex-direction: column;
gap: 12px;
}
.back-to-login-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 14px;
background: var(--btn-primary-bg);
color: var(--btn-primary-text);
border: 1px solid var(--btn-primary-border);
border-radius: var(--radius-lg);
font-size: 16px;
font-weight: var(--font-semibold);
text-decoration: none;
transition: all 0.2s ease;
}
.back-to-login-btn:hover {
background: var(--btn-primary-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
@media (max-width: 768px) {
.auth-layout { padding: 10px; }
.auth-container { border-radius: 16px; }
.form-section { padding: 30px 20px; }
.contact-card { padding: 18px; gap: 12px; }
}
</style>
+85
View File
@@ -33,6 +33,30 @@
<!-- 用户信息区域 -->
<div class="user-section">
<ClientOnly>
<!-- 主题切换按钮 -->
<button
class="utility-btn theme-toggle-btn"
type="button"
aria-label="主题"
title="切换主题"
@click="handleToggleTheme"
>
<Icon :name="isDark ? 'sun' : 'moon'" :size="18" />
<span class="utility-btn-label">主题</span>
</button>
<!-- 语言切换按钮(占位按钮,当前单语种) -->
<button
class="utility-btn lang-toggle-btn"
type="button"
aria-label="语言"
title="语言"
@click="handleLanguageClick"
>
<Icon name="globe" :size="18" />
<span class="utility-btn-label">语言</span>
</button>
<div v-if="isClientAuthenticated" class="user-info">
<div class="user-details-desktop">
<span class="user-name">{{ user?.name || '用户' }}</span>
@@ -674,12 +698,24 @@ import ConfirmDialog from '~/components/UI/ConfirmDialog.vue'
import { useNotifications } from '~/composables/useNotifications'
import { useSiteConfig } from '~/composables/useSiteConfig'
import { useTheme } from '~/composables/useTheme'
import CustomSelect from '~/components/UI/Common/CustomSelect.vue'
// 获取运行时配置
const config = useRuntimeConfig()
const router = useRouter()
// 主题
const { isDark, toggleTheme } = useTheme()
const handleToggleTheme = () => {
toggleTheme()
}
const handleLanguageClick = () => {
if (typeof window !== 'undefined' && window.$showNotification) {
window.$showNotification('当前版本暂不支持多语言切换', 'info')
}
}
// 站点配置
const {
siteTitle,
@@ -1709,6 +1745,55 @@ if (
.user-section {
position: relative;
z-index: 100;
display: flex;
align-items: center;
gap: 10px;
}
/* 通用工具按钮(主题/语言) */
.utility-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 12px;
background: var(--utility-btn-bg, rgba(255, 255, 255, 0.06));
color: var(--utility-btn-color, rgba(255, 255, 255, 0.78));
border: 1px solid var(--utility-btn-border, rgba(255, 255, 255, 0.12));
border-radius: 10px;
font-size: 13px;
font-weight: 500;
line-height: 1;
cursor: pointer;
transition:
background 0.2s ease,
color 0.2s ease,
border-color 0.2s ease,
transform 0.15s ease;
backdrop-filter: blur(6px);
}
.utility-btn:hover {
background: var(--utility-btn-hover-bg, rgba(255, 255, 255, 0.12));
color: var(--utility-btn-hover-color, #ffffff);
border-color: var(--utility-btn-hover-border, rgba(255, 255, 255, 0.22));
}
.utility-btn:active {
transform: scale(0.97);
}
.utility-btn-label {
white-space: nowrap;
}
@media (max-width: 768px) {
.utility-btn-label {
display: none;
}
.utility-btn {
padding: 7px 8px;
border-radius: 9px;
}
}
.user-info {