基于业务需求的vue-tour二次封装

616 阅读1分钟

1. 安装 vue-tour

npm install vue-tour

2. 二次封装

核心点:

<template v-for="(_, slotName) in $scopedSlots" #[slotName]="slotProps" > 
    <slot :name="slotName" v-bind="slotProps" :tour="tour"></slot> 
</template>
<template>
  <div class="demo-tour">
    <v-tour
      ref="v-tour"
      :name="name"
      :steps="customSteps"
      :callbacks="callbacks"
      :options="options"
    >
      <template slot-scope="tour">
        <Transition name="fade">
          <v-step
            v-if="tour.steps[tour.currentStep]"
            :key="tour.currentStep"
            :step="tour.steps[tour.currentStep]"
            :previous-step="tour.previousStep"
            :next-step="tour.nextStep"
            :stop="tour.stop"
            :skip="tour.skip"
            :finish="tour.finish"
            :is-first="tour.isFirst"
            :is-last="tour.isLast"
            :labels="tour.labels"
          >
            <template
              v-for="(_, slotName) in $scopedSlots"
              #[slotName]="slotProps"
            >
              <slot :name="slotName" v-bind="slotProps" :tour="tour"></slot>
            </template>

            <div slot="actions">
              <div
                v-if="steps.length === 1"
                class="v-step__buttons v-step__single-buttons"
              >
                <div
                  v-if="knowMore"
                  class="v-step__button-more v-step__button"
                  @click="callbacks.onMore(tour)"
                >
                  了解更多
                </div>
                <div
                  class="v-step__button-know v-step__button"
                  @click="handleFinish(tour)"
                >
                  我知道了
                </div>
              </div>

              <div v-else class="v-step__buttons">
                <div
                  class="v-step__button v-step__button-skip"
                  @click="tour.skip"
                >
                  跳过
                </div>
                <div>
                  <div
                    v-if="knowMore"
                    class="v-step__button-more v-step__button"
                    @click="callbacks.onMore(tour)"
                  >
                    了解更多
                  </div>
                  <div
                    v-if="tour.currentStep !== 0"
                    class="v-step__button v-step__button-previous"
                    @click="tour.previousStep"
                  >
                    <i class="iconfont icon-back"></i>
                    上一步
                  </div>
                  <div
                    v-if="tour.currentStep !== steps.length - 1"
                    class="v-step__button v-step__button-next"
                    @click="tour.nextStep"
                  >
                    下一步
                    <i class="iconfont icon-right"></i>
                  </div>
                  <div
                    v-if="tour.currentStep === steps.length - 1"
                    class="v-step__button v-step__button-finish"
                    @click="handleFinish(tour)"
                  >
                    完成
                  </div>
                </div>
              </div>
            </div>
          </v-step>
        </Transition>
      </template>
    </v-tour>
  </div>
</template>

<script>
export default {
  name: 'DemoTour',
  inheritAttrs: false,
  props: {
    // 唯一
    name: {
      type: String,
      default: ''
    },
    steps: {
      type: Array,
      default: () => []
    },
    // 了解更多按钮可配置,默认不展示
    knowMore: {
      type: Boolean,
      default: false
    },
    options: {
      type: Object,
      default: () => ({})
    },
    callbacks: {
      onStart: () => {},
      onPreviousStep: () => {},
      onNextStep: () => {},
      onStop: () => {},
      onSkip: () => {},
      onFinish: () => {},
      onMore: () => {}
    }
  },

  computed: {
    customSteps() {
      return this.steps.map((item) => {
        const formatStepOption = {
          header: {
            title: item.title
          },
          content: item.content,
          target: item.target,
          params: {
            highlight: true,
            enableScrolling: false,
            placement: item.placement || 'bottom'
          }
        };
        return formatStepOption;
      });
    }
  },
  methods: {
    start() {
      if (this.name && localStorage.getItem(this.name) !== 'true') {
        this.$tours[this.name].start();
      }
    },
    handleFinish(tour) {
      localStorage.setItem(this.name, true);
      tour.finish();
    }
  }
};
</script>

