♻️ 全面整理重构

This commit is contained in:
目棃
2024-12-13 16:15:01 +08:00
parent 86bfc80b57
commit 3ed6d503d4
229 changed files with 3105 additions and 4127 deletions

View File

@@ -5,7 +5,7 @@
<img src="/source/UI/act_birthday.png" alt="archive_birthday_icon" class="side-icon" />
</div>
<v-switch class="ab-draw-switch" v-model="isAether" />
{{ isAether ? "空" : "荧" }}
<span>{{ isAether ? "空" : "荧" }}</span>
<v-select
v-model="curSelect"
class="ab-select"
@@ -15,8 +15,7 @@
label="角色"
:item-value="(item: TGApp.Archive.Birth.RoleItem) => item"
:item-props="(item: TGApp.Archive.Birth.RoleItem) => getItemProps(item)"
>
</v-select>
/>
</div>
<div class="ab-draw-grid">
<div v-for="item in selectedItem" :key="item.op_id" class="ab-draw">
@@ -34,26 +33,36 @@
<ToArcBrith v-model="showOverlay" :data="current" :choice="isAether" />
</template>
<script lang="ts" setup>
import ToArcBrith from "@comp/pageArchive/to-arcBrith.vue";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { useRoute } from "vue-router";
import ToArcBrith from "../../components/pageArchive/to-arcBrith.vue";
import { ArcBirDraw, ArcBirRole } from "../../data/index.js";
import TGClient from "../../utils/TGClient.js";
import { ArcBirDraw, ArcBirRole } from "@/data/index.js";
import TGClient from "@/utils/TGClient.js";
const route = useRoute();
const page = ref<number>(1);
const length = ref<number>(0);
const visible = ref(0);
const renderItems = shallowRef<TGApp.Archive.Birth.DrawItem[]>([]);
const curSelect = shallowRef<TGApp.Archive.Birth.RoleItem | null>(null);
const current = shallowRef<TGApp.Archive.Birth.DrawItem>();
const visible = ref<number>(0);
const isAether = ref<boolean>(false);
const showOverlay = ref<boolean>(false);
const renderItems = shallowRef<Array<TGApp.Archive.Birth.DrawItem>>([]);
const curSelect = shallowRef<TGApp.Archive.Birth.RoleItem | null>(null);
const current = shallowRef<TGApp.Archive.Birth.DrawItem>();
const selectedItem = computed<Array<TGApp.Archive.Birth.DrawItem>>(() =>
renderItems.value.slice((page.value - 1) * 12, page.value * 12),
);
const selectedItem = computed<TGApp.Archive.Birth.DrawItem[]>(() => {
return renderItems.value.slice((page.value - 1) * 12, page.value * 12);
onMounted(() => {
const { date } = route.params;
if (date) {
const dataFind = ArcBirRole.find((i) => i.role_birthday === date);
if (dataFind) curSelect.value = dataFind;
} else renderItems.value = ArcBirDraw;
length.value = Math.ceil(renderItems.value.length / 12);
visible.value = length.value > 5 ? 5 : length.value;
page.value = 1;
});
watch(
@@ -63,29 +72,14 @@ watch(
renderItems.value = ArcBirDraw.filter(
(item) => item.birthday === curSelect.value?.role_birthday,
);
} else {
renderItems.value = ArcBirDraw;
}
} else renderItems.value = ArcBirDraw;
length.value = Math.ceil(renderItems.value.length / 12);
page.value = 1;
visible.value = length.value > 5 ? 5 : length.value;
},
);
onMounted(() => {
let { date } = route.params;
if (date) {
const dataFind = ArcBirRole.find((i) => i.role_birthday === date);
if (dataFind) curSelect.value = dataFind;
} else {
renderItems.value = ArcBirDraw;
}
length.value = Math.ceil(renderItems.value.length / 12);
visible.value = length.value > 5 ? 5 : length.value;
page.value = 1;
});
function showImg(item: TGApp.Archive.Birth.DrawItem) {
function showImg(item: TGApp.Archive.Birth.DrawItem): void {
current.value = item;
showOverlay.value = true;
}

View File

@@ -30,30 +30,20 @@
<v-btn
class="ua-btn"
@click="shareAbyss()"
:rounded="true"
:disabled="localAbyss.length === 0"
prepend-icon="mdi-share"
>
<v-icon>mdi-share</v-icon>
<span>分享</span>
</v-btn>
<v-btn class="ua-btn" @click="refreshAbyss()" :rounded="true">
<v-icon>mdi-refresh</v-icon>
<span>刷新</span>
</v-btn>
<v-btn class="ua-btn" @click="uploadAbyss()" :rounded="true">
<v-icon>mdi-cloud-upload</v-icon>
<span>上传</span>
</v-btn>
<v-btn class="ua-btn" @click="deleteAbyss()" :rounded="true">
<v-icon>mdi-delete</v-icon>
<span>删除</span>
分享
</v-btn>
<v-btn class="ua-btn" @click="refreshAbyss()" prepend-icon="mdi-refresh">刷新</v-btn>
<v-btn class="ua-btn" @click="uploadAbyss()" prepend-icon="mdi-cloud-upload">上传</v-btn>
<v-btn class="ua-btn" @click="deleteAbyss()" prepend-icon="mdi-delete">删除</v-btn>
</div>
</template>
</v-app-bar>
<div class="ua-box">
<v-tabs v-model="userTab" direction="vertical" class="ua-tabs-box" center-active>
<v-tab v-for="item in localAbyss" :key="item.id" :value="item.id"> {{ item.id }}</v-tab>
<v-tab v-for="item in localAbyss" :key="item.id" :value="item.id">{{ item.id }}</v-tab>
</v-tabs>
<v-window v-model="userTab" class="ua-window">
<v-window-item
@@ -105,67 +95,49 @@
</div>
</template>
<script lang="ts" setup>
import TSubLine from "@comp/app/t-subline.vue";
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TuaDetail from "@comp/userAbyss/tua-detail.vue";
import TuaOverview from "@comp/userAbyss/tua-overview.vue";
import Hutao from "@Hutao/index.js";
import TSUserAbyss from "@Sqlite/modules/userAbyss.js";
import TSUserAvatar from "@Sqlite/modules/userAvatar.js";
import { getVersion } from "@tauri-apps/api/app";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, watch } from "vue";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
import TSubLine from "../../components/app/t-subline.vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import TuaDetail from "../../components/userAbyss/tua-detail.vue";
import TuaOverview from "../../components/userAbyss/tua-overview.vue";
import Hutao from "../../plugins/Hutao/index.js";
import TSUserAbyss from "../../plugins/Sqlite/modules/userAbyss.js";
import TSUserAvatar from "../../plugins/Sqlite/modules/userAvatar.js";
import TSUserCombat from "../../plugins/Sqlite/modules/userCombat.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import { generateShareImg } from "../../utils/TGShare.js";
import TakumiRecordGenshinApi from "../../web/request/recordReq.js";
import { useUserStore } from "@/store/modules/user.js";
import TGLogger from "@/utils/TGLogger.js";
import { generateShareImg } from "@/utils/TGShare.js";
import TakumiRecordGenshinApi from "@/web/request/recordReq.js";
// store
const userStore = storeToRefs(useUserStore());
// data
const userTab = ref<number>(0);
const user = computed<TGApp.Sqlite.Account.Game>(() => userStore.account.value);
const localAbyss = ref<TGApp.Sqlite.Abyss.TableData[]>([]);
const abyssRef = ref<HTMLElement>(<HTMLElement>{});
const version = ref<string>();
const router = useRouter();
const uidList = ref<string[]>();
const { account, cookie } = storeToRefs(useUserStore());
const userTab = ref<number>(0);
const version = ref<string>();
const uidCur = ref<string>();
const abyssIdList = computed<number[]>(() => {
return localAbyss.value.map((abyss) => abyss.id);
});
const uidList = shallowRef<Array<string>>();
const localAbyss = shallowRef<TGApp.Sqlite.Abyss.TableData[]>([]);
const abyssIdList = computed<Array<number>>(() => localAbyss.value.map((abyss) => abyss.id));
onMounted(async () => {
showLoading.start("正在加载深渊数据...");
version.value = await getVersion();
await TGLogger.Info("[UserAbyss][onMounted] 打开角色深渊页面");
uidList.value = await TSUserAbyss.getAllUid();
if (uidList.value.includes(user.value.gameUid)) uidCur.value = user.value.gameUid;
if (uidList.value.includes(account.value.gameUid)) uidCur.value = account.value.gameUid;
else if (uidList.value.length > 0) uidCur.value = uidList.value[0];
else uidCur.value = "";
await loadAbyss();
showLoading.end();
});
watch(
() => uidCur.value,
async () => await loadAbyss(),
);
watch(() => uidCur.value, loadAbyss);
async function toCombat(): Promise<void> {
const check = await TSUserCombat.check();
if (!check) {
showSnackbar.warn("未检测到剧诗表,请更新数据库!");
return;
}
await router.push({ name: "真境剧诗" });
}
@@ -181,12 +153,12 @@ async function loadAbyss(): Promise<void> {
}
async function refreshAbyss(): Promise<void> {
if (!userStore.cookie.value) {
if (!cookie.value) {
showSnackbar.warn("未登录");
await TGLogger.Warn("[UserAbyss][getAbyssData] 未登录");
return;
}
if (uidCur.value && uidCur.value !== user.value.gameUid) {
if (uidCur.value && uidCur.value !== account.value.gameUid) {
const switchCheck = await showDialog.check(
"是否切换游戏账户",
`确认则尝试切换至 ${uidCur.value}`,
@@ -198,7 +170,7 @@ async function refreshAbyss(): Promise<void> {
}
const freshCheck = await showDialog.check(
"确定刷新?",
`用户${user.value.gameUid}与当前UID${uidCur.value}不一致`,
`用户${account.value.gameUid}与当前UID${uidCur.value}不一致`,
);
if (!freshCheck) {
showSnackbar.cancel("已取消深渊数据刷新");
@@ -206,43 +178,53 @@ async function refreshAbyss(): Promise<void> {
}
}
await TGLogger.Info("[UserAbyss][getAbyssData] 更新深渊数据");
showLoading.start("正在获取上期深渊数据...", `UID: ${user.value.gameUid}`);
const resP = await TakumiRecordGenshinApi.spiralAbyss(userStore.cookie.value, user.value, "2");
showLoading.start("正在获取上期深渊数据...", `UID: ${account.value.gameUid}`);
const resP = await TakumiRecordGenshinApi.spiralAbyss(cookie.value, account.value, "2");
if ("retcode" in resP) {
showLoading.end();
showSnackbar.error(`[${resP.retcode}]${resP.message}`);
await TGLogger.Error(`[UserAbyss][getAbyssData] 获取${user.value.gameUid}的上期深渊数据失败`);
await TGLogger.Error(
`[UserAbyss][getAbyssData] 获取${account.value.gameUid}的上期深渊数据失败`,
);
await TGLogger.Error(`[UserAbyss][getAbyssData] ${resP.retcode} ${resP.message}`);
return;
}
await TGLogger.Info("[UserAbyss][getAbyssData] 成功获取上期深渊数据");
showLoading.update("正在保存上期深渊数据...", `UID: ${user.value.gameUid}`);
await TSUserAbyss.saveAbyss(user.value.gameUid, resP);
showLoading.update("正在获取本期深渊数据...", `UID: ${user.value.gameUid}`);
const res = await TakumiRecordGenshinApi.spiralAbyss(userStore.cookie.value, user.value, "1");
showLoading.update("正在保存上期深渊数据...", `UID: ${account.value.gameUid}`);
await TSUserAbyss.saveAbyss(account.value.gameUid, resP);
showLoading.update("正在获取本期深渊数据...", `UID: ${account.value.gameUid}`);
const res = await TakumiRecordGenshinApi.spiralAbyss(cookie.value, account.value, "1");
if ("retcode" in res) {
showLoading.end();
showSnackbar.error(`[${res.retcode}]${res.message}`);
await TGLogger.Error(`[UserAbyss][getAbyssData] 获取${user.value.gameUid}的本期深渊数据失败`);
await TGLogger.Error(
`[UserAbyss][getAbyssData] 获取${account.value.gameUid}的本期深渊数据失败`,
);
await TGLogger.Error(`[UserAbyss][getAbyssData] ${res.retcode} ${res.message}`);
return;
}
showLoading.update("正在保存本期深渊数据...", `UID: ${user.value.gameUid}`);
await TSUserAbyss.saveAbyss(user.value.gameUid, res);
await TGLogger.Info(`[UserAbyss][getAbyssData] 成功获取${user.value.gameUid}的本期深渊数据`);
showLoading.update("正在加载深渊数据...", `UID: ${user.value.gameUid}`);
showLoading.update("正在保存本期深渊数据...", `UID: ${account.value.gameUid}`);
await TSUserAbyss.saveAbyss(account.value.gameUid, res);
await TGLogger.Info(`[UserAbyss][getAbyssData] 成功获取${account.value.gameUid}的本期深渊数据`);
showLoading.update("正在加载深渊数据...", `UID: ${account.value.gameUid}`);
uidList.value = await TSUserAbyss.getAllUid();
uidCur.value = user.value.gameUid;
uidCur.value = account.value.gameUid;
await loadAbyss();
showLoading.end();
}
async function shareAbyss(): Promise<void> {
await TGLogger.Info(`[UserAbyss][shareAbyss][${userTab.value}] 生成深渊数据分享图片`);
const fileName = `【深渊数据】${userTab.value}-${user.value.gameUid}`;
const fileName = `【深渊数据】${userTab.value}-${account.value.gameUid}`;
showLoading.start("正在生成图片", `${fileName}.png`);
abyssRef.value = <HTMLElement>document.getElementById(`user-abyss-${userTab.value}`);
await generateShareImg(fileName, abyssRef.value);
const shareDom = document.querySelector<HTMLElement>(`#user-abyss-${userTab.value}`);
if (shareDom === null) {
showLoading.end();
showSnackbar.error("未找到深渊数据");
await TGLogger.Error("[UserAbyss][shareAbyss] 未找到深渊数据");
return;
}
await generateShareImg(fileName, shareDom);
showLoading.end();
await TGLogger.Info(`[UserAbyss][shareAbyss][${userTab.value}] 生成深渊数据分享图片成功`);
}
@@ -270,18 +252,18 @@ async function uploadAbyss(): Promise<void> {
return;
}
try {
showLoading.start("正在上传深渊数据", `UID: ${user.value.gameUid}`);
showLoading.start("正在上传深渊数据", `UID: ${account.value.gameUid}`);
const transAbyss = Hutao.Abyss.utils.transData(abyssData);
showLoading.update("正在获取角色数据", `UID: ${user.value.gameUid}`);
const roles = await TSUserAvatar.getAvatars(Number(user.value.gameUid));
showLoading.update("正在获取角色数据", `UID: ${account.value.gameUid}`);
const roles = await TSUserAvatar.getAvatars(Number(account.value.gameUid));
if (!roles) {
showLoading.end();
showSnackbar.warn("未找到角色数据");
return;
}
showLoading.update("正在转换角色数据", `UID: ${user.value.gameUid}`);
showLoading.update("正在转换角色数据", `UID: ${account.value.gameUid}`);
transAbyss.Avatars = Hutao.Abyss.utils.transAvatars(roles);
showLoading.update("正在上传深渊数据", `UID: ${user.value.gameUid}`);
showLoading.update("正在上传深渊数据", `UID: ${account.value.gameUid}`);
const res = await Hutao.Abyss.upload(transAbyss);
showLoading.end();
if (res.retcode !== 0) {

View File

@@ -87,71 +87,60 @@
<img src="/source/UI/empty.webp" alt="empty" />
</div>
</div>
<suspense v-if="dataVal">
<TuaDetailOverlay
v-model="showOverlay"
:avatar="dataVal"
:avatars="selectedList"
v-model:mode="showMode"
@to-next="handleSwitch"
@to-avatar="selectRole"
/>
</suspense>
<TuaDetailOverlay
v-if="dataVal"
v-model="showOverlay"
:avatar="dataVal"
:avatars="selectedList"
v-model:mode="showMode"
@to-next="handleSwitch"
@to-avatar="selectRole"
/>
<TwoSelectC v-model="showSelect" @select-c="handleSelect" v-model:reset="resetSelect" />
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TwoSelectC, { type SelectedCValue } from "@comp/pageWiki/two-select-c.vue";
import TuaAvatarBox from "@comp/userAvatar/tua-avatar-box.vue";
import TuaDetailOverlay from "@comp/userAvatar/tua-detail-overlay.vue";
import TSUserAvatar from "@Sqlite/modules/userAvatar.js";
import { getVersion } from "@tauri-apps/api/app";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, watch } from "vue";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import TwoSelectC, { SelectedCValue } from "../../components/pageWiki/two-select-c.vue";
import TuaAvatarBox from "../../components/userAvatar/tua-avatar-box.vue";
import TuaDetailOverlay from "../../components/userAvatar/tua-detail-overlay.vue";
import { AppCharacterData } from "../../data/index.js";
import TSUserAvatar from "../../plugins/Sqlite/modules/userAvatar.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import { generateShareImg } from "../../utils/TGShare.js";
import { timestampToDate } from "../../utils/toolFunc.js";
import TakumiRecordGenshinApi from "../../web/request/recordReq.js";
import { AppCharacterData } from "@/data/index.js";
import { useUserStore } from "@/store/modules/user.js";
import TGLogger from "@/utils/TGLogger.js";
import { generateShareImg } from "@/utils/TGShare.js";
import { timestampToDate } from "@/utils/toolFunc.js";
import TakumiRecordGenshinApi from "@/web/request/recordReq.js";
const { cookie, account, propMap } = storeToRefs(useUserStore());
type TabItem = { label: string; value: string };
// loading
const loadData = ref<boolean>(false);
const loadShare = ref<boolean>(false);
const loadDel = ref<boolean>(false);
const version = ref<string>();
// data
const isEmpty = ref<boolean>(true);
const roleList = ref<TGApp.Sqlite.Character.UserRole[]>([]);
const selectedList = ref<TGApp.Sqlite.Character.UserRole[]>([]);
// overlay
const dataVal = ref<TGApp.Sqlite.Character.UserRole>();
const showOverlay = ref<boolean>(false);
const selectIndex = ref<number>(0);
const showSelect = ref<boolean>(false);
const showMode = ref<"classic" | "card" | "dev">("dev");
const resetSelect = ref<boolean>(false);
const modeList = [
const modeList: Readonly<Array<TabItem>> = [
{ label: "经典视图", value: "classic" },
{ label: "卡片视图(简略)", value: "card" },
{ label: "卡片视图(详细)", value: "dev" },
];
const enableShare = computed<boolean>(() => {
if (showOverlay.value) return true;
return showSelect.value;
});
const { cookie, account, propMap } = storeToRefs(useUserStore());
const loadData = ref<boolean>(false);
const loadShare = ref<boolean>(false);
const loadDel = ref<boolean>(false);
const version = ref<string>();
const isEmpty = ref<boolean>(true);
const showOverlay = ref<boolean>(false);
const selectIndex = ref<number>(0);
const showSelect = ref<boolean>(false);
const showMode = ref<"classic" | "card" | "dev">("dev");
const resetSelect = ref<boolean>(false);
const uidCur = ref<string>();
const uidList = ref<string[]>([]);
const uidList = shallowRef<Array<string>>([]);
const roleList = shallowRef<Array<TGApp.Sqlite.Character.UserRole>>([]);
const selectedList = shallowRef<Array<TGApp.Sqlite.Character.UserRole>>([]);
const dataVal = shallowRef<TGApp.Sqlite.Character.UserRole>();
const enableShare = computed<boolean>(() => (showOverlay.value ? true : showSelect.value));
onMounted(async () => {
showLoading.start("正在获取角色数据...");
@@ -193,31 +182,23 @@ watch(
}
},
);
watch(
() => uidCur.value,
async () => await loadRole(),
);
watch(() => uidCur.value, loadRole);
function getOrderedList(
data: TGApp.Sqlite.Character.UserRole[],
): TGApp.Sqlite.Character.UserRole[] {
data: Array<TGApp.Sqlite.Character.UserRole>,
): Array<TGApp.Sqlite.Character.UserRole> {
return data.sort((a, b) => {
if (a.avatar.rarity !== b.avatar.rarity) return b.avatar.rarity - a.avatar.rarity;
if (a.avatar.element !== b.avatar.element) {
return a.avatar.element.localeCompare(b.avatar.element);
}
return a.cid - b.cid;
if (a.avatar.element === b.avatar.element) return a.cid - b.cid;
return a.avatar.element.localeCompare(b.avatar.element);
});
}
async function loadUid(): Promise<void> {
uidList.value = await TSUserAvatar.getAllUid();
if (uidList.value.length === 0) uidList.value = [account.value.gameUid];
if (uidList.value.includes(account.value.gameUid)) {
uidCur.value = account.value.gameUid;
} else {
uidCur.value = uidList.value[0];
}
if (uidList.value.includes(account.value.gameUid)) uidCur.value = account.value.gameUid;
else uidCur.value = uidList.value[0];
}
async function loadRole(): Promise<void> {
@@ -330,7 +311,11 @@ async function share(): Promise<void> {
return;
}
await TGLogger.Info(`[Character][shareRoles][${account.value.gameUid}] 正在生成分享图片`);
const rolesBox = <HTMLElement>document.querySelector(".uc-box");
const rolesBox = document.querySelector<HTMLElement>(".uc-box");
if (rolesBox === null) {
showSnackbar.error("未找到角色列表");
return;
}
const fileName = `【角色列表】-${account.value.gameUid}`;
showLoading.start("正在生成图片", `${fileName}.png`);
loadShare.value = true;
@@ -359,21 +344,17 @@ async function deleteUid(): Promise<void> {
function getUpdateTime(): string {
if (roleList.value.length === 0) return "";
let lastUpdateTime = 0;
roleList.value.forEach((role) => {
for (const role of roleList.value) {
const updateTime = new Date(role.updated).getTime();
if (updateTime > lastUpdateTime) {
lastUpdateTime = updateTime;
}
});
if (updateTime > lastUpdateTime) lastUpdateTime = updateTime;
}
return timestampToDate(lastUpdateTime);
}
function selectRole(role: TGApp.Sqlite.Character.UserRole): void {
dataVal.value = role;
selectIndex.value = roleList.value.indexOf(role);
if (!showOverlay.value) {
showOverlay.value = true;
}
if (!showOverlay.value) showOverlay.value = true;
}
function handleSelect(val: SelectedCValue) {
@@ -396,9 +377,7 @@ function handleSelect(val: SelectedCValue) {
if (!selectedList.value.includes(dataVal.value)) {
dataVal.value = selectedList.value[0];
selectIndex.value = 0;
} else {
selectIndex.value = selectedList.value.indexOf(dataVal.value);
}
} else selectIndex.value = selectedList.value.indexOf(dataVal.value);
}
function handleSwitch(next: boolean): void {

View File

@@ -12,15 +12,11 @@
title="游戏UID"
/>
<v-btn :rounded="true" class="uc-btn" @click="toAbyss()">
<template #prepend>
<img src="/source/UI/userAbyss.webp" alt="abyss" />
</template>
<template #prepend><img src="/source/UI/userAbyss.webp" alt="abyss" /></template>
<span>深境螺旋</span>
</v-btn>
<v-btn :rounded="true" class="uc-btn" @click="loadWiki()">
<template #prepend>
<img src="/source/UI/wikiAbyss.webp" alt="abyss" />
</template>
<template #prepend><img src="/source/UI/wikiAbyss.webp" alt="abyss" /></template>
<span>统计数据</span>
</v-btn>
</div>
@@ -32,21 +28,23 @@
@click="shareCombat()"
:rounded="true"
:disabled="localCombat.length === 0"
prepend-icon="mdi-share"
>
<v-icon>mdi-share</v-icon>
<span>分享</span>
分享
</v-btn>
<v-btn class="uc-btn" @click="refreshCombat()" :rounded="true">
<v-icon>mdi-refresh</v-icon>
<span>刷新</span>
<v-btn class="uc-btn" @click="refreshCombat()" :rounded="true" prepend-icon="mdi-refresh">
刷新
</v-btn>
<v-btn class="uc-btn" @click="uploadCombat()" :rounded="true">
<v-icon>mdi-cloud-upload</v-icon>
<span>上传</span>
<v-btn
class="uc-btn"
@click="uploadCombat()"
:rounded="true"
prepend-icon="mdi-cloud-upload"
>
上传
</v-btn>
<v-btn class="uc-btn" @click="deleteCombat()" :rounded="true">
<v-icon>mdi-delete</v-icon>
<span>删除</span>
<v-btn class="uc-btn" @click="deleteCombat()" :rounded="true" prepend-icon="mdi-delete">
删除
</v-btn>
</div>
</template>
@@ -97,45 +95,36 @@
<TucOverlay v-model="showData" :data="cloudCombat" />
</template>
<script lang="ts" setup>
import TSubLine from "@comp/app/t-subline.vue";
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TucAvatars from "@comp/userCombat/tuc-avatars.vue";
import TucOverlay from "@comp/userCombat/tuc-overlay.vue";
import TucOverview from "@comp/userCombat/tuc-overview.vue";
import TucRound from "@comp/userCombat/tuc-round.vue";
import Hutao from "@Hutao/index.js";
import TSUserCombat from "@Sqlite/modules/userCombat.js";
import { getVersion } from "@tauri-apps/api/app";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, watch } from "vue";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
import TSubLine from "../../components/app/t-subline.vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import TucAvatars from "../../components/userCombat/tuc-avatars.vue";
import TucOverlay from "../../components/userCombat/tuc-overlay.vue";
import TucOverview from "../../components/userCombat/tuc-overview.vue";
import TucRound from "../../components/userCombat/tuc-round.vue";
import Hutao from "../../plugins/Hutao/index.js";
import TSUserCombat from "../../plugins/Sqlite/modules/userCombat.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import { generateShareImg } from "../../utils/TGShare.js";
import TakumiRecordGenshinApi from "../../web/request/recordReq.js";
import { useUserStore } from "@/store/modules/user.js";
import TGLogger from "@/utils/TGLogger.js";
import { generateShareImg } from "@/utils/TGShare.js";
import TakumiRecordGenshinApi from "@/web/request/recordReq.js";
// store
const userStore = storeToRefs(useUserStore());
// data
const router = useRouter();
const { account, cookie } = storeToRefs(useUserStore());
const userTab = ref<number>(0);
const user = computed<TGApp.Sqlite.Account.Game>(() => userStore.account.value);
const localCombat = ref<TGApp.Sqlite.Combat.SingleTable[]>([]);
const combatRef = ref<HTMLElement>(<HTMLElement>{});
const cloudCombat = ref<TGApp.Plugins.Hutao.Combat.Data>();
const showData = ref<boolean>(false);
const version = ref<string>();
const router = useRouter();
const uidList = ref<string[]>();
const uidCur = ref<string>();
const combatIdList = computed<number[]>(() => {
return localCombat.value.map((combat) => combat.id);
});
const uidList = shallowRef<Array<string>>();
const localCombat = shallowRef<Array<TGApp.Sqlite.Combat.SingleTable>>([]);
const cloudCombat = shallowRef<TGApp.Plugins.Hutao.Combat.Data>();
const combatIdList = computed<Array<number>>(() => localCombat.value.map((combat) => combat.id));
onMounted(async () => {
showLoading.start("正在加载剧诗数据...");
@@ -143,17 +132,14 @@ onMounted(async () => {
await TGLogger.Info("[UserCombat][onMounted] 打开真境剧诗页面");
showLoading.update("正在加载用户数据...");
uidList.value = await TSUserCombat.getAllUid();
if (uidList.value.includes(user.value.gameUid)) uidCur.value = user.value.gameUid;
if (uidList.value.includes(account.value.gameUid)) uidCur.value = account.value.gameUid;
else if (uidList.value.length > 0) uidCur.value = uidList.value[0];
else uidCur.value = "";
await loadCombat();
showLoading.end();
});
watch(
() => uidCur.value,
async () => await loadCombat(),
);
watch(() => uidCur.value, loadCombat);
async function toAbyss(): Promise<void> {
await router.push({ name: "深渊记录" });
@@ -177,12 +163,12 @@ async function loadWiki(): Promise<void> {
}
async function refreshCombat(): Promise<void> {
if (!userStore.cookie.value) {
if (!cookie.value) {
showSnackbar.error("未登录");
await TGLogger.Warn("[UserCombat][getAbyssData] 未登录");
return;
}
if (uidCur.value && uidCur.value !== user.value.gameUid) {
if (uidCur.value && uidCur.value !== account.value.gameUid) {
const switchCheck = await showDialog.check(
"是否切换游戏账户",
`确认则尝试切换至 ${uidCur.value}`,
@@ -194,7 +180,7 @@ async function refreshCombat(): Promise<void> {
}
const freshCheck = await showDialog.check(
"确定刷新?",
`用户${user.value.gameUid}与当前UID${uidCur.value}不一致`,
`用户${account.value.gameUid}与当前UID${uidCur.value}不一致`,
);
if (!freshCheck) {
showSnackbar.cancel("已取消剧诗数据刷新");
@@ -202,8 +188,8 @@ async function refreshCombat(): Promise<void> {
}
}
await TGLogger.Info("[UserCombat][getCombatData] 更新剧诗数据");
showLoading.start("正在获取剧诗数据...", `UID: ${user.value.gameUid}`);
const res = await TakumiRecordGenshinApi.roleCombat(userStore.cookie.value, user.value);
showLoading.start("正在获取剧诗数据...", `UID: ${account.value.gameUid}`);
const res = await TakumiRecordGenshinApi.roleCombat(cookie.value, account.value);
if (res === false) {
showLoading.end();
showSnackbar.warn("用户未解锁幻想真境剧诗");
@@ -212,28 +198,33 @@ async function refreshCombat(): Promise<void> {
if ("retcode" in res) {
showLoading.end();
showSnackbar.error(`[${res.retcode}]${res.message}`);
await TGLogger.Error(`[UserCombat][getCombatData] 获取${user.value.gameUid}的剧诗数据失败`);
await TGLogger.Error(`[UserCombat][getCombatData] 获取${account.value.gameUid}的剧诗数据失败`);
await TGLogger.Error(`[UserCombat][getCombatData] ${res.retcode} ${res.message}`);
return;
}
showLoading.update("正在保存剧诗数据...");
for (const combat of res) {
showLoading.update("正在保存剧诗数据...", `${combat.schedule.schedule_id}`);
await TSUserCombat.saveCombat(user.value.gameUid, combat);
await TSUserCombat.saveCombat(account.value.gameUid, combat);
}
showLoading.update("正在加载剧诗数据...");
uidList.value = await TSUserCombat.getAllUid();
uidCur.value = user.value.gameUid;
uidCur.value = account.value.gameUid;
await loadCombat();
showLoading.end();
}
async function shareCombat(): Promise<void> {
await TGLogger.Info(`[UserCombat][shareCombat][${userTab.value}] 生成剧诗数据分享图片`);
const fileName = `【剧诗数据】${userTab.value}-${user.value.gameUid}`;
const fileName = `【剧诗数据】${userTab.value}-${account.value.gameUid}`;
const shareDom = document.querySelector<HTMLElement>(`#user-combat-${userTab.value}`);
if (shareDom === null) {
showSnackbar.error("未找到分享数据");
await TGLogger.Warn(`[UserCombat][shareCombat][${userTab.value}] 未找到分享数据`);
return;
}
showLoading.start("正在生成图片", `${fileName}.png`);
combatRef.value = <HTMLElement>document.getElementById(`user-combat-${userTab.value}`);
await generateShareImg(fileName, combatRef.value);
await generateShareImg(fileName, shareDom);
showLoading.end();
await TGLogger.Info(`[UserCombat][shareCombat][${userTab.value}] 生成剧诗数据分享图片成功`);
}

View File

@@ -48,54 +48,35 @@
<UgoUid v-model="ovShow" :mode="ovMode" />
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import GroEcharts from "@comp/userGacha/gro-echarts.vue";
import GroHistory from "@comp/userGacha/gro-history.vue";
import GroOverview from "@comp/userGacha/gro-overview.vue";
import GroTable from "@comp/userGacha/gro-table.vue";
import UgoUid from "@comp/userGacha/ugo-uid.vue";
import TSUserGacha from "@Sqlite/modules/userGacha.js";
import { path } from "@tauri-apps/api";
import { open, save } from "@tauri-apps/plugin-dialog";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, watch } from "vue";
import { onMounted, ref, shallowRef, watch } from "vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import GroEcharts from "../../components/userGacha/gro-echarts.vue";
import GroHistory from "../../components/userGacha/gro-history.vue";
import GroOverview from "../../components/userGacha/gro-overview.vue";
import GroTable from "../../components/userGacha/gro-table.vue";
import UgoUid from "../../components/userGacha/ugo-uid.vue";
import { AppCharacterData, AppWeaponData } from "../../data/index.js";
import TSUserGacha from "../../plugins/Sqlite/modules/userGacha.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import { exportUigfData, readUigfData, verifyUigfData } from "../../utils/UIGF.js";
import Hk4eApi from "../../web/request/hk4eReq.js";
import TakumiApi from "../../web/request/takumiReq.js";
import { AppCharacterData, AppWeaponData } from "@/data/index.js";
import { useUserStore } from "@/store/modules/user.js";
import TGLogger from "@/utils/TGLogger.js";
import { exportUigfData, readUigfData, verifyUigfData } from "@/utils/UIGF.js";
import Hk4eApi from "@/web/request/hk4eReq.js";
import TakumiApi from "@/web/request/takumiReq.js";
// store
const userStore = storeToRefs(useUserStore());
const account = computed<TGApp.Sqlite.Account.Game>(() => userStore.account.value);
const { account, cookie } = storeToRefs(useUserStore());
const authkey = ref<string>("");
// data
const selectItem = ref<string[]>([]);
const uidCur = ref<string>();
const gachaListCur = ref<TGApp.Sqlite.GachaRecords.SingleTable[]>([]);
const tab = ref<string>("overview");
// overlay
const ovShow = ref<boolean>(false);
const ovMode = ref<"export" | "import">("import");
// 监听 UID 变化
watch(
() => uidCur.value,
async (newUid) => {
if (!newUid) return;
gachaListCur.value = await TSUserGacha.getGachaRecords(newUid);
showSnackbar.success(`成功获取 ${gachaListCur.value.length} 条祈愿数据`);
await TGLogger.Info(
`[UserGacha][${newUid}][watch] 成功获取 ${gachaListCur.value.length} 条祈愿数据`,
);
},
);
const selectItem = shallowRef<Array<string>>([]);
const gachaListCur = shallowRef<Array<TGApp.Sqlite.GachaRecords.SingleTable>>([]);
onMounted(async () => {
showLoading.start("正在加载祈愿数据...", "正在获取祈愿 UID 列表");
@@ -117,6 +98,19 @@ onMounted(async () => {
showSnackbar.success(`成功获取 ${gachaListCur.value.length} 条祈愿数据`);
});
// 监听 UID 变化
watch(
() => uidCur.value,
async (newUid) => {
if (!newUid) return;
gachaListCur.value = await TSUserGacha.getGachaRecords(newUid);
showSnackbar.success(`成功获取 ${gachaListCur.value.length} 条祈愿数据`);
await TGLogger.Info(
`[UserGacha][${newUid}][watch] 成功获取 ${gachaListCur.value.length} 条祈愿数据`,
);
},
);
// 刷新按钮点击事件
async function confirmRefresh(force: boolean): Promise<void> {
await TGLogger.Info(`[UserGacha][${account.value.gameUid}][confirmRefresh] 刷新祈愿数据`);
@@ -140,13 +134,13 @@ async function confirmRefresh(force: boolean): Promise<void> {
}
}
showLoading.start("正在刷新祈愿数据", "正在获取 authkey");
if (!userStore.cookie.value) {
if (!cookie.value) {
showLoading.end();
showSnackbar.error("请先登录");
await TGLogger.Warn("[UserGacha][${account.gameUid}][confirmRefresh] 未检测到 cookie");
return;
}
const authkeyRes = await TakumiApi.bind.authKey(userStore.cookie.value, account.value);
const authkeyRes = await TakumiApi.bind.authKey(cookie.value, account.value);
if (typeof authkeyRes === "string") {
authkey.value = authkeyRes;
await TGLogger.Info(`[UserGacha][${account.value.gameUid}][confirmRefresh] 成功获取 authkey`);
@@ -168,7 +162,7 @@ async function confirmRefresh(force: boolean): Promise<void> {
showLoading.update("正在刷新祈愿数据", "数据获取完成,即将刷新页面");
showLoading.end();
await TGLogger.Info(`[UserGacha][${account.value.gameUid}][confirmRefresh] 刷新祈愿数据完成`);
// window.location.reload();
window.location.reload();
}
// 刷新单个池子
@@ -242,8 +236,7 @@ async function refreshGachaPool(
}
}
// 导入 v4 版本的祈愿数据
async function importUigf4(): Promise<void> {
function importUigf4(): void {
ovMode.value = "import";
ovShow.value = true;
}

View File

@@ -55,34 +55,30 @@
</div>
</template>
<script lang="ts" setup>
import TSubLine from "@comp/app/t-subline.vue";
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TurAvatarGrid from "@comp/userRecord/tur-avatar-grid.vue";
import TurHomeGrid from "@comp/userRecord/tur-home-grid.vue";
import TurOverviewGrid from "@comp/userRecord/tur-overview-grid.vue";
import TurRoleInfo from "@comp/userRecord/tur-role-info.vue";
import TurWorldGrid from "@comp/userRecord/tur-world-grid.vue";
import TSUserRecord from "@Sqlite/modules/userRecord.js";
import { getVersion } from "@tauri-apps/api/app";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, watch } from "vue";
import { onMounted, ref, shallowRef, watch } from "vue";
import TSubLine from "../../components/app/t-subline.vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import TurAvatarGrid from "../../components/userRecord/tur-avatar-grid.vue";
import TurHomeGrid from "../../components/userRecord/tur-home-grid.vue";
import TurOverviewGrid from "../../components/userRecord/tur-overview-grid.vue";
import TurRoleInfo from "../../components/userRecord/tur-role-info.vue";
import TurWorldGrid from "../../components/userRecord/tur-world-grid.vue";
import TSUserRecord from "../../plugins/Sqlite/modules/userRecord.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import { generateShareImg } from "../../utils/TGShare.js";
import TakumiRecordGenshinApi from "../../web/request/recordReq.js";
import { useUserStore } from "@/store/modules/user.js";
import TGLogger from "@/utils/TGLogger.js";
import { generateShareImg } from "@/utils/TGShare.js";
import TakumiRecordGenshinApi from "@/web/request/recordReq.js";
// store
const userStore = storeToRefs(useUserStore());
const user = computed<TGApp.Sqlite.Account.Game>(() => userStore.account.value);
// data
const { account, cookie } = storeToRefs(useUserStore());
const uidCur = ref<number>();
const uidList = ref<number[]>([]);
const recordData = ref<TGApp.Sqlite.Record.RenderData>();
const version = ref<string>();
const uidList = shallowRef<Array<number>>([]);
const recordData = shallowRef<TGApp.Sqlite.Record.RenderData>();
onMounted(async () => {
showLoading.start("正在获取战绩数据...");
@@ -92,19 +88,14 @@ onMounted(async () => {
showLoading.end();
});
watch(
() => uidCur.value,
async () => await loadRecord(),
);
watch(() => uidCur.value, loadRecord);
async function loadUid(): Promise<void> {
uidList.value = await TSUserRecord.getAllUid();
if (uidList.value.length === 0) uidList.value = [Number(user.value.gameUid)];
if (uidList.value.includes(Number(user.value.gameUid))) {
uidCur.value = Number(user.value.gameUid);
} else {
uidCur.value = uidList.value[0];
}
if (uidList.value.length === 0) uidList.value = [Number(account.value.gameUid)];
if (uidList.value.includes(Number(account.value.gameUid))) {
uidCur.value = Number(account.value.gameUid);
} else uidCur.value = uidList.value[0];
}
async function loadRecord(): Promise<void> {
@@ -116,8 +107,12 @@ async function loadRecord(): Promise<void> {
}
async function refreshRecord(): Promise<void> {
if (!user.value) return;
if (uidCur.value && uidCur.value.toString() !== user.value.gameUid) {
if (!cookie.value) {
showSnackbar.warn("请先登录");
await TGLogger.Warn(`[UserRecord][refresh][${account.value.gameUid}] 未登录`);
return;
}
if (uidCur.value && uidCur.value.toString() !== account.value.gameUid) {
const switchCheck = await showDialog.check(
"是否切换游戏账户",
`确认则尝试切换至${uidCur.value}`,
@@ -129,7 +124,7 @@ async function refreshRecord(): Promise<void> {
}
const freshCheck = await showDialog.check(
"是否刷新战绩数据",
`用户${user.value.gameUid}与当前UID${uidCur.value}不一致`,
`用户${account.value.gameUid}与当前UID${uidCur.value}不一致`,
);
if (!freshCheck) {
showSnackbar.cancel("已取消战绩数据刷新");
@@ -137,28 +132,22 @@ async function refreshRecord(): Promise<void> {
}
}
showLoading.start("正在刷新战绩数据...");
await TGLogger.Info(`[UserRecord][refresh][${user.value.gameUid}] 刷新战绩数据`);
if (!userStore.cookie.value) {
showLoading.end();
showSnackbar.warn("请先登录");
await TGLogger.Warn(`[UserRecord][refresh][${user.value.gameUid}] 未登录`);
return;
}
const res = await TakumiRecordGenshinApi.index(userStore.cookie.value, user.value);
await TGLogger.Info(`[UserRecord][refresh][${account.value.gameUid}] 刷新战绩数据`);
const res = await TakumiRecordGenshinApi.index(cookie.value, account.value);
if ("retcode" in res) {
showLoading.end();
showSnackbar.error(`[${res.retcode}] ${res.message}`);
await TGLogger.Error(`[UserRecord][refresh][${user.value.gameUid}] 获取战绩数据失败`);
await TGLogger.Error(`[UserRecord][refresh][${account.value.gameUid}] 获取战绩数据失败`);
await TGLogger.Error(
`[UserRecord][refresh][${user.value.gameUid}] ${res.retcode} ${res.message}`,
`[UserRecord][refresh][${account.value.gameUid}] ${res.retcode} ${res.message}`,
);
return;
}
await TGLogger.Info(`[UserRecord][refresh][${user.value.gameUid}] 获取战绩数据成功`);
await TGLogger.Info(`[UserRecord][refresh][${user.value.gameUid}]`, false);
await TGLogger.Info(`[UserRecord][refresh][${account.gameUid}] 获取战绩数据成功`);
await TGLogger.Info(`[UserRecord][refresh][${account.value.gameUid}]`, false);
console.log(res);
showLoading.update("正在保存战绩数据");
await TSUserRecord.saveRecord(Number(user.value.gameUid), res);
await TSUserRecord.saveRecord(Number(account.value.gameUid), res);
showLoading.update("正在加载战绩数据");
await loadUid();
await loadRecord();
@@ -171,13 +160,17 @@ async function shareRecord(): Promise<void> {
showSnackbar.warn("未找到战绩数据,请尝试刷新");
return;
}
await TGLogger.Info(`[UserRecord][shareRecord][${user.value.gameUid}] 生成分享图片`);
const recordBox = <HTMLElement>document.querySelector(".ur-box");
const fileName = `【原神战绩】-${user.value.gameUid}`;
await TGLogger.Info(`[UserRecord][shareRecord][${account.value.gameUid}] 生成分享图片`);
const recordBox = document.querySelector<HTMLElement>(".ur-box");
if (recordBox === null) {
showSnackbar.error("未找到战绩数据,请尝试刷新");
return;
}
const fileName = `【原神战绩】-${account.value.gameUid}`;
showLoading.start("正在生成图片", fileName);
await generateShareImg(fileName, recordBox);
showLoading.end();
await TGLogger.Info(`[UserRecord][shareRecord][${user.value.gameUid}] 生成分享图片成功`);
await TGLogger.Info(`[UserRecord][shareRecord][${account.value.gameUid}] 生成分享图片成功`);
}
async function deleteRecord(): Promise<void> {

View File

@@ -16,7 +16,7 @@
</template>
<template #append>
<div class="hta-top-append">
<span @click="show()" v-if="overview">
<span @click="showDialog = !showDialog" v-if="overview">
更新于 {{ timestampToDate(overview.cur.Timestamp) }}
</span>
</div>
@@ -41,16 +41,16 @@
<HtaOverlayOverview v-if="overview" v-model="showDialog" :data="overview" />
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
import showLoading from "@comp/func/loading.js";
import HtaOverlayOverview from "@comp/hutaoAbyss/hta-overlay-overview.vue";
import HtaTabHold from "@comp/hutaoAbyss/hta-tab-hold.vue";
import HtaTabTeam from "@comp/hutaoAbyss/hta-tab-team.vue";
import HtaTabUp from "@comp/hutaoAbyss/hta-tab-up.vue";
import HtaTabUse from "@comp/hutaoAbyss/hta-tab-use.vue";
import Hutao from "@Hutao/index.js";
import { onMounted, ref, shallowRef, triggerRef, watch } from "vue";
import showLoading from "../../components/func/loading.js";
import HtaOverlayOverview from "../../components/hutaoAbyss/hta-overlay-overview.vue";
import HtaTabHold from "../../components/hutaoAbyss/hta-tab-hold.vue";
import HtaTabTeam from "../../components/hutaoAbyss/hta-tab-team.vue";
import HtaTabUp from "../../components/hutaoAbyss/hta-tab-up.vue";
import HtaTabUse from "../../components/hutaoAbyss/hta-tab-use.vue";
import Hutao from "../../plugins/Hutao/index.js";
import { timestampToDate } from "../../utils/toolFunc.js";
import { timestampToDate } from "@/utils/toolFunc.js";
enum AbyssTabEnum {
use = "角色使用",
@@ -63,30 +63,26 @@ type AbyssTab = keyof typeof AbyssTabEnum;
type AbyssList = Array<{ label: AbyssTabEnum; value: AbyssTab }>;
export type AbyssDataItem<T> = { cur: T; last: T };
export type AbyssDataItemType<T extends AbyssTab> = T extends "use"
? AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>
? AbyssDataItem<Array<TGApp.Plugins.Hutao.Abyss.AvatarUse>>
: T extends "up"
? AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUp[]>
? AbyssDataItem<Array<TGApp.Plugins.Hutao.Abyss.AvatarUp>>
: T extends "team"
? TGApp.Plugins.Hutao.Abyss.TeamCombination[]
? Array<TGApp.Plugins.Hutao.Abyss.TeamCombination>
: T extends "hold"
? AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarHold[]>
? AbyssDataItem<Array<TGApp.Plugins.Hutao.Abyss.AvatarHold>>
: null;
type AbyssData = {
[key in AbyssTab]: AbyssDataItemType<key> | null;
};
type AbyssData = { [key in AbyssTab]: AbyssDataItemType<key> | null };
const abyssList: AbyssList = [
const abyssList: Readonly<AbyssList> = [
{ label: AbyssTabEnum.use, value: "use" },
{ label: AbyssTabEnum.up, value: "up" },
{ label: AbyssTabEnum.team, value: "team" },
{ label: AbyssTabEnum.hold, value: "hold" },
];
const showDialog = ref<boolean>(false);
// data
const overview = ref<AbyssDataItem<TGApp.Plugins.Hutao.Abyss.OverviewData>>();
const tab = ref<AbyssTab>("use");
const abyssData = ref<AbyssData>({ use: null, up: null, team: null, hold: null });
const tab = shallowRef<AbyssTab>("use");
const overview = shallowRef<AbyssDataItem<TGApp.Plugins.Hutao.Abyss.OverviewData>>();
const abyssData = shallowRef<AbyssData>({ use: null, up: null, team: null, hold: null });
watch(
() => tab.value,
@@ -105,10 +101,6 @@ onMounted(async () => {
showLoading.end();
});
function show(): void {
showDialog.value = !showDialog.value;
}
async function refreshData(type: AbyssTab): Promise<void> {
if (abyssData.value && abyssData.value[type] !== null) return;
showLoading.update("正在获取深渊数据...", `正在获取 ${AbyssTabEnum[type]} 数据`);
@@ -116,15 +108,19 @@ async function refreshData(type: AbyssTab): Promise<void> {
switch (type) {
case "use":
abyssData.value.use = <AbyssDataItemType<"use">>data;
triggerRef(abyssData);
break;
case "up":
abyssData.value.up = <AbyssDataItemType<"up">>data;
triggerRef(abyssData);
break;
case "team":
abyssData.value.team = <AbyssDataItemType<"team">>data;
triggerRef(abyssData);
break;
case "hold":
abyssData.value.hold = <AbyssDataItemType<"hold">>data;
triggerRef(abyssData);
break;
}
showLoading.end();

View File

@@ -17,27 +17,27 @@
</div>
</div>
<div class="wc-detail">
<TwcCharacter :item="curItem" @error="toOuter(curItem)" />
<TwcCharacter :item="curItem" />
</div>
</div>
<TwoSelectC v-model="showSelect" @select-c="handleSelect" v-model:reset="resetSelect" />
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showSnackbar from "@comp/func/snackbar.js";
import TwcCharacter from "@comp/pageWiki/twc-character.vue";
import TwcListItem from "@comp/pageWiki/twc-list-item.vue";
import TwoSelectC, { type SelectedCValue } from "@comp/pageWiki/two-select-c.vue";
import { onBeforeMount, ref, shallowRef, watch } from "vue";
import { useRoute } from "vue-router";
import showDialog from "../../components/func/dialog.js";
import showSnackbar from "../../components/func/snackbar.js";
import TwcCharacter from "../../components/pageWiki/twc-character.vue";
import TwcListItem from "../../components/pageWiki/twc-list-item.vue";
import TwoSelectC, { SelectedCValue } from "../../components/pageWiki/two-select-c.vue";
import { AppCharacterData } from "../../data/index.js";
import { createObc } from "../../utils/TGWindow.js";
import { AppCharacterData } from "@/data/index.js";
import { createObc } from "@/utils/TGWindow.js";
const id = useRoute().params.id.toString() ?? "0";
const showSelect = ref<boolean>(false);
const resetSelect = ref<boolean>(false);
const cardsInfo = shallowRef<TGApp.App.Character.WikiBriefInfo[]>(AppCharacterData);
const cardsInfo = shallowRef<Array<TGApp.App.Character.WikiBriefInfo>>(AppCharacterData);
const curItem = shallowRef<TGApp.App.Character.WikiBriefInfo>({
id: 0,
contentId: 0,
@@ -69,7 +69,7 @@ watch(resetSelect, (val) => {
if (val) cardsInfo.value = AppCharacterData;
});
function handleSelect(val: SelectedCValue) {
function handleSelect(val: SelectedCValue): void {
showSelect.value = false;
const filterC = AppCharacterData.filter((item) => {
if (!val.star.includes(item.star)) return false;

View File

@@ -54,78 +54,77 @@
<div class="twm-item-id">{{ item.id }}</div>
</div>
</div>
<Suspense>
<TwoMaterial v-model="visible" :data="curMaterial" v-if="curMaterial">
<template #left>
<div class="card-arrow left" @click="switchMaterial(false)">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
<template #right>
<div class="card-arrow" @click="switchMaterial(true)">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
</TwoMaterial>
</Suspense>
<TwoMaterial v-model="visible" :data="curMaterial" v-if="curMaterial">
<template #left>
<div class="card-arrow" @click="switchMaterial(false)">
<img src="@/assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
<template #right>
<div class="card-arrow" @click="switchMaterial(true)">
<img src="@/assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
</TwoMaterial>
</template>
<script lang="ts" setup>
import showSnackbar from "@comp/func/snackbar.js";
import TwoMaterial from "@comp/pageWiki/two-material.vue";
import { onMounted, ref, shallowRef, watch } from "vue";
import showSnackbar from "../../components/func/snackbar.js";
import TwoMaterial from "../../components/pageWiki/two-material.vue";
import { WikiMaterialData } from "../../data/index.js";
const curMaterial = shallowRef<TGApp.App.Material.WikiItem | undefined>();
const sortMaterialsData = shallowRef<Array<TGApp.App.Material.WikiItem>>([]);
const curIndex = ref<number>(0);
const total = ref<number>(0);
const visible = ref<boolean>(false);
import { WikiMaterialData } from "@/data/index.js";
type MaterialType = { type: string; number: number };
const curIndex = ref<number>(0);
const total = ref<number>(0);
const visible = ref<boolean>(false);
const search = ref<string>();
const selectType = ref<string | null>(null);
const materialTypes = ref<MaterialType[]>([]);
const materialTypes = shallowRef<Array<MaterialType>>([]);
const curMaterial = shallowRef<TGApp.App.Material.WikiItem | undefined>();
const sortMaterialsData = shallowRef<Array<TGApp.App.Material.WikiItem>>([]);
onMounted(() => {
const tmpData: Array<MaterialType> = [];
for (const item of WikiMaterialData) {
const typeFindIndex = materialTypes.value.findIndex((itemT) => itemT.type === item.type);
const typeFindIndex = tmpData.findIndex((itemT) => itemT.type === item.type);
if (typeFindIndex === -1) {
const itemN: MaterialType = { type: item.type, number: 1 };
materialTypes.value.push(itemN);
tmpData.push(itemN);
continue;
}
materialTypes.value[typeFindIndex].number++;
tmpData[typeFindIndex].number++;
}
materialTypes.value = tmpData;
sortData(WikiMaterialData);
showSnackbar.success(`成功获取${sortMaterialsData.value.length}条数据`);
});
function getSelectMaterials(): TGApp.App.Material.WikiItem[] {
if (selectType.value === null) return WikiMaterialData;
else return WikiMaterialData.filter((item) => item.type === selectType.value);
}
watch(
() => selectType.value,
() => sortData(getSelectMaterials()),
);
function sortData(data: TGApp.App.Material.WikiItem[]) {
function getSelectMaterials(): Array<TGApp.App.Material.WikiItem> {
if (selectType.value === null) return WikiMaterialData;
else return WikiMaterialData.filter((item) => item.type === selectType.value);
}
function sortData(data: Array<TGApp.App.Material.WikiItem>): void {
sortMaterialsData.value = data;
curIndex.value = 0;
total.value = sortMaterialsData.value.length;
curMaterial.value = sortMaterialsData.value[curIndex.value];
}
function toMaterial(item: TGApp.App.Material.WikiItem) {
function toMaterial(item: TGApp.App.Material.WikiItem): void {
curMaterial.value = item;
curIndex.value = sortMaterialsData.value.findIndex((i) => i.id === item.id);
visible.value = true;
}
function switchMaterial(isNext: boolean) {
function switchMaterial(isNext: boolean): void {
if (isNext) {
if (curIndex.value === total.value - 1) return;
curIndex.value++;
@@ -136,7 +135,7 @@ function switchMaterial(isNext: boolean) {
curMaterial.value = sortMaterialsData.value[curIndex.value];
}
function searchMaterial() {
function searchMaterial(): void {
let selectData = getSelectMaterials();
if (search.value === undefined || search.value === "") {
if (sortMaterialsData.value.length === selectData.length) {
@@ -253,18 +252,18 @@ function searchMaterial() {
align-items: center;
justify-content: center;
cursor: pointer;
img {
width: 30px;
aspect-ratio: 1;
}
&:first-child {
transform: rotate(180deg);
}
}
.dark .card-arrow {
filter: invert(11%) sepia(73%) saturate(11%) hue-rotate(139deg) brightness(97%) contrast(81%);
}
.card-arrow img {
width: 100%;
height: 100%;
}
.card-arrow.left img {
transform: rotate(180deg);
}
</style>

View File

@@ -13,7 +13,6 @@
<v-virtual-scroll :items="sortNameCardsData" :item-height="80">
<template #default="{ item }">
<TopNameCard :data="item" @selected="showNameCard(item)" />
<div style="height: 10px" />
</template>
</v-virtual-scroll>
</div>
@@ -21,30 +20,30 @@
<ToNameCard v-model="visible" :data="curNameCard">
<template #left>
<div class="card-arrow left" @click="switchCard(false)">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
<img src="@/assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
<template #right>
<div class="card-arrow" @click="switchCard(true)">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
<img src="@/assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
</ToNameCard>
</template>
<script lang="ts" setup>
import ToNameCard from "@comp/app/to-nameCard.vue";
import TopNameCard from "@comp/app/top-nameCard.vue";
import showSnackbar from "@comp/func/snackbar.js";
import { onMounted, ref, shallowRef } from "vue";
import ToNameCard from "../../components/app/to-namecard.vue";
import TopNameCard from "../../components/app/top-namecard.vue";
import showSnackbar from "../../components/func/snackbar.js";
import { AppNameCardsData } from "../../data/index.js";
import { AppNameCardsData } from "@/data/index.js";
const curNameCard = shallowRef<TGApp.App.NameCard.Item>();
const sortNameCardsData = shallowRef<TGApp.App.NameCard.Item[]>([]);
const curIndex = ref<number>(0);
const total = ref<number>(0);
const visible = ref<boolean>(false);
const search = ref<string>();
const curNameCard = shallowRef<TGApp.App.NameCard.Item>();
const sortNameCardsData = shallowRef<Array<TGApp.App.NameCard.Item>>([]);
onMounted(() => sortData(AppNameCardsData));

View File

@@ -13,7 +13,7 @@
v-model:cur-item="curItem"
:key="index"
:data="item"
@click="curItem = item"
@click="switchW(item)"
mode="weapon"
/>
</div>
@@ -24,18 +24,17 @@
</div>
<TwoSelectW v-model="showSelect" @select-w="handleSelectW" v-model:reset="resetSelect" />
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showSnackbar from "@comp/func/snackbar.js";
import TwcListItem from "@comp/pageWiki/twc-list-item.vue";
import TwcWeapon from "@comp/pageWiki/twc-weapon.vue";
import TwoSelectW, { type SelectedWValue } from "@comp/pageWiki/two-select-w.vue";
import { onBeforeMount, ref, shallowRef } from "vue";
import { useRoute } from "vue-router";
import showDialog from "../../components/func/dialog.js";
import showSnackbar from "../../components/func/snackbar.js";
import TwcListItem from "../../components/pageWiki/twc-list-item.vue";
import TwcWeapon from "../../components/pageWiki/twc-weapon.vue";
import TwoSelectW, { SelectedWValue } from "../../components/pageWiki/two-select-w.vue";
import { AppWeaponData } from "../../data/index.js";
import { createObc } from "../../utils/TGWindow.js";
import { AppWeaponData } from "@/data/index.js";
import { createObc } from "@/utils/TGWindow.js";
const id = useRoute().params.id.toString() ?? "0";
const showSelect = ref<boolean>(false);
@@ -65,6 +64,10 @@ onBeforeMount(() => {
curItem.value = cardsInfo.value[0];
});
function switchW(item: TGApp.App.Weapon.WikiBriefInfo): void {
curItem.value = item;
}
function handleSelectW(val: SelectedWValue) {
showSelect.value = true;
const reg = /\/icon\/weapon\/(.+?)\.webp/;
@@ -96,7 +99,7 @@ async function toOuter(item?: TGApp.App.Weapon.WikiBriefInfo): Promise<void> {
await createObc(item.contentId, item.name);
}
</script>
<style scoped>
<style lang="css" scoped>
.ww-box {
position: relative;
display: flex;

View File

@@ -37,9 +37,8 @@
v-model:cur="selectedSeries"
:series="item"
:uid="uidCur"
@click="selectSeries(item)"
@click="selectedSeries = item"
/>
<div style="height: 10px" />
</template>
</v-virtual-scroll>
<TuaAchiList
@@ -53,54 +52,45 @@
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TuaAchiList from "@comp/userAchi/tua-achi-list.vue";
import TuaSeries from "@comp/userAchi/tua-series.vue";
import TSUserAchi from "@Sqlite/modules/userAchi.js";
import { path } from "@tauri-apps/api";
import { listen, UnlistenFn } from "@tauri-apps/api/event";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { open, save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import TuaAchiList from "../../components/userAchi/tua-achi-list.vue";
import TuaSeries from "../../components/userAchi/tua-series.vue";
import { AppAchievementSeriesData } from "../../data/index.js";
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
import TGLogger from "../../utils/TGLogger.js";
import { AppAchievementSeriesData } from "@/data/index.js";
import TGLogger from "@/utils/TGLogger.js";
import {
getUiafHeader,
readUiafData,
verifyUiafData,
verifyUiafDataClipboard,
} from "../../utils/UIAF.js";
} from "@/utils/UIAF.js";
const seriesList = AppAchievementSeriesData.sort((a, b) => a.order - b.order).map((s) => s.id);
const route = useRoute();
const router = useRouter();
let achiListener: UnlistenFn | null = null;
const search = ref<string>("");
const isSearch = ref<boolean>(false);
const hideFin = ref<boolean>(false);
const uidList = ref<number[]>([]);
const uidCur = ref<number>(0);
const overview = ref<TGApp.Sqlite.Achievement.Overview>({ fin: 0, total: 1 });
const seriesList = AppAchievementSeriesData.sort((a, b) => a.order - b.order).map((s) => s.id);
const selectedSeries = ref<number>(-1);
const overview = shallowRef<TGApp.Sqlite.Achievement.Overview>({ fin: 0, total: 1 });
const title = computed<string>(() => {
const percentage = ((overview.value.fin * 100) / overview.value.total).toFixed(2);
return `${overview.value.fin}/${overview.value.total} ${percentage}%`;
});
const route = useRoute();
const router = useRouter();
let achiListener: UnlistenFn | null = null;
async function switchHideFin() {
const text = hideFin.value ? "显示已完成" : "隐藏已完成";
hideFin.value = !hideFin.value;
showSnackbar.success(`${text}`);
}
onMounted(async () => {
showLoading.start("正在加载成就数据...");
await TGLogger.Info("[Achievements][onMounted] 打开成就页面");
@@ -112,29 +102,21 @@ onMounted(async () => {
if (route.query.app && typeof route.query.app === "string") {
await handleImportOuter(route.query.app);
}
achiListener = await listen<number>("updateAchi", async () => await refreshOverview());
achiListener = await listen<void>("updateAchi", async () => await refreshOverview());
});
watch(
() => uidCur.value,
async () => await refreshOverview(),
);
watch(() => uidCur.value, refreshOverview);
onUnmounted(async () => {
if (achiListener !== null) {
achiListener();
achiListener = null;
}
});
function switchHideFin(): void {
const text = hideFin.value ? "显示已完成" : "隐藏已完成";
hideFin.value = !hideFin.value;
showSnackbar.success(`${text}`);
}
async function refreshOverview(): Promise<void> {
overview.value = await TSUserAchi.getOverview(uidCur.value);
}
function selectSeries(series: number): void {
selectedSeries.value = series;
}
async function importJson(): Promise<void> {
await TGLogger.Info("[Achievements][importJson] 导入 UIAF 数据");
const selectedFile = await open({
@@ -266,6 +248,13 @@ async function deleteUid(): Promise<void> {
if (uidList.value.length === 0) uidList.value = [0];
uidCur.value = uidList.value[0];
}
onUnmounted(async () => {
if (achiListener !== null) {
achiListener();
achiListener = null;
}
});
</script>
<style lang="css" scoped>
.achi-search {

View File

@@ -10,7 +10,7 @@
<v-select
class="anno-select"
:items="annoServerList"
v-model="curRegion"
v-model="server"
item-title="text"
item-value="value"
label="服务器"
@@ -20,7 +20,7 @@
<v-select
class="anno-select"
:items="annoLangList"
v-model="curLang"
v-model="lang"
item-title="text"
item-value="value"
label="语言"
@@ -30,10 +30,7 @@
</div>
</template>
<template #append>
<v-btn class="anno-switch-btn" @click="switchNews">
<template #prepend>
<v-icon>mdi-bullhorn</v-icon>
</template>
<v-btn class="anno-switch-btn" @click="switchNews" prepend-icon="mdi-bullhorn">
切换米游社咨讯
</v-btn>
</template>
@@ -45,8 +42,8 @@
v-for="item in annoCards[value]"
:key="item.id"
:model-value="item"
:region="curRegion"
:lang="curLang"
:region="server"
:lang="lang"
/>
</div>
</v-window-item>
@@ -54,21 +51,24 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted, ref, watch } from "vue";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TaCard from "@comp/pageAnno/ta-card.vue";
import { storeToRefs } from "pinia";
import { onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import TaCard from "../../components/pageAnno/ta-card.vue";
import { useAppStore } from "../../store/modules/app.js";
import TGLogger from "../../utils/TGLogger.js";
import Hk4eApi, { AnnoLang, AnnoServer } from "../../web/request/hk4eReq.js";
import { getAnnoCard } from "../../web/utils/getAnnoCard.js";
import { decodeRegExp } from "../../web/utils/tools.js";
import { useAppStore } from "@/store/modules/app.js";
import TGLogger from "@/utils/TGLogger.js";
import Hk4eApi, { type AnnoLang, AnnoServer } from "@/web/request/hk4eReq.js";
import { getAnnoCard } from "@/web/utils/getAnnoCard.js";
import { decodeRegExp } from "@/web/utils/tools.js";
type AnnoSelect = { text: string; value: string };
type AnnoKey = keyof typeof AnnoType;
type AnnoCard = { [key in AnnoKey]: TGApp.App.Announcement.ListCard[] };
const annoServerList: AnnoSelect[] = [
const annoServerList: Array<AnnoSelect> = [
{ text: "国服-官方服", value: AnnoServer.CN_ISLAND },
{ text: "国服-渠道服", value: AnnoServer.CN_TREE },
{ text: "国际服-亚服", value: AnnoServer.OS_ASIA },
@@ -76,44 +76,28 @@ const annoServerList: AnnoSelect[] = [
{ text: "国际服-美服", value: AnnoServer.OS_USA },
{ text: "国际服-港澳台服", value: AnnoServer.OS_CHT },
];
const annoLangList: AnnoSelect[] = [
const annoLangList: Array<AnnoSelect> = [
{ text: "简体中文", value: "zh-cn" },
{ text: "繁体中文", value: "zh-tw" },
{ text: "English", value: "en" },
{ text: "日本語", value: "ja" },
];
// 类型定义
enum AnnoType {
activity = "活动公告",
game = "游戏公告",
}
type AnnoKey = keyof typeof AnnoType;
type AnnoCard = {
[key in AnnoKey]: TGApp.App.Announcement.ListCard[];
};
const appStore = useAppStore();
// 路由
const { server, lang } = storeToRefs(useAppStore());
const router = useRouter();
const curRegion = ref<AnnoServer>(appStore.server);
const curLang = ref<AnnoLang>(appStore.lang);
// 数据
const tabValues: Readonly<Array<AnnoKey>> = ["activity", "game"];
const tab = ref<AnnoKey>("activity");
const tabValues = ref<Array<AnnoKey>>(["activity", "game"]);
const annoCards = ref<AnnoCard>({
activity: [],
game: [],
});
const annoCards = shallowRef<AnnoCard>({ activity: [], game: [] });
watch(
() => curRegion.value,
() => server.value,
async () => {
appStore.server = curRegion.value;
const name = getRegionName(curRegion.value);
const name = getRegionName(server.value);
await TGLogger.Info(`[Announcements][watch][curRegionName] 切换服务器:${name}`);
await loadData();
showSnackbar.success(`服务器切换为:${name}`);
@@ -121,10 +105,9 @@ watch(
);
watch(
() => curLang.value,
() => lang.value,
async () => {
appStore.lang = curLang.value;
const name = getLangName(curLang.value);
const name = getLangName(lang.value);
await TGLogger.Info(`[Announcements][watch][curLangName] 切换语言:${name}`);
await loadData();
showSnackbar.success(`语言切换为:${name}`);
@@ -133,22 +116,20 @@ watch(
onMounted(async () => {
await TGLogger.Info("[Announcements][onMounted] 打开公告页面");
curRegion.value = appStore.server;
curLang.value = appStore.lang;
await loadData();
});
async function loadData(): Promise<void> {
showLoading.start(
"正在获取公告数据",
`服务器:${getRegionName(curRegion.value)},语言:${getLangName(curLang.value)}`,
`服务器:${getRegionName(server.value)},语言:${getLangName(lang.value)}`,
);
const annoData = await Hk4eApi.anno.list(curRegion.value, curLang.value);
const annoData = await Hk4eApi.anno.list(server.value, lang.value);
const listCards = getAnnoCard(annoData);
await Promise.all(
listCards.map(async (item) => {
if (item.typeLabel === AnnoType.game) return;
const detail = await Hk4eApi.anno.content(item.id, curRegion.value, "zh-cn");
const detail = await Hk4eApi.anno.content(item.id, server.value, "zh-cn");
const timeStr = getAnnoTime(detail.content);
if (timeStr !== false) item.timeStr = timeStr;
}),
@@ -157,8 +138,7 @@ async function loadData(): Promise<void> {
activity: listCards.filter((item) => item.typeLabel === AnnoType.activity),
game: listCards.filter((item) => item.typeLabel === AnnoType.game),
};
showLoading.update("正在渲染公告数据");
await nextTick(() => showLoading.end());
showLoading.end();
}
function getRegionName(value: AnnoServer): string {

View File

@@ -35,8 +35,8 @@
</template>
<template #append>
<v-switch
v-model="appStore.devMode"
:label="appStore.devMode ? '开启' : '关闭'"
v-model="devMode"
:label="devMode ? '开启' : '关闭'"
:inset="true"
color="#FAC51E"
@click="submitDevMode"
@@ -51,8 +51,8 @@
</template>
<template #append>
<v-switch
v-model="needResize"
:label="needResize ? '开启' : '关闭'"
v-model="isNeedResize"
:label="isNeedResize ? '开启' : '关闭'"
:inset="true"
color="#FAC51E"
@click="submitResize"
@@ -60,9 +60,7 @@
</template>
</v-list-item>
<v-list-item title="分享设置">
<template #subtitle>
默认保存到剪贴板超过{{ appStore.shareDefaultFile }}MB时保存到文件
</template>
<template #subtitle>默认保存到剪贴板超过{{ shareDefaultFile }}MB时保存到文件</template>
<template #prepend>
<div class="config-icon">
<v-icon>mdi-share-variant</v-icon>
@@ -81,8 +79,7 @@
<v-list-item-title @click="confirmUpdateDevice()">刷新设备信息</v-list-item-title>
<v-list-item-subtitle>
<!-- @ts-expect-error-next-line Deprecated symbol used -->
{{ appStore.deviceInfo.device_name }}({{ appStore.deviceInfo.product }}) -
{{ appStore.deviceInfo.device_fp }}
{{ deviceInfo.device_name }}({{ deviceInfo.product }}) - {{ deviceInfo.device_fp }}
</v-list-item-subtitle>
<template #append>
<v-icon @click="confirmUpdateDevice(true)">mdi-bug</v-icon>
@@ -121,38 +118,40 @@
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TcAppBadge from "@comp/pageConfig/tc-appBadge.vue";
import TcDataDir from "@comp/pageConfig/tc-dataDir.vue";
import TcGameBadge from "@comp/pageConfig/tc-gameBadge.vue";
import TcInfo from "@comp/pageConfig/tc-info.vue";
import TcUserBadge from "@comp/pageConfig/tc-userBadge.vue";
import TGSqlite from "@Sqlite/index.js";
import { core } from "@tauri-apps/api";
import { open } from "@tauri-apps/plugin-dialog";
import { remove } from "@tauri-apps/plugin-fs";
import { platform } from "@tauri-apps/plugin-os";
import { exit } from "@tauri-apps/plugin-process";
import { storeToRefs } from "pinia";
import { onMounted, ref } from "vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import TcAppBadge from "../../components/pageConfig/tc-appBadge.vue";
import TcDataDir from "../../components/pageConfig/tc-dataDir.vue";
import TcGameBadge from "../../components/pageConfig/tc-gameBadge.vue";
import TcInfo from "../../components/pageConfig/tc-info.vue";
import TcUserBadge from "../../components/pageConfig/tc-userBadge.vue";
import TGSqlite from "../../plugins/Sqlite/index.js";
import { useAppStore } from "../../store/modules/app.js";
import { useHomeStore } from "../../store/modules/home.js";
import { backUpUserData, restoreUserData } from "../../utils/dataBS.js";
import { getBuildTime } from "../../utils/TGBuild.js";
import TGLogger from "../../utils/TGLogger.js";
import { bytesToSize, getCacheDir, getDeviceInfo, getRandomString } from "../../utils/toolFunc.js";
import OtherApi from "../../web/request/otherReq.js";
import { useAppStore } from "@/store/modules/app.js";
import { useHomeStore } from "@/store/modules/home.js";
import { backUpUserData, restoreUserData } from "@/utils/dataBS.js";
import { getBuildTime } from "@/utils/TGBuild.js";
import TGLogger from "@/utils/TGLogger.js";
import { bytesToSize, getCacheDir, getDeviceInfo, getRandomString } from "@/utils/toolFunc.js";
import OtherApi from "@/web/request/otherReq.js";
// Store
const { needResize, devMode, deviceInfo, shareDefaultFile, userDir, buildTime } =
storeToRefs(useAppStore());
const appStore = useAppStore();
const homeStore = useHomeStore();
// @ts-expect-error-next-line
const isDevEnv = ref<boolean>(import.meta.env.MODE === "development");
const showReset = ref<boolean>(false);
const needResize = ref<boolean>(appStore.needResize !== "false");
const isNeedResize = ref<boolean>(needResize.value !== "false");
const cacheSize = ref<number>(0);
onMounted(async () => {
@@ -176,7 +175,7 @@ async function confirmBackup(): Promise<void> {
showSnackbar.cancel("已取消备份");
return;
}
let saveDir = appStore.userDir;
let saveDir = userDir.value;
if (!bcCheck) {
const dir: string | null = await open({
directory: true,
@@ -189,9 +188,7 @@ async function confirmBackup(): Promise<void> {
}
await TGLogger.Info(`[Config][confirmBackup] 选择备份路径 ${dir.toString()}`);
saveDir = dir;
} else {
await TGLogger.Info(`[Config][confirmBackup] 备份到默认路径 ${saveDir}`);
}
} else await TGLogger.Info(`[Config][confirmBackup] 备份到默认路径 ${saveDir}`);
showLoading.start("正在备份数据...");
await backUpUserData(saveDir);
showLoading.end();
@@ -206,7 +203,7 @@ async function confirmRestore(): Promise<void> {
showSnackbar.cancel("已取消恢复");
return;
}
let saveDir = appStore.userDir;
let saveDir = userDir.value;
if (!rsCheck) {
const dir: string | null = await open({
directory: true,
@@ -219,9 +216,7 @@ async function confirmRestore(): Promise<void> {
}
await TGLogger.Info(`[Config][confirmRestore] 选择恢复路径 ${dir.toString()}`);
saveDir = dir;
} else {
await TGLogger.Info(`[Config][confirmRestore] 恢复到默认路径 ${saveDir}`);
}
} else await TGLogger.Info(`[Config][confirmRestore] 恢复到默认路径 ${saveDir}`);
showLoading.start("正在恢复数据...");
await restoreUserData(saveDir);
showLoading.end();
@@ -238,7 +233,7 @@ async function confirmUpdate(title?: string): Promise<void> {
}
showLoading.start("正在更新数据库...");
await TGSqlite.update();
appStore.buildTime = getBuildTime();
buildTime.value = getBuildTime();
showLoading.end();
showSnackbar.success("数据库已更新!");
await TGLogger.Info("[Config][confirmUpdate] 数据库更新完成");
@@ -250,7 +245,7 @@ async function confirmShare(): Promise<void> {
const input = await showDialog.input(
"请输入分享文件大小阈值(MB)",
"阈值:",
appStore.shareDefaultFile.toString(),
shareDefaultFile.value.toString(),
);
if (input === undefined) {
showSnackbar.cancel("已取消修改分享设置");
@@ -264,7 +259,7 @@ async function confirmShare(): Promise<void> {
showSnackbar.error("阈值必须为数字!");
return;
}
if (Number(input) === appStore.shareDefaultFile) {
if (Number(input) === shareDefaultFile.value) {
showSnackbar.cancel("未修改分享设置");
return;
}
@@ -280,7 +275,7 @@ async function confirmShare(): Promise<void> {
showSnackbar.cancel("已取消修改分享设置");
return;
}
appStore.shareDefaultFile = Number(input);
shareDefaultFile.value = Number(input);
showSnackbar.success(`成功修改分享设置!新阈值为${input}MB`);
}
@@ -290,22 +285,20 @@ async function confirmUpdateDevice(force?: boolean): Promise<void> {
await TGLogger.Info("[Config][confirmUpdateDevice][force] 开始强制更新设备信息");
const forceCheck = await showDialog.check(
"确认强制更新设备信息吗?",
`DeviceFp:${appStore.deviceInfo.device_fp}`,
`DeviceFp:${deviceInfo.value.device_fp}`,
);
if (!forceCheck) {
showSnackbar.cancel("已取消强制更新设备信息");
await TGLogger.Info("[Config][confirmUpdateDevice][force] 取消强制更新设备信息");
return;
}
appStore.deviceInfo = await OtherApi.fp();
if (appStore.deviceInfo.device_fp === "0000000000000") {
appStore.deviceInfo.device_fp = getRandomString(13, "hex");
showSnackbar.warn(`设备信息获取失败!已使用随机值${appStore.deviceInfo.device_fp}代替`);
deviceInfo.value = await OtherApi.fp();
if (deviceInfo.value.device_fp === "0000000000000") {
deviceInfo.value.device_fp = getRandomString(13, "hex");
showSnackbar.warn(`设备信息获取失败!已使用随机值${deviceInfo.value.device_fp}代替`);
await TGLogger.Warn("[Config][confirmUpdateDevice][force] 设备信息获取失败!已使用随机值代替");
} else {
showSnackbar.success(`设备信息已更新! DeviceFp: ${appStore.deviceInfo.device_fp}`);
}
await TGSqlite.saveAppData("deviceInfo", JSON.stringify(appStore.deviceInfo));
} else showSnackbar.success(`设备信息已更新! DeviceFp: ${deviceInfo.value.device_fp}`);
await TGSqlite.saveAppData("deviceInfo", JSON.stringify(deviceInfo.value));
await TGLogger.Info("[Config][confirmUpdateDevice][force] 设备信息更新完成");
return;
}
@@ -319,17 +312,15 @@ async function confirmUpdateDevice(force?: boolean): Promise<void> {
return;
}
}
console.log(appStore.deviceInfo);
appStore.deviceInfo = await OtherApi.fp(appStore.deviceInfo);
console.log(appStore.deviceInfo);
if (appStore.deviceInfo.device_fp === "0000000000000") {
appStore.deviceInfo.device_fp = getRandomString(13, "hex");
showSnackbar.warn(`设备信息获取失败!已使用随机值${appStore.deviceInfo.device_fp}代替`);
deviceInfo.value = await OtherApi.fp(deviceInfo.value);
if (deviceInfo.value.device_fp === "0000000000000") {
deviceInfo.value.device_fp = getRandomString(13, "hex");
showSnackbar.warn(`设备信息获取失败!已使用随机值${deviceInfo.value.device_fp}代替`);
await TGLogger.Warn("[Config][confirmUpdateDevice] 设备信息获取失败!已使用随机值代替");
return;
}
showSnackbar.success(`设备信息已更新! DeviceFp: ${appStore.deviceInfo.device_fp}`);
await TGSqlite.saveAppData("deviceInfo", JSON.stringify(appStore.deviceInfo));
showSnackbar.success(`设备信息已更新! DeviceFp: ${deviceInfo.value.device_fp}`);
await TGSqlite.saveAppData("deviceInfo", JSON.stringify(deviceInfo.value));
await TGLogger.Info("[Config][confirmUpdateDevice] 设备信息更新完成");
}
@@ -433,8 +424,8 @@ function submitDevMode(): void {
// 开启窗口回正
function submitResize(): void {
appStore.needResize = (!needResize.value).toString();
if (needResize.value) {
appStore.needResize = (!isNeedResize.value).toString();
if (isNeedResize.value) {
showSnackbar.success("已关闭窗口回正!");
return;
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="home-container">
<div class="home-top">
<div class="home-tools" v-if="appStore.isLogin">
<div class="home-tools" v-if="isLogin">
<v-select
v-model="curGid"
class="home-tool-select"
@@ -32,18 +32,19 @@
</template>
<script lang="ts" setup>
import TGameNav from "@comp/app/t-gameNav.vue";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import PhCompCalendar from "@comp/pageHome/ph-comp-calendar.vue";
import PhCompPool from "@comp/pageHome/ph-comp-pool.vue";
import PhCompPosition from "@comp/pageHome/ph-comp-position.vue";
import { storeToRefs } from "pinia";
import { type Component, computed, onMounted, ref, shallowRef, watch } from "vue";
import TGameNav from "../../components/app/t-gamenav.vue";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import PhCompCalendar from "../../components/pageHome/ph-comp-calendar.vue";
import PhCompPool from "../../components/pageHome/ph-comp-pool.vue";
import PhCompPosition from "../../components/pageHome/ph-comp-position.vue";
import { useAppStore } from "../../store/modules/app.js";
import { ShowItemEnum, useHomeStore } from "../../store/modules/home.js";
import TGLogger from "../../utils/TGLogger.js";
import TGConstant from "../../web/constant/TGConstant.js";
import { useAppStore } from "@/store/modules/app.js";
import { ShowItemEnum, useHomeStore } from "@/store/modules/home.js";
import TGLogger from "@/utils/TGLogger.js";
import TGConstant from "@/web/constant/TGConstant.js";
type SFComp = Component & {
__file?: string;
@@ -52,7 +53,7 @@ type SFComp = Component & {
__scopeId?: string;
};
const appStore = useAppStore();
const { devMode, isLogin } = storeToRefs(useAppStore());
const homeStore = useHomeStore();
const showItemsAll: Array<ShowItemEnum> = [
@@ -60,12 +61,12 @@ const showItemsAll: Array<ShowItemEnum> = [
ShowItemEnum.pool,
ShowItemEnum.position,
];
const showItems = computed<ShowItemEnum[]>({
const showItems = computed<Array<ShowItemEnum>>({
get: () => homeStore.getShowItems(),
set: (v: ShowItemEnum[]) => homeStore.setShowItems(v),
set: (v: Array<ShowItemEnum>) => homeStore.setShowItems(v),
});
const loadItems = shallowRef<ShowItemEnum[]>([]);
const components = shallowRef<SFComp[]>([]);
const loadItems = shallowRef<Array<ShowItemEnum>>([]);
const components = shallowRef<Array<SFComp>>([]);
const gameSelectList = TGConstant.BBS.CHANNELS;
const curGid = ref<string>(gameSelectList[0].gid);
@@ -74,7 +75,7 @@ onMounted(async () => {
showLoading.start("正在加载首页...");
// @ts-expect-error-next-line The import.meta meta-property is not allowed in files which will build into CommonJS output.
const isProdEnv = import.meta.env.MODE === "production";
if (isProdEnv && appStore.devMode) appStore.devMode = false;
if (isProdEnv && devMode.value) devMode.value = false;
await loadComp();
});
@@ -88,7 +89,7 @@ watch(
async function loadComp(): Promise<void> {
showLoading.start("正在加载首页...");
const temp: SFComp[] = [];
const temp: Array<SFComp> = [];
for (const item of showItems.value) {
switch (item) {
case "限时祈愿":

View File

@@ -94,47 +94,40 @@
<ToCollectPost @submit="load" :post="selectedPost" v-model="showOverlay" />
</template>
<script lang="ts" setup>
import TPostCard from "@comp/app/t-postcard.vue";
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import ToCollectPost from "@comp/pageCollect/to-collectPost.vue";
import TSUserCollection from "@Sqlite/modules/userCollect.js";
import { event } from "@tauri-apps/api";
import { UnlistenFn } from "@tauri-apps/api/event";
import type { UnlistenFn } from "@tauri-apps/api/event";
import { storeToRefs } from "pinia";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
import TPostCard from "../../components/app/t-postcard.vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import ToCollectPost from "../../components/pageCollect/to-collectPost.vue";
import TSUserCollection from "../../plugins/Sqlite/modules/userCollect.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import BBSApi from "../../web/request/bbsReq.js";
const userStore = storeToRefs(useUserStore());
const collections = ref<TGApp.Sqlite.UserCollection.UFCollection[]>([]);
const selected = ref<TGApp.Sqlite.UserCollection.UFPost[]>([]);
const curSelect = ref<string>("未分类");
const page = ref(1);
const length = computed(() => Math.ceil(selected.value.length / 12));
const view = computed(() => {
if (length.value === 1) return 0;
return length.value > 5 ? 5 : length.value;
});
const curPosts = computed<TGApp.Plugins.Mys.Post.FullData[]>(() => {
return selected.value
.slice((page.value - 1) * 12, page.value * 12)
.map((i) => JSON.parse(i.content));
});
const selectedMode = ref<boolean>(false);
const selectedPost = ref<Array<string>>([]);
const showOverlay = ref(false);
const sortId = ref<boolean>(false);
import { useUserStore } from "@/store/modules/user.js";
import TGLogger from "@/utils/TGLogger.js";
import BBSApi from "@/web/request/bbsReq.js";
const { cookie, briefInfo } = storeToRefs(useUserStore());
let collectListener: UnlistenFn | null = null;
const curSelect = ref<string>("未分类");
const page = ref<number>(1);
const selectedMode = ref<boolean>(false);
const showOverlay = ref<boolean>(false);
const sortId = ref<boolean>(false);
const selectedPost = shallowRef<Array<string>>([]);
const collections = shallowRef<Array<TGApp.Sqlite.UserCollection.UFCollection>>([]);
const selected = shallowRef<Array<TGApp.Sqlite.UserCollection.UFPost>>([]);
const length = computed<number>(() => Math.ceil(selected.value.length / 12));
const view = computed<number>(() => (length.value === 1 ? 1 : length.value > 5 ? 5 : length.value));
const curPosts = computed<Array<TGApp.Plugins.Mys.Post.FullData>>(() =>
selected.value.slice((page.value - 1) * 12, page.value * 12).map((i) => JSON.parse(i.content)),
);
onMounted(async () => {
collectListener = await event.listen("refreshCollect", async () => await load());
collectListener = await event.listen<void>("refreshCollect", load);
await load();
});
onUnmounted(() => {
@@ -144,27 +137,22 @@ onUnmounted(() => {
}
});
function handleSelected(v: string) {
if (selectedPost.value.includes(v)) {
selectedPost.value = selectedPost.value.filter((i) => i !== v);
} else {
selectedPost.value.push(v);
function handleSelected(v: string): void {
if (!selectedPost.value.includes(v)) {
selectedPost.value = [...selectedPost.value, v];
return;
}
selectedPost.value = selectedPost.value.filter((i) => i !== v);
}
function sortPost(value: boolean) {
function sortPost(value: boolean): void {
let ori = sortId.value;
sortId.value = value;
selected.value = selected.value.sort((a, b) => {
if (sortId.value) {
return Number(b.id) - Number(a.id);
} else {
return Number(b.updated) - Number(a.updated);
}
});
if (ori !== sortId.value) {
showSnackbar.success(`${sortId.value ? "按帖子ID排序" : "按更新时间排序"}`);
}
selected.value = selected.value.sort((a, b) =>
sortId.value ? Number(b.id) - Number(a.id) : Number(b.updated) - Number(a.updated),
);
if (ori === sortId.value) return;
showSnackbar.success(`${sortId.value ? "按帖子ID排序" : "按更新时间排序"}`);
}
async function load(): Promise<void> {
@@ -187,7 +175,7 @@ async function load(): Promise<void> {
showLoading.end();
}
function toSelect() {
function toSelect(): void {
if (selectedMode.value) {
selectedMode.value = false;
if (selectedPost.value.length === 0) return;
@@ -332,9 +320,7 @@ async function freshPost(select: string | null): Promise<void> {
if (select === "未分类") {
curSelect.value = "未分类";
selected.value = await TSUserCollection.getUnCollectPostList();
} else {
selected.value = await TSUserCollection.getCollectPostList(select);
}
} else selected.value = await TSUserCollection.getCollectPostList(select);
page.value = 1;
showLoading.end();
showSnackbar.success(`切换合集 ${select},共 ${selected.value.length} 条帖子`);
@@ -359,33 +345,26 @@ async function freshOther(): Promise<void> {
}
async function freshUser(uid?: string): Promise<void> {
if (!userStore.cookie.value) {
if (!cookie.value) {
showSnackbar.warn("请先登录");
return;
}
const uidReal = uid || userStore.briefInfo.value.uid;
const uidReal = uid || briefInfo.value.uid;
showLoading.start("获取用户收藏...", `UID: ${uidReal}`);
let res = await BBSApi.lovePost(userStore.cookie.value, uidReal);
let res = await BBSApi.lovePost(cookie.value, uidReal);
while (true) {
if ("retcode" in res) {
showLoading.end();
if (res.retcode === 1001) {
showSnackbar.warn("用户收藏已设为私密,无法获取");
} else {
showSnackbar.error(`[${res.retcode}] ${res.message}`);
}
if (res.retcode === 1001) showSnackbar.warn("用户收藏已设为私密,无法获取");
else showSnackbar.error(`[${res.retcode}] ${res.message}`);
break;
}
let posts = res.list;
showLoading.update("获取用户收藏...", `合并收藏帖子 [offset]${res.next_offset}...`);
await mergePosts(posts, uid || userStore.briefInfo.value.uid);
await mergePosts(posts, uid || briefInfo.value.uid);
if (res.is_last) break;
showLoading.update("获取用户收藏...", `[offset]${res.next_offset} [is_last]${res.is_last}`);
res = await BBSApi.lovePost(
userStore.cookie.value,
uid || userStore.briefInfo.value.uid,
res.next_offset,
);
res = await BBSApi.lovePost(cookie.value, uid || briefInfo.value.uid, res.next_offset);
}
showLoading.end();
showSnackbar.success("获取用户收藏成功");
@@ -394,16 +373,14 @@ async function freshUser(uid?: string): Promise<void> {
// 合并收藏帖子
async function mergePosts(
posts: TGApp.Plugins.Mys.Post.FullData[],
posts: Array<TGApp.Plugins.Mys.Post.FullData>,
collect: string,
): Promise<void> {
const title = `用户收藏-${collect}`;
for (const post of posts) {
showLoading.start("获取用户收藏...", `[POST]${post.post.subject} [collection]${title}`);
const res = await TSUserCollection.addCollect(post.post.post_id, post, title, true);
if (!res) {
await TGLogger.Error(`[PostCollect] mergePosts [${post.post.post_id}]`);
}
if (!res) await TGLogger.Error(`[PostCollect] mergePosts [${post.post.post_id}]`);
}
}
</script>

View File

@@ -67,71 +67,64 @@
<VpOverlaySearch :gid="curGid.toString()" v-model="showSearch" :keyword="search" />
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from "vue";
import TGameNav from "@comp/app/t-gameNav.vue";
import TPostCard from "@comp/app/t-postcard.vue";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import VpOverlaySearch from "@comp/viewPost/vp-overlay-search.vue";
import Mys from "@Mys/index.js";
import { onMounted, ref, shallowRef, watch } from "vue";
import { useRoute } from "vue-router";
import TGameNav from "../../components/app/t-gamenav.vue";
import TPostCard from "../../components/app/t-postcard.vue";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import VpOverlaySearch from "../../components/viewPost/vp-overlay-search.vue";
import Mys from "../../plugins/Mys/index.js";
import TGLogger from "../../utils/TGLogger.js";
import { createPost } from "../../utils/TGWindow.js";
import { getGameName } from "../../web/utils/tools.js";
import TGLogger from "@/utils/TGLogger.js";
import { createPost } from "@/utils/TGWindow.js";
import { getGameName } from "@/web/utils/tools.js";
type SortSelect = {
text: string;
value: number;
};
type SortSelectGame = {
gid: number;
forum: SortSelect[];
text: string;
};
type SortSelect = { text: string; value: number };
type SortSelectGame = { gid: number; forum: Array<SortSelect>; text: string };
const sortOrderList: SortSelect[] = [
const sortOrderList: Array<SortSelect> = [
{ text: "最新回复", value: 1 },
{ text: "最新发布", value: 2 },
{ text: "热门", value: 3 },
];
const forumYsList: SortSelect[] = [
const forumYsList: Array<SortSelect> = [
{ text: "酒馆", value: 26 },
{ text: "攻略", value: 43 },
{ text: "同人图", value: 29 },
{ text: "COS", value: 49 },
{ text: "硬核", value: 50 },
];
const forumSrList: SortSelect[] = [
const forumSrList: Array<SortSelect> = [
{ text: "候车室", value: 52 },
{ text: "攻略", value: 61 },
{ text: "同人图", value: 56 },
{ text: "COS", value: 62 },
];
const forumBh3List: SortSelect[] = [
const forumBh3List: Array<SortSelect> = [
{ text: "甲板", value: 1 },
{ text: "攻略", value: 14 },
{ text: "同人图", value: 4 },
{ text: "同人文", value: 41 },
];
const forumBh2List: SortSelect[] = [
const forumBh2List: Array<SortSelect> = [
{ text: "学园", value: 30 },
{ text: "攻略", value: 51 },
{ text: "同人图", value: 40 },
];
const forumWdList: SortSelect[] = [
const forumWdList: Array<SortSelect> = [
{ text: "律所", value: 37 },
{ text: "攻略", value: 60 },
{ text: "同人文", value: 42 },
{ text: "同人图", value: 38 },
];
const forumZzzList: SortSelect[] = [
const forumZzzList: Array<SortSelect> = [
{ text: "咖啡馆", value: 57 },
{ text: "攻略", value: 64 },
{ text: "同人图", value: 59 },
{ text: "COS", value: 65 },
];
const forumDbyList: SortSelect[] = [
const forumDbyList: Array<SortSelect> = [
{ text: "校园", value: 54 },
{ text: "ACG", value: 35 },
{ text: "生活", value: 34 },
@@ -141,7 +134,7 @@ const forumDbyList: SortSelect[] = [
{ text: "科技", value: 55 },
{ text: "公告", value: 36 },
];
const sortGameList: SortSelectGame[] = [
const sortGameList: Readonly<Array<SortSelectGame>> = [
{ gid: 2, forum: forumYsList, text: "原神" },
{ gid: 6, forum: forumSrList, text: "崩坏:星穹铁道" },
{ gid: 8, forum: forumZzzList, text: "绝区零" },
@@ -150,47 +143,17 @@ const sortGameList: SortSelectGame[] = [
{ gid: 4, forum: forumWdList, text: "未定事件簿" },
{ gid: 5, forum: forumDbyList, text: "大别野" },
];
// 路由
const gid = useRoute().params.gid;
const forum = useRoute().params.forum;
function getGameForums(gid: number): SortSelect[] {
const game = sortGameList.find((item) => item.gid === gid);
if (game) return game.forum;
return [];
}
function getGameLabel(gid: number): string {
const game = sortGameList.find((item) => item.gid === gid);
if (game) return game.text;
return "";
}
function getForumLabel(gid: number, forum: number): string {
const forums = getGameForums(gid);
const forumItem = forums.find((item) => item.value === forum);
return forumItem ? forumItem.text : "";
}
function getSortLabel(value: number): string {
const order = sortOrderList.find((item) => item.value === value);
return order ? order.text : "";
}
// 渲染参数
const { gid, forum } = useRoute().params;
const curGid = ref<number>(2);
const curSortType = ref<number>(1);
const curForum = ref<number>(26);
const curForumLabel = ref<string>("");
// 渲染数据
const posts = ref<TGApp.Plugins.Mys.Post.FullData[]>([]);
const lastId = ref<string>("");
const isLast = ref<boolean>(false);
const search = ref<string>("");
const showSearch = ref<boolean>(false);
const firstLoad = ref<boolean>(false);
const posts = shallowRef<Array<TGApp.Plugins.Mys.Post.FullData>>([]);
onMounted(async () => {
if (gid && typeof gid === "string") curGid.value = Number(gid);
@@ -204,7 +167,6 @@ onMounted(async () => {
await freshPostData();
curForumLabel.value = forumLabel;
});
watch(
() => curGid.value,
() => {
@@ -236,6 +198,29 @@ watch(
},
);
function getGameForums(gid: number): SortSelect[] {
const game = sortGameList.find((item) => item.gid === gid);
if (game) return game.forum;
return [];
}
function getGameLabel(gid: number): string {
const game = sortGameList.find((item) => item.gid === gid);
if (game) return game.text;
return "";
}
function getForumLabel(gid: number, forum: number): string {
const forums = getGameForums(gid);
const forumItem = forums.find((item) => item.value === forum);
return forumItem ? forumItem.text : "";
}
function getSortLabel(value: number): string {
const order = sortOrderList.find((item) => item.value === value);
return order ? order.text : "";
}
async function freshPostData(): Promise<void> {
const gameLabel = getGameLabel(curGid.value);
const forumLabel = getForumLabel(curGid.value, curForum.value);
@@ -266,7 +251,6 @@ async function loadMore(): Promise<void> {
showLoading.end();
}
// 查询帖子
function searchPost(): void {
if (search.value === "") {
showSnackbar.warn("请输入搜索内容");

View File

@@ -25,16 +25,14 @@
/>
</template>
<template #append>
<v-btn class="post-news-btn" @click="firstLoad(tab, true)">
<v-icon>mdi-refresh</v-icon>
</v-btn>
<v-btn class="post-news-btn" @click="showList = true">
<v-icon>mdi-view-list</v-icon>
</v-btn>
<v-btn class="post-news-btn" @click="switchAnno" v-if="gid === '2'">
<template #prepend>
<v-icon>mdi-bullhorn</v-icon>
</template>
<v-btn class="post-news-btn" @click="firstLoad(tab, true)" icon="mdi-refresh" />
<v-btn class="post-news-btn" @click="showList = true" icon="mdi-view-list" />
<v-btn
class="post-news-btn"
@click="switchAnno"
v-if="gid === '2'"
prepend-icon="mdi-bullhorn"
>
切换游戏内公告
</v-btn>
</template>
@@ -56,51 +54,44 @@
<ToChannel v-model="showList" :gid="gid" />
<VpOverlaySearch :gid="gid" v-model="showSearch" :keyword="search" />
</template>
<script lang="ts" setup>
import { computed, nextTick, onMounted, ref } from "vue";
import TPostCard from "@comp/app/t-postcard.vue";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import ToChannel from "@comp/pageNews/to-channel.vue";
import VpOverlaySearch from "@comp/viewPost/vp-overlay-search.vue";
import Mys from "@Mys/index.js";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, shallowRef, triggerRef } from "vue";
import { useRoute, useRouter } from "vue-router";
import TPostCard from "../../components/app/t-postcard.vue";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import ToChannel from "../../components/pageNews/to-channel.vue";
import VpOverlaySearch from "../../components/viewPost/vp-overlay-search.vue";
import Mys from "../../plugins/Mys/index.js";
import { NewsType, NewsTypeEnum, useAppStore } from "../../store/modules/app.js";
import TGLogger from "../../utils/TGLogger.js";
import { createPost } from "../../utils/TGWindow.js";
import { getGameName } from "../../web/utils/tools.js";
import { type NewsType, NewsTypeEnum, useAppStore } from "@/store/modules/app.js";
import TGLogger from "@/utils/TGLogger.js";
import { createPost } from "@/utils/TGWindow.js";
import { getGameName } from "@/web/utils/tools.js";
type PostData = { [key in NewsType]: TGApp.Plugins.Mys.Post.FullData[] };
type RawData = {
[key in NewsType]: { isLast: boolean; name: string; lastId: number };
};
type PostData = { [key in NewsType]: Array<TGApp.Plugins.Mys.Post.FullData> };
type RawData = { [key in NewsType]: { isLast: boolean; name: string; lastId: number } };
const router = useRouter();
const appStore = useAppStore();
const gid = <string>useRoute().params.gid;
const { recentNewsType } = storeToRefs(useAppStore());
const tabValues: Readonly<Array<NewsType>> = ["notice", "activity", "news"];
const { gid } = <{ gid: string }>useRoute().params;
const gameName = getGameName(Number(gid));
const loading = ref<boolean>(false);
const tabValues: Readonly<Array<NewsType>> = ["notice", "activity", "news"];
const showList = ref<boolean>(false);
const showSearch = ref<boolean>(false);
const tab = computed<NewsType>({
get: () => {
if (!(appStore.recentNewsType satisfies NewsType)) return "notice";
return appStore.recentNewsType;
},
set: (v) => (appStore.recentNewsType = v),
});
// 渲染数据
const search = ref<string>("");
const postData = ref<PostData>({ notice: [], activity: [], news: [] });
const rawData = ref<RawData>({
const postData = shallowRef<PostData>({ notice: [], activity: [], news: [] });
const rawData = shallowRef<RawData>({
notice: { isLast: false, name: "公告", lastId: 0 },
activity: { isLast: false, name: "活动", lastId: 0 },
news: { isLast: false, name: "咨讯", lastId: 0 },
});
const tab = computed<NewsType>({
get: () => ((recentNewsType.value satisfies NewsType) ? recentNewsType.value : "notice"),
set: (v) => (recentNewsType.value = v),
});
onMounted(async () => await firstLoad(tab.value));
@@ -116,8 +107,9 @@ async function firstLoad(key: NewsType, refresh: boolean = false): Promise<void>
rawData.value[key].isLast = getData.is_last;
rawData.value[key].lastId = getData.list.length;
postData.value[key] = getData.list;
showLoading.update(`正在渲染${gameName}${rawData.value[key].name}数据...`);
await nextTick(() => showLoading.end());
triggerRef(postData);
triggerRef(rawData);
showLoading.end();
await TGLogger.Info(`[News][${gid}][firstLoad] 获取${rawData.value[key].name}数据成功`);
}
@@ -144,13 +136,15 @@ async function loadMore(key: NewsType): Promise<void> {
rawData.value[key].lastId = rawData.value[key].lastId + getData.list.length;
rawData.value[key].isLast = getData.is_last;
postData.value[key] = postData.value[key].concat(getData.list);
triggerRef(postData);
triggerRef(rawData);
if (rawData.value[key].isLast) {
showLoading.end();
showSnackbar.warn("已经是最后一页了");
loading.value = false;
return;
}
await nextTick(() => showLoading.end());
showLoading.end();
loading.value = false;
}
@@ -168,7 +162,6 @@ async function searchPost(): Promise<void> {
showSearch.value = false;
}
</script>
<style lang="css" scoped>
.news-tab {
margin-bottom: 10px;
@@ -178,6 +171,7 @@ async function searchPost(): Promise<void> {
.post-news-btn {
height: 40px;
border-radius: 3px;
margin-left: 15px;
background: var(--btn-bg-1);
color: var(--btn-text-1);

View File

@@ -42,10 +42,7 @@
@click:append="searchPost"
@keyup.enter="searchPost"
/>
<v-btn :rounded="true" class="post-topic-btn" @click="firstLoad()">
<v-icon>mdi-refresh</v-icon>
<span>刷新</span>
</v-btn>
<v-btn class="post-topic-btn" @click="firstLoad()" prepend-icon="mdi-refresh">刷新</v-btn>
</div>
</v-app-bar>
<div class="post-topic-grid">
@@ -54,39 +51,37 @@
</div>
</div>
<div class="load-more">
<v-btn class="post-topic-btn" :rounded="true" @click="freshPostData()">
<v-btn class="post-topic-btn" @click="freshPostData()">
已加载:{{ posts.length }},加载更多
</v-btn>
</div>
<VpOverlaySearch :gid="curGid.toString()" v-model="showSearch" :keyword="search" />
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, toRaw, watch } from "vue";
import TGameNav from "@comp/app/t-gameNav.vue";
import TPostCard from "@comp/app/t-postcard.vue";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import VpOverlaySearch from "@comp/viewPost/vp-overlay-search.vue";
import Mys from "@Mys/index.js";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { useRoute } from "vue-router";
import TGameNav from "../../components/app/t-gamenav.vue";
import TPostCard from "../../components/app/t-postcard.vue";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import VpOverlaySearch from "../../components/viewPost/vp-overlay-search.vue";
import Mys from "../../plugins/Mys/index.js";
import { createPost } from "../../utils/TGWindow.js";
import { createPost } from "@/utils/TGWindow.js";
const gid = <string>useRoute().params.gid;
const topic = <string>useRoute().params.topic;
type SortSelect = { text: string; value: number };
const { gid, topic } = <{ gid: string; topic: string }>useRoute().params;
const showSearch = ref<boolean>(false);
const curGid = ref<number>(Number(gid));
const curSortType = ref<0 | 1 | 2>(0);
const search = ref<string>("");
const topicInfo = ref<TGApp.Plugins.Mys.Topic.InfoData>();
const posts = ref<TGApp.Plugins.Mys.Post.FullData[]>([]);
const lastPostId = ref<string>();
const isLastPage = ref<boolean>(false);
const curGame = ref<TGApp.Plugins.Mys.Topic.GameInfo>();
type SortSelect = { text: string; value: number };
const sortList = computed<SortSelect[]>(() => {
const topicInfo = shallowRef<TGApp.Plugins.Mys.Topic.InfoData>();
const posts = shallowRef<Array<TGApp.Plugins.Mys.Post.FullData>>([]);
const curGame = shallowRef<TGApp.Plugins.Mys.Topic.GameInfo>();
const sortList = computed<Array<SortSelect>>(() => {
if (!topicInfo.value) return [];
if (!topicInfo.value.good_post_exist) {
return [
@@ -111,11 +106,12 @@ onMounted(async () => {
}
topicInfo.value = info;
if (curGame.value === undefined) {
curGame.value = toRaw(info.game_info_list.find((i) => i.id === curGid.value));
curGame.value = info.game_info_list.find((i) => i.id === curGid.value);
}
if (curGame.value === undefined) curGame.value = info.game_info_list[0];
await firstLoad();
});
watch(
() => curGame.value,
async () => {