vue3-intro-step
Version:
基于vue3的系统引导步骤组件。
412 lines (359 loc) • 16.2 kB
JavaScript
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 };