♻️ 调整目录结构

This commit is contained in:
目棃
2024-11-19 14:45:29 +08:00
parent e1f85d1d92
commit 3fef8467f4
121 changed files with 532 additions and 554 deletions

View File

@@ -0,0 +1,105 @@
<template>
<div class="tab-box">
<img class="tab-icon" src="/icon.webp" alt="App" />
<div class="tab-info click" title="点击前往 Github Release" @click="toRelease()">
TeyvatGuide Beta
</div>
<div class="tab-info">v{{ versionApp }}.{{ buildTime === "" ? "Dev" : buildTime }}</div>
<div class="tab-links">
<div class="tab-link" @click="toGroup()" title="点击加入反馈群">
<img src="/platforms/other/qq.webp" alt="qq" />
</div>
<div class="tab-link" @click="toGithub()" title="点击查看仓库">
<img src="/platforms/other/github.webp" alt="github" />
</div>
<div class="tab-link" @click="toStore()" title="点击查看商店页面">
<img src="/platforms/other/microsoft-store.webp" alt="store" />
</div>
<div class="tab-link" @click="toSite()" title="点击查看更新说明">
<v-icon color="white">mdi-update</v-icon>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { app } from "@tauri-apps/api";
import { computed, onMounted, ref } from "vue";
import { useAppStore } from "../../store/modules/app.js";
const appStore = useAppStore();
const versionApp = ref<string>();
const buildTime = computed(() => appStore.buildTime);
onMounted(async () => {
versionApp.value = await app.getVersion();
});
function toRelease() {
window.open("https://github.com/BTMuli/TeyvatGuide/releases/latest");
}
function toGroup() {
window.open("https://h5.qun.qq.com/s/3cgX0hJ4GA");
}
function toGithub() {
window.open("https://github.com/BTMuli/TeyvatGuide");
}
function toStore() {
window.open("https://www.microsoft.com/store/productId/9NLBNNNBNSJN");
}
function toSite() {
window.open("https://app.btmuli.ink/docs/TeyvatGuide/changelogs.html");
}
</script>
<style lang="css" scoped>
.tab-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px;
border-radius: 10px;
background-image: linear-gradient(to right, #f78ca0 0%, #f9748f 19%, #fd868c 60%, #fe9a8b 100%);
box-shadow: 0 0 10px var(--common-shadow-2);
}
.tab-icon {
width: 200px;
aspect-ratio: 1 / 1;
}
.tab-info {
color: var(--tgc-white-1);
font-family: var(--font-title);
font-size: 14px;
text-align: center;
text-shadow: 0 0 2px rgb(19 84 122 / 80%);
}
.tab-info.click {
color: var(--tgc-yellow-1);
cursor: pointer;
}
.tab-links {
display: flex;
backdrop-filter: blur(20px);
column-gap: 10px;
}
.tab-link {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.tab-link img {
width: 32px;
height: 32px;
}
</style>

View File

@@ -0,0 +1,282 @@
<template>
<v-list class="config-list">
<v-list-subheader :inset="true" class="config-header" title="路径" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item title="用户数据目录" :subtitle="appStore.userDir.value">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-folder-key</v-icon>
</div>
</template>
<template #append>
<div class="config-opers">
<v-icon @click="confirmCUD()" title="修改用户数据目录"> mdi-pencil</v-icon>
<v-icon @click="openPath('user')" title="打开用户数据目录"> mdi-folder-open</v-icon>
<v-icon @click="copyPath('user')" title="复制用户数据目录路径"> mdi-content-copy</v-icon>
</div>
</template>
</v-list-item>
<v-list-item title="应用数据库路径" :subtitle="appStore.dbPath.value">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-folder-account</v-icon>
</div>
</template>
<template #append>
<div class="config-opers">
<v-icon @click="openPath('db')" title="打开数据库目录"> mdi-folder-open</v-icon>
<v-icon @click="copyPath('db')" title="复制数据库目录路径"> mdi-content-copy</v-icon>
</div>
</template>
</v-list-item>
<v-list-item
title="游戏安装目录"
:subtitle="appStore.gameDir.value"
v-if="platform() === 'windows'"
>
<template #prepend>
<div class="config-icon">
<v-icon>mdi-gamepad</v-icon>
</div>
</template>
<template #append>
<div class="config-opers">
<v-icon @click="confirmCGD()" title="修改游戏安装目录"> mdi-pencil</v-icon>
<v-icon @click="openPath('game')" title="打开游戏安装目录"> mdi-folder-open</v-icon>
<v-icon @click="copyPath('game')" title="复制游戏安装目录"> mdi-content-copy</v-icon>
</div>
</template>
</v-list-item>
<v-list-item title="日志目录" :subtitle="appStore.logDir.value">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-folder-multiple</v-icon>
</div>
</template>
<template #append>
<div class="config-opers">
<v-icon @click="confirmCLD()" title="清理日志文件"> mdi-delete</v-icon>
<v-icon @click="openPath('log')" title="打开日志目录"> mdi-folder-open</v-icon>
<v-icon @click="copyPath('log')" title="复制日志目录路径"> mdi-content-copy</v-icon>
</div>
</template>
</v-list-item>
</v-list>
</template>
<script lang="ts" setup>
import { path } from "@tauri-apps/api";
import { open } from "@tauri-apps/plugin-dialog";
import { exists, readDir, remove } from "@tauri-apps/plugin-fs";
import { platform } from "@tauri-apps/plugin-os";
import { storeToRefs } from "pinia";
import { onMounted } from "vue";
import TGSqlite from "../../plugins/Sqlite/index.js";
import { useAppStore } from "../../store/modules/app.js";
import { backUpUserData } from "../../utils/dataBS.js";
import TGShell from "../../utils/TGShell.js";
import showDialog from "../func/dialog.js";
import showSnackbar from "../func/snackbar.js";
const appStore = storeToRefs(useAppStore());
onMounted(async () => {
const logDir = await path.appLogDir();
const dbPath = `${await path.appConfigDir()}${path.sep()}TeyvatGuide.db`;
let message = "";
if (appStore.dbPath.value !== dbPath) {
appStore.dbPath.value = dbPath;
await TGSqlite.saveAppData("dbPath", dbPath);
message += "数据库路径 ";
}
if (appStore.logDir.value !== logDir) {
appStore.logDir.value = logDir;
message += "日志路径 ";
}
if (message !== "") {
showSnackbar.success(`${message}已更新!`);
}
});
async function confirmCUD(): Promise<void> {
const oriDir = appStore.userDir.value;
const changeCheck = await showDialog.check("确认修改用户数据路径吗?");
if (!changeCheck) {
showSnackbar.cancel("已取消修改");
return;
}
const dir: string | null = await open({
directory: true,
defaultPath: oriDir,
multiple: false,
});
if (dir === null) {
showSnackbar.warn("路径不能为空!");
return;
}
if (dir === oriDir) {
showSnackbar.warn("路径未修改!");
return;
}
appStore.userDir.value = dir;
await TGSqlite.saveAppData("userDir", dir);
await backUpUserData(dir);
showSnackbar.success("已修改用户数据路径!");
const delCheck = await showDialog.check("是否删除原用户数据目录?");
if (delCheck) {
await remove(oriDir, { recursive: true });
showSnackbar.success("已删除原用户数据目录!");
}
setTimeout(() => window.location.reload(), 4000);
}
async function confirmCGD(): Promise<void> {
if (platform() !== "windows") {
showSnackbar.warn("不支持的平台!");
return;
}
const oriEmpty = appStore.gameDir.value === "未设置";
const editCheck = await showDialog.check(
oriEmpty ? "确认设置游戏目录?" : "确认修改游戏目录?",
oriEmpty ? "请选择启动器所在目录" : `当前:${appStore.gameDir.value}`,
);
if (!editCheck) {
showSnackbar.cancel(oriEmpty ? "已取消设置" : "已取消修改");
return;
}
const dir: string | null = await open({
directory: true,
defaultPath: oriEmpty ? undefined : appStore.gameDir.value,
multiple: false,
});
if (dir === null) {
showSnackbar.warn("路径不能为空!");
return;
}
if (!oriEmpty && appStore.gameDir.value === dir) {
showSnackbar.warn("路径未修改!");
return;
}
// 校验是否存在游戏本体
if (!(await exists(`${dir}${path.sep()}YuanShen.exe`))) {
showSnackbar.warn("未检测到游戏本体");
return;
}
appStore.gameDir.value = dir;
showSnackbar.success(oriEmpty ? "成功设置游戏目录" : "成功修改游戏目录");
}
// 判断是否超过一周
function isOverWeek(date: string): boolean {
const nowTs = Date.now();
const checkTs = new Date(date).getTime();
const weekTs = 7 * 24 * 60 * 60 * 1000;
return nowTs - checkTs >= weekTs;
}
async function confirmCLD(): Promise<void> {
const delCheck = await showDialog.check("确认清理日志文件吗?", "将保留一周内的日志文件");
if (!delCheck) {
showSnackbar.cancel("已取消清理");
return;
}
const logDir = appStore.logDir.value;
const files = await readDir(logDir);
const delFiles = files.filter((file) => {
// yyyy-mm-dd.log
const reg = /(\d{4}-\d{2}-\d{2}\.log)/;
const match = file.name.match(reg);
if (!Array.isArray(match) || match.length < 1) return false;
const date = match[1].replace(".log", "");
return isOverWeek(date);
});
if (delFiles.length < 1) {
showSnackbar.warn("无需清理!");
return;
}
for (const file of delFiles) {
const filePath = `${logDir}/${file.name}`;
await remove(filePath);
}
showSnackbar.success(`已清理 ${delFiles.length} 个日志文件!`);
}
function copyPath(type: "db" | "user" | "log" | "game"): void {
let targetPath: string, targetName: string;
switch (type) {
case "db":
targetPath = appStore.dbPath.value;
targetName = "数据库";
break;
case "user":
targetPath = appStore.userDir.value;
targetName = "用户数据";
break;
case "log":
targetPath = appStore.logDir.value;
targetName = "日志";
break;
case "game":
if (appStore.gameDir.value === "未设置") {
showSnackbar.warn("未设置游戏目录!");
return;
}
targetPath = appStore.gameDir.value;
targetName = "游戏安装目录";
}
navigator.clipboard.writeText(targetPath);
showSnackbar.success(`${targetName}路径已复制!`);
}
async function openPath(type: "db" | "user" | "log" | "game"): Promise<void> {
let targetPath: string;
switch (type) {
case "db":
targetPath = await path.appConfigDir();
break;
case "user":
targetPath = appStore.userDir.value;
break;
case "log":
targetPath = appStore.logDir.value;
break;
case "game":
if (appStore.gameDir.value === "未设置") {
showSnackbar.warn("未设置游戏目录!");
return;
}
targetPath = appStore.gameDir.value;
break;
}
await TGShell.openPath(targetPath);
}
</script>
<style lang="css" scoped>
.config-header {
margin-top: 10px;
color: var(--common-text-title);
font-family: var(--font-title);
font-size: large;
}
.config-icon {
display: flex;
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
padding: 5px;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
margin-right: 15px;
background: var(--box-bg-2);
color: var(--box-text-2);
}
.config-opers {
display: flex;
align-items: flex-end;
justify-content: center;
column-gap: 10px;
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div class="tgb-box">
<div class="tgb-top">
<div class="tgb-title">原神启动</div>
<v-btn size="small" icon="mdi-rocket" variant="outlined" @click="tryPlayGame()" />
</div>
<v-list-item v-if="account.uid">
<v-list-item-title class="tgb-name">
{{ account.nickname }}({{ account.regionName }})
</v-list-item-title>
<v-list-item-subtitle>{{ account.gameUid }} Lv.{{ account.level }}</v-list-item-subtitle>
</v-list-item>
<v-list-item v-else>
<v-list-item-title>未登录请先登录!</v-list-item-title>
</v-list-item>
</div>
</template>
<script lang="ts" setup>
import { path } from "@tauri-apps/api";
import { exists } from "@tauri-apps/plugin-fs";
import { Command } from "@tauri-apps/plugin-shell";
import { storeToRefs } from "pinia";
import { computed } from "vue";
import { useAppStore } from "../../store/modules/app.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import PassportApi from "../../web/request/passportReq.js";
import showSnackbar from "../func/snackbar.js";
const userStore = storeToRefs(useUserStore());
const appStore = storeToRefs(useAppStore());
const account = computed<TGApp.Sqlite.Account.Game>(() => userStore.account.value);
async function tryPlayGame(): Promise<void> {
if (!userStore.uid.value || !userStore.cookie.value) {
showSnackbar.warn("请先登录!");
return;
}
if (account.value.isOfficial === 0) {
showSnackbar.warn("仅支持官服用户启动!");
return;
}
if (appStore.gameDir.value === "未设置") {
showSnackbar.warn("未设置游戏安装目录!");
return;
}
const gamePath = `${appStore.gameDir.value}${path.sep()}YuanShen.exe`;
if (!(await exists(gamePath))) {
showSnackbar.warn("未检测到原神本体应用!");
return;
}
const resp = await PassportApi.authTicket(account.value, userStore.cookie.value);
if (typeof resp !== "string") {
showSnackbar.error(`[${resp.retcode}] ${resp.message}`);
await TGLogger.Error(
`[config][gameBadge] 尝试获取authTicket失败当前用户${account.value.uid}-${account.value.gameUid}`,
);
await TGLogger.Error(`[config][gameBadge] resp: ${JSON.stringify(resp)}`);
return;
}
showSnackbar.success(`成功获取ticket:${resp},正在启动应用...`);
const cmd = Command.create("exec-sh", [`&"${gamePath}" login_auth_ticket=${resp}`], {
cwd: appStore.gameDir.value,
encoding: "utf-8",
});
console.log(cmd);
const result = await cmd.execute();
if (result.stderr) {
await TGLogger.Error(`[config][gameBadge] 启动游戏本体失败!`);
console.error(result.stderr);
showSnackbar.error(`[${result.code}] ${result.stderr}`);
}
}
</script>
<style lang="css" scoped>
.tgb-box {
position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 10px;
border-radius: 10px;
background-image: linear-gradient(to right, #f78ca0 0%, #f9748f 19%, #fd868c 60%, #fe9a8b 100%);
color: var(--tgc-white-1);
row-gap: 10px;
}
.tgb-top {
position: relative;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
}
.tgb-title {
display: flex;
align-items: center;
justify-content: center;
color: var(--tgc-yellow-1);
font-family: var(--font-title);
font-size: 18px;
}
.tgb-name {
font-family: var(--font-title);
}
</style>

View File

@@ -0,0 +1,149 @@
<template>
<v-list class="config-list">
<v-list-subheader :inset="true" class="config-header" title="相关信息" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item title="Tauri 版本" @click="toOuter('https://v2.tauri.app/')">
<template #prepend>
<v-img class="config-icon" src="/platforms/tauri.webp" alt="Tauri" />
</template>
<template #append>
<v-list-item-subtitle>{{ versionTauri }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="成就版本">
<template #prepend>
<img class="config-icon" src="../../assets/icons/achievements.svg" alt="Achievements" />
</template>
<template #append>
<v-list-item-subtitle>{{ latestAchiVersion }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="系统平台">
<template #prepend>
<div class="config-icon">
<v-icon>{{ iconPlatform }}</v-icon>
</div>
</template>
<template #append>
<v-list-item-subtitle>{{ osPlatform }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="系统版本">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-monitor-dashboard</v-icon>
</div>
</template>
<template #append>
<v-list-item-subtitle>{{ osVersion }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="数据库更新时间">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-database-sync</v-icon>
</div>
</template>
<template #append>
<v-list-item-subtitle
>{{ dbInfo.find((item) => item.key === "dataUpdated")?.value }}
</v-list-item-subtitle>
</template>
<v-list-item-subtitle
>更新于
{{ dbInfo.find((item) => item.key === "dataUpdated")?.updated }}
</v-list-item-subtitle>
</v-list-item>
<v-list-item title="数据库版本">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-database-search</v-icon>
</div>
</template>
<template #append>
<v-list-item-subtitle
>{{ dbInfo.find((item) => item.key === "appVersion")?.value }}
</v-list-item-subtitle>
</template>
<v-list-item-subtitle
>更新于
{{ dbInfo.find((item) => item.key === "appVersion")?.updated }}
</v-list-item-subtitle>
</v-list-item>
</v-list>
</template>
<script lang="ts" setup>
import { app } from "@tauri-apps/api";
import { platform, version } from "@tauri-apps/plugin-os";
import { onMounted, ref } from "vue";
import TGSqlite from "../../plugins/Sqlite/index.js";
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
import TGLogger from "../../utils/TGLogger.js";
import showSnackbar from "../func/snackbar.js";
const latestAchiVersion = TSUserAchi.getLatestAchiVersion();
const versionApp = ref<string>("");
const versionTauri = ref<string>("");
const osPlatform = ref<string>("");
const iconPlatform = ref<string>("mdi-microsoft-windows");
const osVersion = ref<string>("");
const dbInfo = ref<Array<TGApp.Sqlite.AppData.Item>>([]);
onMounted(async () => {
versionApp.value = await app.getVersion();
versionTauri.value = await app.getTauriVersion();
osPlatform.value = platform();
switch (osPlatform.value) {
case "linux":
iconPlatform.value = "mdi-linux";
break;
case "macos":
iconPlatform.value = "mdi-apple";
break;
case "ios":
iconPlatform.value = "mdi-apple-ios";
break;
case "windows":
iconPlatform.value = "mdi-microsoft-windows";
break;
default:
iconPlatform.value = "mdi-desktop-classic";
break;
}
osVersion.value = version();
try {
dbInfo.value = await TGSqlite.getAppData();
} catch (e) {
showSnackbar.warn("加载数据库错误,请重置数据库!");
await TGLogger.Error(`加载数据库错误: ${e}`);
}
});
function toOuter(url: string) {
window.open(url);
}
</script>
<style lang="css" scoped>
.config-header {
margin-top: 10px;
color: var(--common-text-title);
font-family: var(--font-title);
font-size: large;
}
.config-icon {
display: flex;
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
padding: 5px;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
margin-right: 15px;
background: var(--box-bg-2);
color: var(--box-text-2);
}
</style>

View File

@@ -0,0 +1,540 @@
<template>
<v-card class="tcu-box">
<template #prepend>
<v-avatar :image="userInfo.avatar" />
</template>
<template #title>{{ userInfo.nickname }}</template>
<template #subtitle>UID:{{ userInfo.uid }}</template>
<template #text>{{ userInfo.desc }}</template>
<template #append>
<v-menu location="start">
<template v-slot:activator="{ props }">
<v-btn
variant="outlined"
@click="showAccounts()"
title="切换默认游戏账户"
v-bind="props"
icon="mdi-gamepad-variant"
/>
</template>
<v-list>
<v-list-item
v-for="account in gameAccounts"
:key="account.gameUid"
@click="useUserStore().switchGameAccount(account.gameUid)"
>
<v-list-item-title>{{ account.nickname }}</v-list-item-title>
<v-list-item-subtitle>
{{ account.gameUid }}({{ account.regionName }})
</v-list-item-subtitle>
<template #append>
<div v-if="account.gameUid === userStore.account.value.gameUid" title="当前登录账号">
<v-icon color="green">mdi-check</v-icon>
</div>
</template>
</v-list-item>
</v-list>
</v-menu>
</template>
<template #actions>
<v-spacer />
<v-btn
variant="outlined"
@click="tryCaptchaLogin()"
icon="mdi-cellphone"
title="验证码登录"
/>
<v-btn
variant="outlined"
@click="confirmRefreshUser(userStore.uid.value!)"
:disabled="userStore.uid.value === undefined"
icon="mdi-refresh"
title="刷新用户信息"
/>
<v-btn
variant="outlined"
@click="confirmCopyCookie"
:disabled="!userStore.cookie.value"
icon="mdi-cookie"
title="复制Cookie"
/>
<v-menu location="start">
<template v-slot:activator="{ props }">
<v-btn
variant="outlined"
icon="mdi-account-switch"
title="切换账户"
@click="showMenu"
v-bind="props"
/>
</template>
<v-list>
<v-list-item v-for="account in accounts" :key="account.uid">
<v-list-item-title>{{ account.brief.nickname }}</v-list-item-title>
<v-list-item-subtitle>{{ account.brief.uid }}</v-list-item-subtitle>
<template #append>
<div v-if="account.uid === userStore.uid.value" title="当前登录账号">
<v-icon color="green">mdi-account-check</v-icon>
</div>
<v-icon
v-else
size="small"
icon="mdi-account-convert"
title="切换用户"
@click="loadAccount(account.uid)"
/>
<v-icon
class="tcu-btn"
icon="mdi-delete"
title="删除用户"
size="small"
@click="clearUser(account)"
/>
</template>
</v-list-item>
<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>
</v-menu>
</template>
</v-card>
</template>
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { computed, ref } from "vue";
import Mys from "../../plugins/Mys/index.js";
import TSUserAccount from "../../plugins/Sqlite/modules/userAccount.js";
import { useAppStore } from "../../store/modules/app.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import BBSApi from "../../web/request/bbsReq.js";
import PassportApi from "../../web/request/passportReq.js";
import TakumiApi from "../../web/request/takumiReq.js";
import showDialog from "../func/dialog.js";
import showGeetest from "../func/geetest.js";
import showLoading from "../func/loading.js";
import showSnackbar from "../func/snackbar.js";
const userStore = storeToRefs(useUserStore());
const appStore = storeToRefs(useAppStore());
const accounts = ref<TGApp.App.Account.User[]>([]);
const gameAccounts = ref<TGApp.Sqlite.Account.Game[]>([]);
const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
if (userStore.uid.value === undefined)
return {
nickname: "未登录",
uid: "-1",
desc: "请使用短信验证码登录",
avatar: "/source/UI/lumine.webp",
};
return userStore.briefInfo.value;
});
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;
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: "",
};
showLoading.update("正在登录...", "正在获取 LToken");
const ltokenRes = await PassportApi.lToken.get(ck);
if (typeof ltokenRes !== "string") {
showLoading.end();
showSnackbar.error(`[${ltokenRes.retcode}]${ltokenRes.message}`);
await TGLogger.Error(`获取LToken失败${ltokenRes.retcode}-${ltokenRes.message}`);
return;
}
showSnackbar.success("获取LToken成功");
ck.ltoken = ltokenRes;
showLoading.update("正在登录...", "正在获取 CookieToken");
const cookieTokenRes = await PassportApi.cookieToken(ck);
if (typeof cookieTokenRes !== "string") {
showLoading.end();
showSnackbar.error(`[${cookieTokenRes.retcode}]${cookieTokenRes.message}`);
await TGLogger.Error(
`获取CookieToken失败${cookieTokenRes.retcode}-${cookieTokenRes.message}`,
);
return;
}
showSnackbar.success("获取CookieToken成功");
ck.cookie_token = cookieTokenRes;
showLoading.update("正在登录...", "正在获取用户信息");
const briefRes = await BBSApi.userInfo(ck);
if ("retcode" in briefRes) {
showLoading.end();
showSnackbar.error(`[${briefRes.retcode}]${briefRes.message}`);
await TGLogger.Error(`获取用户数据失败:${briefRes.retcode}-${briefRes.message}`);
return;
}
showSnackbar.success("获取用户信息成功");
const briefInfo: TGApp.App.Account.BriefInfo = {
nickname: briefRes.nickname,
uid: briefRes.uid,
avatar: briefRes.avatar_url,
desc: briefRes.introduce,
};
showLoading.update("正在登录...", "正在保存用户数据");
await TSUserAccount.account.saveAccount({
uid: briefInfo.uid,
cookie: ck,
brief: briefInfo,
updated: "",
});
userStore.uid.value = briefInfo.uid;
userStore.briefInfo.value = briefInfo;
userStore.cookie.value = ck;
appStore.isLogin.value = true;
showLoading.update("正在登录...", "正在获取游戏账号");
const gameRes = await TakumiApi.bind.gameRoles(userStore.cookie.value);
if (!Array.isArray(gameRes)) {
showLoading.end();
showSnackbar.error(`[${gameRes.retcode}]${gameRes.message}`);
await TGLogger.Error(`获取游戏账号失败:${gameRes.retcode}-${gameRes.message}`);
return;
}
showSnackbar.success("获取游戏账号成功");
await TSUserAccount.game.saveAccounts(briefInfo.uid, gameRes);
const curAccount = await TSUserAccount.game.getCurAccount(briefInfo.uid);
if (!curAccount) {
showSnackbar.warn("未检测到游戏账号,请重新刷新");
showLoading.end();
return;
}
userStore.account.value = curAccount;
showLoading.end();
showSnackbar.success("成功登录!");
}
async function refreshUser(uid: string) {
let account = await TSUserAccount.account.getAccount(uid);
if (!account) {
showSnackbar.warn(`未获取到${uid}账号数据,请重新登录!`);
return;
}
let ck = account.cookie;
showLoading.start("正在刷新用户信息", "正在验证 LToken");
const verifyLTokenRes = await PassportApi.lToken.verify(ck);
if (typeof verifyLTokenRes === "string") {
showLoading.update("正在刷新用户信息", "验证 LToken 成功");
showSnackbar.success("验证 LToken 成功");
await TGLogger.Info("[tc-userBadge][refreshUser] 验证 LToken 成功");
} else {
showLoading.update("正在刷新用户信息", "验证 LToken 失败");
showSnackbar.error(`[${verifyLTokenRes.retcode}]${verifyLTokenRes.message}`);
await TGLogger.Warn("[tc-userBadge][refreshUser] 验证 LToken 失败");
await TGLogger.Warn(
`[tc-userBadge][refreshUser] ${verifyLTokenRes.retcode}: ${verifyLTokenRes.message}`,
);
const ltokenRes = await PassportApi.lToken.get(ck);
if (typeof ltokenRes === "string") {
showLoading.update("正在刷新用户信息", "获取 LToken 成功");
ck.ltoken = ltokenRes;
await TGLogger.Info("[tc-userBadge][refreshUser] 获取 LToken 成功");
} else {
showLoading.update("正在刷新用户信息", "获取 LToken 失败");
showSnackbar.error(`[${ltokenRes.retcode}]${ltokenRes.message}`);
await TGLogger.Error("[tc-userBadge][refreshUser] 获取 LToken 失败");
await TGLogger.Error(
`[tc-userBadge][refreshUser] ${ltokenRes.retcode}: ${ltokenRes.message}`,
);
}
}
showLoading.update("正在刷新用户信息", "正在获取 CookieToken");
const cookieTokenRes = await PassportApi.cookieToken(ck);
if (typeof cookieTokenRes === "string") {
showLoading.update("正在刷新用户信息", "获取 CookieToken 成功");
ck.cookie_token = cookieTokenRes;
await TGLogger.Info("[tc-userBadge][refreshUser] 获取 CookieToken 成功");
} else {
showLoading.update("正在刷新用户信息", "获取 CookieToken 失败");
showSnackbar.error(`[${cookieTokenRes.retcode}]${cookieTokenRes.message}`);
await TGLogger.Error("[tc-userBadge][refreshUser] 获取 CookieToken 失败");
await TGLogger.Error(
`[tc-userBadge][refreshUser] ${cookieTokenRes.retcode}: ${cookieTokenRes.message}`,
);
}
account.cookie = ck;
showLoading.update("正在刷新用户信息", "正在获取用户信息");
const infoRes = await BBSApi.userInfo(ck);
if ("retcode" in infoRes) {
showLoading.update("正在刷新用户信息", "获取用户信息失败");
showSnackbar.error(`[${infoRes.retcode}]${infoRes.message}`);
await TGLogger.Error("[tc-userBadge][refreshUserInfo] 获取用户信息失败");
await TGLogger.Error(`[tc-userBadge][refreshUserInfo] ${infoRes.retcode}: ${infoRes.message}`);
} else {
showLoading.update("正在刷新用户信息", "获取用户信息成功");
account.brief = {
nickname: infoRes.nickname,
uid: infoRes.uid,
avatar: infoRes.avatar_url,
desc: infoRes.introduce,
};
await TGLogger.Info("[tc-userBadge][refreshUserInfo] 获取用户信息成功");
}
await TSUserAccount.account.saveAccount(account);
showLoading.update("正在刷新用户信息", "正在获取账号信息");
const accountRes = await TakumiApi.bind.gameRoles(ck);
if (Array.isArray(accountRes)) {
showLoading.update("正在刷新用户信息", "获取账号信息成功");
await TGLogger.Info("[tc-userBadge][refreshUserInfo] 获取账号信息成功");
await TSUserAccount.game.saveAccounts(account.uid, accountRes);
} else {
showLoading.update("正在刷新用户信息", "获取账号信息失败");
showSnackbar.error(`[${accountRes.retcode}]${accountRes.message}`);
await TGLogger.Error("[tc-userBadge][refreshUserInfo] 获取账号信息失败");
await TGLogger.Error(
`[tc-userBadge][refreshUserInfo] ${accountRes.retcode}: ${accountRes.message}`,
);
}
showLoading.end();
}
async function loadAccount(uid: string): Promise<void> {
if (userStore.uid.value && uid === userStore.uid.value) {
showSnackbar.warn("该账户已经登录,无需切换");
return;
}
const account = await TSUserAccount.account.getAccount(uid);
if (!account) {
showSnackbar.warn(`未找到${uid}的账号信息,请重新登录`);
return;
}
userStore.uid.value = uid;
userStore.briefInfo.value = account.brief;
userStore.cookie.value = account.cookie;
const gameAccount = await TSUserAccount.game.getCurAccount(uid);
if (!gameAccount) {
showSnackbar.warn(`未找到${uid}的游戏账号信息,请尝试刷新`);
return;
}
userStore.account.value = gameAccount;
showSnackbar.success(`成功切换到用户${uid}`);
}
async function confirmRefreshUser(uid: string): Promise<void> {
const freshCheck = await showDialog.check("确认刷新用户信息吗?", "将会重新获取用户信息");
if (!freshCheck) {
showSnackbar.cancel("已取消刷新用户信息");
return;
}
await refreshUser(uid);
if (userStore.uid.value === uid) {
showSnackbar.success("成功刷新用户信息");
return;
}
const switchCheck = await showDialog.check("是否切换用户?", `将切换到用户${uid}`);
if (!switchCheck) return;
await loadAccount(uid);
}
async function confirmCopyCookie(): Promise<void> {
if (!userStore.cookie.value) {
showSnackbar.warn("请先登录");
return;
}
const copyCheck = await showDialog.check("确认复制 Cookie 吗?", "将会复制当前登录的 Cookie");
if (!copyCheck) {
showSnackbar.cancel("已取消复制 Cookie");
return;
}
const ckText = TSUserAccount.account.copy(userStore.cookie.value);
await navigator.clipboard.writeText(ckText);
showSnackbar.success("已复制 Cookie!");
}
async function tryGetCaptcha(phone: string, aigis?: string): Promise<string | false> {
const captchaResp = await Mys.User.getCaptcha(phone, aigis);
if ("retcode" in captchaResp) {
if (!captchaResp.data || captchaResp.data === "") {
showSnackbar.error(`[${captchaResp.retcode}] ${captchaResp.message}`);
return false;
}
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))}`;
return await tryGetCaptcha(phone, aigisStr);
}
return captchaResp.action_type;
}
async function tryLoginByCaptcha(
phone: string,
captcha: string,
actionType: string,
aigis?: string,
): Promise<TGApp.Plugins.Mys.CaptchaLogin.LoginData | false> {
const loginResp = await Mys.User.login(phone, captcha, actionType, aigis);
if ("retcode" in loginResp) {
if (!loginResp.data || loginResp.data === "") {
showSnackbar.error(`[${loginResp.retcode}] ${loginResp.message}`);
return false;
}
const aigisResp: TGApp.Plugins.Mys.CaptchaLogin.CaptchaAigis = JSON.parse(loginResp.data);
const resp = await showGeetest(JSON.parse(aigisResp.data));
const aigisStr = `${aigisResp.session_id};${btoa(JSON.stringify(resp))}`;
return await tryLoginByCaptcha(phone, captcha, actionType, aigisStr);
}
return loginResp;
}
async function showMenu(): Promise<void> {
accounts.value = await TSUserAccount.account.getAllAccount();
}
async function showAccounts(): Promise<void> {
if (!userStore.uid.value) {
showSnackbar.warn("未登录!");
return;
}
gameAccounts.value = await TSUserAccount.game.getAccount(userStore.uid.value);
if (gameAccounts.value.length === 0) {
showSnackbar.warn("未找到账户的游戏数据,请尝试刷新!");
return;
}
}
async function addByCookie(): Promise<void> {
const ckInput = await showDialog.input("请输入Cookie", "Cookie:");
if (!ckInput) {
showSnackbar.cancel("已取消Cookie输入");
return;
}
if (ckInput === "") {
showSnackbar.warn("请输入Cookie!");
return;
}
const ckArr = ckInput.split(";");
let ckRes = { stoken: "", stuid: "", mid: "" };
for (const ck of ckArr) {
if (ck.startsWith("mid=")) ckRes.mid = ck.substring(4);
else if (ck.startsWith("stoken=")) ckRes.stoken = ck.substring(7);
else if (ck.startsWith("stuid=")) ckRes.stuid = ck.substring(6);
}
if (ckRes.mid === "" || ckRes.stoken === "" || ckRes.stuid === "") {
showSnackbar.warn("Cookie格式错误");
await TGLogger.Error(`解析Cookie失败${ckInput}`);
return;
}
showLoading.start("正在添加用户", "正在尝试刷新Cookie");
const ck: TGApp.App.Account.Cookie = {
account_id: ckRes.stuid,
ltuid: ckRes.stuid,
stuid: ckRes.stuid,
mid: ckRes.mid,
cookie_token: "",
stoken: ckRes.stoken,
ltoken: "",
};
showLoading.update("正在添加用户", "正在获取 LToken");
const ltokenRes = await PassportApi.lToken.get(ck);
if (typeof ltokenRes !== "string") {
showLoading.end();
showSnackbar.error(`[${ltokenRes.retcode}]${ltokenRes.message}`);
await TGLogger.Error(`获取LToken失败${ltokenRes.retcode}-${ltokenRes.message}`);
return;
}
ck.ltoken = ltokenRes;
showLoading.update("正在添加用户", "正在获取 CookieToken");
const cookieTokenRes = await PassportApi.cookieToken(ck);
if (typeof cookieTokenRes !== "string") {
showLoading.end();
showSnackbar.error(`[${cookieTokenRes.retcode}]${cookieTokenRes.message}`);
await TGLogger.Error(
`获取CookieToken失败${cookieTokenRes.retcode}-${cookieTokenRes.message}`,
);
return;
}
ck.cookie_token = cookieTokenRes;
showLoading.update("正在添加用户", "正在获取用户信息");
const briefRes = await BBSApi.userInfo(ck);
if ("retcode" in briefRes) {
showLoading.end();
showSnackbar.error(`[${briefRes.retcode}]${briefRes.message}`);
await TGLogger.Error(`获取用户数据失败:${briefRes.retcode}-${briefRes.message}`);
return;
}
const briefInfo: TGApp.App.Account.BriefInfo = {
nickname: briefRes.nickname,
uid: briefRes.uid,
avatar: briefRes.avatar_url,
desc: briefRes.introduce,
};
showLoading.update("正在添加用户", "正在保存用户数据");
await TSUserAccount.account.saveAccount({
uid: briefInfo.uid,
cookie: ck,
brief: briefInfo,
updated: "",
});
showLoading.update("正在添加用户", "正在获取游戏账号");
const gameRes = await TakumiApi.bind.gameRoles(ck);
if (!Array.isArray(gameRes)) {
showLoading.end();
showSnackbar.error(`[${gameRes.retcode}]${gameRes.message}`);
return;
}
showLoading.update("正在添加用户", "正在保存游戏账号");
await TSUserAccount.game.saveAccounts(briefInfo.uid, gameRes);
const curAccount = await TSUserAccount.game.getCurAccount(briefInfo.uid);
if (!curAccount) {
showLoading.end();
showSnackbar.warn("未检测到游戏账号,请重新刷新");
return;
}
showLoading.end();
showSnackbar.success("成功添加用户!");
}
async function clearUser(user: TGApp.App.Account.User): Promise<void> {
if (user.uid === userStore.uid.value) {
showSnackbar.warn("当前登录用户不许删除!");
return;
}
const delCheck = await showDialog.check("确认删除用户吗?", "将删除账号及其游戏账号数据");
if (!delCheck) {
showSnackbar.cancel("已取消删除用户数据");
return;
}
await TSUserAccount.account.deleteAccount(user.uid);
showSnackbar.success("成功删除用户!");
}
</script>
<style lang="css" scoped>
.tcu-box {
border-radius: 10px;
background-image: linear-gradient(to right, #f78ca0 0%, #f9748f 19%, #fd868c 60%, #fe9a8b 100%);
}
.tcu-btn {
margin-left: 5px;
}
</style>