This commit is contained in:
Leo_Ding 2025-12-03 18:15:10 +08:00
parent f175e5159a
commit 45f557c9cb
4 changed files with 201 additions and 196 deletions

View File

@ -10,3 +10,9 @@ export const login = (params:any) => request.post('/v1/auth/login', params)
// export const updateRole = (id, params) => request.basic.put(`/api/v1/roles/${id}`, params)
// // 删除role
// export const delRole = (id) => request.basic.delete(`/api/v1/roles/${id}`)
// 修改密码
export const updatePassword = (newPassword:any) => request.put('/v1/auth/update_password',newPassword)
// 获取用户信息
export const fetchUserInfo = () => request.get('/v1/auth/info')
// 修改手机号
export const updatePhoneNumber = (params:any) => request.put('/v1/auth/update_phone', params)

View File

@ -1,6 +1,109 @@
<!-- src/components/AccountSecurity.vue -->
<template>
<div class="account-security">
<h2>账号安全</h2>
<div v-for="(item, index) in securityItems" :key="index" class="security-item">
<div class="item-header">
<span class="title">{{ item.title }}</span>
<div v-if="item.title == '登录密码'">
<button class="action-btn" :class="`color-${getActionColor(item.status)}`" @click="item.actionHandler">
{{ item.actionText }}
</button>
</div>
<div class="status" v-if="item.title !== '登录密码'">
<component :is="getStatusIcon(item.status)" v-if="getStatusIcon(item.status)"
:class="['icon', `color-${getStatusColor(item.status)}`]" />
<span :class="['text', `color-${getStatusColor(item.status)}`]">
{{ item.status === 'bound' ? '已绑定' : item.status === 'unverified' ? '未认证' : '未设置' }}
</span>
<span class="divider">|</span>
<button class="action-btn" :class="`color-${getActionColor(item.status)}`" @click="item.actionHandler">
{{ item.actionText }}
</button>
</div>
</div>
<p class="description">{{ item.description }}</p>
</div>
<!-- 设置密码弹窗 -->
<a-modal v-model:visible="setPasswordModalVisible" title="设置密码" width="500px" :footer="null"
@cancel="closeSetPasswordModal">
<div style="padding: 24px 0;">
<div style="margin-bottom: 24px; font-size: 16px; color: #333;">
手机号<span>{{ userPhone }}</span>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">手机验证码</label>
<a-input v-model:value="passwordForm.smsCode" placeholder="请输入验证码" style="flex: 1;" />
<a-button type="primary" :disabled="isSending || countdown > 0" @click="sendSmsCode('password')"
style="width: 120px;">
{{ isSending || countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
</a-button>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">设置新密码</label>
<a-input v-model:value="passwordForm.newPassword" placeholder="请输入816位包含数字和字母的密码" type="password"
style="flex: 1;" />
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">确认新密码</label>
<a-input v-model:value="passwordForm.confirmPassword" placeholder="请确认密码" type="password" style="flex: 1;" />
</div>
</div>
<div style="text-align: right; padding: 16px 24px;">
<a-button @click="closeSetPasswordModal" style="margin-right: 8px;">取消</a-button>
<a-button type="primary" @click="savePassword">保存</a-button>
</div>
</a-modal>
<!-- 修改手机号弹窗 -->
<a-modal v-model:visible="editPhoneModalVisible" title="修改手机号" width="500px" :footer="null"
@cancel="closeEditPhoneModal">
<div style="padding: 24px 0;">
<div style="margin-bottom: 24px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">输入新手机号</label>
<a-select v-model:value="phoneForm.countryCode" style="width: 80px;" placeholder="+86">
<a-select-option value="+86">+86</a-select-option>
<a-select-option value="+852">+852</a-select-option>
<a-select-option value="+853">+853</a-select-option>
</a-select>
<a-input v-model:value="phoneForm.newPhoneNumber" placeholder="请输入新手机号" style="flex: 1;" />
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">手机验证码</label>
<a-input v-model:value="phoneForm.smsCode" placeholder="请输入验证码" style="flex: 1;" />
<a-button type="primary" :disabled="isSending || countdown > 0" @click="sendSmsCode('phone')"
style="width: 120px;">
{{ isSending || countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
</a-button>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">输入账号密码</label>
<a-input v-model:value="phoneForm.password" placeholder="请输入账号登录密码" type="password" style="flex: 1;" />
</div>
</div>
<div style="text-align: right; padding: 16px 24px;">
<a-button @click="closeEditPhoneModal" style="margin-right: 8px;">取消</a-button>
<a-button type="primary" @click="savePhone">确定</a-button>
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ref, reactive, onBeforeMount, computed } from 'vue';
import {
CheckCircleOutlined,
ExclamationCircleOutlined,
@ -10,9 +113,15 @@ import {
import { Modal, message } from 'ant-design-vue';
// <script setup> router
import { useRouter } from 'vue-router';
import { updatePassword, updatePhoneNumber } from '@/apis/login';
const router = useRouter();
const userPhone = ref('')
onBeforeMount(() => {
//
console.log(1111)
userPhone.value = JSON.parse(localStorage.getItem('userInfo') || '{}').phone
})
const getStatusIcon = (status: SecurityItem['status']) => {
switch (status) {
case 'bound':
@ -46,24 +155,23 @@ const getActionColor = (status: SecurityItem['status']) => {
interface SecurityItem {
title: string;
description: string;
status: 'unset' | 'bound' | 'unverified' | 'verified';
actionText: string;
status?: 'unset' | 'bound' | 'unverified' | 'verified';
actionText?: string;
actionHandler: () => void;
}
const securityItems = ref<SecurityItem[]>([
const securityItems = computed<SecurityItem[]>(() => [
{
title: '登录密码',
description: '安全性高的密码可以使账号更安全。建议您定期更换密码设置一个包含字母和数字且长度超过8位的密码',
status: 'unset',
actionText: '设置',
status: 'bound',
actionText: '修改密码',
actionHandler: () => openSetPasswordModal(),
},
{
title: '手机绑定',
description: '您已绑定了手机178****5075您的手机号可以直接用于登录、找回密码等',
status: 'bound',
actionText: '修改',
title: '手机',
description: userPhone.value ? '您已绑定了手机178****5075您的手机号可以直接用于登录、找回密码等' : '未绑定手机',
status: userPhone.value ? 'bound' : 'unset',
actionText: userPhone.value ? '修改手机号' : '绑定手机号',
actionHandler: () => openEditPhoneModal(),
},
{
@ -75,21 +183,33 @@ const securityItems = ref<SecurityItem[]>([
router.push('/layout/admin/realnameAuth');
},
},
// {
// title: '',
// description: '',
// status: 'bound',
// actionText: '',
// actionHandler: () => alert(''),
// },
{
title: '邮箱绑定(即将上线)',
description: '绑定邮箱后可接收系统消息,如余额不足、实例即将到期、实例即将释放等消息',
status: 'unset',
actionText: '绑定',
actionHandler: () => alert('跳转到绑定邮箱页面'),
},
]);
// const securityItems = ref<SecurityItem[]>([
// {
// title: '',
// description: '使8',
// status: 'bound',
// actionText: '',
// actionHandler: () => openSetPasswordModal(),
// },
// {
// title: '',
// description: userPhone.value?'178****5075':'',
// status: userPhone.value?'bound':'unset',
// actionText: userPhone.value?'':'',
// actionHandler: () => openEditPhoneModal(),
// },
// {
// title: '',
// description: '使AutoDL',
// status: 'unverified',
// actionText: '',
// actionHandler: () => {
// router.push('/layout/admin/realnameAuth');
// },
// },
// ]);
//
const setPasswordModalVisible = ref(false);
@ -109,6 +229,7 @@ const closeRealNameAuthModal = () => {
//
const passwordForm = reactive({
phone: userPhone.value,
smsCode: '',
newPassword: '',
confirmPassword: '',
@ -185,7 +306,7 @@ const resetPhoneForm = () => {
//
const savePassword = () => {
const savePassword = async () => {
if (!passwordForm.smsCode) {
message.error('请输入验证码');
return;
@ -206,13 +327,28 @@ const savePassword = () => {
message.error('密码必须为8~16位且包含字母和数字');
return;
}
try {
const passwordData = {
phone: userPhone.value,
code: passwordForm.smsCode,
confirmPassword: passwordForm.confirmPassword,
newPassword: passwordForm.newPassword
};
const res = await updatePassword(passwordData);
message.success('密码设置成功!');
setTimeout(() => {
localStorage.clear();
router.replace('/login');
}, 1500);
closeSetPasswordModal();
} catch (error: any) {
message.error(error);
}
message.success('密码设置成功!');
closeSetPasswordModal();
};
//
const savePhone = () => {
const savePhone = async () => {
if (!phoneForm.newPhoneNumber) {
message.error('请输入新手机号');
return;
@ -229,166 +365,24 @@ const savePhone = () => {
message.error('请输入账号登录密码');
return;
}
message.success('手机号修改成功!');
closeEditPhoneModal();
try {
const phoneData = {
newPhone: phoneForm.newPhoneNumber,
code: phoneForm.smsCode,
Password: phoneForm.password
};
await updatePhoneNumber(phoneData);
message.success('手机号修改成功!');
closeEditPhoneModal();
setTimeout(() => {
localStorage.clear();
router.replace('/login');
}, 1500);
} catch (error: any) {
message.error(error);
}
};
</script>
<template>
<div class="account-security">
<h2>账号安全</h2>
<div v-for="(item, index) in securityItems" :key="index" class="security-item">
<div class="item-header">
<span class="title">{{ item.title }}</span>
<div class="status">
<component
:is="getStatusIcon(item.status)"
v-if="getStatusIcon(item.status)"
:class="['icon', `color-${getStatusColor(item.status)}`]"
/>
<span :class="['text', `color-${getStatusColor(item.status)}`]">
{{ item.status === 'bound' ? '已绑定' : item.status === 'unverified' ? '未认证' : '未设置' }}
</span>
<span class="divider">|</span>
<button
class="action-btn"
:class="`color-${getActionColor(item.status)}`"
@click="item.actionHandler"
>
{{ item.actionText }}
</button>
</div>
</div>
<p class="description">{{ item.description }}</p>
</div>
<!-- 设置密码弹窗 -->
<a-modal
v-model:visible="setPasswordModalVisible"
title="设置密码"
width="500px"
:footer="null"
@cancel="closeSetPasswordModal"
>
<div style="padding: 24px 0;">
<div style="margin-bottom: 24px; font-size: 16px; color: #333;">
手机号+86 178****5075
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">手机验证码</label>
<a-input
v-model:value="passwordForm.smsCode"
placeholder="请输入验证码"
style="flex: 1;"
/>
<a-button
type="primary"
:disabled="isSending || countdown > 0"
@click="sendSmsCode('password')"
style="width: 120px;"
>
{{ isSending || countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
</a-button>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">设置新密码</label>
<a-input
v-model:value="passwordForm.newPassword"
placeholder="请输入816位包含数字和字母的密码"
type="password"
style="flex: 1;"
/>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">确认新密码</label>
<a-input
v-model:value="passwordForm.confirmPassword"
placeholder="请确认密码"
type="password"
style="flex: 1;"
/>
</div>
</div>
<div style="text-align: right; padding: 16px 24px;">
<a-button @click="closeSetPasswordModal" style="margin-right: 8px;">取消</a-button>
<a-button type="primary" @click="savePassword">保存</a-button>
</div>
</a-modal>
<!-- 修改手机号弹窗 -->
<a-modal
v-model:visible="editPhoneModalVisible"
title="修改手机号"
width="500px"
:footer="null"
@cancel="closeEditPhoneModal"
>
<div style="padding: 24px 0;">
<div style="margin-bottom: 24px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">输入新手机号</label>
<a-select
v-model:value="phoneForm.countryCode"
style="width: 80px;"
placeholder="+86"
>
<a-select-option value="+86">+86</a-select-option>
<a-select-option value="+852">+852</a-select-option>
<a-select-option value="+853">+853</a-select-option>
</a-select>
<a-input
v-model:value="phoneForm.newPhoneNumber"
placeholder="请输入新手机号"
style="flex: 1;"
/>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">手机验证码</label>
<a-input
v-model:value="phoneForm.smsCode"
placeholder="请输入验证码"
style="flex: 1;"
/>
<a-button
type="primary"
:disabled="isSending || countdown > 0"
@click="sendSmsCode('phone')"
style="width: 120px;"
>
{{ isSending || countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
</a-button>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">输入账号密码</label>
<a-input
v-model:value="phoneForm.password"
placeholder="请输入账号登录密码"
type="password"
style="flex: 1;"
/>
</div>
</div>
<div style="text-align: right; padding: 16px 24px;">
<a-button @click="closeEditPhoneModal" style="margin-right: 8px;">取消</a-button>
<a-button type="primary" @click="savePhone">确定</a-button>
</div>
</a-modal>
</div>
</template>
<style scoped>
.account-security {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

View File

@ -11,25 +11,26 @@
<div class="user-info">
<a-dropdown>
<div style="display: flex;align-items: center;justify-content: flex-end;">
<a-avatar :size="24" :src="avatar">
<a-avatar :size="24" :src="userInfo.avatar">
<!-- <template #icon>
<UserOutlined />
</template> -->
</a-avatar>
<span style="font-size: 14px;padding-left:5px;">管理员</span>
<span style="font-size: 14px;padding-left:5px;">{{ userInfo.userName }}</span>
</div>
<template #overlay>
<a-card hoverable style="width: 300px">
<template #cover>
<div style="background:#f5f7fa;padding:10px;line-height: 30px">
<div>管理员<a-tag style="margin-left: 10px;" color="red">未实名</a-tag></div>
<div>ID:23455522</div>
<div>{{ userInfo.userName }} <a-tag color="blue" style="margin-left: 10px;">{{ userInfo.accountType==='USER'?'个人认证':'企业认证' }}</a-tag></div>
<div>ID:{{ userInfo.id }}</div>
</div>
<div style="padding: 10px;line-height: 45px;">
<div style="display: flex;justify-content: space-between;align-items: center;"><div>可用余额¥200.00</div> <a-button type="primary" danger ghost size="small">去充值</a-button></div>
<div>冻结余额¥100.00</div>
<div>代金劵¥100.00</div>
<div>实例数量¥{{ userInfo.caseNum }}</div>
<div>冻结余额¥{{ userInfo.freezeBalace }}</div>
<div>未冻结余额¥{{ userInfo.noFreezeBalace }}</div>
</div>
</template>
<template #actions>
@ -56,6 +57,7 @@ import avatar from '@/assets/avator.png'
const router = useRouter()
const route = useRoute()
const isHome = ref(true)
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
// /layout
const getActiveKeyFromRoute = () => {
const path = route.path
@ -100,6 +102,7 @@ const handleMenuClick = (key) => {
}
}
const logout = () => {
localStorage.clear()
router.replace('/login')
}
</script>

View File

@ -19,7 +19,7 @@
<script lang="ts" setup>
import { reactive, ref, shallowRef } from 'vue';
import { useRouter } from 'vue-router';
import { login } from '@/apis/login';
import { login, fetchUserInfo } from '@/apis/login';
import { message, type FormInstance } from 'ant-design-vue';
const router = useRouter();
@ -55,7 +55,7 @@ const onFinish = async (values: FormState) => {
const loginData = {
...values,
login_method: 'SMS'
login_method: 'PWD'//PWDSMS
};
formRef.value?.validateFields().then(async () => {
try {
@ -67,6 +67,8 @@ const onFinish = async (values: FormState) => {
throw new Error('登录失败:未返回有效凭证');
}
localStorage.setItem('token', token);
const userRes=await fetchUserInfo();
localStorage.setItem('userInfo', JSON.stringify(userRes));
router.push('/layout/home');
} catch (error: any) {
console.error('登录请求失败:', error);