631 lines
14 KiB
Vue
631 lines
14 KiB
Vue
<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>
|