mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-12 09:18:14 +08:00
100
src/components/userAvatarCard/duc-detail-olb.vue
Normal file
100
src/components/userAvatarCard/duc-detail-olb.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="duc-dolb-box">
|
||||
<div
|
||||
v-for="constellation in constellations"
|
||||
:key="constellation.pos"
|
||||
:title="constellation.name"
|
||||
class="duc-dolb-item"
|
||||
>
|
||||
<div v-if="!constellation.is_actived" class="duc-dolb-lock">
|
||||
<v-icon color="white">mdi-lock</v-icon>
|
||||
</div>
|
||||
<img class="duc-dolb-icon" :src="constellation.icon" alt="constellation" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch, onUnmounted } from "vue";
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare.js";
|
||||
|
||||
interface DucDetailOlbProps {
|
||||
modelValue: TGApp.Game.Avatar.Constellation[];
|
||||
}
|
||||
|
||||
const props = defineProps<DucDetailOlbProps>();
|
||||
const constellations = ref<TGApp.Game.Avatar.Constellation[]>([]);
|
||||
|
||||
async function loadData() {
|
||||
const tempConstellations = JSON.parse(JSON.stringify(props.modelValue));
|
||||
for (const constellation of tempConstellations) {
|
||||
if (constellation.icon.startsWith("blob:")) return;
|
||||
constellation.icon = await saveImgLocal(constellation.icon);
|
||||
}
|
||||
constellations.value = tempConstellations;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => {
|
||||
for (const constellation of constellations.value) {
|
||||
if (constellation.icon.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(constellation.icon);
|
||||
}
|
||||
}
|
||||
await loadData();
|
||||
},
|
||||
);
|
||||
onUnmounted(() => {
|
||||
for (const constellation of constellations.value) {
|
||||
if (constellation.icon.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(constellation.icon);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.duc-dolb-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-dolb-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgb(0 0 0/40%);
|
||||
}
|
||||
|
||||
.duc-dolb-lock {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3px;
|
||||
border-radius: 50%;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background-color: rgb(0 0 0 / 40%);
|
||||
}
|
||||
|
||||
.duc-dolb-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
padding: 5px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
100
src/components/userAvatarCard/duc-detail-olt.vue
Normal file
100
src/components/userAvatarCard/duc-detail-olt.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="ddo-lt-box">
|
||||
<div class="ddo-ltb-icon" :title="props.data.name">
|
||||
<TItemBox :model-value="boxData" />
|
||||
</div>
|
||||
<div class="ddo-ltb-info">
|
||||
<span>{{ props.data.name }}</span>
|
||||
<span>Lv.{{ props.data.level }}</span>
|
||||
<span>{{ info }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import { getZhElement } from "../../utils/toolFunc.js";
|
||||
import TItemBox, { TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
type DucDetailOltProps =
|
||||
| {
|
||||
data: TGApp.Game.Avatar.Avatar;
|
||||
mode: "avatar";
|
||||
}
|
||||
| {
|
||||
data: TGApp.Game.Avatar.WeaponDetail;
|
||||
mode: "weapon";
|
||||
};
|
||||
|
||||
const props = defineProps<DucDetailOltProps>();
|
||||
const boxData = computed<TItemBoxData>(() => {
|
||||
if (props.mode === "avatar") {
|
||||
const avatar = <TGApp.Game.Avatar.Avatar>props.data;
|
||||
return {
|
||||
bg: `/icon/bg/${avatar.rarity}-Star.webp`,
|
||||
icon: `/WIKI/character/${avatar.id}.webp`,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
innerHeight: 0,
|
||||
innerText: "",
|
||||
clickable: false,
|
||||
lt: `/icon/element/${getZhElement(avatar.element)}元素.webp`,
|
||||
ltSize: "30px",
|
||||
};
|
||||
} else {
|
||||
const weapon = <TGApp.Game.Avatar.WeaponDetail>props.data;
|
||||
return {
|
||||
bg: `/icon/bg/${weapon.rarity}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${weapon.id}.webp`,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
innerHeight: 0,
|
||||
innerText: "",
|
||||
clickable: false,
|
||||
lt: `/icon/weapon/${weapon.type_name}.webp`,
|
||||
ltSize: "30px",
|
||||
};
|
||||
}
|
||||
});
|
||||
const info = computed(() => {
|
||||
if (props.mode === "avatar") {
|
||||
const avatar = <TGApp.Game.Avatar.Avatar>props.data;
|
||||
return `好感 ${avatar.fetter}`;
|
||||
} else {
|
||||
const weapon = <TGApp.Game.Avatar.WeaponDetail>props.data;
|
||||
return `精炼 ${weapon.affix_level}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.ddo-lt-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.ddo-ltb-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
color: var(--tgc-white-1);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ddo-ltb-info :nth-child(1) {
|
||||
margin-bottom: 10px;
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ddo-ltb-info :not(:nth-child(1)) {
|
||||
font-family: var(--font-text);
|
||||
font-size: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
95
src/components/userAvatarCard/duc-detail-ort.vue
Normal file
95
src/components/userAvatarCard/duc-detail-ort.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="duc-dort-box">
|
||||
<div
|
||||
:title="talent.name"
|
||||
v-for="talent in talents"
|
||||
:key="talent.skill_id"
|
||||
class="duc-dort-item"
|
||||
>
|
||||
<span>{{ talent.name }}</span>
|
||||
<img :src="talent.icon" alt="talent" />
|
||||
<span>Lv.{{ talent.level === 0 ? 1 : talent.level }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, watch, ref, onUnmounted } from "vue";
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare.js";
|
||||
|
||||
interface DucDetailOrtProps {
|
||||
modelValue: TGApp.Game.Avatar.Skill[];
|
||||
}
|
||||
|
||||
const props = defineProps<DucDetailOrtProps>();
|
||||
const talents = ref<TGApp.Game.Avatar.Skill[]>([]);
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const tempTalent = JSON.parse(JSON.stringify(props.modelValue));
|
||||
for (const talent of tempTalent) {
|
||||
if (talent.icon.startsWith("blob:")) return;
|
||||
talent.icon = await saveImgLocal(talent.icon);
|
||||
}
|
||||
talents.value = tempTalent;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => {
|
||||
for (const talent of talents.value) {
|
||||
if (talent.icon.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(talent.icon);
|
||||
}
|
||||
}
|
||||
await loadData();
|
||||
},
|
||||
);
|
||||
onUnmounted(() => {
|
||||
for (const talent of talents.value) {
|
||||
if (talent.icon.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(talent.icon);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.duc-dort-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-dort-item {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-dort-item img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 5px;
|
||||
border-radius: 50%;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgba(0 0 0 /40%);
|
||||
}
|
||||
|
||||
.duc-dort-item span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--tgc-white-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
text-shadow: 0 0 5px rgba(0 0 0/40%);
|
||||
}
|
||||
|
||||
.duc-dort-item :nth-last-child(1) {
|
||||
width: 48px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
</style>
|
||||
94
src/components/userAvatarCard/duc-detail-relic.vue
Normal file
94
src/components/userAvatarCard/duc-detail-relic.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="duc-dr-box">
|
||||
<div class="duc-dr-bg">
|
||||
<img :src="`/icon/relic/${props.pos}.webp`" alt="relic" />
|
||||
</div>
|
||||
<div v-if="props.modelValue" class="duc-dr-bg">
|
||||
<img :src="`/icon/bg/${props.modelValue.rarity}-Star.webp`" alt="bg" />
|
||||
</div>
|
||||
<div v-if="props.modelValue" class="duc-dr-icon">
|
||||
<img :src="props.modelValue.icon" alt="relic" />
|
||||
</div>
|
||||
<div v-if="props.modelValue !== false" class="duc-dr-level">
|
||||
{{ props.modelValue.level }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
interface ducDetailRelicProps {
|
||||
modelValue: TGApp.Game.Avatar.Relic | false;
|
||||
pos: number;
|
||||
}
|
||||
|
||||
const props = defineProps<ducDetailRelicProps>();
|
||||
const relicBg = computed<string>(() => {
|
||||
if (props.modelValue === false) return "transparent";
|
||||
if (props.modelValue.rarity === 0 || props.modelValue.rarity === 1) return "var(--tgc-od-white)";
|
||||
if (props.modelValue.rarity === 2) return "var(--tgc-od-green)";
|
||||
if (props.modelValue.rarity === 3) return "var(--tgc-od-blue)";
|
||||
if (props.modelValue.rarity === 4) return "var(--tgc-od-purple)";
|
||||
if (props.modelValue.rarity === 5) return "var(--tgc-od-orange)";
|
||||
return "var(--tgc-od-red)";
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.duc-dr-box {
|
||||
position: relative;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: rgb(50 56 68/50%);
|
||||
}
|
||||
|
||||
.duc-dr-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.duc-dr-bg:nth-child(1) {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.duc-dr-bg img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.duc-dr-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.duc-dr-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.duc-dr-level {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
display: flex;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid var(--tgc-od-red);
|
||||
border-radius: 50%;
|
||||
background: v-bind(relicBg);
|
||||
color: var(--tgc-white-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
184
src/components/userAvatarCard/tuc-detail-card.vue
Normal file
184
src/components/userAvatarCard/tuc-detail-card.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="duc-do-container">
|
||||
<img :src="nameCard" class="duc-doc-bg" v-if="nameCard !== false" alt="bg" />
|
||||
<div class="duc-doc-bgc" />
|
||||
<!-- 左上角色跟武器 -->
|
||||
<div class="duc-doc-lt">
|
||||
<DucDetailOlt :data="props.modelValue.avatar" mode="avatar" />
|
||||
<DucDetailOlt :data="props.modelValue.weapon" mode="weapon" />
|
||||
<div class="duc-relic">
|
||||
<DucDetailRelic
|
||||
v-for="(relic, index) in relicList"
|
||||
:key="index"
|
||||
:model-value="relic"
|
||||
:pos="index + 1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn
|
||||
class="duc-doc-btn"
|
||||
@click="share"
|
||||
variant="outlined"
|
||||
:loading="loading"
|
||||
data-html2canvas-ignore
|
||||
>
|
||||
<v-icon>mdi-share-variant</v-icon>
|
||||
<span>分享</span>
|
||||
</v-btn>
|
||||
<!-- 右侧天赋 -->
|
||||
<div class="duc-doc-rt">
|
||||
<DucDetailOrt :model-value="props.modelValue.skills" />
|
||||
</div>
|
||||
<!-- 左下命座 -->
|
||||
<div class="duc-doc-lb">
|
||||
<DucDetailOlb :model-value="props.modelValue.constellations" />
|
||||
</div>
|
||||
<!-- 底部水印信息 -->
|
||||
<div class="duc-doc-bt">
|
||||
UID: {{ props.modelValue.uid }} Updated: {{ props.modelValue.updated }} | Rendered by
|
||||
TeyvatGuide v{{ version }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { app } from "@tauri-apps/api";
|
||||
import { computed, ref, watch, onMounted } from "vue";
|
||||
|
||||
import TSUserAvatar from "../../plugins/Sqlite/modules/userAvatar.js";
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
|
||||
import DucDetailOlb from "./duc-detail-olb.vue";
|
||||
import DucDetailOlt from "./duc-detail-olt.vue";
|
||||
import DucDetailOrt from "./duc-detail-ort.vue";
|
||||
import DucDetailRelic from "./duc-detail-relic.vue";
|
||||
|
||||
interface DucDetailOverlayProps {
|
||||
modelValue: TGApp.Sqlite.Character.UserRole;
|
||||
}
|
||||
|
||||
type fixedLenArray<T, N extends number> = [T, ...T[]] & { length: N };
|
||||
type RelicList = fixedLenArray<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
|
||||
const props = defineProps<DucDetailOverlayProps>();
|
||||
const version = await app.getVersion();
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const relicList = computed<RelicList>(() => {
|
||||
return [
|
||||
props.modelValue.relics.find((item) => item.pos === 1) || false,
|
||||
props.modelValue.relics.find((item) => item.pos === 2) || false,
|
||||
props.modelValue.relics.find((item) => item.pos === 3) || false,
|
||||
props.modelValue.relics.find((item) => item.pos === 4) || false,
|
||||
props.modelValue.relics.find((item) => item.pos === 5) || false,
|
||||
];
|
||||
});
|
||||
|
||||
const nameCard = ref<string | false>(false);
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => {
|
||||
await loadData();
|
||||
},
|
||||
);
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
if (props.modelValue.cid === 10000005 || props.modelValue.cid === 10000007) {
|
||||
nameCard.value = "/source/nameCard/profile/原神·印象.webp";
|
||||
} else {
|
||||
const card = await TSUserAvatar.getAvatarCard(props.modelValue.cid);
|
||||
if (card !== false) {
|
||||
nameCard.value = `/source/nameCard/profile/${card}.webp`;
|
||||
} else {
|
||||
nameCard.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function share(): Promise<void> {
|
||||
const detailBox = <HTMLElement>document.querySelector(".duc-do-container");
|
||||
const fileName = `【角色详情】-${props.modelValue.avatar.name}`;
|
||||
loading.value = true;
|
||||
await generateShareImg(fileName, detailBox);
|
||||
loading.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.duc-do-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 800px;
|
||||
border-radius: 5px;
|
||||
aspect-ratio: 21 / 10;
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.duc-doc-bg {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.duc-doc-bgc {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
.duc-doc-lt {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 5px;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-relic {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.duc-doc-btn {
|
||||
position: absolute;
|
||||
bottom: 90px;
|
||||
left: 370px;
|
||||
color: var(--tgc-white-1);
|
||||
}
|
||||
|
||||
.duc-doc-rt {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.duc-doc-lb {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.duc-doc-bt {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: -1px;
|
||||
color: var(--tgc-white-1);
|
||||
font-size: 12px;
|
||||
text-shadow: 0 0 2px var(--tgc-dark-2);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user