Files
console-dev/app/components/AuthProviders.vue
T
cneicy 42dfead9cd
前端验证 / verify (push) Failing after 15m3s
feat(ui): 优化控制台响应式布局与视觉体验
- 统一页面标题、圆角、字体和响应式断点,调整暗色主题及图表配色
- 新增表格列标签插件,在窄屏下将表格转换为卡片布局,优化实例、订单、工单等列表展示
- 支持移动端点击订单行进入详情,并完善订单凭据的打印布局
- 在财务中心展示本月及累计消费,补充充值关闭时的提示
- 优化工单详情布局,将附件上传入口整合至富文本编辑器工具栏
- 调整实例操作按钮、SSH 密钥操作及安全菜单图标
- 为多个页面显式导入 definePageMeta,完善页面元数据声明
2026-11-07 00:09:57 +08:00

737 lines
30 KiB
Vue

<script>
/**
* 登录页的微信扫码入口:申请二维码、轮询扫码状态、选择关联账号或转入绑定流程。
* 二维码轮询与倒计时共享代次 K;切换入口、收窄视口或卸载都使旧请求失效。
*/
import "~/assets/styles/AuthProviders.CgAWouSf.css";
import { toDisplayString } from "vue";
import { renderList } from "vue";
import { onMounted } from "vue";
import { onUnmounted } from "vue";
import { normalizeClass } from "vue";
import { watch } from "vue";
import { createBlock } from "vue";
import { ref } from "vue";
import { withCtx } from "vue";
import { createCommentVNode } from "vue";
import { computed } from "vue";
import { isRef } from "vue";
import { createElementBlock } from "vue";
import { Fragment } from "vue";
import { createElementVNode } from "vue";
import { createTextVNode } from "vue";
import { unref } from "vue";
import { defineComponent } from "vue";
import { createVNode } from "vue";
import { openBlock } from "vue";
import { normalizeStyle } from "vue";
import { useRoute } from "#app";
import { useRouter } from "#app";
import { loginErrorMessage } from "~/lib/auth.js";
import { useApi } from "~/lib/auth.js";
import { isValidJwt } from "~/lib/auth.js";
import { useAuth } from "~/lib/auth.js";
import { t as useSiteConfig_t } from "~/lib/useSiteConfig.js";
import button from "primevue/button";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
import message from "primevue/message";
import radiobutton from "primevue/radiobutton";
import { r as wechat_binding_r } from "~/lib/wechat-binding.js";
import { r as auth_redirect_r } from "~/lib/auth-redirect.js";
var fe = {
key: 0,
class: `auth-providers`,
"aria-label": `其他登录方式`,
},
S = [`id`, `aria-labelledby`],
pe = {
key: 0,
class: `login-qr`,
},
me = {
class: `qr-stage`,
},
he = {
key: 0,
class: `qr-clients`,
},
ge = [`aria-busy`],
_e = [`src`],
ve = {
class: `qr-status`,
"aria-live": `polite`,
},
ye = {
key: 1,
class: `qr-tip`,
},
be = {
class: `qr-actions`,
},
xe = [
`id`,
`aria-selected`,
`aria-controls`,
`tabindex`,
`disabled`,
`title`,
`aria-label`,
`onClick`,
],
Se = 5,
Ce = 300,
C = Object.assign(
_plugin_vue_export_helper_t(
defineComponent({
__name: `AuthProviders`,
setup(ee, { expose: b }) {
let C = useAuth(),
w = useRoute(),
T = useRouter(),
E = useApi(),
{ load: D, scanLoginEnabled: O, error: we } = useSiteConfig_t(),
k = ref(!1),
A = null;
function j() {
((k.value = A?.matches ?? !1), k.value || Q());
}
let M = computed(() => [
{
id: `wechat`,
label: `微信`,
enabled: O.value,
},
{
id: `other`,
label: `其他方式`,
enabled: !1,
},
]),
N = ref(0),
P = computed(() => M.value[N.value]),
F = ref([]);
function Te(e) {
let t = M.value.flatMap((e, t) => (e.enabled ? [t] : []));
if (!t.length) return;
let n = t.indexOf(N.value),
r = n;
if (e.key === `ArrowRight`) r = (n + 1) % t.length;
else if (e.key === `ArrowLeft`) r = (n + t.length - 1) % t.length;
else if (e.key === `Home`) r = 0;
else if (e.key === `End`) r = t.length - 1;
else return;
(e.preventDefault(), (N.value = t[r]), F.value[N.value]?.focus());
}
let I = ref(null),
L = ref(!1),
R = ref(!1),
z = ref(``),
B = ref([]),
V = ref(null),
H = ref(!1),
U = ref(0),
W = null,
G = null,
K = 0,
q = null;
function J() {
W !== null && (clearInterval(W), (W = null));
}
function Y() {
(G !== null && (clearInterval(G), (G = null)), (U.value = 0));
}
function Ee() {
// 扫码状态每两秒查询;二维码按服务端到期时间提前五秒刷新,缺省有效期为五分钟。
(J(), Y(), (W = window.setInterval(De, 2e3)));
let e = Math.floor(Date.now() / 1e3),
t = I.value?.expire_time ? I.value.expire_time - e : Ce;
((U.value = Math.max(1, t - Se)),
(G = window.setInterval(() => {
(--U.value, U.value <= 0 && (Y(), X()));
}, 1e3)));
}
async function X() {
// 只在桌面展示区域申请新二维码,并核对图片、ticket 与 token 的完整性。
if (!k.value || L.value) return;
let e = ++K;
(Z(),
(z.value = ``),
(R.value = !1),
(B.value = []),
(V.value = null),
(H.value = !1),
(L.value = !0));
try {
let t = await E.get(`/mp_weixin_notice/qrcode`, void 0, !1, {
timeoutMs: 15e3,
});
if (e !== K) return;
if (
typeof t?.img_url != `string` ||
!t.img_url ||
typeof t.ticket != `string` ||
!t.ticket ||
typeof t.token != `string` ||
!t.token
)
throw Error(`二维码加载失败,请重新获取`);
((I.value = t), (z.value = ``), Ee());
} catch (t) {
if (e !== K) return;
(J(),
Y(),
(z.value = loginErrorMessage(t, `二维码加载失败,请稍后重试`)));
} finally {
e === K && (L.value = !1);
}
}
async function De() {
// 同一代次最多一个查询在途;Wait/Success/Expired/SelectClient/WaitBind 分别处理。
if (!I.value || q === K) return;
let e = K,
t = I.value;
q = e;
try {
let n = await E.get(
`/mp_weixin_notice/qrcode/status`,
{
ticket: t.ticket,
token: t.token,
},
!1,
{
timeoutMs: 1e4,
},
);
if (e !== K) return;
if (!n || typeof n.status != `string`)
throw Error(`扫码登录状态异常,请重新获取二维码`);
if (n.status === `Wait`) return;
if (n.status === `Success`) {
n.jwt
? $(n.jwt)
: (Z(),
(z.value = `登录响应无效,请重新扫码;若持续失败,请联系客服。`));
return;
}
if (n.status === `Expired`) {
(J(),
Y(),
(R.value = !0),
(z.value = `二维码已过期,请刷新后重新扫码`));
return;
}
if (n.status === `SelectClient`) {
(J(),
Y(),
(B.value = Array.isArray(n.client)
? n.client.filter(
(e) =>
e &&
Number.isSafeInteger(e.id) &&
e.id > 0 &&
typeof e.username == `string`,
)
: []),
(V.value = B.value.find((e) => e.status === 1)?.id ?? null),
V.value ||
(z.value = `当前微信没有可登录的账号,请使用账号密码登录或联系客服`));
return;
}
if (n.status === `WaitBind`) {
(Z(),
wechat_binding_r(t, sessionStorage),
await T.push({
path: `/oauth/bind`,
query: {
provider: `wechat`,
redirect: auth_redirect_r(w.query.redirect),
},
}));
return;
}
(Z(), (z.value = `扫码登录状态异常,请重新获取二维码`));
} catch (t) {
if (e !== K) return;
(Z(),
(z.value = loginErrorMessage(
t,
`扫码登录验证失败,请刷新二维码后重试`,
)));
} finally {
q === e && (q = null);
}
}
function Z() {
(J(), Y());
}
function Q() {
// 同时废弃二维码和关联账号选择,不能让旧 ticket 在新一次扫码中继续使用。
(K++,
Z(),
(I.value = null),
(L.value = !1),
(H.value = !1),
(R.value = !1),
(z.value = ``),
(B.value = []),
(V.value = null));
}
function $(e) {
// 成功响应仍需经过 JWT 格式校验,登录完成后的返回路径由统一安全跳转规则约束。
if ((Z(), !isValidJwt(e))) {
z.value = `登录响应无效,请重新扫码;若持续失败,请联系客服。`;
return;
}
(C.loginWithJwt(e), K++, T.push(auth_redirect_r(w.query.redirect)));
}
async function Oe() {
// 多账号扫码只能提交当前返回列表中明确可登录的账号,且请求绑定当前二维码代次。
if (H.value) return;
if (
!I.value ||
!B.value.some((e) => e.id === V.value && e.status === 1)
) {
z.value = `请选择一个可登录的账号`;
return;
}
((z.value = ``), (H.value = !0));
let e = K;
try {
let t = await E.post(
`/mp_weixin_notice/qrcode/select_client_login`,
{
ticket: I.value.ticket,
token: I.value.token,
client_id: V.value,
},
!1,
{
timeoutMs: 15e3,
},
);
if (e !== K) return;
t?.jwt
? $(t.jwt)
: (z.value = `登录响应无效,请重新扫码;若持续失败,请联系客服。`);
} catch (t) {
if (e !== K) return;
z.value = loginErrorMessage(t);
} finally {
e === K && (H.value = !1);
}
}
return (
onMounted(async () => {
((A = window.matchMedia(`(min-width: 769px)`)),
j(),
A.addEventListener(`change`, j),
await D().catch(() => {}));
}),
watch(N, Q),
onUnmounted(() => {
(A?.removeEventListener(`change`, j), Q());
}),
b({
resetQr: Q,
}),
(n, r) => {
let a = radiobutton,
s = button,
u = message;
return unref(k)
? (openBlock(),
createElementBlock(`aside`, fe, [
createElementVNode(
`div`,
{
id: `provider-panel-${unref(P).id}`,
role: `tabpanel`,
"aria-labelledby": `provider-tab-${unref(P).id}`,
class: `provider-panel`,
},
[
unref(O)
? (openBlock(),
createElementBlock(`div`, pe, [
createElementVNode(`div`, me, [
unref(B).length
? (openBlock(),
createElementBlock(`div`, he, [
(r[1] ||= createElementVNode(
`p`,
{
class: `qr-tip`,
},
`请选择要登录的账号`,
-1,
)),
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(B),
(t) => (
openBlock(),
createElementBlock(
`label`,
{
key: t.id,
class: normalizeClass([
`qr-client`,
{
disabled: t.status !== 1,
},
]),
},
[
createVNode(
a,
{
modelValue: unref(V),
"onUpdate:modelValue":
(r[0] ||= (e) =>
isRef(V)
? (V.value = e)
: null),
"input-id": `qr-client-${t.id}`,
value: t.id,
disabled: t.status !== 1,
},
null,
8,
[
`modelValue`,
`input-id`,
`value`,
`disabled`,
],
),
createElementVNode(
`span`,
null,
toDisplayString(t.username),
1,
),
],
2,
)
),
),
128,
)),
createVNode(
s,
{
label: `登录`,
loading: unref(H),
disabled: !unref(V) || unref(H),
class: `qr-client-btn`,
fluid: ``,
onClick: Oe,
},
null,
8,
[`loading`, `disabled`],
),
]))
: (openBlock(),
createElementBlock(
`div`,
{
key: 1,
class: normalizeClass([
`qr-box`,
{
"qr-box-idle": !unref(I),
},
]),
"aria-busy": unref(L),
},
[
unref(I) && !unref(R)
? (openBlock(),
createElementBlock(
`img`,
{
key: 0,
class: normalizeClass([
`qr-img`,
{
"qr-img-dim": unref(L),
},
]),
src: unref(I).img_url,
alt: `微信扫码登录`,
},
null,
10,
_e,
))
: (openBlock(),
createBlock(
s,
{
key: 1,
type: `button`,
label: unref(R)
? `刷新二维码`
: `获取二维码`,
icon: unref(R)
? `pi pi-refresh`
: `pi pi-qrcode`,
severity: `secondary`,
text: ``,
class: `qr-request`,
loading: unref(L),
disabled: unref(L),
onClick: X,
},
null,
8,
[
`label`,
`icon`,
`loading`,
`disabled`,
],
)),
],
10,
ge,
)),
]),
createElementVNode(`div`, ve, [
unref(z)
? (openBlock(),
createBlock(
u,
{
key: 0,
severity: `error`,
closable: !1,
icon: `pi pi-exclamation-circle`,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(z)),
1,
),
]),
_: 1,
},
))
: (openBlock(),
createElementBlock(
`p`,
ye,
toDisplayString(
unref(L) && !unref(I)
? `二维码加载中…`
: unref(B).length
? `选择账号后继续登录`
: `使用微信扫一扫登录`,
),
1,
)),
]),
createElementVNode(`div`, be, [
unref(B).length
? (openBlock(),
createBlock(s, {
key: 0,
type: `button`,
label: `返回重新扫码`,
severity: `secondary`,
text: ``,
size: `small`,
onClick: X,
}))
: unref(I) && !unref(R)
? (openBlock(),
createBlock(
s,
{
key: 1,
type: `button`,
label: `刷新二维码`,
icon: `pi pi-refresh`,
severity: `secondary`,
outlined: ``,
size: `small`,
loading: unref(L),
disabled: unref(L),
onClick: X,
},
null,
8,
[`loading`, `disabled`],
))
: createCommentVNode(``, !0),
]),
]))
: createCommentVNode(``, !0),
unref(O)
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(
Fragment,
{
key: 1,
},
[
(r[2] ||= createElementVNode(
`div`,
{
class: `provider-qr-placeholder`,
"aria-hidden": `true`,
},
[
createElementVNode(`i`, {
class: `pi pi-qrcode`,
}),
],
-1,
)),
(r[3] ||= createElementVNode(
`p`,
{
class: `provider-heading`,
},
`微信扫码登录`,
-1,
)),
(r[4] ||= createElementVNode(
`p`,
{
class: `provider-description`,
},
`扫码登录暂不可用,请使用手机号或邮箱登录`,
-1,
)),
],
64,
)),
unref(we)
? (openBlock(),
createBlock(
u,
{
key: 2,
severity: `warn`,
closable: !1,
class: `provider-config-error`,
},
{
default: withCtx(() => [
(r[5] ||= createTextVNode(
`登录方式加载失败。`,
-1,
)),
createVNode(
s,
{
type: `button`,
label: `重试`,
text: ``,
size: `small`,
onClick: unref(D),
},
null,
8,
[`onClick`],
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
],
8,
S,
),
createElementVNode(
`div`,
{
class: `provider-tabs`,
role: `tablist`,
"aria-label": `扫码登录方式`,
onKeydown: Te,
},
[
createElementVNode(
`span`,
{
class: `provider-tab-indicator`,
style: normalizeStyle({
transform: `translateX(${unref(N) * 100}%)`,
}),
"aria-hidden": `true`,
},
null,
4,
),
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(M),
(t, n) => (
openBlock(),
createElementBlock(
`button`,
{
id: `provider-tab-${t.id}`,
key: t.id,
ref_for: !0,
ref_key: `providerTabs`,
ref: F,
type: `button`,
role: `tab`,
"aria-selected": unref(N) === n,
"aria-controls": t.enabled
? `provider-panel-${t.id}`
: void 0,
tabindex: unref(N) === n ? 0 : -1,
disabled: !t.enabled,
title: t.enabled
? void 0
: `${t.label}暂未开放`,
"aria-label": t.enabled
? `${t.label}登录`
: `${t.label}暂未开放`,
class: normalizeClass({
active: unref(N) === n,
}),
onClick: (e) => (N.value = n),
},
toDisplayString(t.label),
11,
xe,
)
),
),
128,
)),
],
32,
),
]))
: createCommentVNode(``, !0);
}
);
},
}),
[[`__scopeId`, `data-v-9d0ee2f9`]],
),
{
__name: `AuthProviders`,
},
);
export {};
export default C;
</script>