SecurityRoutePanelSwitch.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <template>
  2. <div class="security-route-panel">
  3. <div class="panel-header">
  4. <div class="header-left">
  5. <span class="status-dot"></span>
  6. <span class="title-text">特勤安保路线</span>
  7. <span class="status-text text-danger">未开始</span>
  8. <span class="level-text text-danger">一级</span>
  9. <button class="btn-start">立即开始</button>
  10. </div>
  11. </div>
  12. <div class="carousel-wrapper">
  13. <div
  14. class="nav-arrow left-arrow"
  15. :class="{ 'is-disabled': !canScrollLeft, 'is-active': canScrollLeft }"
  16. @click="handlePrev"
  17. >
  18. <img v-if="canScrollLeft" src="@/assets/main/main-right.png" class="arrow-img left-facing" />
  19. <img v-else src="@/assets/main/main-left.png" class="arrow-img" />
  20. </div>
  21. <div class="route-list-window">
  22. <div class="route-list-track">
  23. <div class="route-card" v-for="route in visibleRoutes" :key="route.id">
  24. <div class="card-top-video">
  25. <XgVideoPlayer :src="route.mainVideo" :autoplay="true" />
  26. </div>
  27. <div class="card-bottom-content">
  28. <div class="route-name">
  29. <span class="blue-dot"></span>
  30. {{ route.name }}
  31. </div>
  32. <div class="map-and-info">
  33. <div class="map-container">
  34. <IntersectionMap :mapData="intersectionData" />
  35. </div>
  36. <div class="info-action-box">
  37. <div class="info-list">
  38. <span>等级:<span class="text-green">{{ route.level }}</span></span>
  39. <span>方式:<span class="text-green">{{ route.mode }}</span></span>
  40. <span>时间:<span class="text-green">{{ route.time }}</span></span>
  41. </div>
  42. <button class="btn-unlock">立即解锁</button>
  43. </div>
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. </div>
  49. <div
  50. class="nav-arrow right-arrow"
  51. :class="{ 'is-disabled': !canScrollRight, 'is-active': canScrollRight }"
  52. @click="handleNext"
  53. >
  54. <img v-if="canScrollRight" src="@/assets/main/main-right.png" class="arrow-img" />
  55. <img v-else src="@/assets/main/main-left.png" class="arrow-img left-facing" />
  56. </div>
  57. </div>
  58. </div>
  59. </template>
  60. <script>
  61. import IntersectionMap from '@/components/ui/IntersectionMapVideos.vue';
  62. import XgVideoPlayer from '@/components/ui/XgVideoPlayer.vue';
  63. import { apiGetSecurityRoutes, apiGetIntersectionData } from '@/api';
  64. export default {
  65. name: 'SecurityRoutePanelSwitch',
  66. components: {
  67. IntersectionMap,
  68. XgVideoPlayer,
  69. },
  70. data() {
  71. return {
  72. intersectionData: {},
  73. currentIndex: 0,
  74. pageSize: 3,
  75. routeList: []
  76. };
  77. },
  78. computed: {
  79. visibleRoutes() {
  80. return this.routeList.slice(this.currentIndex, this.currentIndex + this.pageSize);
  81. },
  82. canScrollLeft() {
  83. return this.currentIndex > 0;
  84. },
  85. canScrollRight() {
  86. return this.currentIndex < this.routeList.length - this.pageSize;
  87. }
  88. },
  89. async mounted() {
  90. this.routeList = await apiGetSecurityRoutes() || [];
  91. this.intersectionData = await apiGetIntersectionData() || {};
  92. },
  93. methods: {
  94. handlePrev() {
  95. if (this.canScrollLeft) {
  96. this.currentIndex--;
  97. }
  98. },
  99. handleNext() {
  100. if (this.canScrollRight) {
  101. this.currentIndex++;
  102. }
  103. }
  104. }
  105. };
  106. </script>
  107. <style scoped>
  108. /* ================== 整体面板 ================== */
  109. .security-route-panel {
  110. width: 100%;
  111. height: 100%;
  112. display: flex;
  113. flex-direction: column;
  114. box-sizing: border-box;
  115. padding: 5px;
  116. }
  117. /* ================== 顶部 Header ================== */
  118. .panel-header {
  119. display: flex;
  120. align-items: center;
  121. margin-bottom: 20px;
  122. padding-left: 30px;
  123. }
  124. .header-left {
  125. display: flex;
  126. align-items: center;
  127. gap: 12px;
  128. }
  129. .status-dot {
  130. width: 10px;
  131. height: 10px;
  132. background-color: #68e75f;
  133. border-radius: 50%;
  134. box-shadow: 0 0 8px rgba(104, 231, 95, 0.6);
  135. }
  136. .title-text {
  137. color: #ffffff;
  138. font-size: 16px;
  139. font-weight: bold;
  140. }
  141. .text-danger {
  142. color: #ff5252;
  143. font-size: 14px;
  144. }
  145. .btn-start {
  146. margin-left: 15px;
  147. background: rgba(40, 90, 180, 0.6);
  148. border: 1px solid #448aff;
  149. color: #fff;
  150. padding: 4px 16px;
  151. border-radius: 4px;
  152. cursor: pointer;
  153. transition: all 0.3s;
  154. }
  155. .btn-start:hover {
  156. background: rgba(40, 90, 180, 0.9);
  157. box-shadow: 0 0 10px rgba(68, 138, 255, 0.5);
  158. }
  159. /* ================== 轮播区容器 ================== */
  160. .carousel-wrapper {
  161. flex: 1;
  162. display: flex;
  163. align-items: center;
  164. gap: 15px;
  165. min-height: 0;
  166. }
  167. /* ================== 图片导航按钮统一样式 ================== */
  168. .nav-arrow {
  169. flex-shrink: 0;
  170. width: 30px;
  171. height: 30px;
  172. display: flex;
  173. justify-content: center;
  174. align-items: center;
  175. transition: all 0.3s;
  176. background: transparent;
  177. border: none;
  178. outline: none;
  179. }
  180. .arrow-img {
  181. width: 100%;
  182. height: 100%;
  183. object-fit: contain;
  184. transition: transform 0.2s ease, filter 0.3s ease;
  185. }
  186. .left-facing {
  187. transform: rotate(180deg);
  188. }
  189. .nav-arrow.is-active {
  190. cursor: pointer;
  191. }
  192. .nav-arrow.is-active:hover .arrow-img {
  193. transform: scale(1.15);
  194. filter: drop-shadow(0 0 10px rgba(0, 229, 255, 0.8));
  195. }
  196. .nav-arrow.left-arrow.is-active:hover .arrow-img.left-facing {
  197. transform: rotate(180deg) scale(1.15);
  198. }
  199. .nav-arrow.is-disabled {
  200. cursor: not-allowed;
  201. opacity: 0.5;
  202. }
  203. /* ================== 卡片列表视窗 ================== */
  204. .route-list-window {
  205. flex: 1;
  206. height: 100%;
  207. overflow: hidden;
  208. }
  209. .route-list-track {
  210. display: flex;
  211. height: 100%;
  212. gap: 15px;
  213. }
  214. /* 【核心修改】:删除了这里原本的 .fade-enter-active, .fade-leave-active 动画样式 */
  215. /* ================== 单个卡片样式 ================== */
  216. .route-card {
  217. flex: 1;
  218. display: flex;
  219. flex-direction: column;
  220. background: rgba(22, 34, 60, 0.6);
  221. border: 1px solid rgba(100, 150, 255, 0.2);
  222. border-radius: 6px;
  223. overflow: hidden;
  224. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
  225. }
  226. .card-top-video {
  227. width: 100%;
  228. height: 55%;
  229. background: #000;
  230. border-bottom: 1px solid rgba(100, 150, 255, 0.2);
  231. }
  232. .responsive-video {
  233. width: 100%;
  234. height: 100%;
  235. display: block;
  236. object-fit: cover;
  237. }
  238. .card-bottom-content {
  239. flex: 1;
  240. padding: 12px;
  241. display: flex;
  242. flex-direction: column;
  243. }
  244. .route-name {
  245. color: #fff;
  246. font-size: 14px;
  247. display: flex;
  248. align-items: center;
  249. margin-bottom: 12px;
  250. }
  251. .blue-dot {
  252. width: 8px;
  253. height: 8px;
  254. background-color: #448aff;
  255. border-radius: 50%;
  256. margin-right: 8px;
  257. box-shadow: 0 0 5px rgba(68, 138, 255, 0.8);
  258. }
  259. .map-and-info {
  260. display: flex;
  261. flex: 1;
  262. gap: 15px;
  263. }
  264. .map-container {
  265. width: 110px;
  266. height: 110px;
  267. flex-shrink: 0;
  268. border-radius: 4px;
  269. overflow: hidden;
  270. position: relative;
  271. }
  272. .info-action-box {
  273. flex: 1;
  274. display: flex;
  275. flex-direction: column;
  276. justify-content: space-between;
  277. }
  278. .info-list {
  279. display: flex;
  280. flex-direction: column;
  281. gap: 6px;
  282. font-size: 12px;
  283. color: #a0a5b0;
  284. }
  285. .text-green {
  286. color: #48c79c;
  287. }
  288. .btn-unlock {
  289. align-self: flex-start;
  290. background: rgba(40, 90, 180, 0.6);
  291. border: 1px solid #448aff;
  292. color: #fff;
  293. padding: 4px 12px;
  294. border-radius: 4px;
  295. cursor: pointer;
  296. font-size: 12px;
  297. transition: all 0.3s;
  298. }
  299. .btn-unlock:hover {
  300. background: rgba(40, 90, 180, 0.9);
  301. box-shadow: 0 0 8px rgba(68, 138, 255, 0.5);
  302. }
  303. </style>