mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-10 08:58:15 +08:00
@@ -1,225 +0,0 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onOverlayCancel" blur-val="20px">
|
||||
<div class="duc-do-box">
|
||||
<!-- 左侧箭头 -->
|
||||
<div class="duc-arrow-left" @click="handleClick('left')">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="left" />
|
||||
</div>
|
||||
<!-- 中间内容 -->
|
||||
<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.dataVal" mode="avatar" />
|
||||
<DucDetailOlt :data="JSON.parse(props.dataVal.weapon)" mode="weapon" />
|
||||
<DucDetailRelics :data="props.dataVal.reliquary" />
|
||||
</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="JSON.parse(props.dataVal.talent)" />
|
||||
</div>
|
||||
<!-- 左下命座 -->
|
||||
<div class="duc-doc-lb">
|
||||
<DucDetailOlb :model-value="JSON.parse(props.dataVal.constellation)" />
|
||||
</div>
|
||||
<!-- 底部水印信息 -->
|
||||
<div class="duc-doc-bt">
|
||||
UID: {{ props.dataVal.uid }} Updated: {{ props.dataVal.updated }} | Rendered by
|
||||
TeyvatGuide v{{ version }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧箭头 -->
|
||||
<div class="duc-arrow-right" @click="handleClick('right')">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { app } from "@tauri-apps/api";
|
||||
import { computed, onMounted, onUpdated, ref } from "vue";
|
||||
|
||||
import TGSqlite from "../../plugins/Sqlite/index.js";
|
||||
import { generateShareImg } from "../../utils/TGShare.js";
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
|
||||
import DucDetailOlb from "./duc-detail-olb.vue";
|
||||
import DucDetailOlt from "./duc-detail-olt.vue";
|
||||
import DucDetailOrt from "./duc-detail-ort.vue";
|
||||
import DucDetailRelics from "./duc-detail-relics.vue";
|
||||
|
||||
interface DucDetailOverlayProps {
|
||||
modelValue: boolean;
|
||||
dataVal: TGApp.Sqlite.Character.UserRole;
|
||||
}
|
||||
|
||||
type DucDetailOverlayEmits = {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
(e: "clickL"): void;
|
||||
(e: "clickR"): void;
|
||||
};
|
||||
|
||||
const props = defineProps<DucDetailOverlayProps>();
|
||||
const emits = defineEmits<DucDetailOverlayEmits>();
|
||||
const version = await app.getVersion();
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
|
||||
// share
|
||||
const loading = ref<boolean>(false);
|
||||
// 渲染数据
|
||||
const nameCard = ref<string | false>(false);
|
||||
|
||||
function onOverlayCancel() {
|
||||
visible.value = false;
|
||||
emits("update:modelValue", false);
|
||||
}
|
||||
|
||||
function handleClick(pos: "left" | "right") {
|
||||
pos === "left" ? emits("clickL") : emits("clickR");
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
onUpdated(async () => {
|
||||
await loadData();
|
||||
console.log("updated, loadData");
|
||||
console.log(props.dataVal);
|
||||
});
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
if (!props.modelValue) return;
|
||||
if (props.dataVal.cid !== 10000005 && props.dataVal.cid !== 10000007) {
|
||||
const role = await TGSqlite.getAppCharacter(props.dataVal.cid);
|
||||
nameCard.value = `/source/nameCard/profile/${role.nameCard}.webp`;
|
||||
} else {
|
||||
nameCard.value = "/source/nameCard/profile/原神·印象.webp";
|
||||
}
|
||||
}
|
||||
|
||||
async function share(): Promise<void> {
|
||||
const detailBox = <HTMLElement>document.querySelector(".duc-do-container");
|
||||
const fileName = `【角色详情】-${props.dataVal.name}`;
|
||||
loading.value = true;
|
||||
await generateShareImg(fileName, detailBox);
|
||||
loading.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.duc-do-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-arrow-left,
|
||||
.duc-arrow-right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dark .duc-arrow-left,
|
||||
.dark .duc-arrow-right {
|
||||
filter: invert(11%) sepia(73%) saturate(11%) hue-rotate(139deg) brightness(97%) contrast(81%);
|
||||
}
|
||||
|
||||
.duc-arrow-left img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.duc-arrow-right img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.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-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>
|
||||
@@ -1,45 +0,0 @@
|
||||
<template>
|
||||
<div class="ddr-box">
|
||||
<DucDetailRelic
|
||||
v-for="(relic, index) in transData"
|
||||
:key="index"
|
||||
:model-value="relic"
|
||||
:pos="index + 1"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import DucDetailRelic from "./duc-detail-relic.vue";
|
||||
|
||||
interface DucDetailRelicsProps {
|
||||
data: string;
|
||||
}
|
||||
|
||||
const props = defineProps<DucDetailRelicsProps>();
|
||||
const transData = computed<Array<TGApp.Sqlite.Character.RoleReliquary | false>>(() => {
|
||||
if (!props.data || props.data === "") return [false, false, false, false, false];
|
||||
try {
|
||||
const parsedData: TGApp.Sqlite.Character.RoleReliquary[] = JSON.parse(props.data);
|
||||
let relics: Array<TGApp.Sqlite.Character.RoleReliquary | false> = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const relic = parsedData.find((relic) => relic.pos === i + 1);
|
||||
if (relic) relics.push(relic);
|
||||
else relics.push(false);
|
||||
}
|
||||
return relics;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return [false, false, false, false, false];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.ddr-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<TOverlay v-model="visible" hide blur-val="20px">
|
||||
<div class="tdo-box">
|
||||
<div class="tdo-tabs-container">
|
||||
<v-tabs v-model="modeTab" class="tdo-tabs" :rounded="true">
|
||||
<v-tabs v-model="modeTab" class="tdo-tabs">
|
||||
<v-tab value="classic">经典视图</v-tab>
|
||||
<v-tab value="card">卡片视图(简略)</v-tab>
|
||||
<v-tab value="dev">卡片视图(详细)</v-tab>
|
||||
</v-tabs>
|
||||
<v-btn @click="onCancel" icon="mdi-close" size="28" variant="outlined" />
|
||||
</div>
|
||||
<div class="tdo-container">
|
||||
<div class="tdo-box-arrow left" @click="handleClick('left')">
|
||||
@@ -14,10 +15,12 @@
|
||||
</div>
|
||||
<v-window class="tdo-box-container" v-model="modeTab">
|
||||
<v-window-item value="classic">
|
||||
<TucDetailCard :data-val="avatar" />
|
||||
<TucDetailOld :model-value="avatar" />
|
||||
</v-window-item>
|
||||
<v-window-item value="card"> </v-window-item>
|
||||
<v-window-item value="dev"> </v-window-item>
|
||||
<v-window-item value="card">
|
||||
<TucDetailCard :model-value="avatar" />
|
||||
</v-window-item>
|
||||
<v-window-item value="dev"></v-window-item>
|
||||
</v-window>
|
||||
<div class="tdo-box-arrow right" @click="handleClick('right')">
|
||||
<img alt="right" src="../../assets/icons/arrow-right.svg" />
|
||||
@@ -30,7 +33,8 @@
|
||||
import { computed } from "vue";
|
||||
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
import TucDetailCard from "../userAvatarOld/tuc-detail-card.vue";
|
||||
import TucDetailCard from "../userAvatarCard/tuc-detail-card.vue";
|
||||
import TucDetailOld from "../userAvatarOld/tuc-detail-old.vue";
|
||||
|
||||
interface TuaDetailOverlayProps {
|
||||
modelValue: boolean;
|
||||
@@ -78,9 +82,15 @@ function handleClick(pos: "left" | "right"): void {
|
||||
}
|
||||
|
||||
.tdo-tabs-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
border-radius: 10px;
|
||||
background: var(--box-bg-1);
|
||||
box-shadow: 0 0 10px var(--common-shadow-2);
|
||||
box-shadow: 0 0 5px var(--common-shadow-2);
|
||||
color: var(--box-text-1);
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.tdo-container {
|
||||
@@ -117,5 +127,6 @@ function handleClick(pos: "left" | "right"): void {
|
||||
|
||||
.tdo-box-container {
|
||||
position: relative;
|
||||
transition: all 1s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:title="constellation.name"
|
||||
class="duc-dolb-item"
|
||||
>
|
||||
<div v-if="!constellation.active" class="duc-dolb-lock">
|
||||
<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" />
|
||||
@@ -14,19 +14,19 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUpdated, ref } from "vue";
|
||||
import { onMounted, ref, watch, onUnmounted } from "vue";
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare.js";
|
||||
|
||||
interface DucDetailOlbProps {
|
||||
modelValue: TGApp.Sqlite.Character.RoleConstellation[];
|
||||
modelValue: TGApp.Game.Avatar.Constellation[];
|
||||
}
|
||||
|
||||
const props = defineProps<DucDetailOlbProps>();
|
||||
const constellations = ref<TGApp.Sqlite.Character.RoleConstellation[]>([]);
|
||||
const constellations = ref<TGApp.Game.Avatar.Constellation[]>([]);
|
||||
|
||||
async function loadData() {
|
||||
const tempConstellations = props.modelValue;
|
||||
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);
|
||||
@@ -37,9 +37,23 @@ async function loadData() {
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
|
||||
onUpdated(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>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="ddo-lt-box">
|
||||
<div class="ddo-ltb-icon" :title="getTitle">
|
||||
<div class="ddo-ltb-icon" :title="props.data.name">
|
||||
<TItemBox :model-value="boxData" />
|
||||
</div>
|
||||
<div class="ddo-ltb-info">
|
||||
@@ -13,67 +13,58 @@
|
||||
<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.Sqlite.Character.UserRole;
|
||||
data: TGApp.Game.Avatar.Avatar;
|
||||
mode: "avatar";
|
||||
}
|
||||
| {
|
||||
data: TGApp.Sqlite.Character.RoleWeapon;
|
||||
data: TGApp.Game.Avatar.WeaponDetail;
|
||||
mode: "weapon";
|
||||
};
|
||||
|
||||
const props = defineProps<DucDetailOltProps>();
|
||||
const getTitle = computed(() => {
|
||||
if (props.mode === "avatar") {
|
||||
return `${props.data.name}`;
|
||||
} else {
|
||||
const descriptionList = props.data.description.split("");
|
||||
return descriptionList.reduce((prev: string, cur: string, index: number) => {
|
||||
if (index % 10 === 0) {
|
||||
return `${prev}\n${cur}`;
|
||||
} else {
|
||||
return `${prev}${cur}`;
|
||||
}
|
||||
}, "");
|
||||
}
|
||||
});
|
||||
const boxData = computed<TItemBoxData>(() => {
|
||||
if (props.mode === "avatar") {
|
||||
const avatar = <TGApp.Game.Avatar.Avatar>props.data;
|
||||
return {
|
||||
bg: `/icon/bg/${props.data.star}-Star.webp`,
|
||||
icon: `/WIKI/character/${props.data.cid}.webp`,
|
||||
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/${props.data.element}.webp`,
|
||||
lt: `/icon/element/${getZhElement(avatar.element)}元素.webp`,
|
||||
ltSize: "30px",
|
||||
};
|
||||
} else {
|
||||
const weapon = <TGApp.Game.Avatar.WeaponDetail>props.data;
|
||||
return {
|
||||
bg: `/icon/bg/${props.data.star}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${props.data.id}.webp`,
|
||||
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/${props.data.type}.webp`,
|
||||
lt: `/icon/weapon/${weapon.type_name}.webp`,
|
||||
ltSize: "30px",
|
||||
};
|
||||
}
|
||||
});
|
||||
const info = computed(() => {
|
||||
if (props.mode === "avatar") {
|
||||
return `好感 ${props.data.fetter}`;
|
||||
const avatar = <TGApp.Game.Avatar.Avatar>props.data;
|
||||
return `好感 ${avatar.fetter}`;
|
||||
} else {
|
||||
return `精炼 ${props.data.affix}`;
|
||||
const weapon = <TGApp.Game.Avatar.WeaponDetail>props.data;
|
||||
return `精炼 ${weapon.affix_level}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div class="duc-dort-box">
|
||||
<div :title="talent.name" v-for="talent in talents" :key="talent.pos" class="duc-dort-item">
|
||||
<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>
|
||||
@@ -8,19 +13,19 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUpdated, ref } from "vue";
|
||||
import { onMounted, watch, ref, onUnmounted } from "vue";
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare.js";
|
||||
|
||||
interface DucDetailOrtProps {
|
||||
modelValue: TGApp.Sqlite.Character.RoleTalent[];
|
||||
modelValue: TGApp.Game.Avatar.Skill[];
|
||||
}
|
||||
|
||||
const props = defineProps<DucDetailOrtProps>();
|
||||
const talents = ref<TGApp.Sqlite.Character.RoleTalent[]>([]);
|
||||
const talents = ref<TGApp.Game.Avatar.Skill[]>([]);
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const tempTalent = props.modelValue;
|
||||
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);
|
||||
@@ -31,9 +36,23 @@ async function loadData(): Promise<void> {
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
|
||||
onUpdated(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>
|
||||
@@ -4,7 +4,7 @@
|
||||
<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.star}-Star.webp`" alt="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" />
|
||||
@@ -15,12 +15,23 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
interface ducDetailRelicProps {
|
||||
modelValue: TGApp.Sqlite.Character.RoleReliquary | false;
|
||||
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 {
|
||||
@@ -72,8 +83,9 @@ const props = defineProps<ducDetailRelicProps>();
|
||||
height: 24px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid var(--tgc-od-red);
|
||||
border-radius: 50%;
|
||||
background: var(--tgc-yellow-3);
|
||||
background: v-bind(relicBg);
|
||||
color: var(--tgc-white-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
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>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="tuc-do-box">
|
||||
<img :src="bg" alt="role" class="tuc-do-bg" />
|
||||
<div v-if="props.dataVal.costumes.length > 0" class="tuc-do-costume">
|
||||
<div v-if="props.modelValue.costumes.length > 0" class="tuc-do-costume">
|
||||
<v-switch v-model="showCostumeSwitch" color="#fb7299" @click="switchBg">
|
||||
<template #label>
|
||||
<v-icon>mdi-tshirt-crew-outline</v-icon>
|
||||
@@ -9,7 +9,7 @@
|
||||
</v-switch>
|
||||
</div>
|
||||
<div v-if="showCostumeSwitch" class="tuc-do-costume-name">
|
||||
{{ props.dataVal.costumes[0].name }}
|
||||
{{ props.modelValue.costumes[0].name }}
|
||||
</div>
|
||||
<div class="tuc-do-show">
|
||||
<div class="tuc-do-main">
|
||||
@@ -17,7 +17,7 @@
|
||||
<div
|
||||
class="tuc-dol-item"
|
||||
:style="`opacity: ${selected.pos === 0 ? '1' : '0.5'}`"
|
||||
@click="showDetail(props.dataVal.weapon, '武器', 0)"
|
||||
@click="showDetail(props.modelValue.weapon, '武器', 0)"
|
||||
>
|
||||
<TucDetailItemBox :model-value="weaponBox" />
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="tuc-do-right">
|
||||
<div class="tuc-dor-box">
|
||||
<TucDetailConstellation
|
||||
v-for="item in props.dataVal.constellations"
|
||||
v-for="item in props.modelValue.constellations"
|
||||
:key="item.pos"
|
||||
class="tuc-dor-item"
|
||||
:model-value="item"
|
||||
@@ -52,7 +52,10 @@
|
||||
</div>
|
||||
<!-- 底部说明 -->
|
||||
<div class="tuc-do-bottom">
|
||||
<TucDetailDescWeapon v-if="selected.type === '武器'" :model-value="props.dataVal.weapon" />
|
||||
<TucDetailDescWeapon
|
||||
v-if="selected.type === '武器'"
|
||||
:model-value="props.modelValue.weapon"
|
||||
/>
|
||||
<TucDetailDescConstellation
|
||||
v-if="selected.type === '命座' && selectConstellation"
|
||||
:model-value="selectConstellation"
|
||||
@@ -77,7 +80,7 @@ import TucDetailItemBox from "./tuc-detail-itembox.vue";
|
||||
import TucDetailRelic from "./tuc-detail-relic.vue";
|
||||
|
||||
interface ToUcDetailProps {
|
||||
dataVal: TGApp.Sqlite.Character.UserRole;
|
||||
modelValue: TGApp.Sqlite.Character.UserRole;
|
||||
}
|
||||
|
||||
interface ToUcDetailSelect {
|
||||
@@ -91,15 +94,15 @@ type RelicList = fixedLenArray<TGApp.Game.Avatar.Relic | false, 5>;
|
||||
const props = defineProps<ToUcDetailProps>();
|
||||
const relicList = computed<RelicList>(() => {
|
||||
return [
|
||||
props.dataVal.relics.find((item) => item.pos === 1) || false,
|
||||
props.dataVal.relics.find((item) => item.pos === 2) || false,
|
||||
props.dataVal.relics.find((item) => item.pos === 3) || false,
|
||||
props.dataVal.relics.find((item) => item.pos === 4) || false,
|
||||
props.dataVal.relics.find((item) => item.pos === 5) || false,
|
||||
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 weaponBox = computed(() => {
|
||||
const weapon = props.dataVal.weapon;
|
||||
const weapon = props.modelValue.weapon;
|
||||
return {
|
||||
icon: `/WIKI/weapon/${weapon.id}.webp`,
|
||||
bg: `/icon/bg/${weapon.rarity}-Star.webp`,
|
||||
@@ -110,7 +113,9 @@ const selectConstellation = ref<TGApp.Game.Avatar.Constellation>();
|
||||
const selectRelic = ref<TGApp.Game.Avatar.Relic>();
|
||||
const selected = ref<ToUcDetailSelect>({ type: "武器", pos: 0 });
|
||||
const bg = computed<string>(() => {
|
||||
return showCostumeSwitch.value ? props.dataVal.costumes[0].icon : props.dataVal.avatar.image;
|
||||
return showCostumeSwitch.value
|
||||
? props.modelValue.costumes[0].icon
|
||||
: props.modelValue.avatar.image;
|
||||
});
|
||||
const bgTransY = computed<string>(() => {
|
||||
return showCostumeSwitch.value ? "0" : "10px";
|
||||
@@ -131,7 +136,7 @@ onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
watch(
|
||||
() => props.dataVal,
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
loadData();
|
||||
},
|
||||
@@ -113,7 +113,7 @@ const showOverlay = ref<boolean>(false);
|
||||
const selectIndex = ref<number>(0);
|
||||
|
||||
const showSelect = ref<boolean>(false);
|
||||
const showMode = ref<"classic" | "card" | "dev">("classic");
|
||||
const showMode = ref<"classic" | "card" | "dev">("card");
|
||||
const resetSelect = ref<boolean>(false);
|
||||
const modeList = [
|
||||
{ label: "经典视图", value: "classic" },
|
||||
|
||||
@@ -61,10 +61,27 @@ async function saveAvatars(uid: string, data: TGApp.Game.Avatar.DetailList[]): P
|
||||
await db.execute(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取角色名片
|
||||
* @since Beta v0.5.3
|
||||
* @param {number} id 角色 id
|
||||
* @returns {Promise<string|false>}
|
||||
*/
|
||||
async function getAvatarCard(id: number): Promise<string | false> {
|
||||
const db = await TGSqlite.getDB();
|
||||
type resType = Array<{ card: string }>;
|
||||
const res = await db.select<resType>("SELECT nameCard as card FROM AppCharacters WHERE id = ?;", [
|
||||
id,
|
||||
]);
|
||||
if (res.length === 0) return false;
|
||||
return res[0].card;
|
||||
}
|
||||
|
||||
const TSUserAvatar = {
|
||||
getAllAvatarId,
|
||||
getAvatars,
|
||||
saveAvatars,
|
||||
getAvatarCard,
|
||||
};
|
||||
|
||||
export default TSUserAvatar;
|
||||
|
||||
Reference in New Issue
Block a user