| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- <template>
- <div class="big-screen-home">
- <!-- 顶部栏 -->
- <header class="header-bar">
- <div class="header-left">
- <span class="time-text">{{ currentTime }}</span>
- </div>
- <h1 class="system-title">沅陵县城市地下管网管控平台</h1>
- <div class="header-right">
- <span class="welcome-text">欢迎,<span class="username">浏览账号</span></span>
- <button class="header-btn" @click="enterBackend">进入后台</button>
- <button class="header-btn" @click="logout">退出系统</button>
- </div>
- </header>
- <!-- 中央核心区域 -->
- <div class="core-area">
- <div class="cloud-container">
- <div class="cloud-bg"></div>
- </div>
- </div>
- <!-- 功能模块入口(3D椭圆旋转环) -->
- <div class="ring-3d-container">
- <div
- class="module-item-3d"
- v-for="(item, index) in modules"
- :key="index"
- :style="moduleStyles[index]"
- @click="handleModuleClick(item)"
- @mouseenter="pauseRotation"
- @mouseleave="resumeRotation"
- >
- <div class="icon-wrapper">
- <div class="icon-circle">
- <i :class="item.iconClass" class="module-icon"></i>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, computed, onMounted, onUnmounted, watch } from 'vue'
- import { useRouter } from 'vue-router'
- import useUserStore from '@/store/modules/user'
- import usePermissionStore from '@/store/modules/permission'
- import { ElMessageBox, ElMessage } from 'element-plus'
- const router = useRouter()
- const userStore = useUserStore()
- const permissionStore = usePermissionStore()
- const currentTime = ref('')
- let timer = null
- let rotationInterval = null
- let animationFrame = null
- let isPaused = ref(false)
- // 椭圆轨迹参数
- const radiusX = 500
- const radiusY = 100
- const modulesCount = ref(7)
- // 连续旋转核心变量
- let currentAngle = ref(0)
- const ANIMATION_DURATION = 500
- const ROTATE_INTERVAL = 5000
- const STEP_ANGLE = (2 * Math.PI) / 7
- // 动态模块(从路由获取)
- const modules = ref([])
- // 【优化1】缓存模块样式,避免频繁计算
- const moduleStyles = reactive<Record<number, any>>({})
- // 【优化2】预计算三角函数值缓存
- const sinCache = new Map<number, number>()
- const cosCache = new Map<number, number>()
- const getCachedSin = (angle: number) => {
- const key = Math.round(angle * 1000)
- if (!sinCache.has(key)) {
- sinCache.set(key, Math.sin(angle))
- }
- return sinCache.get(key)!
- }
- const getCachedCos = (angle: number) => {
- const key = Math.round(angle * 1000)
- if (!cosCache.has(key)) {
- cosCache.set(key, Math.cos(angle))
- }
- return cosCache.get(key)!
- }
- // 【优化3】批量更新所有模块样式
- const updateAllModuleStyles = () => {
- const count = modulesCount.value
- for (let i = 0; i < count; i++) {
- const offsetAngle = (3 * Math.PI / 2) + (i * 2 * Math.PI) / count
- const totalAngle = currentAngle.value + offsetAngle
- // 使用缓存的计算
- const cosVal = getCachedCos(totalAngle)
- const sinVal = getCachedSin(totalAngle)
- const x = cosVal * radiusX
- const y = sinVal * radiusY
- const scale = 0.7 + (sinVal + 1) / 2 * 0.6
- const opacity = 0.6 + (sinVal + 1) / 2 * 0.4
- const zIndex = Math.floor(sinVal * 10) + 10
- moduleStyles[i] = {
- transform: `translate(calc(-50% + ${x}px), calc(10% + ${y}px)) scale(${scale})`,
- margin:'auto',
- opacity: opacity,
- zIndex: zIndex,
- position: 'absolute',
- left: '50%',
- top: '50%'
- }
- }
- }
- // 平滑旋转动画
- let startAngle = 0
- let targetAngle = 0
- let animationStartTime = 0
- const startRotateAnimation = () => {
- if (isPaused.value) return
- startAngle = currentAngle.value
- targetAngle = currentAngle.value + STEP_ANGLE
- animationStartTime = performance.now()
- animate()
- }
- const animate = () => {
- if (isPaused.value) return
- const now = performance.now()
- let progress = (now - animationStartTime) / ANIMATION_DURATION
- progress = Math.min(progress, 1)
- // 缓动函数:更丝滑
- const t = progress === 1 ? 1 : 1 - Math.pow(1 - progress, 3)
- currentAngle.value = startAngle + (targetAngle - startAngle) * t
- // 【优化4】只在角度变化时更新样式
- updateAllModuleStyles()
- if (progress < 1) {
- animationFrame = requestAnimationFrame(animate)
- }
- }
- // 暂停 / 恢复
- const pauseRotation = () => {
- isPaused.value = true
- }
- const resumeRotation = () => {
- isPaused.value = false
- // 恢复时立即开始新的旋转动画
- if (rotationInterval) {
- clearInterval(rotationInterval)
- rotationInterval = setInterval(() => {
- if (!isPaused.value) startRotateAnimation()
- }, ROTATE_INTERVAL)
- }
- }
- // ==============================================
- // 从文件1同步:更新时间
- // ==============================================
- const updateTime = () => {
- const now = new Date()
- const weekArr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
- 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')
- const week = weekArr[now.getDay()]
- currentTime.value = `${year}/${month}/${day} ${hours}:${minutes}:${seconds} ${week}`
- }
- // ==============================================
- // 从文件1同步:路由处理
- // ==============================================
- function updateParentPath(routes) {
- return routes.map(parent => {
- const homeRoute = parent.children?.find(item => item.meta?.title === '首页')
- if (homeRoute) {
- return {
- ...parent,
- path: parent.path + '/' + homeRoute.path
- }
- }
- return { ...parent }
- })
- }
- // ==============================================
- // 加载动态驾驶舱
- // ==============================================
- const loadDynamicModules = () => {
- const subSystemRoutes = permissionStore.sidebarRouters.filter(route =>
- route.name
- )
- const newRoutes = updateParentPath(subSystemRoutes)
- const iconList = ['icon-operation', 'icon-sewage', 'icon-bi', 'icon-water', 'icon-flood', 'icon-123','icon-ps']
- const res = newRoutes.map((item, i) => ({
- name: item.meta?.title || '驾驶舱',
- iconClass: iconList[i] || 'icon-flood',
- path: item.path || '/'
- }))
- while (res.length < 6) {
- res.push({
- name: `驾驶舱${res.length + 1}`,
- iconClass: 'icon-flood',
- path: '/'
- })
- }
- modules.value = res.slice(0, 7)
- modulesCount.value = modules.value.length
- updateAllModuleStyles()
- }
- // ==============================================
- // 进入后台
- // ==============================================
- const enterBackend = () => {
- router.push('/index')
- }
- // ==============================================
- // 退出系统(文件1逻辑)
- // ==============================================
- const logout = () => {
- ElMessageBox.confirm('确定要退出登录吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- await userStore.logout()
- ElMessage.success('退出成功')
- router.push('/login')
- }).catch(() => {})
- }
- // ==============================================
- // 点击驾驶舱
- // ==============================================
- const handleModuleClick = (item) => {
- if (item.path) router.push(item.path)
- // let ip = "192.168.110.147";
- // let port = 81;
- // localStorage.removeItem("routeTitle");
- // localStorage.setItem("routeTitle", item.name);
- // window.location.replace(`http://${ip}:${port}?username=${encodeURIComponent(item.name)}`);
- };
- // ==============================================
- // 生命周期
- // ==============================================
- onMounted(() => {
- updateTime()
- timer = setInterval(updateTime, 1000)
- loadDynamicModules()
- // 启动旋转定时器
- rotationInterval = setInterval(() => {
- if (!isPaused.value) startRotateAnimation()
- }, ROTATE_INTERVAL)
- // 初始样式
- updateAllModuleStyles()
- })
- onUnmounted(() => {
- clearInterval(timer)
- if (rotationInterval) clearInterval(rotationInterval)
- if (animationFrame) cancelAnimationFrame(animationFrame)
- // 清理缓存
- sinCache.clear()
- cosCache.clear()
- })
- </script>
- <style scoped lang="scss">
- /* 样式保持不变,省略... */
- .big-screen-home {
- width: 100vw;
- height: 100vh;
- background-image: url("../../assets/portal/back.png");
- background-size: 100% 100%;
- color: #fff;
- position: relative;
- overflow: hidden;
- font-family: 'Microsoft YaHei', sans-serif;
- perspective: 1200px;
- }
- .header-bar {
- width: 100%;
- height: 70px;
- background-image: url("../../assets/portal/headerBackground.png");
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 30px;
- box-sizing: border-box;
- position: relative;
- z-index: 100;
- .header-left {
- width: 300px;
- .time-text {
- font-size: 14px;
- color: #a0d8ff;
- }
- }
- .system-title {
- font-size: 36px;
- font-weight: 600;
- color: #fff;
- text-shadow: 0 0 10px rgba(0, 191, 255, 0.8);
- margin: 0;
- letter-spacing: 2px;
- }
- .header-right {
- width: 300px;
- display: flex;
- align-items: center;
- gap: 15px;
- .welcome-text {
- font-size: 14px;
- color: #a0d8ff;
- }
- .username {
- color: #fff;
- font-weight: bold;
- }
- .header-btn {
- background: rgba(0, 127, 255, 0.3);
- border: 1px solid #00bfff;
- color: #fff;
- padding: 5px 12px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 13px;
- transition: all 0.2s;
- &:hover {
- background: rgba(0, 127, 255, 0.5);
- box-shadow: 0 0 8px rgba(0,191,255,0.5);
- }
- }
- }
- }
- .core-area {
- position: absolute;
- top: 60%;
- left: 50%;
- transform: translate(-50%, -50%);
- text-align: center;
- z-index: 20;
- pointer-events: none;
- .cloud-container {
- position: relative;
- .cloud-bg {
- width: 400px;
- height: 400px;
- margin: 0 auto;
- position: relative;
- background: url("../../assets/portal/conter.png") no-repeat center center;
- background-size: 100% 100%;
- /* 新增:动态发光 + 呼吸效果 */
- animation: cloudGlow 1s ease-in-out infinite alternate;
- }
- /* 发光动画:柔和呼吸光效 */
- @keyframes cloudGlow {
- 0% {
- filter: brightness(1) drop-shadow(0 0 12px rgba(0, 191, 255, 1));
- }
- 100% {
- filter: brightness(1.5) drop-shadow(0 0 28px rgba(0, 191, 255, 1));
- }
- }
- }
- }
- .ring-3d-container {
- position: absolute;
- top: 65%;
- left: 50%;
- width: 0;
- height: 0;
- transform: translate(-50%, -50%);
- .module-item-3d {
- position: absolute;
- display: flex;
- flex-direction: column;
- align-items: center;
- cursor: pointer;
- filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.3));
- will-change: transform, opacity;
- transition: filter 0.2s ease;
- &:hover {
- filter: drop-shadow(0 0 15px rgba(0, 191, 255, 0.8));
- z-index: 100 !important;
- }
- .icon-wrapper {
- .icon-circle {
- width: 150px;
- height: 80px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- .module-icon {
- width: 150px;
- height: 130px;
- &.icon-operation {
- background: url("../../assets/portal/smxyj.png") no-repeat center center;
- background-size: 100% 100%;
- }
- &.icon-sewage {
- background: url("../../assets/portal/yjzh.png") no-repeat center center;
- background-size: 100% 100%;
- }
- &.icon-bi {
- background: url("../../assets/portal/gwss.png") no-repeat center center;
- background-size: 100% 100%;
- }
- &.icon-water {
- background: url("../../assets/portal/rq.png") no-repeat center center;
- background-size: 100% 100%;
- }
- &.icon-flood {
- background: url("../../assets/portal/gs.png") no-repeat center center;
- background-size: 100% 100%;
- }
- &.icon-123 {
- background: url("../../assets/portal/ps.png") no-repeat center center;
- background-size: 100% 100%;
- }
- &.icon-ps {
- background: url("../../assets/portal/jg.png") no-repeat center center;
- background-size: 100% 100%;
- }
- }
- }
- }
- }
- }
- //@media (max-width: 1366px) {
- // .ring-3d-container .module-item-3d .icon-wrapper .icon-circle {
- // width: 70px;
- // height: 70px;
- //
- // .module-icon {
- // width: 35px;
- // height: 35px;
- // }
- // }
- //
- // .ring-3d-container .module-item-3d {
- // font-size: 12px;
- // padding: 3px 10px;
- // }
- //}
- </style>
|