vue+element时间进度条实现

背景

项目前端开发使用了vue框架+Element组件,但是Element中没有已经实现了的时间进度条可以直接使用,为了满足项目展示任务详情剩余时间和进度的需求,我使用Element的Slider等组件自己封装了一个TimeSlider组件。


Element原组件Slider

  • 可以滑动
  • 可格式化滑点输出信息

效果图及源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="block">
<span class="demonstration">格式化 Tooltip</span>
<el-slider v-model="value4" :format-tooltip="formatTooltip"></el-slider>
</div>
<script>
export default {
data() {
return {
value1: 0,
value2: 50,
value3: 36,
value4: 48,
value5: 42
}
},
methods: {
formatTooltip(val) {
return val / 100;
}
}
}
</script>

目标样式

  • 进度条左右两端有开始时间和结束时间
  • 根据开始时间、当前时间和结束时间刷新进度条,即使用当前时间与开始时间的差占任务总时长的占比来更新进度
  • 格式化滑点输出:
    • 当任务在进行中,显示任务剩余时间
    • 当任务已完成,根据完成时间定格进度,滑点显示已完成
    • 当任务由于发布者取消、领取者放弃或超时,进度定格在100处,滑点显示任务未完成

编码实现

  1. <el-row>中分3列<el-col>,分别展示开始时间、进度条和结束时间,并设置好组件props以满足调用时传参所需。

    所传参数意义为:

    • startTime:任务开始时间
    • endTime:任务结束时间
    • finNum:完成任务的人数(仅当任务为问卷任务时需要)
    • missionState:任务当前状态(包括进行中、已完成、未完成即已结束)
    • finishTime:任务完成的时间
    • orderState:功能类似missionState,用于不同场景
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <template>
    <div>
    <el-row type="flex" align="middle">
    <el-col v-bind:span="5" style="text-align: center">{{ startTime }}</el-col>
    <el-col v-bind:span="14">
    <el-slider></el-slider>
    </el-col>
    <el-col v-bind:span="5" style="text-align: center">{{ endTime }}</el-col>
    </el-row>
    </div>
    </template>
    <script>
    export default {
    name: 'TimeSlider',

    data () {
    return {}
    },

    props: {
    startTime: {
    type: String,
    default: ''
    },

    endTime: {
    type: String,
    default: ''
    },

    finNum: {
    type: Number,
    default: 0
    },

    missionState: {
    type: Number,
    default: 0
    },

    finishTime: {
    type: String,
    default: ''
    },

    orderState: {
    type: Number,
    default: 0
    }
    }
    }
    </script>

    <style scoped>

    </style>
  2. 根据开始时间和结束时间计算时间进度条每次跳动的距离

    下面得到的结果为任务时长,使用100除以上述结果并向下取整即可得到时间进度条每次跳动的距离

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    timeDiff (sTime, eTime) {
    let startTime = new Date(sTime)
    let endTime = new Date(eTime)
    let leftHour = 0
    if (endTime.getTime() > startTime.getTime()) {
    let msDiff = endTime.getTime() - startTime.getTime()
    leftHour = Math.floor(msDiff / (1000 * 3600))
    }
    return leftHour
    }
  3. 计算当前时间距离开始时间过去了多久,并根据2计算的结果设置当前任务进度

    以下根据任务当前的状态设置进度值,若为进行中任务,则继续计算并持续更新进度,若为已完成则定格进度值,若为未完成(取消或超时),则进度值为100

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    passTime (startTime, endTime, finishTime) {
    let sTime = new Date(startTime)
    let nowTime = Date.now()
    let eTime = new Date(endTime)
    if (this.finNum !== 0 || this.orderState === 1) {
    let finTime = new Date(finishTime)
    let passHour = 0
    if (finTime.getTime() > sTime.getTime()) {
    let msDiff = finTime.getTime() - sTime.getTime()
    passHour = Math.ceil(msDiff / (1000 * 3600))
    }
    return passHour * (100.0 / this.timeDiff(startTime, endTime))
    } else if ((this.missionState > 2 && this.finNum === 0) || this.orderState === 2) {
    return 100
    }

    if (nowTime >= eTime.getTime()) {
    return 100
    }

    let passHour = 0
    if (nowTime > sTime.getTime()) {
    let msDiff = nowTime - sTime.getTime()
    passHour = Math.ceil(msDiff / (1000 * 3600))
    }
    return passHour * (100.0 / this.timeDiff(startTime, endTime))
    }
  4. 根据需求格式化滑点的输出

    根据任务状态和需求设置滑点数据的显示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    formatTooltip () {
    if ((this.finNum !== 0 && this.missionState === 2) || this.orderState === 1) {
    return '已完成'
    } else if (this.missionState > 2 && this.finNum === 0 && this.orderState !== 2) {
    return '任务已结束或已取消(未完成)'
    } else if (this.orderState === 2) {
    return '任务已结束或已放弃(未完成)'
    }
    let currTime = Date.now()
    let eTime = new Date(this.endTime.toString())
    let left = '任务未完成,已结束'
    if (eTime.getTime() > currTime) {
    let msDiff = eTime.getTime() - currTime
    // compute day left
    let leftDay = Math.floor(msDiff / (1000 * 24 * 60 * 60))
    // hours left after computing day left
    let leaveForHour = msDiff % (1000 * 24 * 60 * 60)
    // compute hour left
    let leftHour = Math.floor(leaveForHour / (1000 * 60 * 60))
    let leaveForMinute = leaveForHour % (1000 * 3600)
    let leftMinute = Math.floor(leaveForMinute / (1000 * 60))
    let leaveForSecond = leaveForMinute % (1000 * 60)
    let leftSecond = Math.round(leaveForSecond / 1000)
    left = '剩余' + leftDay + '天' + leftHour + '时' + leftMinute + '分' + leftSecond + '秒'
    }

    return left
    }
  5. 进度条不可人为改动,所以需要添加disable


