This commit is contained in:
Mickey wu
2026-05-24 12:02:31 +08:00
2 changed files with 451 additions and 580 deletions

View File

@@ -2,27 +2,10 @@
<view class="page py-16rpx">
<wd-card>
<wd-cell-group border>
<wd-cell
icon="secured"
title="账户密码"
value="修改"
is-link
@click="handleOpenDialog(DialogType.PASSWORD)"
/>
<wd-cell
icon="mobile"
title="绑定手机"
:value="userProfile?.mobile || '未绑定手机号'"
is-link
@click="handleOpenDialog(DialogType.MOBILE)"
/>
<wd-cell
icon="mail"
title="绑定邮箱"
:value="userProfile?.email ? userProfile.email : '未绑定邮箱'"
is-link
@click="handleOpenDialog(DialogType.EMAIL)"
/>
<wd-cell icon="secured" title="账户密码" value="修改" is-link @click="handleOpenDialog(DialogType.PASSWORD)" />
<wd-cell icon="mobile" title="绑定手机" :value="userProfile?.mobile || '未绑定手机号'" is-link @click="handleOpenDialog(DialogType.MOBILE)" />
<wd-cell icon="mail" title="绑定邮箱" :value="userProfile?.email ? userProfile.email : '未绑定邮箱'" is-link
@click="handleOpenDialog(DialogType.EMAIL)" />
<!-- #ifdef MP-WEIXIN -->
<wd-cell icon="wechat" title="微信" value="解绑" is-link @click="handleUnbindWechat" />
<!-- #endif -->
@@ -30,84 +13,28 @@
</wd-card>
<!--用户信息编辑弹出框-->
<wd-popup
v-model="dialog.visible"
position="bottom"
custom-style="border-top-left-radius: 24rpx; border-top-right-radius: 24rpx;"
>
<wd-form
v-if="dialog.type === DialogType.PASSWORD"
ref="passwordChangeFormRef"
:model="passwordChangeForm"
custom-class="edit-form"
>
<wd-popup v-model="dialog.visible" position="bottom" custom-style="border-top-left-radius: 24rpx; border-top-right-radius: 24rpx;">
<wd-form v-if="dialog.type === DialogType.PASSWORD" ref="passwordChangeFormRef" :model="passwordChangeForm" custom-class="edit-form">
<wd-cell-group border>
<wd-input
v-model="passwordChangeForm.oldPassword"
label="原密码"
label-width="160rpx"
show-password
clearable
placeholder="请输入原密码"
prop="oldPassword"
:rules="rules.oldPassword"
/>
<wd-input
v-model="passwordChangeForm.newPassword"
label="新密码"
label-width="160rpx"
show-password
clearable
placeholder="请输入新密码"
prop="newPassword"
:rules="rules.newPassword"
/>
<wd-input
v-model="passwordChangeForm.confirmPassword"
label="确认密码"
label-width="160rpx"
show-password
clearable
placeholder="请确认新密码"
prop="confirmPassword"
:rules="rules.confirmPassword"
/>
<wd-input v-model="passwordChangeForm.oldPassword" label="原密码" label-width="160rpx" show-password clearable placeholder="请输入原密码"
prop="oldPassword" :rules="rules.oldPassword" />
<wd-input v-model="passwordChangeForm.newPassword" label="新密码" label-width="160rpx" show-password clearable placeholder="请输入新密码"
prop="newPassword" :rules="rules.newPassword" />
<wd-input v-model="passwordChangeForm.confirmPassword" label="确认密码" label-width="160rpx" show-password clearable placeholder="请确认新密码"
prop="confirmPassword" :rules="rules.confirmPassword" />
</wd-cell-group>
<view class="p-24rpx">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
<wd-form
v-if="dialog.type === DialogType.MOBILE"
ref="mobileBindingFormRef"
:model="mobileBindingForm"
custom-class="edit-form"
>
<wd-form v-if="dialog.type === DialogType.MOBILE" ref="mobileBindingFormRef" :model="mobileBindingForm" custom-class="edit-form">
<wd-cell-group border>
<wd-input
v-model="mobileBindingForm.mobile"
label="手机号码"
label-width="160rpx"
clearable
placeholder="请输入手机号码"
prop="mobile"
:rules="rules.mobile"
/>
<wd-input
v-model="mobileBindingForm.code"
label="验证码"
label-width="160rpx"
clearable
placeholder="请输入验证码"
prop="code"
:rules="rules.code"
>
<wd-input v-model="mobileBindingForm.mobile" label="手机号码" label-width="160rpx" clearable placeholder="请输入手机号码" prop="mobile"
:rules="rules.mobile" />
<wd-input v-model="mobileBindingForm.code" label="验证码" label-width="160rpx" clearable placeholder="请输入验证码" prop="code"
:rules="rules.code">
<template #suffix>
<wd-button
plain
:disabled="mobileCountdown > 0"
@click="handleSendVerificationCode('MOBILE')"
>
<wd-button plain :disabled="mobileCountdown > 0" @click="handleSendVerificationCode('MOBILE')">
{{ mobileCountdown > 0 ? `${mobileCountdown}s后重新发送` : "发送验证码" }}
</wd-button>
</template>
@@ -117,37 +44,14 @@
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
<wd-form
v-if="dialog.type === DialogType.EMAIL"
ref="emailBindingFormRef"
:model="emailBindingForm"
custom-class="edit-form"
>
<wd-form v-if="dialog.type === DialogType.EMAIL" ref="emailBindingFormRef" :model="emailBindingForm" custom-class="edit-form">
<wd-cell-group border>
<wd-input
v-model="emailBindingForm.email"
label="邮箱"
label-width="160rpx"
clearable
placeholder="请输入邮箱"
prop="email"
:rules="rules.email"
/>
<wd-input
v-model="emailBindingForm.code"
label="验证码"
label-width="160rpx"
clearable
placeholder="请输入验证码"
prop="code"
:rules="rules.code"
>
<wd-input v-model="emailBindingForm.email" label="邮箱" label-width="160rpx" clearable placeholder="请输入邮箱" prop="email"
:rules="rules.email" />
<wd-input v-model="emailBindingForm.code" label="验证码" label-width="160rpx" clearable placeholder="请输入验证码" prop="code"
:rules="rules.code">
<template #suffix>
<wd-button
plain
:disabled="emailCountdown > 0"
@click="handleSendVerificationCode('EMAIL')"
>
<wd-button plain :disabled="emailCountdown > 0" @click="handleSendVerificationCode('EMAIL')">
{{ emailCountdown > 0 ? `${emailCountdown}s后重新发送` : "发送验证码" }}
</wd-button>
</template>
@@ -161,172 +65,172 @@
</view>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from "vue";
import { useToast, useDialog } from "@wot-ui/ui";
import { useCountdown } from "@/composables/useCountdown";
import UserAPI, {
PasswordChangeForm,
MobileBindingForm,
EmailBindingForm,
UserProfile,
} from "@/api/user";
import { onMounted, reactive, ref } from "vue";
import { useToast, useDialog } from "@wot-ui/ui";
import { useCountdown } from "@/composables/useCountdown";
import UserAPI, {
PasswordChangeForm,
MobileBindingForm,
EmailBindingForm,
UserProfile,
} from "@/api/user";
const toast = useToast();
const toast = useToast();
const { confirm } = useDialog();
const validatorConfirmPassword = (value: string) => {
if (!value) {
return Promise.reject("请确认密码");
} else {
if (value !== passwordChangeForm.newPassword) {
return Promise.reject("两次输入的密码不一致");
const validatorConfirmPassword = (value: string) => {
if (!value) {
return Promise.reject("请确认密码");
} else {
return Promise.resolve();
if (value !== passwordChangeForm.newPassword) {
return Promise.reject("两次输入的密码不一致");
} else {
return Promise.resolve();
}
}
};
// 本页面中所有的校验规则
const rules = reactive({
oldPassword: [{ required: true, message: "请填写原密码" }],
newPassword: [{ required: true, message: "请填写新密码" }],
confirmPassword: [{ required: true, message: "请确认密码", validator: validatorConfirmPassword }],
mobile: [{ required: true, pattern: /^1[3-9]\d{9}$/, message: "请填写正确的手机号码" }],
code: [{ required: true, message: "请填写验证码" }],
email: [
{
required: true,
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
message: "请填写正确的邮箱地址",
},
],
});
enum DialogType {
PASSWORD = "password",
MOBILE = "mobile",
EMAIL = "email",
}
const dialog = reactive({
visible: false,
type: "" as DialogType, // 修改账号资料,修改密码、绑定手机、绑定邮箱
});
const userProfile = ref<UserProfile>(); //用户信息
const passwordChangeForm = reactive<PasswordChangeForm>({});
const mobileBindingForm = reactive<MobileBindingForm>({});
const emailBindingForm = reactive<EmailBindingForm>({});
const passwordChangeFormRef = ref();
const mobileBindingFormRef = ref();
const emailBindingFormRef = ref();
const { countdown: mobileCountdown, start: startMobileCountdown } = useCountdown(60);
const { countdown: emailCountdown, start: startEmailCountdown } = useCountdown(60);
const handleUnbindWechat = async () => {
try {
await confirm({
title: "提示",
msg: "确定要解绑微信吗?解绑后将无法使用微信小程序登录",
headerImage: "warning"
});
await UserAPI.unbindSocial("WECHAT_MINI");
toast.success("解绑成功");
loadUserProfile();
} catch {
// 用户取消或解绑失败
}
};
/** 加载用户信息 */
const loadUserProfile = async () => {
userProfile.value = await UserAPI.getProfile();
};
/**
* 打开弹窗
* @param type 弹窗类型 ACCOUNT: 账号资料 PASSWORD: 修改密码 MOBILE: 绑定手机 EMAIL: 绑定邮箱
*/
const handleOpenDialog = (type: DialogType) => {
dialog.type = type;
dialog.visible = true;
switch (type) {
case DialogType.PASSWORD:
passwordChangeForm.oldPassword = "";
passwordChangeForm.newPassword = "";
passwordChangeForm.confirmPassword = "";
break;
case DialogType.MOBILE:
mobileBindingForm.mobile = "";
mobileBindingForm.code = "";
break;
case DialogType.EMAIL:
emailBindingForm.email = "";
emailBindingForm.code = "";
break;
}
};
/**
* 发送验证码
*
* @param contactType 联系方式类型 MOBILE: 手机号码 EMAIL: 邮箱
*/
const handleSendVerificationCode = async (contactType: string) => {
try {
if (contactType === "MOBILE") {
const { valid } = await mobileBindingFormRef.value.validate("mobile");
if (valid) {
await UserAPI.sendVerificationCode(mobileBindingForm.mobile!, "MOBILE");
uni.showToast({ title: "验证码已发送", icon: "none" });
startMobileCountdown();
}
} else if (contactType === "EMAIL") {
const { valid } = await emailBindingFormRef.value.validate("email");
if (valid) {
await UserAPI.sendVerificationCode(emailBindingForm.email!, "EMAIL");
uni.showToast({ title: "验证码已发送", icon: "none" });
startEmailCountdown();
}
}
} catch (error) {
console.error("发送验证码失败:", error);
}
};
// 提交表单
async function handleSubmit() {
try {
if (dialog.type === DialogType.PASSWORD) {
const { valid } = await passwordChangeFormRef.value.validate();
if (valid) {
await UserAPI.changePassword(passwordChangeForm);
uni.showToast({ title: "密码修改成功", icon: "none" });
dialog.visible = false;
}
} else if (dialog.type === DialogType.MOBILE) {
const { valid } = await mobileBindingFormRef.value.validate();
if (valid) {
await UserAPI.bindMobile(mobileBindingForm);
uni.showToast({ title: "手机号绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
}
} else if (dialog.type === DialogType.EMAIL) {
const { valid } = await emailBindingFormRef.value.validate();
if (valid) {
await UserAPI.bindEmail(emailBindingForm);
uni.showToast({ title: "邮箱绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
}
}
} catch (error) {
console.error("提交表单失败:", error);
}
}
};
// 本页面中所有的校验规则
const rules = reactive({
oldPassword: [{ required: true, message: "请填写原密码" }],
newPassword: [{ required: true, message: "请填写新密码" }],
confirmPassword: [{ required: true, message: "请确认密码", validator: validatorConfirmPassword }],
mobile: [{ required: true, pattern: /^1[3-9]\d{9}$/, message: "请填写正确的手机号码" }],
code: [{ required: true, message: "请填写验证码" }],
email: [
{
required: true,
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
message: "请填写正确的邮箱地址",
},
],
});
enum DialogType {
PASSWORD = "password",
MOBILE = "mobile",
EMAIL = "email",
}
const dialog = reactive({
visible: false,
type: "" as DialogType, // 修改账号资料,修改密码、绑定手机、绑定邮箱
});
const userProfile = ref<UserProfile>(); //用户信息
const passwordChangeForm = reactive<PasswordChangeForm>({});
const mobileBindingForm = reactive<MobileBindingForm>({});
const emailBindingForm = reactive<EmailBindingForm>({});
const passwordChangeFormRef = ref();
const mobileBindingFormRef = ref();
const emailBindingFormRef = ref();
const { countdown: mobileCountdown, start: startMobileCountdown } = useCountdown(60);
const { countdown: emailCountdown, start: startEmailCountdown } = useCountdown(60);
const handleUnbindWechat = async () => {
try {
await confirm({
title: "提示",
msg: "确定要解绑微信吗?解绑后将无法使用微信小程序登录",
headerImage: "warning",
});
await UserAPI.unbindSocial("WECHAT_MINI");
toast.success("解绑成功");
onMounted(() => {
loadUserProfile();
} catch {
// 用户取消或解绑失败
}
};
/** 加载用户信息 */
const loadUserProfile = async () => {
userProfile.value = await UserAPI.getProfile();
};
/**
* 打开弹窗
* @param type 弹窗类型 ACCOUNT: 账号资料 PASSWORD: 修改密码 MOBILE: 绑定手机 EMAIL: 绑定邮箱
*/
const handleOpenDialog = (type: DialogType) => {
dialog.type = type;
dialog.visible = true;
switch (type) {
case DialogType.PASSWORD:
passwordChangeForm.oldPassword = "";
passwordChangeForm.newPassword = "";
passwordChangeForm.confirmPassword = "";
break;
case DialogType.MOBILE:
mobileBindingForm.mobile = "";
mobileBindingForm.code = "";
break;
case DialogType.EMAIL:
emailBindingForm.email = "";
emailBindingForm.code = "";
break;
}
};
/**
* 发送验证码
*
* @param contactType 联系方式类型 MOBILE: 手机号码 EMAIL: 邮箱
*/
const handleSendVerificationCode = async (contactType: string) => {
try {
if (contactType === "MOBILE") {
const { valid } = await mobileBindingFormRef.value.validate("mobile");
if (valid) {
await UserAPI.sendVerificationCode(mobileBindingForm.mobile!, "MOBILE");
uni.showToast({ title: "验证码已发送", icon: "none" });
startMobileCountdown();
}
} else if (contactType === "EMAIL") {
const { valid } = await emailBindingFormRef.value.validate("email");
if (valid) {
await UserAPI.sendVerificationCode(emailBindingForm.email!, "EMAIL");
uni.showToast({ title: "验证码已发送", icon: "none" });
startEmailCountdown();
}
}
} catch (error) {
console.error("发送验证码失败:", error);
}
};
// 提交表单
async function handleSubmit() {
try {
if (dialog.type === DialogType.PASSWORD) {
const { valid } = await passwordChangeFormRef.value.validate();
if (valid) {
await UserAPI.changePassword(passwordChangeForm);
uni.showToast({ title: "密码修改成功", icon: "none" });
dialog.visible = false;
}
} else if (dialog.type === DialogType.MOBILE) {
const { valid } = await mobileBindingFormRef.value.validate();
if (valid) {
await UserAPI.bindMobile(mobileBindingForm);
uni.showToast({ title: "手机号绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
}
} else if (dialog.type === DialogType.EMAIL) {
const { valid } = await emailBindingFormRef.value.validate();
if (valid) {
await UserAPI.bindEmail(emailBindingForm);
uni.showToast({ title: "邮箱绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
}
}
} catch (error) {
console.error("提交表单失败:", error);
}
}
onMounted(() => {
loadUserProfile();
});
});
</script>

View File

@@ -15,27 +15,21 @@
<view class="avatar-picker">
<!-- #ifdef MP-WEIXIN -->
<button class="avatar-picker__trigger" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="avatar-picker__trigger" @click="chooseAvatar">
<!-- #endif -->
<image
v-if="profileForm.avatar"
:src="profileForm.avatar"
class="avatar-picker__img"
mode="aspectFill"
/>
<view v-else class="avatar-picker__placeholder">
<wd-icon name="camera" size="40" color="var(--color-text-placeholder)" />
<text class="avatar-picker__hint">选择头像</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="avatar-picker__trigger" @click="chooseAvatar">
<!-- #endif -->
<image v-if="profileForm.avatar" :src="profileForm.avatar" class="avatar-picker__img" mode="aspectFill" />
<view v-else class="avatar-picker__placeholder">
<wd-icon name="camera" size="40" color="var(--color-text-placeholder)" />
<text class="avatar-picker__hint">选择头像</text>
</view>
</view> <!-- #ifdef MP-WEIXIN -->
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
</view>
<!-- #endif -->
</view>
<!-- #endif -->
</view>
<!-- 昵称 -->
@@ -45,22 +39,11 @@
<text class="profile-form__required">*</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<input
v-model="profileForm.nickname"
type="nickname"
class="profile-form__nickname-input"
placeholder="请输入昵称"
:maxlength="20"
/>
<input v-model="profileForm.nickname" type="nickname" class="profile-form__nickname-input" placeholder="请输入昵称" :maxlength="20" />
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<wd-input
v-model="profileForm.nickname"
placeholder="请输入昵称"
prop="nickname"
:rules="rules.nickname"
custom-class="profile-form__nickname-input"
/>
<wd-input v-model="profileForm.nickname" placeholder="请输入昵称" prop="nickname" :rules="rules.nickname"
custom-class="profile-form__nickname-input" />
<!-- #endif -->
</view>
@@ -79,27 +62,15 @@
手机号
<text class="profile-form__required">*</text>
</view>
<wd-input
v-model="profileForm.mobile"
placeholder="请输入手机号"
prop="mobile"
custom-class="profile-form__nickname-input"
/>
<wd-input v-model="profileForm.mobile" placeholder="请输入手机号" prop="mobile" custom-class="profile-form__nickname-input" />
</view>
</wd-form>
</view>
<!-- 底部 -->
<view class="profile-footer">
<wd-button
class="profile-footer__submit"
type="primary"
size="large"
block
:disabled="!canComplete || isLoading"
:loading="isLoading"
@click="handleComplete"
>
<wd-button class="profile-footer__submit" type="primary" size="large" block :disabled="!canComplete || isLoading" :loading="isLoading"
@click="handleComplete">
完成
</wd-button>
<view class="profile-footer__skip" @click="handleSkip">
@@ -108,301 +79,297 @@
</view>
<!-- 头像裁剪 -->
<wd-img-cropper
v-model="cropperVisible"
:img-src="originalImageSrc"
@confirm="handleAvatarConfirm"
/>
<wd-img-cropper v-model="cropperVisible" :img-src="originalImageSrc" @confirm="handleAvatarConfirm" />
<wd-toast />
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { useUserStore } from "@/store";
import UserAPI, { type UserProfileForm } from "@/api/user";
import FileAPI, { type FileInfo } from "@/api/file";
import { ref, reactive, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { useUserStore } from "@/store";
import UserAPI, { type UserProfileForm } from "@/api/user";
import FileAPI, { type FileInfo } from "@/api/file";
const toast = useToast();
const toast = useToast();
const { confirm } = useDialog();
const userStore = useUserStore();
const userStore = useUserStore();
const redirect = ref("/pages/index/index");
const redirect = ref("/pages/index/index");
const profileForm = reactive<UserProfileForm & { mobile?: string }>({
nickname: "",
avatar: "",
gender: 1,
mobile: "",
});
const rules = {
nickname: [
{ required: true, message: "请输入昵称" },
{ required: true, min: 2, max: 20, message: "昵称长度为2-20个字符" },
],
};
const isLoading = ref(false);
const cropperVisible = ref(false);
const originalImageSrc = ref("");
const profileFormRef = ref();
const canComplete = computed(() => {
return profileForm.nickname && profileForm.mobile;
});
onLoad((options: any) => {
if (options?.redirect) {
redirect.value = decodeURIComponent(options.redirect);
}
const userInfo = userStore.userInfo;
if (userInfo) {
profileForm.nickname = userInfo.nickname || "";
profileForm.avatar = userInfo.avatar || "";
profileForm.gender = (userInfo as any).gender || 1;
}
});
const onChooseAvatar = async (e: any) => {
try {
const { avatarUrl } = e.detail;
toast.loading("上传中...");
const fileInfo: FileInfo = await FileAPI.upload(avatarUrl);
profileForm.avatar = fileInfo.url;
toast.success("头像上传成功");
} catch {
toast.error("头像上传失败");
}
};
const chooseAvatar = () => {
// #ifdef MP-WEIXIN
uni.chooseMedia({
count: 1,
mediaType: ["image"],
sourceType: ["album", "camera"],
success: (res) => {
originalImageSrc.value = res.tempFiles[0].tempFilePath;
cropperVisible.value = true;
},
fail: () => {
toast.error("选择图片失败");
},
const profileForm = reactive<UserProfileForm & { mobile?: string }>({
nickname: "",
avatar: "",
gender: 1,
mobile: "",
});
// #endif
// #ifndef MP-WEIXIN
uni.chooseImage({
count: 1,
sourceType: ["album", "camera"],
success: (res) => {
originalImageSrc.value = res.tempFilePaths[0];
cropperVisible.value = true;
},
fail: () => {
toast.error("选择图片失败");
},
const rules = {
nickname: [
{ required: true, message: "请输入昵称" },
{ required: true, min: 2, max: 20, message: "昵称长度为2-20个字符" },
],
};
const isLoading = ref(false);
const cropperVisible = ref(false);
const originalImageSrc = ref("");
const profileFormRef = ref();
const canComplete = computed(() => {
return profileForm.nickname && profileForm.mobile;
});
// #endif
};
const handleAvatarConfirm = async (event: any) => {
try {
const { tempFilePath } = event;
toast.loading("上传中...");
const fileInfo: FileInfo = await FileAPI.upload(tempFilePath);
profileForm.avatar = fileInfo.url;
toast.success("头像上传成功");
} catch {
toast.error("头像上传失败");
}
};
const handleComplete = async () => {
try {
const { valid } = await profileFormRef.value.validate();
if (!valid) return;
if (!profileForm.mobile) {
toast.error("请输入手机号");
return;
onLoad((options: any) => {
if (options?.redirect) {
redirect.value = decodeURIComponent(options.redirect);
}
const userInfo = userStore.userInfo;
if (userInfo) {
profileForm.nickname = userInfo.nickname || "";
profileForm.avatar = userInfo.avatar || "";
profileForm.gender = (userInfo as any).gender || 1;
}
});
isLoading.value = true;
const onChooseAvatar = async (e: any) => {
try {
const { avatarUrl } = e.detail;
toast.loading("上传中...");
const fileInfo: FileInfo = await FileAPI.upload(avatarUrl);
profileForm.avatar = fileInfo.url;
toast.success("头像上传成功");
} catch {
toast.error("头像上传失败");
}
};
await UserAPI.updateProfile({
nickname: profileForm.nickname,
avatar: profileForm.avatar,
gender: profileForm.gender,
const chooseAvatar = () => {
// #ifdef MP-WEIXIN
uni.chooseMedia({
count: 1,
mediaType: ["image"],
sourceType: ["album", "camera"],
success: (res) => {
originalImageSrc.value = res.tempFiles[0].tempFilePath;
cropperVisible.value = true;
},
fail: () => {
toast.error("选择图片失败");
},
});
// #endif
if (profileForm.mobile) {
await UserAPI.bindMobile({ mobile: profileForm.mobile });
// #ifndef MP-WEIXIN
uni.chooseImage({
count: 1,
sourceType: ["album", "camera"],
success: (res) => {
originalImageSrc.value = res.tempFilePaths[0];
cropperVisible.value = true;
},
fail: () => {
toast.error("选择图片失败");
},
});
// #endif
};
const handleAvatarConfirm = async (event: any) => {
try {
const { tempFilePath } = event;
toast.loading("上传中...");
const fileInfo: FileInfo = await FileAPI.upload(tempFilePath);
profileForm.avatar = fileInfo.url;
toast.success("头像上传成功");
} catch {
toast.error("头像上传失败");
}
};
await userStore.getInfo();
toast.success("信息完善成功");
const handleComplete = async () => {
try {
const { valid } = await profileFormRef.value.validate();
if (!valid) return;
setTimeout(() => {
if (!profileForm.mobile) {
toast.error("请输入手机号");
return;
}
isLoading.value = true;
await UserAPI.updateProfile({
nickname: profileForm.nickname,
avatar: profileForm.avatar,
gender: profileForm.gender,
});
if (profileForm.mobile) {
await UserAPI.bindMobile({ mobile: profileForm.mobile });
}
await userStore.getInfo();
toast.success("信息完善成功");
setTimeout(() => {
uni.reLaunch({ url: redirect.value });
}, 1000);
} catch (error: any) {
toast.error(error?.message || "完善信息失败");
} finally {
isLoading.value = false;
}
};
const handleSkip = async () => {
try {
await confirm({ title: "提示", msg: "跳过信息完善可能会影响部分功能使用,确定要跳过吗?", headerImage: "warning" });
uni.reLaunch({ url: redirect.value });
}, 1000);
} catch (error: any) {
toast.error(error?.message || "完善信息失败");
} finally {
isLoading.value = false;
}
};
const handleSkip = async () => {
try {
await confirm({ title: "提示", msg: "跳过信息完善可能会影响部分功能使用,确定要跳过吗?", headerImage: "warning" });
uni.reLaunch({ url: redirect.value });
} catch {
// 用户取消
}
};
} catch {
// 用户取消
}
};
</script>
<style lang="scss" scoped>
.complete-profile__hero {
margin-bottom: 60rpx;
text-align: center;
}
.complete-profile__hero-title {
margin-bottom: 20rpx;
font-size: 48rpx;
font-weight: bold;
color: var(--color-text-inverse);
}
.complete-profile__hero-subtitle {
font-size: 28rpx;
line-height: 1.5;
color: rgba(255, 255, 255, 0.8);
}
.profile-form {
padding: 40rpx;
margin-bottom: 40rpx;
background: var(--color-bg);
border-radius: 24rpx;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
&__section {
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
.complete-profile__hero {
margin-bottom: 60rpx;
text-align: center;
}
&__label {
.complete-profile__hero-title {
margin-bottom: 20rpx;
font-size: 32rpx;
font-weight: 600;
color: var(--color-text);
font-size: 48rpx;
font-weight: bold;
color: var(--color-text-inverse);
}
&__required {
color: var(--color-danger);
.complete-profile__hero-subtitle {
font-size: 28rpx;
line-height: 1.5;
color: rgba(255, 255, 255, 0.8);
}
&__nickname-input {
:deep(.wd-input__inner) {
padding: 24rpx 20rpx;
background: var(--color-bg-tertiary);
border: 1rpx solid var(--color-border-light);
border-radius: 12rpx;
.profile-form {
padding: 40rpx;
margin-bottom: 40rpx;
background: var(--color-bg);
border-radius: 24rpx;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
&__section {
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
}
}
}
.avatar-picker {
display: flex;
justify-content: center;
&__trigger {
display: flex;
align-items: center;
justify-content: center;
width: 160rpx;
height: 160rpx;
padding: 0;
margin: 0 auto;
overflow: hidden;
background: transparent;
border: none;
border-radius: 50%;
&::after {
border: none;
}
}
&__img {
width: 100%;
height: 100%;
border: 4rpx solid var(--color-border-light);
border-radius: 50%;
}
&__placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 160rpx;
height: 160rpx;
background: var(--color-bg-tertiary);
border: 2rpx dashed var(--color-border);
border-radius: 50%;
}
&__hint {
margin-top: 10rpx;
font-size: 24rpx;
color: var(--color-text-placeholder);
}
}
.gender-group {
display: flex;
gap: 20rpx;
.gender-radio {
flex: 1;
:deep(.wd-radio) {
justify-content: center;
width: 100%;
}
}
}
.profile-footer {
&__submit {
margin-bottom: 30rpx;
:deep(.wd-button) {
height: 88rpx;
&__label {
margin-bottom: 20rpx;
font-size: 32rpx;
font-weight: 600;
border-radius: 44rpx;
color: var(--color-text);
}
&__required {
color: var(--color-danger);
}
&__nickname-input {
:deep(.wd-input__inner) {
padding: 24rpx 20rpx;
background: var(--color-bg-tertiary);
border: 1rpx solid var(--color-border-light);
border-radius: 12rpx;
}
}
}
&__skip {
text-align: center;
.avatar-picker {
display: flex;
justify-content: center;
text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
text-decoration: underline;
&__trigger {
display: flex;
align-items: center;
justify-content: center;
width: 160rpx;
height: 160rpx;
padding: 0;
margin: 0 auto;
overflow: hidden;
background: transparent;
border: none;
border-radius: 50%;
&::after {
border: none;
}
}
&__img {
width: 100%;
height: 100%;
border: 4rpx solid var(--color-border-light);
border-radius: 50%;
}
&__placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 160rpx;
height: 160rpx;
background: var(--color-bg-tertiary);
border: 2rpx dashed var(--color-border);
border-radius: 50%;
}
&__hint {
margin-top: 10rpx;
font-size: 24rpx;
color: var(--color-text-placeholder);
}
}
.gender-group {
display: flex;
gap: 20rpx;
.gender-radio {
flex: 1;
:deep(.wd-radio) {
justify-content: center;
width: 100%;
}
}
}
.profile-footer {
&__submit {
margin-bottom: 30rpx;
:deep(.wd-button) {
height: 88rpx;
font-size: 32rpx;
font-weight: 600;
border-radius: 44rpx;
}
}
&__skip {
text-align: center;
text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
text-decoration: underline;
}
}
}
}
</style>