Files
console-dev/app/components/GeetestField.vue
T

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>