mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-06 08:32:51 +08:00
♻️ 全面整理重构
This commit is contained in:
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
- name: Build app
|
||||
run: pnpm build -- ${{ matrix.settings.args }}
|
||||
run: pnpm build ${{ matrix.settings.args }}
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -25,62 +25,52 @@ walkdir = "2.5.0"
|
||||
# deep link 插件
|
||||
[dependencies.tauri-plugin-deep-link]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
|
||||
# dialog 插件
|
||||
[dependencies.tauri-plugin-dialog]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
|
||||
# fs 插件
|
||||
[dependencies.tauri-plugin-fs]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
|
||||
# http 插件
|
||||
[dependencies.tauri-plugin-http]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
features = ["unsafe-headers"]
|
||||
|
||||
# log 插件
|
||||
[dependencies.tauri-plugin-log]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
|
||||
# os 插件
|
||||
[dependencies.tauri-plugin-os]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
|
||||
# process 插件
|
||||
[dependencies.tauri-plugin-process]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
|
||||
# shell 插件
|
||||
[dependencies.tauri-plugin-shell]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
|
||||
# single-instance 插件
|
||||
[dependencies.tauri-plugin-single-instance]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
|
||||
# sqlite 插件
|
||||
[dependencies.tauri-plugin-sql]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
#git = "https://github.com/tauri-apps/plugins-workspace.git"
|
||||
branch = "v2"
|
||||
features = ["sqlite"]
|
||||
|
||||
|
||||
26
src/App.vue
26
src/App.vue
@@ -11,26 +11,26 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TBackTop from "@comp/app/t-backTop.vue";
|
||||
import TSidebar from "@comp/app/t-sidebar.vue";
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TGSqlite from "@Sqlite/index.js";
|
||||
import TSUserAccount from "@Sqlite/modules/userAccount.js";
|
||||
import { app, core, event, webviewWindow } from "@tauri-apps/api";
|
||||
import { PhysicalSize } from "@tauri-apps/api/dpi";
|
||||
import { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { currentMonitor, getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { mkdir } from "@tauri-apps/plugin-fs";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import TBackTop from "./components/app/t-backTop.vue";
|
||||
import TSidebar from "./components/app/t-sidebar.vue";
|
||||
import showDialog from "./components/func/dialog.js";
|
||||
import showSnackbar from "./components/func/snackbar.js";
|
||||
import TGSqlite from "./plugins/Sqlite/index.js";
|
||||
import TSUserAccount from "./plugins/Sqlite/modules/userAccount.js";
|
||||
import { useAppStore } from "./store/modules/app.js";
|
||||
import { useUserStore } from "./store/modules/user.js";
|
||||
import { getBuildTime } from "./utils/TGBuild.js";
|
||||
import TGLogger from "./utils/TGLogger.js";
|
||||
import OtherApi from "./web/request/otherReq.js";
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
import { getBuildTime } from "@/utils/TGBuild.js";
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
import OtherApi from "@/web/request/otherReq.js";
|
||||
|
||||
const router = useRouter();
|
||||
const { theme, needResize, deviceInfo, isLogin, userDir, buildTime } = storeToRefs(useAppStore());
|
||||
@@ -173,7 +173,7 @@ async function checkUserLoad(): Promise<void> {
|
||||
}
|
||||
|
||||
async function getDeepLink(): Promise<UnlistenFn> {
|
||||
return await event.listen("active_deep_link", async (e: Event<string>) => {
|
||||
return await event.listen<string>("active_deep_link", async (e: Event<string>) => {
|
||||
const windowGet = new webviewWindow.WebviewWindow("TeyvatGuide");
|
||||
if (await windowGet.isMinimized()) await windowGet.unminimize();
|
||||
await windowGet.setFocus();
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div v-show="canTop" class="back-top" @click="handleScrollTop">
|
||||
<img src="../../assets/icons/back-top.svg" alt="back-icon" />
|
||||
<img src="@/assets/icons/back-top.svg" alt="back-icon" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
const scrollTop = ref<number>(0); // 滚动条距离顶部的距离
|
||||
const canTop = ref<boolean>(false); // 默认不显示
|
||||
const scrollTop = ref<number>(0);
|
||||
const canTop = ref<boolean>(false);
|
||||
|
||||
// 监听滚动事件
|
||||
function handleScroll(): void {
|
||||
scrollTop.value = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
// 超过500px显示回到顶部按钮
|
||||
canTop.value = scrollTop.value > 500;
|
||||
// 没超过500,但是到底部了,也显示回到顶部按钮
|
||||
if (!canTop.value) {
|
||||
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
|
||||
const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
|
||||
@@ -24,7 +21,6 @@ function handleScroll(): void {
|
||||
}
|
||||
}
|
||||
|
||||
// 点击回到顶部
|
||||
function handleScrollTop(): void {
|
||||
let timer = 0;
|
||||
cancelAnimationFrame(timer);
|
||||
@@ -43,8 +39,7 @@ function handleScrollTop(): void {
|
||||
onMounted(() => window.addEventListener("scroll", handleScroll));
|
||||
onUnmounted(() => window.removeEventListener("scroll", handleScroll));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="css" scoped>
|
||||
.back-top {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
|
||||
@@ -11,24 +11,25 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import Mys from "@Mys/index.js";
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import Mys from "../../plugins/Mys/index.js";
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import TGClient from "../../utils/TGClient.js";
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import { createPost } from "../../utils/TGWindow.js";
|
||||
import OtherApi from "../../web/request/otherReq.js";
|
||||
import showDialog from "../func/dialog.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import ToLivecode from "./to-livecode.vue";
|
||||
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
import TGClient from "@/utils/TGClient.js";
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
import { createPost } from "@/utils/TGWindow.js";
|
||||
import OtherApi from "@/web/request/otherReq.js";
|
||||
|
||||
type TGameNavProps = { modelValue: number };
|
||||
const props = withDefaults(defineProps<TGameNavProps>(), { modelValue: 2 });
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
const nav = shallowRef<TGApp.BBS.Navigator.Navigator[]>([]);
|
||||
const codeData = shallowRef<TGApp.BBS.Navigator.CodeData[]>([]);
|
||||
const showOverlay = ref<boolean>(false);
|
||||
@@ -72,7 +73,7 @@ async function tryGetCode(): Promise<void> {
|
||||
}
|
||||
|
||||
async function toNav(item: TGApp.BBS.Navigator.Navigator): Promise<void> {
|
||||
if (!appStore.isLogin) {
|
||||
if (!isLogin.value) {
|
||||
showSnackbar.warn("请先登录");
|
||||
return;
|
||||
}
|
||||
@@ -133,8 +134,8 @@ async function toBBS(link: URL): Promise<void> {
|
||||
function getLocalPath(forum?: string): string {
|
||||
if (!forum) return "";
|
||||
const forumLocalMap: Record<string, string> = {
|
||||
"31": "/news/3", // 崩坏2官方
|
||||
"6": "/news/1", // 崩坏3官方
|
||||
"31": "/news/3", // 崩坏2 官方
|
||||
"6": "/news/1", // 崩坏3 官方
|
||||
"28": "/news/2", // 原神官方
|
||||
"33": "/news/4", // 未定官方
|
||||
"58": "/news/8", // 绝区零官方
|
||||
@@ -51,11 +51,6 @@ export type TItemBoxData = {
|
||||
type TItemBoxProps = { modelValue: TItemBoxData };
|
||||
|
||||
const props = defineProps<TItemBoxProps>();
|
||||
const sizeInner = `${props.modelValue.innerHeight ?? 0}px`;
|
||||
const fontSizeInner = props.modelValue.innerHeight ? `${props.modelValue.innerHeight / 2}px` : "0";
|
||||
const sizeOuter = `${props.modelValue.outerHeight ?? 0}px`;
|
||||
const fontSizeOuter = props.modelValue.outerHeight ? `${props.modelValue.outerHeight / 2}px` : "0";
|
||||
const innerBlur = props.modelValue.innerBlur ?? "0";
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tib-box {
|
||||
@@ -71,8 +66,8 @@ const innerBlur = props.modelValue.innerBlur ?? "0";
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
width: v-bind("props.modelValue.size");
|
||||
height: v-bind("props.modelValue.size");
|
||||
border-radius: 5px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.tib-bg img {
|
||||
@@ -85,8 +80,8 @@ const innerBlur = props.modelValue.innerBlur ?? "0";
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: v-bind("props.modelValue.size");
|
||||
height: v-bind("props.modelValue.size");
|
||||
border-radius: 5px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.tib-icon img {
|
||||
@@ -101,11 +96,11 @@ const innerBlur = props.modelValue.innerBlur ?? "0";
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: v-bind("props.modelValue.size");
|
||||
height: v-bind("props.modelValue.size");
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.tib-lt {
|
||||
@@ -114,9 +109,9 @@ const innerBlur = props.modelValue.innerBlur ?? "0";
|
||||
left: 3%;
|
||||
display: flex;
|
||||
width: v-bind("props.modelValue.ltSize");
|
||||
height: v-bind("props.modelValue.ltSize");
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.tib-lt img {
|
||||
@@ -131,9 +126,9 @@ const innerBlur = props.modelValue.innerBlur ?? "0";
|
||||
right: 0;
|
||||
display: flex;
|
||||
width: v-bind("props.modelValue.rtSize");
|
||||
height: v-bind("props.modelValue.rtSize");
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1;
|
||||
background: rgb(0 0 0 / 40%);
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
@@ -150,20 +145,20 @@ const innerBlur = props.modelValue.innerBlur ?? "0";
|
||||
height: v-bind("props.modelValue.innerHeight ?? 0") px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-webkit-backdrop-filter: blur(v-bind(innerBlur));
|
||||
backdrop-filter: blur(v-bind(innerBlur));
|
||||
-webkit-backdrop-filter: blur(v-bind("props.modelValue.innerBlur ?? 0"));
|
||||
backdrop-filter: blur(v-bind("props.modelValue.innerBlur ?? 0"));
|
||||
background: rgb(20 20 20 / 40%);
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
color: var(--tgc-white-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: v-bind(fontSizeInner);
|
||||
font-size: v-bind("((props.modelValue.innerHeight ?? 0) / 2).toString() + 'px'");
|
||||
}
|
||||
|
||||
.tib-inner img {
|
||||
width: v-bind(sizeInner);
|
||||
height: v-bind(sizeInner);
|
||||
width: v-bind("(props.modelValue.innerHeight ?? 0).toString() + 'px'");
|
||||
padding: 1px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.tib-inner span {
|
||||
@@ -178,11 +173,11 @@ const innerBlur = props.modelValue.innerBlur ?? "0";
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: v-bind(sizeOuter);
|
||||
height: v-bind("(props.modelValue.outerHeight ?? 0).toString() + 'px'");
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--common-text-title);
|
||||
font-size: v-bind(fontSizeOuter);
|
||||
font-size: v-bind("((props.modelValue.outerHeight ?? 0)/2).toString() + 'px'");
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -10,14 +10,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
type TolProps = { modelValue: boolean; blurVal?: string; dismissible?: boolean };
|
||||
type TolProps = { modelValue: boolean; blurVal?: string };
|
||||
type TolEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
const emit = defineEmits<TolEmits>();
|
||||
const props = withDefaults(defineProps<TolProps>(), {
|
||||
modelValue: false,
|
||||
blurVal: "20px",
|
||||
dismissible: true,
|
||||
});
|
||||
const props = withDefaults(defineProps<TolProps>(), { modelValue: false, blurVal: "20px" });
|
||||
const showTolo = ref<boolean>(false);
|
||||
const showToli = ref<boolean>(false);
|
||||
|
||||
@@ -35,7 +31,6 @@ watch(
|
||||
);
|
||||
|
||||
function toClick(): void {
|
||||
if (!props.dismissible) return;
|
||||
emit("update:modelValue", false);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -8,11 +8,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
const isPined = ref<boolean>(false);
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -67,13 +67,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TpAvatar from "@comp/viewPost/tp-avatar.vue";
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import { generateShareImg, saveImgLocal } from "../../utils/TGShare.js";
|
||||
import { createPost } from "../../utils/TGWindow.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import TpAvatar from "../viewPost/tp-avatar.vue";
|
||||
import { generateShareImg, saveImgLocal } from "@/utils/TGShare.js";
|
||||
import { createPost } from "@/utils/TGWindow.js";
|
||||
|
||||
type TPostCardProps = { modelValue: TGApp.Plugins.Mys.Post.FullData; selectMode?: boolean };
|
||||
type TPostCardEmits = (e: "onSelected", v: string) => void;
|
||||
@@ -91,7 +91,10 @@ const cardBg = computed<string>(() => {
|
||||
|
||||
onMounted(async () => await reload(props.modelValue));
|
||||
|
||||
watch(() => props.modelValue, reload);
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => await reload(props.modelValue),
|
||||
);
|
||||
|
||||
async function reload(data: TGApp.Plugins.Mys.Post.FullData): Promise<void> {
|
||||
if (localCover.value) {
|
||||
@@ -99,8 +102,9 @@ async function reload(data: TGApp.Plugins.Mys.Post.FullData): Promise<void> {
|
||||
localCover.value = undefined;
|
||||
}
|
||||
card.value = getPostCard(data);
|
||||
if (card.value && card.value.cover !== "")
|
||||
if (card.value && card.value.cover !== "") {
|
||||
localCover.value = await saveImgLocal(card.value.cover);
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -110,6 +114,7 @@ onUnmounted(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// todo 优化结构
|
||||
/**
|
||||
* @description 活动状态
|
||||
* @since Alpha v0.2.1
|
||||
@@ -159,13 +164,9 @@ function getActivityStatus(status: number): TGApp.Plugins.Mys.News.RenderStatus
|
||||
|
||||
function getPostCover(item: TGApp.Plugins.Mys.Post.FullData): string {
|
||||
let cover;
|
||||
if (item.cover) {
|
||||
cover = item.cover.url;
|
||||
} else if (item.post.cover) {
|
||||
cover = item.post.cover;
|
||||
} else if (item.post.images.length > 0) {
|
||||
cover = item.post.images[0];
|
||||
}
|
||||
if (item.cover) cover = item.cover.url;
|
||||
else if (item.post.cover) cover = item.post.cover;
|
||||
else if (item.post.images.length > 0) cover = item.post.images[0];
|
||||
if (cover === undefined) return "";
|
||||
if (cover.endsWith(".gif")) return cover;
|
||||
return `${cover}?x-oss-process=image/format,png`;
|
||||
@@ -185,23 +186,19 @@ function getCommonCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys
|
||||
subtitle: item.post.post_id,
|
||||
user: item.user,
|
||||
forum:
|
||||
item.forum !== null
|
||||
? {
|
||||
name: item.forum.name,
|
||||
icon: item.forum.icon,
|
||||
id: item.forum.id,
|
||||
}
|
||||
: null,
|
||||
item.forum === null
|
||||
? null
|
||||
: { name: item.forum.name, icon: item.forum.icon, id: item.forum.id },
|
||||
data:
|
||||
item.stat !== null
|
||||
? {
|
||||
item.stat === null
|
||||
? null
|
||||
: {
|
||||
mark: item.stat.bookmark_num,
|
||||
forward: item.stat.forward_num,
|
||||
like: item.stat.like_num,
|
||||
reply: item.stat.reply_num,
|
||||
view: item.stat.view_num,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
topics: item.topics,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import showLoading from "../func/loading.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
import { generateShareImg } from "@/utils/TGShare.js";
|
||||
|
||||
type TShareBtnProps = { selector: string; title: string };
|
||||
const props = defineProps<TShareBtnProps>();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- todo UI调整 -->
|
||||
<template>
|
||||
<v-navigation-drawer :permanent="true" :rail="rail" class="tsb-box">
|
||||
<v-list class="side-list" density="compact" :nav="true">
|
||||
@@ -24,14 +25,14 @@
|
||||
<v-list-item :title.attr="'公告'" value="announcements" :link="true" href="/announcements">
|
||||
<template #title>公告</template>
|
||||
<template #prepend>
|
||||
<img src="../../assets/icons/board.svg" alt="annoIcon" class="side-icon" />
|
||||
<img src="@/assets/icons/board.svg" alt="annoIcon" class="side-icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
:title.attr="'咨讯'"
|
||||
value="news"
|
||||
:link="true"
|
||||
:href="`/news/2/${appStore.recentNewsType}`"
|
||||
:href="`/news/2/${recentNewsType}`"
|
||||
>
|
||||
<template #title>咨讯</template>
|
||||
<template #prepend>
|
||||
@@ -47,7 +48,7 @@
|
||||
<v-list-item :title.attr="'成就'" value="achievements" :link="true" href="/achievements">
|
||||
<template #title>成就</template>
|
||||
<template #prepend>
|
||||
<img src="../../assets/icons/achievements.svg" alt="achievementsIcon" class="side-icon" />
|
||||
<img src="@/assets/icons/achievements.svg" alt="achievementsIcon" class="side-icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
@@ -134,9 +135,9 @@
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
class="side-item-menu"
|
||||
value="wiki-namecard"
|
||||
value="wiki-nameCard"
|
||||
:link="true"
|
||||
href="/wiki/namecard"
|
||||
href="/wiki/nameCard"
|
||||
>
|
||||
<template #default>
|
||||
<v-icon size="20" color="var(--tgc-yellow-2)">mdi-credit-card-outline</v-icon>
|
||||
@@ -204,70 +205,59 @@
|
||||
<template #title>{{ themeTitle }}</template>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
{{ themeGet === "default" ? "mdi-weather-night" : "mdi-weather-sunny" }}
|
||||
{{ theme === "default" ? "mdi-weather-night" : "mdi-weather-sunny" }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item :title.attr="'设置'" value="config" :link="true" href="/config">
|
||||
<template #title>设置</template>
|
||||
<template #prepend>
|
||||
<img src="../../assets/icons/setting.svg" alt="setting" class="side-icon" />
|
||||
<img src="@/assets/icons/setting.svg" alt="setting" class="side-icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</div>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { event, webviewWindow } from "@tauri-apps/api";
|
||||
import { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, onUnmounted } from "vue";
|
||||
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import mhyClient from "../../utils/TGClient.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
import mhyClient from "@/utils/TGClient.js";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { sidebar, theme, isLogin, recentNewsType } = storeToRefs(useAppStore());
|
||||
const { briefInfo } = storeToRefs(useUserStore());
|
||||
let themeListener: UnlistenFn | null = null;
|
||||
// @ts-expect-error The import.meta meta-property is not allowed in files which will build into CommonJS output.
|
||||
const isDevEnv = import.meta.env.MODE === "development";
|
||||
const rail = computed<boolean>({
|
||||
get: () => appStore.sidebar.collapse,
|
||||
set: (v) => (appStore.sidebar.collapse = v),
|
||||
get: () => sidebar.value.collapse,
|
||||
set: (v) => (sidebar.value.collapse = v),
|
||||
});
|
||||
const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
|
||||
if (briefInfo.value && briefInfo.value.nickname) return briefInfo.value;
|
||||
return {
|
||||
nickname: "未登录",
|
||||
uid: "-1",
|
||||
desc: "请扫码登录",
|
||||
avatar: "/source/UI/lumine.webp",
|
||||
};
|
||||
return { nickname: "未登录", uid: "-1", desc: "请扫码登录", avatar: "/source/UI/lumine.webp" };
|
||||
});
|
||||
const themeGet = computed<string>({
|
||||
get: () => appStore.theme,
|
||||
set: (v) => (appStore.theme = v),
|
||||
});
|
||||
const themeTitle = computed<string>(() => (themeGet.value === "default" ? "夜间模式" : "日间模式"));
|
||||
const themeTitle = computed<string>(() => (theme.value === "default" ? "夜间模式" : "日间模式"));
|
||||
|
||||
onMounted(async () => {
|
||||
themeListener = await event.listen("readTheme", (e: Event<string>) => {
|
||||
const theme = e.payload;
|
||||
themeGet.value = theme === "default" ? "default" : "dark";
|
||||
themeListener = await event.listen<string>("readTheme", (e: Event<string>) => {
|
||||
theme.value = e.payload === "default" ? "default" : "dark";
|
||||
});
|
||||
if (webviewWindow.getCurrentWebviewWindow().label === "TeyvatGuide") await mhyClient.run();
|
||||
});
|
||||
|
||||
async function switchTheme(): Promise<void> {
|
||||
await event.emit("readTheme", themeGet.value === "default" ? "dark" : "default");
|
||||
await event.emit("readTheme", theme.value === "default" ? "dark" : "default");
|
||||
}
|
||||
|
||||
async function openClient(func: string): Promise<void> {
|
||||
if (appStore.isLogin) await mhyClient.open(func);
|
||||
if (isLogin.value) await mhyClient.open(func);
|
||||
else showSnackbar.warn("请前往设置页面登录!");
|
||||
}
|
||||
|
||||
@@ -278,7 +268,6 @@ onUnmounted(() => {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.tsb-box {
|
||||
background: var(--app-side-bg);
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<template>
|
||||
<div class="tsl-box">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
<slot>{{ title }}</slot>
|
||||
<img src="@/assets/icons/arrow-right.svg" alt="right" />
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ title?: string }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tsl-box {
|
||||
display: flex;
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { event } from "@tauri-apps/api";
|
||||
import { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, onUnmounted } from "vue";
|
||||
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
|
||||
const { theme } = storeToRefs(useAppStore());
|
||||
const appStore = useAppStore();
|
||||
let themeListener: UnlistenFn | null = null;
|
||||
|
||||
onMounted(async () => {
|
||||
themeListener = event.listen("readTheme", (e: Event<string>) => {
|
||||
themeListener = await event.listen<string>("readTheme", (e: Event<string>) => {
|
||||
theme.value = e.payload === "default" ? "default" : "dark";
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,21 +37,21 @@
|
||||
variant="outlined"
|
||||
class="tolc-btn"
|
||||
data-html2canvas-ignore
|
||||
></v-btn>
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { computed } from "vue";
|
||||
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import { timestampToDate } from "../../utils/toolFunc.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import TOverlay from "./t-overlay.vue";
|
||||
|
||||
import { generateShareImg } from "@/utils/TGShare.js";
|
||||
import { timestampToDate } from "@/utils/toolFunc.js";
|
||||
|
||||
type ToLiveCodeProps = {
|
||||
data: TGApp.BBS.Navigator.CodeData[];
|
||||
actId: string | undefined;
|
||||
|
||||
@@ -26,20 +26,20 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import TOverlay from "./t-overlay.vue";
|
||||
|
||||
import { generateShareImg } from "@/utils/TGShare.js";
|
||||
|
||||
enum ToNameCardTypeEnum {
|
||||
other = 0,
|
||||
achievement = 1,
|
||||
role = 2,
|
||||
record = 3,
|
||||
activity = 4,
|
||||
unknown = 5,
|
||||
other,
|
||||
achievement,
|
||||
role,
|
||||
record,
|
||||
activity,
|
||||
unknown,
|
||||
}
|
||||
|
||||
type ToNameCardTypeMap = { [key in ToNameCardTypeEnum]: string };
|
||||
@@ -30,6 +30,7 @@ const bgImage = computed<string>(() => {
|
||||
height: 80px;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 10px 50px 50px 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: var(--box-bg-1);
|
||||
background-image: v-bind(bgImage);
|
||||
background-position: right;
|
||||
@@ -31,9 +31,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, reactive, ref, useTemplateRef, watch } from "vue";
|
||||
import { computed, onMounted, reactive, ref, useTemplateRef, watch } from "vue";
|
||||
|
||||
import { DialogCheckParams, DialogInputParams, DialogParams } from "./dialog.js";
|
||||
import type { DialogCheckParams, DialogInputParams, DialogParams } from "./dialog.js";
|
||||
|
||||
const defaultProp: DialogParams = { title: "", text: "", mode: "check", otcancel: false };
|
||||
const props = defineProps<DialogParams>();
|
||||
@@ -114,7 +114,7 @@ async function displayInputBox(params: DialogInputParams): Promise<string | fals
|
||||
data.otcancel = params.otcancel ?? true;
|
||||
show.value = true;
|
||||
return await new Promise<string | false | undefined>((resolve) => {
|
||||
nextTick(() => setTimeout(() => inputEl.value?.focus(), 100));
|
||||
setTimeout(() => inputEl.value?.focus(), 100);
|
||||
watch(
|
||||
() => show.value,
|
||||
() => setTimeout(() => resolve(inputVal.value), 500),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file component/func/geetest.ts
|
||||
* @description 封装自定义 geetest 组件,通过函数调用的方式,简化 geetest 的使用
|
||||
* @since Beta v0.5.1
|
||||
* @since Beta v0.6.6
|
||||
*/
|
||||
|
||||
import { h, render } from "vue";
|
||||
@@ -26,14 +26,14 @@ interface GeetestInstance extends ComponentInternalInstance {
|
||||
};
|
||||
}
|
||||
|
||||
const renderBox = (props: TGApp.Plugins.Mys.Geetest.reqResp): VNode => {
|
||||
function renderBox(props: TGApp.Plugins.Mys.Geetest.reqResp): VNode {
|
||||
const container = document.createElement("div");
|
||||
container.id = geetestId;
|
||||
const boxVNode: VNode = h(geetest, props);
|
||||
render(boxVNode, container);
|
||||
document.body.appendChild(container);
|
||||
return boxVNode;
|
||||
};
|
||||
}
|
||||
|
||||
let geetestInstance: VNode;
|
||||
|
||||
|
||||
@@ -13,56 +13,56 @@
|
||||
<div class="hta-oob-title">数据收集统计</div>
|
||||
<HtaOverviewLine
|
||||
label="当期深渊ID"
|
||||
:cur="dataCur.ScheduleId"
|
||||
:last="dataLast.ScheduleId"
|
||||
:cur="props.data.cur.ScheduleId"
|
||||
:last="props.data.last.ScheduleId"
|
||||
:show-diff="false"
|
||||
/>
|
||||
<HtaOverviewLine
|
||||
label="上传记录总数"
|
||||
:cur="dataCur.RecordTotal"
|
||||
:last="dataLast.RecordTotal"
|
||||
:cur="props.data.cur.RecordTotal"
|
||||
:last="props.data.last.RecordTotal"
|
||||
/>
|
||||
<div class="hta-oob-title">深渊数据统计</div>
|
||||
<HtaOverviewLine
|
||||
label="总计深渊记录"
|
||||
:cur="dataCur.SpiralAbyssTotal"
|
||||
:last="dataLast.SpiralAbyssTotal"
|
||||
:cur="props.data.cur.SpiralAbyssTotal"
|
||||
:last="props.data.last.SpiralAbyssTotal"
|
||||
/>
|
||||
<HtaOverviewLine
|
||||
label="通关深渊记录"
|
||||
:cur="dataCur.SpiralAbyssPassed"
|
||||
:last="dataLast.SpiralAbyssPassed"
|
||||
:cur="props.data.cur.SpiralAbyssPassed"
|
||||
:last="props.data.last.SpiralAbyssPassed"
|
||||
/>
|
||||
<HtaOverviewLine
|
||||
label="满星深渊记录"
|
||||
:cur="dataCur.SpiralAbyssFullStar"
|
||||
:last="dataLast.SpiralAbyssFullStar"
|
||||
:cur="props.data.cur.SpiralAbyssFullStar"
|
||||
:last="props.data.last.SpiralAbyssFullStar"
|
||||
/>
|
||||
<HtaOverviewLine
|
||||
label="平均获取渊星"
|
||||
:cur="dataCur.SpiralAbyssStarTotal / dataCur.SpiralAbyssTotal"
|
||||
:last="dataLast.SpiralAbyssStarTotal / dataLast.SpiralAbyssTotal"
|
||||
:cur="props.data.cur.SpiralAbyssStarTotal / props.data.cur.SpiralAbyssTotal"
|
||||
:last="props.data.last.SpiralAbyssStarTotal / props.data.last.SpiralAbyssTotal"
|
||||
/>
|
||||
<HtaOverviewLine
|
||||
label="平均战斗次数"
|
||||
:cur="dataCur.SpiralAbyssBattleTotal / dataCur.SpiralAbyssTotal"
|
||||
:last="dataLast.SpiralAbyssBattleTotal / dataLast.SpiralAbyssTotal"
|
||||
:cur="props.data.cur.SpiralAbyssBattleTotal / props.data.cur.SpiralAbyssTotal"
|
||||
:last="props.data.last.SpiralAbyssBattleTotal / props.data.last.SpiralAbyssTotal"
|
||||
/>
|
||||
<div class="hta-oob-extra">更新于 {{ timestampToDate(props.data.cur.Timestamp) }}</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import { timestampToDate } from "../../utils/toolFunc.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import HtaOverviewLine from "./hta-overview-line.vue";
|
||||
|
||||
import type { AbyssDataItem } from "@/pages/WIKI/Abyss.vue";
|
||||
import { generateShareImg } from "@/utils/TGShare.js";
|
||||
import { timestampToDate } from "@/utils/toolFunc.js";
|
||||
|
||||
type HtaOverlayOverviewProps = {
|
||||
modelValue: boolean;
|
||||
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.OverviewData>;
|
||||
@@ -76,8 +76,6 @@ const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const dataCur = computed(() => props.data.cur);
|
||||
const dataLast = computed(() => props.data.last);
|
||||
|
||||
async function share(): Promise<void> {
|
||||
loadShare.value = true;
|
||||
@@ -87,7 +85,7 @@ async function share(): Promise<void> {
|
||||
loadShare.value = false;
|
||||
return;
|
||||
}
|
||||
const fileName = `深渊数据统计_${timestampToDate(dataCur.value.Timestamp)}.png`;
|
||||
const fileName = `深渊数据统计_${timestampToDate(props.data.cur.Timestamp)}.png`;
|
||||
await generateShareImg(fileName, shareEl, 2);
|
||||
loadShare.value = false;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
v-if="props.showDiff"
|
||||
class="hta-olv-diff"
|
||||
:title="`上期数据:${getNumStr(props.last)}`"
|
||||
:class="{ 'hta-olv-up': isUp, 'hta-olv-down': !isUp }"
|
||||
:class="{ 'hta-olv-up': props.cur > props.last, 'hta-olv-down': props.cur < props.last }"
|
||||
>
|
||||
{{ getDiff(props.cur, props.last) }}
|
||||
</div>
|
||||
@@ -15,18 +15,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface HtaOverviewLineProps {
|
||||
label: string;
|
||||
cur: number;
|
||||
last: number;
|
||||
showDiff?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<HtaOverviewLineProps>(), {
|
||||
showDiff: true,
|
||||
});
|
||||
|
||||
const isUp = props.cur - props.last > 0;
|
||||
type HtaOverviewLineProps = { label: string; cur: number; last: number; showDiff?: boolean };
|
||||
const props = withDefaults(defineProps<HtaOverviewLineProps>(), { showDiff: true });
|
||||
|
||||
function getNumStr(num: number): string {
|
||||
if (Number.isInteger(num)) return num.toString();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<!-- todo fix typo err -->
|
||||
<v-data-table :headers="headers" fixed-header :items="holdData" height="calc(100vh - 160px)">
|
||||
<template v-slot:item="{ item }">
|
||||
<tr class="hta-th-tr">
|
||||
@@ -9,7 +10,10 @@
|
||||
<span>{{ (item.HoldingRate.cur * 100).toFixed(3) }}%</span>
|
||||
<span
|
||||
v-if="item.HoldingRate.cur !== item.HoldingRate.last"
|
||||
:class="getRateClass(item.HoldingRate.cur, item.HoldingRate.last)"
|
||||
:class="{
|
||||
'rate-up': item.HoldingRate.cur > item.HoldingRate.last,
|
||||
'rate-down': item.HoldingRate.cur < item.HoldingRate.last,
|
||||
}"
|
||||
>
|
||||
{{ getRateStr(item.HoldingRate.cur, item.HoldingRate.last) }}
|
||||
</span>
|
||||
@@ -18,7 +22,10 @@
|
||||
<span>{{ (rate.RateCur * 100).toFixed(3) }}%</span>
|
||||
<span
|
||||
v-if="rate.RateCur !== rate.RateLast"
|
||||
:class="getRateClass(rate.RateCur, rate.RateLast)"
|
||||
:class="{
|
||||
'rate-up': rate.RateCur > rate.RateLast,
|
||||
'rate-down': rate.RateCur < rate.RateLast,
|
||||
}"
|
||||
:title="`${(rate.RateLast * 100).toFixed(3)}%`"
|
||||
>
|
||||
{{ getRateStr(rate.RateCur, rate.RateLast) }}
|
||||
@@ -29,30 +36,22 @@
|
||||
</v-data-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import { onMounted, shallowRef } from "vue";
|
||||
|
||||
import { AppCharacterData } from "../../data/index.js";
|
||||
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
|
||||
import TItemBox, { type TItemBoxData } from "../app/t-item-box.vue";
|
||||
import { AppCharacterData } from "@/data/index.js";
|
||||
import type { AbyssDataItem } from "@/pages/WIKI/Abyss.vue";
|
||||
|
||||
interface HtaTabHoldProps {
|
||||
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarHold[]>;
|
||||
}
|
||||
|
||||
interface HtaTabHoldConstellation {
|
||||
Item: number;
|
||||
RateCur: number;
|
||||
RateLast: number;
|
||||
}
|
||||
|
||||
interface HtaTabHoldData {
|
||||
type HtaTabHoldProps = { data: AbyssDataItem<Array<TGApp.Plugins.Hutao.Abyss.AvatarHold>> };
|
||||
type HtaTabHoldConstellation = { Item: number; RateCur: number; RateLast: number };
|
||||
type HtaTabHoldData = {
|
||||
AvatarId: number;
|
||||
HoldingRate: AbyssDataItem<number>;
|
||||
Constellations: Array<HtaTabHoldConstellation>;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<HtaTabHoldProps>();
|
||||
const holdData = ref<HtaTabHoldData[]>([]);
|
||||
const holdData = shallowRef<Array<HtaTabHoldData>>([]);
|
||||
|
||||
const headers = [
|
||||
{ title: "角色", align: "center", key: "AvatarId" },
|
||||
@@ -67,6 +66,7 @@ const headers = [
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
const tmpData: Array<HtaTabHoldData> = [];
|
||||
for (const avatar of props.data.cur) {
|
||||
const avatarLast = props.data.last.find((a) => a.AvatarId === avatar.AvatarId);
|
||||
if (!avatarLast) continue;
|
||||
@@ -86,18 +86,11 @@ onMounted(() => {
|
||||
RateLast: constellationLast.Rate,
|
||||
});
|
||||
}
|
||||
holdData.value.push({
|
||||
AvatarId: avatar.AvatarId,
|
||||
HoldingRate: Rate,
|
||||
Constellations: Constellations,
|
||||
});
|
||||
tmpData.push({ AvatarId: avatar.AvatarId, HoldingRate: Rate, Constellations: Constellations });
|
||||
}
|
||||
holdData.value = tmpData;
|
||||
});
|
||||
|
||||
function getRateClass(cur: number, last: number): string {
|
||||
return cur > last ? "rate-up" : "rate-down";
|
||||
}
|
||||
|
||||
function getRateStr(cur: number, last: number): string {
|
||||
const diff = Math.abs(cur - last) * 100;
|
||||
return `(${cur > last ? "↑" : "↓"}${diff.toFixed(3)}%)`;
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
<template>
|
||||
<div class="hta-tt-box">
|
||||
<v-tabs v-model="tab" direction="vertical" class="hta-tt-tab">
|
||||
<v-tab value="10">第10层</v-tab>
|
||||
<v-tab value="11">第11层</v-tab>
|
||||
<v-tab value="12">第12层</v-tab>
|
||||
<v-tab :value="10">第10层</v-tab>
|
||||
<v-tab :value="11">第11层</v-tab>
|
||||
<v-tab :value="12">第12层</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab" class="hta-tt-window">
|
||||
<v-window-item
|
||||
v-for="selectItem in select"
|
||||
:key="selectItem.Floor"
|
||||
:value="selectItem.Floor.toString()"
|
||||
>
|
||||
<div v-if="select" class="hta-tt-flex">
|
||||
<v-window-item v-for="selectItem in select" :key="selectItem.Floor" :value="selectItem.Floor">
|
||||
<div class="hta-tt-flex">
|
||||
<div class="hta-tuf-box">
|
||||
<div class="hta-tuf-title">上半</div>
|
||||
<v-virtual-scroll :items="selectItem.Up" item-height="100" class="hta-tuf-item">
|
||||
@@ -34,26 +30,24 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, shallowRef } from "vue";
|
||||
|
||||
import HtaTeamLine from "./hta-team-line.vue";
|
||||
|
||||
interface HtaTabTeamProps {
|
||||
modelValue: TGApp.Plugins.Hutao.Abyss.TeamCombination[];
|
||||
}
|
||||
|
||||
type HtaTabTeamProps = { modelValue: Array<TGApp.Plugins.Hutao.Abyss.TeamCombination> };
|
||||
const props = defineProps<HtaTabTeamProps>();
|
||||
|
||||
// data
|
||||
const tab = ref<string>("9");
|
||||
const select = ref<TGApp.Plugins.Hutao.Abyss.TeamCombination[]>([]);
|
||||
const tab = ref<number>(10);
|
||||
const select = shallowRef<Array<TGApp.Plugins.Hutao.Abyss.TeamCombination>>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
props.modelValue.forEach((item) => {
|
||||
const tempData: Array<TGApp.Plugins.Hutao.Abyss.TeamCombination> = [];
|
||||
for (const item of props.modelValue) {
|
||||
item.Up.sort((a, b) => b.Rate - a.Rate);
|
||||
item.Down.sort((a, b) => b.Rate - a.Rate);
|
||||
select.value.push(item);
|
||||
});
|
||||
tempData.push(item);
|
||||
}
|
||||
select.value = tempData;
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
<template>
|
||||
<div class="hta-tu-box">
|
||||
<v-tabs v-model="tab" direction="vertical" class="hta-tu-tab">
|
||||
<v-tab value="10">第10层</v-tab>
|
||||
<v-tab value="11">第11层</v-tab>
|
||||
<v-tab value="12">第12层</v-tab>
|
||||
<v-tab :value="10">第10层</v-tab>
|
||||
<v-tab :value="11">第11层</v-tab>
|
||||
<v-tab :value="12">第12层</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab" class="hta-tu-window">
|
||||
<v-window-item
|
||||
v-for="selectItem in select"
|
||||
:key="selectItem.Floor"
|
||||
:value="selectItem.Floor.toString()"
|
||||
>
|
||||
<v-window-item v-for="selectItem in select" :key="selectItem.Floor" :value="selectItem.Floor">
|
||||
<div class="hta-tu-grid">
|
||||
<TibWikiAbyss
|
||||
v-for="(item, index) in selectItem.Ranks"
|
||||
@@ -23,44 +19,32 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
|
||||
import { onMounted, ref, shallowRef } from "vue";
|
||||
|
||||
import TibWikiAbyss from "./tib-wiki-abyss.vue";
|
||||
|
||||
interface HtaTabUpProps {
|
||||
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>;
|
||||
}
|
||||
import type { AbyssDataItem } from "@/pages/WIKI/Abyss.vue";
|
||||
|
||||
interface HtaTabUpData {
|
||||
Floor: number;
|
||||
Ranks: Array<AbyssDataItem<TGApp.Plugins.Hutao.Base.Rate>>;
|
||||
}
|
||||
type HtaTabUpProps = { data: AbyssDataItem<Array<TGApp.Plugins.Hutao.Abyss.AvatarUse>> };
|
||||
type HtaTabUpData = { Floor: number; Ranks: Array<AbyssDataItem<TGApp.Plugins.Hutao.Base.Rate>> };
|
||||
|
||||
const props = defineProps<HtaTabUpProps>();
|
||||
|
||||
// data
|
||||
const tab = ref<string>("9");
|
||||
const select = ref<Array<HtaTabUpData>>([]);
|
||||
const tab = ref<number>(10);
|
||||
const select = shallowRef<Array<HtaTabUpData>>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const tmpData: Array<HtaTabUpData> = [];
|
||||
for (const floor of props.data.cur) {
|
||||
const floorLast = props.data.last.find((f) => f.Floor === floor.Floor);
|
||||
const floorRank = {
|
||||
Floor: floor.Floor,
|
||||
Ranks: <Array<AbyssDataItem<TGApp.Plugins.Hutao.Base.Rate>>>[],
|
||||
};
|
||||
const floorRank: HtaTabUpData = { Floor: floor.Floor, Ranks: [] };
|
||||
floor.Ranks.sort((a, b) => b.Rate - a.Rate);
|
||||
for (const rank of floor.Ranks) {
|
||||
const rankLast = floorLast?.Ranks.find((r) => r.Item === rank.Item);
|
||||
floorRank.Ranks.push({
|
||||
cur: rank,
|
||||
last: rankLast ?? { Item: rank.Item, Rate: 0 },
|
||||
});
|
||||
floorRank.Ranks.push({ cur: rank, last: rankLast ?? { Item: rank.Item, Rate: 0 } });
|
||||
}
|
||||
select.value.push(floorRank);
|
||||
tmpData.push(floorRank);
|
||||
}
|
||||
select.value = tmpData;
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
<template>
|
||||
<div class="hta-tus-box">
|
||||
<v-tabs v-model="tab" direction="vertical" class="hta-tus-tab">
|
||||
<v-tab value="10">第10层</v-tab>
|
||||
<v-tab value="11">第11层</v-tab>
|
||||
<v-tab value="12">第12层</v-tab>
|
||||
<v-tab :value="10">第10层</v-tab>
|
||||
<v-tab :value="11">第11层</v-tab>
|
||||
<v-tab :value="12">第12层</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab" class="hta-tus-window">
|
||||
<v-window-item
|
||||
v-for="selectItem in select"
|
||||
:key="selectItem.Floor"
|
||||
:value="selectItem.Floor.toString()"
|
||||
>
|
||||
<v-window-item v-for="selectItem in select" :key="selectItem.Floor" :value="selectItem.Floor">
|
||||
<div class="hta-tus-grid">
|
||||
<TibWikiAbyss
|
||||
v-for="(item, index) in selectItem.Ranks"
|
||||
@@ -23,44 +19,32 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
|
||||
import { onMounted, ref, shallowRef } from "vue";
|
||||
|
||||
import TibWikiAbyss from "./tib-wiki-abyss.vue";
|
||||
|
||||
interface HtaTabUseProps {
|
||||
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>;
|
||||
}
|
||||
import type { AbyssDataItem } from "@/pages/WIKI/Abyss.vue";
|
||||
|
||||
interface HtaTabUseData {
|
||||
Floor: number;
|
||||
Ranks: Array<AbyssDataItem<{ Item: number; Rate: number }>>;
|
||||
}
|
||||
type HtaTabUseProps = { data: AbyssDataItem<Array<TGApp.Plugins.Hutao.Abyss.AvatarUse>> };
|
||||
type HtaTabUseData = { Floor: number; Ranks: Array<AbyssDataItem<{ Item: number; Rate: number }>> };
|
||||
|
||||
const props = defineProps<HtaTabUseProps>();
|
||||
|
||||
// data
|
||||
const tab = ref<string>("9");
|
||||
const select = ref<Array<HtaTabUseData>>([]);
|
||||
const tab = ref<number>(10);
|
||||
const select = shallowRef<Array<HtaTabUseData>>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const tempData: Array<HtaTabUseData> = [];
|
||||
for (const floor of props.data.cur) {
|
||||
const floorLast = props.data.last.find((f) => f.Floor === floor.Floor);
|
||||
const floorRank = {
|
||||
Floor: floor.Floor,
|
||||
Ranks: <Array<AbyssDataItem<{ Item: number; Rate: number }>>>[],
|
||||
};
|
||||
const floorRank: HtaTabUseData = { Floor: floor.Floor, Ranks: [] };
|
||||
floor.Ranks.sort((a, b) => b.Rate - a.Rate);
|
||||
for (const rank of floor.Ranks) {
|
||||
const rankLast = floorLast?.Ranks.find((r) => r.Item === rank.Item);
|
||||
floorRank.Ranks.push({
|
||||
cur: rank,
|
||||
last: rankLast ?? { Item: rank.Item, Rate: 0 },
|
||||
});
|
||||
floorRank.Ranks.push({ cur: rank, last: rankLast ?? { Item: rank.Item, Rate: 0 } });
|
||||
}
|
||||
select.value.push(floorRank);
|
||||
tempData.push(floorRank);
|
||||
}
|
||||
select.value = tempData;
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -11,13 +11,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { AppCharacterData } from "../../data/index.js";
|
||||
import TItemBox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
|
||||
interface HtaTeamLineProps {
|
||||
modelValue: { Item: string; Rate: number };
|
||||
}
|
||||
import { AppCharacterData } from "@/data/index.js";
|
||||
|
||||
type HtaTeamLineProps = { modelValue: { Item: string; Rate: number } };
|
||||
const props = defineProps<HtaTeamLineProps>();
|
||||
|
||||
function getBoxData(id: string): TItemBoxData {
|
||||
|
||||
@@ -4,51 +4,50 @@
|
||||
<div class="twa-diff">
|
||||
<span>{{ avatar?.name ?? "旅行者" }}</span>
|
||||
<span>{{ `${(props.modelValue.cur.Rate * 100).toFixed(3)}%` }}</span>
|
||||
<span :class="diffUp ? 'up' : 'down'">{{ getDiffStr() }}</span>
|
||||
<span
|
||||
:class="{
|
||||
up: props.modelValue.cur.Rate > props.modelValue.last.Rate,
|
||||
down: props.modelValue.cur.Rate < props.modelValue.last.Rate,
|
||||
}"
|
||||
>
|
||||
{{ getDiffStr() }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import { computed, onMounted, shallowRef } from "vue";
|
||||
|
||||
import { AppCharacterData } from "../../data/index.js";
|
||||
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
|
||||
import TItemBox, { type TItemBoxData } from "../app/t-item-box.vue";
|
||||
import { AppCharacterData } from "@/data/index.js";
|
||||
import type { AbyssDataItem } from "@/pages/WIKI/Abyss.vue";
|
||||
|
||||
export interface TibWikiAbyssProps {
|
||||
modelValue: AbyssDataItem<TGApp.Plugins.Hutao.Base.Rate>;
|
||||
}
|
||||
type TibWikiAbyssProps = { modelValue: AbyssDataItem<TGApp.Plugins.Hutao.Base.Rate> };
|
||||
|
||||
const props = defineProps<TibWikiAbyssProps>();
|
||||
const avatar = shallowRef<TGApp.App.Character.WikiBriefInfo>();
|
||||
|
||||
const avatar = ref<TGApp.App.Character.WikiBriefInfo>();
|
||||
const diffUp = computed(() => props.modelValue.cur.Rate > props.modelValue.last.Rate);
|
||||
const box = computed<TItemBoxData>(() => ({
|
||||
bg: `/icon/bg/${avatar.value?.star}-Star.webp`,
|
||||
clickable: false,
|
||||
display: "inner",
|
||||
icon: `/WIKI/character/${avatar.value?.id}.webp`,
|
||||
innerHeight: 0,
|
||||
innerText: avatar.value?.name ?? "旅行者",
|
||||
lt:
|
||||
avatar.value === undefined
|
||||
? ""
|
||||
: avatar.value.element !== ""
|
||||
? `/icon/element/${avatar.value.element}元素.webp`
|
||||
: `/icon/weapon/${avatar.value.weapon}.webp`,
|
||||
ltSize: "15px",
|
||||
size: "60px",
|
||||
height: "60px",
|
||||
}));
|
||||
|
||||
const box = computed<TItemBoxData>(() => {
|
||||
return {
|
||||
bg: `/icon/bg/${avatar.value?.star}-Star.webp`,
|
||||
clickable: false,
|
||||
display: "inner",
|
||||
icon: `/WIKI/character/${avatar.value?.id}.webp`,
|
||||
innerHeight: 0,
|
||||
innerText: avatar.value?.name ?? "旅行者",
|
||||
lt:
|
||||
avatar.value === undefined
|
||||
? ""
|
||||
: avatar.value.element !== ""
|
||||
? `/icon/element/${avatar.value.element}元素.webp`
|
||||
: `/icon/weapon/${avatar.value.weapon}.webp`,
|
||||
ltSize: "15px",
|
||||
size: "60px",
|
||||
height: "60px",
|
||||
};
|
||||
});
|
||||
onMounted(() => (avatar.value = AppCharacterData.find((a) => a.id === props.modelValue.cur.Item)));
|
||||
|
||||
onMounted(async () => {
|
||||
avatar.value = AppCharacterData.find((a) => a.id === props.modelValue.cur.Item);
|
||||
});
|
||||
|
||||
function getDiffStr() {
|
||||
function getDiffStr(): string {
|
||||
if (props.modelValue.cur.Rate === props.modelValue.last.Rate) return "";
|
||||
if (props.modelValue.last.Rate > props.modelValue.cur.Rate) {
|
||||
return `↓${((props.modelValue.last.Rate - props.modelValue.cur.Rate) * 100).toFixed(3)}%`;
|
||||
|
||||
@@ -27,18 +27,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { onMounted, onUnmounted, ref, watch } from "vue";
|
||||
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import { generateShareImg, saveImgLocal } from "../../utils/TGShare.js";
|
||||
import { createTGWindow } from "../../utils/TGWindow.js";
|
||||
|
||||
interface TAnnoCardProps {
|
||||
region: string;
|
||||
modelValue: TGApp.App.Announcement.ListCard;
|
||||
lang: string;
|
||||
}
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
import { generateShareImg, saveImgLocal } from "@/utils/TGShare.js";
|
||||
import { createTGWindow } from "@/utils/TGWindow.js";
|
||||
|
||||
type TAnnoCardProps = { region: string; modelValue: TGApp.App.Announcement.ListCard; lang: string };
|
||||
const props = defineProps<TAnnoCardProps>();
|
||||
const localBanner = ref<string>();
|
||||
const localTag = ref<string>();
|
||||
@@ -87,7 +83,11 @@ async function createAnno(): Promise<void> {
|
||||
|
||||
async function shareAnno(): Promise<void> {
|
||||
const fileName = `AnnoCard_${props.modelValue.id}_${props.modelValue.subtitle}`;
|
||||
const element = <HTMLElement>document.querySelector(`#anno_card_${props.modelValue.id}`);
|
||||
const element = document.querySelector<HTMLElement>(`#anno_card_${props.modelValue.id}`);
|
||||
if (element === null) {
|
||||
showSnackbar.error("分享失败,未找到分享元素");
|
||||
return;
|
||||
}
|
||||
await generateShareImg(fileName, element, 2.5);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,22 +7,21 @@
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { parseAnnoContent } from "../../web/utils/annoParser.js";
|
||||
import TpBackupText from "../viewPost/tp-backupText.vue";
|
||||
import TpImage from "../viewPost/tp-image.vue";
|
||||
import TpText from "../viewPost/tp-text.vue";
|
||||
import TpTexts from "../viewPost/tp-texts.vue";
|
||||
import TpUnknown from "../viewPost/tp-unknown.vue";
|
||||
import TpBackupText from "@comp/viewPost/tp-backupText.vue";
|
||||
import TpImage from "@comp/viewPost/tp-image.vue";
|
||||
import TpText from "@comp/viewPost/tp-text.vue";
|
||||
import TpTexts from "@comp/viewPost/tp-texts.vue";
|
||||
import TpUnknown from "@comp/viewPost/tp-unknown.vue";
|
||||
import type { Component } from "vue";
|
||||
|
||||
import TaTable from "./ta-table.vue";
|
||||
|
||||
interface TaParserProps {
|
||||
data: TGApp.BBS.Announcement.ContentItem;
|
||||
}
|
||||
import parseAnnoContent from "@/web/utils/annoParser.js";
|
||||
|
||||
type TaParserProps = { data: TGApp.BBS.Announcement.ContentItem };
|
||||
const props = defineProps<TaParserProps>();
|
||||
|
||||
function getTaName(ta: TGApp.Plugins.Mys.SctPost.Base) {
|
||||
function getTaName(ta: TGApp.Plugins.Mys.SctPost.Base): Component {
|
||||
if (ta.children) return TpTexts;
|
||||
if (typeof ta.insert === "string") return TpText;
|
||||
if ("image" in ta.insert) return TpImage;
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
<template>
|
||||
<div v-html="props.data.insert.table"></div>
|
||||
<div v-html="props.data.insert.table" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TaTableType {
|
||||
insert: {
|
||||
table: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface TaTableProps {
|
||||
data: TaTableType;
|
||||
}
|
||||
type TaTableType = { insert: { table: string } };
|
||||
type TaTableProps = { data: TaTableType };
|
||||
|
||||
const props = defineProps<TaTableProps>();
|
||||
</script>
|
||||
|
||||
@@ -11,7 +11,12 @@
|
||||
<div v-else-if="item.name !== '未知'" class="toab-dialog-item-name">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div :class="item.icon ? 'toab-dialog-item-text' : 'toab-dialog-item-text-mini'">
|
||||
<div
|
||||
:class="{
|
||||
'toab-dialog-item-text': item.icon !== undefined,
|
||||
'toab-dialog-item-text-mini': item.icon === undefined,
|
||||
}"
|
||||
>
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,15 +33,15 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { fetch } from "@tauri-apps/plugin-http";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
import { xml2json } from "xml-js";
|
||||
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import { copyToClipboard, getImageBuffer, saveCanvasImg } from "../../utils/TGShare.js";
|
||||
import { bytesToSize } from "../../utils/toolFunc.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
import { copyToClipboard, getImageBuffer, saveCanvasImg } from "@/utils/TGShare.js";
|
||||
import { bytesToSize } from "@/utils/toolFunc.js";
|
||||
|
||||
type ToArcBirthProps = {
|
||||
modelValue: boolean;
|
||||
@@ -50,8 +55,8 @@ type XmlTextParse = { name: string; icon?: string; text: string };
|
||||
|
||||
const props = defineProps<ToArcBirthProps>();
|
||||
const emits = defineEmits<ToArcBirthEmits>();
|
||||
const buffer = ref<Uint8Array | null>(null);
|
||||
const showText = ref<boolean>(false);
|
||||
const buffer = shallowRef<Uint8Array | null>(null);
|
||||
const textParse = shallowRef<Array<XmlTextParse>>([]);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
@@ -76,7 +81,7 @@ async function onCopy(): Promise<void> {
|
||||
showSnackbar.success(`图片已复制到剪贴板,大小:${size}`);
|
||||
}
|
||||
|
||||
async function onDownload() {
|
||||
async function onDownload(): Promise<void> {
|
||||
if (!props.data) return;
|
||||
const image = props.data.take_picture[Number(props.choice)];
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||
@@ -107,8 +112,8 @@ async function loadText(): Promise<void> {
|
||||
showText.value = true;
|
||||
}
|
||||
|
||||
function getKeyMap(resSource: unknown): XmlKeyMap[] {
|
||||
const res: XmlKeyMap[] = [];
|
||||
function getKeyMap(resSource: unknown): Array<XmlKeyMap> {
|
||||
const res: Array<XmlKeyMap> = [];
|
||||
if (!resSource || typeof resSource !== "object") return res;
|
||||
if (!("elements" in resSource) || !Array.isArray(resSource["elements"])) return res;
|
||||
const arr1 = resSource.elements;
|
||||
@@ -127,8 +132,8 @@ function getKeyMap(resSource: unknown): XmlKeyMap[] {
|
||||
return res;
|
||||
}
|
||||
|
||||
function getTextList(resXml: unknown): XmlTextList[] {
|
||||
const res: XmlTextList[] = [];
|
||||
function getTextList(resXml: unknown): Array<XmlTextList> {
|
||||
const res: Array<XmlTextList> = [];
|
||||
if (!resXml || typeof resXml !== "object") return res;
|
||||
if (!("elements" in resXml) || !Array.isArray(resXml["elements"])) return res;
|
||||
const arr1 = resXml.elements;
|
||||
@@ -202,7 +207,8 @@ async function parseXml(link: string): Promise<false | unknown> {
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background-color: var(--common-shadow-t-2);
|
||||
border-bottom-right-radius: 10px;
|
||||
border-bottom-right-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
.toab-dialog {
|
||||
|
||||
@@ -22,14 +22,13 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TSUserCollection from "@Sqlite/modules/userCollect.js";
|
||||
import { computed, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import TSUserCollection from "../../plugins/Sqlite/modules/userCollect.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showDialog from "../func/dialog.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
type ToPostCollectProps = { modelValue: boolean; post: string[] };
|
||||
type ToPostCollectProps = { modelValue: boolean; post: Array<string> };
|
||||
type ToPostCollectEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "submit"): void;
|
||||
@@ -39,7 +38,7 @@ const props = defineProps<ToPostCollectProps>();
|
||||
const emits = defineEmits<ToPostCollectEmits>();
|
||||
const select = ref<string>();
|
||||
const submit = ref<boolean>(false);
|
||||
const collectList = shallowRef<TGApp.Sqlite.UserCollection.UFCollection[]>([]);
|
||||
const collectList = shallowRef<Array<TGApp.Sqlite.UserCollection.UFCollection>>([]);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
|
||||
@@ -23,35 +23,33 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { app } from "@tauri-apps/api";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { buildTime } = storeToRefs(useAppStore());
|
||||
const versionApp = ref<string>();
|
||||
const buildTime = computed(() => appStore.buildTime);
|
||||
|
||||
onMounted(async () => {
|
||||
versionApp.value = await app.getVersion();
|
||||
});
|
||||
onMounted(async () => (versionApp.value = await app.getVersion()));
|
||||
|
||||
function toRelease() {
|
||||
function toRelease(): void {
|
||||
window.open("https://github.com/BTMuli/TeyvatGuide/releases/latest");
|
||||
}
|
||||
|
||||
function toGroup() {
|
||||
function toGroup(): void {
|
||||
window.open("https://h5.qun.qq.com/s/3cgX0hJ4GA");
|
||||
}
|
||||
|
||||
function toGithub() {
|
||||
function toGithub(): void {
|
||||
window.open("https://github.com/BTMuli/TeyvatGuide");
|
||||
}
|
||||
|
||||
function toStore() {
|
||||
function toStore(): void {
|
||||
window.open("https://www.microsoft.com/store/productId/9NLBNNNBNSJN");
|
||||
}
|
||||
|
||||
function toSite() {
|
||||
function toSite(): void {
|
||||
window.open("https://app.btmuli.ink/docs/TeyvatGuide/changelogs.html");
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<v-list class="config-list">
|
||||
<v-list-subheader :inset="true" class="config-header" title="路径" />
|
||||
<v-divider :inset="true" class="border-opacity-75" />
|
||||
<v-list-item title="用户数据目录" :subtitle="appStore.userDir.value">
|
||||
<v-list-item title="用户数据目录" :subtitle="userDir">
|
||||
<template #prepend>
|
||||
<div class="config-icon">
|
||||
<v-icon>mdi-folder-key</v-icon>
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="应用数据库路径" :subtitle="appStore.dbPath.value">
|
||||
<v-list-item title="应用数据库路径" :subtitle="dbPath">
|
||||
<template #prepend>
|
||||
<div class="config-icon">
|
||||
<v-icon>mdi-folder-account</v-icon>
|
||||
@@ -29,11 +29,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
title="游戏安装目录"
|
||||
:subtitle="appStore.gameDir.value"
|
||||
v-if="platform() === 'windows'"
|
||||
>
|
||||
<v-list-item title="游戏安装目录" :subtitle="gameDir" v-if="platform() === 'windows'">
|
||||
<template #prepend>
|
||||
<div class="config-icon">
|
||||
<v-icon>mdi-gamepad</v-icon>
|
||||
@@ -47,7 +43,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="日志目录" :subtitle="appStore.logDir.value">
|
||||
<v-list-item title="日志目录" :subtitle="logDir">
|
||||
<template #prepend>
|
||||
<div class="config-icon">
|
||||
<v-icon>mdi-folder-multiple</v-icon>
|
||||
@@ -64,6 +60,9 @@
|
||||
</v-list>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TGSqlite from "@Sqlite/index.js";
|
||||
import { path } from "@tauri-apps/api";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { exists, readDir, remove } from "@tauri-apps/plugin-fs";
|
||||
@@ -71,45 +70,36 @@ import { platform } from "@tauri-apps/plugin-os";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
import TGSqlite from "../../plugins/Sqlite/index.js";
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { backUpUserData } from "../../utils/dataBS.js";
|
||||
import TGShell from "../../utils/TGShell.js";
|
||||
import showDialog from "../func/dialog.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
import { backUpUserData } from "@/utils/dataBS.js";
|
||||
import TGShell from "@/utils/TGShell.js";
|
||||
|
||||
const appStore = storeToRefs(useAppStore());
|
||||
const { dbPath, logDir, userDir, gameDir } = storeToRefs(useAppStore());
|
||||
|
||||
onMounted(async () => {
|
||||
const logDir = await path.appLogDir();
|
||||
const dbPath = `${await path.appConfigDir()}${path.sep()}TeyvatGuide.db`;
|
||||
const logDirGet = await path.appLogDir();
|
||||
const dbPathGet = `${await path.appConfigDir()}${path.sep()}TeyvatGuide.db`;
|
||||
let message = "";
|
||||
if (appStore.dbPath.value !== dbPath) {
|
||||
appStore.dbPath.value = dbPath;
|
||||
await TGSqlite.saveAppData("dbPath", dbPath);
|
||||
if (dbPath.value !== dbPathGet) {
|
||||
dbPath.value = dbPathGet;
|
||||
await TGSqlite.saveAppData("dbPath", dbPathGet);
|
||||
message += "数据库路径 ";
|
||||
}
|
||||
if (appStore.logDir.value !== logDir) {
|
||||
appStore.logDir.value = logDir;
|
||||
if (logDir.value !== logDirGet) {
|
||||
logDir.value = logDirGet;
|
||||
message += "日志路径 ";
|
||||
}
|
||||
if (message !== "") {
|
||||
showSnackbar.success(`${message}已更新!`);
|
||||
}
|
||||
if (message !== "") showSnackbar.success(`${message}已更新!`);
|
||||
});
|
||||
|
||||
async function confirmCUD(): Promise<void> {
|
||||
const oriDir = appStore.userDir.value;
|
||||
const oriDir = userDir.value;
|
||||
const changeCheck = await showDialog.check("确认修改用户数据路径吗?");
|
||||
if (!changeCheck) {
|
||||
showSnackbar.cancel("已取消修改");
|
||||
return;
|
||||
}
|
||||
const dir: string | null = await open({
|
||||
directory: true,
|
||||
defaultPath: oriDir,
|
||||
multiple: false,
|
||||
});
|
||||
const dir: string | null = await open({ directory: true, defaultPath: oriDir, multiple: false });
|
||||
if (dir === null) {
|
||||
showSnackbar.warn("路径不能为空!");
|
||||
return;
|
||||
@@ -118,7 +108,7 @@ async function confirmCUD(): Promise<void> {
|
||||
showSnackbar.warn("路径未修改!");
|
||||
return;
|
||||
}
|
||||
appStore.userDir.value = dir;
|
||||
userDir.value = dir;
|
||||
await TGSqlite.saveAppData("userDir", dir);
|
||||
await backUpUserData(dir);
|
||||
showSnackbar.success("已修改用户数据路径!");
|
||||
@@ -135,10 +125,10 @@ async function confirmCGD(): Promise<void> {
|
||||
showSnackbar.warn("不支持的平台!");
|
||||
return;
|
||||
}
|
||||
const oriEmpty = appStore.gameDir.value === "未设置";
|
||||
const oriEmpty = gameDir.value === "未设置";
|
||||
const editCheck = await showDialog.check(
|
||||
oriEmpty ? "确认设置游戏目录?" : "确认修改游戏目录?",
|
||||
oriEmpty ? "请选择启动器所在目录" : `当前:${appStore.gameDir.value}`,
|
||||
oriEmpty ? "请选择启动器所在目录" : `当前:${gameDir.value}`,
|
||||
);
|
||||
if (!editCheck) {
|
||||
showSnackbar.cancel(oriEmpty ? "已取消设置" : "已取消修改");
|
||||
@@ -146,14 +136,14 @@ async function confirmCGD(): Promise<void> {
|
||||
}
|
||||
const dir: string | null = await open({
|
||||
directory: true,
|
||||
defaultPath: oriEmpty ? undefined : appStore.gameDir.value,
|
||||
defaultPath: oriEmpty ? undefined : gameDir.value,
|
||||
multiple: false,
|
||||
});
|
||||
if (dir === null) {
|
||||
showSnackbar.warn("路径不能为空!");
|
||||
return;
|
||||
}
|
||||
if (!oriEmpty && appStore.gameDir.value === dir) {
|
||||
if (!oriEmpty && gameDir.value === dir) {
|
||||
showSnackbar.warn("路径未修改!");
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +152,7 @@ async function confirmCGD(): Promise<void> {
|
||||
showSnackbar.warn("未检测到游戏本体");
|
||||
return;
|
||||
}
|
||||
appStore.gameDir.value = dir;
|
||||
gameDir.value = dir;
|
||||
showSnackbar.success(oriEmpty ? "成功设置游戏目录" : "成功修改游戏目录");
|
||||
}
|
||||
|
||||
@@ -180,8 +170,7 @@ async function confirmCLD(): Promise<void> {
|
||||
showSnackbar.cancel("已取消清理");
|
||||
return;
|
||||
}
|
||||
const logDir = appStore.logDir.value;
|
||||
const files = await readDir(logDir);
|
||||
const files = await readDir(logDir.value);
|
||||
const delFiles = files.filter((file) => {
|
||||
// yyyy-mm-dd.log
|
||||
const reg = /(\d{4}-\d{2}-\d{2}\.log)/;
|
||||
@@ -205,23 +194,23 @@ function copyPath(type: "db" | "user" | "log" | "game"): void {
|
||||
let targetPath: string, targetName: string;
|
||||
switch (type) {
|
||||
case "db":
|
||||
targetPath = appStore.dbPath.value;
|
||||
targetPath = dbPath.value;
|
||||
targetName = "数据库";
|
||||
break;
|
||||
case "user":
|
||||
targetPath = appStore.userDir.value;
|
||||
targetPath = userDir.value;
|
||||
targetName = "用户数据";
|
||||
break;
|
||||
case "log":
|
||||
targetPath = appStore.logDir.value;
|
||||
targetPath = logDir.value;
|
||||
targetName = "日志";
|
||||
break;
|
||||
case "game":
|
||||
if (appStore.gameDir.value === "未设置") {
|
||||
if (gameDir.value === "未设置") {
|
||||
showSnackbar.warn("未设置游戏目录!");
|
||||
return;
|
||||
}
|
||||
targetPath = appStore.gameDir.value;
|
||||
targetPath = gameDir.value;
|
||||
targetName = "游戏安装目录";
|
||||
}
|
||||
navigator.clipboard.writeText(targetPath);
|
||||
@@ -235,17 +224,17 @@ async function openPath(type: "db" | "user" | "log" | "game"): Promise<void> {
|
||||
targetPath = await path.appConfigDir();
|
||||
break;
|
||||
case "user":
|
||||
targetPath = appStore.userDir.value;
|
||||
targetPath = userDir.value;
|
||||
break;
|
||||
case "log":
|
||||
targetPath = appStore.logDir.value;
|
||||
targetPath = logDir.value;
|
||||
break;
|
||||
case "game":
|
||||
if (appStore.gameDir.value === "未设置") {
|
||||
if (gameDir.value === "未设置") {
|
||||
showSnackbar.warn("未设置游戏目录!");
|
||||
return;
|
||||
}
|
||||
targetPath = appStore.gameDir.value;
|
||||
targetPath = gameDir.value;
|
||||
break;
|
||||
}
|
||||
await TGShell.openPath(targetPath);
|
||||
|
||||
@@ -16,24 +16,22 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { path } from "@tauri-apps/api";
|
||||
import { exists } from "@tauri-apps/plugin-fs";
|
||||
import { Command } from "@tauri-apps/plugin-shell";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import PassportApi from "../../web/request/passportReq.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
import PassportApi from "@/web/request/passportReq.js";
|
||||
|
||||
const userStore = storeToRefs(useUserStore());
|
||||
const appStore = storeToRefs(useAppStore());
|
||||
const account = computed<TGApp.Sqlite.Account.Game>(() => userStore.account.value);
|
||||
const { gameDir } = storeToRefs(useAppStore());
|
||||
const { account, uid, cookie } = storeToRefs(useUserStore());
|
||||
|
||||
async function tryPlayGame(): Promise<void> {
|
||||
if (!userStore.uid.value || !userStore.cookie.value) {
|
||||
if (!uid.value || !cookie.value) {
|
||||
showSnackbar.warn("请先登录!");
|
||||
return;
|
||||
}
|
||||
@@ -41,16 +39,16 @@ async function tryPlayGame(): Promise<void> {
|
||||
showSnackbar.warn("仅支持官服用户启动!");
|
||||
return;
|
||||
}
|
||||
if (appStore.gameDir.value === "未设置") {
|
||||
if (gameDir.value === "未设置") {
|
||||
showSnackbar.warn("未设置游戏安装目录!");
|
||||
return;
|
||||
}
|
||||
const gamePath = `${appStore.gameDir.value}${path.sep()}YuanShen.exe`;
|
||||
const gamePath = `${gameDir.value}${path.sep()}YuanShen.exe`;
|
||||
if (!(await exists(gamePath))) {
|
||||
showSnackbar.warn("未检测到原神本体应用!");
|
||||
return;
|
||||
}
|
||||
const resp = await PassportApi.authTicket(account.value, userStore.cookie.value);
|
||||
const resp = await PassportApi.authTicket(account.value, cookie.value);
|
||||
if (typeof resp !== "string") {
|
||||
showSnackbar.error(`[${resp.retcode}] ${resp.message}`);
|
||||
await TGLogger.Error(
|
||||
@@ -61,7 +59,7 @@ async function tryPlayGame(): Promise<void> {
|
||||
}
|
||||
showSnackbar.success(`成功获取ticket:${resp},正在启动应用...`);
|
||||
const cmd = Command.create("exec-sh", [`&"${gamePath}" login_auth_ticket=${resp}`], {
|
||||
cwd: appStore.gameDir.value,
|
||||
cwd: gameDir.value,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
console.log(cmd);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</v-list-item>
|
||||
<v-list-item title="成就版本">
|
||||
<template #prepend>
|
||||
<img class="config-icon" src="../../assets/icons/achievements.svg" alt="Achievements" />
|
||||
<img class="config-icon" src="@/assets/icons/achievements.svg" alt="Achievements" />
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ latestAchiVersion }}</v-list-item-subtitle>
|
||||
@@ -45,13 +45,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle
|
||||
>{{ dbInfo.find((item) => item.key === "dataUpdated")?.value }}
|
||||
<v-list-item-subtitle>
|
||||
{{ dbInfo.find((item) => item.key === "dataUpdated")?.value }}
|
||||
</v-list-item-subtitle>
|
||||
</template>
|
||||
<v-list-item-subtitle
|
||||
>更新于
|
||||
{{ dbInfo.find((item) => item.key === "dataUpdated")?.updated }}
|
||||
<v-list-item-subtitle>
|
||||
<span>更新于</span>
|
||||
<span>{{ dbInfo.find((item) => item.key === "dataUpdated")?.updated }}</span>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item title="数据库版本">
|
||||
@@ -61,41 +61,40 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle
|
||||
>{{ dbInfo.find((item) => item.key === "appVersion")?.value }}
|
||||
<v-list-item-subtitle>
|
||||
{{ dbInfo.find((item) => item.key === "appVersion")?.value }}
|
||||
</v-list-item-subtitle>
|
||||
</template>
|
||||
<v-list-item-subtitle
|
||||
>更新于
|
||||
{{ dbInfo.find((item) => item.key === "appVersion")?.updated }}
|
||||
<v-list-item-subtitle>
|
||||
<span>更新于</span>
|
||||
<span>{{ dbInfo.find((item) => item.key === "appVersion")?.updated }}</span>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TGSqlite from "@Sqlite/index.js";
|
||||
import TSUserAchi from "@Sqlite/modules/userAchi.js";
|
||||
import { app } from "@tauri-apps/api";
|
||||
import { platform, version } from "@tauri-apps/plugin-os";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, shallowRef } from "vue";
|
||||
|
||||
import TGSqlite from "../../plugins/Sqlite/index.js";
|
||||
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
|
||||
const latestAchiVersion = TSUserAchi.getLatestAchiVersion();
|
||||
const osPlatform = platform();
|
||||
const osVersion = version();
|
||||
|
||||
const versionApp = ref<string>("");
|
||||
const versionTauri = ref<string>("");
|
||||
const osPlatform = ref<string>("");
|
||||
const iconPlatform = ref<string>("mdi-microsoft-windows");
|
||||
const osVersion = ref<string>("");
|
||||
const dbInfo = ref<Array<TGApp.Sqlite.AppData.Item>>([]);
|
||||
const dbInfo = shallowRef<Array<TGApp.Sqlite.AppData.Item>>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
versionApp.value = await app.getVersion();
|
||||
versionTauri.value = await app.getTauriVersion();
|
||||
osPlatform.value = platform();
|
||||
switch (osPlatform.value) {
|
||||
switch (osPlatform) {
|
||||
case "linux":
|
||||
iconPlatform.value = "mdi-linux";
|
||||
break;
|
||||
@@ -112,16 +111,20 @@ onMounted(async () => {
|
||||
iconPlatform.value = "mdi-desktop-classic";
|
||||
break;
|
||||
}
|
||||
osVersion.value = version();
|
||||
try {
|
||||
dbInfo.value = await TGSqlite.getAppData();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
showSnackbar.warn(`加载数据库错误: ${e.message}`);
|
||||
await TGLogger.Error(`加载数据库错误: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
showSnackbar.warn("加载数据库错误,请重置数据库!");
|
||||
await TGLogger.Error(`加载数据库错误: ${e}`);
|
||||
}
|
||||
});
|
||||
|
||||
function toOuter(url: string) {
|
||||
function toOuter(url: string): void {
|
||||
window.open(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -19,16 +19,14 @@
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="account in gameAccounts"
|
||||
:key="account.gameUid"
|
||||
@click="useUserStore().switchGameAccount(account.gameUid)"
|
||||
v-for="ac in gameAccounts"
|
||||
:key="ac.gameUid"
|
||||
@click="useUserStore().switchGameAccount(ac.gameUid)"
|
||||
>
|
||||
<v-list-item-title>{{ account.nickname }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ account.gameUid }}({{ account.regionName }})
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-title>{{ ac.nickname }}</v-list-item-title>
|
||||
<v-list-item-subtitle> {{ ac.gameUid }}({{ ac.regionName }}) </v-list-item-subtitle>
|
||||
<template #append>
|
||||
<div v-if="account.gameUid === userStore.account.value.gameUid" title="当前登录账号">
|
||||
<div v-if="ac.gameUid === account.gameUid" title="当前登录账号">
|
||||
<v-icon color="green">mdi-check</v-icon>
|
||||
</div>
|
||||
</template>
|
||||
@@ -46,15 +44,15 @@
|
||||
/>
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
@click="confirmRefreshUser(userStore.uid.value!)"
|
||||
:disabled="userStore.uid.value === undefined"
|
||||
@click="confirmRefreshUser(uid!)"
|
||||
:disabled="uid === undefined"
|
||||
icon="mdi-refresh"
|
||||
title="刷新用户信息"
|
||||
/>
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
@click="confirmCopyCookie"
|
||||
:disabled="!userStore.cookie.value"
|
||||
:disabled="cookie === undefined"
|
||||
icon="mdi-cookie"
|
||||
title="复制Cookie"
|
||||
/>
|
||||
@@ -69,11 +67,11 @@
|
||||
/>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item v-for="account in accounts" :key="account.uid">
|
||||
<v-list-item-title>{{ account.brief.nickname }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ account.brief.uid }}</v-list-item-subtitle>
|
||||
<v-list-item v-for="ac in accounts" :key="ac.uid">
|
||||
<v-list-item-title>{{ ac.brief.nickname }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ ac.brief.uid }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
<div v-if="account.uid === userStore.uid.value" title="当前登录账号">
|
||||
<div v-if="ac.uid === uid" title="当前登录账号">
|
||||
<v-icon color="green">mdi-account-check</v-icon>
|
||||
</div>
|
||||
<v-icon
|
||||
@@ -81,14 +79,14 @@
|
||||
size="small"
|
||||
icon="mdi-account-convert"
|
||||
title="切换用户"
|
||||
@click="loadAccount(account.uid)"
|
||||
@click="loadAccount(ac.uid)"
|
||||
/>
|
||||
<v-icon
|
||||
class="tcu-btn"
|
||||
icon="mdi-delete"
|
||||
title="删除用户"
|
||||
size="small"
|
||||
@click="clearUser(account)"
|
||||
@click="clearUser(ac)"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
@@ -102,36 +100,35 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showGeetest from "@comp/func/geetest.js";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import Mys from "@Mys/index.js";
|
||||
import TSUserAccount from "@Sqlite/modules/userAccount.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, shallowRef } from "vue";
|
||||
|
||||
import Mys from "../../plugins/Mys/index.js";
|
||||
import TSUserAccount from "../../plugins/Sqlite/modules/userAccount.js";
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import BBSApi from "../../web/request/bbsReq.js";
|
||||
import PassportApi from "../../web/request/passportReq.js";
|
||||
import TakumiApi from "../../web/request/takumiReq.js";
|
||||
import showDialog from "../func/dialog.js";
|
||||
import showGeetest from "../func/geetest.js";
|
||||
import showLoading from "../func/loading.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
import BBSApi from "@/web/request/bbsReq.js";
|
||||
import PassportApi from "@/web/request/passportReq.js";
|
||||
import TakumiApi from "@/web/request/takumiReq.js";
|
||||
|
||||
const userStore = storeToRefs(useUserStore());
|
||||
const appStore = storeToRefs(useAppStore());
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
const { uid, briefInfo, cookie, account } = storeToRefs(useUserStore());
|
||||
|
||||
const accounts = ref<TGApp.App.Account.User[]>([]);
|
||||
const gameAccounts = ref<TGApp.Sqlite.Account.Game[]>([]);
|
||||
const accounts = shallowRef<Array<TGApp.App.Account.User>>([]);
|
||||
const gameAccounts = shallowRef<Array<TGApp.Sqlite.Account.Game>>([]);
|
||||
const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
|
||||
if (userStore.uid.value === undefined)
|
||||
return {
|
||||
nickname: "未登录",
|
||||
uid: "-1",
|
||||
desc: "请使用短信验证码登录",
|
||||
avatar: "/source/UI/lumine.webp",
|
||||
};
|
||||
return userStore.briefInfo.value;
|
||||
if (uid.value) return briefInfo.value;
|
||||
return {
|
||||
nickname: "未登录",
|
||||
uid: "-1",
|
||||
desc: "请使用短信验证码登录",
|
||||
avatar: "/source/UI/lumine.webp",
|
||||
};
|
||||
});
|
||||
|
||||
async function tryCaptchaLogin(): Promise<void> {
|
||||
@@ -196,7 +193,7 @@ async function tryCaptchaLogin(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
showSnackbar.success("获取用户信息成功");
|
||||
const briefInfo: TGApp.App.Account.BriefInfo = {
|
||||
const briefInfoGet: TGApp.App.Account.BriefInfo = {
|
||||
nickname: briefRes.nickname,
|
||||
uid: briefRes.uid,
|
||||
avatar: briefRes.avatar_url,
|
||||
@@ -204,17 +201,17 @@ async function tryCaptchaLogin(): Promise<void> {
|
||||
};
|
||||
showLoading.update("正在登录...", "正在保存用户数据");
|
||||
await TSUserAccount.account.saveAccount({
|
||||
uid: briefInfo.uid,
|
||||
uid: briefInfoGet.uid,
|
||||
cookie: ck,
|
||||
brief: briefInfo,
|
||||
brief: briefInfoGet,
|
||||
updated: "",
|
||||
});
|
||||
userStore.uid.value = briefInfo.uid;
|
||||
userStore.briefInfo.value = briefInfo;
|
||||
userStore.cookie.value = ck;
|
||||
appStore.isLogin.value = true;
|
||||
uid.value = briefInfoGet.uid;
|
||||
briefInfo.value = briefInfoGet;
|
||||
cookie.value = ck;
|
||||
isLogin.value = true;
|
||||
showLoading.update("正在登录...", "正在获取游戏账号");
|
||||
const gameRes = await TakumiApi.bind.gameRoles(userStore.cookie.value);
|
||||
const gameRes = await TakumiApi.bind.gameRoles(cookie.value);
|
||||
if (!Array.isArray(gameRes)) {
|
||||
showLoading.end();
|
||||
showSnackbar.error(`[${gameRes.retcode}]${gameRes.message}`);
|
||||
@@ -222,14 +219,14 @@ async function tryCaptchaLogin(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
showSnackbar.success("获取游戏账号成功");
|
||||
await TSUserAccount.game.saveAccounts(briefInfo.uid, gameRes);
|
||||
const curAccount = await TSUserAccount.game.getCurAccount(briefInfo.uid);
|
||||
await TSUserAccount.game.saveAccounts(briefInfoGet.uid, gameRes);
|
||||
const curAccount = await TSUserAccount.game.getCurAccount(briefInfoGet.uid);
|
||||
if (!curAccount) {
|
||||
showSnackbar.warn("未检测到游戏账号,请重新刷新");
|
||||
showLoading.end();
|
||||
return;
|
||||
}
|
||||
userStore.account.value = curAccount;
|
||||
account.value = curAccount;
|
||||
showLoading.end();
|
||||
showSnackbar.success("成功登录!");
|
||||
}
|
||||
@@ -318,46 +315,46 @@ async function refreshUser(uid: string) {
|
||||
showLoading.end();
|
||||
}
|
||||
|
||||
async function loadAccount(uid: string): Promise<void> {
|
||||
if (userStore.uid.value && uid === userStore.uid.value) {
|
||||
async function loadAccount(ac: string): Promise<void> {
|
||||
if (uid.value && ac === uid.value) {
|
||||
showSnackbar.warn("该账户已经登录,无需切换");
|
||||
return;
|
||||
}
|
||||
const account = await TSUserAccount.account.getAccount(uid);
|
||||
if (!account) {
|
||||
const accountGet = await TSUserAccount.account.getAccount(ac);
|
||||
if (!accountGet) {
|
||||
showSnackbar.warn(`未找到${uid}的账号信息,请重新登录`);
|
||||
return;
|
||||
}
|
||||
userStore.uid.value = uid;
|
||||
userStore.briefInfo.value = account.brief;
|
||||
userStore.cookie.value = account.cookie;
|
||||
const gameAccount = await TSUserAccount.game.getCurAccount(uid);
|
||||
uid.value = ac;
|
||||
briefInfo.value = accountGet.brief;
|
||||
cookie.value = accountGet.cookie;
|
||||
const gameAccount = await TSUserAccount.game.getCurAccount(ac);
|
||||
if (!gameAccount) {
|
||||
showSnackbar.warn(`未找到${uid}的游戏账号信息,请尝试刷新`);
|
||||
return;
|
||||
}
|
||||
userStore.account.value = gameAccount;
|
||||
account.value = gameAccount;
|
||||
showSnackbar.success(`成功切换到用户${uid}`);
|
||||
}
|
||||
|
||||
async function confirmRefreshUser(uid: string): Promise<void> {
|
||||
async function confirmRefreshUser(ac: string): Promise<void> {
|
||||
const freshCheck = await showDialog.check("确认刷新用户信息吗?", "将会重新获取用户信息");
|
||||
if (!freshCheck) {
|
||||
showSnackbar.cancel("已取消刷新用户信息");
|
||||
return;
|
||||
}
|
||||
await refreshUser(uid);
|
||||
if (userStore.uid.value === uid) {
|
||||
await refreshUser(ac);
|
||||
if (uid.value === ac) {
|
||||
showSnackbar.success("成功刷新用户信息");
|
||||
return;
|
||||
}
|
||||
const switchCheck = await showDialog.check("是否切换用户?", `将切换到用户${uid}`);
|
||||
const switchCheck = await showDialog.check("是否切换用户?", `将切换到用户${ac}`);
|
||||
if (!switchCheck) return;
|
||||
await loadAccount(uid);
|
||||
await loadAccount(ac);
|
||||
}
|
||||
|
||||
async function confirmCopyCookie(): Promise<void> {
|
||||
if (!userStore.cookie.value) {
|
||||
if (!cookie.value) {
|
||||
showSnackbar.warn("请先登录");
|
||||
return;
|
||||
}
|
||||
@@ -366,7 +363,7 @@ async function confirmCopyCookie(): Promise<void> {
|
||||
showSnackbar.cancel("已取消复制 Cookie");
|
||||
return;
|
||||
}
|
||||
const ckText = TSUserAccount.account.copy(userStore.cookie.value);
|
||||
const ckText = TSUserAccount.account.copy(cookie.value);
|
||||
await navigator.clipboard.writeText(ckText);
|
||||
showSnackbar.success("已复制 Cookie!");
|
||||
}
|
||||
@@ -411,11 +408,11 @@ async function showMenu(): Promise<void> {
|
||||
}
|
||||
|
||||
async function showAccounts(): Promise<void> {
|
||||
if (!userStore.uid.value) {
|
||||
if (!uid.value) {
|
||||
showSnackbar.warn("未登录!");
|
||||
return;
|
||||
}
|
||||
gameAccounts.value = await TSUserAccount.game.getAccount(userStore.uid.value);
|
||||
gameAccounts.value = await TSUserAccount.game.getAccount(uid.value);
|
||||
if (gameAccounts.value.length === 0) {
|
||||
showSnackbar.warn("未找到账户的游戏数据,请尝试刷新!");
|
||||
return;
|
||||
@@ -515,7 +512,7 @@ async function addByCookie(): Promise<void> {
|
||||
}
|
||||
|
||||
async function clearUser(user: TGApp.App.Account.User): Promise<void> {
|
||||
if (user.uid === userStore.uid.value) {
|
||||
if (user.uid === uid.value) {
|
||||
showSnackbar.warn("当前登录用户不许删除!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,17 +28,18 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from "vue";
|
||||
import TSAvatarBirth from "@Sqlite/modules/avatarBirth.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onBeforeMount, ref, shallowRef } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import TSAvatarBirth from "../../plugins/Sqlite/modules/avatarBirth.js";
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { useAppStore } from "@/store/modules/app.js";
|
||||
|
||||
const isBirthday = ref<boolean>(false);
|
||||
const router = useRouter();
|
||||
const cur = ref<TGApp.Archive.Birth.CalendarItem[]>([]);
|
||||
const next = ref<TGApp.Archive.Birth.RoleItem[]>([]);
|
||||
const appStore = useAppStore();
|
||||
const { recentNewsType } = storeToRefs(useAppStore());
|
||||
const isBirthday = ref<boolean>(false);
|
||||
const cur = shallowRef<Array<TGApp.Archive.Birth.CalendarItem>>([]);
|
||||
const next = shallowRef<Array<TGApp.Archive.Birth.RoleItem>>([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const check = TSAvatarBirth.isAvatarBirth();
|
||||
@@ -64,7 +65,7 @@ function toBirth(type: TGApp.Archive.Birth.RoleItem | true): void {
|
||||
return;
|
||||
}
|
||||
if (cur.value.length > 0 && !cur.value[0].is_subscribe) {
|
||||
appStore.recentNewsType = "news";
|
||||
recentNewsType.value = "news";
|
||||
router.push("/news/2/news");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
<template>
|
||||
<div class="tcm-box">
|
||||
<div class="tcm-left">
|
||||
<div class="tcm-bg"><img :src="props.item.bg" alt="bg" /></div>
|
||||
<div class="tcm-icon"><img :src="props.item.icon" alt="icon" /></div>
|
||||
<div class="tcm-star" v-if="props.item.star !== 0">
|
||||
<img :src="props.item.starIcon" alt="element" />
|
||||
<div class="tcm-bg"><img :src="item.bg" alt="bg" /></div>
|
||||
<div class="tcm-icon"><img :src="item.icon" alt="icon" /></div>
|
||||
<div class="tcm-star" v-if="item.star !== 0">
|
||||
<img :src="item.starIcon" alt="element" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tcm-right">{{ props.item.name }}</div>
|
||||
<div class="tcm-right">{{ item.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TMiniWeaponProps {
|
||||
item: TGApp.App.Calendar.Material;
|
||||
}
|
||||
|
||||
const props = defineProps<TMiniWeaponProps>();
|
||||
defineProps<{ item: TGApp.App.Calendar.Material }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tcm-box {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<TItemBox :model-value="boxData" />
|
||||
<div class="toc-material-grid">
|
||||
<TibCalendarMaterial
|
||||
v-for="(item, index) in itemVal.materials"
|
||||
v-for="(item, index) in props.dataVal.materials"
|
||||
:key="index"
|
||||
:item="item"
|
||||
/>
|
||||
@@ -16,28 +16,29 @@
|
||||
<div class="toc-bottom">
|
||||
<div class="toc-src-box">
|
||||
<div class="toc-src-text">来源:</div>
|
||||
<img :src="`/icon/nation/${itemVal.source.area}.webp`" alt="icon" />
|
||||
<div class="toc-src-text">{{ itemVal.source.area }} - {{ itemVal.source.name }}</div>
|
||||
<img :src="`/icon/nation/${props.dataVal.source.area}.webp`" alt="icon" />
|
||||
<div class="toc-src-text">
|
||||
{{ props.dataVal.source.area }} - {{ props.dataVal.source.name }}
|
||||
</div>
|
||||
</div>
|
||||
<v-btn variant="outlined" @click="toDetail(itemVal)">详情</v-btn>
|
||||
<v-btn variant="outlined" @click="toDetail(props.dataVal)">详情</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import TItemBox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import TibCalendarMaterial from "./ph-calendar-material.vue";
|
||||
|
||||
type ToCalendarProps = {
|
||||
modelValue: boolean;
|
||||
dataType: "weapon" | "avatar";
|
||||
dataType: "weapon" | "character";
|
||||
dataVal: TGApp.App.Calendar.Item;
|
||||
};
|
||||
type ToCalendarEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
@@ -49,22 +50,19 @@ const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const itemVal = computed<TGApp.App.Calendar.Item>(() => props.dataVal);
|
||||
const boxData = computed<TItemBoxData>(() => {
|
||||
return {
|
||||
bg: itemVal.value.bg,
|
||||
icon: itemVal.value.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: false,
|
||||
lt: props.dataType === "avatar" ? (itemVal.value.elementIcon ?? "") : itemVal.value.weaponIcon,
|
||||
ltSize: "20px",
|
||||
innerHeight: 25,
|
||||
innerIcon: props.dataType === "avatar" ? itemVal.value.weaponIcon : undefined,
|
||||
innerText: itemVal.value.name,
|
||||
};
|
||||
});
|
||||
const boxData = computed<TItemBoxData>(() => ({
|
||||
bg: props.dataVal.bg,
|
||||
icon: props.dataVal.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: false,
|
||||
lt: props.dataType === "character" ? (props.dataVal.elementIcon ?? "") : props.dataVal.weaponIcon,
|
||||
ltSize: "20px",
|
||||
innerHeight: 25,
|
||||
innerIcon: props.dataType === "character" ? props.dataVal.weaponIcon : undefined,
|
||||
innerText: props.dataVal.name,
|
||||
}));
|
||||
|
||||
async function toDetail(item: TGApp.App.Calendar.Item): Promise<void> {
|
||||
if (!["character", "weapon"].includes(item.itemType)) {
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
<THomeCard append>
|
||||
<template #title>今日素材 {{ dateNow }}</template>
|
||||
<template #title-append>
|
||||
<v-switch
|
||||
class="tc-switch"
|
||||
@change="switchType = switchType === 'avatar' ? 'weapon' : 'avatar'"
|
||||
/>
|
||||
{{ switchType === "avatar" ? "角色" : "武器" }}
|
||||
<v-switch class="tc-switch" @change="switchType()" />
|
||||
<span>{{ selectedType === "character" ? "角色" : "武器" }}</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="tc-top">
|
||||
@@ -15,22 +12,9 @@
|
||||
v-for="text of btnText"
|
||||
:key="text.week"
|
||||
rounded
|
||||
:style="{
|
||||
border: text.week === weekNow ? '1px solid var(--tgc-yellow-1)' : 'none',
|
||||
backgroundColor:
|
||||
text.week === btnNow
|
||||
? 'var(--tgc-yellow-1)'
|
||||
: text.week === weekNow
|
||||
? 'transparent'
|
||||
: 'var(--tgc-btn-1)',
|
||||
color:
|
||||
text.week === btnNow
|
||||
? 'var(--box-text-4)'
|
||||
: text.week === weekNow
|
||||
? 'var(--tgc-yellow-1)'
|
||||
: 'var(--btn-text)',
|
||||
}"
|
||||
@click="getContents(text.week)"
|
||||
class="tc-btn"
|
||||
:class="{ selected: text.week === btnNow, today: text.week === weekNow }"
|
||||
@click="btnNow = text.week"
|
||||
>
|
||||
{{ text.text }}
|
||||
</v-btn>
|
||||
@@ -53,38 +37,19 @@
|
||||
<ToCalendar v-model="showItem" :data-type="selectedType" :data-val="selectedItem" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
import { AppCalendarData } from "../../data/index.js";
|
||||
import { timestampToDate } from "../../utils/toolFunc.js";
|
||||
import TItemBox, { type TItemBoxData } from "../app/t-item-box.vue";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import { computed, onMounted, ref, shallowRef } from "vue";
|
||||
|
||||
import TCalendarBirth from "./ph-calendar-birth.vue";
|
||||
import ToCalendar from "./ph-calendar-overlay.vue";
|
||||
import THomeCard from "./ph-comp-card.vue";
|
||||
|
||||
const weekNow = ref<number>(0);
|
||||
const btnNow = ref<number>(0);
|
||||
const dateNow = ref<string>("");
|
||||
import { AppCalendarData } from "@/data/index.js";
|
||||
import { timestampToDate } from "@/utils/toolFunc.js";
|
||||
|
||||
// page
|
||||
const page = ref<number>(1);
|
||||
const length = ref<number>(0);
|
||||
const visible = 16;
|
||||
|
||||
// calendar
|
||||
const calendarNow = ref<TGApp.App.Calendar.Item[]>([]);
|
||||
const characterCards = ref<TGApp.App.Calendar.Item[]>([]);
|
||||
const weaponCards = ref<TGApp.App.Calendar.Item[]>([]);
|
||||
|
||||
// calendar item
|
||||
const showItem = ref<boolean>(false);
|
||||
const switchType = ref<"avatar" | "weapon">("avatar");
|
||||
const selectedItem = ref<TGApp.App.Calendar.Item>(<TGApp.App.Calendar.Item>{});
|
||||
const selectedType = ref<"avatar" | "weapon">("avatar");
|
||||
const renderItems = ref<TGApp.App.Calendar.Item[]>([]);
|
||||
|
||||
const btnText = [
|
||||
type BtnItem = { week: 1 | 2 | 3 | 4 | 5 | 6 | 7; text: string };
|
||||
type TCalendarEmits = (e: "success") => void;
|
||||
const btnText: Array<BtnItem> = [
|
||||
{ week: 7, text: "周日" },
|
||||
{ week: 1, text: "周一" },
|
||||
{ week: 2, text: "周二" },
|
||||
@@ -93,98 +58,59 @@ const btnText = [
|
||||
{ week: 5, text: "周五" },
|
||||
{ week: 6, text: "周六" },
|
||||
];
|
||||
|
||||
interface TCalendarEmits {
|
||||
(e: "success"): void;
|
||||
}
|
||||
|
||||
const emits = defineEmits<TCalendarEmits>();
|
||||
const visible = 16;
|
||||
|
||||
onMounted(async () => {
|
||||
const weekNow = ref<number>(0);
|
||||
const btnNow = ref<number>(0);
|
||||
const dateNow = ref<string>("");
|
||||
const page = ref<number>(1);
|
||||
const showItem = ref<boolean>(false);
|
||||
const selectedType = ref<"character" | "weapon">("character");
|
||||
const calendarTotal = computed<Array<TGApp.App.Calendar.Item>>(() =>
|
||||
AppCalendarData.filter(
|
||||
(i) => i.dropDays.includes(btnNow.value) && i.itemType === selectedType.value,
|
||||
),
|
||||
);
|
||||
const length = computed<number>(() => Math.ceil(calendarTotal.value.length / visible));
|
||||
const renderItems = computed<Array<TGApp.App.Calendar.Item>>(() =>
|
||||
calendarTotal.value.slice((page.value - 1) * visible, page.value * visible),
|
||||
);
|
||||
const selectedItem = shallowRef<TGApp.App.Calendar.Item>(renderItems.value[0]);
|
||||
|
||||
onMounted(() => {
|
||||
const dayNow = new Date().getDay() === 0 ? 7 : new Date().getDay();
|
||||
const week = <{ week: number; text: string }>btnText.find((item) => item.week === dayNow);
|
||||
const week = btnText.find((item) => item.week === dayNow) ?? { text: "周日", week: 7 };
|
||||
dateNow.value = `${timestampToDate(new Date().getTime())} ${week.text}`;
|
||||
weekNow.value = dayNow;
|
||||
btnNow.value = dayNow;
|
||||
calendarNow.value = getCalendar(dayNow);
|
||||
characterCards.value = calendarNow.value.filter((item) => item.itemType === "character");
|
||||
weaponCards.value = calendarNow.value.filter((item) => item.itemType === "weapon");
|
||||
renderItems.value = getGrid();
|
||||
emits("success");
|
||||
});
|
||||
|
||||
watch(
|
||||
() => page.value,
|
||||
() => {
|
||||
renderItems.value = getGrid();
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => switchType.value,
|
||||
() => {
|
||||
if (page.value !== 1) page.value = 1;
|
||||
else renderItems.value = getGrid();
|
||||
},
|
||||
);
|
||||
|
||||
// 获取当前日历
|
||||
function getCalendar(day: number): TGApp.App.Calendar.Item[] {
|
||||
return AppCalendarData.filter((item) => item.dropDays.includes(day));
|
||||
}
|
||||
|
||||
function getGrid(): TGApp.App.Calendar.Item[] {
|
||||
let selectedCards: TGApp.App.Calendar.Item[];
|
||||
if (switchType.value === "avatar") selectedCards = characterCards.value;
|
||||
else selectedCards = weaponCards.value;
|
||||
length.value = Math.ceil(selectedCards.length / visible);
|
||||
return selectedCards.slice((page.value - 1) * visible, page.value * visible);
|
||||
function switchType(): void {
|
||||
selectedType.value = selectedType.value === "character" ? "weapon" : "character";
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function selectItem(item: TGApp.App.Calendar.Item): void {
|
||||
selectedItem.value = item;
|
||||
selectedType.value = switchType.value;
|
||||
showItem.value = true;
|
||||
}
|
||||
|
||||
function getContents(day: number): void {
|
||||
btnNow.value = day;
|
||||
calendarNow.value = getCalendar(day);
|
||||
characterCards.value = calendarNow.value.filter((item) => item.itemType === "character");
|
||||
weaponCards.value = calendarNow.value.filter((item) => item.itemType === "weapon");
|
||||
if (page.value !== 1) page.value = 1;
|
||||
else renderItems.value = getGrid();
|
||||
}
|
||||
|
||||
function getBoxData(item: TGApp.App.Calendar.Item): TItemBoxData {
|
||||
if (switchType.value === "avatar") {
|
||||
return {
|
||||
bg: item.bg,
|
||||
icon: item.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
lt: item.elementIcon ?? "",
|
||||
ltSize: "20px",
|
||||
innerHeight: 25,
|
||||
innerIcon: item.weaponIcon,
|
||||
innerText: item.name,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
bg: item.bg,
|
||||
icon: item.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
lt: item.weaponIcon,
|
||||
ltSize: "20px",
|
||||
innerHeight: 25,
|
||||
innerText: item.name,
|
||||
};
|
||||
}
|
||||
return {
|
||||
bg: item.bg,
|
||||
icon: item.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
lt: selectedType.value === "weapon" ? item.weaponIcon : (item.elementIcon ?? ""),
|
||||
ltSize: "20px",
|
||||
innerHeight: 25,
|
||||
innerIcon: selectedType.value === "character" ? item.weaponIcon : undefined,
|
||||
innerText: item.name,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
@@ -212,6 +138,25 @@ function getBoxData(item: TGApp.App.Calendar.Item): TItemBoxData {
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
.tc-btn {
|
||||
background: var(--tgc-btn-1);
|
||||
color: var(--btn-text);
|
||||
|
||||
&.today {
|
||||
border: 1px solid var(--tgc-yellow-1);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--tgc-yellow-1);
|
||||
color: var(--box-text-4);
|
||||
}
|
||||
|
||||
&.today:not(.selected) {
|
||||
background-color: transparent;
|
||||
color: var(--tgc-yellow-1);
|
||||
}
|
||||
}
|
||||
|
||||
.tc-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="thc-title">
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
<div v-if="props.append" class="thc-append">
|
||||
<div v-if="append" class="thc-append">
|
||||
<slot name="title-append"></slot>
|
||||
</div>
|
||||
<div class="thc-box">
|
||||
@@ -12,8 +12,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
type THomeCardProps = { append?: boolean };
|
||||
const props = defineProps<THomeCardProps>();
|
||||
defineProps<{ append?: boolean }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.thc-container {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<THomeCard :append="hasNew">
|
||||
<template #title>限时祈愿</template>
|
||||
<template #title-append>
|
||||
<v-switch class="pool-switch" @change="switchPool" />
|
||||
{{ showNew ? "查看当前祈愿" : "查看后续祈愿" }}
|
||||
<v-switch class="pool-switch" @change="showNew = !showNew" />
|
||||
<span>{{ showNew ? "查看当前祈愿" : "查看后续祈愿" }}</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="pool-grid">
|
||||
@@ -30,20 +30,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="pool-time">
|
||||
<div>
|
||||
<div class="left">
|
||||
<v-icon>mdi-calendar-clock</v-icon>
|
||||
{{ pool.time.str }}
|
||||
<span>{{ pool.time.str }}</span>
|
||||
</div>
|
||||
<v-progress-linear :model-value="poolTimePass[pool.postId]" :rounded="true">
|
||||
</v-progress-linear>
|
||||
<div v-if="poolTimeGet[pool.postId] === '已结束'">
|
||||
{{ poolTimeGet[pool.postId] }}
|
||||
<v-progress-linear
|
||||
:model-value="
|
||||
pool.stat !== 'now' ? 100 : (pool.timeRest * 100) / pool.time.totalStamp
|
||||
"
|
||||
:rounded="true"
|
||||
/>
|
||||
<div v-if="pool.stat !== 'now'" class="time">
|
||||
{{ pool.stat === "future" ? "未开始" : "已结束" }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-else class="time">
|
||||
<span>剩余时间:</span>
|
||||
<span>
|
||||
{{ poolTimeGet[pool.postId] }}
|
||||
</span>
|
||||
<span>{{ stamp2LastTime(pool.timeRest) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,122 +55,95 @@
|
||||
</THomeCard>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TItembox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import Mys from "@Mys/index.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import Mys from "../../plugins/Mys/index.js";
|
||||
import { useHomeStore } from "../../store/modules/home.js";
|
||||
import { createPost, createTGWindow } from "../../utils/TGWindow.js";
|
||||
import { stamp2LastTime } from "../../utils/toolFunc.js";
|
||||
import TItembox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import THomeCard from "./ph-comp-card.vue";
|
||||
|
||||
// store
|
||||
const homeStore = storeToRefs(useHomeStore());
|
||||
import { useHomeStore } from "@/store/modules/home.js";
|
||||
import { createPost, createTGWindow } from "@/utils/TGWindow.js";
|
||||
import { stamp2LastTime } from "@/utils/toolFunc.js";
|
||||
|
||||
const router = useRouter();
|
||||
const hasNew = ref<boolean>(false);
|
||||
const showNew = ref<boolean>(false);
|
||||
|
||||
// data
|
||||
const poolCards = ref<TGApp.Plugins.Mys.Gacha.RenderCard[]>([]);
|
||||
const poolSelect = ref<TGApp.Plugins.Mys.Gacha.RenderCard[]>([]);
|
||||
const poolTimeGet = ref<Record<number, string>>({});
|
||||
const poolTimePass = ref<Record<number, number>>({});
|
||||
const timer = ref<Record<number, any>>({});
|
||||
|
||||
interface TPoolEmits {
|
||||
(e: "success"): void;
|
||||
}
|
||||
type TPoolEmits = (e: "success") => void;
|
||||
type PoolStat = "future" | "now" | "past"; // 未开始 | 进行中 | 已结束
|
||||
type PoolItem = TGApp.Plugins.Mys.Gacha.RenderCard & { timeRest: number; stat: PoolStat };
|
||||
|
||||
const emits = defineEmits<TPoolEmits>();
|
||||
const { poolCover } = storeToRefs(useHomeStore());
|
||||
const router = useRouter();
|
||||
// eslint-disable-next-line no-undef
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
|
||||
function poolLastInterval(postId: number): TGApp.Plugins.Mys.Gacha.RenderCard | undefined {
|
||||
const pool = poolCards.value.find((pool) => pool.postId === postId);
|
||||
if (!pool) return;
|
||||
if (poolTimeGet.value[postId] === "未开始") {
|
||||
const isStart = pool.time.startStamp - Date.now();
|
||||
if (isStart > 0) return;
|
||||
poolTimeGet.value[postId] = stamp2LastTime(pool.time.endStamp - Date.now());
|
||||
poolTimePass.value[postId] = pool.time.endStamp - Date.now();
|
||||
} else {
|
||||
const isEnd = pool.time.endStamp - Date.now();
|
||||
poolTimeGet.value[postId] = stamp2LastTime(isEnd);
|
||||
poolTimePass.value[postId] =
|
||||
((pool.time.endStamp - Date.now()) / (pool.time.endStamp - pool.time.startStamp)) * 100;
|
||||
if (isEnd >= 0) return;
|
||||
clearInterval(timer.value[postId]);
|
||||
timer.value[postId] = null;
|
||||
poolTimePass.value[postId] = 100;
|
||||
poolTimeGet.value[postId] = "已结束";
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
const showNew = ref<boolean>(false);
|
||||
const poolCards = ref<Array<PoolItem>>([]);
|
||||
const hasNew = computed<boolean>(
|
||||
() => poolCards.value.find((pool) => pool.stat === "future") !== undefined,
|
||||
);
|
||||
const poolSelect = computed<Array<PoolItem>>(() => {
|
||||
if (!hasNew.value) return poolCards.value;
|
||||
if (showNew.value) return poolCards.value.filter((pool) => pool.stat === "future");
|
||||
return poolCards.value.filter((pool) => pool.stat !== "future");
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const gachaData = await Mys.Gacha.get();
|
||||
if (!gachaData) {
|
||||
console.error("获取限时祈愿数据失败");
|
||||
return;
|
||||
}
|
||||
console.log("获取限时祈愿数据成功");
|
||||
console.info(gachaData);
|
||||
let cards: Array<TGApp.Plugins.Mys.Gacha.RenderCard>;
|
||||
if (!checkCover(gachaData)) {
|
||||
poolCards.value = await Mys.Gacha.card(gachaData);
|
||||
cards = await Mys.Gacha.card(gachaData);
|
||||
const coverData: Record<string, string> = {};
|
||||
poolCards.value.map((pool) => {
|
||||
coverData[pool.id] = pool.cover;
|
||||
return pool;
|
||||
});
|
||||
homeStore.poolCover.value = coverData;
|
||||
poolCover.value = coverData;
|
||||
} else {
|
||||
poolCards.value = await Mys.Gacha.card(gachaData, homeStore.poolCover.value);
|
||||
cards = await Mys.Gacha.card(gachaData, poolCover.value);
|
||||
}
|
||||
poolCards.value.map((pool) => {
|
||||
poolTimeGet.value[pool.postId] = stamp2LastTime(pool.time.endStamp - Date.now());
|
||||
poolTimePass.value[pool.postId] = pool.time.endStamp - Date.now();
|
||||
if (poolTimePass.value[pool.postId] <= 0) {
|
||||
poolTimeGet.value[pool.postId] = "已结束";
|
||||
poolTimePass.value[pool.postId] = 100;
|
||||
showNew.value = false;
|
||||
} else if (pool.time.startStamp - Date.now() > 0) {
|
||||
poolTimeGet.value[pool.postId] = "未开始";
|
||||
poolTimePass.value[pool.postId] = 100;
|
||||
}
|
||||
timer.value[pool.postId] = setInterval(() => {
|
||||
poolLastInterval(pool.postId);
|
||||
}, 1000);
|
||||
return pool;
|
||||
});
|
||||
if (poolCards.value.length > 2) {
|
||||
poolSelect.value = poolCards.value.filter(
|
||||
(pool) => poolTimeGet.value[pool.postId] !== "未开始",
|
||||
);
|
||||
hasNew.value =
|
||||
poolCards.value.filter((pool) => poolTimeGet.value[pool.postId] === "未开始").length > 0;
|
||||
} else {
|
||||
poolSelect.value = poolCards.value;
|
||||
hasNew.value = false;
|
||||
for (const pool of cards) {
|
||||
const timeRest = pool.time.endStamp - Date.now();
|
||||
poolCards.value.push({
|
||||
...pool,
|
||||
stat: timeRest > pool.time.totalStamp ? "future" : timeRest > 0 ? "now" : "past",
|
||||
timeRest: timeRest,
|
||||
});
|
||||
}
|
||||
if (timer !== null) clearInterval(timer);
|
||||
timer = setInterval(poolTimeout, 1000);
|
||||
emits("success");
|
||||
});
|
||||
|
||||
// 检测新卡池
|
||||
function checkCover(data: TGApp.Plugins.Mys.Gacha.Data[]): boolean {
|
||||
if (!homeStore.poolCover || Object.keys(homeStore.poolCover).length === 0) {
|
||||
return false;
|
||||
}
|
||||
const cover = homeStore.poolCover;
|
||||
if (cover.value === undefined) return false;
|
||||
let checkList = data.length;
|
||||
Object.entries(cover).forEach(([key, value]: [string, unknown]) => {
|
||||
const pool = data.find((item: TGApp.Plugins.Mys.Gacha.Data) => item.id.toString() === key);
|
||||
if (pool && value !== "/source/UI/empty.webp") {
|
||||
checkList--;
|
||||
function poolTimeout(): void {
|
||||
for (const pool of poolCards.value) {
|
||||
if (pool.stat === "past") {
|
||||
if (pool.timeRest !== -1) pool.timeRest = -1;
|
||||
continue;
|
||||
}
|
||||
const timeRest = pool.time.endStamp - Date.now();
|
||||
if (timeRest >= pool.time.totalStamp) {
|
||||
pool.stat = "future";
|
||||
pool.timeRest = timeRest;
|
||||
continue;
|
||||
}
|
||||
if (timeRest <= 0) {
|
||||
pool.stat = "past";
|
||||
pool.timeRest = -1;
|
||||
continue;
|
||||
}
|
||||
pool.stat = "now";
|
||||
pool.timeRest = timeRest;
|
||||
}
|
||||
}
|
||||
|
||||
function checkCover(data: Array<TGApp.Plugins.Mys.Gacha.Data>): boolean {
|
||||
if (poolCover.value === undefined || Object.keys(poolCover.value).length === 0) return false;
|
||||
let checkList = data.length;
|
||||
Object.entries(poolCover.value).forEach(([key, value]: [string, unknown]) => {
|
||||
const pool = data.find((item) => item.id.toString() === key);
|
||||
if (pool && value !== "/source/UI/empty.webp") checkList--;
|
||||
});
|
||||
return checkList === 0;
|
||||
}
|
||||
@@ -205,25 +180,9 @@ function getCBox(info: TGApp.App.Character.WikiBriefInfo): TItemBoxData {
|
||||
};
|
||||
}
|
||||
|
||||
// 更换显示的卡池
|
||||
async function switchPool(): Promise<void> {
|
||||
showNew.value = !showNew.value;
|
||||
if (showNew.value) {
|
||||
poolSelect.value = poolCards.value.filter(
|
||||
(pool) => poolTimeGet.value[pool.postId] === "未开始",
|
||||
);
|
||||
} else {
|
||||
poolSelect.value = poolCards.value.filter(
|
||||
(pool) => poolTimeGet.value[pool.postId] !== "未开始",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
Object.keys(timer.value).forEach((key) => {
|
||||
clearInterval(timer.value[Number(key)]);
|
||||
});
|
||||
timer.value = {};
|
||||
if (timer !== null) clearInterval(timer);
|
||||
timer = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,110 +3,76 @@
|
||||
<template #title>近期活动</template>
|
||||
<template #default>
|
||||
<div class="position-grid">
|
||||
<v-card v-for="(card, index) in positionCards" :key="index" class="position-card" rounded>
|
||||
<v-list class="position-list">
|
||||
<v-list-item :subtitle="card.abstract" :title="card.title">
|
||||
<template #prepend>
|
||||
<v-avatar rounded="0" @click="openPosition(card)">
|
||||
<v-img :src="card.icon" class="position-icon" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-btn class="position-card-btn" @click="openPosition(card)"> 查看</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-divider />
|
||||
<v-card-text>
|
||||
<div class="position-card-text">
|
||||
<v-icon>mdi-calendar-clock</v-icon>
|
||||
<span>{{ card.time.start }}~{{ card.time.end }}</span>
|
||||
</div>
|
||||
<div class="position-card-text">
|
||||
<v-icon>mdi-clock-outline</v-icon>
|
||||
<span v-if="positionTimeGet[card.postId] !== '已结束'">{{
|
||||
positionTimeGet[card.postId]
|
||||
}}</span>
|
||||
<span v-else>已结束</span>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<PhPositionCard v-for="(card, index) in positionCards" :key="index" :position="card" />
|
||||
</div>
|
||||
</template>
|
||||
</THomeCard>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import PhPositionCard from "@comp/pageHome/ph-position-card.vue";
|
||||
import Mys from "@Mys/index.js";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
import Mys from "../../plugins/Mys/index.js";
|
||||
import { parseLink } from "../../utils/linkParser.js";
|
||||
import { createPost } from "../../utils/TGWindow.js";
|
||||
import { stamp2LastTime } from "../../utils/toolFunc.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import THomeCard from "./ph-comp-card.vue";
|
||||
|
||||
// data
|
||||
const positionCards = ref<TGApp.Plugins.Mys.Position.RenderCard[]>([]);
|
||||
const positionTimeGet = ref<Record<number, string>>({}); // 剩余时间/已结束/未知
|
||||
const positionTimeEnd = ref<Record<number, number>>({}); // 结束时间戳
|
||||
const positionTimer = ref<Record<number, any>>({}); // 定时器
|
||||
|
||||
interface TPositionEmits {
|
||||
(e: "success"): void;
|
||||
}
|
||||
|
||||
type TPositionEmits = (e: "success") => void;
|
||||
type PositionStat = "past" | "now" | "future" | "unknown"; // 已结束 | 进行中 | 未开始 | 未知
|
||||
export type PositionItem = TGApp.Plugins.Mys.Position.RenderCard & {
|
||||
timeRest: number;
|
||||
stat: PositionStat;
|
||||
};
|
||||
const emits = defineEmits<TPositionEmits>();
|
||||
|
||||
function positionLastInterval(postId: number): void {
|
||||
const timeGet = positionTimeGet.value[postId];
|
||||
if (timeGet === "未知" || timeGet === "已结束") {
|
||||
clearInterval(positionTimer.value[postId]);
|
||||
positionTimer.value[postId] = null;
|
||||
return;
|
||||
}
|
||||
const timeLast = positionTimeEnd.value[postId] - Date.now();
|
||||
if (timeLast <= 0) {
|
||||
positionTimeGet.value[postId] = "已结束";
|
||||
} else {
|
||||
positionTimeGet.value[postId] = stamp2LastTime(timeLast);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
const positionCards = ref<Array<PositionItem>>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const positionData = await Mys.Position.get();
|
||||
if (!positionData) {
|
||||
console.error("获取近期活动失败");
|
||||
return;
|
||||
}
|
||||
positionCards.value = Mys.Position.card(positionData);
|
||||
positionCards.value.forEach((card) => {
|
||||
if (card.time.endStamp === 0) {
|
||||
positionTimeGet.value[card.postId] = "未知";
|
||||
} else {
|
||||
positionTimeGet.value[card.postId] = stamp2LastTime(card.time.endStamp - Date.now());
|
||||
console.log(positionData);
|
||||
const cards = Mys.Position.card(positionData);
|
||||
for (const position of cards) {
|
||||
if (position.time.endStamp === 0 || position.time.totalStamp < 0) {
|
||||
positionCards.value.push({
|
||||
...position,
|
||||
timeRest: 0,
|
||||
stat: "unknown",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
positionTimeEnd.value[card.postId] = card.time.endStamp;
|
||||
positionTimer.value[card.postId] = setInterval(() => {
|
||||
positionLastInterval(card.postId);
|
||||
}, 1000);
|
||||
});
|
||||
const timeRest = position.time.endStamp - Date.now();
|
||||
positionCards.value.push({
|
||||
...position,
|
||||
timeRest,
|
||||
stat: timeRest > position.time.totalStamp ? "future" : timeRest > 0 ? "now" : "past",
|
||||
});
|
||||
}
|
||||
if (timer !== null) clearInterval(timer);
|
||||
timer = setInterval(getPositionTimer, 1000);
|
||||
emits("success");
|
||||
});
|
||||
|
||||
async function openPosition(card: TGApp.Plugins.Mys.Position.RenderCard): Promise<void> {
|
||||
const res = await parseLink(card.link);
|
||||
if (res === "post") await createPost(card.postId, card.title);
|
||||
if (res === false) {
|
||||
showSnackbar.warn(`未知链接:${card.link}`, 3000);
|
||||
return;
|
||||
function getPositionTimer(): void {
|
||||
for (const position of positionCards.value) {
|
||||
if (position.stat === "unknown") continue;
|
||||
if (position.stat === "past") {
|
||||
position.timeRest = 0;
|
||||
continue;
|
||||
}
|
||||
position.timeRest = position.time.endStamp - Date.now();
|
||||
if (position.timeRest <= 0) {
|
||||
position.stat = "past";
|
||||
position.timeRest = 0;
|
||||
} else if (position.timeRest > position.time.totalStamp) {
|
||||
position.stat = "future";
|
||||
} else {
|
||||
position.stat = "now";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
Object.keys(positionTimer.value).forEach((key) => {
|
||||
clearInterval(positionTimer.value[Number(key)]);
|
||||
});
|
||||
if (timer !== null) clearInterval(timer);
|
||||
timer = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -117,54 +83,4 @@ onUnmounted(() => {
|
||||
grid-gap: 20px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(calc(400px + 2rem), 0.5fr));
|
||||
}
|
||||
|
||||
.position-card {
|
||||
border: none;
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.position-list {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.position-icon {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 5px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.position-icon :deep(img) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.position-icon :hover {
|
||||
cursor: pointer;
|
||||
scale: 1.2;
|
||||
}
|
||||
|
||||
.position-card-btn {
|
||||
border: 1px solid var(--common-shadow-4);
|
||||
border-radius: 5px;
|
||||
background: var(--tgc-btn-1);
|
||||
color: var(--btn-text);
|
||||
}
|
||||
|
||||
.position-card-text {
|
||||
display: inline-block;
|
||||
min-width: 200px;
|
||||
align-items: flex-start;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.position-card-text :nth-child(1) {
|
||||
color: var(--btn-text);
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
</style>
|
||||
|
||||
180
src/components/pageHome/ph-position-card.vue
Normal file
180
src/components/pageHome/ph-position-card.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div class="ph-pool-card">
|
||||
<div class="top">
|
||||
<div class="main">
|
||||
<div class="left"><img :src="props.position.icon" alt="icon" /></div>
|
||||
<div class="right">
|
||||
<div class="title">{{ props.position.title }}</div>
|
||||
<div class="sub">{{ props.position.abstract }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn class="btn" @click="openPosition(props.position)">查看</v-btn>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="period">
|
||||
<v-icon>mdi-calendar-clock</v-icon>
|
||||
<span class="time">
|
||||
{{ timestampToDate(props.position.time.startStamp) }}
|
||||
~
|
||||
{{
|
||||
props.position.time.endStamp === 0
|
||||
? "未知"
|
||||
: timestampToDate(props.position.time.endStamp)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rest">
|
||||
<v-icon>mdi-clock-outline</v-icon>
|
||||
<span v-if="props.position.stat === 'past'">已结束</span>
|
||||
<span v-else-if="props.position.stat === 'unknown'">未知</span>
|
||||
<span v-else-if="props.position.stat === 'now'">
|
||||
剩余时间:{{ stamp2LastTime(props.position.timeRest) }}
|
||||
</span>
|
||||
<span v-else>未开始</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import type { PositionItem } from "@comp/pageHome/ph-comp-position.vue";
|
||||
|
||||
import { parseLink } from "@/utils/linkParser.js";
|
||||
import { createPost } from "@/utils/TGWindow.js";
|
||||
import { stamp2LastTime, timestampToDate } from "@/utils/toolFunc.js";
|
||||
|
||||
type PhPositionCardProps = { position: PositionItem };
|
||||
const props = defineProps<PhPositionCardProps>();
|
||||
|
||||
async function openPosition(card: TGApp.Plugins.Mys.Position.RenderCard): Promise<void> {
|
||||
const res = await parseLink(card.link);
|
||||
if (res === "post") await createPost(card.postId, card.title);
|
||||
if (res === false) {
|
||||
showSnackbar.warn(`未知链接:${card.link}`, 3000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.ph-pool-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
box-shadow: 0 0 5px var(--common-shadow-2);
|
||||
color: var(--box-text-1);
|
||||
}
|
||||
|
||||
.dark .ph-pool-card {
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
.top {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
border-bottom: 2px solid var(--common-shadow-2);
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
column-gap: 8px;
|
||||
|
||||
.left {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 3px;
|
||||
object-fit: contain;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
&:hover img {
|
||||
cursor: pointer;
|
||||
scale: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 1px solid var(--common-shadow-4);
|
||||
border-radius: 5px;
|
||||
background: var(--tgc-btn-1);
|
||||
color: var(--btn-text);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
gap: 6px;
|
||||
|
||||
.period {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
column-gap: 4px;
|
||||
|
||||
:first-child {
|
||||
color: var(--tgc-od-orange);
|
||||
}
|
||||
}
|
||||
|
||||
.rest {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
column-gap: 4px;
|
||||
|
||||
:first-child {
|
||||
color: var(--tgc-od-green);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,41 +1,32 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible">
|
||||
<div class="toc-box">
|
||||
<div class="toc-top">
|
||||
<div class="toc-title">
|
||||
<span>请选择要跳转的频道</span>
|
||||
</div>
|
||||
<div class="toc-list">
|
||||
<div
|
||||
v-for="(item, index) in channelList"
|
||||
:key="index"
|
||||
class="toc-list-item"
|
||||
:class="{ active: props.gid === item.gid }"
|
||||
@click="toChannel(item)"
|
||||
>
|
||||
<img :src="item.icon" alt="icon" />
|
||||
<span>{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toc-close" @click="visible = false">
|
||||
<div class="toc-close-btn">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
<div class="toc-title">请选择要跳转的频道</div>
|
||||
<div class="toc-list">
|
||||
<div
|
||||
v-for="(item, index) in channelList"
|
||||
:key="index"
|
||||
class="toc-list-item"
|
||||
:class="{ active: props.gid === item.gid }"
|
||||
@click="toChannel(item)"
|
||||
>
|
||||
<img :src="item.icon" alt="icon" />
|
||||
<span>{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { NewsType, useAppStore } from "../../store/modules/app.js";
|
||||
import { ToChannelItem } from "../../web/constant/bbs.js";
|
||||
import TGConstant from "../../web/constant/TGConstant.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { type NewsType, useAppStore } from "@/store/modules/app.js";
|
||||
import type { ToChannelItem } from "@/web/constant/bbs.js";
|
||||
import TGConstant from "@/web/constant/TGConstant.js";
|
||||
|
||||
type ToChannelProps = { gid?: string; curType?: string; modelValue: boolean };
|
||||
type ToChannelEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
@@ -68,10 +59,6 @@ async function toChannel(item: ToChannelItem): Promise<void> {
|
||||
<style lang="css" scoped>
|
||||
.toc-box {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.toc-top {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background: var(--app-page-bg);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TopNamecard :data="nameCard" @selected="toNameCard" v-if="nameCard" />
|
||||
<TopNameCard :data="nameCard" @selected="showNc = !showNc" v-if="nameCard" />
|
||||
<TwcMaterials :data="data.materials" />
|
||||
<TwcSkills :data="data.skills" />
|
||||
<TwcConstellations :data="data.constellation" />
|
||||
@@ -93,53 +93,53 @@
|
||||
</template>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
<ToNamecard v-if="hasNc" v-model="showNc" :data="nameCard" />
|
||||
<ToNameCard v-if="hasNc" v-model="showNc" :data="nameCard" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import TItembox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import ToNameCard from "@comp/app/to-nameCard.vue";
|
||||
import TopNameCard from "@comp/app/top-nameCard.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { WikiCharacterData, AppNameCardsData, AppCharacterData } from "../../data/index.js";
|
||||
import { createObc } from "../../utils/TGWindow.js";
|
||||
import { parseHtmlText } from "../../utils/toolFunc.js";
|
||||
import TItembox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import ToNamecard from "../app/to-namecard.vue";
|
||||
import TopNamecard from "../app/top-namecard.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import TwcConstellations from "./twc-constellations.vue";
|
||||
import TwcMaterials from "./twc-materials.vue";
|
||||
import TwcSkills from "./twc-skills.vue";
|
||||
|
||||
interface TwcCharacterProps {
|
||||
item: TGApp.App.Character.WikiBriefInfo;
|
||||
}
|
||||
import { AppCharacterData, AppNameCardsData, WikiCharacterData } from "@/data/index.js";
|
||||
import { createObc } from "@/utils/TGWindow.js";
|
||||
import { parseHtmlText } from "@/utils/toolFunc.js";
|
||||
|
||||
type TwcCharacterProps = { item: TGApp.App.Character.WikiBriefInfo };
|
||||
|
||||
const props = defineProps<TwcCharacterProps>();
|
||||
const router = useRouter();
|
||||
|
||||
const data = ref<TGApp.App.Character.WikiItem>();
|
||||
const box = computed(() => {
|
||||
return <TItemBoxData>{
|
||||
bg: `/icon/bg/${data.value?.star}-Star.webp`,
|
||||
icon: `/WIKI/character/${data.value?.id}.webp`,
|
||||
size: "128px",
|
||||
height: "128px",
|
||||
display: "inner",
|
||||
lt: `/icon/element/${data.value?.element}元素.webp`,
|
||||
ltSize: "30px",
|
||||
innerHeight: 30,
|
||||
innerIcon: `/icon/weapon/${data.value?.weapon}.webp`,
|
||||
innerText: data.value?.name,
|
||||
clickable: false,
|
||||
};
|
||||
});
|
||||
const hasNc = ref(false);
|
||||
const showNc = ref(false);
|
||||
const nameCard = ref<TGApp.App.NameCard.Item>();
|
||||
const hasNc = ref<boolean>(false);
|
||||
const showNc = ref<boolean>(false);
|
||||
const nameCard = shallowRef<TGApp.App.NameCard.Item>();
|
||||
const data = shallowRef<TGApp.App.Character.WikiItem>();
|
||||
const box = computed<TItemBoxData>(() => ({
|
||||
bg: `/icon/bg/${data.value?.star ?? 5}-Star.webp`,
|
||||
icon: `/WIKI/character/${data.value?.id ?? 10000005}.webp`,
|
||||
size: "128px",
|
||||
height: "128px",
|
||||
display: "inner",
|
||||
lt: `/icon/element/${data.value?.element ?? "风"}元素.webp`,
|
||||
ltSize: "30px",
|
||||
innerHeight: 30,
|
||||
innerIcon: `/icon/weapon/${data.value?.weapon}.webp`,
|
||||
innerText: data.value?.name ?? "旅行者",
|
||||
clickable: false,
|
||||
}));
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
onMounted(() => loadData());
|
||||
|
||||
watch(() => props.item, loadData);
|
||||
|
||||
function loadData(): void {
|
||||
const res = WikiCharacterData.find((item) => item.id === props.item.id);
|
||||
if (res === undefined) {
|
||||
showSnackbar.warn(`未获取到角色 ${props.item.name} 的 Wiki 数据`);
|
||||
@@ -150,19 +150,10 @@ async function loadData(): Promise<void> {
|
||||
if (appC !== undefined) {
|
||||
hasNc.value = true;
|
||||
nameCard.value = AppNameCardsData.find((i) => i.name === appC.nameCard);
|
||||
} else {
|
||||
hasNc.value = false;
|
||||
}
|
||||
} else hasNc.value = false;
|
||||
showSnackbar.success(`成功获取角色 ${props.item.name} 的 Wiki 数据`);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.item,
|
||||
async () => await loadData(),
|
||||
);
|
||||
|
||||
onMounted(async () => await loadData());
|
||||
|
||||
async function toWiki(): Promise<void> {
|
||||
if (props.item.contentId === 0) {
|
||||
showSnackbar.warn(`角色 ${props.item.name} 暂无详情`);
|
||||
@@ -175,11 +166,6 @@ async function toBirth(date: string): Promise<void> {
|
||||
const birth = date.replace("月", "/").replace("日", "");
|
||||
await router.push({ name: "留影叙佳期", params: { date: birth } });
|
||||
}
|
||||
|
||||
function toNameCard(): void {
|
||||
if (showNc.value === true) showNc.value = false;
|
||||
showNc.value = true;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.twc-box {
|
||||
|
||||
@@ -28,11 +28,9 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
import { parseHtmlText } from "../../utils/toolFunc.js";
|
||||
import { parseHtmlText } from "@/utils/toolFunc.js";
|
||||
|
||||
interface TwcConstellationProps {
|
||||
data: TGApp.Plugins.Hutao.Character.RhisdTalent[];
|
||||
}
|
||||
type TwcConstellationProps = { data: Array<TGApp.Plugins.Hutao.Character.RhisdTalent> };
|
||||
|
||||
const props = defineProps<TwcConstellationProps>();
|
||||
const tab = ref<string>();
|
||||
@@ -41,7 +39,7 @@ function loadData(): void {
|
||||
tab.value = props.data[0].Name;
|
||||
}
|
||||
|
||||
onMounted(loadData);
|
||||
onMounted(() => loadData());
|
||||
|
||||
watch(() => props.data, loadData);
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
<template>
|
||||
<div class="twc-li-box">
|
||||
<div class="twc-li-left">
|
||||
<img class="twc-li-bg" :src="getBg()" alt="bg" />
|
||||
<img class="twc-li-icon" :src="getIcon()" alt="icon" />
|
||||
<img class="twc-li-bg" :src="`/icon/bg/${props.data.star}-Star.webp`" alt="bg" />
|
||||
<img class="twc-li-icon" :src="`/WIKI/${props.mode}/${props.data.id}.webp`" alt="icon" />
|
||||
</div>
|
||||
<div class="twc-li-right" :title="props.data.name">{{ props.data.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
type TwcListItemProps =
|
||||
| {
|
||||
mode: "character";
|
||||
@@ -23,24 +21,6 @@ type TwcListItemProps =
|
||||
};
|
||||
|
||||
const props = defineProps<TwcListItemProps>();
|
||||
const isSelected = computed(() => {
|
||||
return props.data.id === props.curItem.id;
|
||||
});
|
||||
const bgColor = computed(() => {
|
||||
return isSelected.value ? "var(--box-bg-2)" : "var(--box-bg-1)";
|
||||
});
|
||||
|
||||
function getBg(): string {
|
||||
return `/icon/bg/${props.data.star}-Star.webp`;
|
||||
}
|
||||
|
||||
function getIcon(): string {
|
||||
if (props.mode === "character") {
|
||||
return `/WIKI/character/${props.data.id}.webp`;
|
||||
} else {
|
||||
return `/WIKI/weapon/${props.data.id}.webp`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.twc-li-box {
|
||||
@@ -50,7 +30,7 @@ function getIcon(): string {
|
||||
align-items: center;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 5px;
|
||||
background: v-bind(bgColor);
|
||||
background: v-bind("props.data.id === props.curItem.id ? 'var(--box-bg-2)' : 'var(--box-bg-1)'");
|
||||
cursor: pointer;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@@ -7,49 +7,39 @@
|
||||
@click="checkData(item, index)"
|
||||
>
|
||||
<div class="twc-material-left">
|
||||
<div class="twc-material-bg">
|
||||
<img :src="item.bg" alt="bg" />
|
||||
</div>
|
||||
<div class="twc-material-icon">
|
||||
<img :src="item.icon" alt="icon" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="twc-material-right">
|
||||
{{ item.name }}
|
||||
<div class="twc-material-bg"><img :src="item.bg" alt="bg" /></div>
|
||||
<div class="twc-material-icon"><img :src="item.icon" alt="icon" /></div>
|
||||
</div>
|
||||
<div class="twc-material-right">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Suspense>
|
||||
<TwoMaterial :data="curData" v-model="showOverlay">
|
||||
<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 :data="curData" v-model="showOverlay">
|
||||
<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 { ref } from "vue";
|
||||
|
||||
import { WikiMaterialData } from "../../data/index.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { ref, shallowRef } from "vue";
|
||||
|
||||
import TwoMaterial from "./two-material.vue";
|
||||
|
||||
interface TwcMaterialsProp {
|
||||
data: TGApp.App.Calendar.Material[];
|
||||
}
|
||||
import { WikiMaterialData } from "@/data/index.js";
|
||||
|
||||
type TwcMaterialsProp = { data: Array<TGApp.App.Calendar.Material> };
|
||||
|
||||
const props = defineProps<TwcMaterialsProp>();
|
||||
const showOverlay = ref(false);
|
||||
const curIndex = ref(0);
|
||||
const curData = ref<TGApp.App.Material.WikiItem>({
|
||||
const showOverlay = ref<boolean>(false);
|
||||
const curIndex = ref<number>(0);
|
||||
const curData = shallowRef<TGApp.App.Material.WikiItem>({
|
||||
id: 0,
|
||||
name: "",
|
||||
description: "",
|
||||
@@ -59,7 +49,7 @@ const curData = ref<TGApp.App.Material.WikiItem>({
|
||||
convert: [],
|
||||
});
|
||||
|
||||
function checkData(item: TGApp.App.Calendar.Material, index: number) {
|
||||
function checkData(item: TGApp.App.Calendar.Material, index: number): void {
|
||||
if (showOverlay.value) showOverlay.value = false;
|
||||
const material = WikiMaterialData.find((m) => m.id === item.id);
|
||||
if (material) {
|
||||
@@ -71,7 +61,7 @@ function checkData(item: TGApp.App.Calendar.Material, index: number) {
|
||||
showSnackbar.warn(`材料 ${item.name} 暂无详细信息`);
|
||||
}
|
||||
|
||||
function switchMaterial(isNext: boolean) {
|
||||
function switchMaterial(isNext: boolean): void {
|
||||
if (isNext) {
|
||||
if (curIndex.value === props.data.length - 1) {
|
||||
showSnackbar.warn("已经是最后一个材料了");
|
||||
@@ -157,18 +147,18 @@ function switchMaterial(isNext: boolean) {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:first-child img {
|
||||
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>
|
||||
|
||||
@@ -23,25 +23,26 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import { parseHtmlText } from "../../utils/toolFunc.js";
|
||||
import { parseHtmlText } from "@/utils/toolFunc.js";
|
||||
|
||||
interface TwcSkillsProps {
|
||||
data: TGApp.App.Character.WikiSkill[];
|
||||
}
|
||||
type TwcSkillsProps = { data: Array<TGApp.App.Character.WikiSkill> };
|
||||
type TabItem = { name: string; icon: string };
|
||||
|
||||
const props = defineProps<TwcSkillsProps>();
|
||||
const tab = ref<string>();
|
||||
const tabValues = ref<Array<{ name: string; icon: string }>>([]);
|
||||
const tabValues = shallowRef<Array<TabItem>>([]);
|
||||
|
||||
function loadData(): void {
|
||||
tabValues.value = [];
|
||||
props.data.map((i) => tabValues.value.push({ name: i.Name, icon: i.Icon }));
|
||||
const tmpData: Array<TabItem> = [];
|
||||
props.data.map((i) => tmpData.push({ name: i.Name, icon: i.Icon }));
|
||||
tabValues.value = tmpData;
|
||||
tab.value = tabValues.value[0].name;
|
||||
}
|
||||
|
||||
onMounted(loadData);
|
||||
onMounted(() => loadData());
|
||||
|
||||
watch(() => props.data, loadData);
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="tww-box" v-if="data !== undefined">
|
||||
<div class="tww-brief">
|
||||
<TItembox :model-value="box" />
|
||||
<TItemBox :model-value="box" />
|
||||
<div class="tww-brief-info">
|
||||
<div class="tww-brief-title">
|
||||
<span>{{ data.name }}</span>
|
||||
@@ -51,40 +51,37 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
|
||||
import { WikiWeaponData } from "../../data/index.js";
|
||||
import { createObc } from "../../utils/TGWindow.js";
|
||||
import { parseHtmlText } from "../../utils/toolFunc.js";
|
||||
import TItembox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import TwcMaterials from "./twc-materials.vue";
|
||||
|
||||
interface TwcWeaponProps {
|
||||
item: TGApp.App.Weapon.WikiBriefInfo;
|
||||
}
|
||||
import { WikiWeaponData } from "@/data/index.js";
|
||||
import { createObc } from "@/utils/TGWindow.js";
|
||||
import { parseHtmlText } from "@/utils/toolFunc.js";
|
||||
|
||||
type TwcWeaponProps = { item: TGApp.App.Weapon.WikiBriefInfo };
|
||||
|
||||
const props = defineProps<TwcWeaponProps>();
|
||||
|
||||
const data = ref<TGApp.App.Weapon.WikiItem>();
|
||||
const box = computed(() => {
|
||||
return <TItemBoxData>{
|
||||
bg: `/icon/bg/${data.value?.star}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${data.value?.id}.webp`,
|
||||
size: "128px",
|
||||
height: "128px",
|
||||
display: "inner",
|
||||
lt: `/icon/weapon/${data.value?.weapon}.webp`,
|
||||
ltSize: "40px",
|
||||
innerHeight: 0,
|
||||
clickable: false,
|
||||
};
|
||||
});
|
||||
const data = shallowRef<TGApp.App.Weapon.WikiItem>();
|
||||
const box = computed<TItemBoxData>(() => ({
|
||||
bg: `/icon/bg/${data.value?.star}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${data.value?.id}.webp`,
|
||||
size: "128px",
|
||||
height: "128px",
|
||||
display: "inner",
|
||||
lt: `/icon/weapon/${data.value?.weapon}.webp`,
|
||||
ltSize: "40px",
|
||||
innerHeight: 0,
|
||||
innerText: "",
|
||||
clickable: false,
|
||||
}));
|
||||
const select = ref<number>(1);
|
||||
const selectItems = ref<number[]>([]);
|
||||
const selectItems = shallowRef<Array<number>>([]);
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
function loadData(): void {
|
||||
const res = WikiWeaponData.find((item) => item.id === props.item.id);
|
||||
if (res === undefined) {
|
||||
showSnackbar.warn(`未获取到武器 ${props.item.name} 的 Wiki 数据`);
|
||||
@@ -96,12 +93,9 @@ async function loadData(): Promise<void> {
|
||||
selectItems.value = data.value?.affix.Descriptions.map((item) => item.Level) ?? [];
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.item,
|
||||
async () => await loadData(),
|
||||
);
|
||||
watch(() => props.item, loadData);
|
||||
|
||||
onMounted(async () => await loadData());
|
||||
onMounted(() => loadData());
|
||||
|
||||
async function toWiki(): Promise<void> {
|
||||
if (props.item.contentId === 0) {
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
<div class="twoc-container">
|
||||
<v-icon>mdi-all-inclusive</v-icon>
|
||||
<div class="twoc-box" v-for="(item, index) in props.data.source" :key="index">
|
||||
<img
|
||||
class="twoc-left"
|
||||
:src="`/icon/material/${item.id}.webp`"
|
||||
alt="icon"
|
||||
:style="{
|
||||
'background-image': `url('/icon/bg/${item.star}-BGC.webp')`,
|
||||
}"
|
||||
/>
|
||||
<div class="twoc-left">
|
||||
<img :src="`/icon/bg/${item.star}-BGC.webp`" alt="icon" class="bg" />
|
||||
<img :src="`/icon/material/${item.id}.webp`" alt="icon" class="icon" />
|
||||
</div>
|
||||
<div class="twoc-right">
|
||||
<span>{{ item.name }}</span>
|
||||
<span>{{ item.count }}</span>
|
||||
@@ -18,9 +14,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TwoConvertProps {
|
||||
data: TGApp.App.Material.Convert;
|
||||
}
|
||||
type TwoConvertProps = { data: TGApp.App.Material.Convert };
|
||||
|
||||
const props = defineProps<TwoConvertProps>();
|
||||
</script>
|
||||
@@ -44,15 +38,29 @@ const props = defineProps<TwoConvertProps>();
|
||||
}
|
||||
|
||||
.twoc-left {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
z-index: 2;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.twoc-right {
|
||||
|
||||
@@ -26,30 +26,28 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { computed } from "vue";
|
||||
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import { parseHtmlText } from "../../utils/toolFunc.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
import TwoConvert from "./two-convert.vue";
|
||||
import TwoSource from "./two-source.vue";
|
||||
|
||||
import { generateShareImg } from "@/utils/TGShare.js";
|
||||
import { parseHtmlText } from "@/utils/toolFunc.js";
|
||||
|
||||
type TwoMaterialProps = { modelValue: boolean; data: TGApp.App.Material.WikiItem };
|
||||
type TwoMaterialEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
const props = defineProps<TwoMaterialProps>();
|
||||
const emits = defineEmits<TwoMaterialEmits>();
|
||||
const version = ref<string>();
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const iconBg = computed<string>(() => {
|
||||
if (!props.data) return "url('/icon/bg/0-BGC.webp')";
|
||||
return `url('/icon/bg/${props.data.star}-BGC.webp')`;
|
||||
});
|
||||
const version = await getVersion();
|
||||
|
||||
onMounted(async () => (version.value = await getVersion()));
|
||||
|
||||
async function shareMaterial(): Promise<void> {
|
||||
const element = document.querySelector<HTMLElement>(".twom-box");
|
||||
@@ -111,7 +109,7 @@ async function shareMaterial(): Promise<void> {
|
||||
width: 60px;
|
||||
border-radius: 50%;
|
||||
aspect-ratio: 1;
|
||||
background-image: v-bind(iconBg);
|
||||
background-image: v-bind("'url(/icon/bg/' + props.data.star + '-BGC.webp)'");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
@@ -65,18 +65,16 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
|
||||
export type SelectedCValue = {
|
||||
star: number[];
|
||||
weapon: string[];
|
||||
elements: string[];
|
||||
area: string[];
|
||||
star: Array<number>;
|
||||
weapon: Array<string>;
|
||||
elements: Array<string>;
|
||||
area: Array<string>;
|
||||
};
|
||||
type TwoSelectCProps = { modelValue: boolean; reset: boolean };
|
||||
|
||||
type TwoSelectCEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "update:reset", v: boolean): void;
|
||||
@@ -91,10 +89,10 @@ const selectElementList = ["冰", "岩", "水", "火", "草", "雷", "风"];
|
||||
const selectAreaList = ["蒙德", "璃月", "稻妻", "须弥", "枫丹", "纳塔", "愚人众", "至冬", "其他"];
|
||||
|
||||
// 选中的元素
|
||||
const selectedStar = ref<number[]>(selectStarList);
|
||||
const selectedWeapon = ref<string[]>(selectWeaponList);
|
||||
const selectedElements = ref<string[]>(selectElementList);
|
||||
const selectedArea = ref<string[]>(selectAreaList);
|
||||
const selectedStar = ref<Array<number>>(selectStarList);
|
||||
const selectedWeapon = ref<Array<string>>(selectWeaponList);
|
||||
const selectedElements = ref<Array<string>>(selectElementList);
|
||||
const selectedArea = ref<Array<string>>(selectAreaList);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
|
||||
@@ -35,11 +35,10 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
|
||||
export type SelectedWValue = { star: number[]; weapon: string[] };
|
||||
export type SelectedWValue = { star: Array<number>; weapon: Array<string> };
|
||||
type TwoSelectWProps = { modelValue: boolean; reset: boolean };
|
||||
type TwoSelectWEmits = {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
@@ -53,8 +52,8 @@ const emits = defineEmits<TwoSelectWEmits>();
|
||||
const selectStarList = [4, 5];
|
||||
const selectWeaponList = ["单手剑", "双手剑", "弓", "法器", "长柄武器"];
|
||||
|
||||
const selectedStar = ref<number[]>(selectStarList);
|
||||
const selectedWeapon = ref<string[]>(selectWeaponList);
|
||||
const selectedStar = ref<Array<number>>(selectStarList);
|
||||
const selectedWeapon = ref<Array<string>>(selectWeaponList);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
|
||||
@@ -7,13 +7,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
interface TwoConvertProps {
|
||||
data: TGApp.App.Material.Source;
|
||||
}
|
||||
type TwoConvertProps = { data: TGApp.App.Material.Source };
|
||||
|
||||
const props = defineProps<TwoConvertProps>();
|
||||
|
||||
const textColor = computed(() => {
|
||||
const textColor = computed<string>(() => {
|
||||
if (!props.data || !props.data.days) return "var(--tgc-blue-2)";
|
||||
const days = props.data.days;
|
||||
const day = new Date().getDay();
|
||||
|
||||
@@ -11,13 +11,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { AppCharacterData } from "../../data/index.js";
|
||||
import TItemBox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
|
||||
interface TuaDetailBattleProps {
|
||||
title: string;
|
||||
modelValue: TGApp.Sqlite.Abyss.Battle;
|
||||
}
|
||||
import { AppCharacterData } from "@/data/index.js";
|
||||
|
||||
type TuaDetailBattleProps = { title: string; modelValue: TGApp.Sqlite.Abyss.Battle };
|
||||
|
||||
const props = defineProps<TuaDetailBattleProps>();
|
||||
|
||||
|
||||
@@ -21,9 +21,7 @@
|
||||
import TuaDetailBattle from "./tua-detail-battle.vue";
|
||||
import TuaDetailTitle from "./tua-detail-title.vue";
|
||||
|
||||
interface TuaDetailLevelProps {
|
||||
modelValue: TGApp.Sqlite.Abyss.Level;
|
||||
}
|
||||
type TuaDetailLevelProps = { modelValue: TGApp.Sqlite.Abyss.Level };
|
||||
|
||||
const props = defineProps<TuaDetailLevelProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,39 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="tud-t-box"
|
||||
:style="{
|
||||
fontFamily: props.mode === 'level' ? 'var(--font-text)' : 'var(--font-title)',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="tud-t-title"
|
||||
:style="{
|
||||
fontSize: props.mode === 'level' ? '18px' : '20px',
|
||||
}"
|
||||
>
|
||||
<slot name="title">
|
||||
<span>{{ props.name }}</span>
|
||||
</slot>
|
||||
<div class="tud-t-box">
|
||||
<div class="tud-t-title">
|
||||
<slot name="title">{{ props.name }}</slot>
|
||||
</div>
|
||||
<div
|
||||
class="tud-t-val"
|
||||
:style="{
|
||||
fontSize: props.mode === 'level' ? '18px' : '20px',
|
||||
}"
|
||||
>
|
||||
<div class="tud-t-val">
|
||||
<img src="/icon/star/Abyss.webp" alt="Abyss" />
|
||||
<slot name="val">
|
||||
<span>{{ props.val }}</span>
|
||||
</slot>
|
||||
<slot name="val">{{ props.val }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TuaDetailTitleProps {
|
||||
name: string;
|
||||
val: number;
|
||||
mode: "floor" | "level";
|
||||
}
|
||||
type TuaDetailTitleProps = { name: string; val: number; mode: "floor" | "level" };
|
||||
|
||||
const props = defineProps<TuaDetailTitleProps>();
|
||||
</script>
|
||||
@@ -44,10 +21,12 @@ const props = defineProps<TuaDetailTitleProps>();
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--common-shadow-4);
|
||||
font-family: v-bind("props.mode === 'level' ? 'var(--font-text)' : 'var(--font-title)'");
|
||||
}
|
||||
|
||||
.tud-t-title {
|
||||
color: var(--box-text-4);
|
||||
font-size: v-bind("props.mode === 'level' ? '18px' : '20px'");
|
||||
}
|
||||
|
||||
.tud-t-val {
|
||||
@@ -55,6 +34,7 @@ const props = defineProps<TuaDetailTitleProps>();
|
||||
align-items: center;
|
||||
color: var(--tgc-yellow-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: v-bind("props.mode === 'level' ? '18px' : '20px'");
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
import TuaDetailLevel from "./tua-detail-level.vue";
|
||||
import TuaDetailTitle from "./tua-detail-title.vue";
|
||||
|
||||
interface TuaDetailProps {
|
||||
modelValue: TGApp.Sqlite.Abyss.Floor;
|
||||
}
|
||||
type TuaDetailProps = { modelValue: TGApp.Sqlite.Abyss.Floor };
|
||||
|
||||
const props = defineProps<TuaDetailProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<template>
|
||||
<div class="tuao-box">
|
||||
<div class="tuao-title">
|
||||
<slot name="title">
|
||||
{{ props.title }}
|
||||
</slot>
|
||||
<slot name="title">{{ props.title }}</slot>
|
||||
</div>
|
||||
<div v-if="props.valText" class="tuao-val-text">
|
||||
<slot name="val-text">
|
||||
{{ props.valText }}
|
||||
</slot>
|
||||
<slot name="val-text">{{ props.valText }}</slot>
|
||||
</div>
|
||||
<div v-if="props.valIcons" class="tuao-val-icons">
|
||||
<slot name="val-icons">
|
||||
@@ -22,20 +18,18 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
|
||||
import { AppCharacterData } from "../../data/index.js";
|
||||
import TItemBox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import { AppCharacterData } from "@/data/index.js";
|
||||
|
||||
interface TAOProps {
|
||||
type TAOProps = {
|
||||
title: string;
|
||||
valText?: string | number;
|
||||
valIcons?: TGApp.Sqlite.Abyss.Character[];
|
||||
valIcons?: Array<TGApp.Sqlite.Abyss.Character>;
|
||||
multi4?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<TAOProps>();
|
||||
const getIconNum = computed(() => (props.multi4 ? 4 : 1));
|
||||
|
||||
function getBoxData(avatar: TGApp.Sqlite.Abyss.Character): TItemBoxData {
|
||||
const res = AppCharacterData.find((a) => a.id === avatar.id);
|
||||
@@ -81,6 +75,6 @@ function getBoxData(avatar: TGApp.Sqlite.Abyss.Character): TItemBoxData {
|
||||
.tuao-val-icons {
|
||||
display: grid;
|
||||
column-gap: 10px;
|
||||
grid-template-columns: repeat(v-bind(getIconNum), 1fr);
|
||||
grid-template-columns: repeat(v-bind("props.multi4 ? 4 : 1"), 1fr);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<v-virtual-scroll :items="renderAchi" :item-height="60" class="tua-al-list">
|
||||
<template #default="{ item }">
|
||||
<TuaAchi :modelValue="item" @select-achi="selectAchi" />
|
||||
<div style="height: 10px" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
<ToNameCard v-model="showNc" :data="ncData" v-if="ncData" />
|
||||
@@ -17,56 +16,50 @@
|
||||
@select-series="selectSeries"
|
||||
>
|
||||
<template #left>
|
||||
<div class="card-arrow left" @click="switchAchiInfo(false)">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
<div class="card-arrow" @click="switchAchiInfo(false)">
|
||||
<img src="@/assets/icons/arrow-right.svg" alt="right" />
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<div class="card-arrow" @click="switchAchiInfo(true)">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
<img src="@/assets/icons/arrow-right.svg" alt="right" />
|
||||
</div>
|
||||
</template>
|
||||
</ToAchiInfo>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
|
||||
import { AppAchievementSeriesData, AppNameCardsData } from "../../data/index.js";
|
||||
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
|
||||
import ToNameCard from "../app/to-namecard.vue";
|
||||
import TopNameCard from "../app/top-namecard.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import ToNameCard from "@comp/app/to-nameCard.vue";
|
||||
import TopNameCard from "@comp/app/top-nameCard.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TSUserAchi from "@Sqlite/modules/userAchi.js";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import ToAchiInfo from "./tua-achi-overlay.vue";
|
||||
import TuaAchi from "./tua-achi.vue";
|
||||
|
||||
interface TuaAchiListProps {
|
||||
import { AppAchievementSeriesData, AppNameCardsData } from "@/data/index.js";
|
||||
|
||||
type TuaAchiListProps = {
|
||||
uid: number;
|
||||
hideFin: boolean;
|
||||
series?: number;
|
||||
search?: string;
|
||||
isSearch: boolean;
|
||||
}
|
||||
|
||||
interface TuaAchiListEmits {
|
||||
};
|
||||
type TuaAchiListEmits = {
|
||||
(e: "update:series", v: number): void;
|
||||
|
||||
(e: "update:isSearch", v: boolean): false;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<TuaAchiListProps>();
|
||||
const emits = defineEmits<TuaAchiListEmits>();
|
||||
|
||||
const achievements = ref<TGApp.Sqlite.Achievement.RenderAchi[]>([]);
|
||||
const selectedAchi = ref<TGApp.Sqlite.Achievement.RenderAchi | undefined>(undefined);
|
||||
|
||||
const nameCard = ref<string>();
|
||||
const ncData = ref<TGApp.App.NameCard.Item | undefined>(undefined);
|
||||
const showNc = ref<boolean>(false);
|
||||
|
||||
const showOverlay = ref<boolean>(false);
|
||||
|
||||
const ncData = shallowRef<TGApp.App.NameCard.Item>();
|
||||
const achievements = shallowRef<Array<TGApp.Sqlite.Achievement.RenderAchi>>([]);
|
||||
const selectedAchi = shallowRef<TGApp.Sqlite.Achievement.RenderAchi>();
|
||||
const renderAchi = computed<Array<TGApp.Sqlite.Achievement.RenderAchi>>(() => {
|
||||
if (props.hideFin) return achievements.value.filter((a) => !a.isCompleted);
|
||||
return achievements.value;
|
||||
@@ -74,15 +67,8 @@ const renderAchi = computed<Array<TGApp.Sqlite.Achievement.RenderAchi>>(() => {
|
||||
|
||||
onMounted(async () => await loadAchi());
|
||||
|
||||
watch(
|
||||
() => [props.search, props.isSearch],
|
||||
async () => await searchAchi(),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.series, props.uid],
|
||||
async () => await loadAchi(),
|
||||
);
|
||||
watch(() => [props.search, props.isSearch], searchAchi);
|
||||
watch(() => [props.series, props.uid], loadAchi);
|
||||
|
||||
async function searchAchi(): Promise<void> {
|
||||
if (!props.isSearch) return;
|
||||
@@ -165,7 +151,6 @@ function switchAchiInfo(next: boolean): void {
|
||||
max-height: 100%;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.tua-al-list {
|
||||
@@ -180,18 +165,18 @@ function switchAchiInfo(next: boolean): void {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&: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>
|
||||
|
||||
@@ -52,12 +52,12 @@
|
||||
<VpOverlaySearch gid="2" v-model="showSearch" :keyword="search" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import VpOverlaySearch from "@comp/viewPost/vp-overlay-search.vue";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
import { AppAchievementSeriesData } from "../../data/index.js";
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import VpOverlaySearch from "../viewPost/vp-overlay-search.vue";
|
||||
import { AppAchievementSeriesData } from "@/data/index.js";
|
||||
import TGLogger from "@/utils/TGLogger.js";
|
||||
|
||||
type ToAchiInfoProps = { modelValue: boolean; data: TGApp.Sqlite.Achievement.RenderAchi };
|
||||
type ToAchiInfoEmits = {
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
<div class="achi-pre-info" @click="selectAchi()">
|
||||
<span>
|
||||
<span>{{ data.name }}</span>
|
||||
<span v-if="data.progress !== 0">
|
||||
{{ data.progress }}
|
||||
</span>
|
||||
<span v-if="data.progress !== 0">{{ data.progress }}</span>
|
||||
</span>
|
||||
<span>{{ data.description }}</span>
|
||||
</div>
|
||||
@@ -30,26 +28,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TSUserAchi from "@Sqlite/modules/userAchi.js";
|
||||
import { event } from "@tauri-apps/api";
|
||||
import { toRaw, ref, watch } from "vue";
|
||||
import { ref, toRaw, watch } from "vue";
|
||||
|
||||
import { AppAchievementSeriesData } from "../../data/index.js";
|
||||
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
|
||||
import { timestampToDate } from "../../utils/toolFunc.js";
|
||||
import showDialog from "../func/dialog.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { AppAchievementSeriesData } from "@/data/index.js";
|
||||
import { timestampToDate } from "@/utils/toolFunc.js";
|
||||
|
||||
interface TuaAchiProps {
|
||||
modelValue: TGApp.Sqlite.Achievement.RenderAchi;
|
||||
}
|
||||
|
||||
interface TuaAchiEmits {
|
||||
type TuaAchiProps = { modelValue: TGApp.Sqlite.Achievement.RenderAchi };
|
||||
type TuaAchiEmits = {
|
||||
(e: "update:modelValue", data: TGApp.Sqlite.Achievement.RenderAchi): void;
|
||||
|
||||
(e: "update:update", data: boolean): void;
|
||||
|
||||
(e: "select-achi", data: TGApp.Sqlite.Achievement.RenderAchi): void;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<TuaAchiProps>();
|
||||
const emits = defineEmits<TuaAchiEmits>();
|
||||
@@ -109,6 +102,7 @@ async function setAchiStat(stat: boolean): Promise<void> {
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--box-text-7);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -9,28 +9,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { listen, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { ref, onMounted, watch, onUnmounted } from "vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TSUserAchi from "@Sqlite/modules/userAchi.js";
|
||||
import { type Event, listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { computed, onMounted, onUnmounted, shallowRef, watch } from "vue";
|
||||
|
||||
import { AppAchievementSeriesData } from "../../data/index.js";
|
||||
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { AppAchievementSeriesData } from "@/data/index.js";
|
||||
|
||||
interface TuaSeriesProps {
|
||||
uid: number;
|
||||
series: number;
|
||||
cur: number;
|
||||
}
|
||||
|
||||
interface TuaSeriesEmits {
|
||||
(e: "selectSeries", v: number): void;
|
||||
}
|
||||
type TuaSeriesProps = { uid: number; series: number; cur: number };
|
||||
type TuaSeriesEmits = (e: "selectSeries", v: number) => void;
|
||||
|
||||
const props = defineProps<TuaSeriesProps>();
|
||||
const emits = defineEmits<TuaSeriesEmits>();
|
||||
|
||||
const overview = ref<TGApp.Sqlite.Achievement.Overview>({ fin: 0, total: 0 });
|
||||
const data = ref<TGApp.App.Achievement.Series | undefined>(
|
||||
const overview = shallowRef<TGApp.Sqlite.Achievement.Overview>({ fin: 0, total: 0 });
|
||||
const data = computed<TGApp.App.Achievement.Series | undefined>(() =>
|
||||
AppAchievementSeriesData.find((s) => s.id === props.series),
|
||||
);
|
||||
let achiListener: UnlistenFn | null = null;
|
||||
@@ -50,7 +43,7 @@ async function refreshOverview(): Promise<void> {
|
||||
}
|
||||
|
||||
async function listenAchi(): Promise<UnlistenFn> {
|
||||
return await listen<number>("updateAchi", async (e) => {
|
||||
return await listen<number>("updateAchi", async (e: Event<number>) => {
|
||||
if (e.payload === props.series) await refreshOverview();
|
||||
});
|
||||
}
|
||||
@@ -62,7 +55,7 @@ onUnmounted(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
async function selectSeries(): Promise<void> {
|
||||
function selectSeries(): void {
|
||||
if (props.cur === props.series) {
|
||||
showSnackbar.warn("已选中当前系列!");
|
||||
return;
|
||||
@@ -78,6 +71,7 @@ async function selectSeries(): Promise<void> {
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--box-text-1);
|
||||
column-gap: 10px;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="tua-ab-box" title="点击查看详情">
|
||||
<div class="tua-ab-top">
|
||||
<TItembox v-model="avatarBox" />
|
||||
<TItemBox v-model="avatarBox" />
|
||||
<div class="tua-abt-right">
|
||||
<div class="tua-abt-rt">
|
||||
<TItembox v-model="weaponBox" :title="getWeaponTitle()" />
|
||||
<TItemBox v-model="weaponBox" :title="getWeaponTitle()" />
|
||||
<div class="tua-abt-rtr">
|
||||
<TuaRelicBox :model-value="relicsBox[0]" :position="1" />
|
||||
<TuaRelicBox :model-value="relicsBox[1]" :position="2" />
|
||||
@@ -45,59 +45,50 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import TSUserAvatar from "@Sqlite/modules/userAvatar.js";
|
||||
import { computed } from "vue";
|
||||
|
||||
import TSUserAvatar from "../../plugins/Sqlite/modules/userAvatar.js";
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import { getZhElement } from "../../utils/toolFunc.js";
|
||||
import TItembox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
|
||||
import TuaRelicBox from "./tua-relic-box.vue";
|
||||
|
||||
interface TuaAvatarBoxProps {
|
||||
modelValue: TGApp.Sqlite.Character.UserRole;
|
||||
}
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
import { getZhElement } from "@/utils/toolFunc.js";
|
||||
|
||||
type fixedLenArr<T, N extends number> = [T, ...Array<T>] & { length: N };
|
||||
type AvatarRelics = fixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
type TuaAvatarBoxProps = { modelValue: TGApp.Sqlite.Character.UserRole };
|
||||
|
||||
const props = defineProps<TuaAvatarBoxProps>();
|
||||
const userStore = useUserStore();
|
||||
|
||||
type FixedLenArr<T, N extends number> = [T, ...T[]] & { length: N };
|
||||
type AvatarRelics = FixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
|
||||
const avatarBox = computed<TItemBoxData>(() => {
|
||||
const avatar = props.modelValue.avatar;
|
||||
return {
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
bg: `/icon/bg/${avatar.rarity}-Star.webp`,
|
||||
icon: `/WIKI/character/${avatar.id}.webp`,
|
||||
lt: `/icon/element/${getZhElement(avatar.element)}元素.webp`,
|
||||
ltSize: "20px",
|
||||
rt: avatar.actived_constellation_num.toString() || "0",
|
||||
rtSize: "20px",
|
||||
innerText: avatar.name,
|
||||
innerHeight: 30,
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
};
|
||||
});
|
||||
const weaponBox = computed<TItemBoxData>(() => {
|
||||
const weapon = props.modelValue.weapon;
|
||||
return {
|
||||
size: "65px",
|
||||
height: "65px",
|
||||
bg: `/icon/bg/${weapon.rarity}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${weapon.id}.webp`,
|
||||
lt: `/icon/weapon/${weapon.type_name}.webp`,
|
||||
ltSize: "20px",
|
||||
rt: weapon.affix_level.toString(),
|
||||
rtSize: "20px",
|
||||
innerText: weapon.name,
|
||||
innerHeight: 20,
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
};
|
||||
});
|
||||
const avatarBox = computed<TItemBoxData>(() => ({
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
bg: `/icon/bg/${props.modelValue.avatar.rarity}-Star.webp`,
|
||||
icon: `/WIKI/character/${props.modelValue.avatar.id}.webp`,
|
||||
lt: `/icon/element/${getZhElement(props.modelValue.avatar.element)}元素.webp`,
|
||||
ltSize: "20px",
|
||||
rt: props.modelValue.avatar.actived_constellation_num.toString() || "0",
|
||||
rtSize: "20px",
|
||||
innerText: props.modelValue.avatar.name,
|
||||
innerHeight: 30,
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
}));
|
||||
const weaponBox = computed<TItemBoxData>(() => ({
|
||||
size: "65px",
|
||||
height: "65px",
|
||||
bg: `/icon/bg/${props.modelValue.weapon.rarity}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${props.modelValue.weapon.id}.webp`,
|
||||
lt: `/icon/weapon/${props.modelValue.weapon.type_name}.webp`,
|
||||
ltSize: "20px",
|
||||
rt: props.modelValue.weapon.affix_level.toString(),
|
||||
rtSize: "20px",
|
||||
innerText: props.modelValue.weapon.name,
|
||||
innerHeight: 20,
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
}));
|
||||
const relicsBox = computed<AvatarRelics>(() => {
|
||||
const relics = props.modelValue.relics;
|
||||
return [
|
||||
@@ -114,10 +105,9 @@ const isFetterMax = computed<boolean>(() => {
|
||||
}
|
||||
return props.modelValue.avatar.fetter === 10;
|
||||
});
|
||||
const skills = computed<TGApp.Game.Avatar.Skill[]>(() => {
|
||||
return props.modelValue.skills.filter((skill) => skill.skill_type === 1);
|
||||
});
|
||||
|
||||
const skills = computed<Array<TGApp.Game.Avatar.Skill>>(() =>
|
||||
props.modelValue.skills.filter((skill) => skill.skill_type === 1),
|
||||
);
|
||||
const nameCard = computed<string>(() => {
|
||||
const cardFind = TSUserAvatar.getAvatarCard(props.modelValue.avatar.id);
|
||||
return `/source/nameCard/profile/${cardFind}.webp`;
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TuaDcConstellationsProps {
|
||||
modelValue: TGApp.Game.Avatar.Constellation[];
|
||||
}
|
||||
type TuaDcConstellationsProps = { modelValue: Array<TGApp.Game.Avatar.Constellation> };
|
||||
|
||||
const props = defineProps<TuaDcConstellationsProps>();
|
||||
</script>
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
interface TuaDcPropProps {
|
||||
modelValue: TGApp.Game.Avatar.Prop;
|
||||
prop: TGApp.Game.Avatar.PropMapItem;
|
||||
}
|
||||
type TuaDcPropProps = { modelValue: TGApp.Game.Avatar.Prop; prop: TGApp.Game.Avatar.PropMapItem };
|
||||
|
||||
const props = defineProps<TuaDcPropProps>();
|
||||
const getWidth = computed<string>(() => {
|
||||
|
||||
@@ -59,13 +59,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
|
||||
interface TuaDcRelicProps {
|
||||
type TuaDcRelicProps = {
|
||||
modelValue: TGApp.Game.Avatar.Relic | false;
|
||||
pos: "1" | "2" | "3" | "4" | "5";
|
||||
recommend: TGApp.Game.Avatar.PropRecommend;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<TuaDcRelicProps>();
|
||||
const userStore = useUserStore();
|
||||
@@ -76,9 +76,7 @@ const propMain = computed<TGApp.Game.Avatar.PropMapItem | false>(() => {
|
||||
});
|
||||
const propSubs = computed<Array<TGApp.Game.Avatar.PropMapItem | false>>(() => {
|
||||
if (props.modelValue === false) return [];
|
||||
return props.modelValue.sub_property_list.map((item) => {
|
||||
return userStore.getProp(item.property_type);
|
||||
});
|
||||
return props.modelValue.sub_property_list.map((item) => userStore.getProp(item.property_type));
|
||||
});
|
||||
|
||||
function getRelicPos(): string {
|
||||
@@ -123,7 +121,7 @@ function getPropMainStyle(): string {
|
||||
|
||||
function getPropSubStyle(
|
||||
propItem: TGApp.Game.Avatar.PropMapItem | false,
|
||||
propsR: number[],
|
||||
propsR: Array<number>,
|
||||
): string {
|
||||
if (propItem === false) return "";
|
||||
if (propsR.includes(propItem.property_type)) return "color: var(--tgc-yellow-1);";
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TuaDcTalentsProps {
|
||||
modelValue: TGApp.Game.Avatar.Skill[];
|
||||
}
|
||||
type TuaDcTalentsProps = { modelValue: Array<TGApp.Game.Avatar.Skill> };
|
||||
|
||||
const props = defineProps<TuaDcTalentsProps>();
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<img v-if="propSub !== false && propSub.icon !== ''" :src="propSub.icon" alt="propSub" />
|
||||
<span>{{ propSub !== false ? propSub.name : "未知属性" }}</span>
|
||||
</span>
|
||||
<span>{{ props.modelValue.sub_property.final }}</span>
|
||||
<span>{{ props.modelValue.sub_property?.final }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tua-dcw-share">
|
||||
@@ -42,24 +42,29 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { app } from "@tauri-apps/api";
|
||||
import { computed } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
|
||||
interface TuaDcWeaponProps {
|
||||
type TuaDcWeaponProps = {
|
||||
modelValue: TGApp.Game.Avatar.WeaponDetail;
|
||||
updated: string;
|
||||
uid: number;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<TuaDcWeaponProps>();
|
||||
const userStore = useUserStore();
|
||||
const version = await app.getVersion();
|
||||
const version = ref<string>();
|
||||
|
||||
onMounted(async () => {
|
||||
version.value = await app.getVersion();
|
||||
});
|
||||
|
||||
const propMain = computed<TGApp.Game.Avatar.PropMapItem | false>(() => {
|
||||
return userStore.getProp(props.modelValue.main_property.property_type);
|
||||
});
|
||||
const propSub = computed<TGApp.Game.Avatar.PropMapItem | false>(() => {
|
||||
if (props.modelValue.sub_property === null) return false;
|
||||
return userStore.getProp(props.modelValue.sub_property.property_type);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -66,28 +66,26 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TSUserAvatar from "@Sqlite/modules/userAvatar.js";
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
|
||||
import TSUserAvatar from "../../plugins/Sqlite/modules/userAvatar.js";
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import { generateShareImg, saveImgLocal } from "../../utils/TGShare.js";
|
||||
|
||||
import TuaDcConstellations from "./tua-dc-constellations.vue";
|
||||
import TuaDcProp from "./tua-dc-prop.vue";
|
||||
import TuaDcRelic from "./tua-dc-relic.vue";
|
||||
import TuaDcTalents from "./tua-dc-talents.vue";
|
||||
import TuaDcWeapon from "./tua-dc-weapon.vue";
|
||||
|
||||
interface TuaDetailCardProps {
|
||||
modelValue: TGApp.Sqlite.Character.UserRole;
|
||||
}
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
import { generateShareImg, saveImgLocal } from "@/utils/TGShare.js";
|
||||
|
||||
type fixedLenArr<T, N extends number> = [T, ...Array<T>] & { length: N };
|
||||
type RelicList = fixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
type TuaDetailCardProps = { modelValue: TGApp.Sqlite.Character.UserRole };
|
||||
|
||||
const props = defineProps<TuaDetailCardProps>();
|
||||
const userStore = useUserStore();
|
||||
|
||||
type fixedLenArr<T, N extends number> = [T, ...T[]] & { length: N };
|
||||
type RelicList = fixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
|
||||
const relicList = computed<RelicList>(() => {
|
||||
return [
|
||||
props.modelValue.relics.find((item) => item.pos === 1) || false,
|
||||
@@ -97,11 +95,9 @@ const relicList = computed<RelicList>(() => {
|
||||
props.modelValue.relics.find((item) => item.pos === 5) || false,
|
||||
];
|
||||
});
|
||||
const propMain = computed<Array<TGApp.Game.Avatar.PropMapItem | false>>(() => {
|
||||
return props.modelValue.propSelected.map((item) => {
|
||||
return userStore.getProp(item.property_type);
|
||||
});
|
||||
});
|
||||
const propMain = computed<Array<TGApp.Game.Avatar.PropMapItem | false>>(() =>
|
||||
props.modelValue.propSelected.map((item) => userStore.getProp(item.property_type)),
|
||||
);
|
||||
|
||||
const bg = ref<string>("/source/nameCard/profile/原神·印象.webp");
|
||||
const avatar = ref<string>(props.modelValue.avatar.image);
|
||||
@@ -137,7 +133,11 @@ async function loadData(): Promise<void> {
|
||||
}
|
||||
|
||||
async function share(): Promise<void> {
|
||||
const shareBox = <HTMLElement>document.querySelector(".tua-dc-container");
|
||||
const shareBox = document.querySelector<HTMLElement>(".tua-dc-container");
|
||||
if (shareBox === null) {
|
||||
showSnackbar.error("分享失败,未找到分享内容");
|
||||
return;
|
||||
}
|
||||
const fileName = `【角色详情】${props.modelValue.avatar.name}`;
|
||||
loading.value = true;
|
||||
await generateShareImg(fileName, shareBox);
|
||||
|
||||
@@ -15,15 +15,18 @@
|
||||
@click="emits('toAvatar', avatar)"
|
||||
:title="avatar.avatar.name"
|
||||
>
|
||||
<div class="tdo-avatar" :style="getAvatarBg(avatar)">
|
||||
<div
|
||||
class="tdo-avatar"
|
||||
:class="{ selected: props.avatar.avatar.id === avatar.avatar.id }"
|
||||
>
|
||||
<img :src="avatar.avatar.side_icon" :alt="avatar.avatar.name" />
|
||||
</div>
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
</div>
|
||||
<div class="tdo-card-container">
|
||||
<div class="tdo-box-arrow left" @click="handleClick('left')">
|
||||
<img alt="left" src="../../assets/icons/arrow-right.svg" />
|
||||
<div class="tdo-box-arrow" @click="handleClick('left')">
|
||||
<img alt="left" src="@/assets/icons/arrow-right.svg" />
|
||||
</div>
|
||||
<v-window class="tdo-box-container" v-model="modeTab">
|
||||
<v-window-item value="classic">
|
||||
@@ -36,27 +39,26 @@
|
||||
<TuaDetailCard :model-value="avatar" />
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
<div class="tdo-box-arrow right" @click="handleClick('right')">
|
||||
<img alt="right" src="../../assets/icons/arrow-right.svg" />
|
||||
<div class="tdo-box-arrow" @click="handleClick('right')">
|
||||
<img alt="right" src="@/assets/icons/arrow-right.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import TucDetailCard from "@comp/userAvatarCard/tuc-detail-card.vue";
|
||||
import TucDetailOld from "@comp/userAvatarOld/tuc-detail-old.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import TucDetailCard from "../userAvatarCard/tuc-detail-card.vue";
|
||||
import TucDetailOld from "../userAvatarOld/tuc-detail-old.vue";
|
||||
|
||||
import TuaDetailCard from "./tua-detail-card.vue";
|
||||
|
||||
type TuaDetailOverlayProps = {
|
||||
modelValue: boolean;
|
||||
avatar: TGApp.Sqlite.Character.UserRole;
|
||||
mode: "classic" | "card" | "dev";
|
||||
avatars: TGApp.Sqlite.Character.UserRole[];
|
||||
avatars: Array<TGApp.Sqlite.Character.UserRole>;
|
||||
};
|
||||
type TuaDetailOverlayEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
@@ -101,13 +103,6 @@ function handleClick(pos: "left" | "right"): void {
|
||||
if (pos === "left") emits("toNext", false);
|
||||
else emits("toNext", true);
|
||||
}
|
||||
|
||||
function getAvatarBg(avatar: TGApp.Sqlite.Character.UserRole): string {
|
||||
if (props.avatar.avatar.id === avatar.avatar.id) {
|
||||
return "background-color:var(--tgc-od-white);";
|
||||
}
|
||||
return "background-color:transparent;";
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tdo-box {
|
||||
@@ -136,8 +131,13 @@ function getAvatarBg(avatar: TGApp.Sqlite.Character.UserRole): string {
|
||||
height: 32px;
|
||||
border: 1px solid var(--tgc-white-1);
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--tgc-od-white);
|
||||
}
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
@@ -162,23 +162,21 @@ function getAvatarBg(avatar: TGApp.Sqlite.Character.UserRole): string {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dark .tdo-box-arrow {
|
||||
filter: invert(11%) sepia(73%) saturate(11%) hue-rotate(139deg) brightness(97%) contrast(81%);
|
||||
}
|
||||
|
||||
.tdo-box-arrow.left img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.tdo-box-arrow.right img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tdo-box-container {
|
||||
position: relative;
|
||||
transition: all 1s ease-in-out;
|
||||
|
||||
@@ -19,12 +19,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import { useUserStore } from "@/store/modules/user.js";
|
||||
|
||||
interface TuaRelicBoxProps {
|
||||
modelValue: TGApp.Game.Avatar.Relic | false;
|
||||
position: number;
|
||||
}
|
||||
type TuaRelicBoxProps = { modelValue: TGApp.Game.Avatar.Relic | false; position: number };
|
||||
|
||||
const props = defineProps<TuaRelicBoxProps>();
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -14,16 +14,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch, onUnmounted } from "vue";
|
||||
import { onMounted, onUnmounted, shallowRef, watch } from "vue";
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare.js";
|
||||
import { saveImgLocal } from "@/utils/TGShare.js";
|
||||
|
||||
interface DucDetailOlbProps {
|
||||
modelValue: TGApp.Game.Avatar.Constellation[];
|
||||
}
|
||||
type DucDetailOlbProps = { modelValue: Array<TGApp.Game.Avatar.Constellation> };
|
||||
|
||||
const props = defineProps<DucDetailOlbProps>();
|
||||
const constellations = ref<TGApp.Game.Avatar.Constellation[]>([]);
|
||||
const constellations = shallowRef<Array<TGApp.Game.Avatar.Constellation>>([]);
|
||||
|
||||
async function loadData() {
|
||||
const tempConstellations = JSON.parse(JSON.stringify(props.modelValue));
|
||||
@@ -34,9 +32,7 @@ async function loadData() {
|
||||
constellations.value = tempConstellations;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
onMounted(async () => await loadData());
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => {
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
import { getZhElement } from "../../utils/toolFunc.js";
|
||||
import TItemBox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import { getZhElement } from "@/utils/toolFunc.js";
|
||||
|
||||
type DucDetailOltProps =
|
||||
| {
|
||||
data: TGApp.Game.Avatar.Avatar;
|
||||
mode: "avatar";
|
||||
mode: "character";
|
||||
}
|
||||
| {
|
||||
data: TGApp.Game.Avatar.WeaponDetail;
|
||||
@@ -27,45 +27,24 @@ type DucDetailOltProps =
|
||||
};
|
||||
|
||||
const props = defineProps<DucDetailOltProps>();
|
||||
const boxData = computed<TItemBoxData>(() => {
|
||||
if (props.mode === "avatar") {
|
||||
const avatar = <TGApp.Game.Avatar.Avatar>props.data;
|
||||
return {
|
||||
bg: `/icon/bg/${avatar.rarity}-Star.webp`,
|
||||
icon: `/WIKI/character/${avatar.id}.webp`,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
innerHeight: 0,
|
||||
innerText: "",
|
||||
clickable: false,
|
||||
lt: `/icon/element/${getZhElement(avatar.element)}元素.webp`,
|
||||
ltSize: "30px",
|
||||
};
|
||||
} else {
|
||||
const weapon = <TGApp.Game.Avatar.WeaponDetail>props.data;
|
||||
return {
|
||||
bg: `/icon/bg/${weapon.rarity}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${weapon.id}.webp`,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
innerHeight: 0,
|
||||
innerText: "",
|
||||
clickable: false,
|
||||
lt: `/icon/weapon/${weapon.type_name}.webp`,
|
||||
ltSize: "30px",
|
||||
};
|
||||
}
|
||||
});
|
||||
const info = computed(() => {
|
||||
if (props.mode === "avatar") {
|
||||
const avatar = <TGApp.Game.Avatar.Avatar>props.data;
|
||||
return `好感 ${avatar.fetter}`;
|
||||
} else {
|
||||
const weapon = <TGApp.Game.Avatar.WeaponDetail>props.data;
|
||||
return `精炼 ${weapon.affix_level}`;
|
||||
}
|
||||
const boxData = computed<TItemBoxData>(() => ({
|
||||
bg: `/icon/bg/${props.data.rarity}-Star.webp`,
|
||||
icon: `/WIKI/${props.mode}/${props.data.id}.webp`,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
innerHeight: 0,
|
||||
innerText: "",
|
||||
clickable: false,
|
||||
lt:
|
||||
props.mode === "character"
|
||||
? `/icon/element/${getZhElement(props.data.element)}元素.webp`
|
||||
: `/icon/weapon/${props.data.type_name}.webp`,
|
||||
ltSize: "30px",
|
||||
}));
|
||||
const info = computed<string>(() => {
|
||||
if (props.mode === "character") return `好感 ${props.data.fetter}`;
|
||||
else return `精炼 ${props.data.affix_level}`;
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -13,16 +13,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, watch, ref, onUnmounted } from "vue";
|
||||
import { onMounted, onUnmounted, shallowRef, watch } from "vue";
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare.js";
|
||||
import { saveImgLocal } from "@/utils/TGShare.js";
|
||||
|
||||
interface DucDetailOrtProps {
|
||||
modelValue: TGApp.Game.Avatar.Skill[];
|
||||
}
|
||||
type DucDetailOrtProps = { modelValue: Array<TGApp.Game.Avatar.Skill> };
|
||||
|
||||
const props = defineProps<DucDetailOrtProps>();
|
||||
const talents = ref<TGApp.Game.Avatar.Skill[]>([]);
|
||||
const talents = shallowRef<Array<TGApp.Game.Avatar.Skill>>([]);
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const tempTalent = JSON.parse(JSON.stringify(props.modelValue));
|
||||
@@ -33,9 +31,7 @@ async function loadData(): Promise<void> {
|
||||
talents.value = tempTalent;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
onMounted(async () => await loadData());
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => {
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
interface ducDetailRelicProps {
|
||||
modelValue: TGApp.Game.Avatar.Relic | false;
|
||||
pos: number;
|
||||
}
|
||||
type ducDetailRelicProps = { modelValue: TGApp.Game.Avatar.Relic | false; pos: number };
|
||||
|
||||
const props = defineProps<ducDetailRelicProps>();
|
||||
const relicBg = computed<string>(() => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="duc-doc-bgc" />
|
||||
<!-- 左上角色跟武器 -->
|
||||
<div class="duc-doc-lt">
|
||||
<DucDetailOlt :data="props.modelValue.avatar" mode="avatar" />
|
||||
<DucDetailOlt :data="props.modelValue.avatar" mode="character" />
|
||||
<DucDetailOlt :data="props.modelValue.weapon" mode="weapon" />
|
||||
<div class="duc-relic">
|
||||
<DucDetailRelic
|
||||
@@ -41,27 +41,24 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TSUserAvatar from "@Sqlite/modules/userAvatar.js";
|
||||
import { app } from "@tauri-apps/api";
|
||||
import { computed, ref, watch, onMounted } from "vue";
|
||||
|
||||
import TSUserAvatar from "../../plugins/Sqlite/modules/userAvatar.js";
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
|
||||
import DucDetailOlb from "./duc-detail-olb.vue";
|
||||
import DucDetailOlt from "./duc-detail-olt.vue";
|
||||
import DucDetailOrt from "./duc-detail-ort.vue";
|
||||
import DucDetailRelic from "./duc-detail-relic.vue";
|
||||
|
||||
interface DucDetailOverlayProps {
|
||||
modelValue: TGApp.Sqlite.Character.UserRole;
|
||||
}
|
||||
import { generateShareImg } from "@/utils/TGShare.js";
|
||||
|
||||
type fixedLenArray<T, N extends number> = [T, ...T[]] & { length: N };
|
||||
type RelicList = fixedLenArray<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
type DucDetailOverlayProps = { modelValue: TGApp.Sqlite.Character.UserRole };
|
||||
type fixedLenArr<T, N extends number> = [T, ...Array<T>] & { length: N };
|
||||
type RelicList = fixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
|
||||
const props = defineProps<DucDetailOverlayProps>();
|
||||
const version = await app.getVersion();
|
||||
|
||||
const version = ref<string>();
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const relicList = computed<RelicList>(() => {
|
||||
@@ -77,22 +74,22 @@ const relicList = computed<RelicList>(() => {
|
||||
const nameCard = ref<string | false>(false);
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
version.value = await app.getVersion();
|
||||
loadData();
|
||||
});
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => {
|
||||
await loadData();
|
||||
},
|
||||
);
|
||||
watch(() => props.modelValue, loadData);
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
function loadData(): void {
|
||||
const card = TSUserAvatar.getAvatarCard(props.modelValue.cid);
|
||||
nameCard.value = `/source/nameCard/profile/${card}.webp`;
|
||||
}
|
||||
|
||||
async function share(): Promise<void> {
|
||||
const detailBox = <HTMLElement>document.querySelector(".duc-do-container");
|
||||
const detailBox = document.querySelector<HTMLElement>(".duc-do-container");
|
||||
if (detailBox === null) {
|
||||
showSnackbar.error("未找到角色详情");
|
||||
return;
|
||||
}
|
||||
const fileName = `【角色详情】-${props.modelValue.avatar.name}`;
|
||||
loading.value = true;
|
||||
await generateShareImg(fileName, detailBox);
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
</TucDetailDesc>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { parseHtmlText } from "../../utils/toolFunc.js";
|
||||
|
||||
import TucDetailConstellation from "./tuc-detail-constellation.vue";
|
||||
import TucDetailDesc from "./tuc-detail-desc.vue";
|
||||
|
||||
import { parseHtmlText } from "@/utils/toolFunc.js";
|
||||
|
||||
defineProps<{ modelValue: TGApp.Game.Avatar.Constellation }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -4,21 +4,21 @@
|
||||
<span>圣遗物</span>
|
||||
</template>
|
||||
<template #content>
|
||||
<TucDetailRelic :model-value="props.modelValue" :pos="props.modelValue.pos" />
|
||||
<TucDetailRelic :model-value="modelValue" :pos="modelValue.pos" />
|
||||
<div class="tuc-ddr-content">
|
||||
<div class="tuc-ddrc-top">
|
||||
<span>{{ props.modelValue.name }}</span>
|
||||
<span>{{ modelValue.name }}</span>
|
||||
<span>+</span>
|
||||
<span>{{ props.modelValue.level }}</span>
|
||||
<span>{{ modelValue.level }}</span>
|
||||
</div>
|
||||
<div class="tuc-ddrc-bottom">
|
||||
<img :src="`/icon/star/${props.modelValue.rarity}.webp`" alt="star" />
|
||||
<img :src="`/icon/star/${modelValue.rarity}.webp`" alt="star" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #desc>
|
||||
<div class="tuc-ddrd-title">{{ props.modelValue.set.name }}:</div>
|
||||
<div v-for="(desc, index) in props.modelValue.set.affixes" :key="index" class="tuc-ddrc-desc">
|
||||
<div class="tuc-ddrd-title">{{ modelValue.set.name }}:</div>
|
||||
<div v-for="(desc, index) in modelValue.set.affixes" :key="index" class="tuc-ddrc-desc">
|
||||
<span>{{ desc.activation_number }}件套:</span>
|
||||
<span>{{ desc.effect }}</span>
|
||||
</div>
|
||||
@@ -29,11 +29,7 @@
|
||||
import TucDetailDesc from "./tuc-detail-desc.vue";
|
||||
import TucDetailRelic from "./tuc-detail-relic.vue";
|
||||
|
||||
interface TucDetailDescRelicProps {
|
||||
modelValue: TGApp.Game.Avatar.Relic;
|
||||
}
|
||||
|
||||
const props = defineProps<TucDetailDescRelicProps>();
|
||||
defineProps<{ modelValue: TGApp.Game.Avatar.Relic }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-ddr-content {
|
||||
|
||||
@@ -4,44 +4,35 @@
|
||||
<span>武器</span>
|
||||
</template>
|
||||
<template #content>
|
||||
<TucDetailItemBox v-model="box" />
|
||||
<TucDetailItemBox
|
||||
:icon="`/icon/bg/${modelValue.rarity}-Star.webp`"
|
||||
:bg="`/WIKI/weapon/${modelValue.id}.webp`"
|
||||
/>
|
||||
<div class="tuc-ddw-content">
|
||||
<div class="tuc-ddwc-top">
|
||||
<span>{{ props.modelValue.name }}</span>
|
||||
<span>Lv.{{ props.modelValue.level }}</span>
|
||||
<span>{{ modelValue.name }}</span>
|
||||
<span>Lv.{{ modelValue.level }}</span>
|
||||
<span>精炼</span>
|
||||
<span>{{ props.modelValue.affix_level }}</span>
|
||||
<span>{{ modelValue.affix_level }}</span>
|
||||
<span>阶</span>
|
||||
</div>
|
||||
<div class="tuc-ddwc-bottom">
|
||||
<img :src="`/icon/star/${props.modelValue.rarity}.webp`" alt="star" />
|
||||
<img :src="`/icon/star/${modelValue.rarity}.webp`" alt="star" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #desc>
|
||||
<span v-if="props.modelValue.desc" v-html="parseHtmlText(props.modelValue.desc)"></span>
|
||||
<span v-if="modelValue.desc" v-html="parseHtmlText(modelValue.desc)"></span>
|
||||
</template>
|
||||
</TucDetailDesc>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import { parseHtmlText } from "../../utils/toolFunc.js";
|
||||
|
||||
import TucDetailDesc from "./tuc-detail-desc.vue";
|
||||
import TucDetailItemBox from "./tuc-detail-itembox.vue";
|
||||
|
||||
interface TucDetailDescWeaponProps {
|
||||
modelValue: TGApp.Game.Avatar.WeaponDetail;
|
||||
}
|
||||
import { parseHtmlText } from "@/utils/toolFunc.js";
|
||||
|
||||
const props = defineProps<TucDetailDescWeaponProps>();
|
||||
const box = computed(() => {
|
||||
return {
|
||||
bg: `/icon/bg/${props.modelValue.rarity}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${props.modelValue.id}.webp`,
|
||||
};
|
||||
});
|
||||
defineProps<{ modelValue: TGApp.Game.Avatar.WeaponDetail }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-ddw-content {
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
<template>
|
||||
<div class="tuc-dd-box">
|
||||
<div class="tuc-dd-title">
|
||||
<slot name="title" />
|
||||
</div>
|
||||
<div class="tuc-dd-title"><slot name="title" /></div>
|
||||
<div class="tuc-dd-divider" />
|
||||
<div class="tuc-dd-content">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
<div class="tuc-dd-desc">
|
||||
<slot name="desc" />
|
||||
</div>
|
||||
<div class="tuc-dd-content"><slot name="content" /></div>
|
||||
<div class="tuc-dd-desc"><slot name="desc" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
<template>
|
||||
<div class="tuc-dib-box">
|
||||
<div v-if="modelValue.bg" class="tuc-dib-bg">
|
||||
<img :src="modelValue.bg" alt="bg" />
|
||||
<div v-if="bg" class="tuc-dib-bg">
|
||||
<img :src="bg" alt="bg" />
|
||||
</div>
|
||||
<div v-if="modelValue.icon" class="tuc-dib-icon">
|
||||
<img :src="modelValue.icon" alt="icon" />
|
||||
<div v-if="icon" class="tuc-dib-icon">
|
||||
<img :src="icon" alt="icon" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TucDetailItemBoxProps {
|
||||
modelValue: {
|
||||
icon?: string;
|
||||
bg?: string;
|
||||
};
|
||||
}
|
||||
|
||||
defineProps<TucDetailItemBoxProps>();
|
||||
defineProps<{ icon?: string; bg?: string }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-dib-box {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- todo 调整逻辑 -->
|
||||
<template>
|
||||
<div class="tuc-do-box">
|
||||
<img :src="bg" alt="role" class="tuc-do-bg" />
|
||||
@@ -9,7 +10,10 @@
|
||||
:style="`opacity: ${selected.pos === 0 ? '1' : '0.5'}`"
|
||||
@click="showDetail(props.modelValue.weapon, '武器', 0)"
|
||||
>
|
||||
<TucDetailItemBox :model-value="weaponBox" />
|
||||
<TucDetailItemBox
|
||||
:icon="`/WIKI/weapon/${props.modelValue.weapon.id}.webp`"
|
||||
:bg="`/icon/bg/${props.modelValue.weapon.rarity}-Star.webp`"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in relicList"
|
||||
@@ -74,7 +78,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import TucDetailConstellation from "./tuc-detail-constellation.vue";
|
||||
import TucDetailDescConstellation from "./tuc-detail-desc-constellation.vue";
|
||||
@@ -83,17 +87,10 @@ import TucDetailDescWeapon from "./tuc-detail-desc-weapon.vue";
|
||||
import TucDetailItemBox from "./tuc-detail-itembox.vue";
|
||||
import TucDetailRelic from "./tuc-detail-relic.vue";
|
||||
|
||||
interface ToUcDetailProps {
|
||||
modelValue: TGApp.Sqlite.Character.UserRole;
|
||||
}
|
||||
|
||||
interface ToUcDetailSelect {
|
||||
type: "命座" | "武器" | "圣遗物";
|
||||
pos: number;
|
||||
}
|
||||
|
||||
type fixedLenArray<T, N extends number> = [T, ...T[]] & { length: N };
|
||||
type RelicList = fixedLenArray<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
type ToUcDetailProps = { modelValue: TGApp.Sqlite.Character.UserRole };
|
||||
type ToUcDetailSelect = { type: "命座" | "武器" | "圣遗物"; pos: number };
|
||||
type fixedLenArr<T, N extends number> = [T, ...Array<T>] & { length: N };
|
||||
type RelicList = fixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
|
||||
const props = defineProps<ToUcDetailProps>();
|
||||
const relicList = computed<RelicList>(() => {
|
||||
@@ -105,28 +102,15 @@ const relicList = computed<RelicList>(() => {
|
||||
props.modelValue.relics.find((item) => item.pos === 5) || false,
|
||||
];
|
||||
});
|
||||
const weaponBox = computed(() => {
|
||||
const weapon = props.modelValue.weapon;
|
||||
return {
|
||||
icon: `/WIKI/weapon/${weapon.id}.webp`,
|
||||
bg: `/icon/bg/${weapon.rarity}-Star.webp`,
|
||||
};
|
||||
});
|
||||
const showCostumeSwitch = ref(false);
|
||||
const selectConstellation = ref<TGApp.Game.Avatar.Constellation>();
|
||||
const selectRelic = ref<TGApp.Game.Avatar.Relic>();
|
||||
const selected = ref<ToUcDetailSelect>({ type: "武器", pos: 0 });
|
||||
const bg = computed<string>(() => {
|
||||
return showCostumeSwitch.value
|
||||
? props.modelValue.costumes[0].icon
|
||||
: props.modelValue.avatar.image;
|
||||
});
|
||||
const bgTransY = computed<string>(() => {
|
||||
return showCostumeSwitch.value ? "0" : "10px";
|
||||
});
|
||||
const bgFit = computed<string>(() => {
|
||||
return showCostumeSwitch.value ? "cover" : "contain";
|
||||
});
|
||||
const showCostumeSwitch = ref<boolean>(false);
|
||||
const selectConstellation = shallowRef<TGApp.Game.Avatar.Constellation>();
|
||||
const selectRelic = shallowRef<TGApp.Game.Avatar.Relic>();
|
||||
const selected = shallowRef<ToUcDetailSelect>({ type: "武器", pos: 0 });
|
||||
const bg = computed<string>(() =>
|
||||
showCostumeSwitch.value ? props.modelValue.costumes[0].icon : props.modelValue.avatar.image,
|
||||
);
|
||||
const bgTransY = computed<string>(() => (showCostumeSwitch.value ? "0" : "10px"));
|
||||
const bgFit = computed<string>(() => (showCostumeSwitch.value ? "cover" : "contain"));
|
||||
|
||||
// 加载数据
|
||||
function loadData(): void {
|
||||
@@ -136,15 +120,8 @@ function loadData(): void {
|
||||
showCostumeSwitch.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
loadData();
|
||||
},
|
||||
);
|
||||
onMounted(() => loadData());
|
||||
watch(() => props.modelValue, loadData);
|
||||
|
||||
function showDetail(
|
||||
item:
|
||||
@@ -168,10 +145,7 @@ function showDetail(
|
||||
default:
|
||||
break;
|
||||
}
|
||||
selected.value = {
|
||||
type: selectType,
|
||||
pos: selectPos,
|
||||
};
|
||||
selected.value = { type: selectType, pos: selectPos };
|
||||
}
|
||||
|
||||
function switchBg(): void {
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
<template>
|
||||
<div class="tuc-dr-box">
|
||||
<div class="tuc-dr-bg">
|
||||
<img :src="`/icon/relic/${props.pos}.webp`" alt="relic" />
|
||||
<img :src="`/icon/relic/${pos}.webp`" alt="relic" />
|
||||
</div>
|
||||
<div v-if="props.modelValue" class="tuc-dr-bg">
|
||||
<img :src="`/icon/bg/${props.modelValue.rarity}-Star.webp`" alt="bg" />
|
||||
<div v-if="modelValue" class="tuc-dr-bg">
|
||||
<img :src="`/icon/bg/${modelValue.rarity}-Star.webp`" alt="bg" />
|
||||
</div>
|
||||
<div v-if="props.modelValue" class="tuc-dr-icon">
|
||||
<img :src="props.modelValue.icon" alt="relic" />
|
||||
<div v-if="modelValue" class="tuc-dr-icon">
|
||||
<img :src="modelValue.icon" alt="relic" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TucDetailRelicProps {
|
||||
modelValue: TGApp.Game.Avatar.Relic | false;
|
||||
pos: number;
|
||||
}
|
||||
|
||||
const props = defineProps<TucDetailRelicProps>();
|
||||
defineProps<{ modelValue: TGApp.Game.Avatar.Relic | false; pos: number }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-dr-box {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
<template>
|
||||
<div class="tuca-box">
|
||||
<TItembox v-for="(item, idx) in props.modelValue" :key="idx" :model-value="getItemBox(item)" />
|
||||
<TItemBox v-for="(item, idx) in props.modelValue" :key="idx" :model-value="getItemBox(item)" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
import { getZhElement } from "../../utils/toolFunc.js";
|
||||
import TItembox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import { getZhElement } from "@/utils/toolFunc.js";
|
||||
|
||||
interface TucAvatarsProps {
|
||||
modelValue: TGApp.Game.Combat.Avatar[];
|
||||
}
|
||||
type TucAvatarsProps = { modelValue: Array<TGApp.Game.Combat.Avatar> };
|
||||
|
||||
const props = defineProps<TucAvatarsProps>();
|
||||
const columnCnt = computed<number>(() => {
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
interface TucBuffProps {
|
||||
modelValue: TGApp.Game.Combat.SplendourBuff;
|
||||
}
|
||||
type TucBuffProps = { modelValue: TGApp.Game.Combat.SplendourBuff };
|
||||
|
||||
const props = defineProps<TucBuffProps>();
|
||||
const columnCnt = computed<number>(() => {
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
<template>
|
||||
<div class="tuc-cards-box">
|
||||
<div
|
||||
class="tuc-cards-item"
|
||||
v-for="(card, idx) in props.modelValue"
|
||||
:key="idx"
|
||||
:title="card.name"
|
||||
>
|
||||
<div class="tuc-cards-item" v-for="(card, idx) in modelValue" :key="idx" :title="card.name">
|
||||
<img :src="card.icon" :alt="card.name" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TucCardsProps {
|
||||
modelValue: TGApp.Game.Combat.Card[];
|
||||
}
|
||||
|
||||
const props = defineProps<TucCardsProps>();
|
||||
defineProps<{ modelValue: Array<TGApp.Game.Combat.Card> }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-cards-box {
|
||||
|
||||
@@ -3,32 +3,29 @@
|
||||
<div class="tucfi-label">
|
||||
<slot name="label">{{ props.label }}</slot>
|
||||
</div>
|
||||
<div v-if="props.data === null">
|
||||
<span class="tucfi-data">暂无数据</span>
|
||||
</div>
|
||||
<div v-if="props.data === null"><span class="tucfi-data">暂无数据</span></div>
|
||||
<div v-else-if="!Array.isArray(props.data)" class="tucfi-data">
|
||||
<TItembox :model-value="getBox()" />
|
||||
<TItemBox :model-value="getBox(props.data)" />
|
||||
</div>
|
||||
<div class="tucfi-icons" v-else>
|
||||
<div v-for="(item, idx) in props.data" :key="idx" class="tucfi-icon">
|
||||
<TItembox :model-value="getBox2(item)" />
|
||||
<TItemBox :model-value="getBox2(item)" />
|
||||
</div>
|
||||
<div v-if="props.data.length === 0" class="tucfi-data">暂无数据</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TItembox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
|
||||
interface TucFightProps {
|
||||
type TucFightProps = {
|
||||
label: string;
|
||||
data: TGApp.Game.Combat.AvatarMini | TGApp.Game.Combat.AvatarMini[] | null;
|
||||
}
|
||||
data: TGApp.Game.Combat.AvatarMini | Array<TGApp.Game.Combat.AvatarMini> | null;
|
||||
};
|
||||
|
||||
const props = defineProps<TucFightProps>();
|
||||
|
||||
function getBox(): TItemBoxData {
|
||||
const role = <TGApp.Game.Combat.AvatarMini>props.data;
|
||||
function getBox(role: TGApp.Game.Combat.AvatarMini): TItemBoxData {
|
||||
return {
|
||||
bg: `/icon/bg/${role.rarity === 105 ? 5 : role.rarity}-BGC.webp`,
|
||||
clickable: false,
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { computed } from "vue";
|
||||
|
||||
import { AppCharacterData } from "../../data/index.js";
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import { timestampToDate } from "../../utils/toolFunc.js";
|
||||
import TItemBox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showLoading from "../func/loading.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { AppCharacterData } from "@/data/index.js";
|
||||
import { generateShareImg } from "@/utils/TGShare.js";
|
||||
import { timestampToDate } from "@/utils/toolFunc.js";
|
||||
|
||||
type TucOverlayProps = {
|
||||
modelValue: boolean;
|
||||
@@ -39,9 +39,9 @@ const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const raw = computed<TGApp.Plugins.Hutao.Base.Rate[]>(() => {
|
||||
const raw = computed<Array<TGApp.Plugins.Hutao.Base.Rate>>(() => {
|
||||
if (!props.data) return [];
|
||||
const res: TGApp.Plugins.Hutao.Base.Rate[] = props.data.BackupAvatarRates;
|
||||
const res = props.data.BackupAvatarRates;
|
||||
return res.sort((a, b) => b.Rate - a.Rate);
|
||||
});
|
||||
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
import TucFight from "./tuc-fight.vue";
|
||||
import TucTile from "./tuc-tile.vue";
|
||||
|
||||
interface TucOverviewProps {
|
||||
data: TGApp.Game.Combat.Stat;
|
||||
fights: TGApp.Game.Combat.FightStatisic;
|
||||
}
|
||||
type TucOverviewProps = { data: TGApp.Game.Combat.Stat; fights: TGApp.Game.Combat.FightStatisic };
|
||||
|
||||
const props = defineProps<TucOverviewProps>();
|
||||
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
<template>
|
||||
<div class="tucr-box">
|
||||
<div class="tucr-title">
|
||||
<img :src="`/icon/star/combat${props.modelValue.is_get_medal ? 1 : 0}.webp`" alt="combat" />
|
||||
<span class="main">第{{ props.modelValue.round_id }}幕</span>
|
||||
<span class="sub">{{ timestampToDate(Number(props.modelValue.finish_time) * 1000) }}</span>
|
||||
<img :src="`/icon/star/combat${modelValue.is_get_medal ? 1 : 0}.webp`" alt="combat" />
|
||||
<span class="main">第{{ modelValue.round_id }}幕</span>
|
||||
<span class="sub">{{ timestampToDate(Number(modelValue.finish_time) * 1000) }}</span>
|
||||
</div>
|
||||
<div class="tucr-content">
|
||||
<TucSub title="出演角色" class="main">
|
||||
<TucAvatars :model-value="props.modelValue.avatars" />
|
||||
<TucAvatars :model-value="modelValue.avatars" />
|
||||
</TucSub>
|
||||
<TucSub title="辉彩祝福" class="main">
|
||||
<TucBuffs :model-value="props.modelValue.splendour_buff" />
|
||||
<TucBuffs :model-value="modelValue.splendour_buff" />
|
||||
</TucSub>
|
||||
<TucSub :title="`神秘收获(${props.modelValue.choice_cards.length})`" class="sub">
|
||||
<TucCards :model-value="props.modelValue.choice_cards" />
|
||||
<TucSub :title="`神秘收获(${modelValue.choice_cards.length})`" class="sub">
|
||||
<TucCards :model-value="modelValue.choice_cards" />
|
||||
</TucSub>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { timestampToDate } from "../../utils/toolFunc.js";
|
||||
|
||||
import TucAvatars from "./tuc-avatars.vue";
|
||||
import TucBuffs from "./tuc-buffs.vue";
|
||||
import TucCards from "./tuc-cards.vue";
|
||||
import TucSub from "./tuc-sub.vue";
|
||||
|
||||
interface TucRoundProps {
|
||||
modelValue: TGApp.Game.Combat.RoundData;
|
||||
}
|
||||
import { timestampToDate } from "@/utils/toolFunc.js";
|
||||
|
||||
const props = defineProps<TucRoundProps>();
|
||||
defineProps<{ modelValue: TGApp.Game.Combat.RoundData }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tucr-box {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
<template>
|
||||
<div class="tuc-sub-box">
|
||||
<div class="tuc-sub-title">{{ props.title }}</div>
|
||||
<div class="tuc-sub-title">{{ title }}</div>
|
||||
<slot name="default" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TucSubProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const props = defineProps<TucSubProps>();
|
||||
defineProps<{ title: string }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-sub-box {
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
<template>
|
||||
<div class="tuct-box">
|
||||
<div class="tuct-title">
|
||||
<slot name="title">{{ props.title }}</slot>
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</div>
|
||||
<div class="tuct-text" v-if="!Array.isArray(props.val)">
|
||||
<slot name="text">{{ props.val }}</slot>
|
||||
<div class="tuct-text" v-if="!Array.isArray(val)">
|
||||
<slot name="text">{{ val }}</slot>
|
||||
</div>
|
||||
<div class="tuct-icons" v-else>
|
||||
<img
|
||||
v-for="(val, idx) in props.val"
|
||||
:key="idx"
|
||||
:src="`/icon/star/combat${val}.webp`"
|
||||
:alt="`${val}`"
|
||||
/>
|
||||
<img v-for="(v, idx) in val" :key="idx" :src="`/icon/star/combat${v}.webp`" :alt="`${v}`" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TucTileProps {
|
||||
title: string;
|
||||
val: string | number | number[];
|
||||
}
|
||||
|
||||
const props = defineProps<TucTileProps>();
|
||||
defineProps<{ title: string; val: string | number | Array<number> }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuct-box {
|
||||
|
||||
@@ -17,28 +17,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TSUserGacha from "@Sqlite/modules/userGacha.js";
|
||||
import { computed } from "vue";
|
||||
|
||||
import { AppGachaData } from "../../data/index.js";
|
||||
import TSUserGacha from "../../plugins/Sqlite/modules/userGacha.js";
|
||||
import { AppGachaData } from "@/data/index.js";
|
||||
|
||||
export interface GroDataLineProps {
|
||||
data: TGApp.Sqlite.GachaRecords.SingleTable;
|
||||
count: number;
|
||||
}
|
||||
export type GroDataLineProps = { data: TGApp.Sqlite.GachaRecords.SingleTable; count: number };
|
||||
|
||||
const props = defineProps<GroDataLineProps>();
|
||||
const hint = getEndHint();
|
||||
|
||||
function getIcon(): string {
|
||||
const itemType = TSUserGacha.getGachaItemType(props.data.itemId);
|
||||
if (itemType[0] === "角色") {
|
||||
return `/WIKI/character/${props.data.itemId}.webp`;
|
||||
} else if (itemType[0] === "武器") {
|
||||
return `/WIKI/weapon/${props.data.itemId}.webp`;
|
||||
} else {
|
||||
return `/source/UI/paimon.webp`;
|
||||
}
|
||||
if (itemType[0] === "角色") return `/WIKI/character/${props.data.itemId}.webp`;
|
||||
if (itemType[0] === "武器") return `/WIKI/weapon/${props.data.itemId}.webp`;
|
||||
return `/source/UI/paimon.webp`;
|
||||
}
|
||||
|
||||
function getEndHint(): string {
|
||||
@@ -55,7 +48,8 @@ function getEndHint(): string {
|
||||
if (props.data.rank === "5") {
|
||||
if (poolsFind.some((pool) => pool.up5List.includes(Number(props.data.itemId)))) return "UP";
|
||||
return "歪";
|
||||
} else if (props.data.rank === "4") {
|
||||
}
|
||||
if (props.data.rank === "4") {
|
||||
if (poolsFind.some((pool) => pool.up4List.includes(Number(props.data.itemId)))) return "UP";
|
||||
return "歪";
|
||||
}
|
||||
@@ -73,11 +67,8 @@ const progressWidth = computed<string>(() => {
|
||||
if (props.data.rank === "5") {
|
||||
if (props.data.gachaType === "302") final = 80;
|
||||
else final = 90;
|
||||
} else if (props.data.rank === "4") {
|
||||
final = 10;
|
||||
} else {
|
||||
return "0%";
|
||||
}
|
||||
} else if (props.data.rank === "4") final = 10;
|
||||
else return "0%";
|
||||
return ((props.count / final) * 100).toFixed(2) + "%";
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -60,14 +60,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import GroDataLine, { GroDataLineProps } from "./gro-data-line.vue";
|
||||
import GroDataLine, { type GroDataLineProps } from "./gro-data-line.vue";
|
||||
|
||||
interface GachaDataViewProps {
|
||||
type GachaDataViewProps = {
|
||||
dataType: "new" | "avatar" | "weapon" | "normal" | "mix";
|
||||
dataVal: TGApp.Sqlite.GachaRecords.SingleTable[];
|
||||
}
|
||||
dataVal: Array<TGApp.Sqlite.GachaRecords.SingleTable>;
|
||||
};
|
||||
|
||||
const props = defineProps<GachaDataViewProps>();
|
||||
|
||||
@@ -76,8 +76,8 @@ const loading = ref<boolean>(true); // 是否加载完
|
||||
const title = ref<string>(""); // 卡片标题
|
||||
const startDate = ref<string>(""); // 最早的时间
|
||||
const endDate = ref<string>(""); // 最晚的时间
|
||||
const star5List = ref<GroDataLineProps[]>([]); // 5星物品数据
|
||||
const star4List = ref<GroDataLineProps[]>([]); // 4星物品数据
|
||||
const star5List = shallowRef<Array<GroDataLineProps>>([]); // 5星物品数据
|
||||
const star4List = shallowRef<Array<GroDataLineProps>>([]); // 4星物品数据
|
||||
const reset5count = ref<number>(1); // 5星垫抽数量
|
||||
const reset4count = ref<number>(1); // 4星垫抽数量
|
||||
const star3count = ref<number>(0); // 3星物品数量
|
||||
@@ -92,6 +92,8 @@ onMounted(() => {
|
||||
function loadData(): void {
|
||||
title.value = getTitle("top");
|
||||
const tempData = props.dataVal;
|
||||
const temp5Data: Array<GroDataLineProps> = [];
|
||||
const temp4Data: Array<GroDataLineProps> = [];
|
||||
// 按照 id 升序
|
||||
tempData
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
@@ -106,24 +108,17 @@ function loadData(): void {
|
||||
star3count.value++;
|
||||
} else if (item.rank === "4") {
|
||||
reset5count.value++;
|
||||
star4List.value.push({
|
||||
data: item,
|
||||
count: reset4count.value,
|
||||
});
|
||||
temp4Data.push({ data: item, count: reset4count.value });
|
||||
reset4count.value = 1;
|
||||
} else if (item.rank === "5") {
|
||||
reset4count.value++;
|
||||
star5List.value.push({
|
||||
data: item,
|
||||
count: reset5count.value,
|
||||
});
|
||||
temp5Data.push({ data: item, count: reset5count.value });
|
||||
reset5count.value = 1;
|
||||
}
|
||||
});
|
||||
star5avg.value = getStar5Avg();
|
||||
// 两个列表反序
|
||||
star5List.value.reverse();
|
||||
star4List.value.reverse();
|
||||
star5List.value = temp5Data.reverse();
|
||||
star4List.value = temp4Data.reverse();
|
||||
}
|
||||
|
||||
// 获取标题
|
||||
@@ -135,24 +130,24 @@ function getTitle(type: "top" | "5" | "4" | "3"): string {
|
||||
if (props.dataType === "normal") return "常驻祈愿";
|
||||
if (props.dataType === "mix") return "集录祈愿";
|
||||
return "";
|
||||
} else if (props.dataVal.length === 0) {
|
||||
return "暂无数据";
|
||||
} else if (type === "5") {
|
||||
}
|
||||
if (props.dataVal.length === 0) return "暂无数据";
|
||||
if (type === "5") {
|
||||
// 5星物品统计 00.00%
|
||||
return `${star5List.value.length} [${((star5List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
} else if (type === "4") {
|
||||
}
|
||||
if (type === "4") {
|
||||
// 4星物品统计
|
||||
return `${star4List.value.length} [${((star4List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
} else {
|
||||
// 3星物品统计
|
||||
return `${star3count.value} [${((star3count.value * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
}
|
||||
// 3星物品统计
|
||||
return `${star3count.value} [${((star3count.value * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
}
|
||||
|
||||
// 获取5星平均抽数
|
||||
@@ -5,15 +5,19 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// about import err,see:https://github.com/apache/echarts/issues/19992
|
||||
// @ts-expect-error no-exported-member
|
||||
import { PieChart } from "echarts/charts.js";
|
||||
import {
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
ToolboxComponent,
|
||||
TooltipComponent,
|
||||
} from "echarts/components.js";
|
||||
// @ts-expect-error no-exported-member
|
||||
import { use } from "echarts/core.js";
|
||||
// @ts-expect-error no-exported-member
|
||||
import { LabelLayout } from "echarts/features.js";
|
||||
// @ts-expect-error no-exported-member
|
||||
import { CanvasRenderer } from "echarts/renderers.js";
|
||||
import type { EChartsOption } from "echarts/types/dist/shared.js";
|
||||
import { provide } from "vue";
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="gro-pi-time">{{ getTimeStr(pool) }}</div>
|
||||
<div class="gro-pi-sub">Up 五星</div>
|
||||
<div class="gro-pool-up lv5">
|
||||
<TItembox
|
||||
<TItemBox
|
||||
v-for="i in pool.up5List"
|
||||
:key="i"
|
||||
:model-value="getBox(i)"
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<div class="gro-pi-sub">Up 四星</div>
|
||||
<div class="gro-pool-up lv4">
|
||||
<TItembox
|
||||
<TItemBox
|
||||
v-for="i in pool.up4List"
|
||||
:key="i"
|
||||
:model-value="getBox(i)"
|
||||
@@ -51,38 +51,32 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import TItemBox, { type TItemBoxData } from "@comp/app/t-itemBox.vue";
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { onMounted, ref, shallowRef } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { AppGachaData, AppCharacterData, AppWeaponData } from "../../data/index.js";
|
||||
import { createPost } from "../../utils/TGWindow.js";
|
||||
import { timestampToDate } from "../../utils/toolFunc.js";
|
||||
import TItembox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import showDialog from "../func/dialog.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
import { AppCharacterData, AppGachaData, AppWeaponData } from "@/data/index.js";
|
||||
import { createPost } from "@/utils/TGWindow.js";
|
||||
import { timestampToDate } from "@/utils/toolFunc.js";
|
||||
|
||||
interface GroHistoryMap {
|
||||
tab: string;
|
||||
value: TGApp.App.Gacha.PoolItem[];
|
||||
}
|
||||
type GroHistoryMap = { tab: string; value: Array<TGApp.App.Gacha.PoolItem> };
|
||||
|
||||
const historyTab = ref<string>("");
|
||||
const tabList = ref<GroHistoryMap[]>([]);
|
||||
const tabList = shallowRef<Array<GroHistoryMap>>([]);
|
||||
const router = useRouter();
|
||||
|
||||
onMounted(() => {
|
||||
const res: GroHistoryMap[] = [];
|
||||
AppGachaData.forEach((pool) => {
|
||||
const res: Array<GroHistoryMap> = [];
|
||||
for (const pool of AppGachaData) {
|
||||
const index = res.findIndex((item) => item.tab === pool.version);
|
||||
if (index === -1) {
|
||||
res.push({
|
||||
tab: pool.version,
|
||||
value: [pool],
|
||||
});
|
||||
} else {
|
||||
res[index].value.push(pool);
|
||||
res.push({ tab: pool.version, value: [pool] });
|
||||
continue;
|
||||
}
|
||||
});
|
||||
res[index].value.push(pool);
|
||||
}
|
||||
tabList.value = res.reverse();
|
||||
historyTab.value = res[0].tab;
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user