<style lang="scss" scoped>
@import url("vue-tour/dist/vue-tour.css");
.demo-tour {
  .v-tour {
    ::v-deep {
      .v-step {
        padding: 16px;
        min-width: 320px;
        max-width: fit-content;
        border-radius: 7px;

        // background-color: $blue-5;
        background-color: #0078ff;
        .v-step__header {
          margin: 0;
          padding: 0;
          font-size: 16px;
          font-weight: 600;
          border-radius: 7px;
          text-align: left;
          background-color: #0078ff;
          line-height: 24px;
          > div {
            font-size: 16px;
          }
        }
        .v-step__content {
          margin: 0;
          padding: 8px 0 16px;
          text-align: left;
          white-space: pre-wrap;
          line-height: 20px;
        }
        .v-step__buttons {
          display: flex;
          justify-content: space-between;
          align-items: center;
          .v-step__button {
            display: inline-flex;
            align-items: center;
            margin: 0;
            padding: 6px 8px;
            height: unset;
            font-size: 14px;
            border: none;
            border-radius: 5px;
            background: rgba(255, 255, 255, .2);
            line-height: 16px;
            .icon-back {
              margin-right: 4px;
            }
            .icon-right,
            .icon-check {
              margin-left: 4px;
            }
          }
          .v-step__button-skip,
          .v-step__button-more {
            color: #fff;
            background-color: transparent;
            &:hover {
              background: rgba(255, 255, 255, .4);
            }
          }
          .v-step__button-know,
          .v-step__button-previous,
          .v-step__button-next,
          .v-step__button-stop,
          .v-step__button-finish {
            margin-left: 8px;
            color: #0078ff;
            background: #fff;
            &:hover {
              background: rgba(255, 255, 255, .8);
            }
          }
          .v-step__button-more {
            background-color: transparent;
          }
        }
        .v-step__single-buttons {
          justify-content: flex-end;
        }
        .v-step__arrow {
          &::before {
            // background-color: $blue-5;
            background-color: #0078ff;
          }
        }
      }
    }
  }
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity .2s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
</style>

<style lang="scss">
#app .v-tour__target--highlighted {
  box-shadow: none !important;
}
</style>

Demo示例

 <el-button class="start-btn" size='mini' type='primary' @click=startSingleTour>默认单步</el-button>
    <div class="dom-list">
      <div class="dom-item">
        <span data-v-step="single-1">Step</span>
      </div>
    </div>
    <DemoTour
      ref="single"
      name='single'
      :steps="singleStep"
      :callbacks='callbacks'
      >
    </DemoTour>

<script>
export default {
  data() {
    return {
      singleStep: [
        {
          title: '标题',
          content: '这是一段很长长长长长长长长长长长长长长长的内容',
          target: '[data-v-step="single-1"]' // 模块-功能-step-id
        }
      ],
      callbacks:{
        onMore: this.getMoreTourInfo
      }
    }
  },

  methods: {
    startSingleTour(){
      this.$refs['single'].start();
    },
    getMoreTourInfo(tour) {
      console.log(tour)
      alert('more action!')
    }
  }
};
</script>

  <div>
    <el-button size='mini' type='primary' @click=startMultipleTour>默认多步</el-button>
    <div class="dom-list">
      <div class="dom-item">
        <span data-v-step="multiple-1">Step1</span>
      </div>
      <div class="dom-item">
        <span data-v-step="multiple-2">Step2</span>
      </div>
      <div class="dom-item">
        <span data-v-step="multiple-3">Step3</span>
      </div>
    </div>

    <DemoTour
      ref="multiple"
      name='multiple'
      :steps="steps"
      :callbacks='callbacks'
    ></DemoTour>
  </div>

<script>
export default {
  data() {
    return {
     callbacks:{
        onMore: this.getMoreTourInfo
      },
      steps: [
        {
          title: '标题1',
          content: '这是一段很长长长长长长长长长长长长长长长的内容1',
          target: '[data-v-step="multiple-1"]' // 模块-功能-step-id
        },
        {
          title: '标题2',
          content: '这是一段很长长长长长长长长长长长长长长长的内容2',
          target: '[data-v-step="multiple-2"]'
        },
        {
          title: '标题3',
          content: '这是一段很长长长长长长长长长长长长长长长的内容3',
          target: '[data-v-step="multiple-3"]',
          placement: 'right'
        }
      ]
    }
  },

  methods: {
      getMoreTourInfo(tour) {
      console.log(tour)
      alert('more action!')
    },
    startMultipleTour(){
      this.$refs['multiple'].start();
    }
  }
};
</script>

  <div>
    <el-button size='mini' type='primary' @click=startTour>自定义内容插槽</el-button>
    <div class="dom-list">
      <div class="dom-item">
        <span data-v-step="content-slot-1">Step1</span>
      </div>
      <div class="dom-item">
        <span data-v-step="content-slot-2">Step2</span>
      </div>
      <div class="dom-item">
        <span data-v-step="content-slot-3">Step3</span>
      </div>
    </div>

    <DemoTour
      ref="content-slot"
      name='content-slot'
      :steps="steps"
    >
      <template #content="{ tour }">
         <template v-if="tour.currentStep === 0">
            <img src='https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg' />
         </template>
        <template v-else-if="tour.currentStep === 1">
            <div style='padding: 16px'>
              自定义内容2333333
            </div>
         </template>
        <template v-else-if="tour.currentStep === 2">
          <img src='https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png' style="border-radius: 50%"/>
        </template>
      </template>
    </DemoTour>
  </div>

