379 lines
12 KiB
Vue
379 lines
12 KiB
Vue
<script>
|
|
/** 极验 V4 浏览器适配:脚本加载复用,但每轮挑战独立创建/销毁,通过 verified 回传四项证明字段。 */
|
|
import "~/assets/styles/GeetestField.DiieTibK.css";
|
|
import { toDisplayString } from "vue";
|
|
import { onMounted } from "vue";
|
|
import { normalizeClass } from "vue";
|
|
import { onBeforeUnmount } from "vue";
|
|
import { watch } from "vue";
|
|
import { ref } from "vue";
|
|
import { computed } from "vue";
|
|
import { createElementBlock } from "vue";
|
|
import { createElementVNode } from "vue";
|
|
import { unref } from "vue";
|
|
import { defineComponent } from "vue";
|
|
import { openBlock } from "vue";
|
|
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
|
|
var p = `https://static.geetest.com/v4/gt4.js`,
|
|
m = 1e4,
|
|
h = 8e3,
|
|
g = null;
|
|
function _() {
|
|
let e = window.initGeetest4;
|
|
return typeof e == `function` ? e : void 0;
|
|
}
|
|
function v() {
|
|
// 合并并发脚本加载,超时或失败后清空共享 Promise,使用户可重新加载而非永久卡住。
|
|
if (typeof window > `u` || typeof document > `u`)
|
|
return Promise.reject(Error(`请在浏览器中完成人机验证`));
|
|
let e = _();
|
|
return e
|
|
? Promise.resolve(e)
|
|
: g ||
|
|
((g = new Promise((e, t) => {
|
|
let n = document.createElement(`script`),
|
|
r = !1,
|
|
i = (i) => {
|
|
if (r) return;
|
|
((r = !0),
|
|
window.clearTimeout(a),
|
|
(n.onload = null),
|
|
(n.onerror = null));
|
|
let o = _();
|
|
i || !o
|
|
? (n.remove(), t(i ?? Error(`验证组件加载异常,请重试`)))
|
|
: e(o);
|
|
},
|
|
a = window.setTimeout(
|
|
() => i(Error(`验证组件加载超时,请重试`)),
|
|
m,
|
|
);
|
|
((n.src = p),
|
|
(n.async = !0),
|
|
(n.onload = () => i()),
|
|
(n.onerror = () => i(Error(`验证组件加载失败,请检查网络后重试`))),
|
|
document.head.appendChild(n));
|
|
}).catch((e) => {
|
|
throw ((g = null), e);
|
|
})),
|
|
g);
|
|
}
|
|
function y(e) {
|
|
// 仅挑选后端协议要求的非空验证字段,不透传第三方对象的额外属性。
|
|
if (!e || typeof e != `object` || Array.isArray(e)) return null;
|
|
let t = e,
|
|
n = [`lot_number`, `captcha_output`, `pass_token`, `gen_time`];
|
|
return n.some((e) => typeof t[e] != `string` || !t[e].trim())
|
|
? null
|
|
: Object.fromEntries(n.map((e) => [e, t[e]]));
|
|
}
|
|
function b(e) {
|
|
if (!e || typeof e != `object`) return !1;
|
|
let t = e;
|
|
return [
|
|
`onReady`,
|
|
`onSuccess`,
|
|
`onClose`,
|
|
`onError`,
|
|
`onBoxShow`,
|
|
`getValidate`,
|
|
`showCaptcha`,
|
|
`destroy`,
|
|
].every((e) => typeof t[e] == `function`);
|
|
}
|
|
function x(e, t) {
|
|
// 单轮挑战生命周期;销毁幂等,迟到或重复的 SDK 实例也要释放,避免遗留验证浮层。
|
|
let n = !1,
|
|
r = !1,
|
|
i = !1,
|
|
a = null,
|
|
o,
|
|
s,
|
|
c = new WeakSet(),
|
|
l = (e) => {
|
|
if (!e || typeof e != `object` || c.has(e)) return;
|
|
c.add(e);
|
|
let t = e.destroy;
|
|
if (typeof t == `function`)
|
|
try {
|
|
t.call(e);
|
|
} catch {}
|
|
},
|
|
u = () => {
|
|
(window.clearTimeout(o), window.clearTimeout(s), (o = s = void 0));
|
|
},
|
|
d = () => {
|
|
n || ((n = !0), (r = i = !1), u(), l(a), (a = null));
|
|
},
|
|
f = (e) => {
|
|
n || (d(), t.onError(Error(e)));
|
|
};
|
|
return (
|
|
e.trim()
|
|
? v()
|
|
.then((c) => {
|
|
if (!n) {
|
|
o = window.setTimeout(() => f(`验证组件初始化超时,请重试`), m);
|
|
try {
|
|
c(
|
|
{
|
|
captchaId: e,
|
|
product: `bind`,
|
|
language: `zho`,
|
|
},
|
|
(e) => {
|
|
if (n) {
|
|
l(e);
|
|
return;
|
|
}
|
|
if (a) {
|
|
e !== a && l(e);
|
|
return;
|
|
}
|
|
if ((window.clearTimeout(o), !b(e))) {
|
|
(l(e), f(`验证组件初始化异常,请重试`));
|
|
return;
|
|
}
|
|
((a = e),
|
|
(o = window.setTimeout(
|
|
() => f(`验证组件准备超时,请重试`),
|
|
m,
|
|
)),
|
|
e.onSuccess(() => {
|
|
if (n || !r || !i) return;
|
|
let a = null;
|
|
try {
|
|
a = y(e.getValidate());
|
|
} catch {}
|
|
if (!a) {
|
|
f(`验证结果无效,请重新验证`);
|
|
return;
|
|
}
|
|
(d(), t.onSuccess(a));
|
|
}),
|
|
e.onClose(() => {
|
|
!n && i && (d(), t.onClose());
|
|
}),
|
|
e.onError(() => f(`人机验证异常,请重试`)),
|
|
e.onBoxShow(() => {
|
|
!n && i && (window.clearTimeout(s), (s = void 0));
|
|
}),
|
|
e.onReady(() => {
|
|
n ||
|
|
r ||
|
|
(window.clearTimeout(o),
|
|
(o = void 0),
|
|
(r = !0),
|
|
t.onReady());
|
|
}));
|
|
},
|
|
);
|
|
} catch {
|
|
f(`验证组件初始化失败,请重试`);
|
|
}
|
|
}
|
|
})
|
|
.catch((e) =>
|
|
f(e instanceof Error ? e.message : `验证组件加载失败,请重试`),
|
|
)
|
|
: Promise.resolve().then(() => f(`人机验证尚未配置,请联系管理员`)),
|
|
{
|
|
show() {
|
|
if (!(n || i)) {
|
|
if (!r || !a) {
|
|
f(`验证组件尚未就绪,请重试`);
|
|
return;
|
|
}
|
|
((i = !0),
|
|
(s = window.setTimeout(
|
|
() =>
|
|
f(`验证面板未能显示,请检查网络或联系管理员确认极验域名配置`),
|
|
h,
|
|
)));
|
|
try {
|
|
a.showCaptcha();
|
|
} catch {
|
|
f(`验证面板打开失败,请重试`);
|
|
}
|
|
}
|
|
},
|
|
destroy: d,
|
|
}
|
|
);
|
|
}
|
|
var S = {
|
|
class: `geetest-field`,
|
|
},
|
|
C = [`disabled`, `aria-busy`, `aria-label`],
|
|
w = {
|
|
key: 0,
|
|
class: `pi pi-spin pi-spinner`,
|
|
"aria-hidden": `true`,
|
|
},
|
|
T = {
|
|
key: 1,
|
|
class: `pi pi-check-circle`,
|
|
"aria-hidden": `true`,
|
|
},
|
|
E = {
|
|
key: 2,
|
|
class: `pi pi-exclamation-circle`,
|
|
"aria-hidden": `true`,
|
|
},
|
|
D = {
|
|
key: 3,
|
|
class: `pi pi-shield`,
|
|
"aria-hidden": `true`,
|
|
},
|
|
O = [`role`],
|
|
k = Object.assign(
|
|
_plugin_vue_export_helper_t(
|
|
defineComponent({
|
|
__name: `GeetestField`,
|
|
props: {
|
|
captchaId: {},
|
|
disabled: {
|
|
type: Boolean,
|
|
},
|
|
},
|
|
emits: [`verified`, `invalid`],
|
|
setup(u, { emit: f }) {
|
|
let p = u,
|
|
m = f,
|
|
h = ref(`loading`),
|
|
g = ref(``),
|
|
_ = null,
|
|
v = 0,
|
|
y = !1,
|
|
b = computed(() =>
|
|
h.value === `loading`
|
|
? `正在加载验证…`
|
|
: h.value === `verifying`
|
|
? `请完成安全验证…`
|
|
: h.value === `verified`
|
|
? `验证通过,点击重新验证`
|
|
: h.value === `error`
|
|
? `重新加载验证`
|
|
: `点击完成验证`,
|
|
),
|
|
k = computed(
|
|
() => h.value === `loading` || h.value === `verifying`,
|
|
);
|
|
function A() {
|
|
(v++, _?.destroy(), (_ = null));
|
|
}
|
|
function j(e = !1) {
|
|
// 重建时先通知父级旧证明无效,再以代次验证 SDK 回调,拒绝旧挑战覆盖新状态。
|
|
if ((A(), m(`invalid`), (g.value = ``), !y || p.disabled)) {
|
|
h.value = `idle`;
|
|
return;
|
|
}
|
|
h.value = `loading`;
|
|
let t = v,
|
|
n = () => y && !p.disabled && t === v;
|
|
_ = x(p.captchaId, {
|
|
onReady() {
|
|
n() && ((h.value = `ready`), e && M());
|
|
},
|
|
onSuccess(e) {
|
|
n() && ((_ = null), (h.value = `verified`), m(`verified`, e));
|
|
},
|
|
onClose() {
|
|
n() &&
|
|
((_ = null),
|
|
(h.value = `idle`),
|
|
(g.value = `已取消验证,请重试`),
|
|
m(`invalid`));
|
|
},
|
|
onError(e) {
|
|
n() &&
|
|
((_ = null),
|
|
(h.value = `error`),
|
|
(g.value = e.message),
|
|
m(`invalid`));
|
|
},
|
|
});
|
|
}
|
|
function M() {
|
|
// 必须由用户点击显示 bind 验证面板;组件未就绪时先加载,准备完成后再打开。
|
|
if (!(p.disabled || k.value)) {
|
|
if (!_) {
|
|
j(!0);
|
|
return;
|
|
}
|
|
((g.value = ``), m(`invalid`), (h.value = `verifying`), _.show());
|
|
}
|
|
}
|
|
return (
|
|
watch(
|
|
() => [p.captchaId, p.disabled],
|
|
([e], [t]) => {
|
|
y && (e !== t || h.value !== `verified`) && j();
|
|
},
|
|
{
|
|
flush: `sync`,
|
|
},
|
|
),
|
|
onMounted(() => {
|
|
((y = !0), j());
|
|
}),
|
|
onBeforeUnmount(() => {
|
|
((y = !1), A());
|
|
}),
|
|
(t, r) => (
|
|
openBlock(),
|
|
createElementBlock(`div`, S, [
|
|
createElementVNode(
|
|
`button`,
|
|
{
|
|
type: `button`,
|
|
class: normalizeClass([
|
|
`geetest-trigger`,
|
|
{
|
|
verified: unref(h) === `verified`,
|
|
"has-error": !!unref(g),
|
|
},
|
|
]),
|
|
disabled: u.disabled || unref(k),
|
|
"aria-busy": unref(k),
|
|
"aria-label": unref(g) || unref(b),
|
|
onClick: M,
|
|
},
|
|
[
|
|
unref(k)
|
|
? (openBlock(), createElementBlock(`i`, w))
|
|
: unref(h) === `verified`
|
|
? (openBlock(), createElementBlock(`i`, T))
|
|
: unref(g)
|
|
? (openBlock(), createElementBlock(`i`, E))
|
|
: (openBlock(), createElementBlock(`i`, D)),
|
|
createElementVNode(
|
|
`span`,
|
|
{
|
|
role: unref(g) ? `alert` : `status`,
|
|
"aria-live": `polite`,
|
|
"aria-atomic": `true`,
|
|
},
|
|
toDisplayString(unref(g) || unref(b)),
|
|
9,
|
|
O,
|
|
),
|
|
],
|
|
10,
|
|
C,
|
|
),
|
|
])
|
|
)
|
|
);
|
|
},
|
|
}),
|
|
[[`__scopeId`, `data-v-4cd14cdc`]],
|
|
),
|
|
{
|
|
__name: `GeetestField`,
|
|
},
|
|
);
|
|
export {};
|
|
export default k;
|
|
</script>
|