mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-05-08 00:24:06 +08:00
@@ -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}`);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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')">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
<TuaAvatarBox
|
||||
v-for="(role, index) in selectedList"
|
||||
:key="index"
|
||||
:model-value="role"
|
||||
:role
|
||||
@click="selectRole(role)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user