<script>
export default {
  data() {
    return {
      steps: [
        {
          title: '标题1',
          target: '[data-v-step="content-slot-1"]' // 模块-功能-step-id
        },
        {
          title: '标题2',
          target: '[data-v-step="content-slot-2"]',
        },
        {
          title: '标题3',
          target: '[data-v-step="content-slot-3"]',
          placement: 'right'
        }
      ]
    }
  },

  methods: {
    startTour(){
      this.$refs['content-slot'].start();
    }
  }
};
</script>
  <div>
    <el-button size='mini' type='primary' @click=startActionsSlotTour>自定义底部插槽</el-button>
    <div class="dom-list">
      <div class="dom-item">
        <span data-v-step="actions-slot-1">Step1</span>
      </div>
      <div class="dom-item">
        <span data-v-step="actions-slot-2">Step2</span>
      </div>
      <div class="dom-item">
        <span data-v-step="actions-slot-3">Step3</span>
      </div>
    </div>

    <DemoTour
      ref="actions"
      name='actions-slot'
      :steps="steps"
    >
 
      <template #actions="{ tour }">
        <template v-for="(_, index) in steps">
          <div
            v-if="index === tour.currentStep"
            :key="index + 'actions'"
            class="demo-step__btns"
          >
            <el-button
              type="text"
              size="mini"
              @click="tour.skip"
              style="color: #fff"
            >
              Skip
            </el-button>
            <div>
              <el-button
                v-if="
                  tour.currentStep !== 0 &&
                  tour.currentStep !== steps.length - 1
                "
                size="mini"
                type="primary"
                class="previous"
                @click="tour.previousStep"
              >
                Previous
              </el-button>
              <el-button
                v-if="tour.currentStep !== steps.length - 1"
                size="mini"
                type="primary"
                class="next"
                @click="tour.nextStep"
              >
                Next
              </el-button>
              <el-button
                v-if="tour.currentStep === steps.length - 1"
                size="mini"
                type="primary"
                class="next"
                @click="tour.finish"
              >
                Finish
              </el-button>
            </div>
          </div>
        </template>
      </template>
    </DemoTour>
  </div>

<script>
export default {
  data() {
    return {
      steps: [
        {
          title: '标题1',
          content: '这是一段很长长长长长长长长长长长长长长长的内容1',
          target: '[data-v-step="actions-slot-1"]' // 模块-功能-step-id
        },
        {
          title: '标题2',
          content: '这是一段很长长长长长长长长长长长长长长长的内容2',
          target: '[data-v-step="actions-slot-2"]',
        },
        {
          title: '标题3',
          content: '这是一段很长长长长长长长长长长长长长长长的内容3',
          target: '[data-v-step="actions-slot-3"]',
          placement: 'right'
        }
      ]
    }
  },

  methods: {
    startActionsSlotTour(){
      this.$refs['actions'].start();
    }
  }
};
</script>

屏幕录制2023-07-22 18.59.50.2023-07-22 19_00_28.gif

Attributes

参数说明类型可选值默认值
name1. 用于对外暴露vue-tour的同名start方法
2. 用于存入localStorage,name值唯一
String必选"demo-tour"
knowMore了解更多功能Boolean可选false
steps步骤配置Array必选元素属性:
title: "自定义"
content: "自定义"
target: "必填"
placement: "选填(left, top, right, bottom)", 默认值 bottom
options全局配置属性对象Object可选vue-tour
callbacks回调方法映射对象Object可选onStart
onPreviousStep
onNextStep
onStop
onSkip
onFinish
onMore (可选回调参数 tour )

Events

事件说明回调参数
start开始执行tour,使用 this.$refs[refName].start() 调用-

Scoped Slot

name说明
header自定义头部(使用方法参照上述demo)
content自定义内容(使用方法参照上述demo)
actions自定义行为(使用方法参照上述demo)