feat: 修改密码全局
All checks were successful
构建并部署前端到测试环境 / deploy (push) Successful in 56s

This commit is contained in:
luo
2026-09-28 11:17:39 +08:00
parent 015f990fb6
commit e79d599da1
12 changed files with 319 additions and 5 deletions

View File

@@ -6,7 +6,6 @@ import { BaseService } from '../BaseService'
import type {
LoginParams,
LoginData,
UserInfo,
UserInfoResponse,
RefreshTokenParams,
RefreshTokenData,
@@ -51,7 +50,9 @@ export class AuthService extends BaseService {
* @param params 修改密码参数
*/
static changePassword(params: ChangePasswordParams): Promise<BaseResponse> {
return this.put<BaseResponse>('/api/auth/password', params)
return this.put<BaseResponse>('/api/auth/password', params, {
requestOptions: { errorMessageMode: 'none' }
})
}
/**

View File

@@ -0,0 +1,151 @@
<template>
<ElDialog
v-model="dialogVisible"
:title="t('passwordChange.title')"
width="min(420px, calc(100vw - 32px))"
:close-on-click-modal="false"
:close-on-press-escape="!submitting"
:show-close="!submitting"
@closed="resetForm"
>
<ElForm
ref="formRef"
:model="formData"
:rules="formRules"
label-position="top"
@submit.prevent="handleSubmit"
>
<ElFormItem prop="old_password" :label="t('passwordChange.oldPassword')">
<ElInput
v-model="formData.old_password"
type="password"
autocomplete="current-password"
show-password
:disabled="submitting"
/>
</ElFormItem>
<ElFormItem prop="new_password" :label="t('passwordChange.newPassword')">
<ElInput
v-model="formData.new_password"
type="password"
autocomplete="new-password"
show-password
:disabled="submitting"
/>
</ElFormItem>
<ElFormItem prop="confirm_password" :label="t('passwordChange.confirmPassword')">
<ElInput
v-model="formData.confirm_password"
type="password"
autocomplete="new-password"
show-password
:disabled="submitting"
@keyup.enter="handleSubmit"
/>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton :disabled="submitting" @click="dialogVisible = false">
{{ t('common.cancel') }}
</ElButton>
<ElButton type="primary" :loading="submitting" @click="handleSubmit">
{{ t('passwordChange.submit') }}
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { AuthService } from '@/api/modules'
import { useUserStore } from '@/store/modules/user'
import { normalizeApiError } from '@/utils/business/apiError'
import { useI18n } from 'vue-i18n'
defineOptions({ name: 'ChangePasswordDialog' })
const props = defineProps<{ modelValue: boolean }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
const { t } = useI18n()
const userStore = useUserStore()
const dialogVisible = computed({
get: () => props.modelValue,
set: (value: boolean) => emit('update:modelValue', value)
})
const formRef = ref<FormInstance>()
const submitting = ref(false)
const formData = reactive({
old_password: '',
new_password: '',
confirm_password: ''
})
const formRules = computed<FormRules>(() => ({
old_password: [
{ required: true, message: t('passwordChange.oldPasswordRequired'), trigger: 'blur' }
],
new_password: [
{ required: true, message: t('passwordChange.newPasswordRequired'), trigger: 'blur' }
],
confirm_password: [
{ required: true, message: t('passwordChange.confirmPasswordRequired'), trigger: 'blur' },
{
validator: (_rule, value: string, callback) => {
if (value && value !== formData.new_password) {
callback(new Error(t('passwordChange.passwordMismatch')))
return
}
callback()
},
trigger: 'blur'
}
]
}))
const resetForm = () => {
formData.old_password = ''
formData.new_password = ''
formData.confirm_password = ''
formRef.value?.clearValidate()
}
const handleSubmit = async () => {
if (!formRef.value || submitting.value) return
try {
await formRef.value.validate()
} catch {
return
}
if (submitting.value) return
submitting.value = true
try {
const response = await AuthService.changePassword({
old_password: formData.old_password,
new_password: formData.new_password
})
if (response.code !== 0) {
ElMessage.error(response.msg || t('passwordChange.failure'))
return
}
ElMessage.success(t('passwordChange.success'))
resetForm()
dialogVisible.value = false
userStore.clearLocalState()
} catch (error) {
const normalized = normalizeApiError(error)
ElMessage.error(normalized.message || t('passwordChange.failure'))
} finally {
submitting.value = false
}
}
</script>

View File

@@ -158,6 +158,12 @@
<i class="menu-icon iconfont-sys">&#xe734;</i>
<span class="menu-txt">{{ $t('topBar.user.userCenter') }}</span>
</li>
<li>
<button class="user-menu-action" type="button" @click="openChangePassword">
<el-icon class="menu-icon"><Key /></el-icon>
<span class="menu-txt">{{ $t('topBar.user.changePassword') }}</span>
</button>
</li>
<li @click="lockScreen()">
<i class="menu-icon iconfont-sys">&#xe817;</i>
<span class="menu-txt">{{ $t('topBar.user.lockScreen') }}</span>
@@ -175,12 +181,15 @@
</div>
<ArtWorkTab />
<ChangePasswordDialog v-model="changePasswordDialogVisible" />
<art-notification v-model="showNotice" />
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue'
import { Key } from '@element-plus/icons-vue'
import ChangePasswordDialog from './ChangePasswordDialog.vue'
import { MenuTypeEnum, MenuWidth } from '@/enums/appEnum'
import { useSettingStore } from '@/store/modules/setting'
import { useUserStore } from '@/store/modules/user'
@@ -217,6 +226,7 @@
const { menuList } = storeToRefs(useMenuStore())
const showNotice = ref(false)
const changePasswordDialogVisible = ref(false)
const userMenuPopover = ref()
const isLeftMenu = computed(() => menuType.value === MenuTypeEnum.LEFT)
@@ -348,6 +358,13 @@
mittBus.emit('openLockScreen')
}
const openChangePassword = () => {
closeUserMenu()
window.setTimeout(() => {
changePasswordDialogVisible.value = true
}, 100)
}
const closeUserMenu = () => {
setTimeout(() => {
userMenuPopover.value.hide()

View File

@@ -88,6 +88,23 @@
}
}
.user-menu-action {
display: flex;
align-items: center;
width: 100%;
padding: 0;
margin: 0;
text-align: left;
cursor: pointer;
background: transparent;
border: 0;
&:focus-visible {
outline: 2px solid var(--el-color-primary);
outline-offset: 2px;
}
}
.line {
width: 100%;
height: 1px;

View File

@@ -7,6 +7,7 @@
"userCenter": "User center",
"docs": "Document",
"github": "Github",
"changePassword": "Change password",
"lockScreen": "Lock screen",
"logout": "Log out"
}
@@ -17,6 +18,19 @@
"confirm": "Confirm",
"logOutTips": "Do you want to log out?"
},
"passwordChange": {
"title": "Change password",
"oldPassword": "Current password",
"newPassword": "New password",
"confirmPassword": "Confirm new password",
"oldPasswordRequired": "Please enter your current password",
"newPasswordRequired": "Please enter a new password",
"confirmPasswordRequired": "Please enter the new password again",
"passwordMismatch": "The new passwords do not match",
"submit": "Change password",
"success": "Password changed successfully",
"failure": "Failed to change password"
},
"search": {
"placeholder": "Search page",
"historyTitle": "Search history",

View File

@@ -7,6 +7,7 @@
"userCenter": "个人中心",
"docs": "使用文档",
"github": "Github",
"changePassword": "修改密码",
"lockScreen": "锁定屏幕",
"logout": "退出登录"
}
@@ -18,6 +19,19 @@
"logOutTips": "您是否要退出登录?",
"back": "返回"
},
"passwordChange": {
"title": "修改密码",
"oldPassword": "当前密码",
"newPassword": "新密码",
"confirmPassword": "确认新密码",
"oldPasswordRequired": "请输入当前密码",
"newPasswordRequired": "请输入新密码",
"confirmPasswordRequired": "请再次输入新密码",
"passwordMismatch": "两次输入的新密码不一致",
"submit": "确认修改",
"success": "密码修改成功",
"failure": "密码修改失败"
},
"search": {
"placeholder": "搜索页面",
"historyTitle": "搜索历史",

View File

@@ -63,7 +63,6 @@ export interface RefreshTokenData {
// 修改密码参数
export interface ChangePasswordParams {
oldPassword: string
newPassword: string
confirmPassword: string
old_password: string
new_password: string
}

View File

@@ -80,6 +80,7 @@ declare module 'vue' {
BoxStyleSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue')['default']
CardOperationDialog: typeof import('./../components/business/CardOperationDialog.vue')['default']
CardStatusTag: typeof import('./../components/business/CardStatusTag.vue')['default']
ChangePasswordDialog: typeof import('./../components/core/layouts/art-header-bar/ChangePasswordDialog.vue')['default']
ChunkErrorBoundary: typeof import('./../components/core/others/ChunkErrorBoundary.vue')['default']
CodeGeneratorButton: typeof import('./../components/business/CodeGeneratorButton.vue')['default']
ColorSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ColorSettings.vue')['default']