扫码登录

This commit is contained in:
目棃
2025-01-08 14:58:00 +08:00
parent f801363440
commit d5f40a5775
18 changed files with 1293 additions and 970 deletions

View File

@@ -1,4 +1,5 @@
<template>
<ToGameLogin v-model="showLoginQr" @success="tryGetTokens" />
<v-card class="tcu-box">
<template #prepend>
<v-avatar :image="userInfo.avatar" />
@@ -36,12 +37,18 @@
</template>
<template #actions>
<v-spacer />
<v-btn
variant="outlined"
@click="tryCaptchaLogin()"
icon="mdi-cellphone"
title="验证码登录"
/>
<!-- <v-btn-->
<!-- variant="outlined"-->
<!-- @click="showLoginQr = true"-->
<!-- icon="mdi-qrcode-scan"-->
<!-- title="扫码登录"-->
<!-- />-->
<!-- <v-btn-->
<!-- variant="outlined"-->
<!-- @click="tryCaptchaLogin()"-->
<!-- icon="mdi-cellphone"-->
<!-- title="验证码登录"-->
<!-- />-->
<v-btn
variant="outlined"
@click="confirmRefreshUser(uid!)"
@@ -62,6 +69,7 @@
variant="outlined"
icon="mdi-account-switch"
title="切换账户"
:disabled="accounts.length === 0"
@click="showMenu"
v-bind="props"
/>
@@ -90,10 +98,25 @@
/>
</template>
</v-list-item>
</v-list>
</v-menu>
<v-menu location="start">
<template v-slot:activator="{ props }">
<v-btn variant="outlined" icon="mdi-login" title="登录" v-bind="props" />
</template>
<v-list>
<v-list-item @click="addByCookie()" append-icon="mdi-account-plus">
<v-list-item-title>手动添加</v-list-item-title>
<v-list-item-subtitle>手动输入Cookie</v-list-item-subtitle>
</v-list-item>
<v-list-item @click="tryCaptchaLogin()" append-icon="mdi-cellphone">
<v-list-item-title>验证码登录</v-list-item-title>
<v-list-item-subtitle>使用手机号登录</v-list-item-subtitle>
</v-list-item>
<v-list-item @click="showLoginQr = true" append-icon="mdi-qrcode-scan">
<v-list-item-title>扫码登录</v-list-item-title>
<v-list-item-subtitle>使用米游社扫码登录</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-menu>
</template>
@@ -104,10 +127,11 @@ import showDialog from "@comp/func/dialog.js";
import showGeetest from "@comp/func/geetest.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import ToGameLogin from "@comp/pageConfig/tco-gameLogin.vue";
import Mys from "@Mys/index.js";
import TSUserAccount from "@Sqlite/modules/userAccount.js";
import { storeToRefs } from "pinia";
import { computed, shallowRef } from "vue";
import { computed, ref, shallowRef } from "vue";
import { useAppStore } from "@/store/modules/app.js";
import { useUserStore } from "@/store/modules/user.js";
@@ -119,6 +143,7 @@ import TakumiApi from "@/web/request/takumiReq.js";
const { isLogin } = storeToRefs(useAppStore());
const { uid, briefInfo, cookie, account } = storeToRefs(useUserStore());
const showLoginQr = ref<boolean>(false);
const accounts = shallowRef<Array<TGApp.App.Account.User>>([]);
const gameAccounts = shallowRef<Array<TGApp.Sqlite.Account.Game>>([]);
const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
@@ -131,37 +156,7 @@ const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
};
});
async function tryCaptchaLogin(): Promise<void> {
const phone = await showDialog.input("请输入手机号", "+86");
if (!phone) {
showSnackbar.cancel("已取消验证码登录");
return;
}
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(phone)) {
showSnackbar.warn("请输入正确的手机号");
return;
}
const actionType = await tryGetCaptcha(phone);
if (!actionType) return;
showSnackbar.success(`已发送验证码到 ${phone}`);
const captcha = await showDialog.input("请输入验证码", "验证码:", undefined, false);
if (!captcha) {
showSnackbar.warn("输入验证码为空");
return;
}
const loginResp = await tryLoginByCaptcha(phone, captcha, actionType);
if (!loginResp) return;
await showLoading.start("正在尝试登录...");
const ck: TGApp.App.Account.Cookie = {
account_id: loginResp.user_info.aid,
ltuid: loginResp.user_info.aid,
stuid: loginResp.user_info.aid,
mid: loginResp.user_info.mid,
cookie_token: "",
stoken: loginResp.token.token,
ltoken: "",
};
async function tryGetTokens(ck: TGApp.App.Account.Cookie): Promise<void> {
await showLoading.update("正在获取 LToken");
const ltokenRes = await PassportApi.lToken.get(ck);
if (typeof ltokenRes !== "string") {
@@ -231,6 +226,40 @@ async function tryCaptchaLogin(): Promise<void> {
showSnackbar.success("成功登录!");
}
async function tryCaptchaLogin(): Promise<void> {
const phone = await showDialog.input("请输入手机号", "+86");
if (!phone) {
showSnackbar.cancel("已取消验证码登录");
return;
}
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(phone)) {
showSnackbar.warn("请输入正确的手机号");
return;
}
const actionType = await tryGetCaptcha(phone);
if (!actionType) return;
showSnackbar.success(`已发送验证码到 ${phone}`);
const captcha = await showDialog.input("请输入验证码", "验证码:", undefined, false);
if (!captcha) {
showSnackbar.warn("输入验证码为空");
return;
}
const loginResp = await tryLoginByCaptcha(phone, captcha, actionType);
if (!loginResp) return;
await showLoading.start("正在尝试登录...");
const ck: TGApp.App.Account.Cookie = {
account_id: loginResp.user_info.aid,
ltuid: loginResp.user_info.aid,
stuid: loginResp.user_info.aid,
mid: loginResp.user_info.mid,
cookie_token: "",
stoken: loginResp.token.token,
ltoken: "",
};
await tryGetTokens(ck);
}
async function refreshUser(uid: string) {
let account = await TSUserAccount.account.getAccount(uid);
if (!account) {
@@ -375,6 +404,7 @@ async function tryGetCaptcha(phone: string, aigis?: string): Promise<string | fa
showSnackbar.error(`[${captchaResp.retcode}] ${captchaResp.message}`);
return false;
}
// @ts-expect-error type {} is not assignable to type string
const aigisResp: TGApp.Plugins.Mys.CaptchaLogin.CaptchaAigis = JSON.parse(captchaResp.data);
const resp = await showGeetest(JSON.parse(aigisResp.data));
const aigisStr = `${aigisResp.session_id};${btoa(JSON.stringify(resp))}`;

View File

@@ -0,0 +1,129 @@
<template>
<TOverlay v-model="model" hide blur-val="20px">
<div class="tog-box">
<div class="tog-top">
<div class="tog-title">请使用米游社进行扫码操作</div>
</div>
<div class="tog-mid">
<qrcode-vue
v-if="codeData"
class="tog-qr"
:value="codeData.url"
render-as="svg"
:background="'var(--box-bg-1)'"
foreground="var(--box-text-1)"
/>
</div>
</div>
</TOverlay>
</template>
<script setup lang="ts">
import TOverlay from "@comp/app/t-overlay.vue";
import showSnackbar from "@comp/func/snackbar.js";
import QrcodeVue from "qrcode.vue";
import { onUnmounted, shallowRef, watch } from "vue";
import PassportReq from "@/web/request/passportReq.js";
type ToGameLoginEmits = (e: "success", data: TGApp.App.Account.Cookie) => void;
// eslint-disable-next-line no-undef
let cycleTimer: NodeJS.Timeout | null = null;
const model = defineModel<boolean>({ default: false });
const emits = defineEmits<ToGameLoginEmits>();
const codeData = shallowRef<TGApp.BBS.GameLogin.GetLoginQrData>();
watch(model, async (value) => {
if (value) {
await freshQr();
cycleTimer = setInterval(cycleGetData, 1000);
}
});
async function freshQr(): Promise<void> {
const res = await PassportReq.qrLogin.create();
if ("retcode" in res) {
showSnackbar.error(`[${res.retcode}] ${res.message}`);
return;
}
codeData.value = res;
}
async function cycleGetData() {
if (cycleTimer === null || !codeData.value) return;
const res = await PassportReq.qrLogin.query(codeData.value.ticket);
console.log(res);
if ("retcode" in res) {
showSnackbar.error(`[${res.retcode}] ${res.message}`);
if (res.retcode === -106) {
await freshQr();
} else {
clearInterval(cycleTimer);
cycleTimer = null;
model.value = false;
}
return;
}
if (res.status === "Created" || res.status === "Scanned") return;
if (res.status === "Confirmed") {
clearInterval(cycleTimer);
cycleTimer = null;
const ck: TGApp.App.Account.Cookie = {
mid: res.user_info.mid,
stoken: res.tokens[0].token,
stuid: res.user_info.aid,
account_id: res.user_info.aid,
cookie_token: "",
ltoken: "",
ltuid: "",
};
emits("success", ck);
model.value = false;
}
}
onUnmounted(() => {
if (cycleTimer !== null) clearInterval(cycleTimer);
cycleTimer = null;
});
</script>
<style lang="css" scoped>
.tog-box {
display: flex;
flex-direction: column;
padding: 10px;
border-radius: 5px;
background-color: var(--box-bg-1);
color: var(--app-page-content);
gap: 10px;
}
.tog-top {
border-bottom: 1px solid var(--common-shadow-4);
font-family: var(--font-title);
text-align: center;
}
.tog-title {
color: var(--common-text-title);
font-size: 20px;
}
.tog-mid {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
aspect-ratio: 1;
background: var(--box-bg-2);
}
.tog-qr {
width: 256px;
height: 256px;
}
</style>