Merge branch 'master' of https://gitee.com/mickeywaugh/youlai-app
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user