sms、邮件数据库配置

This commit is contained in:
2025-11-26 13:38:36 +08:00
parent 8d8ecf8763
commit 4ff1bc1101
16 changed files with 847 additions and 186 deletions

View File

@@ -1,6 +1,5 @@
<template>
<div class="account-settings">
<div class="account-settings" v-loading="loading">
<div class="settings-section">
<h3>修改密码</h3>
<el-form :model="passwordForm" :rules="passwordRules" ref="passwordFormRef" label-width="120px">
@@ -30,10 +29,11 @@
<i class="icon">📱</i>
<div>
<h4>手机绑定</h4>
<p>已绑定手机138****8888</p>
<p v-if="userInfo.phone">已绑定手机{{ maskPhone(userInfo.phone) }}</p>
<p v-else class="not-bind">未绑定</p>
</div>
</div>
<el-button size="small">修改</el-button>
<el-button size="small" @click="showPhoneDialog">{{ userInfo.phone ? '修改' : '绑定' }}</el-button>
</div>
<div class="security-item">
@@ -41,23 +41,78 @@
<i class="icon"></i>
<div>
<h4>邮箱绑定</h4>
<p>已绑定邮箱user@example.com</p>
<p v-if="userInfo.email">已绑定邮箱{{ maskEmail(userInfo.email) }}</p>
<p v-else class="not-bind">未绑定</p>
</div>
</div>
<el-button size="small">修改</el-button>
<el-button size="small" @click="showEmailDialog">{{ userInfo.email ? '修改' : '绑定' }}</el-button>
</div>
</div>
</div>
</div>
<!-- 手机号绑定弹窗 -->
<el-dialog v-model="phoneDialogVisible" title="手机号绑定" width="500px">
<el-form :model="phoneForm" :rules="phoneRules" ref="phoneFormRef" label-width="100px">
<el-form-item label="手机号" prop="phone">
<el-input v-model="phoneForm.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="验证码" prop="code">
<div class="code-input-wrapper">
<el-input v-model="phoneForm.code" placeholder="请输入验证码" />
<el-button
:disabled="phoneCounting"
@click="sendPhoneCode"
>
{{ phoneCounting ? `${phoneCountdown}秒后重试` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="phoneDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleBindPhone" :loading="phoneBinding">确定</el-button>
</template>
</el-dialog>
<!-- 邮箱绑定弹窗 -->
<el-dialog v-model="emailDialogVisible" title="邮箱绑定" width="500px">
<el-form :model="emailForm" :rules="emailRules" ref="emailFormRef" label-width="100px">
<el-form-item label="邮箱" prop="email">
<el-input v-model="emailForm.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="验证码" prop="code">
<div class="code-input-wrapper">
<el-input v-model="emailForm.code" placeholder="请输入验证码" />
<el-button
:disabled="emailCounting"
@click="sendEmailCode"
>
{{ emailCounting ? `${emailCountdown}秒后重试` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="emailDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleBindEmail" :loading="emailBinding">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElForm, ElFormItem, ElInput, ElButton, ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { UserCenterLayout } from '@/views/user/user-center';
import { ref, onMounted } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { userProfileApi } from '@/apis/usercenter';
import { authApi } from '@/apis/system/auth';
import type { UserVO } from '@/types';
const loading = ref(false);
const passwordFormRef = ref<FormInstance>();
const phoneFormRef = ref<FormInstance>();
const emailFormRef = ref<FormInstance>();
// 用户信息
const userInfo = ref<UserVO>({});
const passwordForm = ref({
oldPassword: '',
@@ -88,18 +143,283 @@ const passwordRules: FormRules = {
]
};
// 手机号绑定
const phoneDialogVisible = ref(false);
const phoneBinding = ref(false);
const phoneCounting = ref(false);
const phoneCountdown = ref(60);
const phoneForm = ref({
phone: '',
code: ''
});
const phoneRules: FormRules = {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' }
]
};
// 邮箱绑定
const emailDialogVisible = ref(false);
const emailBinding = ref(false);
const emailCounting = ref(false);
const emailCountdown = ref(60);
const emailForm = ref({
email: '',
code: ''
});
const emailRules: FormRules = {
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' }
]
};
onMounted(() => {
loadUserInfo();
});
/**
* 加载用户信息
*/
async function loadUserInfo() {
loading.value = true;
try {
const result = await userProfileApi.getUserProfile();
if (result.code === 200 && result.data) {
userInfo.value = result.data;
} else {
ElMessage.error(result.message || '获取用户信息失败');
}
} catch (error: any) {
console.error('加载用户信息失败:', error);
ElMessage.error('加载用户信息失败: ' + (error.message || '未知错误'));
} finally {
loading.value = false;
}
}
/**
* 修改密码
*/
async function handleChangePassword() {
if (!passwordFormRef.value) return;
try {
await passwordFormRef.value.validate();
// TODO: 调用修改密码API
ElMessage.success('密码修改成功');
passwordFormRef.value.resetFields();
} catch (error) {
console.error('表单验证失败', error);
const result = await userProfileApi.changePassword(
passwordForm.value.oldPassword,
passwordForm.value.newPassword
);
if (result.code === 200) {
ElMessage.success('密码修改成功');
passwordFormRef.value.resetFields();
} else {
ElMessage.error(result.message || '密码修改失败');
}
} catch (error: any) {
console.error('密码修改失败:', error);
ElMessage.error('密码修改失败');
}
}
/**
* 显示手机号绑定弹窗
*/
function showPhoneDialog() {
phoneForm.value = {
phone: userInfo.value.phone || '',
code: ''
};
phoneDialogVisible.value = true;
}
/**
* 发送手机验证码
*/
async function sendPhoneCode() {
if (!phoneForm.value.phone) {
ElMessage.warning('请先输入手机号');
return;
}
if (!/^1[3-9]\d{9}$/.test(phoneForm.value.phone)) {
ElMessage.warning('请输入正确的手机号');
return;
}
try {
const result = await authApi.sendSmsCode(phoneForm.value.phone);
if (result.code === 200) {
ElMessage.success('验证码已发送');
startPhoneCountdown();
} else {
ElMessage.error(result.message || '发送失败');
}
} catch (error: any) {
console.error('发送验证码失败:', error);
ElMessage.error('发送验证码失败');
}
}
/**
* 开始手机验证码倒计时
*/
function startPhoneCountdown() {
phoneCounting.value = true;
phoneCountdown.value = 60;
const timer = setInterval(() => {
phoneCountdown.value--;
if (phoneCountdown.value <= 0) {
clearInterval(timer);
phoneCounting.value = false;
}
}, 1000);
}
/**
* 绑定手机号
*/
async function handleBindPhone() {
if (!phoneFormRef.value) return;
try {
await phoneFormRef.value.validate();
phoneBinding.value = true;
const result = await userProfileApi.bindPhone(
phoneForm.value.phone,
phoneForm.value.code
);
if (result.code === 200) {
ElMessage.success('手机号绑定成功');
phoneDialogVisible.value = false;
await loadUserInfo();
} else {
ElMessage.error(result.message || '绑定失败');
}
} catch (error: any) {
console.error('绑定手机号失败:', error);
ElMessage.error('绑定失败');
} finally {
phoneBinding.value = false;
}
}
/**
* 显示邮箱绑定弹窗
*/
function showEmailDialog() {
emailForm.value = {
email: userInfo.value.email || '',
code: ''
};
emailDialogVisible.value = true;
}
/**
* 发送邮箱验证码
*/
async function sendEmailCode() {
if (!emailForm.value.email) {
ElMessage.warning('请先输入邮箱');
return;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailForm.value.email)) {
ElMessage.warning('请输入正确的邮箱地址');
return;
}
try {
const result = await authApi.sendEmailCode(emailForm.value.email);
if (result.code === 200) {
ElMessage.success('验证码已发送');
startEmailCountdown();
} else {
ElMessage.error(result.message || '发送失败');
}
} catch (error: any) {
console.error('发送验证码失败:', error);
ElMessage.error('发送验证码失败');
}
}
/**
* 开始邮箱验证码倒计时
*/
function startEmailCountdown() {
emailCounting.value = true;
emailCountdown.value = 60;
const timer = setInterval(() => {
emailCountdown.value--;
if (emailCountdown.value <= 0) {
clearInterval(timer);
emailCounting.value = false;
}
}, 1000);
}
/**
* 绑定邮箱
*/
async function handleBindEmail() {
if (!emailFormRef.value) return;
try {
await emailFormRef.value.validate();
emailBinding.value = true;
const result = await userProfileApi.bindEmail(
emailForm.value.email,
emailForm.value.code
);
if (result.code === 200) {
ElMessage.success('邮箱绑定成功');
emailDialogVisible.value = false;
await loadUserInfo();
} else {
ElMessage.error(result.message || '绑定失败');
}
} catch (error: any) {
console.error('绑定邮箱失败:', error);
ElMessage.error('绑定失败');
} finally {
emailBinding.value = false;
}
}
/**
* 脱敏手机号
*/
function maskPhone(phone: string): string {
if (!phone || phone.length < 11) return phone;
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
/**
* 脱敏邮箱
*/
function maskEmail(email: string): string {
if (!email) return email;
const [username, domain] = email.split('@');
if (username.length <= 3) {
return email;
}
const maskedUsername = username.substring(0, 2) + '****' + username.substring(username.length - 1);
return maskedUsername + '@' + domain;
}
</script>
<style lang="scss" scoped>
@@ -157,6 +477,23 @@ async function handleChangePassword() {
p {
font-size: 14px;
color: #666;
&.not-bind {
color: #999;
}
}
}
.code-input-wrapper {
display: flex;
gap: 10px;
.el-input {
flex: 1;
}
.el-button {
white-space: nowrap;
}
}
</style>