Files
TeyvatGuide/src/pages/User/Characters.vue
2024-08-24 15:47:28 +08:00

312 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ToLoading v-model="loading" :title="loadingTitle" :subtitle="loadingSub" />
<v-app-bar>
<template #title>
<span v-if="user"> {{ user.nickname }}({{ user.gameUid }})</span>
<span v-else> 暂无数据 </span>
</template>
<template #append>
<div class="uc-top-btns">
<v-btn @click="refresh()" rounded variant="outlined" v-model:loading="loadData">
<template #prepend>
<v-icon>mdi-refresh</v-icon>
</template>
刷新
</v-btn>
<v-btn @click="share()" rounded variant="outlined" v-model:loading="loadShare">
<template #prepend>
<v-icon>mdi-share</v-icon>
</template>
分享
</v-btn>
</div>
</template>
<template #extension>
<div class="uc-select">
<v-btn variant="outlined" @click="showSelect = true">筛选角色</v-btn>
<v-btn variant="outlined" @click="resetSelect = true">重置筛选</v-btn>
</div>
<!-- todo 展示模式切换 -->
</template>
</v-app-bar>
<div class="uc-box">
<div class="uc-top">
<div class="uc-top-title">
<span v-if="user">
{{ user.nickname }} UID{{ user.gameUid }} 更新于 {{ getUpdateTime() }}
<!-- todo 展示筛选条件 -->
</span>
<span v-else> 暂无数据 </span>
</div>
</div>
<div class="uc-grid">
<TuaAvatarBox
v-for="(role, index) in selectedList"
:key="index"
:model-value="role"
@click="selectRole(role)"
/>
</div>
</div>
<TwoSelectC v-model="showSelect" @select-c="handleSelect" v-model:reset="resetSelect" />
</template>
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { onBeforeMount, onMounted, ref, watch } from "vue";
import showSnackbar from "../../components/func/snackbar.js";
import ToLoading from "../../components/overlay/to-loading.vue";
import TuaAvatarBox from "../../components/userAvatar/tua-avatar-box.vue";
import TwoSelectC, { SelectedCValue } from "../../components/wiki/two-select-c.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 TGRequest from "../../web/request/TGRequest.js";
// store
const userStore = storeToRefs(useUserStore());
const user = ref<TGApp.Sqlite.Account.Game>();
// loading
const loading = ref<boolean>(false);
const loadData = ref<boolean>(false);
const loadShare = ref<boolean>(false);
const loadingTitle = ref<string>();
const loadingSub = ref<string>();
// data
const isEmpty = ref(true);
const roleList = ref<TGApp.Sqlite.Character.UserRole[]>([]);
const selectedList = ref<TGApp.Sqlite.Character.UserRole[]>([]);
// overlay
const visible = ref(false);
const dataVal = ref<TGApp.Sqlite.Character.UserRole>(<TGApp.Sqlite.Character.UserRole>{});
const selectIndex = ref(0);
const showSelect = ref<boolean>(false);
const resetSelect = ref<boolean>(false);
onBeforeMount(() => {
if (userStore.account.value) user.value = userStore.account.value;
});
onMounted(async () => {
await TGLogger.Info("[Character][onMounted] 进入角色页面");
loadingTitle.value = "正在获取角色数据";
loading.value = true;
loadData.value = true;
await load();
loading.value = false;
loadData.value = false;
});
watch(resetSelect, (val) => {
if (val) {
selectedList.value = getOrderedList(roleList.value);
showSnackbar({
text: "已重置筛选条件",
color: "success",
});
}
});
function getOrderedList(
data: TGApp.Sqlite.Character.UserRole[],
): 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;
});
}
async function load(): Promise<void> {
if (!user.value) return;
const roleData = await TSUserAvatar.getAvatars(user.value.gameUid);
roleList.value = getOrderedList(roleData);
selectedList.value = roleList.value;
dataVal.value = roleData[selectIndex.value];
isEmpty.value = false;
await TGLogger.Info(`[Character][loadRole][${user.value.gameUid}] 成功加载角色数据`);
await TGLogger.Info(
`[Character][loadRole][${user.value.gameUid}] 共获取到${roleData.length}个角色`,
);
showSnackbar({
text: `成功加载${roleData.length}个角色`,
color: "success",
});
}
async function refresh(): Promise<void> {
if (!user.value) return;
await TGLogger.Info(`[Character][refreshRoles][${user.value.gameUid}] 正在更新角色数据`);
loadingTitle.value = "正在获取角色列表";
loading.value = true;
loadData.value = true;
if (!userStore.cookie.value) {
showSnackbar({
text: "请先登录",
color: "error",
});
loading.value = false;
loadData.value = false;
return;
}
const cookie = {
account_id: userStore.cookie.value.account_id,
cookie_token: userStore.cookie.value.cookie_token,
};
const listRes = await TGRequest.User.byCookie.getAvatarList(cookie, user.value.gameUid);
if (!Array.isArray(listRes)) {
showSnackbar({
text: `[${listRes.retcode}] ${listRes.message}`,
color: "error",
});
await TGLogger.Error(`[Character][refreshRoles][${user.value.gameUid}] 获取角色列表失败`);
await TGLogger.Error(
`[Character][refreshRoles][${user.value.gameUid}] ${listRes.retcode} ${listRes.message}`,
);
loading.value = false;
loadData.value = false;
return;
}
const idList = listRes.map((i) => i.id.toString());
loadingTitle.value = "正在获取角色数据";
loadingSub.value = `${idList.length}个角色`;
const res = await TGRequest.User.byCookie.getAvatarDetail(cookie, user.value.gameUid, idList);
if ("retcode" in res) {
showSnackbar({
text: `[${res.retcode}] ${res.message}`,
color: "error",
});
await TGLogger.Error(`[Character][refreshRoles][${user.value.gameUid}] 获取角色数据失败`);
await TGLogger.Error(
`[Character][refreshRoles][${user.value.gameUid}] ${res.retcode} ${res.message}`,
);
loading.value = false;
loadData.value = false;
return;
}
userStore.propMap.value = res.property_map;
loadingTitle.value = "正在保存角色数据";
await TSUserAvatar.saveAvatars(user.value.gameUid, res.list);
await TGLogger.Info(`[Character][refreshRoles][${user.value.gameUid}] 成功更新角色数据`);
await TGLogger.Info(
`[Character][refreshRoles][${user.value.gameUid}] 共更新${res.list.length}个角色`,
);
await load();
loading.value = false;
loadData.value = false;
}
async function share(): Promise<void> {
if (!user.value) return;
await TGLogger.Info(`[Character][shareRoles][${user.value.gameUid}] 正在生成分享图片`);
const rolesBox = <HTMLElement>document.querySelector(".uc-box");
const fileName = `【角色列表】-${user.value.gameUid}`;
loadingTitle.value = "正在生成图片";
loadingSub.value = `${fileName}.png`;
loading.value = true;
loadShare.value = true;
await generateShareImg(fileName, rolesBox);
loadingSub.value = "";
loading.value = false;
loadShare.value = false;
await TGLogger.Info(`[Character][shareRoles][${user.value.gameUid}] 生成分享图片成功`);
}
function getUpdateTime(): string {
let lastUpdateTime = 0;
roleList.value.forEach((role) => {
const updateTime = new Date(role.updated).getTime();
if (updateTime > lastUpdateTime) {
lastUpdateTime = updateTime;
}
});
return new Date(lastUpdateTime).toLocaleString().replace(/\//g, "-");
}
function selectRole(role: TGApp.Sqlite.Character.UserRole): void {
dataVal.value = role;
selectIndex.value = roleList.value.indexOf(role);
visible.value = true;
}
function handleSelect(val: SelectedCValue) {
showSelect.value = false;
const filterC = AppCharacterData.filter((avatar) => {
if (!roleList.value.find((role) => role.avatar.id === avatar.id)) return false;
if (!val.star.includes(avatar.star)) return false;
if (!val.weapon.includes(avatar.weapon)) return false;
if (!val.elements.includes(avatar.element)) return false;
return val.area.includes(avatar.area);
});
if (filterC.length === 0) {
showSnackbar({
text: "未找到符合条件的角色",
color: "warn",
});
return;
}
showSnackbar({
text: `筛选出符合条件的角色 ${filterC.length}`,
color: "success",
});
const selectedId = filterC.map((item) => item.id);
selectedList.value = roleList.value.filter((role) => selectedId.includes(role.avatar.id));
}
</script>
<style lang="css" scoped>
.uc-select {
display: flex;
align-items: center;
justify-content: flex-start;
margin: 0 10px;
gap: 10px;
}
.uc-box {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
background: var(--box-bg-1);
gap: 10px;
}
.uc-top {
display: flex;
width: 100%;
height: 50px;
align-items: center;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid var(--common-shadow-2);
}
.uc-top-title {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
.uc-top-btns {
display: flex;
align-content: center;
margin: 0 10px;
column-gap: 10px;
}
.uc-grid {
display: grid;
grid-gap: 15px;
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
</style>