UNPKG

secure-action

Version:

Secure Action 是一个基于行为的验证码组件,适用于 Vue 3。

314 lines (313 loc) 13 kB
import { defineComponent, ref, computed, watch, watchPostEffect, onUnmounted, resolveDirective, openBlock, createBlock, Teleport, createElementVNode, normalizeClass, withModifiers, renderSlot, toDisplayString, createElementBlock, unref, createCommentVNode, withDirectives, createTextVNode, Fragment, renderList, normalizeStyle } from "vue"; import IconClose from "./assets/close.svg.mjs"; import fallbackImg from "./assets/fallback-img.svg.mjs"; import IconRefresh from "./assets/refresh.svg.mjs"; import { secureActionProps } from "./props.mjs"; import { addPoint } from "./utils.mjs"; import { useSecureActionStore } from "./hooks/useSecureActionStore.mjs"; const _hoisted_1 = { class: "secure-action__header" }; const _hoisted_2 = { class: "sa-title" }; const _hoisted_3 = ["src"]; const _hoisted_4 = { class: "secure-action__body" }; const _hoisted_5 = { class: "sa-thumbnail-wrap" }; const _hoisted_6 = /* @__PURE__ */ createElementVNode("label", null, [ /* @__PURE__ */ createTextVNode("请在下图"), /* @__PURE__ */ createElementVNode("mark", null, "依次"), /* @__PURE__ */ createTextVNode("点击:") ], -1); const _hoisted_7 = ["src", "alt"]; const _hoisted_8 = ["src"]; const _hoisted_9 = { class: "sa-loading-warpper" }; const _hoisted_10 = { key: 0, class: "loading-content" }; const _hoisted_11 = /* @__PURE__ */ createElementVNode("svg", { class: "loading-icon", xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none" }, [ /* @__PURE__ */ createElementVNode("path", { d: "M15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM3.1 8C3.1 10.7062 5.2938 12.9 8 12.9C10.7062 12.9 12.9 10.7062 12.9 8C12.9 5.2938 10.7062 3.1 8 3.1C5.2938 3.1 3.1 5.2938 3.1 8Z", fill: "white" }), /* @__PURE__ */ createElementVNode("path", { d: "M15 8C15 9.38447 14.5895 10.7378 13.8203 11.889C13.0511 13.0401 11.9579 13.9373 10.6788 14.4672C9.3997 14.997 7.99223 15.1356 6.63437 14.8655C5.2765 14.5954 4.02922 13.9287 3.05025 12.9497C2.07128 11.9708 1.4046 10.7235 1.1345 9.36563C0.864406 8.00777 1.00303 6.6003 1.53284 5.32122C2.06266 4.04213 2.95986 2.94888 4.11101 2.17971C5.26215 1.41054 6.61553 1 8 1V3.1C7.03087 3.1 6.08351 3.38738 5.27771 3.9258C4.4719 4.46422 3.84386 5.22949 3.47299 6.12485C3.10212 7.02021 3.00508 8.00544 3.19415 8.95594C3.38322 9.90645 3.8499 10.7795 4.53518 11.4648C5.22045 12.1501 6.09355 12.6168 7.04406 12.8058C7.99456 12.9949 8.97979 12.8979 9.87515 12.527C10.7705 12.1561 11.5358 11.5281 12.0742 10.7223C12.6126 9.91649 12.9 8.96913 12.9 8H15Z", fill: "#AEB5C5" }) ], -1); const _hoisted_12 = { class: "sa-image-wrap" }; const _hoisted_13 = ["src", "alt"]; const _hoisted_14 = { key: 0, class: "sa-fallback" }; const _hoisted_15 = ["src"]; const _hoisted_16 = ["disabled"]; const _sfc_main = /* @__PURE__ */ defineComponent({ ...{ directives: { "fallback-image": { mounted(el) { el.classList.remove("is-fallback"); el.addEventListener("error", () => { el.classList.add("is-fallback"); }); } } } }, __name: "SecureAction", props: secureActionProps, emits: ["visible-change"], setup(__props, { expose: __expose, emit }) { const props = __props; const status = ref(""); const isValidating = ref(false); const loading = ref(true); const visible = ref(false); const dots = ref([]); const points = ref([]); const formState = ref({ image: "", thumbnail: "" }); const okDisabled = computed(() => { return points.value.length < 1 || isValidating.value || loading.value || status.value === "SUCCESS"; }); const customMessage = ref(""); const statusText = computed(() => { if (customMessage.value.trim()) return customMessage.value; if (status.value === "SUCCESS") return props.successText || ""; if (status.value === "ERROR") return props.failText || ""; return ""; }); const handleListenerEsc = (event) => { if (event.key === "Escape" || event.key === "Esc") { if (visible.value && props.keyboard) { visible.value = false; } } }; const onError = (refresh, message) => { status.value = "ERROR"; customMessage.value = message || ""; if (refresh) { setTimeout(() => { handleRefresh(); status.value = ""; customMessage.value = ""; }, props.errorRefreshWait || 2e3); } }; const handleRefresh = () => { var _a; points.value = []; isValidating.value = false; (_a = props == null ? void 0 : props.request) == null ? void 0 : _a.call(props, { loading, onSuccess(image, thumbnail) { handleReset(false); formState.value.image = image; formState.value.thumbnail = thumbnail; }, onError: (message) => onError(false, message) }); }; const { openPromise, openResolve } = useSecureActionStore(); const handleOpen = async () => { visible.value = true; isValidating.value = false; formState.value.image = ""; formState.value.thumbnail = ""; handleRefresh(); document.addEventListener("keydown", handleListenerEsc); openPromise.value = new Promise((resolve) => { openResolve.value = resolve; }); return openPromise.value; }; watch(visible, (v) => { emit("visible-change", v); if (!v) handleReset(); }); const handleReset = (close = true) => { points.value = []; isValidating.value = false; formState.value.image = ""; formState.value.thumbnail = ""; customMessage.value = ""; status.value = ""; if (close) { visible.value = false; } }; const handleClose = (type) => { var _a; if (type === "mask" && !props.maskClosable) return; visible.value = false; (_a = openResolve.value) == null ? void 0 : _a.call(openResolve, false); }; watchPostEffect(() => { dots.value = props.flat ? points.value.flat() : points.value; }); const handleOk = () => { props.verify && props.verify({ loading, points: dots.value, onError: (message) => onError(true, message), onSuccess: (message) => { status.value = "SUCCESS"; customMessage.value = message || ""; setTimeout(() => { var _a; handleReset(); (_a = openResolve.value) == null ? void 0 : _a.call(openResolve, true); }, props.successCloseWait || 2e3); } }); }; const handleClickImage = (event) => { if (points.value.length === props.maxPoint) return; const newPoint = [event.offsetX, event.offsetY]; const res = addPoint(points.value, newPoint, props.r); points.value = res; }; onUnmounted(() => { document == null ? void 0 : document.removeEventListener("keydown", handleListenerEsc, false); }); __expose({ verify: handleOpen }); return (_ctx, _cache) => { var _a, _b, _c, _d; const _directive_fallback_image = resolveDirective("fallback-image"); return visible.value ? (openBlock(), createBlock(Teleport, { key: 0, to: "body" }, [ createElementVNode("div", { class: normalizeClass(["secure-action__mask", _ctx.maskClass]), onClick: _cache[3] || (_cache[3] = withModifiers(($event) => handleClose("mask"), ["self"])) }, [ createElementVNode("div", { class: normalizeClass(["secure-action", props.class]) }, [ createElementVNode("header", _hoisted_1, [ renderSlot(_ctx.$slots, "title", {}, () => [ createElementVNode("label", _hoisted_2, toDisplayString(_ctx.title), 1) ]), _ctx.closable ? (openBlock(), createElementBlock("button", { key: 0, onClick: _cache[0] || (_cache[0] = ($event) => handleClose("closable")), class: "sa-button is-icon no-border" }, [ createElementVNode("img", { src: unref(IconClose) }, null, 8, _hoisted_3) ])) : createCommentVNode("", true) ]), createElementVNode("section", _hoisted_4, [ createElementVNode("header", _hoisted_5, [ _hoisted_6, withDirectives((openBlock(), createElementBlock("img", { class: "sa-thumbnail", key: (_a = formState.value) == null ? void 0 : _a.thumbnail, src: (_b = formState.value) == null ? void 0 : _b.thumbnail, alt: _ctx.title }, null, 8, _hoisted_7)), [ [_directive_fallback_image] ]), createElementVNode("button", { onClick: handleRefresh, class: "sa-button is-icon" }, [ createElementVNode("img", { src: unref(IconRefresh) }, null, 8, _hoisted_8) ]) ]), createElementVNode("div", _hoisted_9, [ loading.value ? (openBlock(), createElementBlock("div", _hoisted_10, [ renderSlot(_ctx.$slots, "loading", {}, () => [ _hoisted_11, createTextVNode(" " + toDisplayString(isValidating.value ? _ctx.validatingText : _ctx.loadingText), 1) ]) ])) : createCommentVNode("", true), createElementVNode("main", _hoisted_12, [ (openBlock(true), createElementBlock(Fragment, null, renderList(points.value, (item, i) => { return openBlock(), createElementBlock("i", { class: "sa-point", style: normalizeStyle({ "--sa-point-x": `${item[0]}px`, "--sa-point-y": `${item[1]}px` }), key: String(item) }, toDisplayString(i + 1), 5); }), 128)), withDirectives((openBlock(), createElementBlock("img", { key: (_c = formState.value) == null ? void 0 : _c.image, class: normalizeClass(["sa-image", { "is-disabled": points.value.length === props.maxPoint }]), onClick: handleClickImage, onContextmenu: _cache[1] || (_cache[1] = withModifiers(() => { }, ["prevent", "stop"])), src: (_d = formState.value) == null ? void 0 : _d.image, alt: _ctx.title }, null, 42, _hoisted_13)), [ [_directive_fallback_image] ]), renderSlot(_ctx.$slots, "fallback", {}, () => [ !loading.value ? (openBlock(), createElementBlock("div", _hoisted_14, [ createElementVNode("img", { src: unref(fallbackImg), class: "sa-fallback__image" }, null, 8, _hoisted_15), createElementVNode("span", null, toDisplayString(_ctx.fallbackText), 1) ])) : createCommentVNode("", true) ]), renderSlot(_ctx.$slots, "status", { status: status.value, statusText: statusText.value }, () => [ createElementVNode("div", { class: normalizeClass([ "sa-status", { "is-success": status.value === "SUCCESS", "is-error": status.value === "ERROR", "is-show": !!statusText.value } ]) }, toDisplayString(statusText.value), 3) ]) ]) ]) ]), createElementVNode("footer", { class: normalizeClass(["secure-action__footer", [`is-${_ctx.footerAlign}`]]) }, [ !_ctx.hideCancel ? (openBlock(), createElementBlock("button", { key: 0, class: "sa-button", onClick: _cache[2] || (_cache[2] = ($event) => handleClose("cancel")) }, "取消")) : createCommentVNode("", true), createElementVNode("button", { disabled: okDisabled.value, class: "sa-button is-primary", onClick: handleOk }, " 确认 ", 8, _hoisted_16) ], 2) ], 2) ], 2) ])) : createCommentVNode("", true); }; } }); export { _sfc_main as default };