1041 lines
27 KiB
Vue
1041 lines
27 KiB
Vue
<script lang="ts" setup>
|
|
import { useSettingsStore } from '@/store/modules/settings'
|
|
import { useUserStore } from '@/store/modules/user'
|
|
import { useRoutesStore } from '@/store/modules/routes'
|
|
import { toLoginRoute } from '@/utils/routes'
|
|
import { Save, RotateCcw, Minimize } from 'lucide-vue-next'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const settingsStore = useSettingsStore()
|
|
const userStore = useUserStore()
|
|
const routesStore = useRoutesStore()
|
|
|
|
const { logo, title } = storeToRefs(settingsStore)
|
|
const { avatar, username } = storeToRefs(userStore)
|
|
const { getRoutes: routes } = storeToRefs(routesStore)
|
|
const { logout } = userStore
|
|
|
|
const $sub: any = inject('$sub')
|
|
const $unsub: any = inject('$unsub')
|
|
|
|
const currentDate = ref('')
|
|
const currentTime = ref('')
|
|
const isCollapsed = ref(false)
|
|
const scrollPosition = ref(0)
|
|
const showMenuDropdown = ref(false)
|
|
const isDragEnabled = ref(true)
|
|
const isHomePageActive = ref(false)
|
|
|
|
// 检查是否在 homePage 路由
|
|
const isHomePage = computed(() => {
|
|
// 使用响应式状态,实时更新
|
|
return isHomePageActive.value
|
|
})
|
|
|
|
// 检查是否为访客模式(通过路由路径判断)
|
|
const isGuestMode = computed(() => {
|
|
const path = route.path.toLowerCase()
|
|
return path.includes('/guest/') || path.includes('guest-homepage') || path.includes('guest/homepage')
|
|
})
|
|
|
|
// 监听 homePage 的挂载和卸载
|
|
const handleHomePageMounted = () => {
|
|
isHomePageActive.value = true
|
|
console.log('HomePage 已挂载,显示功能按钮')
|
|
}
|
|
|
|
const handleHomePageUnmounted = () => {
|
|
isHomePageActive.value = false
|
|
console.log('HomePage 已卸载,隐藏功能按钮')
|
|
}
|
|
|
|
// 监听路由变化,实时更新按钮显示状态
|
|
watch(() => route.path, (newPath) => {
|
|
console.log('路由变化:', newPath)
|
|
// 如果离开了 homePage 路由,立即隐藏按钮
|
|
const path = newPath.toLowerCase()
|
|
if (!path.includes('homepage') && !path.includes('home-page')) {
|
|
isHomePageActive.value = false
|
|
// 同时清理全局标识
|
|
if ((window as any).__isHomePage__) {
|
|
(window as any).__isHomePage__ = false
|
|
}
|
|
}
|
|
}, { immediate: true })
|
|
|
|
const updateDateTime = () => {
|
|
const now = new Date()
|
|
const year = now.getFullYear()
|
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
const day = String(now.getDate()).padStart(2, '0')
|
|
const hours = String(now.getHours()).padStart(2, '0')
|
|
const minutes = String(now.getMinutes()).padStart(2, '0')
|
|
const seconds = String(now.getSeconds()).padStart(2, '0')
|
|
|
|
currentDate.value = `${year}-${month}-${day}`
|
|
currentTime.value = `${hours}:${minutes}:${seconds}`
|
|
}
|
|
|
|
// 处理滚动事件
|
|
const handleScroll = () => {
|
|
const currentScroll = window.pageYOffset || document.documentElement.scrollTop
|
|
|
|
// 向下滚动超过50px时收起
|
|
if (currentScroll > 50 && !isCollapsed.value) {
|
|
isCollapsed.value = true
|
|
}
|
|
// 回到顶部时展开
|
|
else if (currentScroll <= 10 && isCollapsed.value) {
|
|
isCollapsed.value = false
|
|
}
|
|
|
|
scrollPosition.value = currentScroll
|
|
}
|
|
|
|
// 切换展开/收起状态
|
|
const toggleCollapse = () => {
|
|
isCollapsed.value = !isCollapsed.value
|
|
console.log('切换 Header 状态:', isCollapsed.value ? '已收起' : '已展开')
|
|
}
|
|
|
|
// 定义顶部菜单项
|
|
const topMenuItems = computed(() => {
|
|
const rootRoute = routes.value[0]
|
|
if (!rootRoute || !rootRoute.children) return []
|
|
|
|
const children = rootRoute.children
|
|
|
|
// 查找特定路由
|
|
const intelQa = children.find(r => r.name === 'IntelQa')
|
|
const homePage = children.find(r => r.name === 'HomePage')
|
|
const datasets = children.find(r => r.name === 'DataSets')
|
|
const intelRetrieval = children.find(r => r.name === 'IntelRetrieval')
|
|
const commQA = children.find(r => r.name === 'CommQA')
|
|
const historicalRecords = children.find(r => r.name === 'historicalRecords')
|
|
const userManage = children.find(r => r.name === 'UserManage')
|
|
|
|
const menuItems = []
|
|
|
|
// 首页
|
|
if (intelQa) {
|
|
menuItems.push({
|
|
name: '首页',
|
|
icon: 'home-4-line',
|
|
path: homePage.path
|
|
})
|
|
}
|
|
|
|
// 知识库
|
|
if (datasets) {
|
|
menuItems.push({
|
|
name: '知识库',
|
|
icon: 'database-2-line',
|
|
path: datasets.path
|
|
})
|
|
}
|
|
|
|
// 智能检索
|
|
if (intelRetrieval) {
|
|
menuItems.push({
|
|
name: '智能检索',
|
|
icon: 'search-line',
|
|
path: intelRetrieval.path
|
|
})
|
|
}
|
|
|
|
// 通用问答
|
|
if (commQA) {
|
|
menuItems.push({
|
|
name: '通用问答',
|
|
icon: 'question-answer-line',
|
|
path: commQA.path
|
|
})
|
|
}
|
|
|
|
// 历史问答
|
|
if (historicalRecords) {
|
|
menuItems.push({
|
|
name: '历史问答',
|
|
icon: 'history-line',
|
|
path: historicalRecords.path
|
|
})
|
|
}
|
|
|
|
return menuItems
|
|
})
|
|
|
|
// 系统配置子菜单
|
|
const systemMenuItems = computed(() => {
|
|
const rootRoute = routes.value[0]
|
|
if (!rootRoute || !rootRoute.children) return []
|
|
|
|
const userManage = rootRoute.children.find(r => r.name === 'UserManage')
|
|
if (!userManage || !userManage.children) return []
|
|
|
|
return userManage.children
|
|
.filter(child => !child.meta?.hidden)
|
|
.map(child => ({
|
|
name: child.meta?.title || child.name,
|
|
icon: child.meta?.icon || '',
|
|
path: child.path
|
|
}))
|
|
})
|
|
|
|
let timeInterval: any = null
|
|
|
|
// 监听消息发送事件,自动收起顶部栏
|
|
const handleMessageSent = () => {
|
|
if (!isCollapsed.value) {
|
|
isCollapsed.value = true
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
updateDateTime()
|
|
timeInterval = setInterval(updateDateTime, 1000)
|
|
|
|
// 添加滚动监听
|
|
window.addEventListener('scroll', handleScroll, { passive: true })
|
|
|
|
// 订阅消息发送事件
|
|
if ($sub) {
|
|
$sub('message-sent', handleMessageSent)
|
|
$sub('chat-message-sent', handleMessageSent)
|
|
}
|
|
|
|
// 监听 homePage 的挂载和卸载事件
|
|
window.addEventListener('homepage-mounted', handleHomePageMounted)
|
|
window.addEventListener('homepage-unmounted', handleHomePageUnmounted)
|
|
|
|
// 监听来自homePage的拖拽状态更新
|
|
window.addEventListener('update-drag-mode', ((e: CustomEvent) => {
|
|
isDragEnabled.value = e.detail.enabled
|
|
}) as EventListener)
|
|
|
|
// 初始化时检查是否已经在 homePage
|
|
if ((window as any).__isHomePage__) {
|
|
isHomePageActive.value = true
|
|
}
|
|
|
|
// 监听路由变化,在特定页面发送消息后自动收起
|
|
watch(() => route.path, () => {
|
|
// 在智能问答和通用问答页面,监听消息发送
|
|
if (route.path.includes('intelQa') || route.path.includes('commQA') || route.path.includes('IntelRetrieval')) {
|
|
// 延迟一点收起,等待内容渲染
|
|
setTimeout(() => {
|
|
const hasContent = document.querySelector('.chat-messages, .message-list, .message-container')
|
|
if (hasContent && hasContent.children.length > 0) {
|
|
isCollapsed.value = true
|
|
}
|
|
}, 300)
|
|
}
|
|
})
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (timeInterval) {
|
|
clearInterval(timeInterval)
|
|
}
|
|
window.removeEventListener('scroll', handleScroll)
|
|
window.removeEventListener('homepage-mounted', handleHomePageMounted)
|
|
window.removeEventListener('homepage-unmounted', handleHomePageUnmounted)
|
|
window.removeEventListener('update-drag-mode', ((e: CustomEvent) => {
|
|
isDragEnabled.value = e.detail.enabled
|
|
}) as EventListener)
|
|
|
|
// 取消订阅
|
|
if ($unsub) {
|
|
$unsub('message-sent')
|
|
$unsub('chat-message-sent')
|
|
}
|
|
})
|
|
|
|
const handleLogout = async () => {
|
|
await logout()
|
|
await router.push(toLoginRoute(route.fullPath))
|
|
}
|
|
|
|
const navigateTo = (path: string) => {
|
|
router.push(path)
|
|
}
|
|
|
|
// homePage 专用功能函数
|
|
const toggleDragMode = () => {
|
|
isDragEnabled.value = !isDragEnabled.value
|
|
// 触发自定义事件通知 homePage
|
|
window.dispatchEvent(new CustomEvent('toggle-drag-mode', { detail: { enabled: isDragEnabled.value } }))
|
|
}
|
|
|
|
const saveCurrentLayout = () => {
|
|
window.dispatchEvent(new CustomEvent('save-layout'))
|
|
}
|
|
|
|
const restoreLayout = () => {
|
|
window.dispatchEvent(new CustomEvent('restore-layout'))
|
|
}
|
|
|
|
const toggleHeaderCollapse = () => {
|
|
// 调用统一的收起/展开函数
|
|
toggleCollapse()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="vab-top-bar-wrapper">
|
|
<div class="vab-top-bar" :class="{ 'is-collapsed': isCollapsed }">
|
|
<div class="top-bar-border-top"></div>
|
|
<div class="top-bar-content">
|
|
<!-- 左侧Logo区域 -->
|
|
<div class="top-bar-left">
|
|
<div class="logo-section">
|
|
<img src="@/assets/BRI.png" alt="BRI Logo" class="logo-image" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 中部标题区域 -->
|
|
<div class="top-bar-center">
|
|
<div class="title-section">
|
|
<h1 class="main-title">{{ title || '应急指挥智能体' }}</h1>
|
|
<div class="datetime-section">
|
|
<span class="date-display">{{ currentDate }}</span>
|
|
<span class="time-separator">|</span>
|
|
<span class="time-display">{{ currentTime }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧操作按钮区域 -->
|
|
<div class="top-bar-right">
|
|
<div class="action-buttons">
|
|
<!-- 调试信息 - 开发环境显示 -->
|
|
<!-- <span v-if="true" style="color: #00D4FF; font-size: 12px; margin-right: 10px; padding: 4px 8px; background: rgba(0,0,0,0.3); border-radius: 4px;">
|
|
路由: {{ route.path }} | 显示按钮: {{ isHomePage }}
|
|
</span> -->
|
|
|
|
<!-- homePage 专用控制按钮 -->
|
|
<template v-if="isHomePage">
|
|
<!-- 布局控制按钮 -->
|
|
<div class="layout-controls">
|
|
<button
|
|
class="layout-btn hover-glow"
|
|
@click="saveCurrentLayout"
|
|
title="保存当前布局"
|
|
>
|
|
<Save :size="18" />
|
|
</button>
|
|
<button
|
|
class="layout-btn hover-glow"
|
|
@click="restoreLayout"
|
|
title="还原保存的布局"
|
|
>
|
|
<RotateCcw :size="18" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 拖拽控制开关 -->
|
|
<div class="drag-toggle-container">
|
|
<span class="drag-toggle-label">拖拽</span>
|
|
<button
|
|
class="drag-toggle-btn"
|
|
:class="{ 'active': isDragEnabled }"
|
|
@click="toggleDragMode"
|
|
:title="isDragEnabled ? '关闭拖拽功能' : '开启拖拽功能'"
|
|
>
|
|
<div class="toggle-slider"></div>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 缩略按钮 -->
|
|
<button
|
|
class="collapse-btn hover-glow"
|
|
@click="toggleHeaderCollapse"
|
|
title="缩略到左上角"
|
|
>
|
|
<Minimize :size="20" />
|
|
</button>
|
|
</template>
|
|
|
|
<!-- 导航菜单 - 非访客模式显示 -->
|
|
<el-dropdown v-if="!isGuestMode" trigger="hover" @command="navigateTo">
|
|
<div class="action-btn menu-btn">
|
|
<vab-icon icon="menu-line" />
|
|
<span>菜单</span>
|
|
</div>
|
|
<template #dropdown>
|
|
<el-dropdown-menu class="tech-dropdown">
|
|
<el-dropdown-item
|
|
v-for="item in topMenuItems"
|
|
:key="item.path"
|
|
:command="item.path"
|
|
>
|
|
<vab-icon :icon="item.icon" />
|
|
<span>{{ item.name }}</span>
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
|
|
<!-- 系统配置下拉菜单 - 非访客模式显示 -->
|
|
<el-dropdown v-if="!isGuestMode && systemMenuItems.length > 0" trigger="hover" @command="navigateTo">
|
|
<div class="action-btn settings-btn">
|
|
<vab-icon icon="settings-3-line" />
|
|
<span>系统</span>
|
|
</div>
|
|
<template #dropdown>
|
|
<el-dropdown-menu class="tech-dropdown">
|
|
<el-dropdown-item
|
|
v-for="subItem in systemMenuItems"
|
|
:key="subItem.path"
|
|
:command="subItem.path"
|
|
>
|
|
<vab-icon v-if="subItem.icon" :icon="subItem.icon" />
|
|
<span>{{ subItem.name }}</span>
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
|
|
<!-- 用户信息区域 - 非访客模式显示 -->
|
|
<div v-if="!isGuestMode" class="user-section">
|
|
<el-avatar class="user-avatar" :src="avatar" />
|
|
<span class="user-name">{{ username }}</span>
|
|
<el-icon class="logout-icon" @click="handleLogout" title="退出登录">
|
|
<vab-icon icon="logout-circle-r-line" />
|
|
</el-icon>
|
|
</div>
|
|
|
|
<!-- 访客模式提示 -->
|
|
<div v-if="isGuestMode" class="guest-indicator">
|
|
<div class="guest-badge">
|
|
<vab-icon icon="user-line" />
|
|
<span>指挥中心</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="top-bar-border-bottom"></div>
|
|
</div>
|
|
|
|
<!-- 收起后的悬浮按钮 - 移到外部容器 -->
|
|
<transition name="float-fade">
|
|
<div v-if="isCollapsed" class="floating-toggle-btn" @click="toggleCollapse">
|
|
<img src="@/assets/BRI.png" alt="BRI Logo" class="floating-logo" />
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
// Wrapper - 不占据文档流,避免挤压内容
|
|
.vab-top-bar-wrapper {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 1003;
|
|
pointer-events: none; // 允许点击穿透到下方内容
|
|
|
|
// 子元素恢复 pointer-events
|
|
> * {
|
|
pointer-events: auto;
|
|
}
|
|
}
|
|
|
|
.vab-top-bar {
|
|
width: 100%;
|
|
height: 70px;
|
|
background: linear-gradient(180deg, rgba(10, 37, 64, 0.95) 0%, rgba(13, 47, 77, 0.95) 100%);
|
|
backdrop-filter: blur(10px);
|
|
border-bottom: 1px solid rgba(0, 212, 255, 0.3);
|
|
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.1);
|
|
position: relative;
|
|
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1),
|
|
opacity 0.4s ease,
|
|
box-shadow 0.3s ease;
|
|
animation: slideDownHeader 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
&.is-collapsed {
|
|
transform: translateY(-100%);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.top-bar-border-top {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
background: linear-gradient(90deg,
|
|
transparent 0%,
|
|
#00d4ff 20%,
|
|
#0a8bff 50%,
|
|
#00d4ff 80%,
|
|
transparent 100%
|
|
);
|
|
animation: borderFlow 3s linear infinite;
|
|
}
|
|
|
|
.top-bar-border-bottom {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 1px;
|
|
background: linear-gradient(90deg,
|
|
transparent 0%,
|
|
rgba(0, 212, 255, 0.5) 50%,
|
|
transparent 100%
|
|
);
|
|
}
|
|
|
|
.top-bar-content {
|
|
height: 100%;
|
|
display: grid;
|
|
grid-template-columns: 1fr auto 1fr;
|
|
align-items: center;
|
|
gap: 20px;
|
|
padding: 0 32px;
|
|
max-width: 1920px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.top-bar-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
|
|
.logo-section {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
transition: all 0.3s ease;
|
|
|
|
.logo-image {
|
|
height: 40px;
|
|
width: auto;
|
|
object-fit: contain;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
transform: scale(1.1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.top-bar-center {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
.title-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 6px;
|
|
|
|
.main-title {
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
background: linear-gradient(135deg, #00D4FF 0%, #1890FF 100%);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
.datetime-section {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 14px;
|
|
color: #94A3B8;
|
|
|
|
.date-display {
|
|
font-weight: 500;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.time-separator {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.time-display {
|
|
font-family: 'Courier New', monospace;
|
|
font-weight: 600;
|
|
color: #00D4FF;
|
|
letter-spacing: 1px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.top-bar-right {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
|
|
.action-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px 16px;
|
|
color: #a8d8ea;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
border-radius: 6px;
|
|
border: 1px solid transparent;
|
|
position: relative;
|
|
overflow: hidden;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: -100%;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.2), transparent);
|
|
transition: left 0.5s ease;
|
|
}
|
|
|
|
&:hover {
|
|
color: #00d4ff;
|
|
background: rgba(0, 212, 255, 0.1);
|
|
border-color: rgba(0, 212, 255, 0.3);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.2);
|
|
|
|
&::before {
|
|
left: 100%;
|
|
}
|
|
|
|
:deep(.vab-icon) {
|
|
transform: scale(1.1);
|
|
filter: drop-shadow(0 0 6px rgba(0, 212, 255, 0.6));
|
|
}
|
|
}
|
|
|
|
:deep(.vab-icon) {
|
|
font-size: 18px;
|
|
transition: all 0.3s ease;
|
|
|
|
&.dropdown-arrow {
|
|
font-size: 14px;
|
|
margin-left: 2px;
|
|
}
|
|
}
|
|
|
|
&.settings-btn {
|
|
padding: 8px 12px;
|
|
|
|
:deep(.vab-icon) {
|
|
font-size: 20px;
|
|
}
|
|
|
|
&:hover {
|
|
:deep(.vab-icon) {
|
|
animation: rotate 0.6s ease;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.user-section {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-left: 15px;
|
|
padding-left: 15px;
|
|
border-left: 1px solid rgba(0, 212, 255, 0.3);
|
|
|
|
.user-avatar {
|
|
width: 36px;
|
|
height: 36px;
|
|
border: 2px solid rgba(0, 212, 255, 0.5);
|
|
box-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
|
|
}
|
|
|
|
.user-name {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
max-width: 100px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.logout-icon {
|
|
font-size: 20px;
|
|
color: #a8d8ea;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
color: #ff6b6b;
|
|
transform: rotate(180deg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes borderFlow {
|
|
0% {
|
|
background-position: 0% 50%;
|
|
}
|
|
100% {
|
|
background-position: 200% 50%;
|
|
}
|
|
}
|
|
|
|
// 顶部栏初始下滑动画
|
|
@keyframes slideDownHeader {
|
|
0% {
|
|
transform: translateY(-100%);
|
|
opacity: 0;
|
|
}
|
|
60% {
|
|
transform: translateY(5px);
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
// 悬浮切换按钮 - 参考 CLClient HomePage 设计
|
|
.floating-toggle-btn {
|
|
position: fixed;
|
|
top: 12px;
|
|
left: 12px;
|
|
z-index: 1004;
|
|
background: linear-gradient(135deg, rgba(0, 212, 255, 0.15) 0%, rgba(24, 144, 255, 0.15) 100%);
|
|
border: 1px solid rgba(0, 212, 255, 0.3);
|
|
border-radius: 8px;
|
|
padding: 6px;
|
|
cursor: pointer;
|
|
box-shadow: 0 8px 32px rgba(0, 162, 255, 0.2);
|
|
backdrop-filter: blur(15px);
|
|
transition: all 0.3s ease;
|
|
display: inline-block;
|
|
animation: slideInFromLeft 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
&:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 12px 40px rgba(0, 162, 255, 0.3);
|
|
border-color: rgba(0, 212, 255, 0.5);
|
|
}
|
|
|
|
&:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.floating-logo {
|
|
height: 32px;
|
|
width: auto;
|
|
display: block;
|
|
filter: drop-shadow(0 0 8px rgba(0, 212, 255, 0.6));
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
transform: scale(1.1);
|
|
filter: drop-shadow(0 0 12px rgba(0, 212, 255, 0.8));
|
|
}
|
|
}
|
|
}
|
|
|
|
// 悬浮按钮淡入淡出动画
|
|
.float-fade-enter-active {
|
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
animation: slideInFromLeft 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.float-fade-leave-active {
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.float-fade-enter-from {
|
|
opacity: 0;
|
|
transform: translateX(-100px) scale(0.8);
|
|
}
|
|
|
|
.float-fade-leave-to {
|
|
opacity: 0;
|
|
transform: translateX(-100px) scale(0.8);
|
|
}
|
|
|
|
// 从左侧滑入动画
|
|
@keyframes slideInFromLeft {
|
|
0% {
|
|
opacity: 0;
|
|
transform: translateX(-100px) scale(0.8);
|
|
}
|
|
60% {
|
|
opacity: 1;
|
|
transform: translateX(5px) scale(1.05);
|
|
}
|
|
100% {
|
|
opacity: 1;
|
|
transform: translateX(0) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
transform: scale(1.1);
|
|
}
|
|
}
|
|
|
|
@keyframes rotate {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
// homePage 专用按钮样式
|
|
.layout-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.layout-btn {
|
|
background: rgba(0, 162, 255, 0.1);
|
|
border: 1px solid rgba(0, 162, 255, 0.3);
|
|
border-radius: 6px;
|
|
padding: 6px;
|
|
color: #00A2FF;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
&:hover {
|
|
background: rgba(0, 162, 255, 0.2);
|
|
border-color: rgba(0, 162, 255, 0.5);
|
|
transform: scale(1.05);
|
|
box-shadow: 0 4px 12px rgba(0, 162, 255, 0.2);
|
|
}
|
|
|
|
&:active {
|
|
transform: scale(0.95);
|
|
}
|
|
}
|
|
|
|
.drag-toggle-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.drag-toggle-label {
|
|
font-size: 14px;
|
|
color: #94A3B8;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.drag-toggle-btn {
|
|
position: relative;
|
|
width: 44px;
|
|
height: 24px;
|
|
background: rgba(55, 65, 81, 0.6);
|
|
border: 1px solid rgba(0, 162, 255, 0.3);
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
outline: none;
|
|
|
|
&:hover {
|
|
border-color: rgba(0, 162, 255, 0.5);
|
|
box-shadow: 0 2px 8px rgba(0, 162, 255, 0.2);
|
|
}
|
|
|
|
&.active {
|
|
background: rgba(0, 162, 255, 0.2);
|
|
border-color: rgba(0, 162, 255, 0.6);
|
|
|
|
.toggle-slider {
|
|
transform: translateX(20px);
|
|
background: #00A2FF;
|
|
box-shadow: 0 2px 8px rgba(0, 162, 255, 0.4);
|
|
}
|
|
}
|
|
}
|
|
|
|
.toggle-slider {
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 2px;
|
|
width: 18px;
|
|
height: 18px;
|
|
background: #64748B;
|
|
border-radius: 50%;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.collapse-btn {
|
|
background: rgba(0, 162, 255, 0.1);
|
|
border: 1px solid rgba(0, 162, 255, 0.3);
|
|
border-radius: 8px;
|
|
padding: 8px;
|
|
color: #00A2FF;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 8px;
|
|
|
|
&:hover {
|
|
background: rgba(0, 162, 255, 0.2);
|
|
border-color: rgba(0, 162, 255, 0.5);
|
|
transform: scale(1.05);
|
|
box-shadow: 0 4px 12px rgba(0, 162, 255, 0.2);
|
|
}
|
|
|
|
&:active {
|
|
transform: scale(0.95);
|
|
}
|
|
}
|
|
|
|
// 访客模式指示器样式
|
|
.guest-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-left: 15px;
|
|
padding-left: 15px;
|
|
border-left: 1px solid rgba(0, 212, 255, 0.3);
|
|
}
|
|
|
|
.guest-badge {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 6px 12px;
|
|
background: rgba(250, 173, 20, 0.1);
|
|
border: 1px solid rgba(250, 173, 20, 0.3);
|
|
border-radius: 16px;
|
|
color: #FAAD14;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
|
|
.vab-icon {
|
|
font-size: 16px;
|
|
}
|
|
}
|
|
|
|
@media only screen and (max-width: 1200px) {
|
|
.top-bar-content {
|
|
padding: 0 20px;
|
|
}
|
|
|
|
.top-bar-center {
|
|
.title-section {
|
|
.main-title {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.datetime-section {
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.action-btn span {
|
|
display: none;
|
|
}
|
|
|
|
.user-section {
|
|
.user-name {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.guest-badge {
|
|
span {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
@media only screen and (max-width: 768px) {
|
|
.top-bar-content {
|
|
grid-template-columns: auto 1fr auto;
|
|
gap: 10px;
|
|
padding: 0 16px;
|
|
}
|
|
|
|
.top-bar-center {
|
|
.title-section {
|
|
.main-title {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.datetime-section {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<style lang="scss">
|
|
// 全局样式 - 下拉菜单主题
|
|
.tech-dropdown {
|
|
background: linear-gradient(135deg, rgba(15, 23, 42, 0.98) 0%, rgba(30, 41, 59, 0.98) 100%) !important;
|
|
border: 1px solid rgba(0, 212, 255, 0.5) !important;
|
|
border-radius: 12px !important;
|
|
backdrop-filter: blur(20px) !important;
|
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(0, 212, 255, 0.2) !important;
|
|
padding: 8px !important;
|
|
|
|
.el-dropdown-menu__item {
|
|
color: #ffffff !important;
|
|
padding: 10px 16px !important;
|
|
border-radius: 6px !important;
|
|
transition: all 0.2s ease !important;
|
|
|
|
&:hover {
|
|
background: rgba(0, 212, 255, 0.1) !important;
|
|
color: #00D4FF !important;
|
|
}
|
|
|
|
.vab-icon {
|
|
margin-right: 8px;
|
|
color: #00D4FF;
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|