Selaa lähdekoodia

refactor: 阶段方块统一用 PhaseDiagram 渲染 + 清理 arrow_*.png

- mock api.js:
  - _makeCardPhases 加 icons 字段 (4 个标准阶段 token 数组)
  - 删 arrow_1~4 import + ARROWS 数组
  - 4 处 img: ARROWS[i] → img: null
- IntersectionControlCard.vue:
  - 模板优先用 <PhaseDiagram> 渲染, 不再依赖 PNG
  - .phase-box 加 aspect-ratio: 1/1 配合 PhaseDiagram 方形容器
  - 保留 img / icon 双层 fallback
- CrossingDetailPanel.vue:
  - img v-else fallback 改为 <div> 显示阶段编号占位
- 删除 4 个 PNG 文件 (~40KB)
- 阶段渲染统一: 路口详情面板 + 卡片同一套 PhaseDiagram
画安 2 viikkoa sitten
vanhempi
commit
0ba0f87bf1

BIN
src/assets/images/arrow_1.png


BIN
src/assets/images/arrow_2.png


BIN
src/assets/images/arrow_3.png


BIN
src/assets/images/arrow_4.png


+ 8 - 4
src/components/ui/CrossingDetailPanel.vue

@@ -94,7 +94,7 @@
                                                 :bg-color="item.bgColor"
                                                 :number-color="item.numberColor"
                                             />
-                                            <img v-else :src="item.img" alt="stage" class="phase-image" />
+                                            <div v-else class="phase-placeholder">{{ item.no || item.value }}</div>
                                         </div>
 
                                         <div class="bottom-controls">
@@ -1210,11 +1210,15 @@ export default {
     overflow: hidden;
 }
 
-.phase-image {
+.phase-placeholder {
     width: 100%;
     height: 100%;
-    object-fit: contain;
-    display: block;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: clamp(14px, calc(var(--s, 1) * 18px), 22px);
+    font-weight: bold;
+    color: #1f2937;
 }
 
 .phase-box::after {

+ 21 - 5
src/components/ui/IntersectionControlCard.vue

@@ -33,7 +33,13 @@
                 :class="{ 'is-active': phase.active }"
                 @click="selectPhase(phase)"
             >
-                <img v-if="phase.img" :src="phase.img" alt="phase-img" class="phase-image" />
+                <PhaseDiagram
+                    v-if="phase.icons && phase.icons.length"
+                    :icons="phase.icons"
+                    :no="phase.id"
+                />
+                <img v-else-if="phase.img" :src="phase.img" alt="phase-img" class="phase-image" />
+                <span v-else class="phase-icon">{{ phase.icon }}</span>
             </div>
         </div>
     </div>
@@ -42,10 +48,11 @@
 <script>
 import IntersectionMap from '@/components/ui/IntersectionMap.vue';
 import DropdownSelect from '@/components/ui/DropdownSelect.vue';
+import PhaseDiagram from '@/components/ui/PhaseDiagram.vue';
 
 export default {
     name: 'IntersectionControlCard',
-    components: { IntersectionMap, DropdownSelect },
+    components: { IntersectionMap, DropdownSelect, PhaseDiagram },
     props: {
         data: { type: Object, required: true }
     },
@@ -197,9 +204,10 @@ export default {
 
 .phase-box {
     position: relative;
-    flex: 1; /* 平分剩余空间 */
-    height: 100%; /* 撑满 footer 的高 */
-    background: #E6F0FF; 
+    flex: 0 0 auto;       /* 不平分; 由 aspect-ratio 控制宽度 */
+    height: 100%;          /* 撑满 footer 的高 */
+    aspect-ratio: 1 / 1;   /* 正方形, 配合 PhaseDiagram 的 30×30 比例容器 */
+    background: #E6F0FF;
     border-radius: 4px;
     display: flex;
     align-items: center;
@@ -218,6 +226,14 @@ export default {
     display: block;
 }
 
+/* img fallback: unicode 箭头字符 (↑/↰ 等), 居中显示 */
+.phase-icon {
+    font-size: clamp(16px, 2vw, 28px);
+    font-weight: bold;
+    color: #1f2937;
+    line-height: 1;
+}
+
 .phase-box::after {
     content: '';
     position: absolute;

+ 17 - 10
src/mock/api.js

@@ -23,13 +23,8 @@ import video1 from '@/assets/videos/video1.mp4'
 import video2 from '@/assets/videos/video2.mp4'
 import video3 from '@/assets/videos/video3.mp4'
 import video4 from '@/assets/videos/video4.mp4'
-import arrow1 from '@/assets/images/arrow_1.png'
-import arrow2 from '@/assets/images/arrow_2.png'
-import arrow3 from '@/assets/images/arrow_3.png'
-import arrow4 from '@/assets/images/arrow_4.png'
 
 const VIDEOS = [video1, video2, video3, video4]
-const ARROWS = [arrow1, arrow2, arrow3, arrow4]
 
 function pickVideo(i) { return VIDEOS[i % VIDEOS.length] }
 
@@ -431,13 +426,25 @@ function _makeCornerVideos() {
 
 function _makeStageList() {
   return [1, 2, 3, 4].map((_, i) => ({
-    value: String(i + 1), time: [30, 30, 50, 30][i], img: ARROWS[i],
+    value: String(i + 1), time: [30, 30, 50, 30][i], img: null,
   }))
 }
 
 function _makeCardPhases(activeIndex = 0) {
-  return ARROWS.map((img, i) => ({
-    id: i + 1, icon: ['↑', '↰', '↑', '↰'][i], img, active: i === activeIndex,
+  // 4 个标准阶段的 PhaseDiagram token 数组 (对应 _makePhaseData 的 default 配置):
+  //   P1 南北直行 / P2 南北左转 / P3 东西直行 / P4 东西左转
+  const PHASE_TOKENS = [
+    ['STRAIGHT_DOWN',  'STRAIGHT_UP'],
+    ['TURN_DOWN_LEFT', 'TURN_UP_LEFT'],
+    ['STRAIGHT_LEFT',  'STRAIGHT_RIGHT'],
+    ['TURN_LEFT_DOWN', 'TURN_RIGHT_UP'],
+  ]
+  return [1, 2, 3, 4].map((_, i) => ({
+    id: i + 1,
+    icons: PHASE_TOKENS[i],            // PhaseDiagram 渲染用
+    icon: ['↑', '↰', '↑', '↰'][i],    // 兜底 unicode (PhaseDiagram 失败时显示)
+    img: null,
+    active: i === activeIndex,
   }))
 }
 
@@ -590,7 +597,7 @@ export async function apiGetIntersectionStages(id) {
     const hasTrack1 = timing.data.phaseData.some(p => p[0] === 1)
     const phases = timing.data.phaseData.filter(p => p[0] === (hasTrack1 ? 1 : 0) && p[5] === 'green')
     return ok(phases.map((p, i) => ({
-      value: String(i + 1), time: p[8], phaseName: p[3], direction: p[6], img: ARROWS[i % ARROWS.length],
+      value: String(i + 1), time: p[8], phaseName: p[3], direction: p[6], img: null,
     })))
   }
   return ok(_makeStageList())
@@ -1207,7 +1214,7 @@ export async function apiGetCrossingDetailData(id, { iconMode = 'default' } = {}
         time: p[8],
         phaseName: p[3],
         direction: p[6],
-        img: ARROWS[i],
+        img: null,
         locktimeOptions: stageLockOptions[i] || stageLockOptions[0],
       }))