2026-05-09 10:34:51 +08:00

950 lines
23 KiB
Vue

<script setup lang="ts">
import { XMessage, useAudio } from '@/hooks'
import Sidebar from './components/UserCenterSidebar.vue'
import { debounce } from '@/hooks'
import { userStore, globalStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { $sleep } from '@/utils'
import userApi from '@/api/system/user'
const props = defineProps<{
modelValue: boolean
}>()
const emits = defineEmits(['update:modelValue'])
const OSS_URL = import.meta.env.VITE_OSS_URL
const bg = `url(${OSS_URL}/urm/exchange_fireworks.png) 100%/cover no-repeat`
const receiveReward = `${OSS_URL}/sound/receive_reward.mp3`
const { getUserInfo } = userStore()
const { userInfo } = storeToRefs(userStore())
const { system } = globalStore()
// 侧边栏
const config = [
{ id: 0, name: '头像' },
{ id: 1, name: '昵称' },
{ id: 2, name: '性别' },
{ id: 3, name: '昵称框' },
{ id: 4, name: '头像框' },
]
const active = ref(0)
// 头像列表
const avatarList = ref<anyObj[]>([])
// 设置的各种url
const setUrl = ref<anyObj>({})
// 修改昵称
const nickname = ref('')
const errorMsg = ref('')
async function editName() {
if (!canChangeNickName.value) return XMessage.error(`钻石不足,请通过学习获取钻石哦!`, true)
await userApi.useDiamondChangeNickName({ newNickName: nickname.value })
userInfo.value.nickName = nickname.value
userInfo.value.nickNameChangeCardCount--
XMessage.success('昵称修改成功')
}
function onCheck() {
if (!canChangeNickName.value) {
XMessage.error(`钻石不足,请通过学习获取钻石哦!`, true)
}
}
// 切换回昵称页面时显示原来的昵称
function handleChangeSidebar(item: anyObj) {
showExchangeBtn.value = false
if (item.id === 0) previewConfig.avatarUrl && (showExchangeBtn.value = true)
if (item.id === 3) previewConfig.nicknameBg && (showExchangeBtn.value = true)
if (item.id === 4) previewConfig.avatarFrameUrl && (showExchangeBtn.value = true)
if (item.id === 1) {
nickname.value = userInfo.value.nickName
}
}
// 修改性别
function editSex(sex: 1 | 2) {
if (userInfo.value.sex === sex) return
userApi.updateUserSex({ sex })
userInfo.value.sex = sex
}
const debounceEditSex = debounce(editSex, 500)
// 头像框列表
const avatarFrameList = ref<anyObj[]>([])
// 昵称框列表
const nickFrameList = ref<anyObj[]>([])
const nicknameBg = computed(
() =>
previewConfig.nicknameBg ||
userInfo.value?.personInUsePersonalities.find((i: anyObj) => i.remark === 4)
?.personalInfoResourceUrl,
)
const showPreviewDia = ref(false)
const diaData = ref<anyObj>({})
const showExchange = ref(false)
const showExchangeBtn = ref(false)
// 预览修改
const _previewConfig = {
avatarUrl: '',
avatarFrameUrl: '',
nicknameBg: '',
}
const TYPE_AVATAR = 0
const TYPE_NICKNAME = 1
const TYPE_GENDER = 2
const TYPE_NICKNAME_FRAME = 3
const TYPE_AVATAR_FRAME = 4
const REMARK_AVATAR = 1
const REMARK_AVATAR_FRAME = 2
const REMARK_NICKNAME_FRAME = 4
const SELECTED = 1
const UNSELECTED = 0
const MALE = 1
const FEMALE = 2
const lastSelectData = reactive<anyObj>({})
const lastSelectType = ref<0 | 3 | 4>(0)
const previewConfig = reactive({ ..._previewConfig })
const previewConfigChange = debounce((item: anyObj, type: 0 | 3 | 4) => {
if (item.useStatus) return
lastSelectData[type] = item
lastSelectType.value = type
// 预览模式
if (type === TYPE_AVATAR) {
avatarList.value.forEach(item => (item.useStatus = UNSELECTED))
previewConfig.avatarUrl = item.personalInfoResourceUrl
} else if (type === TYPE_NICKNAME_FRAME) {
nickFrameList.value.forEach(item => (item.useStatus = UNSELECTED))
previewConfig.nicknameBg = item.personalInfoResourceUrl
} else {
avatarFrameList.value.forEach(item => (item.useStatus = UNSELECTED))
previewConfig.avatarFrameUrl = item.personalInfoResourceUrl
}
if (item.lockStatus) {
// 未解锁的头像
showExchangeBtn.value = true
} else {
// 已经解锁的头像
showExchangeBtn.value = false
changeUserConfig(item, type)
}
item.useStatus = SELECTED
}, 300)
// 修改头像/头像框/昵称框
async function changeUserConfig(item: anyObj, type: 0 | 3 | 4) {
userApi.updatePersonalConfig({ unlockId: item.unlockId }).then((_: any) => {
if (type === TYPE_AVATAR) {
setUrl.value.avatar = item.resourceUrl
} else if (type === TYPE_NICKNAME_FRAME) {
setUrl.value.nickname_frame = item.resourceUrl
} else {
setUrl.value.avatar_frame = item.resourceUrl
}
})
// updatePersonalConfig({ unlockId: item.unlockId }).then(res => {
// getUserInfo().then(res => {
// if (type === 0) {
// previewConfig.avatarUrl = ''
// } else if (type === 3) {
// previewConfig.nicknameBg = ''
// } else {
// previewConfig.avatarFrameUrl = ''
// }
// switch (item.remark) {
// case 1:
// initHead()
// break
// case 2:
// initBorder()
// break
// case 4:
// initNkBorder()
// break
// }
// })
// })
// await updatePersonalConfig({ unlockId: item.unlockId })
// await getUserInfo()
// console.log('changeUserConfig', item);
//
// if (type === 0) {
// previewConfig.avatarUrl = ''
// } else if (type === 3) {
// previewConfig.nicknameBg = ''
// } else {
// previewConfig.avatarFrameUrl = ''
// }
}
function onExchange() {
showPreviewDia.value = true
// showExchange.value = false;
diaData.value = lastSelectData[active.value]
}
async function onOk() {
// if (showExchange.value) return (showPreviewDia.value = false);
if (userInfo.value.diamond < lastSelectData[active.value].currencyNum) {
XMessage.error(`钻石不足,请通过学习获取钻石哦!`, true)
return
}
const { data: res } = await userApi.exchangeVirtualItem({ id: diaData.value.id })
// showExchange.value = true;
// 清空状态
// switch (lastSelectType.value) {
// case 0:
// previewConfig.avatarUrl = '';
// break;
// case 3:
// previewConfig.nicknameBg = '';
// break;
// case 4:
// previewConfig.avatarFrameUrl = '';
// break;
// }
lastSelectData[active.value].unlockId = res.data
lastSelectData[active.value].useStatus = UNSELECTED
changeUserConfig(lastSelectData[active.value], lastSelectType.value).then(_ => {
useAudio({
url: receiveReward,
autoplay: true,
volume: system.volume.effectsVolume / 100,
})
showExchangeBtn.value = false
switch (diaData.value.remark) {
case REMARK_AVATAR:
initAvatar()
break
case REMARK_AVATAR_FRAME:
initAvatarFrame()
break
case REMARK_NICKNAME_FRAME:
initNicknameFrame()
break
}
showPreviewDia.value = false
XMessage.success('兑换成功')
})
}
// const debounceChangeUserConfig = debounce(changeUserConfig, 300)
const canChangeNickName = computed(() => Number(userInfo.value.diamond) >= 2000)
watch(
() => props.modelValue,
async newVal => {
if (newVal) Object.assign(previewConfig, _previewConfig)
// 弹窗打开和关闭时更新userInfo
await getUserInfo()
nickname.value = userInfo.value.nickName
if (!newVal) {
avatarList.value.forEach(item => {
if (item.resourceUrl === setUrl.value.avatar) {
item.useStatus = SELECTED
} else {
item.useStatus = UNSELECTED
}
})
avatarFrameList.value.forEach(item => {
if (item.resourceUrl === setUrl.value.avatar_frame) {
item.useStatus = SELECTED
} else {
item.useStatus = UNSELECTED
}
})
nickFrameList.value.forEach(item => {
if (item.resourceUrl === setUrl.value.nickname_frame) {
item.useStatus = SELECTED
} else {
item.useStatus = UNSELECTED
}
})
}
},
)
function initAvatar() {
userApi.getUnlockInfoByRemark(REMARK_AVATAR).then(({ data: res }: any) => {
// 头像列表
if (res.code === 200) {
avatarList.value = res.data
res.data.forEach((item: any) => {
if (!item.useStatus) return
setUrl.value.avatar = item.resourceUrl
})
}
})
}
function initAvatarFrame() {
userApi.getUnlockInfoByRemark(REMARK_AVATAR_FRAME).then(({ data: res }: any) => {
// 头像框列表
if (res.code === 200) {
avatarFrameList.value = res.data
res.data.forEach((item: any) => {
if (!item.useStatus) return
setUrl.value.avatar_frame = item.resourceUrl
})
}
})
}
function initNicknameFrame() {
userApi.getUnlockInfoByRemark(REMARK_NICKNAME_FRAME).then(({ data: res }: any) => {
// 昵称框列表
if (res.code === 200) {
nickFrameList.value = res.data
res.data.forEach((item: any) => {
if (!item.useStatus) return
setUrl.value.nickname_frame = item.resourceUrl
})
}
})
}
onMounted(async () => {
// fix: 延迟加载数据,并发高安卓会卡顿
await $sleep(2000)
initAvatar()
initAvatarFrame()
initNicknameFrame()
})
// onActivated()
</script>
<template>
<XLDia
class="userInfoDia"
:modelValue="modelValue"
@update:modelValue="emits('update:modelValue', $event)"
>
<template #title>
<!-- <user-figure
class="user"
:personal-list="userInfo.personInUsePersonalities"
:sex="userInfo.sex"
:nickname="userInfo.nickName"
/> -->
<div class="user">
<UserHead
size="88"
:head="previewConfig.avatarUrl"
:border="previewConfig.avatarFrameUrl"
:personalList="userInfo.personInUsePersonalities"
/>
<Icon class="sex" :name="userInfo.sex === 1 ? 'boy_icon' : 'girl_icon'" size="48"></Icon>
<div
class="nickname"
:style="{
'background-image': `url(${nicknameBg})`,
}"
>
<LineFont class="name" :text="userInfo.nickName" size="28" border="6" />
</div>
</div>
<div class="exchange-btn">
<mj-button
v-if="
[TYPE_AVATAR, TYPE_NICKNAME_FRAME, TYPE_AVATAR_FRAME].includes(active as any) &&
showExchangeBtn
"
type="warn"
tilt
@click="onExchange"
>
兑换
</mj-button>
</div>
</template>
<div class="left-sidebar">
<Sidebar v-model="active" :config="config" @handleChange="handleChangeSidebar"></Sidebar>
</div>
<div class="left-main">
<!-- 修改头像 -->
<div v-if="active === TYPE_AVATAR" class="left-main-box avatar">
<div class="list hidden_scroll avatar-list">
<div
v-for="i in avatarList"
:key="i.id"
:class="{
'list-item': true,
pointer: true,
active: i.useStatus,
}"
@click="previewConfigChange(i, TYPE_AVATAR)"
>
<img :src="i.resourceUrl" />
<Icon v-show="i.useStatus" name="circle_check" class="icon" width="56"></Icon>
<!-- <Icon v-show="i.lockStatus" name="lock" class="lock-icon" width="48"></Icon> -->
<div v-show="i.lockStatus" class="lock-icon">
<Icon name="diamond" size="26" />
{{ i.currencyNum }}
</div>
</div>
</div>
</div>
<!-- 修改昵称 -->
<div v-else-if="active === TYPE_NICKNAME" class="nickName-box left-main-box">
<div class="input-box" @click="onCheck">
<div class="error-msg">{{ errorMsg }}</div>
<input v-model="nickname" :maxlength="8" @focus="errorMsg = ''" />
</div>
<div class="tips">
本次改名将消耗
<Icon name="diamond" size="48" />
2000
</div>
<div class="btn">
<mj-button
type="info"
:disabled="!canChangeNickName"
:width="272"
:size="32"
@click="editName()"
>
确认修改
</mj-button>
</div>
</div>
<!-- 修改性别 -->
<div v-else-if="active === TYPE_GENDER" class="sex-box left-main-box">
<div class="sex-item">
<div
:class="['sex-icon', 'male', 'pointer', userInfo.sex === MALE ? 'active' : '']"
@click="debounceEditSex(MALE)"
>
<Icon name="male" width="158"></Icon>
</div>
<span class="text">男</span>
</div>
<div class="sex-item">
<div
:class="['sex-icon', 'female', 'pointer', userInfo.sex === FEMALE ? 'active' : '']"
@click="debounceEditSex(FEMALE)"
>
<Icon name="female" width="158"></Icon>
</div>
<span class="text">女</span>
</div>
</div>
<!-- 修改昵称框 -->
<div v-else-if="active === TYPE_NICKNAME_FRAME" class="left-main-box nick_frame">
<div class="list hidden_scroll nick_frame-list">
<div
v-for="i in nickFrameList"
:key="i.id"
:class="{
'list-item': true,
pointer: true,
active: i.useStatus,
}"
@click="previewConfigChange(i, TYPE_NICKNAME_FRAME)"
>
<div class="img-box">
<img :src="i.resourceUrl" />
</div>
<Icon v-show="i.useStatus" name="circle_check" class="icon" width="56"></Icon>
<!-- <Icon v-show="i.lockStatus" name="lock" class="lock-icon" width="48"></Icon> -->
<div v-show="i.lockStatus" class="lock-icon">
<Icon name="diamond" size="26" />
{{ i.currencyNum }}
</div>
</div>
</div>
</div>
<!-- 修改头像框 -->
<div v-else-if="active === TYPE_AVATAR_FRAME" class="left-main-box avatar_frame">
<div class="list hidden_scroll avatar_frame-list">
<div
v-for="i in avatarFrameList"
:key="i.id"
:class="{
'list-item': true,
pointer: true,
active: i.useStatus,
}"
@click="previewConfigChange(i, TYPE_AVATAR_FRAME)"
>
<img :src="i.resourceUrl" />
<Icon v-show="i.useStatus" name="circle_check" class="icon" width="56"></Icon>
<!-- <Icon v-show="i.lockStatus" name="lock" class="lock-icon" width="48"></Icon> -->
<div v-show="i.lockStatus" class="lock-icon">
<Icon name="diamond" size="26" />
{{ i.currencyNum }}
</div>
</div>
</div>
</div>
</div>
</XLDia>
<mj-dialog
v-model="showPreviewDia"
:title="showExchange ? '兑换成功' : '兑换'"
:hide-footer="true"
:ok="onOk"
>
<div
:class="{
'preview-box': true,
ex: showExchange,
}"
:style="{
'--bg': bg,
}"
>
<img
:class="{
'preview-img': true,
'preview-head': diaData.remark === 1,
border: diaData.remark !== 1,
}"
:src="diaData.remark === 4 ? diaData.resourceUrl : diaData.personalInfoResourceUrl"
/>
</div>
<template #footer>
<div style="text-align: center">
<mj-button v-if="!showExchange" type="warn" tilt width="320" @click="onOk">
<Icon name="diamond" size="48" />
{{ diaData.currencyNum }}
</mj-button>
<mj-button v-else type="success" tilt @click="showPreviewDia = false">确定</mj-button>
</div>
</template>
</mj-dialog>
</template>
<style scoped lang="scss">
.userInfoDia {
:deep(.xl-dia-main) {
padding: 2.5vh 56px 2.5vh 10px;
display: flex;
}
:deep(.xl-dia-box) {
width: 1680px;
height: 73.67vh;
position: absolute;
top: 20vh;
.title {
top: -12.67vh;
.user {
height: 11vh;
display: flex;
padding: 0 36px;
align-items: center;
gap: $space-4;
.x-user-head {
margin-top: 1.67vh;
}
.sex {
margin-top: 3.33vh;
}
.nickname {
width: 29.67vh;
height: 10vh;
display: flex;
padding: 1.33vh 24px 0;
background-repeat: no-repeat;
background-size: 100% 100%;
position: relative;
box-sizing: border-box;
@include single_ellipsis;
.x-line-font {
position: absolute;
bottom: 1.75vh;
left: 50%;
transform: translateX(-50%);
}
}
}
}
.exchange-btn {
position: absolute;
left: 1300px;
top: 2.5vh;
}
}
}
.preview-box {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
position: relative;
&.ex {
&::before {
content: '';
display: block;
background: var(--bg);
width: 49.67vh;
height: 19.83vh;
position: absolute;
top: -0.83vh;
left: 50%;
transform: translateX(-50%);
background-size: 100% 100%;
}
.preview-img {
width: 16.67vh;
height: 16.67vh;
}
}
.preview-img {
width: 22.67vh;
height: 22.67vh;
margin-bottom: $space-3_h;
&.border {
border-radius: 0.67vh;
border: 0.5vh solid #fff;
background: #edf1f5;
box-shadow: 0 0.33vh 0.33vh 0 rgba(0, 0, 0, 0.15);
}
}
.preview-head {
border: 0.25vh solid #323834;
border-radius: 50%;
background: #fffef0;
box-shadow: 0 0.42vh 0.42vh 0 #00000026;
}
.tips {
margin-bottom: $space-4_h;
text-align: center;
font-size: 2.33vh;
font-style: normal;
font-weight: 600;
height: 4vh;
display: flex;
align-items: center;
.x-icon {
margin: 0 $space-2;
}
}
}
.left-sidebar {
width: 20vh;
height: 68.67vh;
margin-right: 40px;
margin-left: 20px;
flex-shrink: 0;
:deep(.sidebar-item) {
height: 13.83vh;
line-height: 7.5vh;
&.active {
background-position: center 1.33vh;
}
}
}
$avatar_wh: 13.67vh;
$nick_frame_wh: 15.33vh;
$avatar_frame_wh: 14vh;
.left-main {
flex: 1;
position: relative;
&-box {
width: 100%;
display: flex;
justify-content: center;
height: 100%;
.list {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 3vh 4vh;
.list-item {
display: flex;
justify-content: center;
align-items: center;
position: relative;
.icon {
position: absolute;
top: 0;
right: 0;
}
.lock-icon {
display: flex;
align-items: center;
position: absolute;
bottom: 2vh;
left: 50%;
transform: translate(-50%);
padding: 0.5vh 12px;
background: rgba(0, 0, 0, 0.45);
border-radius: 3.75vh;
color: #ffffff;
font-family: $font-special;
font-size: 1.5vh;
.x-icon {
margin-right: 4px;
}
}
}
}
}
.nickName-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.input-box {
background-color: #b9d1ea;
// box-shadow: 0px 0px 6px 0px #4c749e inset;
width: 504px;
height: 6.67vh;
border-radius: 6.67vh;
overflow: hidden;
padding: 0 24px;
margin-bottom: 4.83vh;
position: relative;
overflow: visible;
z-index: 1;
.error-msg {
color: #f00a0a;
font-family: 'Alibaba Health Font 2.0 CN';
font-size: 2vh;
font-style: normal;
font-weight: 600;
line-height: 3.5vh; /* 175% */
position: absolute;
top: -3.5vh;
left: 48px;
}
input {
width: 100%;
height: 100%;
background-color: transparent;
outline: none;
color: #fff;
text-align: center;
text-shadow: 0 0.17vh 0.17vh #4c749e;
font-family: $font-special;
font-size: 2.67vh;
font-style: normal;
font-weight: 400;
line-height: 4vh; /* 150% */
}
}
.tips {
color: #999;
text-align: center;
font-family: 'Alibaba Health Font 2.0 CN';
font-size: 2vh;
font-style: normal;
font-weight: 600;
height: 4vh;
margin-bottom: $space-2_h;
display: flex;
align-items: center;
.x-icon {
margin: 0 $space-2;
}
}
}
.sex-box {
display: flex;
align-items: center;
justify-content: center;
.sex-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.text {
color: $font-color;
text-align: center;
font-family: $font-special;
font-size: 6vh;
font-style: normal;
font-weight: 400;
line-height: 9vh; /* 150% */
margin-top: 2vh;
}
}
.sex-icon {
box-sizing: content-box;
width: 20vh;
height: 20vh;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border-radius: 50%;
border: 0.67vh #fff solid;
margin: 0 60px;
position: relative;
&.male {
background-color: #18d6ff;
}
&.female {
background-color: #f07097;
}
&::before {
content: '';
background-color: transparent;
width: 22.67vh;
height: 22.67vh;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
z-index: -1;
}
&.active {
&::before {
background-color: #fef854;
}
}
}
}
.avatar {
&-list {
// grid-template-columns: repeat(7, 15vh);
// grid-template-rows: 15vh;
// grid-auto-rows: 15vh;
.list-item {
width: $avatar_wh;
height: $avatar_wh;
border-radius: 50%;
&.active {
background-color: #fef854;
box-shadow: 0 0.42vh 0.42vh 0 #00000026;
img {
border-color: transparent;
box-shadow: none;
}
}
img {
width: $avatar_wh;
height: $avatar_wh;
border: 0.25vh solid #323834;
border-radius: 50%;
background: #fffef0;
box-shadow: 0 0.42vh 0.42vh 0 #00000026;
}
}
}
}
.avatar_frame {
&-list {
.list-item {
width: $avatar_frame_wh;
height: $avatar_frame_wh;
border: 0.17vh solid #fff;
background-color: #ecf1f4;
border-radius: 1vh;
position: relative;
img {
background-color: #ecf1f4;
width: $avatar_frame_wh;
height: $avatar_frame_wh;
}
&.active {
background-color: #fef854;
}
}
}
}
.nick_frame {
&-list {
.list-item {
width: $nick_frame_wh;
height: $nick_frame_wh;
border: 0.17vh solid #fff;
background-color: #ecf1f4;
border-radius: 1vh;
position: relative;
overflow: hidden;
padding: 1.33vh;
.img-box {
background-color: #ecf1f4;
width: 14vh;
height: 14vh;
display: flex;
justify-content: center;
align-items: center;
}
img {
width: 168px;
height: 14vh;
}
&.active {
background-color: #fef854;
}
}
}
}
}
</style>