|
|
@@ -9,32 +9,21 @@
|
|
|
:style="dockStyles"
|
|
|
@mouseleave="handleDockLeave">
|
|
|
|
|
|
- <div class="nav-arrow left-arrow"
|
|
|
- :class="{ 'is-disabled': mode === 'linear' && !canScrollLeft, 'is-active': mode === 'ellipse' || canScrollLeft }"
|
|
|
- @click="mode === 'ellipse' ? rotateMenu(-1) : scrollList(-1)"
|
|
|
- @mouseenter="pauseAutoRotate"
|
|
|
- @mouseleave="resumeAutoRotate">
|
|
|
- <img v-if="mode === 'ellipse' || canScrollLeft" src="@/assets/main/main-right.png" class="arrow-img left-facing" draggable="false" />
|
|
|
- <img v-else src="@/assets/main/main-left.png" class="arrow-img" draggable="false" />
|
|
|
+ <div class="nav-arrow left-arrow" :class="{ 'is-disabled': !canScrollLeft, 'is-active': canScrollLeft }"
|
|
|
+ @click="scrollList(-1)">
|
|
|
+ <img v-if="canScrollLeft" src="@/assets/main/main-right.png" class="arrow-img left-facing" />
|
|
|
+ <img v-else src="@/assets/main/main-left.png" class="arrow-img" />
|
|
|
</div>
|
|
|
|
|
|
- <div class="dock-list-container"
|
|
|
- :class="{ 'is-ellipse-mode': mode === 'ellipse' }"
|
|
|
- ref="listContainer"
|
|
|
- @scroll="checkScrollState"
|
|
|
- @mousedown.prevent="handleDragStart"
|
|
|
- @touchstart.passive="handleDragStart"
|
|
|
- @mouseenter="pauseAutoRotate"
|
|
|
- @mouseleave="resumeAutoRotate">
|
|
|
-
|
|
|
- <div class="dock-list" :class="{ 'is-ellipse-mode': mode === 'ellipse' }">
|
|
|
+ <div class="dock-list-container" ref="listContainer" @scroll="checkScrollState">
|
|
|
+ <div class="dock-list">
|
|
|
<div v-for="(item, index) in dockItems" :key="index" class="dock-item"
|
|
|
:class="{
|
|
|
'is-active': activeIndex === index,
|
|
|
[`theme-${item.theme}`]: item.theme,
|
|
|
- 'is-front': mode === 'ellipse' && frontIndex === index
|
|
|
+ 'is-breathing': waveAnimation
|
|
|
}"
|
|
|
- :style="mode === 'ellipse' ? getEllipseStyle(index) : {}"
|
|
|
+ :style="waveAnimation ? { animationDelay: `${index * 0.15}s` } : {}"
|
|
|
@click="handleSelect(index, item)"
|
|
|
@mouseenter="hoverIndex = index"
|
|
|
@mouseleave="hoverIndex = null"
|
|
|
@@ -42,7 +31,8 @@
|
|
|
<div class="item-icon">
|
|
|
<img v-if="item.imgUrl"
|
|
|
:src="(activeIndex === index || hoverIndex === index) && item.activeImgUrl ? item.activeImgUrl : item.imgUrl"
|
|
|
- class="custom-icon" draggable="false" />
|
|
|
+ class="custom-icon" />
|
|
|
+
|
|
|
<i v-else :class="item.iconClass"></i>
|
|
|
</div>
|
|
|
<div class="item-label">{{ item.label }}</div>
|
|
|
@@ -50,13 +40,10 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="nav-arrow right-arrow"
|
|
|
- :class="{ 'is-disabled': mode === 'linear' && !canScrollRight, 'is-active': mode === 'ellipse' || canScrollRight }"
|
|
|
- @click="mode === 'ellipse' ? rotateMenu(1) : scrollList(1)"
|
|
|
- @mouseenter="pauseAutoRotate"
|
|
|
- @mouseleave="resumeAutoRotate">
|
|
|
- <img v-if="mode === 'ellipse' || canScrollRight" src="@/assets/main/main-right.png" class="arrow-img" draggable="false" />
|
|
|
- <img v-else src="@/assets/main/main-left.png" class="arrow-img left-facing" draggable="false" />
|
|
|
+ <div class="nav-arrow right-arrow" :class="{ 'is-disabled': !canScrollRight, 'is-active': canScrollRight }"
|
|
|
+ @click="scrollList(1)">
|
|
|
+ <img v-if="canScrollRight" src="@/assets/main/main-right.png" class="arrow-img" />
|
|
|
+ <img v-else src="@/assets/main/main-left.png" class="arrow-img left-facing" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -65,16 +52,31 @@
|
|
|
export default {
|
|
|
name: 'BottomDock',
|
|
|
props: {
|
|
|
- autoHide: { type: Boolean, default: true },
|
|
|
- bottomOffset: { type: Number, default: 0 },
|
|
|
- customStyle: { type: Object, default: () => ({}) },
|
|
|
- customClass: { type: [String, Array, Object], default: '' },
|
|
|
-
|
|
|
- mode: { type: String, default: 'linear' },
|
|
|
- autoRotate: { type: Boolean, default: true },
|
|
|
- autoRotateSpeed: { type: Number, default: 2500 },
|
|
|
- radiusX: { type: Number, default: 380 },
|
|
|
- radiusY: { type: Number, default: 60 },
|
|
|
+ // 是否自动隐藏 (默认 true: 悬浮升起; false: 常驻显示)
|
|
|
+ autoHide: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ },
|
|
|
+ // 距离屏幕底部的偏移量 (单位 px,正数代表往上抬高)
|
|
|
+ bottomOffset: {
|
|
|
+ type: Number,
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ // 允许外部传入的自定义容器样式 (如背景色、宽度等)
|
|
|
+ customStyle: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({})
|
|
|
+ },
|
|
|
+ // 允许外部传入自定义 class (支持字符串、数组、对象)
|
|
|
+ customClass: {
|
|
|
+ type: [String, Array, Object],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ // 是否开启波浪呼吸动画
|
|
|
+ waveAnimation: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ }
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
@@ -83,34 +85,71 @@ export default {
|
|
|
dockExpanded: false,
|
|
|
canScrollLeft: false,
|
|
|
canScrollRight: true,
|
|
|
-
|
|
|
- ellipseRotation: Math.PI / 2,
|
|
|
- frontIndex: 0,
|
|
|
-
|
|
|
- // --- 拖拽交互状态 ---
|
|
|
- isDragging: false,
|
|
|
- hasDragged: false,
|
|
|
- startX: 0,
|
|
|
- currentX: 0, // 记录实时拖动位置
|
|
|
- startRotation: 0,
|
|
|
- startFrontIndex: 0, // 记录拖拽开始时的正前方项目
|
|
|
- rotateTimer: null,
|
|
|
-
|
|
|
dockItems: [
|
|
|
- { label: '首页', imgUrl: require('@/assets/main/main-home.png'), activeImgUrl: require('@/assets/main/main-home-hover.png'), route: '/home', theme: 'blue' },
|
|
|
- { label: '状态监控', imgUrl: require('@/assets/main/main-surve.png'), activeImgUrl: require('@/assets/main/main-surve-hover.png'), route: '/surve', theme: 'blue' },
|
|
|
- { label: '勤务管理', imgUrl: require('@/assets/main/main-security.png'), activeImgUrl: require('@/assets/main/main-security-hover.png'), route: '/security', theme: 'gold' },
|
|
|
- { label: '干线协调', imgUrl: require('@/assets/main/main-coor.png'), activeImgUrl: require('@/assets/main/main-coor-hover.png'), route: '/coor', theme: 'blue' },
|
|
|
- { label: '数据分析', imgUrl: require('@/assets/main/main-watch.png'), activeImgUrl: require('@/assets/main/main-watch-hover.png'), route: '/watch', theme: 'blue' },
|
|
|
- { label: '系统设置', imgUrl: require('@/assets/main/main-setting.png'), activeImgUrl: require('@/assets/main/main-setting-hover.png'), route: '/setting', theme: 'blue' },
|
|
|
- // { label: '测试1', imgUrl: require('@/assets/main/main-home.png'), activeImgUrl: require('@/assets/main/main-home-hover.png'), theme: 'blue' },
|
|
|
- // { label: '测试2', imgUrl: require('@/assets/main/main-surve.png'), activeImgUrl: require('@/assets/main/main-surve-hover.png'), theme: 'blue' },
|
|
|
- // { label: '测试3', imgUrl: require('@/assets/main/main-security.png'), activeImgUrl: require('@/assets/main/main-security-hover.png'), theme: 'blue' },
|
|
|
+ {
|
|
|
+ label: '首页',
|
|
|
+ imgUrl: require('@/assets/main/main-home.png'),
|
|
|
+ activeImgUrl: require('@/assets/main/main-home-hover.png'),
|
|
|
+ route: '/home',
|
|
|
+ theme: 'blue',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '状态监控',
|
|
|
+ imgUrl: require('@/assets/main/main-surve.png'),
|
|
|
+ activeImgUrl: require('@/assets/main/main-surve-hover.png'),
|
|
|
+ route: '/surve',
|
|
|
+ theme: 'blue',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '勤务管理',
|
|
|
+ imgUrl: require('@/assets/main/main-security.png'),
|
|
|
+ activeImgUrl: require('@/assets/main/main-security-hover.png'),
|
|
|
+ route: '/security',
|
|
|
+ theme: 'gold',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '干线协调',
|
|
|
+ imgUrl: require('@/assets/main/main-coor.png'),
|
|
|
+ activeImgUrl: require('@/assets/main/main-coor-hover.png'),
|
|
|
+ route: '/coor',
|
|
|
+ theme: 'blue',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '数据分析',
|
|
|
+ imgUrl: require('@/assets/main/main-watch.png'),
|
|
|
+ activeImgUrl: require('@/assets/main/main-watch-hover.png'),
|
|
|
+ route: '/watch',
|
|
|
+ theme: 'blue',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '系统设置',
|
|
|
+ imgUrl: require('@/assets/main/main-setting.png'),
|
|
|
+ activeImgUrl: require('@/assets/main/main-setting-hover.png'),
|
|
|
+ route: '/setting',
|
|
|
+ theme: 'blue',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '测试1',
|
|
|
+ imgUrl: require('@/assets/main/main-home.png'),
|
|
|
+ theme: 'blue',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '测试2',
|
|
|
+ imgUrl: require('@/assets/main/main-surve.png'),
|
|
|
+ theme: 'blue',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '测试3',
|
|
|
+ imgUrl: require('@/assets/main/main-security.png'),
|
|
|
+ theme: 'blue',
|
|
|
+ },
|
|
|
]
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
+ // 动态计算 CSS 变量和合并自定义样式
|
|
|
dockStyles() {
|
|
|
+ // 统一只传一个基础 bottom 偏移量,动画交由 CSS transform 处理
|
|
|
return {
|
|
|
'--dock-bottom': `${this.bottomOffset}px`,
|
|
|
...this.customStyle
|
|
|
@@ -127,69 +166,43 @@ export default {
|
|
|
},
|
|
|
mounted() {
|
|
|
this.$nextTick(() => {
|
|
|
- if (this.mode === 'linear') {
|
|
|
- this.checkScrollState();
|
|
|
- window.addEventListener('resize', this.checkScrollState);
|
|
|
- } else {
|
|
|
- this.resumeAutoRotate();
|
|
|
- }
|
|
|
+ this.checkScrollState();
|
|
|
+ window.addEventListener('resize', this.checkScrollState);
|
|
|
if (this.autoHide) {
|
|
|
document.addEventListener('mousemove', this.handleGlobalMouseMove);
|
|
|
}
|
|
|
- window.addEventListener('mousemove', this.handleDragging);
|
|
|
- window.addEventListener('mouseup', this.handleDragEnd);
|
|
|
- window.addEventListener('touchmove', this.handleDragging, { passive: false });
|
|
|
- window.addEventListener('touchend', this.handleDragEnd);
|
|
|
});
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
window.removeEventListener('resize', this.checkScrollState);
|
|
|
document.removeEventListener('mousemove', this.handleGlobalMouseMove);
|
|
|
- window.removeEventListener('mousemove', this.handleDragging);
|
|
|
- window.removeEventListener('mouseup', this.handleDragEnd);
|
|
|
- window.removeEventListener('touchmove', this.handleDragging);
|
|
|
- window.removeEventListener('touchend', this.handleDragEnd);
|
|
|
- this.pauseAutoRotate();
|
|
|
},
|
|
|
methods: {
|
|
|
updateActiveIndexByRoute() {
|
|
|
- const currentPath = this.$route.path;
|
|
|
+ const currentPath = this.$route?.path || '';
|
|
|
const matchIndex = this.dockItems.findIndex(item => {
|
|
|
return item.route && currentPath.startsWith(item.route);
|
|
|
});
|
|
|
|
|
|
if (matchIndex !== -1) {
|
|
|
this.activeIndex = matchIndex;
|
|
|
- if (this.mode === 'ellipse') {
|
|
|
- this.rotateTo(matchIndex);
|
|
|
- }
|
|
|
}
|
|
|
},
|
|
|
handleSelect(index, item) {
|
|
|
- if (this.hasDragged) {
|
|
|
- this.hasDragged = false;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (this.mode === 'ellipse') {
|
|
|
- this.rotateTo(index);
|
|
|
- } else if (this.activeIndex === index) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
+ if (this.activeIndex === index) return;
|
|
|
this.activeIndex = index;
|
|
|
|
|
|
- if (item.route) {
|
|
|
+ if (item.route && this.$router) {
|
|
|
this.$router.push(item.route).catch(err => {
|
|
|
if (err.name !== 'NavigationDuplicated') {
|
|
|
console.error('路由跳转失败:', err);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
+
|
|
|
this.$emit('change', item);
|
|
|
},
|
|
|
checkScrollState() {
|
|
|
- if (this.mode !== 'linear') return;
|
|
|
const container = this.$refs.listContainer;
|
|
|
if (!container) return;
|
|
|
|
|
|
@@ -201,10 +214,12 @@ export default {
|
|
|
const el = this.$refs.dockWrapper;
|
|
|
if (!el) return;
|
|
|
const rect = el.getBoundingClientRect();
|
|
|
+ // 发光线区域:居中,宽度与 CSS clamp(150px, 20vw, 250px) 一致
|
|
|
const lineWidth = Math.min(250, Math.max(150, window.innerWidth * 0.2));
|
|
|
const centerX = window.innerWidth / 2;
|
|
|
const left = centerX - lineWidth / 2;
|
|
|
const right = centerX + lineWidth / 2;
|
|
|
+ // 垂直:dock 可见顶部往上延伸 30px(与 ::after 热区一致)
|
|
|
if (e.clientX >= left && e.clientX <= right && e.clientY >= rect.top - 30) {
|
|
|
this.dockExpanded = true;
|
|
|
}
|
|
|
@@ -225,125 +240,13 @@ export default {
|
|
|
behavior: 'smooth'
|
|
|
});
|
|
|
}
|
|
|
- },
|
|
|
- getEllipseStyle(index) {
|
|
|
- const total = this.dockItems.length;
|
|
|
- const baseAngle = (index / total) * Math.PI * 2;
|
|
|
- const finalAngle = baseAngle + this.ellipseRotation;
|
|
|
-
|
|
|
- const x = Math.cos(finalAngle) * this.radiusX;
|
|
|
- const y = Math.sin(finalAngle) * this.radiusY;
|
|
|
-
|
|
|
- const sinVal = Math.sin(finalAngle);
|
|
|
- const normalizedDepth = (sinVal + 1) / 2;
|
|
|
-
|
|
|
- let scale = 0.5 + (normalizedDepth * 0.7);
|
|
|
- if (this.hoverIndex === index) scale *= 1.2;
|
|
|
-
|
|
|
- const opacity = 0.3 + (normalizedDepth * 0.7);
|
|
|
- const zIndex = Math.round(normalizedDepth * 100);
|
|
|
-
|
|
|
- return {
|
|
|
- position: 'absolute',
|
|
|
- left: '50%',
|
|
|
- top: '50%',
|
|
|
- transform: `translate(-50%, -50%) translate(${x}px, ${y}px) scale(${scale})`,
|
|
|
- opacity: opacity,
|
|
|
- zIndex: zIndex,
|
|
|
- transition: this.isDragging ? 'none' : 'transform 0.6s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.6s'
|
|
|
- };
|
|
|
- },
|
|
|
- rotateMenu(direction) {
|
|
|
- const total = this.dockItems.length;
|
|
|
- const stepAngle = (Math.PI * 2) / total;
|
|
|
- this.ellipseRotation -= direction * stepAngle;
|
|
|
- this.updateFrontIndex();
|
|
|
- },
|
|
|
- rotateTo(index) {
|
|
|
- const total = this.dockItems.length;
|
|
|
- let diff = this.frontIndex - index;
|
|
|
- if (diff > total / 2) diff -= total;
|
|
|
- if (diff < -total / 2) diff += total;
|
|
|
-
|
|
|
- const stepAngle = (Math.PI * 2) / total;
|
|
|
- this.ellipseRotation += diff * stepAngle;
|
|
|
- this.frontIndex = index;
|
|
|
- },
|
|
|
- updateFrontIndex() {
|
|
|
- const total = this.dockItems.length;
|
|
|
- const stepAngle = (Math.PI * 2) / total;
|
|
|
- const currentRot = ((this.ellipseRotation % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
|
|
|
- let targetIndex = Math.round((Math.PI / 2 - currentRot) / stepAngle);
|
|
|
- this.frontIndex = ((targetIndex % total) + total) % total;
|
|
|
- },
|
|
|
-
|
|
|
- // ================= 拖拽逻辑核心修改区 =================
|
|
|
- handleDragStart(e) {
|
|
|
- if (this.mode !== 'ellipse') return;
|
|
|
- this.isDragging = true;
|
|
|
- this.hasDragged = false;
|
|
|
- this.pauseAutoRotate();
|
|
|
- this.startX = e.clientX || (e.touches && e.touches[0].clientX);
|
|
|
- this.currentX = this.startX;
|
|
|
-
|
|
|
- // 记录拖拽前的完美状态
|
|
|
- this.startRotation = this.ellipseRotation;
|
|
|
- this.startFrontIndex = this.frontIndex;
|
|
|
- },
|
|
|
- handleDragging(e) {
|
|
|
- if (!this.isDragging) return;
|
|
|
- this.currentX = e.clientX || (e.touches && e.touches[0].clientX);
|
|
|
- const diffX = this.currentX - this.startX;
|
|
|
- if (Math.abs(diffX) > 5) this.hasDragged = true;
|
|
|
-
|
|
|
- // 拖动时,视觉上跟随鼠标
|
|
|
- this.ellipseRotation = this.startRotation - (diffX / 350);
|
|
|
- this.updateFrontIndex(); // 让中间的图标实时发光
|
|
|
- },
|
|
|
- handleDragEnd() {
|
|
|
- if (!this.isDragging) return;
|
|
|
- this.isDragging = false;
|
|
|
-
|
|
|
- const diffX = this.currentX - this.startX;
|
|
|
- const threshold = 40; // 触发切换的距离阈值(滑动超过 40px 就切换)
|
|
|
-
|
|
|
- // 【关键】无论拖动多远,先把底层状态恢复到起点
|
|
|
- // 配合 isDragging = false 时的 transition 过渡,这能保证完美的滑动动画
|
|
|
- this.ellipseRotation = this.startRotation;
|
|
|
- this.frontIndex = this.startFrontIndex;
|
|
|
-
|
|
|
- if (diffX < -threshold) {
|
|
|
- // 向左滑:精准切换到“下一个”
|
|
|
- this.rotateMenu(1);
|
|
|
- } else if (diffX > threshold) {
|
|
|
- // 向右滑:精准切换到“上一个”
|
|
|
- this.rotateMenu(-1);
|
|
|
- } else {
|
|
|
- // 滑动距离不够,原地吸附回正(无操作,因为上面已经还原了 startRotation)
|
|
|
- }
|
|
|
-
|
|
|
- this.resumeAutoRotate();
|
|
|
- },
|
|
|
- // ======================================================
|
|
|
-
|
|
|
- resumeAutoRotate() {
|
|
|
- if (this.mode !== 'ellipse' || !this.autoRotate || this.rotateTimer) return;
|
|
|
- this.rotateTimer = setInterval(() => {
|
|
|
- this.rotateMenu(-1);
|
|
|
- }, this.autoRotateSpeed);
|
|
|
- },
|
|
|
- pauseAutoRotate() {
|
|
|
- if (this.rotateTimer) {
|
|
|
- clearInterval(this.rotateTimer);
|
|
|
- this.rotateTimer = null;
|
|
|
- }
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-/* ================= 以下是你的原版 CSS (原封不动) ================= */
|
|
|
+/* ================= 整体容器布局 ================= */
|
|
|
.dock-wrapper {
|
|
|
display: flex !important;
|
|
|
flex-direction: row !important;
|
|
|
@@ -360,6 +263,7 @@ export default {
|
|
|
pointer-events: auto !important;
|
|
|
}
|
|
|
|
|
|
+/* === 状态 A:自动隐藏 === */
|
|
|
.dock-wrapper.is-auto-hide {
|
|
|
transform: translateY(calc(100% - clamp(15px, 4vh, 20px)));
|
|
|
pointer-events: none;
|
|
|
@@ -369,6 +273,7 @@ export default {
|
|
|
pointer-events: auto;
|
|
|
}
|
|
|
|
|
|
+/* 中间触发热区:只覆盖发光线范围,pointer-events 始终开启 */
|
|
|
.dock-wrapper.is-auto-hide::after {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
@@ -381,12 +286,14 @@ export default {
|
|
|
pointer-events: auto;
|
|
|
}
|
|
|
|
|
|
+/* 发光的指示线自适应 */
|
|
|
.dock-wrapper.is-auto-hide::before {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
left: 50%;
|
|
|
transform: translateX(-50%);
|
|
|
+ /* 宽度和厚度也做一点自适应,小屏自动变短点 */
|
|
|
width: clamp(150px, 20vw, 250px);
|
|
|
height: clamp(3px, 0.5vh, 5px);
|
|
|
background: rgba(0, 229, 255, 0.6);
|
|
|
@@ -400,6 +307,7 @@ export default {
|
|
|
opacity: 0;
|
|
|
}
|
|
|
|
|
|
+/* === 状态 B:常驻显示 === */
|
|
|
.dock-wrapper.is-always-show {
|
|
|
transform: translateY(0);
|
|
|
}
|
|
|
@@ -408,6 +316,7 @@ export default {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
+/* ================= 内部列表与滚动容器 ================= */
|
|
|
.dock-list-container {
|
|
|
width: 750px;
|
|
|
height: 160px;
|
|
|
@@ -434,6 +343,7 @@ export default {
|
|
|
gap: 30px;
|
|
|
}
|
|
|
|
|
|
+/* ================= 左右控制箭头 ================= */
|
|
|
.nav-arrow {
|
|
|
flex-shrink: 0;
|
|
|
width: 40px;
|
|
|
@@ -474,6 +384,7 @@ export default {
|
|
|
opacity: 0.6;
|
|
|
}
|
|
|
|
|
|
+/* ================= 单个导航项 ================= */
|
|
|
.dock-item {
|
|
|
flex-shrink: 0;
|
|
|
position: relative;
|
|
|
@@ -503,6 +414,7 @@ export default {
|
|
|
letter-spacing: 1px;
|
|
|
}
|
|
|
|
|
|
+/* ================= 交互状态:悬浮与选中 ================= */
|
|
|
.dock-item:hover {
|
|
|
transform: translateY(-15px) scale(1.15);
|
|
|
}
|
|
|
@@ -553,40 +465,25 @@ export default {
|
|
|
filter: drop-shadow(0 0 8px rgba(0, 229, 255, 0.8));
|
|
|
}
|
|
|
|
|
|
-/* ================= 针对 3D 椭圆模式的样式覆盖 ================= */
|
|
|
-.dock-list-container.is-ellipse-mode {
|
|
|
- overflow: visible !important;
|
|
|
- cursor: grab;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
-}
|
|
|
-.dock-list-container.is-ellipse-mode:active {
|
|
|
- cursor: grabbing;
|
|
|
+/* ================= 呼吸波浪动态效果 ================= */
|
|
|
+@keyframes breathing-wave {
|
|
|
+ 0%, 100% {
|
|
|
+ transform: translateY(0) scale(1);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ transform: translateY(-8px) scale(1.08); /* 呼吸时轻微上浮和放大 */
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-.dock-list.is-ellipse-mode {
|
|
|
- position: relative !important;
|
|
|
- width: 0 !important;
|
|
|
- height: 0 !important;
|
|
|
- min-width: 0 !important;
|
|
|
- padding-bottom: 0 !important;
|
|
|
- gap: 0 !important;
|
|
|
+.dock-item.is-breathing {
|
|
|
+ /* 动画周期设为 2.5s,无限循环,平滑过渡 */
|
|
|
+ animation: breathing-wave 2.5s infinite ease-in-out;
|
|
|
}
|
|
|
|
|
|
-.dock-list.is-ellipse-mode .dock-item {
|
|
|
- position: absolute;
|
|
|
-}
|
|
|
-.dock-list.is-ellipse-mode .dock-item:hover {
|
|
|
- transform: none;
|
|
|
-}
|
|
|
-
|
|
|
-.dock-list.is-ellipse-mode .dock-item.is-front .item-label {
|
|
|
- color: #ffffff;
|
|
|
- font-weight: bold;
|
|
|
- text-shadow: 0 0 10px #00e5ff;
|
|
|
-}
|
|
|
-.dock-list.is-ellipse-mode .dock-item.is-front .custom-icon {
|
|
|
- filter: drop-shadow(0 0 10px rgba(0, 229, 255, 0.8));
|
|
|
+/* 覆盖动画冲突:当鼠标悬浮或该项处于激活状态时,停止呼吸动画,采用原有的高亮放大效果 */
|
|
|
+.dock-item:hover,
|
|
|
+.dock-item.is-active {
|
|
|
+ animation: none !important; /* 强制停止动画 */
|
|
|
+ transform: translateY(-15px) scale(1.15) !important;
|
|
|
}
|
|
|
</style>
|