浏览代码

优化路线指引页面

yawuga 1 天之前
父节点
当前提交
85b6781708
共有 3 个文件被更改,包括 1156 次插入225 次删除
  1. 38 0
      src/stores/navigation.js
  2. 520 64
      src/views/navigation/Index.vue
  3. 598 161
      src/views/navigation/Status.vue

+ 38 - 0
src/stores/navigation.js

@@ -70,10 +70,47 @@ export const useNavigationStore = defineStore('navigation', () => {
       }
       return res
     } catch (e) {
+      if (import.meta.env.DEV) {
+        setMockNavigationTask(destination)
+        return
+      }
       throw e
     }
   }
 
+  function setMockNavigationTask(destination) {
+    const dest = destination || {
+      destinationId: 'mock-front-desk',
+      name: '前台',
+      floor: '1楼',
+      description: '综合服务前台',
+      estimatedTime: '2分钟'
+    }
+    currentTask.value = {
+      taskId: `mock_nav_${Date.now()}`,
+      destinationId: dest.destinationId,
+      destinationName: dest.name,
+      targetName: dest.name,
+      name: dest.name,
+      floor: dest.floor,
+      description: dest.description,
+      estimatedTime: dest.estimatedTime || '2分钟',
+      status: 'navigating',
+      progress: 0,
+      startTime: new Date().toISOString()
+    }
+    navigationStatus.value = {
+      taskId: currentTask.value.taskId,
+      status: 'navigating',
+      targetName: dest.name,
+      floor: dest.floor,
+      description: dest.description,
+      progress: 0,
+      estimatedTime: dest.estimatedTime || '2分钟'
+    }
+    return currentTask.value
+  }
+
   async function fetchNavigationStatus() {
     if (!currentTask.value) return null
     try {
@@ -168,6 +205,7 @@ export const useNavigationStore = defineStore('navigation', () => {
     destinationsByCategory,
     fetchDestinations,
     startNavigation,
+    setMockNavigationTask,
     fetchNavigationStatus,
     cancelNavigation,
     addToHistory,

+ 520 - 64
src/views/navigation/Index.vue

@@ -1,42 +1,152 @@
 <template>
-  <ScreenLayout :show-back-btn="true" back-text="返回菜单" back-target="/menu">
-    <div class="page-navigation">
-      <h1 class="page-title">路线引导</h1>
-      <p class="page-subtitle">请选择您要去的地方</p>
-
-      <div class="destinations-grid">
-        <div
-          v-for="dest in destinations"
-          :key="dest.destinationId"
-          class="destination-card"
-          @click="selectDestination(dest)"
-        >
-          <div class="dest-icon" :style="{ background: getCategoryColor(dest.category) }">
-            <component :is="getCategoryIcon(dest.category)" />
+  <div class="navi-layout">
+    <!-- 顶部状态栏 -->
+    <StatusBar :title="robotName" />
+
+    <!-- 主内容区 -->
+    <div class="layout-main">
+      <div class="navi-page">
+        <!-- 动态背景层 -->
+        <div class="bg-layer">
+          <div class="bg-orb orb-1"></div>
+          <div class="bg-orb orb-2"></div>
+          <div class="bg-orb orb-3"></div>
+          <div class="bg-grid-overlay"></div>
+        </div>
+
+        <!-- 背景模糊遮罩(让主内容区更清晰) -->
+        <div class="content-shade"></div>
+
+        <!-- 内容区 -->
+        <div class="navi-content">
+          <!-- 标题区 -->
+          <div class="navi-hero">
+            <h1 class="page-title">路线引导</h1>
+            <p class="page-subtitle">请选择您要去的地方</p>
           </div>
-          <div class="dest-info">
-            <h3>{{ dest.name }}</h3>
-            <p>{{ dest.floor }} · {{ dest.description }}</p>
+
+          <!-- 2×2 点位卡片区 -->
+          <div
+            class="destinations-grid"
+            @touchstart="onTouchStart"
+            @touchend="onTouchEnd"
+          >
+            <div
+              v-for="dest in pagedDestinations"
+              :key="dest.destinationId"
+              class="destination-card"
+              @click="selectDestination(dest)"
+            >
+              <div class="dest-icon" :style="{ background: getCategoryColor(dest.category) }">
+                <component :is="getCategoryIcon(dest.category)" />
+              </div>
+              <div class="dest-info">
+                <h3>{{ dest.name }}</h3>
+                <p>{{ dest.floor }} · {{ dest.description }}</p>
+                <div class="dest-time">
+                  <span>约{{ dest.estimatedTime }}</span>
+                </div>
+              </div>
+            </div>
           </div>
-          <div class="dest-time">
-            <span>约{{ dest.estimatedTime }}</span>
+
+          <!-- 分页控件 -->
+          <div v-if="totalPages > 1" class="pager">
+            <button
+              class="pager-btn"
+              :disabled="currentPage === 0"
+              @click="prevPage"
+            >
+              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
+                <path d="M15 18l-6-6 6-6" />
+              </svg>
+              上一页
+            </button>
+            <div class="pager-dots">
+              <span
+                v-for="i in totalPages"
+                :key="i"
+                class="pager-dot"
+                :class="{ active: i - 1 === currentPage }"
+                @click="goToPage(i - 1)"
+              ></span>
+            </div>
+            <button
+              class="pager-btn"
+              :disabled="currentPage >= totalPages - 1"
+              @click="nextPage"
+            >
+              下一页
+              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
+                <path d="M9 18l6-6-6-6" />
+              </svg>
+            </button>
           </div>
         </div>
       </div>
     </div>
-  </ScreenLayout>
+
+    <!-- 左下角返回菜单按钮 -->
+    <button class="btn-back" @click="goMenu">
+      <svg class="back-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
+        <path d="M15 18l-6-6 6-6" />
+      </svg>
+      <span>返回菜单</span>
+    </button>
+  </div>
 </template>
 
 <script setup>
-import { ref, onMounted, h } from 'vue'
+import { ref, computed, onMounted, h } from 'vue'
 import { useRouter } from 'vue-router'
 import { useNavigationStore } from '@/stores/navigation'
-import ScreenLayout from '@/layouts/ScreenLayout.vue'
+import { useScreenStore } from '@/stores/screen'
+import StatusBar from '@/components/StatusBar.vue'
 
 const router = useRouter()
 const navigationStore = useNavigationStore()
+const screenStore = useScreenStore()
+
+const robotName = computed(() => screenStore.robotName || '智能迎宾机器人')
 
 const destinations = ref([])
+const pageSize = 4
+const currentPage = ref(0)
+
+const totalPages = computed(() => Math.ceil(destinations.value.length / pageSize))
+const pagedDestinations = computed(() => {
+  const start = currentPage.value * pageSize
+  return destinations.value.slice(start, start + pageSize)
+})
+
+const prevPage = () => {
+  if (currentPage.value > 0) currentPage.value--
+}
+
+const nextPage = () => {
+  if (currentPage.value < totalPages.value - 1) currentPage.value++
+}
+
+const goToPage = (i) => {
+  currentPage.value = i
+}
+
+const goMenu = () => {
+  router.push('/menu')
+}
+
+// 触摸滑动
+let touchStartX = 0
+const onTouchStart = (e) => {
+  touchStartX = e.touches[0].clientX
+}
+const onTouchEnd = (e) => {
+  const delta = e.changedTouches[0].clientX - touchStartX
+  if (Math.abs(delta) > 50) {
+    if (delta < 0) nextPage()
+    else prevPage()
+  }
+}
 
 // 图标组件
 const ReceptionIcon = () => h('svg', { viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2' }, [
@@ -107,8 +217,21 @@ const getCategoryColor = (category) => {
 }
 
 const selectDestination = async (dest) => {
-  await navigationStore.startNavigation(dest)
-  router.push('/navigation/status')
+  try {
+    console.log('[Navigation] select destination:', dest)
+    await navigationStore.startNavigation(dest)
+    console.log('[Navigation] startNavigation success:', navigationStore.currentTask)
+    router.push('/navigation/status')
+  } catch (error) {
+    console.error('[Navigation] startNavigation failed:', error)
+    if (import.meta.env.DEV) {
+      console.warn('[Navigation] DEV: creating mock navigation task')
+      navigationStore.setMockNavigationTask(dest)
+      router.push('/navigation/status')
+      return
+    }
+    alert('路线引导功能暂不可用,请稍后再试')
+  }
 }
 
 onMounted(async () => {
@@ -118,105 +241,438 @@ onMounted(async () => {
 </script>
 
 <style scoped>
-.page-navigation {
-  width: 100%;
+/* ===== 布局结构 ===== */
+.navi-layout {
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  background: linear-gradient(155deg, #e8f4fd 0%, #dbeafe 40%, #eff6ff 100%);
+}
+
+.layout-main {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.navi-page {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  overflow: hidden;
+}
+
+/* ===== 动态背景层 ===== */
+.bg-layer {
+  position: absolute;
+  inset: 0;
+  overflow: hidden;
+  pointer-events: none;
+  z-index: 0;
+}
+
+.bg-orb {
+  position: absolute;
+  border-radius: 50%;
+  filter: blur(80px);
+  opacity: 0.45;
+}
+
+.orb-1 {
+  width: 480px;
+  height: 480px;
+  background: rgba(47, 142, 229, 0.28);
+  top: -180px;
+  left: -140px;
+  animation: float1 12s ease-in-out infinite;
+}
+
+.orb-2 {
+  width: 360px;
+  height: 360px;
+  background: rgba(32, 183, 199, 0.22);
+  bottom: -100px;
+  right: -80px;
+  animation: float2 15s ease-in-out infinite;
+}
+
+.orb-3 {
+  width: 280px;
+  height: 280px;
+  background: rgba(139, 92, 246, 0.18);
+  top: 50%;
+  right: 15%;
+  animation: float3 18s ease-in-out infinite;
+}
+
+@keyframes float1 {
+  0%, 100% { transform: translate(-50%, -50%) scale(1); }
+  50% { transform: translate(-40%, -60%) scale(1.08); }
+}
+
+@keyframes float2 {
+  0%, 100% { transform: translate(50%, 50%) scale(1); }
+  50% { transform: translate(60%, 40%) scale(1.06); }
+}
+
+@keyframes float3 {
+  0%, 100% { transform: translate(0, 0) scale(1); }
+  50% { transform: translate(-5%, 8%) scale(1.1); }
+}
+
+.bg-grid-overlay {
+  position: absolute;
+  inset: 0;
+  background-image: radial-gradient(circle, rgba(47, 142, 229, 0.08) 1px, transparent 1px);
+  background-size: 32px 32px;
+}
+
+.content-shade {
+  position: absolute;
+  inset: 0;
+  background: rgba(255, 255, 255, 0.18);
+  z-index: 0;
+  pointer-events: none;
+}
+
+/* ===== 内容区 ===== */
+.navi-content {
   height: 100%;
   display: flex;
   flex-direction: column;
-  padding: 20px 0;
+  align-items: center;
+  padding: 0 36px 16px;
+  z-index: 1;
+  overflow: hidden;
 }
 
-.page-title {
-  font-size: 32px;
-  font-weight: 600;
-  color: var(--text-primary);
+/* ===== 标题区 ===== */
+.navi-hero {
   text-align: center;
+  margin: 46px 0 28px;
+  flex-shrink: 0;
+}
+
+.page-title {
+  font-size: 42px;
+  font-weight: 900;
+  color: #111827;
   margin: 0;
+  letter-spacing: 2px;
+  line-height: 1.2;
 }
 
 .page-subtitle {
-  font-size: 18px;
-  color: var(--text-muted);
-  text-align: center;
-  margin: 8px 0 32px;
+  font-size: 22px;
+  color: #6b7280;
+  margin: 6px 0 0;
 }
 
+/* ===== 2×2 点位卡片网格 ===== */
 .destinations-grid {
   flex: 1;
   display: grid;
   grid-template-columns: repeat(2, 1fr);
-  gap: 20px;
-  overflow-y: auto;
-  padding: 0 16px;
+  grid-template-rows: repeat(2, minmax(0, 1fr));
+  gap: 20px 24px;
+  width: min(1240px, calc(100vw - 72px));
+  max-height: 560px;
+  min-height: 500px;
 }
 
 .destination-card {
   display: flex;
   align-items: center;
-  gap: 16px;
-  padding: 20px;
-  background: var(--bg-card);
-  border-radius: var(--radius-lg);
-  box-shadow: var(--shadow-sm);
+  gap: 26px;
+  padding: 36px 42px;
+  background: rgba(255, 255, 255, 0.88);
+  backdrop-filter: blur(12px);
+  -webkit-backdrop-filter: blur(12px);
+  border-radius: 28px;
+  box-shadow: 0 8px 28px rgba(47, 142, 229, 0.1), 0 2px 8px rgba(0, 0, 0, 0.05);
   cursor: pointer;
-  transition: all var(--transition-fast);
+  transition: transform 0.2s ease, box-shadow 0.2s ease;
+  box-sizing: border-box;
 }
 
 .destination-card:hover {
   transform: translateY(-4px);
-  box-shadow: var(--shadow-lg);
+  box-shadow: 0 18px 40px rgba(47, 142, 229, 0.16), 0 4px 12px rgba(0, 0, 0, 0.08);
 }
 
 .destination-card:active {
-  transform: translateY(-2px);
+  transform: scale(0.97);
 }
 
 .dest-icon {
-  width: 56px;
-  height: 56px;
+  width: 92px;
+  height: 92px;
   display: flex;
   align-items: center;
   justify-content: center;
-  border-radius: 14px;
+  border-radius: 24px;
   color: white;
   flex-shrink: 0;
 }
 
 .dest-icon svg {
-  width: 28px;
-  height: 28px;
+  width: 46px;
+  height: 46px;
 }
 
 .dest-info {
   flex: 1;
   min-width: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
 }
 
 .dest-info h3 {
-  font-size: 20px;
-  font-weight: 600;
-  color: var(--text-primary);
-  margin: 0 0 4px;
+  font-size: 38px;
+  font-weight: 900;
+  color: #111827;
+  margin: 0 0 6px;
+  line-height: 1.15;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
 .dest-info p {
-  font-size: 14px;
-  color: var(--text-muted);
-  margin: 0;
+  font-size: 23px;
+  color: #6b7280;
+  margin: 0 0 12px;
+  line-height: 1.4;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
 .dest-time {
-  flex-shrink: 0;
+  margin-top: 0;
 }
 
 .dest-time span {
-  font-size: 14px;
-  color: var(--primary);
-  padding: 6px 12px;
-  background: var(--primary-soft);
-  border-radius: var(--radius-full);
+  display: inline-block;
+  font-size: 20px;
+  color: #2f8ee5;
+  padding: 10px 16px;
+  background: rgba(47, 142, 229, 0.1);
+  border-radius: 999px;
+  white-space: nowrap;
+  font-weight: 700;
+}
+
+/* ===== 分页控件 ===== */
+.pager {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 18px;
+  margin-top: 14px;
+  flex-shrink: 0;
+  height: 66px;
+}
+
+.pager-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  height: 64px;
+  min-width: 132px;
+  padding: 0 28px;
+  background: rgba(255, 255, 255, 0.88);
+  backdrop-filter: blur(10px);
+  border: 2px solid rgba(47, 142, 229, 0.2);
+  border-radius: 999px;
+  color: #2f8ee5;
+  font-size: 22px;
+  font-weight: 700;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  box-sizing: border-box;
+}
+
+.pager-btn svg {
+  width: 24px;
+  height: 24px;
+}
+
+.pager-btn:hover:not(:disabled) {
+  background: rgba(255, 255, 255, 0.98);
+  border-color: #2f8ee5;
+  transform: translateY(-1px);
+  box-shadow: 0 8px 24px rgba(47, 142, 229, 0.22);
+}
+
+.pager-btn:active:not(:disabled) {
+  transform: scale(0.97);
+}
+
+.pager-btn:disabled {
+  opacity: 0.35;
+  cursor: not-allowed;
+}
+
+.pager-dots {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.pager-dot {
+  width: 14px;
+  height: 14px;
+  border-radius: 50%;
+  background: rgba(47, 142, 229, 0.2);
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.pager-dot.active {
+  background: #2f8ee5;
+  width: 38px;
+  border-radius: 999px;
+}
+
+.pager-dot:hover:not(.active) {
+  background: rgba(47, 142, 229, 0.4);
+}
+
+/* ===== 左下角返回菜单按钮 ===== */
+.btn-back {
+  position: fixed;
+  left: 24px;
+  bottom: 28px;
+  z-index: 100;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  height: 66px;
+  padding: 0 28px;
+  background: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(10px);
+  -webkit-backdrop-filter: blur(10px);
+  border: 2px solid rgba(148, 163, 184, 0.22);
+  border-radius: 999px;
+  color: #374151;
+  font-size: 22px;
+  font-weight: 800;
+  cursor: pointer;
+  letter-spacing: 1px;
+  transition: all 0.2s ease;
+  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.1);
+  box-sizing: border-box;
+}
+
+.btn-back:hover,
+.btn-back:focus-visible {
+  color: #2f8ee5;
+  border-color: rgba(47, 142, 229, 0.36);
+  background: rgba(255, 255, 255, 0.98);
+  transform: translateY(-2px);
+  box-shadow: 0 10px 30px rgba(47, 142, 229, 0.18);
+}
+
+.btn-back:active {
+  transform: scale(0.97);
+}
+
+.back-icon {
+  width: 22px;
+  height: 22px;
+  flex-shrink: 0;
+}
+
+/* ===== 响应式适配 ===== */
+@media (max-height: 700px) {
+  .navi-hero {
+    margin: 8px 0 12px;
+  }
+
+  .page-title {
+    font-size: 36px;
+  }
+
+  .page-subtitle {
+    font-size: 18px;
+    margin-top: 4px;
+  }
+
+  .destinations-grid {
+    gap: 14px 18px;
+    min-height: 460px;
+    max-height: 490px;
+  }
+
+  .destination-card {
+    padding: 26px 32px;
+  }
+
+  .dest-icon {
+    width: 76px;
+    height: 76px;
+    border-radius: 20px;
+  }
+
+  .dest-icon svg {
+    width: 38px;
+    height: 38px;
+  }
+
+  .dest-info h3 {
+    font-size: 30px;
+  }
+
+  .dest-info p {
+    font-size: 18px;
+    margin-bottom: 10px;
+  }
+
+  .dest-time span {
+    font-size: 17px;
+    padding: 8px 13px;
+  }
+
+  .pager {
+    margin-top: 10px;
+    gap: 14px;
+    height: 60px;
+  }
+
+  .pager-btn {
+    height: 58px;
+    min-width: 118px;
+    padding: 0 22px;
+    font-size: 20px;
+  }
+
+  .pager-btn svg {
+    width: 22px;
+    height: 22px;
+  }
+
+  .pager-dot {
+    width: 13px;
+    height: 13px;
+  }
+
+  .pager-dot.active {
+    width: 34px;
+  }
+
+  .btn-back {
+    height: 58px;
+    padding: 0 22px;
+    font-size: 20px;
+    left: 20px;
+    bottom: 22px;
+  }
 }
 </style>

+ 598 - 161
src/views/navigation/Status.vue

@@ -1,59 +1,110 @@
 <template>
-  <ScreenLayout :show-back-btn="false">
-    <div class="page-status">
-      <!-- 导航状态 -->
-      <div class="nav-container">
-        <div class="nav-header">
-          <h1>导航中</h1>
-          <p>目的地:{{ targetName }}</p>
+  <div class="status-layout">
+    <!-- 顶部状态栏 -->
+    <StatusBar :title="robotName" />
+
+    <!-- 主内容区 -->
+    <div class="layout-main">
+      <div class="status-page">
+        <!-- 动态背景层 -->
+        <div class="bg-layer">
+          <div class="bg-orb orb-1"></div>
+          <div class="bg-orb orb-2"></div>
+          <div class="bg-orb orb-3"></div>
+          <div class="bg-grid-overlay"></div>
         </div>
 
-        <!-- 机器人动画 -->
-        <div class="robot-animation">
-          <div class="robot-icon" :class="{ moving: isMoving }">
-            <svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
-              <circle cx="40" cy="28" r="16" fill="currentColor" />
-              <rect x="20" y="48" width="40" height="24" rx="8" fill="currentColor" />
-              <circle cx="32" cy="24" r="3" fill="white" class="eye-left" />
-              <circle cx="48" cy="24" r="3" fill="white" class="eye-right" />
-              <rect x="60" y="56" width="16" height="4" rx="2" fill="currentColor" class="wheel" />
-              <rect x="4" y="56" width="16" height="4" rx="2" fill="currentColor" class="wheel" />
-            </svg>
-          </div>
-
-          <div class="path-line">
-            <div class="path-progress" :style="{ width: progress + '%' }"></div>
-          </div>
+        <!-- 背景模糊遮罩 -->
+        <div class="content-shade"></div>
 
-          <div class="target-icon">
-            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-              <circle cx="12" cy="12" r="10" />
-              <circle cx="12" cy="12" r="6" />
-              <circle cx="12" cy="12" r="2" />
-            </svg>
+        <!-- 内容区 -->
+        <div class="status-content">
+          <!-- 标题区 -->
+          <div class="status-hero">
+            <h1 class="page-title">导航中</h1>
+            <p class="page-subtitle">正在前往:{{ targetName }}</p>
           </div>
-        </div>
-
-        <!-- 状态消息 -->
-        <div class="status-message" :class="statusClass">
-          <span class="message-text">{{ statusMessage }}</span>
-        </div>
 
-        <!-- 进度 -->
-        <div class="progress-info">
-          <div class="progress-bar">
-            <div class="progress-fill" :style="{ width: progress + '%' }"></div>
+          <!-- 导航状态大卡片 -->
+          <div class="nav-container">
+            <!-- 顶部状态提示 -->
+            <div class="status-banner" :class="statusClass">
+              <span class="banner-text">{{ statusMessage }}</span>
+            </div>
+
+            <!-- 路线状态:机器人 + 路径线 + 终点 -->
+            <div class="route-track">
+              <!-- 机器人 -->
+              <div class="route-robot" :class="{ moving: isMoving }">
+                <svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
+                  <circle cx="40" cy="28" r="16" fill="currentColor" />
+                  <rect x="20" y="48" width="40" height="24" rx="8" fill="currentColor" />
+                  <circle cx="32" cy="24" r="3" fill="white" class="eye-left" />
+                  <circle cx="48" cy="24" r="3" fill="white" class="eye-right" />
+                  <rect x="60" y="56" width="16" height="4" rx="2" fill="currentColor" class="wheel" />
+                  <rect x="4" y="56" width="16" height="4" rx="2" fill="currentColor" class="wheel" />
+                </svg>
+              </div>
+
+              <!-- 路径条 -->
+              <div class="route-line">
+                <div class="route-progress" :style="{ width: displayProgress + '%' }"></div>
+              </div>
+
+              <!-- 终点图标 -->
+              <div class="route-end">
+                <svg viewBox="0 0 24 24" fill="currentColor">
+                  <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
+                </svg>
+              </div>
+            </div>
+
+            <!-- 目的地信息三列卡片 -->
+            <div class="info-panel">
+              <div class="info-item">
+                <div class="info-label">目的地</div>
+                <div class="info-value">{{ targetName }}</div>
+              </div>
+              <div class="info-item">
+                <div class="info-label">位置</div>
+                <div class="info-value">{{ targetFloor }} · {{ targetDescription }}</div>
+              </div>
+              <div class="info-item">
+                <div class="info-label">预计用时</div>
+                <div class="info-value">{{ estimatedTime }}</div>
+              </div>
+            </div>
+
+            <!-- 进度区 -->
+            <div class="progress-section">
+              <div class="progress-header">
+                <span class="progress-label">导航进度</span>
+                <span class="progress-value">{{ displayProgress }}%</span>
+              </div>
+              <div class="progress-track">
+                <div class="progress-bar">
+                  <div class="progress-fill" :style="{ width: displayProgress + '%' }"></div>
+                </div>
+              </div>
+            </div>
+
+            <!-- 取消按钮 -->
+            <button class="btn-cancel" @click="handleCancel">
+              取消导航
+            </button>
           </div>
-          <span class="progress-text">{{ progress }}%</span>
         </div>
-
-        <!-- 取消按钮 -->
-        <button class="btn-cancel" @click="handleCancel">
-          取消导航
-        </button>
       </div>
     </div>
-  </ScreenLayout>
+
+    <!-- 左下角返回按钮 -->
+    <button class="btn-back" @click="handleCancel">
+      <svg class="back-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
+        <path d="M19 12H5M12 5l-7 7 7 7" />
+      </svg>
+      返回路线引导
+    </button>
+  </div>
 </template>
 
 <script setup>
@@ -61,7 +112,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { useNavigationStore } from '@/stores/navigation'
 import { useScreenStore } from '@/stores/screen'
-import ScreenLayout from '@/layouts/ScreenLayout.vue'
+import StatusBar from '@/components/StatusBar.vue'
 
 const router = useRouter()
 const navigationStore = useNavigationStore()
@@ -70,44 +121,110 @@ const screenStore = useScreenStore()
 const progress = ref(0)
 const isMoving = ref(true)
 
-const targetName = computed(() => navigationStore.navigationStatus.targetName || '未知地点')
+const robotName = computed(() => screenStore.screenTheme?.robotName || screenStore.robotName || '迎宾巡逻机器人')
+
+const displayProgress = computed(() => Math.round(progress.value))
+
+const targetName = computed(() => {
+  return navigationStore.navigationStatus?.targetName
+    || navigationStore.currentTask?.targetName
+    || navigationStore.currentTask?.destinationName
+    || navigationStore.currentTask?.name
+    || '前台'
+})
+
+const targetFloor = computed(() => {
+  return navigationStore.currentTask?.floor
+    || navigationStore.navigationStatus?.floor
+    || '1楼'
+})
+
+const targetDescription = computed(() => {
+  return navigationStore.currentTask?.description
+    || navigationStore.navigationStatus?.description
+    || '综合服务前台'
+})
+
+const estimatedTime = computed(() => {
+  const t = navigationStore.currentTask?.estimatedTime
+    || navigationStore.navigationStatus?.estimatedTime
+    || '约2分钟'
+  return t
+})
 
 const statusMessage = computed(() => {
-  const p = progress.value
-  if (p === 0) return '正在规划路线...'
-  if (p < 30) return '开始带路,请跟我来'
-  if (p < 60) return '前方直行'
-  if (p < 90) return '即将到达'
-  if (p === 100) return '已到达目的地'
-  return '导航中'
+  const p = displayProgress.value
+  if (p === 0) return '正在规划路线'
+  if (p < 30) return '请跟随机器人前进'
+  if (p < 60) return '正在前往目的地'
+  if (p < 90) return '即将到达,请继续跟随'
+  if (p < 100) return '即将到达目的地'
+  return '已到达目的地'
 })
 
 const statusClass = computed(() => {
-  if (progress.value === 100) return 'status-arrived'
-  if (progress.value >= 90) return 'status-near'
-  return 'status-moving'
+  if (displayProgress.value === 100) return 'banner-arrived'
+  if (displayProgress.value >= 90) return 'banner-near'
+  return 'banner-moving'
 })
 
-let timer = null
+const isMock = ref(false)
+
+let progressTimer = null
+let alertTimer = null
+let finishTimer = null
+
+const createMockTask = () => {
+  const mockTask = {
+    taskId: `mock_nav_${Date.now()}`,
+    targetName: '前台',
+    destinationName: '前台',
+    name: '前台',
+    floor: '1楼',
+    description: '综合服务前台',
+    estimatedTime: '约2分钟',
+    status: 'navigating'
+  }
+  navigationStore.currentTask = mockTask
+  navigationStore.navigationStatus = {
+    taskId: mockTask.taskId,
+    status: 'navigating',
+    targetName: mockTask.name,
+    progress: 0,
+    estimatedTime: mockTask.estimatedTime
+  }
+  isMock.value = true
+}
+
+const clearAllTimers = () => {
+  if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
+  if (alertTimer) { clearTimeout(alertTimer); alertTimer = null }
+  if (finishTimer) { clearTimeout(finishTimer); finishTimer = null }
+}
 
 const startNavigation = () => {
-  timer = setInterval(() => {
+  progress.value = 0
+  isMoving.value = true
+
+  progressTimer = setInterval(() => {
     if (progress.value < 100) {
       progress.value += Math.random() * 5 + 2
       if (progress.value >= 100) {
         progress.value = 100
         isMoving.value = false
-        clearInterval(timer)
-        // 到达后延迟返回
-        setTimeout(() => {
+        clearInterval(progressTimer)
+        progressTimer = null
+
+        alertTimer = setTimeout(() => {
           screenStore.showAlert({
             type: 'success',
             message: `已到达${targetName.value}`,
-            duration: 3000
+            duration: import.meta.env.DEV ? 10000 : 3000
           })
-          setTimeout(() => {
-            handleFinish()
-          }, 3000)
+
+          if (!import.meta.env.DEV) {
+            finishTimer = setTimeout(handleFinish, 3000)
+          }
         }, 1000)
       }
     }
@@ -115,98 +232,253 @@ const startNavigation = () => {
 }
 
 const handleCancel = async () => {
-  if (timer) clearInterval(timer)
+  clearAllTimers()
   try {
     await navigationStore.cancelNavigation()
-    router.push('/navigation')
   } catch {
-    router.push('/navigation')
+    // ignore
   }
+  router.push('/navigation')
 }
 
 const handleFinish = () => {
+  clearAllTimers()
   navigationStore.clearNavigation()
   router.push('/idle')
 }
 
 onMounted(() => {
   if (!navigationStore.currentTask) {
-    router.push('/navigation')
-    return
+    if (import.meta.env.DEV) {
+      createMockTask()
+    } else {
+      router.push('/navigation')
+      return
+    }
   }
   startNavigation()
 })
 
 onUnmounted(() => {
-  if (timer) clearInterval(timer)
+  clearAllTimers()
 })
 </script>
 
 <style scoped>
-.page-status {
-  width: 100%;
+/* ===== 布局结构 ===== */
+.status-layout {
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  background: linear-gradient(155deg, #e8f4fd 0%, #dbeafe 40%, #eff6ff 100%);
+}
+
+.layout-main {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.status-page {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  overflow: hidden;
+}
+
+/* ===== 动态背景层 ===== */
+.bg-layer {
+  position: absolute;
+  inset: 0;
+  overflow: hidden;
+  pointer-events: none;
+  z-index: 0;
+}
+
+.bg-orb {
+  position: absolute;
+  border-radius: 50%;
+  filter: blur(80px);
+  opacity: 0.45;
+}
+
+.orb-1 {
+  width: 480px;
+  height: 480px;
+  background: rgba(47, 142, 229, 0.28);
+  top: -180px;
+  left: -140px;
+  animation: float1 12s ease-in-out infinite;
+}
+
+.orb-2 {
+  width: 360px;
+  height: 360px;
+  background: rgba(32, 183, 199, 0.22);
+  bottom: -100px;
+  right: -80px;
+  animation: float2 15s ease-in-out infinite;
+}
+
+.orb-3 {
+  width: 280px;
+  height: 280px;
+  background: rgba(139, 92, 246, 0.18);
+  top: 50%;
+  right: 15%;
+  animation: float3 18s ease-in-out infinite;
+}
+
+@keyframes float1 {
+  0%, 100% { transform: translate(-50%, -50%) scale(1); }
+  50% { transform: translate(-40%, -60%) scale(1.08); }
+}
+
+@keyframes float2 {
+  0%, 100% { transform: translate(50%, 50%) scale(1); }
+  50% { transform: translate(60%, 40%) scale(1.06); }
+}
+
+@keyframes float3 {
+  0%, 100% { transform: translate(0, 0) scale(1); }
+  50% { transform: translate(-5%, 8%) scale(1.1); }
+}
+
+.bg-grid-overlay {
+  position: absolute;
+  inset: 0;
+  background-image: radial-gradient(circle, rgba(47, 142, 229, 0.08) 1px, transparent 1px);
+  background-size: 32px 32px;
+}
+
+.content-shade {
+  position: absolute;
+  inset: 0;
+  background: rgba(255, 255, 255, 0.18);
+  z-index: 0;
+  pointer-events: none;
+}
+
+/* ===== 内容区 ===== */
+.status-content {
   height: 100%;
   display: flex;
+  flex-direction: column;
   align-items: center;
-  justify-content: center;
-  background: linear-gradient(135deg, #e9f8ff 0%, #dff8f5 50%, #f7fbff 100%);
+  padding: 0 36px 24px;
+  z-index: 1;
+  overflow: hidden;
 }
 
+/* ===== 标题区 ===== */
+.status-hero {
+  text-align: center;
+  margin: 28px 0 16px;
+  flex-shrink: 0;
+}
+
+.page-title {
+  font-size: 46px;
+  font-weight: 900;
+  color: #111827;
+  margin: 0;
+  letter-spacing: 2px;
+  line-height: 1.2;
+}
+
+.page-subtitle {
+  font-size: 24px;
+  color: #6b7280;
+  margin: 8px 0 0;
+}
+
+/* ===== 导航状态大卡片 ===== */
 .nav-container {
-  width: 100%;
-  max-width: 600px;
-  padding: 40px;
+  flex: 1;
   display: flex;
   flex-direction: column;
   align-items: center;
-  gap: 32px;
+  justify-content: space-between;
+  gap: 20px;
+  width: min(920px, calc(100vw - 120px));
+  max-width: 920px;
+  padding: 32px 48px;
+  background: rgba(255, 255, 255, 0.88);
+  backdrop-filter: blur(12px);
+  -webkit-backdrop-filter: blur(12px);
+  border-radius: 36px;
+  box-shadow: 0 12px 40px rgba(47, 142, 229, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06);
+  box-sizing: border-box;
 }
 
-.nav-header {
+/* ===== 顶部状态提示 ===== */
+.status-banner {
+  width: 100%;
+  padding: 16px 32px;
+  border-radius: 20px;
   text-align: center;
+  flex-shrink: 0;
 }
 
-.nav-header h1 {
-  font-size: 36px;
-  font-weight: 600;
-  color: var(--text-primary);
-  margin: 0 0 8px;
+.banner-moving {
+  background: rgba(47, 142, 229, 0.08);
 }
 
-.nav-header p {
-  font-size: 18px;
-  color: var(--text-secondary);
-  margin: 0;
+.banner-near {
+  background: rgba(245, 158, 11, 0.1);
+}
+
+.banner-arrived {
+  background: rgba(16, 185, 129, 0.1);
 }
 
-.robot-animation {
+.banner-text {
+  font-size: 30px;
+  font-weight: 800;
+  color: #111827;
+}
+
+.banner-near .banner-text {
+  color: #d97706;
+}
+
+.banner-arrived .banner-text {
+  color: #10b981;
+}
+
+/* ===== 路线状态:机器人 + 路径 + 终点 ===== */
+.route-track {
   position: relative;
   width: 100%;
   height: 120px;
   display: flex;
   align-items: center;
-  justify-content: center;
 }
 
-.robot-icon {
-  width: 80px;
-  height: 80px;
-  color: var(--primary);
+.route-robot {
+  width: 100px;
+  height: 100px;
+  color: #2f8ee5;
   z-index: 2;
+  flex-shrink: 0;
   transition: transform 0.5s ease;
 }
 
-.robot-icon.moving {
+.route-robot.moving {
   animation: walkBounce 0.5s ease-in-out infinite;
 }
 
-.robot-icon svg {
+.route-robot svg {
   width: 100%;
   height: 100%;
 }
 
-.robot-icon .eye-left,
-.robot-icon .eye-right {
+.route-robot .eye-left,
+.route-robot .eye-right {
   animation: blink 3s ease-in-out infinite;
 }
 
@@ -221,106 +493,271 @@ onUnmounted(() => {
   95% { transform: scaleY(0.1); }
 }
 
-.path-line {
-  position: absolute;
-  left: 10%;
-  right: 10%;
-  height: 8px;
-  background: var(--border-light);
-  border-radius: 4px;
+.route-line {
+  flex: 1;
+  height: 14px;
+  background: rgba(47, 142, 229, 0.1);
+  border-radius: 999px;
   overflow: hidden;
+  margin: 0 12px;
 }
 
-.path-progress {
+.route-progress {
   height: 100%;
-  background: linear-gradient(90deg, var(--primary), var(--secondary));
-  border-radius: 4px;
-  transition: width 0.3s ease;
+  background: linear-gradient(90deg, #2f8ee5, #20b7c7);
+  border-radius: 999px;
+  transition: width 0.4s ease;
 }
 
-.target-icon {
-  position: absolute;
-  right: 8%;
-  width: 32px;
-  height: 32px;
-  color: var(--success);
+.route-end {
+  width: 52px;
+  height: 52px;
+  color: #10b981;
+  flex-shrink: 0;
 }
 
-.target-icon svg {
+.route-end svg {
   width: 100%;
   height: 100%;
-  animation: pulse 1.5s ease-in-out infinite;
+  animation: endPulse 1.5s ease-in-out infinite;
 }
 
-@keyframes pulse {
+@keyframes endPulse {
   0%, 100% { transform: scale(1); opacity: 1; }
-  50% { transform: scale(1.2); opacity: 0.7; }
+  50% { transform: scale(1.12); opacity: 0.75; }
 }
 
-.status-message {
-  padding: 16px 32px;
-  background: var(--bg-card);
-  border-radius: var(--radius-full);
-  box-shadow: var(--shadow-md);
+/* ===== 目的地信息三列卡片 ===== */
+.info-panel {
+  width: 100%;
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 14px;
+  flex-shrink: 0;
 }
 
-.message-text {
-  font-size: 20px;
-  font-weight: 500;
-  color: var(--text-primary);
+.info-item {
+  background: rgba(47, 142, 229, 0.06);
+  border-radius: 18px;
+  padding: 16px 18px;
+  box-sizing: border-box;
 }
 
-.status-arrived .message-text {
-  color: var(--success);
+.info-label {
+  font-size: 18px;
+  color: #6b7280;
+  margin-bottom: 6px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
-.status-near .message-text {
-  color: var(--primary);
+.info-value {
+  font-size: 24px;
+  font-weight: 800;
+  color: #111827;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
-.progress-info {
+/* ===== 进度区 ===== */
+.progress-section {
   width: 100%;
+  flex-shrink: 0;
+}
+
+.progress-header {
   display: flex;
+  justify-content: space-between;
   align-items: center;
-  gap: 16px;
+  margin-bottom: 10px;
+}
+
+.progress-label {
+  font-size: 20px;
+  color: #6b7280;
+  font-weight: 600;
+}
+
+.progress-value {
+  font-size: 26px;
+  font-weight: 800;
+  color: #2f8ee5;
+}
+
+.progress-track {
+  width: 100%;
 }
 
 .progress-bar {
-  flex: 1;
-  height: 12px;
-  background: var(--border-light);
-  border-radius: 6px;
+  height: 18px;
+  background: rgba(47, 142, 229, 0.1);
+  border-radius: 999px;
   overflow: hidden;
 }
 
 .progress-fill {
   height: 100%;
-  background: linear-gradient(90deg, var(--primary), var(--secondary));
-  border-radius: 6px;
-  transition: width 0.3s ease;
-}
-
-.progress-text {
-  font-size: 18px;
-  font-weight: 600;
-  color: var(--primary);
-  min-width: 50px;
-  text-align: right;
+  background: linear-gradient(90deg, #2f8ee5, #20b7c7);
+  border-radius: 999px;
+  transition: width 0.4s ease;
 }
 
+/* ===== 取消导航按钮 ===== */
 .btn-cancel {
-  padding: 16px 48px;
-  font-size: 18px;
-  color: var(--text-secondary);
-  background: var(--bg-card);
-  border: 2px solid var(--border-light);
-  border-radius: var(--radius-full);
+  height: 72px;
+  padding: 0 56px;
+  font-size: 24px;
+  font-weight: 800;
+  color: #6b7280;
+  background: rgba(255, 255, 255, 0.9);
+  border: 2px solid rgba(148, 163, 184, 0.3);
+  border-radius: 999px;
   cursor: pointer;
-  transition: all var(--transition-fast);
+  transition: all 0.2s ease;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+  box-sizing: border-box;
+  flex-shrink: 0;
 }
 
 .btn-cancel:hover {
-  border-color: var(--danger);
-  color: var(--danger);
+  border-color: #ef4444;
+  color: #ef4444;
+  box-shadow: 0 6px 20px rgba(239, 68, 68, 0.15);
+  transform: translateY(-2px);
+}
+
+.btn-cancel:active {
+  transform: scale(0.97);
+}
+
+/* ===== 左下角返回按钮 ===== */
+.btn-back {
+  position: fixed;
+  left: 24px;
+  bottom: 28px;
+  z-index: 100;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  height: 66px;
+  padding: 0 28px;
+  background: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(10px);
+  -webkit-backdrop-filter: blur(10px);
+  border: 2px solid rgba(148, 163, 184, 0.22);
+  border-radius: 999px;
+  color: #374151;
+  font-size: 22px;
+  font-weight: 800;
+  cursor: pointer;
+  letter-spacing: 1px;
+  transition: all 0.2s ease;
+  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.1);
+  box-sizing: border-box;
+}
+
+.btn-back:hover,
+.btn-back:focus-visible {
+  color: #2f8ee5;
+  border-color: rgba(47, 142, 229, 0.36);
+  background: rgba(255, 255, 255, 0.98);
+  transform: translateY(-2px);
+  box-shadow: 0 10px 30px rgba(47, 142, 229, 0.18);
+}
+
+.btn-back:active {
+  transform: scale(0.97);
+}
+
+.back-icon {
+  width: 22px;
+  height: 22px;
+  flex-shrink: 0;
+}
+
+/* ===== 响应式适配 ===== */
+@media (max-height: 700px) {
+  .status-hero {
+    margin: 18px 0 12px;
+  }
+
+  .page-title {
+    font-size: 38px;
+  }
+
+  .page-subtitle {
+    font-size: 20px;
+  }
+
+  .nav-container {
+    gap: 14px;
+    padding: 24px 36px;
+  }
+
+  .status-banner {
+    padding: 12px 24px;
+  }
+
+  .banner-text {
+    font-size: 24px;
+  }
+
+  .route-track {
+    height: 100px;
+  }
+
+  .route-robot {
+    width: 80px;
+    height: 80px;
+  }
+
+  .route-end {
+    width: 44px;
+    height: 44px;
+  }
+
+  .route-line {
+    height: 12px;
+  }
+
+  .info-panel {
+    gap: 10px;
+  }
+
+  .info-item {
+    padding: 12px 14px;
+  }
+
+  .info-label {
+    font-size: 16px;
+  }
+
+  .info-value {
+    font-size: 20px;
+  }
+
+  .progress-value {
+    font-size: 22px;
+  }
+
+  .progress-bar {
+    height: 16px;
+  }
+
+  .btn-cancel {
+    height: 62px;
+    padding: 0 44px;
+    font-size: 22px;
+  }
+
+  .btn-back {
+    height: 58px;
+    padding: 0 22px;
+    font-size: 20px;
+    left: 20px;
+    bottom: 22px;
+  }
 }
 </style>