|
@@ -0,0 +1,236 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="isShow"
|
|
|
|
|
+ class="user-guide"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div
|
|
|
|
|
+ ref="target-mark"
|
|
|
|
|
+ class="target-mark"
|
|
|
|
|
+ :style="{
|
|
|
|
|
+ top: markTop,
|
|
|
|
|
+ left: markLeft,
|
|
|
|
|
+ width: markWidth,
|
|
|
|
|
+ height: markHeight,
|
|
|
|
|
+ borderRadius: padding + 'px',
|
|
|
|
|
+ }"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="guideInfo && guideInfo[currentStep]"
|
|
|
|
|
+ ref="dialog"
|
|
|
|
|
+ class="dialog"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div
|
|
|
|
|
+ ref="arrow"
|
|
|
|
|
+ class="arrow"
|
|
|
|
|
+ />
|
|
|
|
|
+ <h2>{{ guideInfo[currentStep].title }}</h2>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="content"
|
|
|
|
|
+ v-html="guideInfo[currentStep].content"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="page">
|
|
|
|
|
+ {{ currentStep + 1 }}/{{ guideInfo.length }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-show="currentStep > 0"
|
|
|
|
|
+ class="prev"
|
|
|
|
|
+ @click="currentStep--"
|
|
|
|
|
+ >
|
|
|
|
|
+ < 上一条
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="skip"
|
|
|
|
|
+ @click="isShow = false"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ currentStep === guideInfo.length - 1 ? '完成' : '跳过' }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-show="currentStep < guideInfo.length - 1"
|
|
|
|
|
+ class="next"
|
|
|
|
|
+ @click="currentStep++"
|
|
|
|
|
+ >
|
|
|
|
|
+ 下一条 >
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { computePosition, offset, flip, shift, arrow } from '@floating-ui/dom'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ props: {
|
|
|
|
|
+ },
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ padding: 5,
|
|
|
|
|
+
|
|
|
|
|
+ isShow: false,
|
|
|
|
|
+
|
|
|
|
|
+ guideInfo: [],
|
|
|
|
|
+ currentStep: -1,
|
|
|
|
|
+
|
|
|
|
|
+ markTop: '',
|
|
|
|
|
+ markLeft: '',
|
|
|
|
|
+ markWidth: '',
|
|
|
|
|
+ markHeight: '',
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ watch: {
|
|
|
|
|
+ currentStep: {
|
|
|
|
|
+ handler(v) {
|
|
|
|
|
+ if (v !== -1) {
|
|
|
|
|
+ this.getCurrentGuideInfo()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ created() {
|
|
|
|
|
+ this.$root.userGuide = this
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ beginGuide(guideInfo) {
|
|
|
|
|
+ // guideInfo = [
|
|
|
|
|
+ // {
|
|
|
|
|
+ // selector: ...,
|
|
|
|
|
+ // title: ...,
|
|
|
|
|
+ // content: ...,
|
|
|
|
|
+ // },
|
|
|
|
|
+ // ]
|
|
|
|
|
+ this.isShow = true
|
|
|
|
|
+ this.guideInfo = guideInfo
|
|
|
|
|
+ this.currentStep = 0
|
|
|
|
|
+ },
|
|
|
|
|
+ getCurrentGuideInfo() {
|
|
|
|
|
+ // 设置高亮区域的位置
|
|
|
|
|
+ const selector = this.guideInfo[this.currentStep].selector
|
|
|
|
|
+ const targetElem = document.querySelector(selector)
|
|
|
|
|
+ const rect = targetElem.getBoundingClientRect()
|
|
|
|
|
+ this.markTop = rect.y - this.padding + 'px'
|
|
|
|
|
+ this.markLeft = rect.x - this.padding + 'px'
|
|
|
|
|
+ if (this.guideInfo[this.currentStep].customWidth) {
|
|
|
|
|
+ this.markWidth = `calc(${this.guideInfo[this.currentStep].customWidth} + ${this.padding}px * 2)`
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.markWidth = `calc(${rect.width}px + ${this.padding}px * 2)`
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.guideInfo[this.currentStep].customHeight) {
|
|
|
|
|
+ this.markHeight = `calc(${this.guideInfo[this.currentStep].customHeight} + ${this.padding}px * 2)`
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.markHeight = `calc(${rect.height}px + ${this.padding}px * 2)`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ // 设置对话框的位置
|
|
|
|
|
+ computePosition(this.$refs['target-mark'], this.$refs.dialog, {
|
|
|
|
|
+ placement: 'bottom',
|
|
|
|
|
+ middleware: [
|
|
|
|
|
+ offset(2 * this.$oneRemToPx),
|
|
|
|
|
+ flip(),
|
|
|
|
|
+ shift({ padding: 12 }),
|
|
|
|
|
+ arrow({
|
|
|
|
|
+ element: this.$refs.arrow,
|
|
|
|
|
+ padding: 3,
|
|
|
|
|
+ }),
|
|
|
|
|
+ ],
|
|
|
|
|
+ }).then(({ x, y, placement, middlewareData }) => {
|
|
|
|
|
+ Object.assign(this.$refs.dialog.style, {
|
|
|
|
|
+ left: `${x}px`,
|
|
|
|
|
+ top: `${y}px`,
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const { x: arrowX, y: arrowY } = middlewareData.arrow
|
|
|
|
|
+ const arrowPlacement = {
|
|
|
|
|
+ top: 'bottom',
|
|
|
|
|
+ right: 'left',
|
|
|
|
|
+ bottom: 'top',
|
|
|
|
|
+ left: 'right',
|
|
|
|
|
+ }[placement.split('-')[0]]
|
|
|
|
|
+ const dialogPlacement2arrowRotate = {
|
|
|
|
|
+ top: '-135deg',
|
|
|
|
|
+ right: '-45deg',
|
|
|
|
|
+ bottom: '45deg',
|
|
|
|
|
+ left: '135deg',
|
|
|
|
|
+ }
|
|
|
|
|
+ Object.assign(this.$refs.arrow.style, {
|
|
|
|
|
+ top: 'initial',
|
|
|
|
|
+ bottom: 'initial',
|
|
|
|
|
+ left: 'initial',
|
|
|
|
|
+ right: 'initial',
|
|
|
|
|
+ })
|
|
|
|
|
+ Object.assign(this.$refs.arrow.style, {
|
|
|
|
|
+ left: arrowX != null ? `${arrowX}px` : '',
|
|
|
|
|
+ [arrowPlacement]: -this.$oneRemToPx + 'px',
|
|
|
|
|
+ transform: `scaleX(0.7) rotate(${dialogPlacement2arrowRotate[placement]})`,
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="less" scoped>
|
|
|
|
|
+.user-guide {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ > .target-mark {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ box-shadow: 0px 0px 0px 5000px rgba(34, 34, 34, 0.80);
|
|
|
|
|
+ }
|
|
|
|
|
+ > .dialog {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ border: 0.08rem solid #D8B275;
|
|
|
|
|
+ border-radius: 0.5rem;
|
|
|
|
|
+ background-color: rgba(34, 34, 34, 1);
|
|
|
|
|
+ padding: 1.54rem;
|
|
|
|
|
+ > .arrow {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ background-color: inherit;
|
|
|
|
|
+ width: 2rem;
|
|
|
|
|
+ height: 2rem;
|
|
|
|
|
+ border: 0.08rem solid transparent;
|
|
|
|
|
+ border-left: inherit;
|
|
|
|
|
+ border-top: inherit;
|
|
|
|
|
+ }
|
|
|
|
|
+ > h2 {
|
|
|
|
|
+ font-size: 2rem;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #D8B275;
|
|
|
|
|
+ line-height: 2.34rem;
|
|
|
|
|
+ margin-bottom: 0.42rem;
|
|
|
|
|
+ }
|
|
|
|
|
+ > .content {
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ line-height: 1.76rem;
|
|
|
|
|
+ margin-bottom: 2.42rem;
|
|
|
|
|
+ }
|
|
|
|
|
+ > button {
|
|
|
|
|
+ width: 8.33rem;
|
|
|
|
|
+ height: 4.17rem;
|
|
|
|
|
+ border-radius: 0.21rem 0.21rem 0.21rem 0.21rem;
|
|
|
|
|
+ border: 0.08rem solid #D8B275;
|
|
|
|
|
+ min-width: 8.33rem;
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ margin-right: 0.83rem;
|
|
|
|
|
+ &:last-of-type {
|
|
|
|
|
+ margin-right: initial;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ > .page {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 2rem;
|
|
|
|
|
+ right: 1.54rem;
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|