feat:新增用户悬浮卡片

This commit is contained in:
Xwen
2024-01-07 20:51:20 +08:00
parent dba380ff24
commit 5bf95f8e75
6 changed files with 216 additions and 74 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,30 +1,32 @@
<template> <template>
<div class="avatar"> <div class="avatar">
<div class="avatar-left"> <div class="avatar-left">
<el-avatar :size="props.size" :src="iconUrl" />
<div v-if="props.isSelf" class="avatar-center"> <div v-if="props.isSelf" class="avatar-center">
<el-avatar :size="props.size" :src="iconUrl" />
<div class="nick">{{ userInfo.nick }}</div> <div class="nick">{{ userInfo.nick }}</div>
</div> </div>
<div v-if="!props.isSelf" class="avatar-card">
<div v-if="!props.isSelf"> <!-- 悬浮卡片 -->
<div class="nick" :class="{ mt_1: props.time != 'undefined' }"> <userInfo-card :userInfo="userInfo" :iconUrl="iconUrl" />
<div class="text">{{ userInfo.nick }}</div> <div class="content">
<div class="level"> <div class="nick" :class="{ mt_1: props.time != 'undefined' }">
<el-tag round effect="light" type="success" v-if="userInfo.level">{{ <div class="text">{{ userInfo.nick }}</div>
userInfo.level <div class="level">
}}</el-tag> <el-tag round effect="light" type="success" v-if="userInfo.level"
>等级{{ userInfo.level }}</el-tag
>
</div>
<div class="status" v-if="userInfo.userLimit">
<el-tag round effect="light" :type="userInfo.userLimit.type">
{{ userInfo.userLimit.label }}
</el-tag>
</div>
</div> </div>
<div class="status" v-if="userInfo.userLimit"> <div class="remarks" v-if="props.time">{{ props.time }}</div>
<el-tag round effect="light" :type="userInfo.userLimit.type"> <div class="remarks">
{{ userInfo.userLimit.label }} <slot name="bottom" />
</el-tag>
</div> </div>
</div> </div>
<div class="remarks" v-if="props.time">{{ props.time }}</div>
<div class="remarks">
<slot name="bottom" />
</div>
</div> </div>
<div class="info" v-if="!props.isSelf"> <div class="info" v-if="!props.isSelf">
<el-tag class="ml-2" type="warning">V8</el-tag> <el-tag class="ml-2" type="warning">V8</el-tag>
@@ -44,6 +46,11 @@
import useUserStore from "@/stores/user"; import useUserStore from "@/stores/user";
import { reactive, watch, onMounted, computed, ref } from "vue"; import { reactive, watch, onMounted, computed, ref } from "vue";
import { upload } from "@/apis/fileApi"; import { upload } from "@/apis/fileApi";
import useAuths from "@/hooks/useAuths";
import UserInfoCard from "./UserInfoCard/index.vue";
const { getToken } = useAuths();
const isHasToken = getToken();
//userInfo //userInfo
//{icon,name,role,id},根据判断userInfo是否等于未定义来觉得是当前登录用户信息还是其他人信息 //{icon,name,role,id},根据判断userInfo是否等于未定义来觉得是当前登录用户信息还是其他人信息
@@ -99,7 +106,7 @@ const Init = () => {
userInfo.nick = props.userInfo.nick; userInfo.nick = props.userInfo.nick;
userInfo.role = props.userInfo.role; userInfo.role = props.userInfo.role;
userInfo.id = props.userInfo.id; userInfo.id = props.userInfo.id;
userInfo.level = "等级" + props.userInfo.level; userInfo.level = props.userInfo.level;
userInfo.userLimit = getStatusInfo(props.userInfo.userLimit); userInfo.userLimit = getStatusInfo(props.userInfo.userLimit);
iconUrl.value = iconUrlHandler(userInfo.icon); iconUrl.value = iconUrlHandler(userInfo.icon);
} }
@@ -136,21 +143,12 @@ const getStatusInfo = (type) => {
return statusTypeList.filter((item) => item.value === type)[0]; return statusTypeList.filter((item) => item.value === type)[0];
}; };
</script> </script>
<style scoped>
<style lang="scss" scoped>
.mt_1 { .mt_1 {
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.nick {
display: flex;
align-items: center;
justify-content: space-between;
font-weight: bold;
> div {
margin-right: 10px;
}
}
.info { .info {
flex: 1; flex: 1;
margin-top: 0.6rem; margin-top: 0.6rem;
@@ -171,12 +169,27 @@ const getStatusInfo = (type) => {
justify-content: space-between; justify-content: space-between;
} }
.avatar-left { .avatar-left,
.avatar-card {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.content {
margin-left: 10px;
}
.nick {
display: flex;
align-items: center;
justify-content: space-between;
font-weight: bold;
> div {
margin-right: 10px;
}
}
} }
.avatar-center { .avatar-center {
display: flex;
flex: 2; flex: 2;
} }
.el-avatar { .el-avatar {

View File

@@ -1,52 +1,178 @@
<template> <template>
<div v-if="modelValue" class="userInfo-card" ref="cardRef"> <!-- 悬浮卡片 -->
<!-- 个人信息内容 --> <el-popover
<div class="info"> placement="right"
<h2>温海靖</h2> :width="300"
<p>XXX</p> :show-arrow="false"
<!-- 其他个人信息... --> trigger="hover"
popper-class="userCard"
v-if="!props.isSelf"
>
<template #reference>
<el-avatar :size="props.size" :src="iconUrl" />
</template>
<div class="top">
<div class="left">
<div class="image">
<img :src="iconUrl" alt="" />
</div>
</div>
<div class="right">
<div class="userInfo">
<div class="name">{{ userInfo.nick }}</div>
<div class="level">
<el-tag effect="light" type="success" v-if="userInfo.level"
>等级{{ userInfo.level }}</el-tag
>
</div>
</div>
<div class="website">
<div class="icon">
<img src="@/assets/box/github-icon.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/box/website-icon.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/box/gitee-icon.png" alt="" />
</div>
</div>
<div class="btn">
<el-button type="primary" icon="Plus">关注</el-button>
</div>
</div>
</div> </div>
</div> <div class="line"></div>
<div class="bottom">
<div class="header">
<div class="score">积分3005</div>
<div class="status">
<span>状态</span>
<span> {{ getStatusInfo(userInfo.userLimit.label) }}</span>
</div>
</div>
<div class="hobby">
<span>关注</span>
<el-tag type="info">C#</el-tag>
<el-tag type="info">前端</el-tag>
<el-tag type="info">Python</el-tag>
<el-tag type="info">算法</el-tag>
</div>
</div>
</el-popover>
</template> </template>
<script setup> <script setup name="UserInfoCard">
import { ref, computed, nextTick, defineProps, defineEmits } from "vue"; import { computed, defineProps } from "vue";
const props = defineProps({ const props = defineProps({
modelValue: { // 用户信息
type: Boolean, userInfo: {
default: false, type: Object,
default: () => {},
},
// icon地址
iconUrl: {
type: String,
default: () => "",
}, },
actOnRef: {},
}); });
const emit = defineEmits("update:modelValue");
const cardRef = ref(null);
const avatarRef = computed(() => props.actOnRef);
nextTick(() => { const userInfo = computed(() => props.userInfo);
document.addEventListener("mouseup", (e) => {
// 如果点击的是按钮 则不执行下面的操作 const statusTypeList = [
if (avatarRef.value && cardRef.value) { {
if (cardRef.value.contains(e.target)) { label: "正常",
return; value: "Normal",
} type: "success",
if (!cardRef.value.contains(e.target)) { },
// 点击的区域不包含在弹窗区域之内就关闭弹窗 {
emit("update:modelValue", false); label: "危险",
} value: "Dangerous",
} type: "warning",
}); },
}); {
label: "已禁止",
value: "Ban",
type: "danger",
},
];
const getStatusInfo = (type) => {
return statusTypeList.filter((item) => item.value === type)[0];
};
</script> </script>
<style scoped> <style scoped lang="scss">
.userInfo-card { .userCard {
width: 100px; .top {
height: 100px; display: flex;
position: absolute; width: 100%;
top: 0; height: 100px;
left: 0; .left {
background-color: pink; width: 80px;
/* 卡片样式... */ .image {
width: 80px;
height: 80px;
img {
width: 100%;
height: 100%;
}
}
}
.right {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
margin-left: 20px;
.userInfo {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
.level {
margin: 0 10px;
}
}
.website {
display: flex;
.icon {
cursor: pointer;
width: 20px;
height: 20px;
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
.line {
margin: 20px 0;
border-top: 1px solid #eee;
}
.bottom {
display: flex;
flex-direction: column;
justify-content: space-around;
width: 100%;
height: 100px;
.header {
display: flex;
align-items: center;
.status {
margin-left: 50px;
}
}
.hobby {
display: flex;
align-items: center;
> .el-tag {
margin-right: 10px;
}
}
}
} }
</style> </style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="point-box"> <div class="point-box">
<div class="left"> <div class="left">
<div class="icon"><img :src="userImageSrc" alt="" /></div> <UserInfoCard :userInfo="pointsData" :iconUrl="userImageSrc" />
</div> </div>
<div class="center"> <div class="center">
<div class="top"> <div class="top">
@@ -36,6 +36,7 @@
<script setup name="PointsRanking"> <script setup name="PointsRanking">
import { defineProps, computed } from "vue"; import { defineProps, computed } from "vue";
import UserInfoCard from "@/components/UserInfoCard/index.vue";
const props = defineProps({ const props = defineProps({
pointsData: { pointsData: {
@@ -44,6 +45,8 @@ const props = defineProps({
}, },
}); });
const pointsData = computed(() => props.pointsData);
const statusTypeList = [ const statusTypeList = [
{ {
label: "正常", label: "正常",
@@ -66,10 +69,10 @@ const getStatusInfo = (type) => {
return statusTypeList.filter((item) => item.value === type)[0]; return statusTypeList.filter((item) => item.value === type)[0];
}; };
const userLimit = computed(() => getStatusInfo(props.pointsData.userLimit)); const userLimit = computed(() => getStatusInfo(pointsData.value.userLimit));
const userImageSrc = computed(() => { const userImageSrc = computed(() => {
if (props.pointsData.icon) { if (pointsData.value.icon) {
return import.meta.env.VITE_APP_BASEAPI + "/file/" + props.pointsData.icon; return import.meta.env.VITE_APP_BASEAPI + "/file/" + pointsData.value.icon;
} else { } else {
return "acquiesce.png"; return "acquiesce.png";
} }