This commit is contained in:
aaron 2025-04-02 14:50:29 +08:00
parent 54be0f58f2
commit cdd46d912e
4 changed files with 287 additions and 3 deletions

View File

@ -46,3 +46,21 @@ export function submitUserAuth(data) {
data
});
}
// 发送验证码
export function sendCode(data) {
return request({
url: '/api/user/send-code',
method: 'post',
data
});
}
// 修改密码
export function changePassword(data) {
return request({
url: '/api/user/change-password',
method: 'post',
data
});
}

View File

@ -11,6 +11,9 @@
v-model:openKeys="openKeys"
theme="dark"
mode="inline"
:inline-collapsed="collapsed"
:defaultOpenKeys="['finance']"
:defaultSelectedKeys="['dashboard']"
style="background: #1a1a1a;"
>
<a-menu-item key="dashboard" @click="() => $router.push('/dashboard')">
@ -72,6 +75,9 @@
</a>
<template #overlay>
<a-menu>
<a-menu-item key="1">
<a href="javascript:;" @click="changePasswordVisible = true">修改密码</a>
</a-menu-item>
<a-menu-item key="0">
<a href="javascript:;" @click="handleLogout">退出登录</a>
</a-menu-item>
@ -93,6 +99,7 @@
蜂快 · 运营商平台 ©2025
</a-layout-footer> -->
</a-layout>
<change-password v-model:visible="changePasswordVisible" />
</a-layout>
</template>
@ -109,6 +116,7 @@ import {
UserOutlined,
ProfileOutlined
} from '@ant-design/icons-vue';
import ChangePassword from '../views/ChangePassword.vue';
export default {
name: 'AdminLayout',
@ -119,12 +127,14 @@ export default {
CreditCardOutlined,
WalletOutlined,
UserOutlined,
ProfileOutlined
ProfileOutlined,
ChangePassword
},
setup() {
const store = useStore();
const route = useRoute();
const router = useRouter();
const changePasswordVisible = ref(false);
const collapsed = computed({
get: () => store.getters.sidebarCollapsed,
@ -132,7 +142,7 @@ export default {
});
const userInfo = computed(() => store.getters.userInfo);
const selectedKeys = ref([]);
const selectedKeys = ref(['dashboard']);
const openKeys = ref(['finance']);
//
@ -176,7 +186,8 @@ export default {
userInfo,
handleLogout,
currentPageTitle,
route
route,
changePasswordVisible
};
}
};

View File

@ -65,6 +65,12 @@ const routes = [
name: 'UserAuth',
component: () => import('../views/UserAuth.vue'),
meta: { title: '实名认证', icon: 'user' }
},
{
path: 'user/change-password',
name: 'ChangePassword',
component: () => import('../views/ChangePassword.vue'),
meta: { title: '修改密码', icon: 'lock' }
}
]
},

View File

@ -0,0 +1,249 @@
<template>
<a-modal
:visible="visible"
@update:visible="$emit('update:visible', $event)"
title="修改密码"
:confirm-loading="loading"
@ok="handleSubmit"
@cancel="handleCancel"
:maskClosable="false"
:keyboard="false"
width="400px"
:destroyOnClose="true"
>
<a-form
ref="formRef"
:model="formState"
name="changePassword"
:rules="rules"
class="password-form"
>
<a-form-item name="phone">
<a-input
v-model:value="formState.phone"
placeholder="请输入手机号"
:maxLength="11"
class="input-item"
disabled
>
<template #prefix>
<mobile-outlined />
</template>
</a-input>
</a-form-item>
<a-form-item name="verify_code">
<div class="verify-code-input">
<a-input
v-model:value="formState.verify_code"
placeholder="请输入验证码"
:maxLength="6"
class="input-item"
>
<template #prefix>
<safety-certificate-outlined />
</template>
</a-input>
<a-button
type="primary"
:disabled="!!countdown"
@click="handleSendCode"
class="send-code-btn"
>
{{ countdown ? `${countdown}s后重试` : '获取验证码' }}
</a-button>
</div>
</a-form-item>
<a-form-item name="new_password">
<a-input-password
v-model:value="formState.new_password"
placeholder="请输入新密码"
class="input-item"
>
<template #prefix>
<lock-outlined />
</template>
</a-input-password>
</a-form-item>
<a-form-item name="confirm_password">
<a-input-password
v-model:value="formState.confirm_password"
placeholder="请确认新密码"
class="input-item"
>
<template #prefix>
<lock-outlined />
</template>
</a-input-password>
</a-form-item>
</a-form>
</a-modal>
</template>
<script>
import { ref, reactive, watch, onMounted, onBeforeUnmount } from 'vue';
import { message } from 'ant-design-vue';
import { useStore } from 'vuex';
import {
LockOutlined,
MobileOutlined,
SafetyCertificateOutlined
} from '@ant-design/icons-vue';
import { sendCode, changePassword } from '../api/user';
export default {
name: 'ChangePassword',
components: {
LockOutlined,
MobileOutlined,
SafetyCertificateOutlined
},
props: {
visible: {
type: Boolean,
default: false
}
},
emits: ['update:visible'],
setup(props, { emit }) {
const store = useStore();
const loading = ref(false);
const countdown = ref(0);
const formRef = ref(null);
let countdownTimer = null;
const formState = reactive({
phone: '',
verify_code: '',
new_password: '',
confirm_password: ''
});
const validateConfirmPassword = async (rule, value) => {
if (!value) {
return Promise.reject('请确认新密码');
}
if (value !== formState.new_password) {
return Promise.reject('两次输入的密码不一致');
}
return Promise.resolve();
};
const rules = {
verify_code: [{ required: true, message: '请输入验证码' }],
new_password: [
{ required: true, message: '请输入新密码' },
{ min: 6, message: '密码长度不能小于6位' }
],
confirm_password: [{ required: true, validator: validateConfirmPassword }]
};
const startCountdown = () => {
countdown.value = 60;
countdownTimer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(countdownTimer);
countdownTimer = null;
}
}, 1000);
};
const handleSendCode = async () => {
try {
await sendCode({ phone: formState.phone });
message.success('验证码已发送');
startCountdown();
} catch (error) {
message.error(error.message || '发送验证码失败');
}
};
const handleSubmit = async () => {
try {
await formRef.value.validate();
loading.value = true;
await changePassword({
phone: formState.phone,
verify_code: formState.verify_code,
new_password: formState.new_password
});
message.success('密码修改成功');
handleCancel();
} catch (error) {
if (error.message) {
message.error(error.message);
}
} finally {
loading.value = false;
}
};
const handleCancel = () => {
//
if (formRef.value) {
formRef.value.resetFields();
}
//
countdown.value = 0;
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
//
emit('update:visible', false);
};
// visible
watch(() => props.visible, (val) => {
if (val) {
const userInfo = store.getters.userInfo;
if (userInfo && userInfo.phone) {
formState.phone = userInfo.phone;
}
}
});
//
onBeforeUnmount(() => {
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
});
return {
formRef,
formState,
rules,
loading,
countdown,
handleSubmit,
handleSendCode,
handleCancel
};
}
};
</script>
<style scoped>
.password-form {
max-width: 100%;
}
.input-item {
width: 100%;
}
.verify-code-input {
display: flex;
gap: 12px;
}
.send-code-btn {
white-space: nowrap;
min-width: 120px;
}
</style>