topfans/frontend/pages/profile/setNickname.vue

631 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<LoginBackground :show-logo="true">
<!-- 内容区域 -->
<view class="nickname-content">
<!-- 头像 -->
<view class="avatar-wrapper">
<image
class="avatar-image"
src="/static/login/portal/register-avatar.png"
mode="aspectFit"
></image>
</view>
<!-- 设置卡片 -->
<view class="nickname-card">
<view class="nav-back" @tap="goBack">
<text class="nav-back-icon"></text>
</view>
<!-- 标题 -->
<view class="card-title-wrapper">
<text class="card-title">设置头像与昵称</text>
</view>
<!-- 头像选择区域 -->
<view class="avatar-select-wrapper" @tap="handleAvatarClick">
<view class="avatar-select-box">
<image
class="avatar-select-bg"
src="/static/login/portal/setnickname-avatar-box.png"
mode="aspectFit"
></image>
<view class="avatar-select-inner">
<image
v-if="userAvatarUrl"
class="avatar-inner-img"
:src="userAvatarUrl"
mode="aspectFill"
></image>
<image
v-else
class="avatar-edit-icon"
src="/static/login/portal/setnickname-edit-icon.png"
mode="aspectFit"
></image>
</view>
</view>
</view>
<!-- 表单 -->
<view class="form-area">
<!-- 昵称输入 -->
<view class="input-group">
<view class="input-outer">
<view class="input-inner">
<input
class="input-field"
type="text"
v-model="nickname"
placeholder="请输入昵称"
placeholder-class="input-placeholder"
maxlength="20"
/>
</view>
</view>
</view>
<!-- 错误提示 -->
<view v-if="errorMessage" class="error-message">
<text>{{ errorMessage }}</text>
</view>
</view>
<!-- 分割线 -->
<view class="divider"></view>
<!-- 下一步按钮 -->
<view class="next-btn-wrapper">
<view class="next-btn" @tap="handleNext">
<text class="next-btn-text">下一步</text>
</view>
</view>
</view>
</view>
<!-- 头像上传弹窗 -->
<view
v-if="showAvatarModal"
class="avatar-modal-mask"
@tap="closeAvatarModal"
>
<view class="avatar-modal" @tap.stop>
<view class="modal-title">设置头像</view>
<view class="avatar-preview">
<image
v-if="userAvatarUrl"
class="avatar-preview-img"
:src="userAvatarUrl"
mode="aspectFill"
></image>
</view>
<button
class="upload-avatar-btn"
@tap="handleUploadAvatar"
:disabled="uploadingAvatar"
>
{{ uploadingAvatar ? "上传中..." : "上传头像" }}
</button>
<view class="upload-hint">支持JPG、PNG格式大小不超过10MB</view>
<view class="modal-buttons">
<button class="modal-btn-cancel" @tap="closeAvatarModal">确认</button>
</view>
</view>
</view>
</LoginBackground>
</template>
<script setup>
import { ref } from "vue";
import { useStore } from "vuex";
import LoginBackground from "@/components/LoginBackground.vue";
import { checkNicknameApi, getPublicOssSignatureApi } from "@/utils/api.js";
import { validateNickname } from "@/utils/validator.js";
const store = useStore();
// 响应式数据
const nickname = ref("");
const errorMessage = ref("");
const isChecking = ref(false);
// 头像上传相关
const userAvatarUrl = ref("");
const showAvatarModal = ref(false);
const uploadingAvatar = ref(false);
const goBack = () => {
uni.navigateBack()
};
// 打开头像上传弹窗
const handleAvatarClick = () => {
showAvatarModal.value = true;
};
// 关闭头像上传弹窗
const closeAvatarModal = () => {
if (uploadingAvatar.value) return;
showAvatarModal.value = false;
};
// 选择并上传头像
const handleUploadAvatar = () => {
uni.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: ["album", "camera"],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0];
uni.getFileInfo({
filePath: tempFilePath,
success: async (fileInfo) => {
if (fileInfo.size > 10 * 1024 * 1024) {
uni.showToast({ title: "图片大小不能超过10MB", icon: "none" });
return;
}
await uploadAvatarToOss(tempFilePath);
},
fail: (error) => {
console.error("获取文件信息失败:", error);
uni.showToast({ title: "获取文件信息失败", icon: "none" });
},
});
},
fail: (error) => {
console.error("选择图片失败:", error);
},
});
};
// 通过公开 OSS 签名接口上传头像
const uploadAvatarToOss = async (filePath) => {
try {
uploadingAvatar.value = true;
uni.showLoading({ title: "上传中...", mask: true });
const mobile = uni.getStorageSync("temp_register_mobile") || "anon";
const signRes = await getPublicOssSignatureApi("register", mobile);
if (signRes.code !== 0) {
throw new Error(signRes.message || "获取签名失败");
}
const ossKey = signRes.data.key;
uni.uploadFile({
url: signRes.data.host,
filePath: filePath,
name: "file",
formData: {
key: ossKey,
policy: signRes.data.policy,
success_action_status: "200",
"x-oss-credential": signRes.data.x_oss_credential,
"x-oss-date": signRes.data.x_oss_date,
"x-oss-security-token": signRes.data.security_token,
"x-oss-signature": signRes.data.signature,
"x-oss-signature-version": signRes.data.x_oss_signature_version,
},
success: (uploadRes) => {
uni.hideLoading();
if (uploadRes.statusCode === 200 || uploadRes.statusCode === 204) {
userAvatarUrl.value = `${signRes.data.host}/${ossKey}`;
uni.showToast({ title: "头像已选择", icon: "success" });
closeAvatarModal();
} else {
uni.showToast({
title: `上传失败,状态码: ${uploadRes.statusCode}`,
icon: "none",
duration: 2000,
});
}
uploadingAvatar.value = false;
},
fail: (error) => {
console.error("OSS上传失败:", error);
uni.hideLoading();
uni.showToast({ title: "上传失败", icon: "none", duration: 2000 });
uploadingAvatar.value = false;
},
});
} catch (error) {
console.error("上传头像失败:", error);
uni.hideLoading();
uni.showToast({
title: error.message || "上传失败",
icon: "none",
duration: 2000,
});
uploadingAvatar.value = false;
}
};
// 下一步
const handleNext = async () => {
const trimmedNickname = nickname.value.trim();
const validation = validateNickname(trimmedNickname);
if (!validation.valid) {
errorMessage.value = validation.message;
uni.showToast({ title: validation.message, icon: "none" });
return;
}
errorMessage.value = "";
if (isChecking.value) return;
isChecking.value = true;
try {
const res = await checkNicknameApi(trimmedNickname);
if (res.data.exists === true) {
errorMessage.value = "该昵称已被注册";
uni.showToast({ title: "该昵称已被注册", icon: "none" });
isChecking.value = false;
return;
}
// 保存昵称和头像到 storage由 selectRole 统一完成注册
uni.setStorageSync("temp_register_nickname", trimmedNickname);
const avatar_url = userAvatarUrl.value || "https://top-fans-test.oss-cn-shanghai.aliyuncs.com/avatar/13/87/character.png";
uni.setStorageSync("temp_register_avatar_url", avatar_url);
uni.reLaunch({ url: "/pages/profile/selectRole" });
} catch (error) {
errorMessage.value = error.message || "检查昵称失败,请重试";
uni.showToast({ title: errorMessage.value, icon: "none" });
} finally {
isChecking.value = false;
}
};
</script>
<style scoped>
.nickname-content {
position: relative;
z-index: 10;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 120rpx;
}
/* 头像 */
.avatar-wrapper {
width: 296rpx;
height: 274rpx;
margin-top: 130rpx;
filter: drop-shadow(2rpx 4rpx 98rpx rgba(0, 0, 0, 0.94));
z-index: 11;
}
.avatar-image {
width: 100%;
height: 100%;
}
/* 卡片 */
.nickname-card {
width: 710rpx;
margin-top: -104rpx;
padding: 30rpx 104rpx 30rpx;
box-sizing: border-box;
border-radius: 56rpx;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(64rpx);
-webkit-backdrop-filter: blur(64rpx);
box-shadow: 0 0 20.5px 0 rgba(0, 0, 0, 0.31);
display: flex;
flex-direction: column;
align-items: center;
z-index: 12;
}
.nav-back {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
left: 32rpx;
}
.nav-back-icon {
font-size: 48rpx;
font-weight: bold;
color: #fff;
}
/* 标题 */
.card-title-wrapper {
position: relative;
width: 324rpx;
height: 84rpx;
display: flex;
align-items: center;
justify-content: center;
background:
linear-gradient(240deg, #cdaffa 9.77%, rgba(205, 175, 250, 0) 62.44%),
linear-gradient(108deg, #f7cbbf 13.91%, rgba(247, 203, 191, 0) 51.34%),
linear-gradient(297deg, #81a9f8 31.28%, rgba(129, 169, 248, 0) 90.74%),
linear-gradient(80deg, #69f4e9 39.86%, rgba(164, 252, 245, 0) 66.88%);
border-radius: 84rpx;
}
.card-title {
position: relative;
z-index: 1;
font-size: 40rpx;
color: #fff9e7;
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
font-family: "Abhaya Libre Medium", sans-serif;
}
/* 头像选择区域 */
.avatar-select-wrapper {
position: relative;
width: 270rpx;
height: 270rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 48rpx 0;
}
.avatar-select-box {
position: relative;
width: 270rpx;
height: 270rpx;
}
.avatar-select-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
filter: drop-shadow(0 0 23rpx rgba(135, 16, 16, 0.67));
}
.avatar-select-inner {
position: absolute;
top: 18rpx;
left: 18rpx;
right: 18rpx;
bottom: 18rpx;
border-radius: 50%;
background: linear-gradient(
147deg,
rgba(223, 223, 146, 0.87) 17%,
rgba(135, 197, 216, 0.87) 39%,
rgba(194, 123, 209, 0.87) 99%
);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
n.avatar-inner-img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.avatar-placeholder-text {
font-size: 26rpx;
color: #fff9e7;
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
}
.avatar-edit-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 74rpx;
height: 74rpx;
opacity: 0.58;
}
/* 表单 */
.form-area {
width: 100%;
display: flex;
flex-direction: column;
gap: 20rpx;
padding: 0 14rpx;
}
.input-group {
width: 100%;
}
.input-outer {
width: 100%;
height: 74rpx;
border-radius: 58rpx;
background: linear-gradient(
90deg,
rgba(255, 222, 8, 0.06) 0%,
rgba(252, 100, 102, 0.12) 64%,
rgba(244, 88, 104, 0.12) 100%
);
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
display: flex;
align-items: center;
padding: 4rpx;
box-sizing: border-box;
}
.input-inner {
width: 100%;
height: 60rpx;
border-radius: 58rpx;
background: rgba(255, 255, 255, 0.65);
display: flex;
align-items: center;
padding: 0 24rpx;
box-sizing: border-box;
}
.input-field {
width: 100%;
height: 100%;
font-size: 30rpx;
color: #333;
}
.input-placeholder {
font-size: 30rpx;
color: #565656;
font-family: "Abhaya Libre Medium", sans-serif;
}
/* 错误提示 */
.error-message {
width: 100%;
padding-left: 24rpx;
}
.error-message text {
font-size: 24rpx;
color: #ff6b6b;
}
/* 分割线 */
.divider {
width: 612rpx;
height: 4rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 100rpx;
margin: 28rpx 0 24rpx;
}
/* 下一步按钮 */
.next-btn-wrapper {
width: 100%;
display: flex;
justify-content: center;
padding: 64rpx 0;
}
.next-btn {
width: 270rpx;
height: 90rpx;
border-radius: 58rpx;
background: linear-gradient(
90deg,
rgba(255, 222, 8, 0.28) 0%,
rgba(252, 100, 102, 0.58) 64%,
rgba(244, 88, 104, 0.58) 100%
);
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
display: flex;
align-items: center;
justify-content: center;
opacity: 0.96;
}
.next-btn-text {
font-size: 36rpx;
color: #fff9e7;
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
font-family: "Abhaya Libre Medium", sans-serif;
}
/* ===== 头像上传弹窗 ===== */
.avatar-modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.avatar-modal {
width: 80%;
max-width: 600rpx;
background: #e6e6e6;
border-radius: 30rpx;
padding: 60rpx 40rpx;
box-sizing: border-box;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
text-align: center;
margin-bottom: 40rpx;
}
.avatar-preview {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 40rpx;
}
.avatar-preview-img {
width: 360rpx;
height: 360rpx;
border-radius: 50%;
object-fit: cover;
}
.upload-avatar-btn {
width: 100%;
height: 88rpx;
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
background: #000000;
margin-bottom: 20rpx;
}
.upload-avatar-btn::after {
border: none;
}
.upload-avatar-btn:disabled {
opacity: 0.6;
}
.upload-hint {
font-size: 24rpx;
color: #999999;
text-align: center;
margin-bottom: 40rpx;
}
.modal-buttons {
display: flex;
gap: 20rpx;
}
.modal-btn-cancel {
flex: 1;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
background: #f5f5f5;
color: #666666;
}
.modal-btn-cancel::after {
border: none;
}
</style>