这篇文章主要介绍了Vue怎么实现新国标红绿灯效果的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue怎么实现新国标红绿灯效果文章都会有所收获,下面我们一起来看看吧。
1 组件分析
组件化开发是我们一贯的风格。如何进行这个红绿灯组件的设计呢?
从上图可以看出我对新国标红绿灯的组件拆分。
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>
运行如下: