UNPKG

vue3-intro-step

Version:

基于vue3的系统引导步骤组件。

412 lines (359 loc) 16.2 kB
import { defineComponent, openBlock, createBlock, Transition, withCtx, createElementBlock, createElementVNode, normalizeStyle, toDisplayString, createCommentVNode, renderSlot, pushScopeId, popScopeId } from 'vue'; var _this = undefined; const throttle = (fn, delay) => { let timerId = null; let flag = true; return function () { if (!flag) return; flag = false; for (var _len = arguments.length, arg = new Array(_len), _key = 0; _key < _len; _key++) { arg[_key] = arguments[_key]; } let args = arg; timerId && clearTimeout(timerId); timerId = setTimeout(() => { flag = true; fn.apply(_this, args); }, delay || 1000); }; }; var script = /*#__PURE__*/defineComponent({ name: 'Vue3IntroStep', props: { show: { type: Boolean, required: true }, config: { type: Object, required: true } }, emits: ['update:show'], data() { return { // 聚焦盒子的信息 originalBox: { left: 250, top: 250, width: 200, height: 100 }, // 提示盒子的位置 tipBoxPosition: 'bottom', // 当前显示的提示进度索引 currentIndex: 0 }; }, watch: { config: { deep: true, handler() { // 监听配置变化 重置当前显示的索引 this.currentIndex = 0; }, immediate: true }, show(val) { if (val) { this.setBoxInfo(); } else { // 允许页面滚动 document.body.style.overflow = 'auto'; } } }, computed: { // 计算提示盒子的位置 // eslint-disable-next-line vue/return-in-computed-property tipBoxStyle() { // 如果提示盒子的位置是right if (this.tipBoxPosition === 'right') { return { left: `${this.originalBox.left + this.originalBox.width}px`, top: `${this.originalBox.top}px` }; } else if (this.tipBoxPosition === 'left') { return { right: `${window.innerWidth - this.originalBox.left}px`, top: `${this.originalBox.top}px` }; } else if (this.tipBoxPosition === 'top') { return { left: `${this.originalBox.left}px`, bottom: `${window.innerHeight - this.originalBox.top}px` }; } else if (this.tipBoxPosition === 'bottom') { return { left: `${this.originalBox.left > window.innerWidth - 300 ? window.innerWidth - 300 : this.originalBox.left}px`, top: `${this.originalBox.top + this.originalBox.height}px` }; } } }, created() { this.init(); }, mounted() { window.onresize = throttle(() => { if (this.show) { this.setBoxInfo(); } }, 100); }, beforeUnmount() { window.onresize = null; }, methods: { async prev() { // 判断是否有onPrev 是否可以继续往下走 let flag = true; if (this.config.tips[this.currentIndex] && this.config.tips[this.currentIndex].onPrev) { flag = await this.config.tips[this.currentIndex].onPrev(); } // 如果不能继续往下走 if (!flag) { throw new Error('onPrev 需要 Promise.resolve(true) 才可以继续往下走'); } this.setBoxInfo(this.currentIndex - 1); }, async next() { // 判断是否有onNext 是否可以继续往下走 let flag = true; if (this.config.tips[this.currentIndex] && this.config.tips[this.currentIndex].onNext) { flag = await this.config.tips[this.currentIndex].onNext(); } // 如果不能继续往下走 if (!flag) { throw new Error('onNext 需要 Promise.resolve(true) 才可以继续往下走'); } this.setBoxInfo(this.currentIndex + 1); }, done() { this.$emit('update:show', false); }, // 根据标签获取盒子的位置 async setBoxInfo(index) { try { if (index === undefined) { index = this.currentIndex; } if (this.show) { // 禁止页面滚动 document.body.style.overflow = 'hidden'; } let el = this.config.tips[index].el; let box = document.querySelector(el); if (!box) { throw new Error('没有找到相应的元素'); } let rect = box.getBoundingClientRect(); this.originalBox = { left: rect.left, top: rect.top, width: rect.width, height: rect.height }; this.tipBoxPosition = this.config.tips[index].tipPosition; this.currentIndex = index; } catch (e) { throw new Error(e.message); } }, // 根据配置初始化 init() { // 获取config中的tips数组 const { tips } = this.config; let timer = null; // 判断tips是否存在 并且tips是否是数组 if (tips && Array.isArray(tips)) { // 如果tips存在 并且tips是数组 // 判断tips数组是否有数据 if (tips.length > 0) { // 如果tips数组有数据 // 初始化当前提示进度索引 this.currentIndex = 0; // 获取第一个盒子 try { let firstBox = document.querySelector(tips[0].el); timer = setInterval(() => { firstBox = document.querySelector(tips[0].el); if (firstBox) { // 如果第一个盒子存在 this.setBoxInfo(0); clearInterval(timer); } }, 0); } catch (e) { throw new Error(e.message); } } else { throw new Error('tips数组不能为空'); } } else { throw new Error('config中的tips不存在或者不是数组'); } } } }); const _withScopeId = n => (pushScopeId("data-v-5d3b253c"), n = n(), popScopeId(), n); const _hoisted_1 = { key: 0, id: "intro_box" }; const _hoisted_2 = /*#__PURE__*/_withScopeId(() => /*#__PURE__*/createElementVNode("div", { class: "round round-flicker" }, null, -1)); const _hoisted_3 = [_hoisted_2]; const _hoisted_4 = { class: "tip-content" }; const _hoisted_5 = { class: "action", style: { justifyContent: 'center' } }; function render(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createBlock(Transition, { name: "custom-classes-transition", "enter-active-class": "animate__animated animate__fadeIn animate__faster", "leave-active-class": "animate__animated animate__fadeOut animate__faster" }, { default: withCtx(() => [_ctx.show ? (openBlock(), createElementBlock("div", _hoisted_1, [createElementVNode("div", { class: "top", style: normalizeStyle({ height: `${_ctx.originalBox.top}px`, backgroundColor: `rgba(0, 0, 0, ${_ctx.config.backgroundOpacity ? _ctx.config.backgroundOpacity : 0.9})` }) }, null, 4), createElementVNode("div", { class: "content", style: normalizeStyle({ height: `${_ctx.originalBox.height}px` }) }, [createElementVNode("div", { class: "left", style: normalizeStyle({ top: `${_ctx.originalBox.top}px`, width: `${_ctx.originalBox.left}px`, height: `${_ctx.originalBox.height}px`, backgroundColor: `rgba(0, 0, 0, ${_ctx.config.backgroundOpacity ? _ctx.config.backgroundOpacity : 0.9})` }) }, null, 4), createElementVNode("div", { class: "original-box", style: normalizeStyle({ top: `${_ctx.originalBox.top}px`, left: `${_ctx.originalBox.left}px`, width: `${_ctx.originalBox.width}px`, height: `${_ctx.originalBox.height}px` }) }, _hoisted_3, 4), createElementVNode("div", { class: "tip-box", style: normalizeStyle(_ctx.tipBoxStyle) }, [createElementVNode("div", _hoisted_4, [_ctx.config.tips[_ctx.currentIndex].title ? (openBlock(), createElementBlock("div", { key: 0, class: "title", style: normalizeStyle({ textAlign: _ctx.config.titleStyle ? _ctx.config.titleStyle.textAlign ? _ctx.config.titleStyle.textAlign : 'center' : 'center', fontSize: _ctx.config.titleStyle ? _ctx.config.titleStyle.fontSize ? _ctx.config.titleStyle.fontSize : '19px' : '19px' }) }, toDisplayString(_ctx.config.tips[_ctx.currentIndex].title), 5)) : createCommentVNode("", true), createElementVNode("div", { class: "content", style: normalizeStyle({ textAlign: _ctx.config.contentStyle ? _ctx.config.contentStyle.textAlign ? _ctx.config.contentStyle.textAlign : 'center' : 'center', fontSize: _ctx.config.contentStyle ? _ctx.config.contentStyle.fontSize ? _ctx.config.contentStyle.fontSize : '15px' : '15px' }) }, toDisplayString(_ctx.config.tips[_ctx.currentIndex].content), 5), createElementVNode("div", _hoisted_5, [_ctx.currentIndex !== 0 ? renderSlot(_ctx.$slots, "prev", { key: 0, index: _ctx.currentIndex, tipItem: _ctx.config.tips[_ctx.currentIndex] }, () => [createElementVNode("div", { class: "item prev", onClick: _cache[0] || (_cache[0] = function () { return _ctx.prev && _ctx.prev(...arguments); }) }, "上一步")]) : createCommentVNode("", true), _ctx.currentIndex !== _ctx.config.tips.length - 1 ? renderSlot(_ctx.$slots, "next", { key: 1, index: _ctx.currentIndex, tipItem: _ctx.config.tips[_ctx.currentIndex] }, () => [createElementVNode("div", { class: "item next", onClick: _cache[1] || (_cache[1] = function () { return _ctx.next && _ctx.next(...arguments); }) }, "下一步")]) : createCommentVNode("", true), _ctx.currentIndex === _ctx.config.tips.length - 1 ? renderSlot(_ctx.$slots, "done", { key: 2, index: _ctx.currentIndex, tipItem: _ctx.config.tips[_ctx.currentIndex] }, () => [createElementVNode("div", { class: "item done", onClick: _cache[2] || (_cache[2] = function () { return _ctx.done && _ctx.done(...arguments); }) }, "完成")]) : renderSlot(_ctx.$slots, "skip", { key: 3, index: _ctx.currentIndex, tipItem: _ctx.config.tips[_ctx.currentIndex] }, () => [createElementVNode("div", { class: "item skip", onClick: _cache[3] || (_cache[3] = function () { return _ctx.done && _ctx.done(...arguments); }) }, "跳过")])])])], 4), createElementVNode("div", { class: "right", style: normalizeStyle({ top: `${_ctx.originalBox.top}px`, left: `${_ctx.originalBox.left + _ctx.originalBox.width}px`, width: `calc(100% - ${_ctx.originalBox.left + _ctx.originalBox.width}px)`, height: `${_ctx.originalBox.height}px`, backgroundColor: `rgba(0, 0, 0, ${_ctx.config.backgroundOpacity ? _ctx.config.backgroundOpacity : 0.9})` }), ref: "tip_box" }, null, 4)], 4), createElementVNode("div", { class: "bottom", style: normalizeStyle({ height: `calc(100% - ${_ctx.originalBox.top}px - ${_ctx.originalBox.height}px)`, backgroundColor: `rgba(0, 0, 0, ${_ctx.config.backgroundOpacity ? _ctx.config.backgroundOpacity : 0.9})` }) }, null, 4)])) : createCommentVNode("", true)]), _: 3 }); } function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css_248z = "\n#intro_box[data-v-5d3b253c] {\n position: fixed;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n z-index: 99999;\n}\n#intro_box > .top[data-v-5d3b253c] {\n width: 100%;\n}\n#intro_box > .content[data-v-5d3b253c] {\n width: 100%;\n}\n#intro_box > .content > .left[data-v-5d3b253c] {\n position: absolute;\n left: 0;\n}\n#intro_box > .content > .original-box[data-v-5d3b253c] {\n position: absolute;\n background-color: transparent;\n transition: all 0.3s cubic-bezier(0, 0, 0.58, 1);\n}\n#intro_box > .content > .original-box .round[data-v-5d3b253c] {\n position: absolute;\n left: 10px;\n top: 50%;\n transform: translateY(-50%);\n width: 10px;\n height: 10px;\n border-radius: 50%;\n opacity: 0.65;\n background-color: #9900ff;\n}\n#intro_box > .content > .original-box .round-flicker[data-v-5d3b253c]:before,\n#intro_box > .content > .original-box .round-flicker[data-v-5d3b253c]:after {\n content: '';\n width: 100%;\n height: 100%;\n position: absolute;\n left: -1px;\n top: -1px;\n box-shadow: #9900ff 0px 0px 2px 2px;\n border: 1px solid rgba(153, 0, 255, 0.5);\n border-radius: 50%;\n animation: warn-5d3b253c 2s linear 0s infinite;\n}\n@keyframes warn-5d3b253c {\n0% {\n transform: scale(0.5);\n opacity: 1;\n}\n25% {\n transform: scale(1);\n opacity: 0.75;\n}\n50% {\n transform: scale(1.5);\n opacity: 0.5;\n}\n75% {\n transform: scale(2);\n opacity: 0.25;\n}\n100% {\n transform: scale(2.5);\n opacity: 0;\n}\n}\n#intro_box > .content > .tip-box[data-v-5d3b253c] {\n position: absolute;\n /*宽度应为内容宽*/\n width: fit-content;\n max-width: 300px;\n box-sizing: border-box;\n /*高度应为内容高度*/\n height: fit-content;\n transition: all 0.3s;\n z-index: 99999;\n padding: 12px;\n font-size: 15px;\n}\n#intro_box > .content > .tip-box > .tip-content[data-v-5d3b253c] {\n border-radius: 10px;\n overflow: hidden;\n padding: 10px;\n color: #fff;\n}\n#intro_box > .content > .tip-box > .tip-content > .title[data-v-5d3b253c] {\n font-weight: bold;\n margin-bottom: 10px;\n}\n#intro_box > .content > .tip-box > .tip-content > .content[data-v-5d3b253c] {\n white-space: normal;\n overflow-wrap: break-word;\n line-height: 1.5;\n}\n#intro_box > .content > .tip-box > .tip-content > .action[data-v-5d3b253c] {\n margin-top: 15px;\n width: 100%;\n display: flex;\n}\n#intro_box > .content > .tip-box > .tip-content > .action > .item[data-v-5d3b253c] {\n display: flex;\n justify-content: center;\n align-items: center;\n text-align: center;\n border-radius: 15px;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.3s;\n padding: 5px 15px;\n color: #fff;\n font-weight: bold;\n border: 1px solid #ccc;\n margin: 5px;\n}\n#intro_box > .content > .tip-box > .tip-content > .action > .item.prev[data-v-5d3b253c] {\n color: #ccc;\n}\n#intro_box > .content > .tip-box > .tip-content > .action > .item.next[data-v-5d3b253c] {\n color: #ccc;\n}\n#intro_box > .content > .tip-box > .tip-content > .action > .item.done[data-v-5d3b253c] {\n color: #ccc;\n}\n#intro_box > .content > .tip-box > .tip-content > .action > .item.skip[data-v-5d3b253c] {\n color: #ccc;\n}\n#intro_box > .content > .right[data-v-5d3b253c] {\n position: absolute;\n background-color: rgba(0, 0, 0, 0.9);\n}\n#intro_box > .bottom[data-v-5d3b253c] {\n width: 100%;\n background-color: rgba(0, 0, 0, 0.9);\n}\n"; styleInject(css_248z); script.render = render; script.__scopeId = "data-v-5d3b253c"; // Import vue component // IIFE injects install function into component, allowing component // to be registered via Vue.use() as well as Vue.component(), var entry_esm = /*#__PURE__*/(() => { // Assign InstallableComponent type const installable = script; // Attach install function executed by Vue.use() installable.install = app => { app.component('Vue3IntroStep', installable); }; return installable; })(); // It's possible to expose named exports when writing components that can // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; // export const RollupDemoDirective = directive; export { entry_esm as default };