mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-03-15 03:53:16 +08:00
@@ -1,12 +1,12 @@
|
||||
<!-- 胡桃悬浮层 -->
|
||||
<template>
|
||||
<TOverlay v-model="visible" blur-val="5px">
|
||||
<div class="ugo-hutao-download">
|
||||
<div class="ugo-hd-title">请选择要下载的数据</div>
|
||||
<div class="ugo-hutao-du">
|
||||
<div class="ugo-hd-title">请选择要{{ props.mode === "upload" ? "上传" : "下载" }}的数据</div>
|
||||
<v-progress-circular v-if="loading" color="var(--tgc-od-blue)" indeterminate />
|
||||
<v-item-group v-else v-model="selectedUid" class="ugo-hd-list">
|
||||
<v-item-group v-else v-model="selectedUid" :multiple="true" class="ugo-hd-list">
|
||||
<v-item
|
||||
v-for="(item, idx) in uploadInfo"
|
||||
v-for="(item, idx) in uidList"
|
||||
:key="idx"
|
||||
v-slot="{ isSelected, toggle }"
|
||||
:value="item.uid"
|
||||
@@ -35,38 +35,56 @@
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import hutao from "@Hutao/index.js";
|
||||
import TSUserGacha from "@Sqlm/userGacha.js";
|
||||
import useHutaoStore from "@store/hutao.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, shallowRef, watch } from "vue";
|
||||
|
||||
type UgoHutaoDownloadUid = { uid: string; cnt: number };
|
||||
type UgoHutaoDuUid = { uid: string; cnt: number };
|
||||
|
||||
type UgoHutaoDownloadEmits = (e: "selected", v: Array<string>) => void;
|
||||
type UgoHutaoDuProps = { mode: "download" | "upload" };
|
||||
type UgoHutaoDuEmits = (e: "selected", v: Array<string>, m: boolean) => void;
|
||||
|
||||
const visible = defineModel<boolean>();
|
||||
const emits = defineEmits<UgoHutaoDownloadEmits>();
|
||||
const emits = defineEmits<UgoHutaoDuEmits>();
|
||||
const props = defineProps<UgoHutaoDuProps>();
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const uploadInfo = shallowRef<Array<UgoHutaoDownloadUid>>([]);
|
||||
const uidList = shallowRef<Array<UgoHutaoDuUid>>([]);
|
||||
const selectedUid = shallowRef<Array<string>>([]);
|
||||
|
||||
const hutaoStore = useHutaoStore();
|
||||
const { accessToken, isLogin } = storeToRefs(hutaoStore);
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
() => [visible.value, props.mode],
|
||||
async () => {
|
||||
if (visible.value) {
|
||||
loading.value = true;
|
||||
selectedUid.value = [];
|
||||
uploadInfo.value = [];
|
||||
await loadOverview();
|
||||
uidList.value = [];
|
||||
if (props.mode == "download") {
|
||||
await loadDownload();
|
||||
} else {
|
||||
await loadUpload();
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
async function loadOverview(): Promise<void> {
|
||||
async function loadUpload(): Promise<void> {
|
||||
const uids = await TSUserGacha.getUidList();
|
||||
const tmpData: Array<UgoHutaoDuUid> = [];
|
||||
for (const uid of uids) {
|
||||
const dataRaw = await TSUserGacha.record.all(uid);
|
||||
tmpData.push({ uid: uid, cnt: dataRaw.length });
|
||||
}
|
||||
uidList.value = tmpData;
|
||||
}
|
||||
|
||||
async function loadDownload(): Promise<void> {
|
||||
if (!isLogin.value) return;
|
||||
if (!hutaoStore.checkIsValid()) await hutaoStore.tryRefreshToken();
|
||||
if (!accessToken.value) return;
|
||||
@@ -76,7 +94,7 @@ async function loadOverview(): Promise<void> {
|
||||
showSnackbar.warn(`[${info.retcode}] ${info.message}`);
|
||||
return;
|
||||
}
|
||||
uploadInfo.value = info.map((i) => ({ uid: i.Uid, cnt: i.ItemCount }));
|
||||
uidList.value = info.map((i) => ({ uid: i.Uid, cnt: i.ItemCount }));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -87,12 +105,12 @@ function handleSelected(): void {
|
||||
showSnackbar.warn("请选择至少一个UID");
|
||||
return;
|
||||
}
|
||||
emits("selected", selectedUid.value);
|
||||
emits("selected", selectedUid.value, props.mode === "upload");
|
||||
visible.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ugo-hutao-download {
|
||||
.ugo-hutao-du {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 400px;
|
||||
@@ -145,7 +145,7 @@
|
||||
</v-window>
|
||||
</div>
|
||||
<UgoUid v-model="ovShow" :mode="ovMode" />
|
||||
<UgoHutaoDownload v-model="showHutaoD" @selected="handleHutaoDownload" />
|
||||
<UgoHutaoDu v-model="hutaoShow" :mode="htMode" @selected="handleHutaoDu" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
@@ -156,8 +156,9 @@ import GroHistory from "@comp/userGacha/gro-history.vue";
|
||||
import GroIframe from "@comp/userGacha/gro-iframe.vue";
|
||||
import GroOverview from "@comp/userGacha/gro-overview.vue";
|
||||
import GroTable from "@comp/userGacha/gro-table.vue";
|
||||
import UgoHutaoDownload from "@comp/userGacha/ugo-hutao-download.vue";
|
||||
import UgoHutaoDu from "@comp/userGacha/ugo-hutao-du.vue";
|
||||
import UgoUid from "@comp/userGacha/ugo-uid.vue";
|
||||
import hutao from "@Hutao/index.js";
|
||||
import hk4eReq from "@req/hk4eReq.js";
|
||||
import takumiReq from "@req/takumiReq.js";
|
||||
import TSUserGacha from "@Sqlm/userGacha.js";
|
||||
@@ -168,6 +169,7 @@ import { path } from "@tauri-apps/api";
|
||||
import { open, save } from "@tauri-apps/plugin-dialog";
|
||||
import Hakushi from "@utils/Hakushi.js";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { str2timeStr, timeStr2str } from "@utils/toolFunc.js";
|
||||
import { exportUigfData, readUigfData, verifyUigfData } from "@utils/UIGF.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
@@ -180,14 +182,15 @@ const hutaoStore = useHutaoStore();
|
||||
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
const { account, cookie } = storeToRefs(useUserStore());
|
||||
const { isLogin: isLoginHutao, userName } = storeToRefs(hutaoStore);
|
||||
const { isLogin: isLoginHutao, accessToken, userName, userInfo } = storeToRefs(hutaoStore);
|
||||
|
||||
const authkey = ref<string>("");
|
||||
const uidCur = ref<string>();
|
||||
const tab = ref<string>("overview");
|
||||
const ovShow = ref<boolean>(false);
|
||||
const showHutaoD = ref<boolean>(false);
|
||||
const hutaoShow = ref<boolean>(false);
|
||||
const ovMode = ref<"export" | "import">("import");
|
||||
const htMode = ref<"download" | "upload">("download");
|
||||
const selectItem = shallowRef<Array<string>>([]);
|
||||
const gachaListCur = shallowRef<Array<TGApp.Sqlite.Gacha.Gacha>>([]);
|
||||
const hakushiData = shallowRef<Array<TGApp.Plugins.Hakushi.ConvertData>>([]);
|
||||
@@ -229,17 +232,74 @@ async function tryLoginHutao(): Promise<void> {
|
||||
}
|
||||
|
||||
async function tryUploadGacha(): Promise<void> {
|
||||
// TODO: implement upload gacha records to hutao cloud
|
||||
if (!isLoginHutao.value) return;
|
||||
htMode.value = "upload";
|
||||
hutaoShow.value = true;
|
||||
}
|
||||
|
||||
async function tryDownloadGacha(): Promise<void> {
|
||||
if (!isLoginHutao.value) return;
|
||||
showHutaoD.value = true;
|
||||
htMode.value = "download";
|
||||
hutaoShow.value = true;
|
||||
}
|
||||
|
||||
async function handleHutaoDu(uids: Array<string>, isUpload: boolean): Promise<void> {
|
||||
if (isUpload) await handleHutaoUpload(uids);
|
||||
else await handleHutaoDownload(uids);
|
||||
}
|
||||
|
||||
async function handleHutaoUpload(uids: Array<string>): Promise<void> {
|
||||
if (uids.length === 0) {
|
||||
showSnackbar.warn("没有选中的UID");
|
||||
return;
|
||||
}
|
||||
if (!isLoginHutao.value) {
|
||||
showSnackbar.warn("未登录胡桃云账号");
|
||||
return;
|
||||
}
|
||||
if (!userInfo.value) {
|
||||
await hutaoStore.tryRefreshInfo();
|
||||
if (!userInfo.value) {
|
||||
showSnackbar.warn("未检测到胡桃云用户信息");
|
||||
return;
|
||||
}
|
||||
}
|
||||
const isExpire = hutaoStore.checkGachaExpire();
|
||||
if (isExpire) {
|
||||
const check = await showDialog.checkF({
|
||||
title: "胡桃云祈愿已过期,确定上传?",
|
||||
text: `到期时间:${timeStr2str(userInfo.value.GachaLogExpireAt)}`,
|
||||
});
|
||||
if (!check) return;
|
||||
}
|
||||
await showLoading.start("正在上传至胡桃云...", "正在刷新Token");
|
||||
await hutaoStore.tryRefreshToken();
|
||||
for (const u of uids) {
|
||||
await showLoading.update(`正在上传UID:${u}`);
|
||||
const dataRaw = await TSUserGacha.record.all(u);
|
||||
const data: TGApp.Plugins.Hutao.Gacha.UploadData = {
|
||||
Uid: u,
|
||||
Items: dataRaw.map((i) => ({
|
||||
GachaType: Number(i.gachaType),
|
||||
QueryType: Number(i.uigfType),
|
||||
ItemId: Number(i.itemId),
|
||||
Time: str2timeStr(i.time),
|
||||
Id: BigInt(i.id),
|
||||
})),
|
||||
};
|
||||
const resp = await hutao.Gacha.upload(accessToken.value!, data);
|
||||
if (resp.retcode === 0) {
|
||||
showSnackbar.success(`成功上传祈愿数据:${resp.message}`);
|
||||
} else {
|
||||
showSnackbar.warn(`[${resp.retcode}] ${resp.message}`);
|
||||
}
|
||||
}
|
||||
await showLoading.end();
|
||||
}
|
||||
|
||||
async function handleHutaoDownload(uids: Array<string>): Promise<void> {
|
||||
console.log(uids);
|
||||
// TODO: implement download gacha records from hutao cloud
|
||||
// TODO:implement download gacha logs
|
||||
}
|
||||
|
||||
async function reloadUid(): Promise<void> {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "./request/abyssReq.js";
|
||||
import { getUserInfo, loginPassport, refreshToken } from "./request/accountReq.js";
|
||||
import { getCombatStatistic, uploadCombatData } from "./request/combatReq.js";
|
||||
import { getEndIds, getEntries, getGachaLogs } from "./request/gachaReq.js";
|
||||
import { getEndIds, getEntries, getGachaLogs, uploadGachaLogs } from "./request/gachaReq.js";
|
||||
import { transAbyssAvatars, transAbyssLocal } from "./utils/abyssUtil.js";
|
||||
import { transCombatLocal } from "./utils/combatUtil.js";
|
||||
|
||||
@@ -61,7 +61,7 @@ const Hutao = {
|
||||
entry: getEntries,
|
||||
endIds: getEndIds,
|
||||
logs: getGachaLogs,
|
||||
upload: _,
|
||||
upload: uploadGachaLogs,
|
||||
delete: _,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -78,3 +78,23 @@ export async function getGachaLogs(
|
||||
if (resp.retcode !== 0) return <TGApp.Plugins.Hutao.Base.Resp>resp;
|
||||
return <TGApp.Plugins.Hutao.Gacha.GachaLogRes>resp.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传抽卡记录
|
||||
* @since Beta v0.9.1
|
||||
* @param tk - token
|
||||
* @param data - 上传数据
|
||||
* @returns 上传结果
|
||||
*/
|
||||
export async function uploadGachaLogs(
|
||||
tk: string,
|
||||
data: TGApp.Plugins.Hutao.Gacha.UploadData,
|
||||
): Promise<TGApp.Plugins.Hutao.Gacha.UploadResp | TGApp.Plugins.Hutao.Base.Resp> {
|
||||
const url = `${GachaUrl}Upload`;
|
||||
const header = await getReqHeader(tk);
|
||||
return await TGHttp<TGApp.Plugins.Hutao.Gacha.UploadResp>(url, {
|
||||
method: "POST",
|
||||
headers: header,
|
||||
body: JSON.stringify(data, (_, v) => (typeof v === "bigint" ? v.toString() : v)),
|
||||
});
|
||||
}
|
||||
|
||||
19
src/plugins/Hutao/types/Gacha.d.ts
vendored
19
src/plugins/Hutao/types/Gacha.d.ts
vendored
@@ -83,6 +83,23 @@ declare namespace TGApp.Plugins.Hutao.Gacha {
|
||||
*/
|
||||
Time: string;
|
||||
/** Id */
|
||||
Id: number;
|
||||
Id: number | bigint;
|
||||
};
|
||||
|
||||
/**
|
||||
* 上传响应
|
||||
* @since Beta v0.9.1
|
||||
*/
|
||||
type UploadResp = TGApp.Plugins.Hutao.Base.Resp<string>;
|
||||
|
||||
/**
|
||||
* 上传数据
|
||||
* @since Beta v0.9.1
|
||||
*/
|
||||
type UploadData = {
|
||||
/** UID */
|
||||
Uid: string;
|
||||
/** 数据 */
|
||||
Items: Array<GachaLog>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -113,6 +113,13 @@ const useHutaoStore = defineStore(
|
||||
}
|
||||
}
|
||||
|
||||
function checkGachaExpire(): boolean {
|
||||
if (!userInfo.value) return true;
|
||||
if (userInfo.value.IsMaintainer || userInfo.value.IsLicensedDeveloper) return false;
|
||||
const expire = new Date(userInfo.value.GachaLogExpireAt).getTime();
|
||||
return Date.now() < expire;
|
||||
}
|
||||
|
||||
return {
|
||||
isLogin,
|
||||
userName,
|
||||
@@ -124,6 +131,7 @@ const useHutaoStore = defineStore(
|
||||
tryLogin,
|
||||
tryRefreshToken,
|
||||
tryRefreshInfo,
|
||||
checkGachaExpire,
|
||||
};
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ import { path } from "@tauri-apps/api";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { type } from "@tauri-apps/plugin-os";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { format, parseISO } from "date-fns";
|
||||
import { format, parse, parseISO } from "date-fns";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
import { AppCalendarData, AppCharacterData, AppWeaponData } from "@/data/index.js";
|
||||
@@ -381,3 +381,16 @@ export function timeStr2str(str: string): string {
|
||||
in: tz("Asia/Shanghai"),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收本地时间字符串,转成 ISO8601(含 +08:00)
|
||||
* @since Beta v0.9.1
|
||||
* @param str - 时间字符串
|
||||
* @example "2025-09-18 09:01:39" → "2025-09-18T09:01:39+08:00"
|
||||
*/
|
||||
export function str2timeStr(str: string): string {
|
||||
// 解析为上海时区的本地日期(你可以改成别的时区)
|
||||
const d = parse(str, "yyyy-MM-dd HH:mm:ss", new Date(), { in: tz("Asia/Shanghai") });
|
||||
// 输出为 UTC 的 ISO 字符串
|
||||
return format(d, "yyyy-MM-dd'T'HH:mm:ss.SSSX", { in: tz("UTC") });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user