完整代码及效果图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<template>
<div>
<el-row type="flex" align="middle">
<el-col v-bind:span="5" style="text-align: center">{{ startTime }}</el-col>
<el-col v-bind:span="14">
<el-slider
v-bind:step="Math.floor(100 / timeDiff(startTime, endTime))"
v-bind:value="passTime(startTime, endTime, finishTime)"
v-bind:format-tooltip="formatTooltip"
disabled>
</el-slider>
</el-col>
<el-col v-bind:span="5" style="text-align: center">{{ endTime }}</el-col>
</el-row>
</div>
</template>

<script>
export default {
name: 'TimeSlider',

data () {
return {}
},

props: {
startTime: {
type: String,
default: ''
},

endTime: {
type: String,
default: ''
},

finNum: {
type: Number,
default: 0
},

missionState: {
type: Number,
default: 0
},

finishTime: {
type: String,
default: ''
},

orderState: {
type: Number,
default: 0
}
},

methods: {
formatTooltip () {
if ((this.finNum !== 0 && this.missionState === 2) || this.orderState === 1) {
return '已完成'
} else if (this.missionState > 2 && this.finNum === 0 && this.orderState !== 2) {
return '任务已结束或已取消(未完成)'
} else if (this.orderState === 2) {
return '任务已结束或已放弃(未完成)'
}
let currTime = Date.now()
let eTime = new Date(this.endTime.toString())
let left = '任务未完成,已结束'
if (eTime.getTime() > currTime) {
let msDiff = eTime.getTime() - currTime
// compute day left
let leftDay = Math.floor(msDiff / (1000 * 24 * 60 * 60))
// hours left after computing day left
let leaveForHour = msDiff % (1000 * 24 * 60 * 60)
// compute hour left
let leftHour = Math.floor(leaveForHour / (1000 * 60 * 60))
let leaveForMinute = leaveForHour % (1000 * 3600)
let leftMinute = Math.floor(leaveForMinute / (1000 * 60))
let leaveForSecond = leaveForMinute % (1000 * 60)
let leftSecond = Math.round(leaveForSecond / 1000)
left = '剩余' + leftDay + '天' + leftHour + '时' + leftMinute + '分' + leftSecond + '秒'
}

return left
},

timeDiff (sTime, eTime) {
let startTime = new Date(sTime)
let endTime = new Date(eTime)
let leftHour = 0
if (endTime.getTime() > startTime.getTime()) {
let msDiff = endTime.getTime() - startTime.getTime()
leftHour = Math.floor(msDiff / (1000 * 3600))
}
return leftHour
},

passTime (startTime, endTime, finishTime) {
let sTime = new Date(startTime)
let nowTime = Date.now()
let eTime = new Date(endTime)
if (this.finNum !== 0 || this.orderState === 1) {
let finTime = new Date(finishTime)
let passHour = 0
if (finTime.getTime() > sTime.getTime()) {
let msDiff = finTime.getTime() - sTime.getTime()
passHour = Math.ceil(msDiff / (1000 * 3600))
}
return passHour * (100.0 / this.timeDiff(startTime, endTime))
} else if ((this.missionState > 2 && this.finNum === 0) || this.orderState === 2) {
return 100
}

if (nowTime >= eTime.getTime()) {
return 100
}

let passHour = 0
if (nowTime > sTime.getTime()) {
let msDiff = nowTime - sTime.getTime()
passHour = Math.ceil(msDiff / (1000 * 3600))
}
return passHour * (100.0 / this.timeDiff(startTime, endTime))
}
}
}
</script>

<style scoped>

</style>

在页面主键中引用上述TimeSlider并传相应参数即可使用该组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div>
<time-slider
v-bind:start-time="startTime"
v-bind:end-time="endTime"
v-bind:mission-state="missionState"
v-bind:fin-num="fin_num"
v-bind:finish-time="finishTime"></time-slider>
</div>
</template>
<script>
import backend from '../backend'
import TimeSlider from '../components/TimeSlider' // import TimeSlider 组件

export default {
name: 'PublishedPage',

components: { TimeSlider },

data () {
return {
startTime: '2019-06-25 23:28:45',
endTime: '2019-06-27 23:28:28',
finishTime: '', // 空表示未完成
fin_num: 0, // 0表示未完成
missionState: 0 // 0表示未完成
}
}
}

效果图如下: