🌱 卡片视图适配,2/3

#121
This commit is contained in:
目棃
2024-08-24 20:00:23 +08:00
parent 1fe33ba4fd
commit 4dd14a9d93
11 changed files with 319 additions and 336 deletions

View 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>

View 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>

View 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>

View 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>

View 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>