赞
踩
time-slider/index.vue
- <template>
- <div class="sutpc-play-slider">
- <div class="play-wrap">
- <icon-svg
- name="play"
- @click.native="play"
- v-if="!isPlaying"
- />
- <icon-svg
- name="pause"
- @click.native="pause"
- v-else
- />
- <img
- class="hidden"
- src="./svgs/play.svg"
- />
- <img
- class="hidden"
- src="./svgs/pause.svg"
- />
- <div class="slider-wrap">
- <time-slider
- ref="slider"
- :interFilter="interFilter"
- :interFormat="interFormat"
- :tooltipFormat="tooltipFormat"
- v-model="currentValue"
- @move="sliderMoveHandler"
- :sliderData="sliderData"
- ></time-slider>
- </div>
- </div>
- </div>
- </template>
-
- <script>
-
- import TimeSlider from "./components/slider";
-
- export default {
- props: {
- //时间间隔为5分钟
- timePrecision: {
- type: Number,
- default: 5
- },
- playData: {
- type: Array,
- default: function () {
- return [];
- }
- },
- value: {
- type: Number,
- default: 0
- },
- sliderData: {
- type: Array,
- default: function () {
- return [];
- }
- },
- playInterval: {
- type: Number,
- default: 2000
- }
- },
- components: {
- TimeSlider
- },
- data() {
- return {
- currentValue: this.value,
- isPlaying: false,
- currentPlayIndex: 0,
- initValue: this.value
- }
- },
- watch: {
- value: {
- handler() {
-
- if (this.currentValue != this.value) {
- this.initValue = this.value;
- }
-
- this.currentValue = this.value;
-
- let currentPlayIndex = this.enabledSliderIndexArray.indexOf(this.currentValue);
- // 添加判断,初始化时enabledSliderIndexArray为空,currentPlayIndex为-1
- this.currentPlayIndex = currentPlayIndex == -1 ? 0 : currentPlayIndex;
- },
- immediate: true
- },
- currentValue() {
- this.$emit("input", this.currentValue);
- }
- },
- computed: {
- enabledSliderIndexArray() {
- let indexArray = [];
- this.sliderData.forEach(item => {
- if (item.type == 'disabled') {
- return;
- }
- for (let i = item.from; i <= item.to; i++) {
- indexArray.push(i);
- }
- });
- return indexArray;
- },
- currentTime() {
- let showValue = this.currentValue;
- if (this.dataFormat) {
- showValue = this.dataFormat(showValue,
- this.$refs.slider &&
- this.$refs.slider.getValueType &&
- this.$refs.slider.getValueType(showValue));
- }
- return `${showValue}`;
- },
- interFilter: ({ timePrecision }) => Object.seal(value => (value % ((60 / timePrecision) * 2) === 0 || value === 1)),
- interFormat: ({ timePrecision }) => Object.seal(value => {
- if ((value % ((60 / timePrecision) * 4) === 0)) {
- return `${Math.floor(value / (60 / timePrecision)).toString().padStart(2, '0')}:${(value % (60 / timePrecision) * timePrecision).toString().padStart(2, '0')}`;
- }
- return ''
- }),
- tooltipFormat: ({ timePrecision }) => Object.seal(value => `${Math.floor(value / (60 / timePrecision)).toString().padStart(2, '0')}:${(value % (60 / timePrecision) * timePrecision).toString().padStart(2, '0')}`),
- dataFormat: ({ timePrecision }) => Object.seal(value => `${Math.floor(value / (60 / timePrecision)).toString().padStart(2, '0')}:${(value % (60 / timePrecision) * timePrecision).toString().padStart(2, '0')}`),
- },
- methods: {
- sliderMoveHandler() {
- this.currentPlayIndex = this.enabledSliderIndexArray.indexOf(this.currentValue);
- if (this.isPlaying) {
- clearInterval(this.playInter);
- this.play();
- }
- },
- play() {
- // 判断是否第一次播放并且是播放进度的最后一个时刻
- // 如果是则currentPlayIndex=0,直接从头播放
- if (this.isFirstTime == undefined && this.currentPlayIndex == this.enabledSliderIndexArray.length - 1) {
- this.currentPlayIndex = 0;
- this.isFirstTime = false;
- }
-
- this.isPlaying = true;
- this.currentValue = this.enabledSliderIndexArray[this.currentPlayIndex];
- this.playInter = setInterval(() => {
- this.currentPlayIndex++;
- if (this.currentPlayIndex >= this.enabledSliderIndexArray.length) {
- clearInterval(this.playInter);
- this.currentPlayIndex = 0;
- this.isPlaying = false;
- return;
- }
- this.currentValue = this.enabledSliderIndexArray[this.currentPlayIndex];
- }, this.playInterval);
- },
- pause() {
- this.isPlaying = false;
- clearInterval(this.playInter);
- }
- },
- beforeDestroy() {
- clearInterval(this.playInter);
- }
- }
- </script>
-
- <style lang="less" scoped>
- .hidden {
- display: none;
- }
- .sutpc-play-slider {
- padding: @side-gap @side-gap + 20px @side-gap @side-gap;
- color: @color-primary;
- display: flex;
- flex-direction: column;
- font-size: 12px;
- position: absolute;
- right: 0;
- left: 0;
- margin: auto;
- bottom: @side-gap;
- z-index: 1500;
- width: 680px;
- .panel-bg;
- .box-shadow;
- .play-wrap {
- display: flex;
- align-items: center;
- width: 100%;
- flex: 1;
- }
- .slider-wrap {
- flex: 1;
- width: 0;
- }
- .svg-icon {
- font-size: 26px;
- cursor: pointer;
- margin-right: @side-gap;
- }
- }
- </style>
components/slider.vue
- <template>
- <div
- class="sutpc-slider"
- @mouseleave="containerMouseleaveHandler"
- @mousedown="containerMousedownHandler"
- @mouseup="containerMouseupHandler"
- @mousemove="containerMousemoveHandler"
- ref="container"
- >
- <span
- v-for="(item, index) in sliderData"
- :key="index"
- :class="getItemClass(item)"
- :style="getItemStyle(item, index)"
- @mousedown="mousedownHandler(item, $event)"
- ></span>
- <div
- class="block"
- :style="getBlockStyle"
- >
- <div
- class="block-tooltip"
- v-text="tooltipContent"
- />
- </div>
- <div class="interval-wrap">
- <template v-for="inter in intervalArray">
- <span
- class="interval"
- :key="inter"
- v-if="interFilter ? interFilter(inter) : true"
- :style="getIntervalStyle(inter)"
- >
- {{ interFormat ? interFormat(inter, getValueType(inter)) : inter }}
- <i
- class="interval-tick"
- :class="getIntervalClass(inter)"
- />
- </span>
- </template>
- </div>
- </div>
- </template>
-
- <script>
- // v-model 当前所在的时间片
- // @change 选择的时间片变化时,回传出去
- export default {
- props: {
- step: {
- type: Number,
- default: 1
- },
- /**
- * [{from, to, type: 'predict|enabled|disabled'}]
- */
- sliderData: {
- type: Array,
- default: function () {
- return [];
- }
- },
- value: {
- type: Number,
- default: 0
- },
- interFilter: {
- type: Function,
- default: value => value
- },
- interFormat: {
- type: Function,
- default: value => value
- },
- tooltipFormat: {
- type: Function,
- default: value => value
- },
- debounce: {
- type: Number,
- default: 0
- },
- version: {
- type: [Number, String],
- default: 1
- }
- },
- data() {
- return {
- // 原始值,在绘制的时候需要除以step
- currentValue: this.value,
- // 是否鼠标按住中
- isMouseDown: false,
- }
- },
- computed: {
- tooltipContent() {
- return this.tooltipFormat && this.tooltipFormat(this.currentValue, this.getValueType(this.currentValue));
- },
- intervalArray() {
- let arr = [];
- for (let i = this.minValue; i <= this.maxValue; i++) {
- arr.push(i);
- }
- return arr;
- },
- maxValue() {
- let max = Number.NEGATIVE_INFINITY;
- this.sliderData.forEach(({ from, to, type }) => {
- max = Math.max(from, max, to);
- });
- return max;
- },
- minValue() {
- let min = Number.POSITIVE_INFINITY;
- this.sliderData.forEach(({ from, to, type }) => {
- min = Math.min(from, min, to);
- });
- return min;
- },
- totalInter() {
- return this.maxValue - this.minValue;
- },
- getBlockStyle() {
- if (this.currentValue === '' || this.currentValue < this.minValue) {
- return {
- display: 'none'
- }
- }
- return {
- left: ((this.currentValue - this.minValue) / this.totalInter) * 100 + '%'
- }
- },
- stepWidth() {
- return this.$refs.container.offsetWidth / (this.totalInter / this.step);
- }
- },
- methods: {
- getIntervalClass(interval) {
- const havePredict = this.sliderData.find(item => item.type === 'predict');
- const [{ to: enabled }] = this.sliderData;
- if (interval <= enabled + (havePredict ? 20 : 0)) {
- return 'enabled'
- } else {
- return 'disabled'
- }
- },
- getIntervalStyle(interval) {
- return {
- marginLeft: ((interval - this.minValue) / this.totalInter) * 100 + '%'
- }
- },
- getItemClass({ type }) {
- return {
- 'enabled': 'enabled',
- 'disabled': 'disabled',
- 'predict': 'predict'
- }[type || 'enabled'];
- },
- getItemStyle({ from, to }, index) {
-
- let itemWidth = null;
-
- // v1的数据区间是断节的,类似[{from: 0, to: 2}, {from: 3, to: 5}],经确认后认为该区间划分不合理,因此升级了v2
- if (this.version == 1) {
- // 因为区间的不连续,区间的宽度需要+1,例如0-2,则区间宽度是 2 - 0 + 1 * 单位宽度
- itemWidth = ((to - from + 1) / this.totalInter) * 100 + '%';
- // 最后一个区间则不能+1,否则最后会多出一段多余的
- if (index >= this.sliderData.length - 1) {
- itemWidth = ((to - from) / this.totalInter) * 100 + '%';
- }
- }
- // v2的数据区间格式如下:[{from: 0, to: 3}, {from:3, to:5}],2个区间之间是连续的
- else {
- itemWidth = ((to - from) / this.totalInter) * 100 + '%';
- }
-
- // 最前和最后一个区间,宽度加长,往左边和右边突出一点
- if (index <= 0 || index >= this.sliderData.length - 1) {
- return {
- width: `calc(${itemWidth} + 3px)`
- }
- }
-
- return {
- width: itemWidth
- }
- },
- mousedownHandler({ type }, e) {
- if (type === 'disabled') {
- e.stopPropagation();
- e.preventDefault();
- return false;
- }
- },
- containerMousedownHandler(e) {
- if (e.target === e.currentTarget) {
- return;
- }
- let { offsetWidth } = e.currentTarget;
- // 鼠标点击位置相对于滑动条的左边距离
- let offsetX = e.clientX - e.currentTarget.getBoundingClientRect().left;
- this.resetRealBlockPos(offsetX);
-
- this.isMouseDown = true;
- },
- containerMouseupHandler(e) {
- this.isMouseDown = false;
- },
- containerMousemoveHandler(e) {
- if (!this.isMouseDown) {
- return;
- }
- let { offsetWidth } = e.currentTarget;
- // 鼠标点击位置相对于滑动条的左边距离
- let offsetX = e.clientX - e.currentTarget.getBoundingClientRect().left;
- this.resetRealBlockPos(offsetX);
- },
- containerMouseleaveHandler() {
- this.isMouseDown = false;
- },
- resetRealBlockPos(offsetX) {
-
- let currentValue = offsetX / this.stepWidth * this.step + this.minValue;
-
- if (currentValue > this.maxValue || currentValue < this.minValue) {
- return;
- }
-
- // v1的数据区间是断节的,类似[{from: 0, to: 2}, {from: 3, to: 5}],经确认后认为该区间划分不合理,因此升级了v2
- if (this.version == 1) {
- for (let { from, to, type } of this.sliderData) {
- if (currentValue >= from &&
- currentValue <= to + 1 &&
- type === 'disabled') {
- return;
- }
- }
- }
- // v2的数据区间格式如下:[{from: 0, to: 3}, {from:3, to:5}],2个区间之间是连续的
- else {
- for (let { from, to, type } of this.sliderData) {
- if (currentValue >= from &&
- currentValue <= to &&
- type === 'disabled') {
- return;
- }
- }
- }
-
- this.currentValue = Math.round(currentValue);
-
- let currentType = this.getValueType(this.currentValue);
-
- this.$emit("move", {
- value: this.currentValue,
- type: currentType
- });
- },
- getValueType(value) {
- let valueType = '';
- for (let { from, to, type } of this.sliderData) {
- if (value >= from && value <= to) {
- valueType = type;
- }
- }
- return valueType;
- }
- },
- watch: {
- currentValue() {
- let currentType = this.getValueType(this.currentValue);
- if (this.debounce <= 0) {
- this.$emit("input", this.currentValue);
- this.$emit("change", {
- value: this.currentValue,
- type: currentType
- });
- }
- else {
- clearTimeout(this.debounceTimeout);
- this.debounceTimeout = setTimeout(() => {
- this.$emit("input", this.currentValue);
- this.$emit("change", {
- value: this.currentValue,
- type: currentType
- });
- }, this.debounce);
- }
- },
- value() {
- this.currentValue = this.value;
- }
- }
- }
- </script>
- <style lang="less" scoped>
- .sutpc-slider {
- position: relative;
- display: flex;
- justify-content: center;
- align-items: center;
- background: fadeout(@color-primary, 90%);
- height: 12px;
- > span {
- flex-shrink: 0;
- height: 3px;
- background: #ccc;
- box-sizing: border-box;
- &:first-of-type {
- border-top-left-radius: 3px;
- border-bottom-left-radius: 3px;
- }
- &:last-of-type {
- border-top-right-radius: 3px;
- border-bottom-right-radius: 3px;
- }
- &.enabled {
- cursor: pointer;
- }
- }
- .block {
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%) translateX(-50%);
- border-radius: 100%;
- user-select: none;
- transition: all 0.1s;
- cursor: grab;
- z-index: 1;
- height: 12px;
- width: 12px;
- box-sizing: border-box;
- border: none;
- background: @color-primary;
-
- .block-tooltip {
- position: absolute;
- left: 50%;
- top: -2 * @side-gap;
- transform: translateX(-50%);
- padding: 5px;
- .box-shadow;
- .panel-bg;
- border-radius: @border-radius;
- color: @text-color-regular;
-
- &::after {
- content: "";
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- bottom: -6px;
- border-right: 6px solid transparent;
- border-left: 6px solid transparent;
- border-top: 6px solid @color-primary;
- .box-shadow;
- }
- }
- }
- .interval-wrap {
- position: absolute;
- margin-top: 12px;
- left: 0;
- right: 0;
- top: 0;
- font-size: 12px;
- .interval {
- position: absolute;
- left: 0;
- top: 0;
- transform: translateX(-50%);
- white-space: nowrap;
- font-size: @font-size-small;
- color: @text-color-secondary;
- }
- .interval-tick {
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- .panel-bg;
- width: 12px;
- height: 12px;
- top: -12px;
- border-radius: 50%;
- box-sizing: border-box;
- border: 3px solid @color-primary;
-
- &.disabled {
- border-color: @text-color-placeholder;
- }
- }
- }
- }
- </style>
-
- <style lang="less">
- .slider-tooltip {
- border: none;
- .box-shadow;
-
- .popper__arrow {
- &::after {
- border-top-color: @color-primary !important;
- }
- }
- }
- .sutpc-slider {
- > span {
- &.enabled {
- background: @color-primary;
- }
- &.predict {
- background: linear-gradient(
- to right,
- @color-primary 50%,
- transparent 0
- );
- background-size: @side-gap-medium 100%;
- }
- }
- .block {
- border: 2px solid @color-primary;
- }
- .interval-wrap {
- color: @text-color-secondary;
- }
- }
- </style>
使用组件
- <template>
- <PlaySlider
- :sliderData="sliderData"
- v-model="currentPeriod"
- :timePrecision="5"
- :playInter="4000"
- :debounce="300"
- />
- </template>
-
- <script>
- import PlaySlider from 'components/time-slider';
- import { state, action } from '../store';
- // 播放器的最大时间
- const SLIDER_MAX_VALUE = 288;
-
- export default {
- components: {
- PlaySlider,
- },
- data: () => ({
- sliderData: [],
- }),
- computed: {
- //当前时间片
- currentPeriod: {
- get: () => state.currentPeriod,
- set: (value) => action.updateCurrentPeriod(value)
- },
- },
- watch: {
- currentPeriod(value) {
- //如果不是currentPeriod第一次设置则返回
- if (this.sliderData.length) {
- return;
- }
-
- this.sliderData = [{
- from: 1,
- to: value,
- type: 'enabled'
- }];
-
- if (value >= SLIDER_MAX_VALUE) {
- return;
- }
-
- let disabledStart = value + 1;
- if (disabledStart > SLIDER_MAX_VALUE) {
- return;
- }
-
- this.sliderData.push({
- from: disabledStart,
- to: SLIDER_MAX_VALUE,
- type: 'disabled'
- });
- //非响应式
- this.sliderData = Object.freeze(this.sliderData);
- }
- },
- }
- </script>
store.js
- export default {
- state: {
- //当前时间片
- currentPeriod: null,
- },
- mutations:{
- setCurrentPeriod(state, value) {
- state.currentPeriod = value;
- },
- },
- action:{
- updateCurrentPeriod({ commit }, newPeriod) {
- commit("setCurrentPeriod", newPeriod);
- }
- }
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。