3

Vue 3-150行代码实现新国标红绿灯效果案例 - 程序员优雅哥(\/同)

 2 years ago
source link: https://www.cnblogs.com/youyacoder/p/16620988.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

昨天刷视频,都是关于新国标红绿灯的,看大家议论纷纷,下班就用150行代码通过Vue组件实践红绿模拟演示,视频也跟大家展示过了。今天接着更新图文版本,大家跟着优雅哥通过该案例实操模拟一下。

不过新国标红绿灯的设计,这个专业性、逻辑性、艺术性就是厉害,三个方向 * 三种颜色,玩转九宫格。大部分场景都可以不加思考就知道应该通行或者等待,只有某些情况下对应行驶方向的灯不亮时,才需要 if ... else ... 判断。优雅哥比较脑残,无论哪个方向:左转、直行、右转,如果灯都亮着,不就可以一眼看出能否通行了吗,对应方向红灯就停、绿灯就通行,这样是不是就可以不需要思考了?或许专家是为了省电吧,三个方向都亮灯太费电了。
后面得知,关于新国标红绿灯信息是误传,以上说辞纯属个人当时根据信息的一些看法,请大家不要误以为真。

现在来说说作为程序员,咱就用 Vue3 来模拟新国标红绿灯玩一玩。

1 组件分析

组件化开发是我们一贯的风格。如何进行这个红绿灯组件的设计呢?

image

从上图可以看出我对新国标红绿灯的组件拆分。

1.1 lamp

lamp 代表每个灯,在上图中一共有9个灯,分别是左转三个颜色的灯、直行三个颜色的灯、右转三个颜色的灯。这 9 个灯的区别是颜色和内部的箭头,而内部的箭头与车辆行驶方向(左转、右转、直行)有关,故颜色和方向可定义为属性,由外部传递。

1.2 lamp-group

图中一共有三个 lamp-group,每个行驶方向的三个颜色的灯组成一个 lamp-group。同样的,lamp-group 有一个属性为方向,该属性表示左转、直行或右转。此外,每个 lamp-group 中最多只有一个灯亮,故可以定义一个属性为状态,表示这个 lamp-group 中哪个灯亮、或者都不亮。

1.3 traffic-lamp

traffic-lamp 表示整个新国标红绿灯。有三个 lamp-group 组成。整个红绿灯也需要一个状态属性,描述三个方向的 lamp-group 的状态。

2 全局文件定义

2.1 样式变量

在 src/assets/ 中创建目录 scss,并在该目录下创建样式变量文件 traffic-lamp-common.scss,在该文件中定义红黄绿颜色、间距等常见样式,便于全局保持一致。由于咱 demo 较小,将 scss 变量与通用样式定义在一起就可以了,如果在正式开发中,要遵守 CSS 架构规范,无论是 ITCSS 还是 SMACSS。

src/assets/scss/traffic-lamp-common.scss

$red: #e42621;
$yellow: #eecd48;
$green: #59e02e;

$commonPadding: 10px;
$commonMargin: 10px;
$lampSize: 80px;
$radius: 8px;

.red {
  color: $red !important;
}
.yellow {
  color: $yellow !important;
}
.green {
  color: $green !important;
}
.bg-red {
  background-color: $red !important;
}
.bg-yellow {
  background-color: $yellow !important;
}
.bg-green {
  background-color: $green !important;
}

2.2 常量定义

创建 src/common/traffic-lamp-common.ts,在该文件中定义两个枚举类,分别是行驶方向(左、直、右)和灯的状态,O - off 表示灯不亮。

export enum Direction {
  L = 'left',
  C = 'center',
  R = 'right'
}
export enum Status {
  R = 'red',
  Y = 'yellow',
  G = 'green',
  O = 'off'
}

2.3 导入资源

由于灯里面有左箭头、右箭头两个图标,故从 iconfont 上搜索并下载这两个图标,优雅哥采用 iconfont 的方式使用图标,资源文件位于 src/assets/iconfont 下。在 main.ts 中引入iconfont:

import '@/assets/iconfont/iconfont.css'

3 组件开发

在 src/components 目录下创建目录 traffic-lamp,上面分析的三个组件就在该目录下开发。

3.1 实现 lamp 组件

lamp.vue

<template>
  <div class="lamp" :class="colorClass">
    <span v-if="direction === Direction.L" class="iconfont icon-left"></span>
    <span v-if="direction === Direction.R" class="iconfont icon-right"></span>
  </div>
</template>

<script lang="ts" setup>
import { computed, defineProps, PropType } from 'vue'
import { Direction, Status } from '@/common/traffic-lamp-common'

const props = defineProps({
  direction: {
    type: String as PropType<Direction>,
    required: true
  },
  color: {
    type: String as PropType<Status>,
    required: false,
    default: Status.O
  }
})

const colorClass = computed(() => {
  if (!props.color) {
    return ''
  }
  return `${props.direction}` === Direction.C ? `bg-${props.color}` : props.color
})
</script>

<style scoped lang="scss">
@import "~@/assets/scss/traffic-lamp-common.scss";

.lamp {
  width: $lampSize;
  height: $lampSize;
  line-height: $lampSize;
  background-color: #0e0e0e;
  margin: 5px;
  border-radius: 50%;
  text-align: center;
  color: gray;
  .iconfont {
    font-size: $lampSize - 10px;
    font-weight: bolder;
  }
}
</style>

可以在测试页面测试:

<lamp direction="left" color="green"></lamp>

3.2 实现 lamp-group 组件

lamp-group 组件中容纳了三个 lamp,分别是红灯、黄灯、绿灯。

lamp-group.vue

<template>
  <div class="lamp-group" :class="{
    'radius-left': direction === Direction.L,
    'radius-right': direction === Direction.R}">
    <div class="wrapper">
      <lamp :direction="direction" :color="status === Status.R ? status : Status.O" />
      <lamp :direction="direction" :color="status === Status.Y ? status : Status.O" />
      <lamp :direction="direction" :color="status === Status.G ? status : Status.O" />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { defineProps, PropType } from 'vue'
import Lamp from './lamp.vue'
import { Direction, Status } from '@/common/traffic-lamp-common'

defineProps({
  direction: {
    type: String as PropType<Direction>,
    required: true
  },
  status: {
    type: String as PropType<Status>,
    required: false,
    default: Status.O
  }
})
</script>

<style scoped lang="scss">
@import "~@/assets/scss/traffic-lamp-common.scss";

.lamp-group {
  background-color: #777;
  margin: 0 10px;
  padding: 10px;
  .wrapper {
    background-color: #5d5d5d;
    padding:5px;
  }
}
.radius-left {
  border-radius: $radius 0 0 $radius;
}
.radius-right {
  border-radius: 0 $radius $radius 0;
}
</style>

可以在测试页面测试该组件:

<lamp-group direction="right" status="green" />

3.3 实现 traffic-lamp 组件

traffic-lamp 组件容纳三个 lamp-group,分别代表左转、直行、右转。其中属性 status 要代表三个 group 的状态,父组件在使用时可以用逗号分隔。在设计的时候,也可以将状态拆分为三个属性,每个方向对应一个状态。

traffic-lamp.vue

<template>
  <div class="traffic-lamp">
    <lamp-group :direction="Direction.L" :status="statusList[0] || Status.O" />
    <lamp-group :direction="Direction.C" :status="statusList[1] || Status.O" />
    <lamp-group :direction="Direction.R" :status="statusList[2] || Status.O" />
  </div>
</template>

<script lang="ts" setup>
import { computed, defineProps } from 'vue'
import LampGroup from './lamp-group.vue'
import { Status, Direction } from '@/common/traffic-lamp-common'

const props = defineProps({
  status: {
    type: String,
    required: false,
    default: ''
  }
})

const statusList = computed(() => {
  const list = props.status.split(',')
  const remain = 3 - list.length
  for (let i = 0; i < remain; i++) {
    list.push(Status.O)
  }
  return list
})
</script>

<style scoped lang="scss">
.traffic-lamp {
  display: flex;
}
</style>

可以在测试页面测试该组件:

<traffic-lamp status="red,red"></traffic-lamp>

4 附加功能

到这里为止,交通灯功能就模拟实现完成了,切换交通灯红绿灯状态时,只需要改变 status 即可。

现在咱额外新增一个功能,新国标有 8 种状态,咱就让这 8 种状态自动切换。

下列所有代码都编写在测试页面中。在测试页面中使用 traffic-lamp 组件。

4.1 状态数组

在测试页面中定义 8 种状态列表:

const { R, G, O } = Status
const statusList = [
  `${R},${R},${R}`,
  `${R},${R},${O}`,
  `${G},${R},${R}`,
  `${O},${R},${O}`,
  `${R},${G},${R}`,
  `${O},${G},${R}`,
  `${R},${G},${O}`,
  `${O},${G},${O}`
]

4.2 定义索引

在测试页面中定义遍历状态列表的索引:

const currentIndexRef = ref(0)
const currentStatus = computed(() => statusList[currentIndexRef.value])

在模板中动态设置 traffic-lamp 的 status 属性:

<traffic-lamp :status="currentStatus"></traffic-lamp>

4.3 自动切换

在测试页面 onMounted 生命周期函数中,定时修改索引 currentIndexRef 的值,从而实现红绿灯的自动切换:

onMounted(() => {
  setInterval(() => {
    currentIndexRef.value += 1
    currentIndexRef.value = currentIndexRef.value % statusList.length
  }, 1000)
})

4.4 文字描述

可以在红绿灯下面添加是否可以通行的文字描述。

<traffic-lamp :status="currentStatus"></traffic-lamp>
<div class="display">
  <div :class="left">{{getText(left)}}</div>
  <div :class="center">{{getText(center)}}</div>
  <div :class="right">{{getText(right)}}</div>
</div>

TS 代码:

const left = computed(() => {
  const list = currentStatus.value.split(',')
  return list[0] === Status.O ? list[1] : list[0]
})
const center = computed(() => {
  return currentStatus.value.split(',')[1] || Status.O
})
const right = computed(() => {
  const list = currentStatus.value.split(',')
  return list[2] === Status.R ? Status.R : Status.G
})

const getText = (status: string) => {
  if (status === Status.G) {
    return '可以通行'
  }
  if (status === Status.R) {
    return '停车等待'
  }
  return ''
}
<style scoped lang="scss">
@import "~@/assets/scss/traffic-lamp-common.scss";
.display {
  width: 420px;
  display: flex;
  margin-top: 10px;

  div {
    flex: 1;
    text-align: center;
  }
}
</style>

运行如下:

image

\/ “程序员优雅哥”,今日学习到此结束,期待一箭三连(赞、藏、转)~~~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK