Skip to content

Commit 8d54189

Browse files
committed
✨ 上传祈愿数据
#202
1 parent bd081e2 commit 8d54189

7 files changed

Lines changed: 162 additions & 26 deletions

File tree

src/components/userGacha/ugo-hutao-download.vue renamed to src/components/userGacha/ugo-hutao-du.vue

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<!-- 胡桃悬浮层 -->
22
<template>
33
<TOverlay v-model="visible" blur-val="5px">
4-
<div class="ugo-hutao-download">
5-
<div class="ugo-hd-title">请选择要下载的数据</div>
4+
<div class="ugo-hutao-du">
5+
<div class="ugo-hd-title">请选择要{{ props.mode === "upload" ? "上传" : "下载" }}的数据</div>
66
<v-progress-circular v-if="loading" color="var(--tgc-od-blue)" indeterminate />
7-
<v-item-group v-else v-model="selectedUid" class="ugo-hd-list">
7+
<v-item-group v-else v-model="selectedUid" :multiple="true" class="ugo-hd-list">
88
<v-item
9-
v-for="(item, idx) in uploadInfo"
9+
v-for="(item, idx) in uidList"
1010
:key="idx"
1111
v-slot="{ isSelected, toggle }"
1212
:value="item.uid"
@@ -35,38 +35,56 @@
3535
import TOverlay from "@comp/app/t-overlay.vue";
3636
import showSnackbar from "@comp/func/snackbar.js";
3737
import hutao from "@Hutao/index.js";
38+
import TSUserGacha from "@Sqlm/userGacha.js";
3839
import useHutaoStore from "@store/hutao.js";
3940
import { storeToRefs } from "pinia";
4041
import { ref, shallowRef, watch } from "vue";
4142
42-
type UgoHutaoDownloadUid = { uid: string; cnt: number };
43+
type UgoHutaoDuUid = { uid: string; cnt: number };
4344
44-
type UgoHutaoDownloadEmits = (e: "selected", v: Array<string>) => void;
45+
type UgoHutaoDuProps = { mode: "download" | "upload" };
46+
type UgoHutaoDuEmits = (e: "selected", v: Array<string>, m: boolean) => void;
4547
4648
const visible = defineModel<boolean>();
47-
const emits = defineEmits<UgoHutaoDownloadEmits>();
49+
const emits = defineEmits<UgoHutaoDuEmits>();
50+
const props = defineProps<UgoHutaoDuProps>();
51+
4852
const loading = ref<boolean>(false);
49-
const uploadInfo = shallowRef<Array<UgoHutaoDownloadUid>>([]);
53+
const uidList = shallowRef<Array<UgoHutaoDuUid>>([]);
5054
const selectedUid = shallowRef<Array<string>>([]);
5155
5256
const hutaoStore = useHutaoStore();
5357
const { accessToken, isLogin } = storeToRefs(hutaoStore);
5458
5559
watch(
56-
() => visible.value,
60+
() => [visible.value, props.mode],
5761
async () => {
5862
if (visible.value) {
5963
loading.value = true;
6064
selectedUid.value = [];
61-
uploadInfo.value = [];
62-
await loadOverview();
65+
uidList.value = [];
66+
if (props.mode == "download") {
67+
await loadDownload();
68+
} else {
69+
await loadUpload();
70+
}
6371
loading.value = false;
6472
}
6573
},
6674
{ immediate: true },
6775
);
6876
69-
async function loadOverview(): Promise<void> {
77+
async function loadUpload(): Promise<void> {
78+
const uids = await TSUserGacha.getUidList();
79+
const tmpData: Array<UgoHutaoDuUid> = [];
80+
for (const uid of uids) {
81+
const dataRaw = await TSUserGacha.record.all(uid);
82+
tmpData.push({ uid: uid, cnt: dataRaw.length });
83+
}
84+
uidList.value = tmpData;
85+
}
86+
87+
async function loadDownload(): Promise<void> {
7088
if (!isLogin.value) return;
7189
if (!hutaoStore.checkIsValid()) await hutaoStore.tryRefreshToken();
7290
if (!accessToken.value) return;
@@ -76,7 +94,7 @@ async function loadOverview(): Promise<void> {
7694
showSnackbar.warn(`[${info.retcode}] ${info.message}`);
7795
return;
7896
}
79-
uploadInfo.value = info.map((i) => ({ uid: i.Uid, cnt: i.ItemCount }));
97+
uidList.value = info.map((i) => ({ uid: i.Uid, cnt: i.ItemCount }));
8098
} catch (e) {
8199
console.error(e);
82100
}
@@ -87,12 +105,12 @@ function handleSelected(): void {
87105
showSnackbar.warn("请选择至少一个UID");
88106
return;
89107
}
90-
emits("selected", selectedUid.value);
108+
emits("selected", selectedUid.value, props.mode === "upload");
91109
visible.value = false;
92110
}
93111
</script>
94112
<style lang="scss" scoped>
95-
.ugo-hutao-download {
113+
.ugo-hutao-du {
96114
position: relative;
97115
display: flex;
98116
width: 400px;

src/pages/User/Gacha.vue

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145
</v-window>
146146
</div>
147147
<UgoUid v-model="ovShow" :mode="ovMode" />
148-
<UgoHutaoDownload v-model="showHutaoD" @selected="handleHutaoDownload" />
148+
<UgoHutaoDu v-model="hutaoShow" :mode="htMode" @selected="handleHutaoDu" />
149149
</template>
150150
<script lang="ts" setup>
151151
import showDialog from "@comp/func/dialog.js";
@@ -156,8 +156,9 @@ import GroHistory from "@comp/userGacha/gro-history.vue";
156156
import GroIframe from "@comp/userGacha/gro-iframe.vue";
157157
import GroOverview from "@comp/userGacha/gro-overview.vue";
158158
import GroTable from "@comp/userGacha/gro-table.vue";
159-
import UgoHutaoDownload from "@comp/userGacha/ugo-hutao-download.vue";
159+
import UgoHutaoDu from "@comp/userGacha/ugo-hutao-du.vue";
160160
import UgoUid from "@comp/userGacha/ugo-uid.vue";
161+
import hutao from "@Hutao/index.js";
161162
import hk4eReq from "@req/hk4eReq.js";
162163
import takumiReq from "@req/takumiReq.js";
163164
import TSUserGacha from "@Sqlm/userGacha.js";
@@ -168,6 +169,7 @@ import { path } from "@tauri-apps/api";
168169
import { open, save } from "@tauri-apps/plugin-dialog";
169170
import Hakushi from "@utils/Hakushi.js";
170171
import TGLogger from "@utils/TGLogger.js";
172+
import { str2timeStr, timeStr2str } from "@utils/toolFunc.js";
171173
import { exportUigfData, readUigfData, verifyUigfData } from "@utils/UIGF.js";
172174
import { storeToRefs } from "pinia";
173175
import { onMounted, ref, shallowRef, watch } from "vue";
@@ -180,14 +182,15 @@ const hutaoStore = useHutaoStore();
180182
181183
const { isLogin } = storeToRefs(useAppStore());
182184
const { account, cookie } = storeToRefs(useUserStore());
183-
const { isLogin: isLoginHutao, userName } = storeToRefs(hutaoStore);
185+
const { isLogin: isLoginHutao, accessToken, userName, userInfo } = storeToRefs(hutaoStore);
184186
185187
const authkey = ref<string>("");
186188
const uidCur = ref<string>();
187189
const tab = ref<string>("overview");
188190
const ovShow = ref<boolean>(false);
189-
const showHutaoD = ref<boolean>(false);
191+
const hutaoShow = ref<boolean>(false);
190192
const ovMode = ref<"export" | "import">("import");
193+
const htMode = ref<"download" | "upload">("download");
191194
const selectItem = shallowRef<Array<string>>([]);
192195
const gachaListCur = shallowRef<Array<TGApp.Sqlite.Gacha.Gacha>>([]);
193196
const hakushiData = shallowRef<Array<TGApp.Plugins.Hakushi.ConvertData>>([]);
@@ -229,17 +232,74 @@ async function tryLoginHutao(): Promise<void> {
229232
}
230233
231234
async function tryUploadGacha(): Promise<void> {
232-
// TODO: implement upload gacha records to hutao cloud
235+
if (!isLoginHutao.value) return;
236+
htMode.value = "upload";
237+
hutaoShow.value = true;
233238
}
234239
235240
async function tryDownloadGacha(): Promise<void> {
236241
if (!isLoginHutao.value) return;
237-
showHutaoD.value = true;
242+
htMode.value = "download";
243+
hutaoShow.value = true;
244+
}
245+
246+
async function handleHutaoDu(uids: Array<string>, isUpload: boolean): Promise<void> {
247+
if (isUpload) await handleHutaoUpload(uids);
248+
else await handleHutaoDownload(uids);
249+
}
250+
251+
async function handleHutaoUpload(uids: Array<string>): Promise<void> {
252+
if (uids.length === 0) {
253+
showSnackbar.warn("没有选中的UID");
254+
return;
255+
}
256+
if (!isLoginHutao.value) {
257+
showSnackbar.warn("未登录胡桃云账号");
258+
return;
259+
}
260+
if (!userInfo.value) {
261+
await hutaoStore.tryRefreshInfo();
262+
if (!userInfo.value) {
263+
showSnackbar.warn("未检测到胡桃云用户信息");
264+
return;
265+
}
266+
}
267+
const isExpire = hutaoStore.checkGachaExpire();
268+
if (isExpire) {
269+
const check = await showDialog.checkF({
270+
title: "胡桃云祈愿已过期,确定上传?",
271+
text: `到期时间:${timeStr2str(userInfo.value.GachaLogExpireAt)}`,
272+
});
273+
if (!check) return;
274+
}
275+
await showLoading.start("正在上传至胡桃云...", "正在刷新Token");
276+
await hutaoStore.tryRefreshToken();
277+
for (const u of uids) {
278+
await showLoading.update(`正在上传UID:${u}`);
279+
const dataRaw = await TSUserGacha.record.all(u);
280+
const data: TGApp.Plugins.Hutao.Gacha.UploadData = {
281+
Uid: u,
282+
Items: dataRaw.map((i) => ({
283+
GachaType: Number(i.gachaType),
284+
QueryType: Number(i.uigfType),
285+
ItemId: Number(i.itemId),
286+
Time: str2timeStr(i.time),
287+
Id: BigInt(i.id),
288+
})),
289+
};
290+
const resp = await hutao.Gacha.upload(accessToken.value!, data);
291+
if (resp.retcode === 0) {
292+
showSnackbar.success(`成功上传祈愿数据:${resp.message}`);
293+
} else {
294+
showSnackbar.warn(`[${resp.retcode}] ${resp.message}`);
295+
}
296+
}
297+
await showLoading.end();
238298
}
239299
240300
async function handleHutaoDownload(uids: Array<string>): Promise<void> {
241301
console.log(uids);
242-
// TODO: implement download gacha records from hutao cloud
302+
// TODO:implement download gacha logs
243303
}
244304
245305
async function reloadUid(): Promise<void> {

src/plugins/Hutao/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from "./request/abyssReq.js";
1515
import { getUserInfo, loginPassport, refreshToken } from "./request/accountReq.js";
1616
import { getCombatStatistic, uploadCombatData } from "./request/combatReq.js";
17-
import { getEndIds, getEntries, getGachaLogs } from "./request/gachaReq.js";
17+
import { getEndIds, getEntries, getGachaLogs, uploadGachaLogs } from "./request/gachaReq.js";
1818
import { transAbyssAvatars, transAbyssLocal } from "./utils/abyssUtil.js";
1919
import { transCombatLocal } from "./utils/combatUtil.js";
2020

@@ -61,7 +61,7 @@ const Hutao = {
6161
entry: getEntries,
6262
endIds: getEndIds,
6363
logs: getGachaLogs,
64-
upload: _,
64+
upload: uploadGachaLogs,
6565
delete: _,
6666
},
6767
};

src/plugins/Hutao/request/gachaReq.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,23 @@ export async function getGachaLogs(
7878
if (resp.retcode !== 0) return <TGApp.Plugins.Hutao.Base.Resp>resp;
7979
return <TGApp.Plugins.Hutao.Gacha.GachaLogRes>resp.data;
8080
}
81+
82+
/**
83+
* 上传抽卡记录
84+
* @since Beta v0.9.1
85+
* @param tk - token
86+
* @param data - 上传数据
87+
* @returns 上传结果
88+
*/
89+
export async function uploadGachaLogs(
90+
tk: string,
91+
data: TGApp.Plugins.Hutao.Gacha.UploadData,
92+
): Promise<TGApp.Plugins.Hutao.Gacha.UploadResp | TGApp.Plugins.Hutao.Base.Resp> {
93+
const url = `${GachaUrl}Upload`;
94+
const header = await getReqHeader(tk);
95+
return await TGHttp<TGApp.Plugins.Hutao.Gacha.UploadResp>(url, {
96+
method: "POST",
97+
headers: header,
98+
body: JSON.stringify(data, (_, v) => (typeof v === "bigint" ? v.toString() : v)),
99+
});
100+
}

src/plugins/Hutao/types/Gacha.d.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,23 @@ declare namespace TGApp.Plugins.Hutao.Gacha {
8383
*/
8484
Time: string;
8585
/** Id */
86-
Id: number;
86+
Id: number | bigint;
87+
};
88+
89+
/**
90+
* 上传响应
91+
* @since Beta v0.9.1
92+
*/
93+
type UploadResp = TGApp.Plugins.Hutao.Base.Resp<string>;
94+
95+
/**
96+
* 上传数据
97+
* @since Beta v0.9.1
98+
*/
99+
type UploadData = {
100+
/** UID */
101+
Uid: string;
102+
/** 数据 */
103+
Items: Array<GachaLog>;
87104
};
88105
}

src/store/modules/hutao.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ const useHutaoStore = defineStore(
113113
}
114114
}
115115

116+
function checkGachaExpire(): boolean {
117+
if (!userInfo.value) return true;
118+
if (userInfo.value.IsMaintainer || userInfo.value.IsLicensedDeveloper) return false;
119+
const expire = new Date(userInfo.value.GachaLogExpireAt).getTime();
120+
return Date.now() < expire;
121+
}
122+
116123
return {
117124
isLogin,
118125
userName,
@@ -124,6 +131,7 @@ const useHutaoStore = defineStore(
124131
tryLogin,
125132
tryRefreshToken,
126133
tryRefreshInfo,
134+
checkGachaExpire,
127135
};
128136
},
129137
{

src/utils/toolFunc.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { path } from "@tauri-apps/api";
1111
import { invoke } from "@tauri-apps/api/core";
1212
import { type } from "@tauri-apps/plugin-os";
1313
import TGLogger from "@utils/TGLogger.js";
14-
import { format, parseISO } from "date-fns";
14+
import { format, parse, parseISO } from "date-fns";
1515
import { v4 } from "uuid";
1616

1717
import { AppCalendarData, AppCharacterData, AppWeaponData } from "@/data/index.js";
@@ -381,3 +381,16 @@ export function timeStr2str(str: string): string {
381381
in: tz("Asia/Shanghai"),
382382
});
383383
}
384+
385+
/**
386+
* 接收本地时间字符串,转成 ISO8601(含 +08:00)
387+
* @since Beta v0.9.1
388+
* @param str - 时间字符串
389+
* @example "2025-09-18 09:01:39" → "2025-09-18T09:01:39+08:00"
390+
*/
391+
export function str2timeStr(str: string): string {
392+
// 解析为上海时区的本地日期(你可以改成别的时区)
393+
const d = parse(str, "yyyy-MM-dd HH:mm:ss", new Date(), { in: tz("Asia/Shanghai") });
394+
// 输出为 UTC 的 ISO 字符串
395+
return format(d, "yyyy-MM-dd'T'HH:mm:ss.SSSX", { in: tz("UTC") });
396+
}

0 commit comments

Comments
 (0)