我的角色页面渲染衣装数据

close #190
This commit is contained in:
BTMuli
2025-12-31 01:38:41 +08:00
parent 92e5aabbb0
commit 5030af2366
6 changed files with 140 additions and 75 deletions

View File

@@ -34,20 +34,20 @@
</div>
</div>
<div class="tua-abl-bottom">
<div :title="`好感度:${props.modelValue.avatar.fetter}`" class="tua-abl-fetter">
<div :title="`好感度:${props.role.avatar.fetter}`" class="tua-abl-fetter">
<img alt="fetter" src="/icon/material/105.webp" />
<span>{{ props.modelValue.avatar.fetter }}</span>
<span>{{ props.role.avatar.fetter }}</span>
</div>
<div class="tua-abl-costume">
<span
v-if="props.modelValue.costumes.length > 0"
:title="`衣装: ${props.modelValue.costumes.map((i) => i.name).join(', ')}`"
v-if="props.role.costumes.length > 0"
:title="`衣装: ${props.role.costumes.map((i) => i.name).join(', ')}`"
>
<v-icon size="small">mdi-tshirt-crew</v-icon>
</span>
</div>
<div class="tua-abl-level">
<span>Lv.{{ props.modelValue.avatar.level }}</span>
<span>Lv.{{ props.role.avatar.level }}</span>
</div>
</div>
</div>
@@ -62,23 +62,30 @@ import { computed } from "vue";
import TuaRelicBox from "./tua-relic-box.vue";
import { AppCharacterData } from "@/data/index.js";
type fixedLenArr<T, N extends number> = [T, ...Array<T>] & { length: N };
type AvatarRelics = fixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
type TuaAvatarBoxProps = { modelValue: TGApp.Sqlite.Character.TableTrans };
type TuaAvatarBoxProps = { role: TGApp.Sqlite.Character.TableTrans };
const props = defineProps<TuaAvatarBoxProps>();
const userStore = useUserStore();
const avatarIcon = computed<string>(() => {
const costume = getCostume();
if (costume) return `/WIKI/costume/${costume.id}.webp`;
return `/WIKI/character/${props.role.avatar.id}.webp`;
});
const avatarBox = computed<TItemBoxData>(() => ({
size: "100px",
height: "100px",
bg: `/icon/bg/${props.modelValue.avatar.rarity}-Star.webp`,
icon: `/WIKI/character/${props.modelValue.avatar.id}.webp`,
lt: `/icon/element/${getZhElement(props.modelValue.avatar.element)}元素.webp`,
bg: `/icon/bg/${props.role.avatar.rarity}-Star.webp`,
icon: avatarIcon.value,
lt: `/icon/element/${getZhElement(props.role.avatar.element)}元素.webp`,
ltSize: "20px",
rt: props.modelValue.avatar.actived_constellation_num.toString() || "0",
rt: props.role.avatar.actived_constellation_num.toString() || "0",
rtSize: "20px",
innerText: props.modelValue.avatar.name,
innerText: props.role.avatar.name,
innerHeight: 30,
display: "inner",
clickable: true,
@@ -86,19 +93,19 @@ const avatarBox = computed<TItemBoxData>(() => ({
const weaponBox = computed<TItemBoxData>(() => ({
size: "65px",
height: "65px",
bg: `/icon/bg/${props.modelValue.weapon.rarity}-Star.webp`,
icon: `/WIKI/weapon/${props.modelValue.weapon.id}.webp`,
lt: `/icon/weapon/${props.modelValue.weapon.type_name}.webp`,
bg: `/icon/bg/${props.role.weapon.rarity}-Star.webp`,
icon: `/WIKI/weapon/${props.role.weapon.id}.webp`,
lt: `/icon/weapon/${props.role.weapon.type_name}.webp`,
ltSize: "20px",
rt: props.modelValue.weapon.affix_level.toString(),
rt: props.role.weapon.affix_level.toString(),
rtSize: "20px",
innerText: props.modelValue.weapon.name,
innerText: props.role.weapon.name,
innerHeight: 20,
display: "inner",
clickable: true,
}));
const relicsBox = computed<AvatarRelics>(() => {
const relics = props.modelValue.relics;
const relics = props.role.relics;
return [
relics.find((i) => i.pos === 1) || false,
relics.find((i) => i.pos === 2) || false,
@@ -109,19 +116,31 @@ const relicsBox = computed<AvatarRelics>(() => {
});
const isFetterMax = computed<boolean>(() => {
const skipList = [10000005, 10000007, 10000117, 10000118];
if (skipList.includes(props.modelValue.avatar.id)) return true;
return props.modelValue.avatar.fetter === 10;
if (skipList.includes(props.role.avatar.id)) return true;
return props.role.avatar.fetter === 10;
});
const skills = computed<Array<TGApp.Game.Avatar.Skill>>(() =>
props.modelValue.skills.filter((skill) => skill.skill_type === 1),
props.role.skills.filter((skill) => skill.skill_type === 1),
);
const nameCard = computed<string>(() => {
const cardFind = TSUserAvatar.getAvatarCard(props.modelValue.avatar.id);
const cardFind = TSUserAvatar.getAvatarCard(props.role.avatar.id);
return `/WIKI/nameCard/profile/${cardFind}.webp`;
});
function getCostume(): TGApp.App.Character.Costume | false {
if (props.role.costumes.length === 0) return false;
const findC = AppCharacterData.find((i) => i.id === props.role.cid);
if (!findC) return false;
let res: TGApp.App.Character.Costume | false = false;
for (const costume of props.role.costumes) {
const findCostume = findC.costumes.find((i) => i.id === costume.id);
if (findCostume !== undefined && !findCostume.isDefault) return findCostume;
}
return res;
}
function getWeaponTitle(): string {
const weapon = props.modelValue.weapon;
const weapon = props.role.weapon;
const title: Array<string> = [];
title.push(`${weapon.type_name} - ${weapon.name}`);
title.push(`${weapon.rarity}星 精炼${weapon.affix_level} Lv.${weapon.level}`);

View File

@@ -1,34 +1,35 @@
<!-- 卡片视图详细 -->
<template>
<div class="tua-dc-container">
<div class="tua-dc-avatar">
<TMiImg :src="props.modelValue.avatar.image" :ori="true" alt="avatar" />
<TMiImg :ori="true" :src="fullIcon" alt="avatar" />
</div>
<v-btn
class="tua-dc-share"
prepend-icon="mdi-share-variant"
@click="share"
variant="outlined"
:loading="loading"
class="tua-dc-share"
data-html2canvas-ignore
prepend-icon="mdi-share-variant"
size="small"
variant="outlined"
@click="share"
>
分享
</v-btn>
<!-- 右上整体属性&角色-->
<div class="tua-dc-rt">
<div class="tua-dcr-avatar">
<span>{{ props.modelValue.avatar.name }}</span>
<span>Lv.{{ props.modelValue.avatar.level }}</span>
<span>好感{{ props.modelValue.avatar.fetter }}</span>
<span>{{ props.avatar.avatar.name }}</span>
<span>Lv.{{ props.avatar.avatar.level }}</span>
<span>好感{{ props.avatar.avatar.fetter }}</span>
<v-icon
:title="`解锁衣装:${props.modelValue.costumes.map((i) => i.name).join(',')}`"
v-if="props.modelValue.costumes.length !== 0"
v-if="props.avatar.costumes.length !== 0"
:title="`解锁衣装:${props.avatar.costumes.map((i) => i.name).join(',')}`"
size="14"
>
mdi-tshirt-crew
</v-icon>
</div>
<div v-for="(prop, index) in props.modelValue.propSelected" :key="index">
<div v-for="(prop, index) in props.avatar.propSelected" :key="index">
<div v-if="propMain[index] !== false" class="tua-dc-prop">
<TuaDcProp :model-value="prop" :prop="propMain[index]" />
</div>
@@ -37,40 +38,40 @@
<!-- 右侧武器跟圣遗物具体属性 -->
<div class="tua-dc-detail">
<TuaDcWeapon
:model-value="props.modelValue.weapon"
:uid="props.modelValue.uid"
:updated="props.modelValue.updated"
:model-value="props.avatar.weapon"
:uid="props.avatar.uid"
:updated="props.avatar.updated"
/>
<TuaDcRelic
:model-value="relicList[0]"
:recommend="props.avatar.propRecommend.recommend_properties"
pos="1"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
<TuaDcRelic
:model-value="relicList[1]"
:recommend="props.avatar.propRecommend.recommend_properties"
pos="2"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
<TuaDcRelic
:model-value="relicList[2]"
:recommend="props.avatar.propRecommend.recommend_properties"
pos="3"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
<TuaDcRelic
:model-value="relicList[3]"
:recommend="props.avatar.propRecommend.recommend_properties"
pos="4"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
<TuaDcRelic
:model-value="relicList[4]"
:recommend="props.avatar.propRecommend.recommend_properties"
pos="5"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
</div>
<!-- 左下命座跟天赋 -->
<div class="tua-dc-lb">
<TuaDcTalents :model-value="props.modelValue.skills" />
<TuaDcConstellations :model-value="props.modelValue.constellations" />
<TuaDcTalents :model-value="props.avatar.skills" />
<TuaDcConstellations :model-value="props.avatar.constellations" />
</div>
</div>
</template>
@@ -88,28 +89,35 @@ import TuaDcRelic from "./tua-dc-relic.vue";
import TuaDcTalents from "./tua-dc-talents.vue";
import TuaDcWeapon from "./tua-dc-weapon.vue";
import { AppCharacterData } from "@/data/index.js";
type fixedLenArr<T, N extends number> = [T, ...Array<T>] & { length: N };
type RelicList = fixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
type TuaDetailCardProps = { modelValue: TGApp.Sqlite.Character.TableTrans };
type TuaDetailCardProps = { avatar: TGApp.Sqlite.Character.TableTrans };
const props = defineProps<TuaDetailCardProps>();
const userStore = useUserStore();
const fullIcon = computed<string>(() => {
const costume = getCostume();
if (costume) return `/WIKI/costume/${costume.id}_full.webp`;
return props.avatar.avatar.image;
});
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,
props.avatar.relics.find((item) => item.pos === 1) || false,
props.avatar.relics.find((item) => item.pos === 2) || false,
props.avatar.relics.find((item) => item.pos === 3) || false,
props.avatar.relics.find((item) => item.pos === 4) || false,
props.avatar.relics.find((item) => item.pos === 5) || false,
];
});
const propMain = computed<Array<TGApp.Game.Avatar.PropMapItem | false>>(() =>
props.modelValue.propSelected.map((item) => userStore.getProp(item.property_type)),
props.avatar.propSelected.map((item) => userStore.getProp(item.property_type)),
);
const bg = computed<string>(() => {
const card = TSUserAvatar.getAvatarCard(props.modelValue.cid);
const card = TSUserAvatar.getAvatarCard(props.avatar.cid);
return `url("/WIKI/nameCard/profile/${card}.webp")`;
});
const loading = ref<boolean>(false);
@@ -120,11 +128,23 @@ async function share(): Promise<void> {
showSnackbar.error("分享失败,未找到分享内容");
return;
}
const fileName = `【角色详情】${props.modelValue.avatar.name}`;
const fileName = `【角色详情】${props.avatar.avatar.name}`;
loading.value = true;
await generateShareImg(fileName, shareBox);
loading.value = false;
}
function getCostume(): TGApp.App.Character.Costume | false {
if (props.avatar.costumes.length === 0) return false;
const findC = AppCharacterData.find((i) => i.id === props.avatar.cid);
if (!findC) return false;
let res: TGApp.App.Character.Costume | false = false;
for (const costume of props.avatar.costumes) {
const findCostume = findC.costumes.find((i) => i.id === costume.id);
if (findCostume !== undefined && !findCostume.isDefault) return findCostume;
}
return res;
}
</script>
<style lang="css" scoped>
.tua-dc-container {

View File

@@ -33,10 +33,10 @@
<TucDetailOld :model-value="avatar" />
</v-window-item>
<v-window-item value="card">
<TucDetailCard :model-value="avatar" />
<TucDetailCard :avatar />
</v-window-item>
<v-window-item value="dev">
<TuaDetailCard :model-value="avatar" />
<TuaDetailCard :avatar />
</v-window-item>
</v-window>
<div class="tdo-box-arrow" @click="handleClick('right')">

View File

@@ -1,6 +1,6 @@
<template>
<div class="ddo-lt-box">
<div class="ddo-ltb-icon" :title="props.data.name">
<div :title="title" class="ddo-ltb-icon">
<TItemBox :model-value="boxData" />
</div>
<div class="ddo-ltb-info">
@@ -18,6 +18,7 @@ import { computed } from "vue";
type DucDetailOltProps =
| {
data: TGApp.Game.Avatar.Avatar;
costume: TGApp.App.Character.Costume | false;
mode: "character";
}
| {
@@ -26,9 +27,18 @@ type DucDetailOltProps =
};
const props = defineProps<DucDetailOltProps>();
const icon = computed(() => {
if (props.mode === "weapon" || !props.costume) return `/WIKI/${props.mode}/${props.data.id}.webp`;
return `/WIKI/costume/${props.costume.id}.webp`;
});
const title = computed<string>(() => {
if (props.mode === "weapon" || !props.costume) return props.data.name;
return `${props.data.name}-${props.costume.name}`;
});
const boxData = computed<TItemBoxData>(() => ({
bg: `/icon/bg/${props.data.rarity}-Star.webp`,
icon: `/WIKI/${props.mode}/${props.data.id}.webp`,
icon: icon.value,
size: "100px",
height: "100px",
display: "inner",

View File

@@ -1,11 +1,11 @@
<template>
<div class="duc-do-container">
<img :src="nameCard" class="duc-doc-bg" v-if="nameCard !== false" alt="bg" />
<img v-if="nameCard !== false" :src="nameCard" alt="bg" class="duc-doc-bg" />
<div class="duc-doc-bgc" />
<!-- 左上角色跟武器 -->
<div class="duc-doc-lt">
<DucDetailOlt :data="props.modelValue.avatar" mode="character" />
<DucDetailOlt :data="props.modelValue.weapon" mode="weapon" />
<DucDetailOlt :costume :data="props.avatar.avatar" mode="character" />
<DucDetailOlt :data="props.avatar.weapon" mode="weapon" />
<div class="duc-relic">
<DucDetailRelic
v-for="(relic, index) in relicList"
@@ -16,26 +16,26 @@
</div>
</div>
<v-btn
class="duc-doc-btn"
@click="share"
variant="outlined"
:loading="loading"
class="duc-doc-btn"
data-html2canvas-ignore
variant="outlined"
@click="share"
>
<v-icon>mdi-share-variant</v-icon>
<span>分享</span>
</v-btn>
<!-- 右侧天赋 -->
<div class="duc-doc-rt">
<DucDetailOrt :model-value="props.modelValue.skills" />
<DucDetailOrt :model-value="props.avatar.skills" />
</div>
<!-- 左下命座 -->
<div class="duc-doc-lb">
<DucDetailOlb :model-value="props.modelValue.constellations" />
<DucDetailOlb :model-value="props.avatar.constellations" />
</div>
<!-- 底部水印信息 -->
<div class="duc-doc-bt">
UID: {{ props.modelValue.uid }} {{ props.modelValue.updated }} | TeyvatGuide v{{ version }}
UID: {{ props.avatar.uid }} {{ props.avatar.updated }} | TeyvatGuide v{{ version }}
</div>
</div>
</template>
@@ -51,7 +51,9 @@ import DucDetailOlt from "./duc-detail-olt.vue";
import DucDetailOrt from "./duc-detail-ort.vue";
import DucDetailRelic from "./duc-detail-relic.vue";
type DucDetailOverlayProps = { modelValue: TGApp.Sqlite.Character.TableTrans };
import { AppCharacterData } from "@/data/index.js";
type DucDetailOverlayProps = { avatar: TGApp.Sqlite.Character.TableTrans };
type fixedLenArr<T, N extends number> = [T, ...Array<T>] & { length: N };
type RelicList = fixedLenArr<TGApp.Game.Avatar.Relic | false, 5>;
@@ -61,25 +63,27 @@ 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,
props.avatar.relics.find((item) => item.pos === 1) || false,
props.avatar.relics.find((item) => item.pos === 2) || false,
props.avatar.relics.find((item) => item.pos === 3) || false,
props.avatar.relics.find((item) => item.pos === 4) || false,
props.avatar.relics.find((item) => item.pos === 5) || false,
];
});
const nameCard = ref<string | false>(false);
const costume = ref<TGApp.App.Character.Costume | false>(false);
onMounted(async () => {
version.value = await app.getVersion();
loadData();
});
watch(() => props.modelValue, loadData);
watch(() => props.avatar, loadData);
function loadData(): void {
const card = TSUserAvatar.getAvatarCard(props.modelValue.cid);
const card = TSUserAvatar.getAvatarCard(props.avatar.cid);
nameCard.value = `/WIKI/nameCard/profile/${card}.webp`;
costume.value = getCostume();
}
async function share(): Promise<void> {
@@ -88,11 +92,23 @@ async function share(): Promise<void> {
showSnackbar.error("未找到角色详情");
return;
}
const fileName = `【角色详情】-${props.modelValue.avatar.name}`;
const fileName = `【角色详情】-${props.avatar.avatar.name}`;
loading.value = true;
await generateShareImg(fileName, detailBox);
loading.value = false;
}
function getCostume(): TGApp.App.Character.Costume | false {
if (props.avatar.costumes.length === 0) return false;
const findC = AppCharacterData.find((i) => i.id === props.avatar.cid);
if (!findC) return false;
let res: TGApp.App.Character.Costume | false = false;
for (const costume of props.avatar.costumes) {
const findCostume = findC.costumes.find((i) => i.id === costume.id);
if (findCostume !== undefined && !findCostume.isDefault) return findCostume;
}
return res;
}
</script>
<style lang="css" scoped>
.duc-do-container {

View File

@@ -128,7 +128,7 @@
<TuaAvatarBox
v-for="(role, index) in selectedList"
:key="index"
:model-value="role"
:role
@click="selectRole(role)"
/>
</div>