mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-13 09:28:14 +08:00
252
src/components/overlay/to-arcBrith.vue
Normal file
252
src/components/overlay/to-arcBrith.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" 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="顶部图像" />
|
||||
<div class="toab-dialog" v-show="showText">
|
||||
<div v-for="(item, index) in textParse" :key="index" class="toab-dialog-item">
|
||||
<div class="toab-dialog-item-icon" v-if="item.icon" :title="item.name">
|
||||
<img :src="item.icon" alt="对白头像" />
|
||||
</div>
|
||||
<div v-else-if="item.name !== '未知'" class="toab-dialog-item-name">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div :class="item.icon ? 'toab-dialog-item-text' : 'toab-dialog-item-text-mini'">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toab-top-tools">
|
||||
<v-icon @click="onCopy" title="复制到剪贴板">mdi-content-copy</v-icon>
|
||||
<v-icon @click="onDownload" title="下载到本地">mdi-download</v-icon>
|
||||
<v-icon @click="loadText" :title="showText ? '隐藏对白' : '显示对白'">
|
||||
{{ showText ? "mdi-eye-off" : "mdi-eye" }}
|
||||
</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { http } from "@tauri-apps/api";
|
||||
import { ResponseType } from "@tauri-apps/api/http";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { xml2json } from "xml-js";
|
||||
|
||||
import { copyToClipboard, getImageBuffer, saveCanvasImg } from "../../utils/TGShare";
|
||||
import { bytesToSize } from "../../utils/toolFunc";
|
||||
import showSnackbar from "../func/snackbar";
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
|
||||
interface ToArcBirthProps {
|
||||
modelValue: boolean;
|
||||
data?: TGApp.Archive.Birth.DrawItem;
|
||||
choice: boolean;
|
||||
}
|
||||
|
||||
interface ToArcBirthEmits {
|
||||
(event: "update:modelValue", value: boolean): void;
|
||||
}
|
||||
|
||||
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({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
|
||||
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() {
|
||||
buffer.value = null;
|
||||
textParse.value = [];
|
||||
showText.value = false;
|
||||
}
|
||||
|
||||
async function onCopy(): Promise<void> {
|
||||
if (!props.data) return;
|
||||
const image = props.data.take_picture[Number(props.choice)];
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||
const size = bytesToSize(buffer.value.byteLength);
|
||||
await copyToClipboard(buffer.value);
|
||||
showSnackbar({ text: `图片已复制到剪贴板,大小:${size}` });
|
||||
}
|
||||
|
||||
async function onDownload() {
|
||||
if (!props.data) return;
|
||||
const image = props.data.take_picture[Number(props.choice)];
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||
const size = bytesToSize(buffer.value.byteLength);
|
||||
await saveCanvasImg(buffer.value, Date.now().toString());
|
||||
showSnackbar({ text: `图片已下载到本地,大小:${size}` });
|
||||
}
|
||||
|
||||
async function loadText(): Promise<void> {
|
||||
if (!props.data) return;
|
||||
if (textParse.value.length > 0) {
|
||||
showText.value = !showText.value;
|
||||
return;
|
||||
}
|
||||
const resSource: any = await parseXml(props.data.gal_resource);
|
||||
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,
|
||||
};
|
||||
});
|
||||
showText.value = true;
|
||||
}
|
||||
|
||||
async function parseXml(link: string) {
|
||||
const res = await http.fetch<string>(link, {
|
||||
method: "GET",
|
||||
responseType: ResponseType.Text,
|
||||
});
|
||||
return JSON.parse(xml2json(res.data));
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.toab-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 50vw;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toab-img {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toab-img img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.toab-top-tools {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
background-color: var(--common-shadow-t-2);
|
||||
}
|
||||
|
||||
.toab-dialog {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 40vh;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background: var(--common-shadow-t-2);
|
||||
color: var(--box-text-1);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.toab-dialog-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.toab-dialog-item-icon {
|
||||
width: 50px;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.toab-dialog-item-name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.toab-dialog-item-text {
|
||||
font-size: 1.2rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.toab-dialog-item-text-mini {
|
||||
margin-left: 3rem;
|
||||
font-size: 1rem;
|
||||
opacity: 0.8;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -1,29 +1,37 @@
|
||||
<template>
|
||||
<div class="ab-container">
|
||||
<div class="ab-draw-top">
|
||||
<v-switch v-model="isLumine" :label="isLumine ? '荧' : '空'" />
|
||||
<v-switch v-model="isAether" :label="isAether ? '空' : '荧'" />
|
||||
</div>
|
||||
<div class="ab-draw-grid">
|
||||
<div v-for="item in selectedItem" :key="item.op_id" class="ab-draw-item">
|
||||
<v-img
|
||||
:src="item.take_picture[isLumine ? 0 : 1]"
|
||||
:lazy-src="item.unread_picture[isLumine ? 0 : 1]"
|
||||
/>
|
||||
<span>{{ item.year }} {{ item.birthday }} {{ item.role_name }}</span>
|
||||
<div v-for="item in selectedItem" :key="item.op_id" class="ab-draw">
|
||||
<div class="ab-draw-cover" @click="showImg(item)">
|
||||
<img
|
||||
:src="item.take_picture[Number(isAether)]"
|
||||
:data-src="item.unread_picture[Number(isAether)]"
|
||||
:alt="item.word_text"
|
||||
/>
|
||||
</div>
|
||||
<div class="ab-di-info">{{ item.year }} {{ item.birthday }} {{ item.role_name }}</div>
|
||||
<div class="ab-di-text" :title="item.word_text">{{ item.word_text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-pagination v-model="page" :length="length" />
|
||||
</div>
|
||||
<ToArcBrith v-model="showOverlay" :data="current" :choice="isAether" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch, watchEffect } from "vue";
|
||||
|
||||
import ToArcBrith from "../../components/overlay/to-arcBrith.vue";
|
||||
import { ArcBirDraw } from "../../data";
|
||||
|
||||
const page = ref(1);
|
||||
const length = ref(0);
|
||||
const selectedItem = ref<TGApp.Archive.Birth.DrawItem[]>(ArcBirDraw);
|
||||
const isLumine = ref<boolean>(true);
|
||||
const current = ref<TGApp.Archive.Birth.DrawItem>();
|
||||
const isAether = ref<boolean>(false);
|
||||
const showOverlay = ref(false);
|
||||
|
||||
watch(page, (val) => {
|
||||
const start = (val - 1) * 12;
|
||||
@@ -38,6 +46,11 @@ watchEffect(() => {
|
||||
onMounted(() => {
|
||||
length.value = Math.ceil(ArcBirDraw.length / 12);
|
||||
});
|
||||
|
||||
function showImg(item: TGApp.Archive.Birth.DrawItem) {
|
||||
current.value = item;
|
||||
showOverlay.value = true;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.ab-container {
|
||||
@@ -59,11 +72,54 @@ onMounted(() => {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
column-gap: 10px;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.ab-draw-item {
|
||||
text-align: center;
|
||||
.ab-draw {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ab-draw-cover {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
border-radius: 5px;
|
||||
aspect-ratio: 125 / 54;
|
||||
}
|
||||
|
||||
.ab-draw-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.ab-draw-cover img:hover {
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transform: scale(1.1);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.ab-di-info {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ab-di-text {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user