mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-12 09:18:14 +08:00
♻️ 代码结构调整
This commit is contained in:
38
src/App.vue
38
src/App.vue
@@ -34,13 +34,11 @@ import TGLogger from "./utils/TGLogger.js";
|
||||
import OtherApi from "./web/request/otherReq.js";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = storeToRefs(useUserStore());
|
||||
const router = useRouter();
|
||||
const { uid, briefInfo, account, cookie } = storeToRefs(useUserStore());
|
||||
const isMain = ref<boolean>(false);
|
||||
const theme = ref<string>(appStore.theme);
|
||||
const router = useRouter();
|
||||
const vuetifyTheme = computed(() => {
|
||||
return appStore.theme === "dark" ? "dark" : "light";
|
||||
});
|
||||
const vuetifyTheme = computed<string>(() => (appStore.theme === "dark" ? "dark" : "light"));
|
||||
|
||||
let themeListener: UnlistenFn | null = null;
|
||||
let urlListener: UnlistenFn | null = null;
|
||||
@@ -77,7 +75,7 @@ async function checkResize(): Promise<void> {
|
||||
),
|
||||
);
|
||||
await windowCur.setZoom((1 / screen.scaleFactor) * Math.min(widthScale, heightScale));
|
||||
await windowCur.center();
|
||||
await windowCur.setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,26 +159,26 @@ async function checkUserLoad(): Promise<void> {
|
||||
}
|
||||
if (!appStore.isLogin) appStore.isLogin = true;
|
||||
// 然后获取最近的UID
|
||||
if (userStore.uid.value === undefined || !uidDB.includes(userStore.uid.value)) {
|
||||
userStore.uid.value = uidDB[0];
|
||||
if (uid.value === undefined || !uidDB.includes(uid.value)) {
|
||||
uid.value = uidDB[0];
|
||||
}
|
||||
const curAccount = await TSUserAccount.account.getAccount(userStore.uid.value);
|
||||
const curAccount = await TSUserAccount.account.getAccount(uid.value);
|
||||
if (curAccount === false) {
|
||||
showSnackbar.error(`未获取到${userStore.uid.value}的账号数据!`);
|
||||
await TGLogger.Error(`[App][listenOnInit] 获取${userStore.uid.value}账号数据失败`);
|
||||
showSnackbar.error(`未获取到${uid.value}的账号数据!`);
|
||||
await TGLogger.Error(`[App][listenOnInit] 获取${uid.value}账号数据失败`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
} else {
|
||||
userStore.briefInfo.value = curAccount.brief;
|
||||
userStore.cookie.value = curAccount.cookie;
|
||||
briefInfo.value = curAccount.brief;
|
||||
cookie.value = curAccount.cookie;
|
||||
}
|
||||
const curGameAccount = await TSUserAccount.game.getCurAccount(userStore.uid.value);
|
||||
if (curGameAccount === false) {
|
||||
showSnackbar.error(`未获取到${userStore.uid.value}的游戏数据!`);
|
||||
await TGLogger.Error(`[App][listenOnInit] 获取${userStore.uid.value}游戏数据失败`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
} else {
|
||||
userStore.account.value = curGameAccount;
|
||||
const curGameAccount = await TSUserAccount.game.getCurAccount(uid.value);
|
||||
if (curGameAccount !== false) {
|
||||
account.value = curGameAccount;
|
||||
return;
|
||||
}
|
||||
showSnackbar.error(`未获取到${uid.value}的游戏数据!`);
|
||||
await TGLogger.Error(`[App][listenOnInit] 获取${uid.value}游戏数据失败`);
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
async function getDeepLink(): Promise<UnlistenFn> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<transition enter-from-class="tolo-enter-from" name="tolo">
|
||||
<div v-if="showTolo" class="tolo-box" @click.self.prevent="toClick">
|
||||
<div v-if="showTolo" class="tolo-box" @click.self.prevent="toClick()">
|
||||
<transition enter-from-class="toli-enter-from" name="toli">
|
||||
<slot v-if="showToli" />
|
||||
</transition>
|
||||
@@ -10,11 +10,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
type TolProps = { modelValue: boolean; toClick?: () => void; blurVal: string; hide?: true };
|
||||
|
||||
const props = withDefaults(defineProps<TolProps>(), { modelValue: false, blurVal: "20px" });
|
||||
const showTolo = ref<boolean>(!props.hide);
|
||||
const showToli = ref<boolean>(!props.hide);
|
||||
type TolProps = { modelValue: boolean; blurVal?: string; dismissible?: boolean };
|
||||
type TolEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
const emit = defineEmits<TolEmits>();
|
||||
const props = withDefaults(defineProps<TolProps>(), {
|
||||
modelValue: false,
|
||||
blurVal: "20px",
|
||||
dismissible: true,
|
||||
});
|
||||
const showTolo = ref<boolean>(false);
|
||||
const showToli = ref<boolean>(false);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
@@ -28,6 +33,11 @@ watch(
|
||||
setTimeout(() => (showTolo.value = false), 300);
|
||||
},
|
||||
);
|
||||
|
||||
function toClick(): void {
|
||||
if (!props.dismissible) return;
|
||||
emit("update:modelValue", false);
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tolo-enter-active,
|
||||
|
||||
@@ -198,16 +198,6 @@
|
||||
<img src="/source/UI/posts.png" alt="collect" class="side-icon-menu" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
class="side-item-menu"
|
||||
title="登录"
|
||||
@click="login"
|
||||
v-show="cookie?.stoken === ''"
|
||||
>
|
||||
<template #prepend>
|
||||
<img src="/source/UI/lumine.webp" class="side-icon-menu" alt="login" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-list-item :title.attr="themeTitle" @click="switchTheme()">
|
||||
@@ -233,7 +223,7 @@
|
||||
import { event, webviewWindow } from "@tauri-apps/api";
|
||||
import { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, onUnmounted, shallowRef } from "vue";
|
||||
import { computed, onMounted, onUnmounted } from "vue";
|
||||
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
@@ -241,14 +231,13 @@ import mhyClient from "../../utils/TGClient.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { cookie, briefInfo } = storeToRefs(useUserStore());
|
||||
|
||||
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 themeListener = shallowRef<UnlistenFn | null>(null);
|
||||
const rail = computed<boolean>({
|
||||
get: () => appStore.sidebar.collapse,
|
||||
set: (v: boolean) => (appStore.sidebar.collapse = v),
|
||||
set: (v) => (appStore.sidebar.collapse = v),
|
||||
});
|
||||
const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
|
||||
if (briefInfo.value && briefInfo.value.nickname) return briefInfo.value;
|
||||
@@ -261,12 +250,12 @@ const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
|
||||
});
|
||||
const themeGet = computed<string>({
|
||||
get: () => appStore.theme,
|
||||
set: (v: string) => (appStore.theme = v),
|
||||
set: (v) => (appStore.theme = v),
|
||||
});
|
||||
const themeTitle = computed<string>(() => (themeGet.value === "default" ? "夜间模式" : "日间模式"));
|
||||
|
||||
onMounted(async () => {
|
||||
themeListener.value = await event.listen("readTheme", (e: Event<string>) => {
|
||||
themeListener = await event.listen("readTheme", (e: Event<string>) => {
|
||||
const theme = e.payload;
|
||||
themeGet.value = theme === "default" ? "default" : "dark";
|
||||
});
|
||||
@@ -283,9 +272,9 @@ async function openClient(func: string): Promise<void> {
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (themeListener.value !== null) {
|
||||
themeListener.value();
|
||||
themeListener.value = null;
|
||||
if (themeListener !== null) {
|
||||
themeListener();
|
||||
themeListener = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<TOverlay
|
||||
v-model="visible"
|
||||
:hide="true"
|
||||
:to-click="onCancel"
|
||||
blur-val="20px"
|
||||
class="tolc-overlay"
|
||||
>
|
||||
<TOverlay v-model="visible" class="tolc-overlay">
|
||||
<div class="tolc-box">
|
||||
<div class="tolc-title">
|
||||
<span>兑换码</span>
|
||||
@@ -58,26 +52,20 @@ import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import TOverlay from "./t-overlay.vue";
|
||||
|
||||
interface ToLiveCodeProps {
|
||||
type ToLiveCodeProps = {
|
||||
data: TGApp.BBS.Navigator.CodeData[];
|
||||
actId: string | undefined;
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
};
|
||||
type ToLiveCodeEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
|
||||
const props = withDefaults(defineProps<ToLiveCodeProps>(), { modelValue: false });
|
||||
const emits = defineEmits<ToLiveCodeEmits>();
|
||||
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v: boolean) => emits("update:modelValue", v),
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
function onCancel(): void {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function copy(code: string): void {
|
||||
navigator.clipboard.writeText(code);
|
||||
showSnackbar.success("已复制到剪贴板");
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div v-if="props.data" class="ton-container">
|
||||
<slot name="left"></slot>
|
||||
<div class="ton-box">
|
||||
<img alt="bg" class="ton-bg" v-if="props.data" :src="props.data.profile" />
|
||||
<div class="ton-content">
|
||||
<span>{{ props.data.name }}</span>
|
||||
<span>{{ parseNamecard(props.data.desc) }}</span>
|
||||
<span>{{ parseNameCard(props.data.desc) }}</span>
|
||||
<span>获取途径:{{ props.data.source }}</span>
|
||||
</div>
|
||||
<div class="ton-type">{{ getType }}</div>
|
||||
<v-btn
|
||||
class="ton-share"
|
||||
@click="shareNamecard"
|
||||
@click="shareNameCard"
|
||||
variant="outlined"
|
||||
:loading="loading"
|
||||
data-html2canvas-ignore
|
||||
@@ -29,15 +29,11 @@
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import TOverlay from "./t-overlay.vue";
|
||||
|
||||
interface ToNamecardProps {
|
||||
modelValue: boolean;
|
||||
data?: TGApp.App.NameCard.Item;
|
||||
}
|
||||
|
||||
enum ToNamecardTypeEnum {
|
||||
enum ToNameCardTypeEnum {
|
||||
other = 0,
|
||||
achievement = 1,
|
||||
role = 2,
|
||||
@@ -46,55 +42,32 @@ enum ToNamecardTypeEnum {
|
||||
unknown = 5,
|
||||
}
|
||||
|
||||
type ToNamecardTypeMap = {
|
||||
[key in ToNamecardTypeEnum]: string;
|
||||
type ToNameCardTypeMap = { [key in ToNameCardTypeEnum]: string };
|
||||
type ToNameCardProps = { modelValue: boolean; data?: TGApp.App.NameCard.Item };
|
||||
type ToNameCardEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
const props = defineProps<ToNameCardProps>();
|
||||
const emits = defineEmits<ToNameCardEmits>();
|
||||
const typeMap: ToNameCardTypeMap = {
|
||||
0: "其他",
|
||||
1: "成就",
|
||||
2: "角色",
|
||||
3: "纪行",
|
||||
4: "活动",
|
||||
5: "未知",
|
||||
};
|
||||
|
||||
const typeMap: ToNamecardTypeMap = {
|
||||
[ToNamecardTypeEnum.other]: "其他",
|
||||
[ToNamecardTypeEnum.achievement]: "成就",
|
||||
[ToNamecardTypeEnum.role]: "角色",
|
||||
[ToNamecardTypeEnum.record]: "纪行",
|
||||
[ToNamecardTypeEnum.activity]: "活动",
|
||||
[ToNamecardTypeEnum.unknown]: "未知",
|
||||
};
|
||||
|
||||
type ToNamecardEmits = (e: "update:modelValue", value: boolean) => void;
|
||||
|
||||
const props = defineProps<ToNamecardProps>();
|
||||
|
||||
const emits = defineEmits<ToNamecardEmits>();
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const getType = computed(() => {
|
||||
if (!props.data) return typeMap[ToNamecardTypeEnum.unknown];
|
||||
switch (props.data.type) {
|
||||
case ToNamecardTypeEnum.achievement:
|
||||
return typeMap[ToNamecardTypeEnum.achievement];
|
||||
case ToNamecardTypeEnum.role:
|
||||
return typeMap[ToNamecardTypeEnum.role];
|
||||
case ToNamecardTypeEnum.record:
|
||||
return typeMap[ToNamecardTypeEnum.record];
|
||||
case ToNamecardTypeEnum.activity:
|
||||
return typeMap[ToNamecardTypeEnum.activity];
|
||||
default:
|
||||
return typeMap[ToNamecardTypeEnum.other];
|
||||
}
|
||||
});
|
||||
|
||||
const visible = computed({
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const getType = computed<string>(() => {
|
||||
if (!props.data) return typeMap[ToNameCardTypeEnum.unknown];
|
||||
if (!(props.data.type satisfies ToNameCardTypeEnum)) return typeMap[5];
|
||||
const type: ToNameCardTypeEnum = props.data.type;
|
||||
return typeMap[type];
|
||||
});
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function parseNamecard(desc: string): string {
|
||||
function parseNameCard(desc: string): string {
|
||||
let array = [];
|
||||
if (desc.startsWith("名片纹饰。「") && desc.endsWith("」")) {
|
||||
array.push("名片纹饰。");
|
||||
@@ -104,22 +77,19 @@ function parseNamecard(desc: string): string {
|
||||
for (const item of match) {
|
||||
if (item.length <= 34) {
|
||||
array.push(item);
|
||||
} else {
|
||||
array.push("「");
|
||||
array.push(...parseDesc(item.slice(1, -1), true));
|
||||
const maxLength = Math.max(...array.map((item) => item.length));
|
||||
array.push(" ".repeat(maxLength - 4) + "」");
|
||||
continue;
|
||||
}
|
||||
array.push("「");
|
||||
array.push(...parseDesc(item.slice(1, -1), true));
|
||||
const maxLength = Math.max(...array.map((item) => item.length));
|
||||
array.push(" ".repeat(maxLength - 4) + "」");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
array.push("名片纹饰。");
|
||||
const content = desc.slice(5);
|
||||
if (content.length <= 32) {
|
||||
array.push(content);
|
||||
} else {
|
||||
array.push(...parseDesc(content));
|
||||
}
|
||||
if (content.length <= 32) array.push(content);
|
||||
else array.push(...parseDesc(content));
|
||||
}
|
||||
const res = array.join("\n");
|
||||
if (!res.endsWith("\n")) return res + "\n";
|
||||
@@ -136,31 +106,31 @@ function parseDesc(desc: string, inQuote: boolean = false): string[] {
|
||||
res = res.replace("时候,", "时候,\n");
|
||||
res = res.replace("。\n」", "。」");
|
||||
}
|
||||
if (!desc.includes("!」")) {
|
||||
res = res.replace(/!/g, "!\n");
|
||||
}
|
||||
if (!desc.includes("!」")) res = res.replace(/!/g, "!\n");
|
||||
res = res.replace(/…/g, "…\n");
|
||||
const match = res.split("\n");
|
||||
let array: string[] = [];
|
||||
for (const item of match) {
|
||||
if (item.length > 0 && item.length <= 32) {
|
||||
array.push(item);
|
||||
} else {
|
||||
const match2 = item.replace(/,/g, ",\n").split("\n");
|
||||
for (const item2 of match2) {
|
||||
if (item2.length > 0) array.push(item2);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const match2 = item.replace(/,/g, ",\n").split("\n");
|
||||
match2.map((i) => (i.length > 0 ? array.push(i) : null));
|
||||
}
|
||||
if (inQuote) array = array.map((item) => ` ${item}`);
|
||||
return array;
|
||||
}
|
||||
|
||||
async function shareNamecard(): Promise<void> {
|
||||
const namecardBox = <HTMLElement>document.querySelector(".ton-box");
|
||||
async function shareNameCard(): Promise<void> {
|
||||
const nameCardBox = document.querySelector<HTMLElement>(".ton-box");
|
||||
if (nameCardBox === null) {
|
||||
showSnackbar.error("未找到名片内容");
|
||||
return;
|
||||
}
|
||||
const fileName = `【${getType.value}名片】-${props.data?.name}`;
|
||||
loading.value = true;
|
||||
await generateShareImg(fileName, namecardBox);
|
||||
await generateShareImg(fileName, nameCardBox);
|
||||
loading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</transition>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useTemplateRef } from "vue";
|
||||
import { ref, useTemplateRef, watch } from "vue";
|
||||
|
||||
const show = ref<boolean>(false);
|
||||
const showOuter = ref<boolean>(false);
|
||||
@@ -36,11 +36,15 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
declare function initGeetest(
|
||||
params: TGApp.Plugins.Mys.Geetest.InitGeetestParams,
|
||||
callback: (captchaObj: TGApp.Plugins.Mys.Geetest.GeetestCaptcha) => void,
|
||||
): void;
|
||||
|
||||
async function displayBox(
|
||||
props: TGApp.Plugins.Mys.Geetest.reqResp,
|
||||
): Promise<TGApp.Plugins.Mys.Geetest.validateResp | false> {
|
||||
return await new Promise<TGApp.Plugins.Mys.Geetest.validateResp>((resolve) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
initGeetest(
|
||||
{
|
||||
gt: props.gt,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="hta-oo-box">
|
||||
<v-btn
|
||||
:loading="loadShare"
|
||||
@@ -63,34 +63,21 @@ import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import HtaOverviewLine from "./hta-overview-line.vue";
|
||||
|
||||
interface HtaOverlayOverviewProps {
|
||||
type HtaOverlayOverviewProps = {
|
||||
modelValue: boolean;
|
||||
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.OverviewData>;
|
||||
}
|
||||
|
||||
interface HtaOverlayOverviewEmits {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
(e: "cancel"): void;
|
||||
}
|
||||
};
|
||||
type HtaOverlayOverviewEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
|
||||
const props = defineProps<HtaOverlayOverviewProps>();
|
||||
const emits = defineEmits<HtaOverlayOverviewEmits>();
|
||||
const loadShare = ref<boolean>(false);
|
||||
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);
|
||||
const loadShare = ref<boolean>(false);
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
|
||||
function onCancel(): void {
|
||||
visible.value = false;
|
||||
emits("cancel");
|
||||
}
|
||||
|
||||
async function share(): Promise<void> {
|
||||
loadShare.value = true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="5px">
|
||||
<TOverlay v-model="visible" blur-val="5px">
|
||||
<div class="toab-container" v-if="props.data">
|
||||
<div class="toab-img">
|
||||
<img :src="props.data.take_picture[Number(props.choice)]" alt="顶部图像" />
|
||||
@@ -29,61 +29,39 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { fetch } from "@tauri-apps/plugin-http";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
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";
|
||||
|
||||
interface ToArcBirthProps {
|
||||
type ToArcBirthProps = {
|
||||
modelValue: boolean;
|
||||
data?: TGApp.Archive.Birth.DrawItem;
|
||||
choice: boolean;
|
||||
}
|
||||
|
||||
interface ToArcBirthEmits {
|
||||
(event: "update:modelValue", value: boolean): void;
|
||||
}
|
||||
};
|
||||
type ToArcBirthEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
type XmlKeyMap = { id: string; rel: string; group?: string; icon: string };
|
||||
type XmlTextList = { chara: string; img: string; text: string };
|
||||
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(false);
|
||||
const textParse = ref<Array<XmlTextParse>>([]);
|
||||
|
||||
const visible = computed({
|
||||
const showText = ref<boolean>(false);
|
||||
const textParse = shallowRef<Array<XmlTextParse>>([]);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
onMounted(() => clearData());
|
||||
watch(() => props.data, clearData);
|
||||
watch(() => props.choice, clearData);
|
||||
|
||||
onMounted(loadData);
|
||||
watch(() => props.data, loadData);
|
||||
watch(() => props.choice, loadData);
|
||||
|
||||
interface XmlKeyMap {
|
||||
id: string;
|
||||
rel: string;
|
||||
group?: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
interface XmlTextList {
|
||||
chara: string;
|
||||
img: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface XmlTextParse {
|
||||
name: string;
|
||||
icon?: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
function clearData(): void {
|
||||
buffer.value = null;
|
||||
textParse.value = [];
|
||||
showText.value = false;
|
||||
@@ -113,68 +91,80 @@ async function loadText(): Promise<void> {
|
||||
showText.value = !showText.value;
|
||||
return;
|
||||
}
|
||||
const resSource: any = await parseXml(props.data.gal_resource);
|
||||
const resSource: unknown = await parseXml(props.data.gal_resource);
|
||||
if (resSource === false) {
|
||||
showSnackbar.warn("对白数据加载失败");
|
||||
return;
|
||||
}
|
||||
const keyMap: XmlKeyMap[] = resSource["elements"][0]["elements"][0]["elements"]
|
||||
.map((item: any) => {
|
||||
if (item["name"] === "chara")
|
||||
return <XmlKeyMap>{
|
||||
id: item["attributes"]["id"],
|
||||
rel: item["attributes"]["rel"],
|
||||
group: item["attributes"]["group"],
|
||||
icon: item["attributes"]["src"],
|
||||
};
|
||||
})
|
||||
.filter((item: any) => item !== undefined);
|
||||
const resXml = await parseXml(props.data.gal_xml);
|
||||
const textList: XmlTextList[] = resXml["elements"][0]["elements"][0]["elements"][0]["elements"]
|
||||
.map((item: any) => {
|
||||
if (item["name"] === "simple_dialog") {
|
||||
let img = item["attributes"]["img"];
|
||||
if (!props.choice && img) img = img.replace("aether", "lumine");
|
||||
return <XmlTextList>{
|
||||
chara: item["attributes"]["chara"],
|
||||
img: img,
|
||||
text: item["elements"][0]["text"],
|
||||
};
|
||||
}
|
||||
})
|
||||
.filter((item: any) => item !== undefined);
|
||||
textParse.value = textList.map((item: XmlTextList) => {
|
||||
const key = keyMap.find((keyItem: XmlKeyMap) => keyItem.id === item.img);
|
||||
if (!key) {
|
||||
return {
|
||||
name: "未知",
|
||||
text: item.text,
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: key.group ?? key.id,
|
||||
text: item.text,
|
||||
icon: key.icon,
|
||||
};
|
||||
const keyMap = getKeyMap(resSource);
|
||||
const resXml: unknown = await parseXml(props.data.gal_xml);
|
||||
const textList = getTextList(resXml);
|
||||
textParse.value = textList.map((item) => {
|
||||
const key = keyMap.find((keyItem) => keyItem.id === item.img);
|
||||
if (!key) return { name: "未知", text: item.text };
|
||||
return { name: key.group ?? key.id, text: item.text, icon: key.icon };
|
||||
});
|
||||
showText.value = true;
|
||||
}
|
||||
|
||||
async function parseXml(link: string) {
|
||||
function getKeyMap(resSource: unknown): XmlKeyMap[] {
|
||||
const res: XmlKeyMap[] = [];
|
||||
if (!resSource || typeof resSource !== "object") return res;
|
||||
if (!("elements" in resSource) || !Array.isArray(resSource["elements"])) return res;
|
||||
const arr1 = resSource.elements;
|
||||
if (arr1.length === 0 || !("elements" in arr1[0]) || !Array.isArray(arr1[0].elements)) return res;
|
||||
const arr2 = arr1[0].elements;
|
||||
if (arr2.length === 0 || !("elements" in arr2[0]) || !Array.isArray(arr2[0].elements)) return res;
|
||||
const arr3 = arr2[0].elements;
|
||||
for (const item of arr3) {
|
||||
if (!("name" in item)) continue;
|
||||
if (!("attributes" in item)) continue;
|
||||
const attr = item.attributes;
|
||||
if (!("id" in attr) || !("rel" in attr) || !("group" in attr) || !("src" in attr)) continue;
|
||||
if (item.name !== "chara") continue;
|
||||
res.push({ id: attr.id, rel: attr.rel, group: attr.group, icon: attr.src });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getTextList(resXml: unknown): XmlTextList[] {
|
||||
const res: XmlTextList[] = [];
|
||||
if (!resXml || typeof resXml !== "object") return res;
|
||||
if (!("elements" in resXml) || !Array.isArray(resXml["elements"])) return res;
|
||||
const arr1 = resXml.elements;
|
||||
if (arr1.length === 0 || !("elements" in arr1[0]) || !Array.isArray(arr1[0].elements)) return res;
|
||||
const arr2 = arr1[0].elements;
|
||||
if (arr2.length === 0 || !("elements" in arr2[0]) || !Array.isArray(arr2[0].elements)) return res;
|
||||
const arr3 = arr2[0].elements;
|
||||
if (arr3.length === 0 || !("elements" in arr3[0]) || !Array.isArray(arr3[0].elements)) return res;
|
||||
const arr4 = arr3[0].elements;
|
||||
for (const item of arr4) {
|
||||
if (!("name" in item)) continue;
|
||||
if (!("attributes" in item)) continue;
|
||||
if (!("elements" in item) || !Array.isArray(item.elements)) continue;
|
||||
const attr = item.attributes;
|
||||
if (!("chara" in attr) || !("img" in attr)) continue;
|
||||
if (item.name !== "simple_dialog") continue;
|
||||
const img = props.choice ? attr.img : attr.img.replace("aether", "lumine");
|
||||
res.push({ chara: attr.chara, img: img, text: item.elements[0].text });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async function parseXml(link: string): Promise<false | unknown> {
|
||||
try {
|
||||
const response = await fetch(link, { method: "GET" });
|
||||
const data = await response.arrayBuffer();
|
||||
const xml = new TextDecoder("utf-8").decode(data);
|
||||
return JSON.parse(xml2json(xml));
|
||||
return JSON.parse(xml2json(new TextDecoder("utf-8").decode(data)));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error instanceof Error) {
|
||||
await TGLogger.Error(`[to-arcBirth] parseXml: ${error.message}`);
|
||||
} else {
|
||||
await TGLogger.Error(`[to-arcBirth] parseXml: 未知错误-${error}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.toab-container {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="tocp-container">
|
||||
<div class="tocp-title">选择分类</div>
|
||||
<div class="tocp-list">
|
||||
@@ -15,55 +15,38 @@
|
||||
</div>
|
||||
<div class="tocp-bottom">
|
||||
<v-btn class="tocp-btn" rounded @click="newCollect">新建分类</v-btn>
|
||||
<v-btn class="tocp-btn" rounded @click="onCancel">取消</v-btn>
|
||||
<v-btn class="tocp-btn" rounded @click="visible = false">取消</v-btn>
|
||||
<v-btn :loading="submit" class="tocp-btn" rounded @click="onSubmit">确定</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
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";
|
||||
|
||||
interface ToPostCollectProps {
|
||||
modelValue: boolean;
|
||||
post: string[];
|
||||
}
|
||||
|
||||
interface ToPostCollectEmits {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
type ToPostCollectProps = { modelValue: boolean; post: string[] };
|
||||
type ToPostCollectEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "submit"): void;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<ToPostCollectProps>();
|
||||
const emits = defineEmits<ToPostCollectEmits>();
|
||||
const select = ref<string>();
|
||||
const collectList = ref<TGApp.Sqlite.UserCollection.UFCollection[]>([]);
|
||||
const submit = ref(false);
|
||||
|
||||
const visible = computed({
|
||||
const submit = ref<boolean>(false);
|
||||
const collectList = shallowRef<TGApp.Sqlite.UserCollection.UFCollection[]>([]);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (val) => {
|
||||
if (val) {
|
||||
await freshData();
|
||||
}
|
||||
},
|
||||
async (val) => (val ? await freshData() : null),
|
||||
);
|
||||
|
||||
async function onSubmit(): Promise<void> {
|
||||
@@ -83,7 +66,6 @@ async function onSubmit(): Promise<void> {
|
||||
}
|
||||
showSnackbar.success(`成功处理 ${props.post.length} 个帖子`);
|
||||
submit.value = false;
|
||||
visible.value = false;
|
||||
emits("submit");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" :to-click="onCancel" blur-val="20px" hide>
|
||||
<TOverlay v-model="visible">
|
||||
<div class="toc-box">
|
||||
<div class="box-div">
|
||||
<div class="toc-top">
|
||||
<TItemBox :model-value="getBoxData()" />
|
||||
<TItemBox :model-value="boxData" />
|
||||
<div class="toc-material-grid">
|
||||
<TibCalendarMaterial
|
||||
v-for="(item, index) in itemVal.materials"
|
||||
@@ -31,76 +31,47 @@ 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";
|
||||
|
||||
interface ToCalendarProps {
|
||||
type ToCalendarProps = {
|
||||
modelValue: boolean;
|
||||
dataType: "weapon" | "avatar";
|
||||
dataVal: TGApp.App.Calendar.Item;
|
||||
}
|
||||
};
|
||||
type ToCalendarEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
|
||||
interface ToCalendarEmits {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
(e: "cancel"): void;
|
||||
}
|
||||
|
||||
const emits = defineEmits<ToCalendarEmits>();
|
||||
const props = defineProps<ToCalendarProps>();
|
||||
|
||||
const emits = defineEmits<ToCalendarEmits>();
|
||||
const router = useRouter();
|
||||
|
||||
const visible = computed({
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const itemVal = computed<TGApp.App.Calendar.Item>(() => props.dataVal);
|
||||
|
||||
const onCancel = (): void => {
|
||||
visible.value = false;
|
||||
emits("cancel");
|
||||
};
|
||||
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,
|
||||
};
|
||||
});
|
||||
|
||||
async function toDetail(item: TGApp.App.Calendar.Item): Promise<void> {
|
||||
if (item.itemType === "character") {
|
||||
await router.push(`/wiki/character/${item.id}`);
|
||||
} else {
|
||||
await router.push(`/wiki/weapon/${item.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getBoxData(): TItemBoxData {
|
||||
if (props.dataType === "avatar") {
|
||||
return {
|
||||
bg: props.dataVal.bg,
|
||||
icon: props.dataVal.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: false,
|
||||
lt: props.dataVal.elementIcon ?? "",
|
||||
ltSize: "20px",
|
||||
innerHeight: 25,
|
||||
innerIcon: props.dataVal.weaponIcon,
|
||||
innerText: props.dataVal.name,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
bg: props.dataVal.bg,
|
||||
icon: props.dataVal.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: false,
|
||||
lt: props.dataVal.weaponIcon,
|
||||
ltSize: "20px",
|
||||
innerHeight: 25,
|
||||
innerText: props.dataVal.name,
|
||||
};
|
||||
if (!["character", "weapon"].includes(item.itemType)) {
|
||||
showSnackbar.error("未知类型");
|
||||
return;
|
||||
}
|
||||
await router.push(`/wiki/${item.itemType}/${item.id}`);
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="toc-box">
|
||||
<div class="toc-top">
|
||||
<div class="toc-title">
|
||||
@@ -9,7 +9,8 @@
|
||||
<div
|
||||
v-for="(item, index) in channelList"
|
||||
:key="index"
|
||||
:class="props.gid === item.gid ? 'toc-list-item active' : 'toc-list-item'"
|
||||
class="toc-list-item"
|
||||
:class="{ active: props.gid === item.gid }"
|
||||
@click="toChannel(item)"
|
||||
>
|
||||
<img :src="item.icon" alt="icon" />
|
||||
@@ -17,7 +18,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toc-close" @click="onCancel">
|
||||
<div class="toc-close" @click="visible = false">
|
||||
<div class="toc-close-btn">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</div>
|
||||
@@ -26,6 +27,7 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
@@ -35,32 +37,16 @@ import TGConstant from "../../web/constant/TGConstant.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
interface ToChannelProps {
|
||||
gid?: string;
|
||||
curType?: string;
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
type ToChannelEmits = (e: "update:modelValue", value: boolean) => void;
|
||||
|
||||
const props = withDefaults(defineProps<ToChannelProps>(), {
|
||||
modelValue: false,
|
||||
});
|
||||
type ToChannelProps = { gid?: string; curType?: string; modelValue: boolean };
|
||||
type ToChannelEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
const props = withDefaults(defineProps<ToChannelProps>(), { modelValue: false });
|
||||
const emits = defineEmits<ToChannelEmits>();
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const { recentNewsType } = storeToRefs(useAppStore());
|
||||
const channelList = TGConstant.BBS.CHANNELS;
|
||||
|
||||
function onCancel(): void {
|
||||
visible.value = false;
|
||||
}
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
async function toChannel(item: ToChannelItem): Promise<void> {
|
||||
if (props.gid === item.gid) {
|
||||
@@ -69,14 +55,13 @@ async function toChannel(item: ToChannelItem): Promise<void> {
|
||||
}
|
||||
visible.value = false;
|
||||
let link = `/news/${item.gid}/{type}`;
|
||||
const typeList = ["notice", "news", "activity"];
|
||||
if (typeList.includes(appStore.recentNewsType)) {
|
||||
link = link.replace("{type}", appStore.recentNewsType);
|
||||
if (recentNewsType.value satisfies TGApp.App.Store.NewsType) {
|
||||
link = link.replace("{type}", recentNewsType.value);
|
||||
} else {
|
||||
link = link.replace("{type}", "notice");
|
||||
appStore.recentNewsType = "notice";
|
||||
recentNewsType.value = "notice";
|
||||
}
|
||||
await router.push(link);
|
||||
await useRouter().push(link);
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
@@ -113,26 +98,26 @@ async function toChannel(item: ToChannelItem): Promise<void> {
|
||||
color: var(--box-text-1);
|
||||
cursor: pointer;
|
||||
transition: all 0.5s linear;
|
||||
}
|
||||
|
||||
.toc-list-item.active {
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
background: var(--box-bg-2);
|
||||
color: var(--box-text-2);
|
||||
}
|
||||
&.active {
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
background: var(--box-bg-2);
|
||||
color: var(--box-text-2);
|
||||
}
|
||||
|
||||
.toc-list-item img {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
margin-right: 10px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
img {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
margin-right: 10px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
.toc-list-item span {
|
||||
margin-right: 10px;
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
span {
|
||||
margin-right: 10px;
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.toc-close {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div v-if="props.data" class="twom-container">
|
||||
<slot name="left"></slot>
|
||||
<div class="twom-box">
|
||||
@@ -32,37 +32,31 @@ 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 TwoConvert from "./two-convert.vue";
|
||||
import TwoSource from "./two-source.vue";
|
||||
|
||||
interface TwoMaterialProps {
|
||||
modelValue: boolean;
|
||||
data: TGApp.App.Material.WikiItem;
|
||||
}
|
||||
|
||||
type TwoMaterialEmits = (e: "update:modelValue", value: boolean) => void;
|
||||
|
||||
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 visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emits("update:modelValue", val),
|
||||
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();
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function shareMaterial(): Promise<void> {
|
||||
const element = <HTMLElement>document.querySelector(".twom-box");
|
||||
const element = document.querySelector<HTMLElement>(".twom-box");
|
||||
if (element === null) {
|
||||
showSnackbar.error("未获取到分享内容");
|
||||
return;
|
||||
}
|
||||
const fileName = `material_${props.data.id}`;
|
||||
await generateShareImg(fileName, element, 1.2, true);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="two-sc-container">
|
||||
<div class="two-sc-item">
|
||||
<div class="two-sc-title">星级</div>
|
||||
@@ -58,7 +58,7 @@
|
||||
</v-item-group>
|
||||
</div>
|
||||
<div class="tow-sc-submit">
|
||||
<v-btn variant="tonal" @click="onCancel">取消</v-btn>
|
||||
<v-btn variant="tonal" @click="visible = false">取消</v-btn>
|
||||
<v-btn @click="confirmSelect">确定</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,7 +69,22 @@ import { computed, ref, watch } from "vue";
|
||||
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
|
||||
// 一些数据
|
||||
export type SelectedCValue = {
|
||||
star: number[];
|
||||
weapon: string[];
|
||||
elements: string[];
|
||||
area: string[];
|
||||
};
|
||||
type TwoSelectCProps = { modelValue: boolean; reset: boolean };
|
||||
|
||||
type TwoSelectCEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "update:reset", v: boolean): void;
|
||||
(e: "select-c", v: SelectedCValue): void;
|
||||
};
|
||||
|
||||
const props = defineProps<TwoSelectCProps>();
|
||||
const emits = defineEmits<TwoSelectCEmits>();
|
||||
const selectStarList = [4, 5];
|
||||
const selectWeaponList = ["单手剑", "双手剑", "弓", "法器", "长柄武器"];
|
||||
const selectElementList = ["冰", "岩", "水", "火", "草", "雷", "风"];
|
||||
@@ -80,33 +95,9 @@ const selectedStar = ref<number[]>(selectStarList);
|
||||
const selectedWeapon = ref<string[]>(selectWeaponList);
|
||||
const selectedElements = ref<string[]>(selectElementList);
|
||||
const selectedArea = ref<string[]>(selectAreaList);
|
||||
|
||||
export interface SelectedCValue {
|
||||
star: number[];
|
||||
weapon: string[];
|
||||
elements: string[];
|
||||
area: string[];
|
||||
}
|
||||
|
||||
interface TwoSelectCProps {
|
||||
modelValue: boolean;
|
||||
reset: boolean;
|
||||
}
|
||||
|
||||
interface TwoSelectCEmits {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
(e: "update:reset", value: boolean): void;
|
||||
|
||||
(e: "select-c", value: SelectedCValue): void;
|
||||
}
|
||||
|
||||
const props = defineProps<TwoSelectCProps>();
|
||||
const emits = defineEmits<TwoSelectCEmits>();
|
||||
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v: boolean) => emits("update:modelValue", v),
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const reset = computed<boolean>({
|
||||
get: () => props.reset,
|
||||
@@ -126,10 +117,6 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function confirmSelect() {
|
||||
const value: SelectedCValue = {
|
||||
star: selectedStar.value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="two-sw-container">
|
||||
<div class="two-sw-item">
|
||||
<div class="two-sw-title">星级</div>
|
||||
@@ -28,7 +28,7 @@
|
||||
</v-item-group>
|
||||
</div>
|
||||
<div class="tow-sc-submit">
|
||||
<v-btn variant="tonal" @click="onCancel">取消</v-btn>
|
||||
<v-btn variant="tonal" @click="visible = false">取消</v-btn>
|
||||
<v-btn @click="confirmSelect">确定</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,50 +39,29 @@ import { computed, ref, watch } from "vue";
|
||||
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
|
||||
// 一些数据
|
||||
const selectStarList = [4, 5];
|
||||
const selectWeaponList = ["单手剑", "双手剑", "弓", "法器", "长柄武器"];
|
||||
|
||||
// 选中的元素
|
||||
const selectedStar = ref<number[]>(selectStarList);
|
||||
const selectedWeapon = ref<string[]>(selectWeaponList);
|
||||
|
||||
export interface SelectedWValue {
|
||||
star: number[];
|
||||
weapon: string[];
|
||||
}
|
||||
|
||||
interface TwoSelectWProps {
|
||||
modelValue: boolean;
|
||||
reset: boolean;
|
||||
}
|
||||
|
||||
interface TwoSelectWEmits {
|
||||
export type SelectedWValue = { star: number[]; weapon: string[] };
|
||||
type TwoSelectWProps = { modelValue: boolean; reset: boolean };
|
||||
type TwoSelectWEmits = {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
(e: "update:reset", value: boolean): void;
|
||||
|
||||
(e: "select-w", value: SelectedWValue): void;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<TwoSelectWProps>();
|
||||
const emits = defineEmits<TwoSelectWEmits>();
|
||||
|
||||
const visible = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
const selectStarList = [4, 5];
|
||||
const selectWeaponList = ["单手剑", "双手剑", "弓", "法器", "长柄武器"];
|
||||
|
||||
const selectedStar = ref<number[]>(selectStarList);
|
||||
const selectedWeapon = ref<string[]>(selectWeaponList);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const reset = computed({
|
||||
get() {
|
||||
return props.reset;
|
||||
},
|
||||
set(value) {
|
||||
emits("update:reset", value);
|
||||
},
|
||||
const reset = computed<boolean>({
|
||||
get: () => props.reset,
|
||||
set: (v) => emits("update:reset", v),
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -97,16 +76,8 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function confirmSelect() {
|
||||
const value: SelectedWValue = {
|
||||
star: selectedStar.value,
|
||||
weapon: selectedWeapon.value,
|
||||
};
|
||||
emits("select-w", value);
|
||||
function confirmSelect(): void {
|
||||
emits("select-w", { star: selectedStar.value, weapon: selectedWeapon.value });
|
||||
visible.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="0">
|
||||
<TOverlay v-model="visible" blur-val="0">
|
||||
<div class="tua-ao-container" v-if="props.data">
|
||||
<slot name="left"></slot>
|
||||
<div class="tua-ao-box">
|
||||
@@ -59,33 +59,21 @@ import TGLogger from "../../utils/TGLogger.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import VpOverlaySearch from "../viewPost/vp-overlay-search.vue";
|
||||
|
||||
interface ToAchiInfoProps {
|
||||
modelValue: boolean;
|
||||
data: TGApp.Sqlite.Achievement.RenderAchi;
|
||||
}
|
||||
|
||||
interface ToAchiInfoEmits {
|
||||
type ToAchiInfoProps = { modelValue: boolean; data: TGApp.Sqlite.Achievement.RenderAchi };
|
||||
type ToAchiInfoEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
|
||||
(e: "select-series", v: number): void;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<ToAchiInfoProps>();
|
||||
const emits = defineEmits<ToAchiInfoEmits>();
|
||||
const showSearch = ref<boolean>(false);
|
||||
const search = ref<string>();
|
||||
|
||||
const visible = computed({
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function searchDirect(word: string): Promise<void> {
|
||||
await TGLogger.Info(`[ToAchiInfo][${props.data.id}][Search] 查询 ${word}`);
|
||||
search.value = word;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" :hide="true" blur-val="5px" :to-click="onCancel">
|
||||
<TOverlay v-model="visible" blur-val="5px">
|
||||
<div class="tdo-box">
|
||||
<div class="tdo-avatars-container">
|
||||
<v-tabs
|
||||
@@ -12,7 +12,7 @@
|
||||
v-for="avatar in avatars"
|
||||
:key="avatar.avatar.id"
|
||||
:value="avatar.avatar.id"
|
||||
@click="onAvatarClick(avatar)"
|
||||
@click="emits('toAvatar', avatar)"
|
||||
:title="avatar.avatar.name"
|
||||
>
|
||||
<div class="tdo-avatar" :style="getAvatarBg(avatar)">
|
||||
@@ -52,34 +52,29 @@ import TucDetailOld from "../userAvatarOld/tuc-detail-old.vue";
|
||||
|
||||
import TuaDetailCard from "./tua-detail-card.vue";
|
||||
|
||||
interface TuaDetailOverlayProps {
|
||||
type TuaDetailOverlayProps = {
|
||||
modelValue: boolean;
|
||||
avatar: TGApp.Sqlite.Character.UserRole;
|
||||
mode: "classic" | "card" | "dev";
|
||||
avatars: TGApp.Sqlite.Character.UserRole[];
|
||||
}
|
||||
|
||||
interface TuaDetailOverlayEmits {
|
||||
(e: "update:modelValue", val: boolean): void;
|
||||
|
||||
};
|
||||
type TuaDetailOverlayEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "update:mode", val: "classic" | "card" | "dev"): void;
|
||||
|
||||
(e: "toNext", val: boolean): void;
|
||||
|
||||
(e: "toAvatar", val: TGApp.Sqlite.Character.UserRole): void;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<TuaDetailOverlayProps>();
|
||||
const emits = defineEmits<TuaDetailOverlayEmits>();
|
||||
const avatarTab = ref<number>();
|
||||
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emits("update:modelValue", val),
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const modeTab = computed<"classic" | "card" | "dev">({
|
||||
get: () => props.mode,
|
||||
set: (val) => emits("update:mode", val),
|
||||
set: (v) => emits("update:mode", v),
|
||||
});
|
||||
|
||||
const avatarsWidth = computed<string>(() => {
|
||||
@@ -102,19 +97,11 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
function onCancel(): void {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleClick(pos: "left" | "right"): void {
|
||||
if (pos === "left") emits("toNext", false);
|
||||
else emits("toNext", true);
|
||||
}
|
||||
|
||||
function onAvatarClick(avatar: TGApp.Sqlite.Character.UserRole): void {
|
||||
emits("toAvatar", avatar);
|
||||
}
|
||||
|
||||
function getAvatarBg(avatar: TGApp.Sqlite.Character.UserRole): string {
|
||||
if (props.avatar.avatar.id === avatar.avatar.id) {
|
||||
return "background-color:var(--tgc-od-white);";
|
||||
|
||||
@@ -9,11 +9,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TucDetailConstellationProps {
|
||||
modelValue: TGApp.Game.Avatar.Constellation;
|
||||
}
|
||||
|
||||
defineProps<TucDetailConstellationProps>();
|
||||
defineProps<{ modelValue: TGApp.Game.Avatar.Constellation }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-dc-box {
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
<span>命之座</span>
|
||||
</template>
|
||||
<template #content>
|
||||
<TucDetailConstellation :model-value="props.modelValue" />
|
||||
<TucDetailConstellation :model-value="modelValue" />
|
||||
<div class="tuc-ddc-content">
|
||||
<div class="tuc-ddc-top">
|
||||
{{ props.modelValue.name }}
|
||||
{{ modelValue.name }}
|
||||
</div>
|
||||
<div class="tuc-ddc-bottom">
|
||||
<span>命之座</span>
|
||||
<span>第</span>
|
||||
<span>{{ props.modelValue.pos }}</span>
|
||||
<span>{{ modelValue.pos }}</span>
|
||||
<span>层</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #desc>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="parseHtmlText(props.modelValue.effect)"></span>
|
||||
<span v-html="parseHtmlText(modelValue.effect)"></span>
|
||||
</template>
|
||||
</TucDetailDesc>
|
||||
</template>
|
||||
@@ -29,11 +29,7 @@ import { parseHtmlText } from "../../utils/toolFunc.js";
|
||||
import TucDetailConstellation from "./tuc-detail-constellation.vue";
|
||||
import TucDetailDesc from "./tuc-detail-desc.vue";
|
||||
|
||||
interface TucDetailDescConstellationProps {
|
||||
modelValue: TGApp.Game.Avatar.Constellation;
|
||||
}
|
||||
|
||||
const props = defineProps<TucDetailDescConstellationProps>();
|
||||
defineProps<{ modelValue: TGApp.Game.Avatar.Constellation }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-ddc-content {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="tuc-overlay-box" v-if="data">
|
||||
<div class="tuc-overlay-top">
|
||||
<span class="tuc-overlay-title" @click="share()">
|
||||
@@ -25,29 +25,26 @@ 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";
|
||||
|
||||
interface TucOverlayProps {
|
||||
type TucOverlayProps = {
|
||||
modelValue: boolean;
|
||||
data: TGApp.Plugins.Hutao.Combat.Data | undefined;
|
||||
}
|
||||
|
||||
};
|
||||
type TucOverlayEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
|
||||
const props = defineProps<TucOverlayProps>();
|
||||
const emits = defineEmits<TucOverlayEmits>();
|
||||
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const raw = computed<TGApp.Plugins.Hutao.Base.Rate[]>(() => {
|
||||
if (!props.data) return [];
|
||||
const res: TGApp.Plugins.Hutao.Base.Rate[] = props.data.BackupAvatarRates;
|
||||
return res.sort((a, b) => b.Rate - a.Rate);
|
||||
});
|
||||
|
||||
function onCancel(): void {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function getBoxData(item: TGApp.Plugins.Hutao.Base.Rate): TItemBoxData {
|
||||
const avatar = AppCharacterData.find((i) => i.id === item.Item);
|
||||
return {
|
||||
@@ -73,7 +70,11 @@ function getBoxData(item: TGApp.Plugins.Hutao.Base.Rate): TItemBoxData {
|
||||
|
||||
async function share(): Promise<void> {
|
||||
showLoading.start("正在生成分享图");
|
||||
const element = <HTMLElement>document.querySelector(".tuc-overlay-box");
|
||||
const element = document.querySelector<HTMLElement>(".tuc-overlay-box");
|
||||
if (element === null) {
|
||||
showSnackbar.error("未获取到分享内容");
|
||||
return;
|
||||
}
|
||||
const fileName = `真境剧诗_${new Date().getTime()}.png`;
|
||||
await generateShareImg(fileName, element, 1.2, true);
|
||||
showLoading.end();
|
||||
|
||||
@@ -25,7 +25,7 @@ import VpOverlayCollect from "./vp-overlay-collect.vue";
|
||||
|
||||
const isCollected = ref(false);
|
||||
const collect = ref<Array<TGApp.Sqlite.UserCollection.UFMap>>([]);
|
||||
const showEdit = ref(false);
|
||||
const showEdit = ref<boolean>(false);
|
||||
|
||||
interface TbCollectProps {
|
||||
modelValue: number;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<!-- 编辑收藏帖子的合集 -->
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="topc-container">
|
||||
<div class="topc-post-info">
|
||||
{{ props.post?.post.subject }}
|
||||
@@ -27,7 +26,7 @@
|
||||
</div>
|
||||
<div class="topc-bottom">
|
||||
<v-btn class="topc-btn" rounded @click="newCollect">新建分类</v-btn>
|
||||
<v-btn class="topc-btn" rounded @click="onCancel">取消</v-btn>
|
||||
<v-btn class="topc-btn" rounded @click="visible = false">取消</v-btn>
|
||||
<v-btn :loading="submit" class="topc-btn" rounded @click="onSubmit">确定</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,31 +40,29 @@ import TOverlay from "../app/t-overlay.vue";
|
||||
import showDialog from "../func/dialog.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
interface ToPostCollectProps {
|
||||
type ToPostCollectProps = {
|
||||
modelValue: boolean;
|
||||
post: TGApp.Plugins.Mys.Post.FullData | undefined;
|
||||
}
|
||||
|
||||
interface ToPostCollectEmits {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
};
|
||||
type ToPostCollectEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "submit"): void;
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<ToPostCollectProps>();
|
||||
const emits = defineEmits<ToPostCollectEmits>();
|
||||
const collectList = ref<TGApp.Sqlite.UserCollection.UFCollection[]>([]);
|
||||
const postCollect = ref<TGApp.Sqlite.UserCollection.UFMap[]>([]);
|
||||
const selectList = ref<string[]>([]);
|
||||
const submit = ref(false);
|
||||
const submit = ref<boolean>(false);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (val) => {
|
||||
if (val) {
|
||||
await freshData();
|
||||
}
|
||||
},
|
||||
async (v) => (v ? await freshData() : null),
|
||||
);
|
||||
|
||||
async function freshData(): Promise<void> {
|
||||
@@ -75,19 +72,14 @@ async function freshData(): Promise<void> {
|
||||
if (Array.isArray(collectRes)) {
|
||||
postCollect.value = collectRes;
|
||||
selectList.value = postCollect.value.map((i) => i.collection);
|
||||
} else if (collectRes) {
|
||||
return;
|
||||
}
|
||||
if (collectRes) {
|
||||
postCollect.value = [];
|
||||
selectList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
|
||||
async function deleteCollect(item: TGApp.Sqlite.UserCollection.UFCollection): Promise<void> {
|
||||
const delCheck = await showDialog.check("确定删除分类?", "该分类若有帖子,则会变为未分类");
|
||||
if (!delCheck) {
|
||||
@@ -148,10 +140,6 @@ async function onSubmit(): Promise<void> {
|
||||
}
|
||||
showSnackbar.success("更新成功");
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.topc-container {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" :hide="true" :to-click="onCancel" blur-val="5px">
|
||||
<TOverlay v-model="visible" blur-val="5px">
|
||||
<div class="tpoc-box">
|
||||
<div class="tpoc-top">
|
||||
<span>{{ props.collection.collection_title }}</span>
|
||||
@@ -47,43 +47,35 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, useTemplateRef, watch } from "vue";
|
||||
import { computed, onMounted, shallowRef, useTemplateRef, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import Mys from "../../plugins/Mys/index.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
interface TpoCollectionProps {
|
||||
type TpoCollectionProps = {
|
||||
collection: TGApp.Plugins.Mys.Post.Collection;
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
type TpoCollectionEmits = (e: "update:modelValue", value: boolean) => void;
|
||||
|
||||
interface TpoCollectionItem {
|
||||
};
|
||||
type TpoCollectionEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
type TpoCollectionItem = {
|
||||
postId: string;
|
||||
title: string;
|
||||
created: number;
|
||||
updated: number;
|
||||
comments: number;
|
||||
likes: number;
|
||||
}
|
||||
|
||||
};
|
||||
const router = useRouter();
|
||||
const props = defineProps<TpoCollectionProps>();
|
||||
const emits = defineEmits<TpoCollectionEmits>();
|
||||
const postListEl = useTemplateRef<HTMLDivElement>("postListRef");
|
||||
|
||||
const visible = computed({
|
||||
const posts = shallowRef<TpoCollectionItem[]>([]);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const posts = ref<TpoCollectionItem[]>([]);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
async (value) => {
|
||||
@@ -115,10 +107,6 @@ onMounted(async () => {
|
||||
posts.value = tempArr;
|
||||
});
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function getDate(date: number): string {
|
||||
return new Date(date * 1000).toLocaleString().replace(/\//g, "-").split(" ")[0];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="10px">
|
||||
<TOverlay v-model="visible" blur-val="10px">
|
||||
<div class="tpoi-box">
|
||||
<div :class="isOriSize ? 'tpoi-top-ori' : 'tpoi-top'">
|
||||
<div :class="{ 'tpoi-top-ori': isOriSize, 'tpoi-top': !isOriSize }">
|
||||
<img :src="props.image.insert.image" alt="图片" @click="resizeImg" />
|
||||
</div>
|
||||
<div class="tpoi-bottom">
|
||||
@@ -16,7 +16,7 @@
|
||||
<v-icon @click="setBlackBg" title="切换背景色">mdi-format-color-fill</v-icon>
|
||||
<v-icon @click="onCopy" title="复制到剪贴板">mdi-content-copy</v-icon>
|
||||
<v-icon @click="onDownload" title="下载到本地">mdi-download</v-icon>
|
||||
<v-icon @click="onCancel" title="关闭浮窗">mdi-close</v-icon>
|
||||
<v-icon @click="visible = false" title="关闭浮窗">mdi-close</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,49 +32,34 @@ import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import { TpImage } from "./tp-image.vue";
|
||||
|
||||
interface TpoImageProps {
|
||||
image: TpImage;
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
type TpoImageEmits = {
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
};
|
||||
|
||||
type TpoImageProps = { image: TpImage; modelValue: boolean };
|
||||
type TpoImageEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
const props = defineProps<TpoImageProps>();
|
||||
const emits = defineEmits<TpoImageEmits>();
|
||||
const buffer = ref<Uint8Array | null>(null);
|
||||
const bgMode = ref(0); // 0: transparent, 1: black, 2: white
|
||||
const isOriSize = ref(false);
|
||||
|
||||
const visible = computed({
|
||||
const bgMode = ref<number>(0); // 0: transparent, 1: black, 2: white
|
||||
const isOriSize = ref<boolean>(false);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
const bg = computed(() => {
|
||||
if (bgMode.value === 1) return "black";
|
||||
if (bgMode.value === 2) return "white";
|
||||
return "transparent";
|
||||
});
|
||||
|
||||
const format = computed(() => {
|
||||
const format = computed<string>(() => {
|
||||
if (props.image.attributes?.ext) return props.image.attributes.ext;
|
||||
const imageFormat = props.image.insert.image.split(".").pop();
|
||||
if (imageFormat !== undefined) return imageFormat;
|
||||
return "png";
|
||||
});
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function resizeImg() {
|
||||
function resizeImg(): void {
|
||||
isOriSize.value = !isOriSize.value;
|
||||
}
|
||||
|
||||
function setBlackBg() {
|
||||
function setBlackBg(): void {
|
||||
bgMode.value = (bgMode.value + 1) % 3;
|
||||
const bgLabelList = ["透明", "黑色", "白色"];
|
||||
showSnackbar.success(`背景已切换为${bgLabelList[bgMode.value]}`);
|
||||
@@ -92,7 +77,7 @@ async function onCopy(): Promise<void> {
|
||||
showSnackbar.success(`图片已复制到剪贴板,大小:${size}`);
|
||||
}
|
||||
|
||||
async function onDownload() {
|
||||
async function onDownload(): Promise<void> {
|
||||
const image = props.image.insert.image;
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||
const size = bytesToSize(buffer.value.byteLength);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="tpol-box" v-if="card">
|
||||
<div class="tpol-title">
|
||||
<span>抽奖详情</span>
|
||||
@@ -48,45 +48,24 @@ import Mys from "../../plugins/Mys/index.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
interface TpoLotteryProps {
|
||||
modelValue: boolean;
|
||||
lottery: string | undefined;
|
||||
}
|
||||
|
||||
interface TpoLotteryEmits {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
(e: "cancel"): void;
|
||||
}
|
||||
|
||||
type TpoLotteryProps = { modelValue: boolean; lottery: string | undefined };
|
||||
type TpoLotteryEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
const props = defineProps<TpoLotteryProps>();
|
||||
const emits = defineEmits<TpoLotteryEmits>();
|
||||
const card = ref<TGApp.Plugins.Mys.Lottery.RenderCard>();
|
||||
const jsonData = ref<TGApp.Plugins.Mys.Lottery.FullData>();
|
||||
const timeStatus = ref<string>("未知");
|
||||
const upWay = ref<string>("未知");
|
||||
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
// eslint-disable-next-line no-undef
|
||||
let timer: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
|
||||
const onCancel = (): void => {
|
||||
visible.value = false;
|
||||
emits("cancel");
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.lottery,
|
||||
async (value) => {
|
||||
if (!value) return;
|
||||
await load();
|
||||
},
|
||||
async (v) => (v ? await load() : undefined),
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
|
||||
@@ -1,73 +1,44 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible">
|
||||
<div class="tops-box">
|
||||
<div class="tops-top">查找:{{ search }}</div>
|
||||
<div class="tops-act">
|
||||
<span>分区:{{ getGidLabel() }}</span>
|
||||
<span>分区:{{ getGameName(Number(game)) }}</span>
|
||||
<v-btn :loading="load" size="small" class="tops-btn" @click="searchPosts()" rounded>
|
||||
加载更多({{ results.length }})
|
||||
</v-btn>
|
||||
</div>
|
||||
<div class="tops-list">
|
||||
<div v-for="item in results" :key="item.post.post_id">
|
||||
<TPostCard :model-value="item" />
|
||||
</div>
|
||||
<TPostCard v-for="item in results" :key="item.post.post_id" :model-value="item" />
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import Mys from "../../plugins/Mys/index.js";
|
||||
import { getGameName } from "../../web/utils/tools.js";
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import TPostCard from "../app/t-postcard.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
// data
|
||||
const search = ref<string>();
|
||||
const lastId = ref<string>("");
|
||||
const game = ref<string>("2");
|
||||
const isLast = ref<boolean>(false);
|
||||
const results = ref<TGApp.Plugins.Mys.Post.FullData[]>([]);
|
||||
const results = shallowRef<TGApp.Plugins.Mys.Post.FullData[]>([]);
|
||||
const load = ref<boolean>(false);
|
||||
|
||||
interface ToPostSearchProps {
|
||||
modelValue: boolean;
|
||||
gid: string;
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
interface ToPostSearchEmits {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
(e: "cancel"): void;
|
||||
}
|
||||
|
||||
type ToPostSearchProps = { modelValue: boolean; gid: string; keyword?: string };
|
||||
type ToPostSearchEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
const props = defineProps<ToPostSearchProps>();
|
||||
const emits = defineEmits<ToPostSearchEmits>();
|
||||
|
||||
const gameList: Record<string, string> = {
|
||||
"1": "崩坏3",
|
||||
"2": "原神",
|
||||
"3": "崩坏2",
|
||||
"4": "未定事件簿",
|
||||
"5": "大别野",
|
||||
"6": "崩坏:星穹铁道",
|
||||
"8": "绝区零",
|
||||
};
|
||||
// overlay
|
||||
const visible = computed({
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
game.value = props.gid;
|
||||
if (props.keyword && props.keyword !== "") search.value = props.keyword;
|
||||
@@ -145,13 +116,6 @@ async function searchPosts() {
|
||||
visible.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function getGidLabel(): string {
|
||||
if (gameList[game.value]) {
|
||||
return gameList[game.value];
|
||||
}
|
||||
return "未知";
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tops-box {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" :hide="true" :to-click="onCancel" blur-val="0">
|
||||
<TOverlay v-model="visible" blur-val="0">
|
||||
<div class="tpr-debug-box">
|
||||
<div class="tpr-debug-title">
|
||||
<span>文件:</span>
|
||||
@@ -15,37 +15,24 @@
|
||||
<script lang="ts" setup>
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, shallowRef } from "vue";
|
||||
|
||||
import TOverlay from "../app/t-overlay.vue";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
import TprReply from "./vp-reply-item.vue";
|
||||
|
||||
interface TprDebugProps {
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
interface TprDebugEmits {
|
||||
(event: "update:modelValue", value: boolean): void;
|
||||
}
|
||||
type TprDebugProps = { modelValue: boolean };
|
||||
type TprDebugEmits = (e: "update:modelValue", v: boolean) => void;
|
||||
|
||||
const props = defineProps<TprDebugProps>();
|
||||
const emits = defineEmits<TprDebugEmits>();
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
|
||||
const filePath = ref<string>("");
|
||||
const replyData = ref<TGApp.Plugins.Mys.Reply.ReplyFull | null>(null);
|
||||
|
||||
function onCancel(): void {
|
||||
visible.value = false;
|
||||
}
|
||||
const replyData = shallowRef<TGApp.Plugins.Mys.Reply.ReplyFull | null>(null);
|
||||
const visible = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
async function selectFile(): Promise<void> {
|
||||
const file = await open({
|
||||
|
||||
@@ -6,22 +6,22 @@
|
||||
|
||||
import type { Schema } from "ajv";
|
||||
|
||||
import achievements from "./app/achievements.json";
|
||||
import achievementSeries from "./app/achievementSeries.json";
|
||||
import calendar from "./app/calendar.json";
|
||||
import character from "./app/character.json";
|
||||
import gacha from "./app/gacha.json";
|
||||
import nameCards from "./app/namecard.json";
|
||||
import weapon from "./app/weapon.json";
|
||||
import arcBirCalendar from "./archive/birth_calendar.json";
|
||||
import arcBirDraw from "./archive/birth_draw.json";
|
||||
import arcBirRole from "./archive/birth_role.json";
|
||||
import schemaUiaf from "./schema/uiaf-schema.json";
|
||||
import schemaUigf from "./schema/uigf-schema.json";
|
||||
import scheamUigf4 from "./schema/uigf4-schema.json";
|
||||
import wikiCharacter from "./WIKI/character.json";
|
||||
import wikiMaterial from "./WIKI/material.json";
|
||||
import wikiWeapon from "./WIKI/weapon.json";
|
||||
import achievements from "./app/achievements.json" assert { type: "json" };
|
||||
import achievementSeries from "./app/achievementSeries.json" assert { type: "json" };
|
||||
import calendar from "./app/calendar.json" assert { type: "json" };
|
||||
import character from "./app/character.json" assert { type: "json" };
|
||||
import gacha from "./app/gacha.json" assert { type: "json" };
|
||||
import nameCards from "./app/namecard.json" assert { type: "json" };
|
||||
import weapon from "./app/weapon.json" assert { type: "json" };
|
||||
import arcBirCalendar from "./archive/birth_calendar.json" assert { type: "json" };
|
||||
import arcBirDraw from "./archive/birth_draw.json" assert { type: "json" };
|
||||
import arcBirRole from "./archive/birth_role.json" assert { type: "json" };
|
||||
import schemaUiaf from "./schema/uiaf-schema.json" assert { type: "json" };
|
||||
import schemaUigf from "./schema/uigf-schema.json" assert { type: "json" };
|
||||
import scheamUigf4 from "./schema/uigf4-schema.json" assert { type: "json" };
|
||||
import wikiCharacter from "./WIKI/character.json" assert { type: "json" };
|
||||
import wikiMaterial from "./WIKI/material.json" assert { type: "json" };
|
||||
import wikiWeapon from "./WIKI/weapon.json" assert { type: "json" };
|
||||
|
||||
// App
|
||||
export const AppAchievementsData: TGApp.App.Achievement.Item[] = achievements;
|
||||
|
||||
@@ -34,32 +34,27 @@
|
||||
<ToArcBrith v-model="showOverlay" :data="current" :choice="isAether" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import ToArcBrith from "../../components/pageArchive/to-arcBrith.vue";
|
||||
import { ArcBirDraw, ArcBirRole } from "../../data/index.js";
|
||||
import TGClient from "../../utils/TGClient.js";
|
||||
|
||||
const page = ref(1);
|
||||
const length = ref(0);
|
||||
const visible = ref(0);
|
||||
const renderItems = ref<TGApp.Archive.Birth.DrawItem[]>([]);
|
||||
const curSelect = ref<TGApp.Archive.Birth.RoleItem | null>(null);
|
||||
const selectedItem = ref<TGApp.Archive.Birth.DrawItem[]>([]);
|
||||
const current = ref<TGApp.Archive.Birth.DrawItem>();
|
||||
const isAether = ref<boolean>(false);
|
||||
const showOverlay = ref(false);
|
||||
const route = useRoute();
|
||||
|
||||
watch(
|
||||
() => page.value,
|
||||
() => {
|
||||
const start = (page.value - 1) * 12;
|
||||
const end = start + 12;
|
||||
selectedItem.value = renderItems.value.slice(start, end);
|
||||
},
|
||||
);
|
||||
const page = ref<number>(1);
|
||||
const length = ref<number>(0);
|
||||
const visible = ref(0);
|
||||
const renderItems = shallowRef<TGApp.Archive.Birth.DrawItem[]>([]);
|
||||
const curSelect = shallowRef<TGApp.Archive.Birth.RoleItem | null>(null);
|
||||
const current = shallowRef<TGApp.Archive.Birth.DrawItem>();
|
||||
const isAether = ref<boolean>(false);
|
||||
const showOverlay = ref<boolean>(false);
|
||||
|
||||
const selectedItem = computed<TGApp.Archive.Birth.DrawItem[]>(() => {
|
||||
return renderItems.value.slice((page.value - 1) * 12, page.value * 12);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => curSelect.value,
|
||||
@@ -73,7 +68,6 @@ watch(
|
||||
}
|
||||
length.value = Math.ceil(renderItems.value.length / 12);
|
||||
page.value = 1;
|
||||
selectedItem.value = renderItems.value.slice(0, 12);
|
||||
visible.value = length.value > 5 ? 5 : length.value;
|
||||
},
|
||||
);
|
||||
@@ -85,7 +79,6 @@ onMounted(() => {
|
||||
if (dataFind) curSelect.value = dataFind;
|
||||
} else {
|
||||
renderItems.value = ArcBirDraw;
|
||||
selectedItem.value = renderItems.value.slice(0, 12);
|
||||
}
|
||||
length.value = Math.ceil(renderItems.value.length / 12);
|
||||
visible.value = length.value > 5 ? 5 : length.value;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<TwoSelectC v-model="showSelect" @select-c="handleSelect" v-model:reset="resetSelect" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref, watch } from "vue";
|
||||
import { onBeforeMount, ref, shallowRef, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import showDialog from "../../components/func/dialog.js";
|
||||
@@ -35,10 +35,10 @@ import { AppCharacterData } from "../../data/index.js";
|
||||
import { createObc } from "../../utils/TGWindow.js";
|
||||
|
||||
const id = useRoute().params.id.toString() ?? "0";
|
||||
const showSelect = ref(false);
|
||||
const resetSelect = ref(false);
|
||||
const cardsInfo = ref(AppCharacterData);
|
||||
const curItem = ref<TGApp.App.Character.WikiBriefInfo>({
|
||||
const showSelect = ref<boolean>(false);
|
||||
const resetSelect = ref<boolean>(false);
|
||||
const cardsInfo = shallowRef<TGApp.App.Character.WikiBriefInfo[]>(AppCharacterData);
|
||||
const curItem = shallowRef<TGApp.App.Character.WikiBriefInfo>({
|
||||
id: 0,
|
||||
contentId: 0,
|
||||
name: "",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<Suspense>
|
||||
<TwoMaterial v-model="visible" :data="curMaterial">
|
||||
<TwoMaterial v-model="visible" :data="curMaterial" v-if="curMaterial">
|
||||
<template #left>
|
||||
<div class="card-arrow left" @click="switchMaterial(false)">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
@@ -70,47 +70,41 @@
|
||||
</Suspense>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import showSnackbar from "../../components/func/snackbar.js";
|
||||
import TwoMaterial from "../../components/pageWiki/two-material.vue";
|
||||
import { WikiMaterialData } from "../../data/index.js";
|
||||
|
||||
const curMaterial = ref<TGApp.App.Material.WikiItem>(<TGApp.App.Material.WikiItem>{});
|
||||
const sortMaterialsData = ref<Array<TGApp.App.Material.WikiItem>>([]);
|
||||
const curIndex = ref(0);
|
||||
const total = ref(0);
|
||||
const visible = ref(false);
|
||||
const curMaterial = shallowRef<TGApp.App.Material.WikiItem | undefined>();
|
||||
const sortMaterialsData = shallowRef<Array<TGApp.App.Material.WikiItem>>([]);
|
||||
const curIndex = ref<number>(0);
|
||||
const total = ref<number>(0);
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
interface MaterialType {
|
||||
type: string;
|
||||
number: number;
|
||||
}
|
||||
type MaterialType = { type: string; number: number };
|
||||
|
||||
const search = ref<string>();
|
||||
const selectType = ref<string | null>(null);
|
||||
const materialTypes = ref<MaterialType[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
WikiMaterialData.forEach((item: TGApp.App.Material.WikiItem) => {
|
||||
for (const item of WikiMaterialData) {
|
||||
const typeFindIndex = materialTypes.value.findIndex((itemT) => itemT.type === item.type);
|
||||
if (typeFindIndex !== -1) {
|
||||
materialTypes.value[typeFindIndex].number++;
|
||||
} else {
|
||||
if (typeFindIndex === -1) {
|
||||
const itemN: MaterialType = { type: item.type, number: 1 };
|
||||
materialTypes.value.push(itemN);
|
||||
continue;
|
||||
}
|
||||
});
|
||||
materialTypes.value[typeFindIndex].number++;
|
||||
}
|
||||
sortData(WikiMaterialData);
|
||||
showSnackbar.success(`成功获取${sortMaterialsData.value.length}条数据`);
|
||||
});
|
||||
|
||||
function getSelectMaterials(): TGApp.App.Material.WikiItem[] {
|
||||
if (selectType.value === null) {
|
||||
return WikiMaterialData;
|
||||
} else {
|
||||
return WikiMaterialData.filter((item) => item.type === selectType.value);
|
||||
}
|
||||
if (selectType.value === null) return WikiMaterialData;
|
||||
else return WikiMaterialData.filter((item) => item.type === selectType.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -133,14 +127,10 @@ function toMaterial(item: TGApp.App.Material.WikiItem) {
|
||||
|
||||
function switchMaterial(isNext: boolean) {
|
||||
if (isNext) {
|
||||
if (curIndex.value === total.value - 1) {
|
||||
return;
|
||||
}
|
||||
if (curIndex.value === total.value - 1) return;
|
||||
curIndex.value++;
|
||||
} else {
|
||||
if (curIndex.value === 0) {
|
||||
return;
|
||||
}
|
||||
if (curIndex.value === 0) return;
|
||||
curIndex.value--;
|
||||
}
|
||||
curMaterial.value = sortMaterialsData.value[curIndex.value];
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
label="搜索"
|
||||
:hide-details="true"
|
||||
variant="outlined"
|
||||
@click:prepend-inner="searchNamecard"
|
||||
@keyup.enter="searchNamecard"
|
||||
@click:prepend-inner="searchNameCard()"
|
||||
@keyup.enter="searchNameCard()"
|
||||
/>
|
||||
<div class="tw-nc-list">
|
||||
<v-virtual-scroll :items="sortNameCardsData" :item-height="80">
|
||||
<template #default="{ item }">
|
||||
<TopNamecard :data="item" @selected="toNameCard" />
|
||||
<TopNameCard :data="item" @selected="showNameCard(item)" />
|
||||
<div style="height: 10px" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</div>
|
||||
</div>
|
||||
<ToNamecard v-model="visible" :data="curNameCard">
|
||||
<ToNameCard v-model="visible" :data="curNameCard">
|
||||
<template #left>
|
||||
<div class="card-arrow left" @click="switchCard(false)">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
@@ -29,28 +29,26 @@
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
</div>
|
||||
</template>
|
||||
</ToNamecard>
|
||||
</ToNameCard>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, shallowRef } from "vue";
|
||||
|
||||
import ToNamecard from "../../components/app/to-namecard.vue";
|
||||
import TopNamecard from "../../components/app/top-namecard.vue";
|
||||
import ToNameCard from "../../components/app/to-namecard.vue";
|
||||
import TopNameCard from "../../components/app/top-namecard.vue";
|
||||
import showSnackbar from "../../components/func/snackbar.js";
|
||||
import { AppNameCardsData } from "../../data/index.js";
|
||||
|
||||
const curNameCard = ref<TGApp.App.NameCard.Item>();
|
||||
const sortNameCardsData = ref<TGApp.App.NameCard.Item[]>([]);
|
||||
const curIndex = ref(0);
|
||||
const total = ref(0);
|
||||
const visible = ref(false);
|
||||
const search = ref("");
|
||||
const curNameCard = shallowRef<TGApp.App.NameCard.Item>();
|
||||
const sortNameCardsData = shallowRef<TGApp.App.NameCard.Item[]>([]);
|
||||
const curIndex = ref<number>(0);
|
||||
const total = ref<number>(0);
|
||||
const visible = ref<boolean>(false);
|
||||
const search = ref<string>();
|
||||
|
||||
onMounted(() => {
|
||||
sortData(AppNameCardsData);
|
||||
});
|
||||
onMounted(() => sortData(AppNameCardsData));
|
||||
|
||||
function sortData(data: TGApp.App.NameCard.Item[]) {
|
||||
function sortData(data: TGApp.App.NameCard.Item[]): void {
|
||||
sortNameCardsData.value = data.sort((a, b) => a.type - b.type || a.index - b.index);
|
||||
curIndex.value = 0;
|
||||
total.value = sortNameCardsData.value.length;
|
||||
@@ -58,48 +56,45 @@ function sortData(data: TGApp.App.NameCard.Item[]) {
|
||||
showSnackbar.success(`共搜索到 ${sortNameCardsData.value.length} 个结果`);
|
||||
}
|
||||
|
||||
function toNameCard(item: TGApp.App.NameCard.Item) {
|
||||
function showNameCard(item: TGApp.App.NameCard.Item): void {
|
||||
curNameCard.value = item;
|
||||
curIndex.value = sortNameCardsData.value.findIndex((i) => i.name === item.name);
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
function switchCard(isNext: boolean) {
|
||||
if (isNext) {
|
||||
if (curIndex.value === total.value - 1) {
|
||||
showSnackbar.warn("已经是最后一个了");
|
||||
return;
|
||||
}
|
||||
curIndex.value++;
|
||||
} else {
|
||||
if (curIndex.value === 0) {
|
||||
showSnackbar.warn("已经是第一个了");
|
||||
return;
|
||||
}
|
||||
curIndex.value--;
|
||||
function switchCard(isNext: boolean): void {
|
||||
if (isNext && curIndex.value === total.value - 1) {
|
||||
showSnackbar.warn("已经是最后一个了");
|
||||
return;
|
||||
}
|
||||
if (!isNext && curIndex.value === 0) {
|
||||
showSnackbar.warn("已经是第一个了");
|
||||
return;
|
||||
}
|
||||
curIndex.value += isNext ? 1 : -1;
|
||||
curNameCard.value = sortNameCardsData.value[curIndex.value];
|
||||
}
|
||||
|
||||
function searchNamecard() {
|
||||
if (!search.value) {
|
||||
function searchNameCard(): void {
|
||||
if (search.value === undefined) {
|
||||
sortData(AppNameCardsData);
|
||||
} else if (search.value === "") {
|
||||
return;
|
||||
}
|
||||
if (search.value === "") {
|
||||
if (sortNameCardsData.value.length === AppNameCardsData.length) {
|
||||
showSnackbar.warn("请先输入搜索内容");
|
||||
} else {
|
||||
sortData(AppNameCardsData);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const searchResult = AppNameCardsData.filter((item) => {
|
||||
return (
|
||||
item.name.includes(search.value) ||
|
||||
item.desc.includes(search.value) ||
|
||||
item.source.includes(search.value)
|
||||
);
|
||||
});
|
||||
sortData(searchResult);
|
||||
sortData(AppNameCardsData);
|
||||
return;
|
||||
}
|
||||
const searchResult = AppNameCardsData.filter(
|
||||
(item) =>
|
||||
item.name.includes(search.value!) ||
|
||||
item.desc.includes(search.value!) ||
|
||||
item.source.includes(search.value!),
|
||||
);
|
||||
sortData(searchResult);
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
v-model:cur-item="curItem"
|
||||
:key="index"
|
||||
:data="item"
|
||||
@click="switchW(item)"
|
||||
@click="curItem = item"
|
||||
mode="weapon"
|
||||
/>
|
||||
</div>
|
||||
@@ -26,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from "vue";
|
||||
import { onBeforeMount, ref, shallowRef } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import showDialog from "../../components/func/dialog.js";
|
||||
@@ -38,10 +38,10 @@ import { AppWeaponData } from "../../data/index.js";
|
||||
import { createObc } from "../../utils/TGWindow.js";
|
||||
|
||||
const id = useRoute().params.id.toString() ?? "0";
|
||||
const showSelect = ref(false);
|
||||
const resetSelect = ref(false);
|
||||
const cardsInfo = ref(AppWeaponData);
|
||||
const curItem = ref<TGApp.App.Weapon.WikiBriefInfo>({
|
||||
const showSelect = ref<boolean>(false);
|
||||
const resetSelect = ref<boolean>(false);
|
||||
const cardsInfo = shallowRef<Array<TGApp.App.Weapon.WikiBriefInfo>>(AppWeaponData);
|
||||
const curItem = shallowRef<TGApp.App.Weapon.WikiBriefInfo>({
|
||||
id: 0,
|
||||
contentId: 0,
|
||||
name: "",
|
||||
@@ -82,10 +82,6 @@ function handleSelectW(val: SelectedWValue) {
|
||||
cardsInfo.value = filterW;
|
||||
}
|
||||
|
||||
async function switchW(item: TGApp.App.Weapon.WikiBriefInfo): Promise<void> {
|
||||
curItem.value = item;
|
||||
}
|
||||
|
||||
async function toOuter(item?: TGApp.App.Weapon.WikiBriefInfo): Promise<void> {
|
||||
if (!item) return;
|
||||
if (item.contentId === 0) {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
:key="index"
|
||||
:value="value"
|
||||
@click="firstLoad(value)"
|
||||
>{{ rawData[value].name }}
|
||||
>
|
||||
{{ rawData[value].name }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
@@ -71,78 +72,39 @@ import TGLogger from "../../utils/TGLogger.js";
|
||||
import { createPost } from "../../utils/TGWindow.js";
|
||||
import { getGameName } from "../../web/utils/tools.js";
|
||||
|
||||
// 类型定义
|
||||
enum NewsType {
|
||||
notice = "1",
|
||||
activity = "2",
|
||||
news = "3",
|
||||
}
|
||||
|
||||
type NewsKey = keyof typeof NewsType;
|
||||
type PostData = {
|
||||
[key in NewsKey]: TGApp.Plugins.Mys.Post.FullData[];
|
||||
};
|
||||
type PostData = { [key in TGApp.App.Store.NewsType]: TGApp.Plugins.Mys.Post.FullData[] };
|
||||
type RawData = {
|
||||
[key in NewsKey]: {
|
||||
isLast: boolean;
|
||||
name: string;
|
||||
lastId: number;
|
||||
};
|
||||
[key in TGApp.App.Store.NewsType]: { isLast: boolean; name: string; lastId: number };
|
||||
};
|
||||
|
||||
// 路由
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const gid = <string>useRoute().params.gid;
|
||||
const gameName = getGameName(Number(gid));
|
||||
// loading
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// UI 数据
|
||||
const appStore = useAppStore();
|
||||
const tabValues: Readonly<Array<TGApp.App.Store.NewsType>> = ["notice", "activity", "news"];
|
||||
const showList = ref<boolean>(false);
|
||||
const showSearch = ref<boolean>(false);
|
||||
const tabValues = ref<Array<NewsKey>>(["notice", "activity", "news"]);
|
||||
const tabList = ["notice", "activity", "news"];
|
||||
const tab = computed({
|
||||
const tab = computed<TGApp.App.Store.NewsType>({
|
||||
get: () => {
|
||||
if (appStore.recentNewsType === "" || !tabList.includes(appStore.recentNewsType)) {
|
||||
return "notice";
|
||||
}
|
||||
return <NewsKey>appStore.recentNewsType;
|
||||
},
|
||||
set: (val) => {
|
||||
appStore.recentNewsType = val;
|
||||
if (!(appStore.recentNewsType satisfies TGApp.App.Store.NewsType)) return "notice";
|
||||
return appStore.recentNewsType;
|
||||
},
|
||||
set: (v) => (appStore.recentNewsType = v),
|
||||
});
|
||||
|
||||
// 渲染数据
|
||||
const search = ref<string>("");
|
||||
const postData = ref<PostData>({
|
||||
notice: [],
|
||||
activity: [],
|
||||
news: [],
|
||||
});
|
||||
const postData = ref<PostData>({ notice: [], activity: [], news: [] });
|
||||
const rawData = ref<RawData>({
|
||||
notice: {
|
||||
isLast: false,
|
||||
name: "公告",
|
||||
lastId: 0,
|
||||
},
|
||||
activity: {
|
||||
isLast: false,
|
||||
name: "活动",
|
||||
lastId: 0,
|
||||
},
|
||||
news: {
|
||||
isLast: false,
|
||||
name: "咨讯",
|
||||
lastId: 0,
|
||||
},
|
||||
notice: { isLast: false, name: "公告", lastId: 0 },
|
||||
activity: { isLast: false, name: "活动", lastId: 0 },
|
||||
news: { isLast: false, name: "咨讯", lastId: 0 },
|
||||
});
|
||||
|
||||
onMounted(async () => await firstLoad(tab.value));
|
||||
|
||||
async function firstLoad(key: NewsKey, refresh: boolean = false): Promise<void> {
|
||||
async function firstLoad(key: TGApp.App.Store.NewsType, refresh: boolean = false): Promise<void> {
|
||||
if (rawData.value[key].lastId !== 0) {
|
||||
if (!refresh) return;
|
||||
postData.value[key] = [];
|
||||
@@ -150,7 +112,7 @@ async function firstLoad(key: NewsKey, refresh: boolean = false): Promise<void>
|
||||
}
|
||||
showLoading.start(`正在获取${gameName}${rawData.value[key].name}数据...`);
|
||||
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
|
||||
const getData = await Mys.Painter.getNewsList(gid, NewsType[key]);
|
||||
const getData = await Mys.Painter.getNewsList(gid, TGApp.App.Store.NewsTypeEnum[key]);
|
||||
rawData.value[key].isLast = getData.is_last;
|
||||
rawData.value[key].lastId = getData.list.length;
|
||||
postData.value[key] = getData.list;
|
||||
@@ -165,7 +127,7 @@ async function switchAnno(): Promise<void> {
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
async function loadMore(key: NewsKey): Promise<void> {
|
||||
async function loadMore(key: TGApp.App.Store.NewsType): Promise<void> {
|
||||
loading.value = true;
|
||||
if (rawData.value[key].isLast) {
|
||||
showSnackbar.warn("已经是最后一页了");
|
||||
@@ -173,7 +135,12 @@ async function loadMore(key: NewsKey): Promise<void> {
|
||||
return;
|
||||
}
|
||||
showLoading.start(`正在获取${gameName}${rawData.value[key].name}数据...`);
|
||||
const getData = await Mys.Painter.getNewsList(gid, NewsType[key], 20, rawData.value[key].lastId);
|
||||
const getData = await Mys.Painter.getNewsList(
|
||||
gid,
|
||||
TGApp.App.Store.NewsTypeEnum[key],
|
||||
20,
|
||||
rawData.value[key].lastId,
|
||||
);
|
||||
rawData.value[key].lastId = rawData.value[key].lastId + getData.list.length;
|
||||
rawData.value[key].isLast = getData.is_last;
|
||||
postData.value[key] = postData.value[key].concat(getData.list);
|
||||
@@ -195,10 +162,10 @@ async function searchPost(): Promise<void> {
|
||||
const numCheck = Number(search.value);
|
||||
if (isNaN(numCheck)) {
|
||||
showSearch.value = true;
|
||||
} else {
|
||||
await createPost(search.value);
|
||||
showSearch.value = false;
|
||||
return;
|
||||
}
|
||||
await createPost(search.value);
|
||||
showSearch.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -174,22 +174,6 @@ class Sqlite {
|
||||
);
|
||||
await this.initDB();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测特定表是否存在
|
||||
* @since Beta v0.4.5
|
||||
* @param {string} table 表名
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async checkTableExist(table: string): Promise<boolean> {
|
||||
const db = await this.getDB();
|
||||
const sql = `SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table'
|
||||
AND name = '${table}';`;
|
||||
const res: Array<{ name: string }> = await db.select(sql);
|
||||
return res.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
const TGSqlite = new Sqlite();
|
||||
|
||||
@@ -46,7 +46,7 @@ export const useAppStore = defineStore(
|
||||
// 语言
|
||||
const lang = ref<AnnoLang>("zh-cn");
|
||||
// 最近的咨讯类型
|
||||
const recentNewsType = ref<string>("notice");
|
||||
const recentNewsType = ref<TGApp.App.Store.NewsType>("notice");
|
||||
// 是否开启分辨率回正
|
||||
const needResize = ref<string>("true");
|
||||
// 分享图生成默认设置,为0表示默认保存到文件,为数字表示当大小超过xMB时保存到文件,否则保存到剪贴板
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file store modules home.ts
|
||||
* @file store/modules/home.ts
|
||||
* @description Home store module
|
||||
* @since Alpha v0.1.6
|
||||
* @since Beta v0.6.5
|
||||
*/
|
||||
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
25
src/types/App/Store.d.ts
vendored
Normal file
25
src/types/App/Store.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @file types/App/Store.d.ts
|
||||
* @description 涉及store但是没有特定的store模块的类型定义文件
|
||||
* @since Beta v0.6.5
|
||||
*/
|
||||
|
||||
declare namespace TGApp.App.Store {
|
||||
/**
|
||||
* @description 咨讯类型枚举
|
||||
* @since Beta v0.6.5
|
||||
* @enum {string}
|
||||
*/
|
||||
enum NewsTypeEnum {
|
||||
notice = "1",
|
||||
activity = "2",
|
||||
news = "3",
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 咨讯类型枚举
|
||||
* @since Beta v0.6.5
|
||||
* @type {keyof typeof NewsTypeEnum}
|
||||
*/
|
||||
type NewsType = keyof typeof NewsTypeEnum;
|
||||
}
|
||||
13
src/types/Plugins/UIAF.d.ts
vendored
13
src/types/Plugins/UIAF.d.ts
vendored
@@ -57,17 +57,4 @@ declare namespace TGApp.Plugins.UIAF {
|
||||
current: number;
|
||||
status: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface Backup
|
||||
* @description 数据备份时的格式,用于标识不同存档
|
||||
* @since Beta v0.6.0
|
||||
* @property {number} uid - 存档UID
|
||||
* @property {Achievement[]} data - 存档数据
|
||||
* @returns Backup
|
||||
*/
|
||||
interface Backup {
|
||||
uid: number;
|
||||
data: Achievement[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,12 +107,12 @@ import TGConstant from "../web/constant/TGConstant.js";
|
||||
|
||||
const appVersion = await app.getVersion();
|
||||
const postId = Number(useRoute().params.post_id);
|
||||
const renderPost = shallowRef<TGApp.Plugins.Mys.SctPost.Base[]>([]);
|
||||
const postData = shallowRef<TGApp.Plugins.Mys.Post.FullData>();
|
||||
const showCollection = ref<boolean>(false);
|
||||
const shareTime = ref<number>(Math.floor(Date.now() / 1000));
|
||||
// eslint-disable-next-line no-undef
|
||||
const shareTimeTimer = ref<NodeJS.Timeout | undefined>(undefined);
|
||||
const renderPost = shallowRef<TGApp.Plugins.Mys.SctPost.Base[]>([]);
|
||||
const postData = shallowRef<TGApp.Plugins.Mys.Post.FullData>();
|
||||
|
||||
function getGameIcon(gameId: number): string {
|
||||
const find = TGConstant.BBS.CHANNELS.find((item) => item.gid === gameId.toString());
|
||||
|
||||
11
src/vite-env.d.ts
vendored
11
src/vite-env.d.ts
vendored
@@ -50,14 +50,3 @@ interface ImportMetaEnv {
|
||||
declare interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 极验验证的请求方法-请求参数
|
||||
* @param {TGApp.BBS.Geetest.InitGeetestParams} params
|
||||
* @param {(captchaObj: TGApp.BBS.Geetest.GeetestCaptcha) => void} callback
|
||||
* @return void
|
||||
*/
|
||||
declare function initGeetest(
|
||||
params: TGApp.Plugins.Mys.Geetest.InitGeetestParams,
|
||||
callback: (captchaObj: TGApp.Plugins.Mys.Geetest.GeetestCaptcha) => void,
|
||||
): void;
|
||||
|
||||
Reference in New Issue
Block a user