|
|
@@ -0,0 +1,417 @@
|
|
|
+<template>
|
|
|
+ <div class="security-route-panel">
|
|
|
+
|
|
|
+ <div class="panel-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <div>
|
|
|
+ <span class="status-dot"></span>
|
|
|
+ <span class="title-text">特勤安保路线</span>
|
|
|
+ <span class="status-text text-danger">未开始</span>
|
|
|
+ <span class="level-text text-danger">一级</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span>2026.2.18执行</span>
|
|
|
+ <span class="close-btn" @click="$emit('close-dialog');">x</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="carousel-wrapper">
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="nav-arrow left-arrow"
|
|
|
+ :class="{ 'is-disabled': !canScrollLeft, 'is-active': canScrollLeft }"
|
|
|
+ @click="handlePrev"
|
|
|
+ >
|
|
|
+ <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="route-list-window">
|
|
|
+ <div class="route-list-track">
|
|
|
+
|
|
|
+ <div class="route-card" v-for="route in visibleRoutes" :key="route.id">
|
|
|
+
|
|
|
+ <div class="card-top-video">
|
|
|
+ <video class="responsive-video" :src="route.mainVideo" autoplay loop muted></video>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="card-bottom-content">
|
|
|
+ <div class="route-name">
|
|
|
+ <span class="blue-dot"></span>
|
|
|
+ {{ route.name }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="map-and-info">
|
|
|
+ <div class="map-container">
|
|
|
+ <IntersectionMap
|
|
|
+ :mapData="intersectionData"
|
|
|
+ :videoUrls="route.cornerVideos"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-action-box">
|
|
|
+ <div class="info-list">
|
|
|
+ <span>等级:<span class="text-green">{{ route.level }}</span></span>
|
|
|
+ <span>方式:<span class="text-green">{{ route.mode }}</span></span>
|
|
|
+ <span>时间:<span class="text-green">{{ route.time }}</span></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="nav-arrow right-arrow"
|
|
|
+ :class="{ 'is-disabled': !canScrollRight, 'is-active': canScrollRight }"
|
|
|
+ @click="handleNext"
|
|
|
+ >
|
|
|
+ <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>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import IntersectionMap from '@/components/ui/IntersectionMapVideos.vue';
|
|
|
+import { getIntersectionData } from '@/mock/data';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'SecurityRoutePanelSwitchSmall',
|
|
|
+ components: {
|
|
|
+ IntersectionMap,
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ intersectionData: {},
|
|
|
+
|
|
|
+ currentIndex: 0,
|
|
|
+ pageSize: 3,
|
|
|
+
|
|
|
+ routeList: [
|
|
|
+ {
|
|
|
+ id: 1, name: '靖远路与北公路交叉口 1', level: '一级', mode: '快进', time: '30s',
|
|
|
+ mainVideo: require('@/assets/videos/video1.mp4'),
|
|
|
+ cornerVideos: { nw: require('@/assets/videos/video1.mp4'), ne: require('@/assets/videos/video2.mp4'), sw: require('@/assets/videos/video2.mp4'), se: require('@/assets/videos/video1.mp4') }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2, name: '靖远路与北公路交叉口 2', level: '一级', mode: '快进', time: '30s',
|
|
|
+ mainVideo: require('@/assets/videos/video1.mp4'),
|
|
|
+ cornerVideos: { nw: require('@/assets/videos/video1.mp4'), ne: require('@/assets/videos/video2.mp4'), sw: require('@/assets/videos/video2.mp4'), se: require('@/assets/videos/video1.mp4') }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3, name: '靖远路与北公路交叉口 3', level: '一级', mode: '快进', time: '30s',
|
|
|
+ mainVideo: require('@/assets/videos/video1.mp4'),
|
|
|
+ cornerVideos: { nw: require('@/assets/videos/video1.mp4'), ne: require('@/assets/videos/video2.mp4'), sw: require('@/assets/videos/video2.mp4'), se: require('@/assets/videos/video1.mp4') }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4, name: '靖远路与北公路交叉口 4', level: '一级', mode: '快进', time: '30s',
|
|
|
+ mainVideo: require('@/assets/videos/video1.mp4'),
|
|
|
+ cornerVideos: { nw: require('@/assets/videos/video1.mp4'), ne: require('@/assets/videos/video2.mp4'), sw: require('@/assets/videos/video2.mp4'), se: require('@/assets/videos/video1.mp4') }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 5, name: '靖远路与北公路交叉口 5', level: '一级', mode: '快进', time: '30s',
|
|
|
+ mainVideo: require('@/assets/videos/video1.mp4'),
|
|
|
+ cornerVideos: { nw: require('@/assets/videos/video1.mp4'), ne: require('@/assets/videos/video2.mp4'), sw: require('@/assets/videos/video2.mp4'), se: require('@/assets/videos/video1.mp4') }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ visibleRoutes() {
|
|
|
+ return this.routeList.slice(this.currentIndex, this.currentIndex + this.pageSize);
|
|
|
+ },
|
|
|
+ canScrollLeft() {
|
|
|
+ return this.currentIndex > 0;
|
|
|
+ },
|
|
|
+ canScrollRight() {
|
|
|
+ return this.currentIndex < this.routeList.length - this.pageSize;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async mounted() {
|
|
|
+ this.intersectionData = await getIntersectionData();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ handlePrev() {
|
|
|
+ if (this.canScrollLeft) {
|
|
|
+ this.currentIndex--;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleNext() {
|
|
|
+ if (this.canScrollRight) {
|
|
|
+ this.currentIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* ================== 整体面板 ================== */
|
|
|
+.security-route-panel {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ================== 顶部 Header ================== */
|
|
|
+.panel-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ padding-left: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ width: 100%;
|
|
|
+ gap: 12px;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.close-btn {
|
|
|
+ cursor: pointer;
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 16px;
|
|
|
+ line-height: 1;
|
|
|
+ font-weight: 300;
|
|
|
+ opacity: 0.8;
|
|
|
+ transition: all 0.2s;
|
|
|
+ margin-left: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.close-btn:hover {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1.1);
|
|
|
+}
|
|
|
+.header-left>div {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+ gap: 12px;
|
|
|
+ color: #fff;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+.header-left>div+div {
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+.status-dot {
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ background-color: #68e75f;
|
|
|
+ border-radius: 50%;
|
|
|
+ box-shadow: 0 0 8px rgba(104, 231, 95, 0.6);
|
|
|
+}
|
|
|
+
|
|
|
+.title-text {
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.text-danger {
|
|
|
+ color: #ff5252;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-start {
|
|
|
+ margin-left: 15px;
|
|
|
+ background: rgba(40, 90, 180, 0.6);
|
|
|
+ border: 1px solid #448aff;
|
|
|
+ color: #fff;
|
|
|
+ padding: 4px 16px;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+.btn-start:hover {
|
|
|
+ background: rgba(40, 90, 180, 0.9);
|
|
|
+ box-shadow: 0 0 10px rgba(68, 138, 255, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+/* ================== 轮播区容器 ================== */
|
|
|
+.carousel-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 15px;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* ================== 图片导航按钮统一样式 ================== */
|
|
|
+.nav-arrow {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ transition: all 0.3s;
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+ outline: none;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+ transition: transform 0.2s ease, filter 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.left-facing {
|
|
|
+ transform: rotate(180deg);
|
|
|
+}
|
|
|
+
|
|
|
+.right-arrow {
|
|
|
+ position: absolute;
|
|
|
+ right: 0px;
|
|
|
+}
|
|
|
+.left-arrow {
|
|
|
+ position: absolute;
|
|
|
+ left: 0px;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-arrow.is-active {
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+.nav-arrow.is-active:hover .arrow-img {
|
|
|
+ transform: scale(1.15);
|
|
|
+ filter: drop-shadow(0 0 10px rgba(0, 229, 255, 0.8));
|
|
|
+}
|
|
|
+
|
|
|
+.nav-arrow.left-arrow.is-active:hover .arrow-img.left-facing {
|
|
|
+ transform: rotate(180deg) scale(1.15);
|
|
|
+}
|
|
|
+
|
|
|
+.nav-arrow.is-disabled {
|
|
|
+ cursor: not-allowed;
|
|
|
+ opacity: 0.5;
|
|
|
+}
|
|
|
+
|
|
|
+/* ================== 卡片列表视窗 ================== */
|
|
|
+.route-list-window {
|
|
|
+ flex: 1;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.route-list-track {
|
|
|
+ display: flex;
|
|
|
+ height: 100%;
|
|
|
+ gap: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【核心修改】:删除了这里原本的 .fade-enter-active, .fade-leave-active 动画样式 */
|
|
|
+
|
|
|
+/* ================== 单个卡片样式 ================== */
|
|
|
+.route-card {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background: rgba(22, 34, 60, 0.6);
|
|
|
+ border: 1px solid rgba(100, 150, 255, 0.2);
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.card-top-video {
|
|
|
+ width: 100%;
|
|
|
+ height: 55%;
|
|
|
+ background: #000;
|
|
|
+ border-bottom: 1px solid rgba(100, 150, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.responsive-video {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: block;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.card-bottom-content {
|
|
|
+ flex: 1;
|
|
|
+ padding: 12px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.route-name {
|
|
|
+ color: #fff;
|
|
|
+ font-size: 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.blue-dot {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ background-color: #448aff;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 8px;
|
|
|
+ box-shadow: 0 0 5px rgba(68, 138, 255, 0.8);
|
|
|
+}
|
|
|
+
|
|
|
+.map-and-info {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ gap: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.map-container {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.info-action-box {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.info-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #a0a5b0;
|
|
|
+}
|
|
|
+
|
|
|
+.text-green {
|
|
|
+ color: #48c79c;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-unlock {
|
|
|
+ align-self: flex-start;
|
|
|
+ background: rgba(40, 90, 180, 0.6);
|
|
|
+ border: 1px solid #448aff;
|
|
|
+ color: #fff;
|
|
|
+ padding: 4px 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 12px;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-unlock:hover {
|
|
|
+ background: rgba(40, 90, 180, 0.9);
|
|
|
+ box-shadow: 0 0 8px rgba(68, 138, 255, 0.5);
|
|
|
+}
|
|
|
+</style>
|