前端个人资料页面增加重置密码按钮

This commit is contained in:
jingrow 2025-09-07 15:36:27 +08:00
parent baf5183079
commit 25e196b38e
4 changed files with 351 additions and 5 deletions

View File

@ -3,11 +3,11 @@
<div class="prose prose-sm max-w-none">
<h1 class="text-2xl font-semibold">欢迎来到 今果 Jingrow</h1>
<p>
今果 Jingrow是下一代智能工作平台"一切皆页面"的革新理念让您像搭积木一样灵活配置业务系统通过AI驱动的智能工作流可视化数据建模实时协作与权限管理为您打造专属的工作空间支持团队协作让复杂的管理任务变得优雅而高效
Jingrow是一站式通用数字化平台"一切皆页面"的革新理念帮助您快速构建从个人工作到团队协作的一站式数字化解决方案通过AI Agent智能体可视化工作流编排零代码可视化数据建模自动化任务团队协作角色与权限管理文件库知识库笔记会议待办事项通知智能报表等模块引领数字化协作新趋势
</p>
</div>
<p class="mt-6 text-base text-gray-800">
从这里开始工作更智能更高效
从这里开始一切数字化系统化智能化自动化
</p>
<div class="mt-6 space-y-6">
<!-- 步骤 1 - 账户已创建 -->
@ -155,7 +155,7 @@
<div v-else class="rounded-md">
<div class="flex items-center space-x-2">
<TextInsideCircle>3</TextInsideCircle>
<div class="text-base font-medium">创建您的第一个智能工作空间</div>
<div class="text-base font-medium">创建您的第一个数字化平台</div>
</div>
<Button class="ml-7 mt-4" :route="'/sites/new'">

View File

@ -66,6 +66,16 @@
</Button>
</template>
</ListItem>
<ListItem
title="重置密码"
subtitle="更改您的账户登录密码"
>
<template #actions>
<Button @click="showResetPasswordDialog = true">
重置密码
</Button>
</template>
</ListItem>
<ListItem
:title="teamEnabled ? '禁用账户' : '启用账户'"
:subtitle="
@ -188,6 +198,10 @@
v-model="showAddPrepaidCreditsDialog"
@success="reloadAccount"
/>
<ResetPasswordDialog
v-if="showResetPasswordDialog"
v-model="showResetPasswordDialog"
/>
</Card>
<TFADialog v-model="show2FADialog" />
</template>
@ -198,6 +212,7 @@ import { defineAsyncComponent, h } from 'vue';
import FileUploader from '@/components/FileUploader.vue';
import { confirmDialog, renderDialog } from '../../../utils/components';
import TFADialog from './TFADialog.vue';
import ResetPasswordDialog from './ResetPasswordDialog.vue';
import router from '../../../router';
import AddPrepaidCreditsDialog from '../../billing/AddPrepaidCreditsDialog.vue';
@ -205,12 +220,14 @@ export default {
name: 'AccountProfile',
components: {
TFADialog,
ResetPasswordDialog,
FileUploader,
AddPrepaidCreditsDialog,
},
data() {
return {
show2FADialog: false,
showResetPasswordDialog: false,
disableAccount2FACode: '',
showProfileEditDialog: false,
showEnableAccountDialog: false,

View File

@ -0,0 +1,302 @@
<template>
<Dialog v-model="showDialog" :options="{ title: '重置密码' }">
<template #body-content>
<div class="space-y-4">
<div v-if="!isResetMode" class="space-y-4">
<FormControl
v-model="oldPassword"
type="password"
:fieldtype="'Password'"
:label="'当前密码'"
:fieldname="'old_password'"
:reqd="1"
:placeholder="'请输入当前密码'"
/>
</div>
<div class="space-y-4">
<FormControl
v-model="newPassword"
type="password"
:fieldtype="'Password'"
:label="'新密码'"
:fieldname="'new_password'"
:reqd="1"
:placeholder="'请输入新密码'"
/>
<div v-if="passwordStrengthMessage" class="text-sm" :class="passwordStrengthClass">
{{ passwordStrengthMessage }}
</div>
<FormControl
v-model="confirmPassword"
type="password"
:fieldtype="'Password'"
:label="'确认密码'"
:fieldname="'confirm_password'"
:reqd="1"
:placeholder="'请再次输入新密码'"
/>
<div v-if="passwordMismatchMessage" class="text-sm text-red-600">
{{ passwordMismatchMessage }}
</div>
</div>
</div>
<ErrorMessage class="mt-2" :message="error" />
</template>
<template #actions>
<div class="flex gap-2 w-full">
<Button
variant="outline"
class="flex-1"
@click="hide"
>
取消
</Button>
<Button
variant="solid"
class="flex-1"
:loading="isLoading"
:disabled="!isFormValid"
@click="onConfirm"
>
确认
</Button>
</div>
</template>
</Dialog>
</template>
<script>
import { ErrorMessage, FormControl } from 'jingrow-ui';
import { toast } from 'vue-sonner';
export default {
name: 'ResetPasswordDialog',
props: {
isResetMode: {
type: Boolean,
default: false
},
modelValue: {
type: Boolean,
default: false
}
},
emits: ['update:modelValue'],
expose: ['show', 'hide'],
data() {
return {
error: null,
isLoading: false,
oldPassword: '',
newPassword: '',
confirmPassword: '',
passwordStrengthMessage: '',
passwordStrengthClass: '',
passwordMismatchMessage: '',
passwordStrengthTimeout: null
};
},
computed: {
showDialog: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
}
},
isFormValid() {
const hasNewPassword = this.newPassword && this.newPassword.length > 0;
const hasConfirmPassword = this.confirmPassword && this.confirmPassword.length > 0;
const passwordsMatch = this.newPassword === this.confirmPassword;
const passwordsDifferent = this.oldPassword !== this.newPassword;
if (this.isResetMode) {
return hasNewPassword && hasConfirmPassword && passwordsMatch;
} else {
return this.oldPassword && hasNewPassword && hasConfirmPassword && passwordsMatch && passwordsDifferent;
}
}
},
components: { FormControl, ErrorMessage },
watch: {
newPassword() {
this.checkPasswordStrength();
},
confirmPassword() {
this.checkPasswordMismatch();
},
oldPassword() {
this.checkPasswordMismatch();
}
},
methods: {
checkPasswordStrength() {
if (this.passwordStrengthTimeout) {
clearTimeout(this.passwordStrengthTimeout);
}
this.passwordStrengthTimeout = setTimeout(() => {
if (!this.newPassword) {
this.passwordStrengthMessage = '';
return;
}
// API
this.$resources.checkPasswordStrength.submit({
new_password: this.newPassword,
old_password: this.oldPassword
});
}, 200);
},
checkPasswordMismatch() {
if (this.oldPassword && this.newPassword && this.oldPassword === this.newPassword) {
this.passwordMismatchMessage = '新密码不能与当前密码相同';
this.passwordStrengthMessage = '';
} else if (this.confirmPassword && this.newPassword !== this.confirmPassword) {
this.passwordMismatchMessage = '两次输入的密码不一致';
this.passwordStrengthMessage = '';
} else {
this.passwordMismatchMessage = '';
}
},
async onConfirm() {
this.error = null;
if (!this.validateForm()) {
return;
}
this.isLoading = true;
try {
await this.$resources.updatePassword.submit({
old_password: this.oldPassword,
new_password: this.newPassword,
confirm_password: this.confirmPassword,
logout_all_sessions: 1
});
} catch (error) {
this.error = error.message || '密码更新失败';
} finally {
this.isLoading = false;
}
},
validateForm() {
if (!this.isResetMode && !this.oldPassword) {
this.error = '请输入当前密码';
return false;
}
if (!this.newPassword) {
this.error = '请输入新密码';
return false;
}
if (this.newPassword !== this.confirmPassword) {
this.error = '两次输入的密码不一致';
return false;
}
if (!this.isResetMode && this.oldPassword === this.newPassword) {
this.error = '新密码不能与当前密码相同';
return false;
}
return true;
},
show() {
this.showDialog = true;
this.resetForm();
},
hide() {
this.showDialog = false;
this.resetForm();
},
resetForm() {
this.oldPassword = '';
this.newPassword = '';
this.confirmPassword = '';
this.error = null;
this.passwordStrengthMessage = '';
this.passwordStrengthClass = '';
this.passwordMismatchMessage = '';
if (this.passwordStrengthTimeout) {
clearTimeout(this.passwordStrengthTimeout);
this.passwordStrengthTimeout = null;
}
}
},
resources: {
checkPasswordStrength() {
return {
url: 'jingrow.core.pagetype.user.user.test_password_strength',
params: {
old_password: this.oldPassword,
new_password: this.newPassword
},
onSuccess(data) {
if (data.message) {
const score = data.message.score;
const feedback = data.message.feedback;
if (feedback.password_policy_validation_passed) {
this.passwordStrengthMessage = '密码强度良好 👍';
this.passwordStrengthClass = 'text-green-600';
} else {
let message = [];
if (feedback.suggestions && feedback.suggestions.length) {
message = message.concat(feedback.suggestions);
} else if (feedback.warning) {
message.push(feedback.warning);
}
message.push('提示:密码应包含符号、数字和大写字母');
this.passwordStrengthMessage = message.join(' ');
this.passwordStrengthClass = 'text-red-600';
}
}
},
onError() {
this.passwordStrengthMessage = '';
}
};
},
updatePassword() {
return {
url: 'jingrow.core.pagetype.user.user.update_password',
onSuccess(data) {
// 5
toast.success('密码更新成功', {
duration: 3000,
description: '您的密码已更新'
});
//
this.hide();
//
if (data.message) {
setTimeout(() => {
window.location.href = data.message;
}, 1000);
}
},
onError(error) {
if (error.status === 401) {
this.error = '当前密码不正确';
} else {
this.error = error.message || '密码更新失败';
}
}
};
}
}
};
</script>

View File

@ -202,11 +202,16 @@
<FormControl
label="手机号"
type="tel"
placeholder="您的手机号码"
placeholder="请输入您的手机号码"
class="mt-4"
v-model="phoneNumber"
autocomplete="tel"
required
@blur="validatePhoneNumber"
/>
<div v-if="phoneNumberFormatError" class="mt-2 text-sm text-red-600">
请输入正确的手机号码格式
</div>
<FormControl
label="密码"
type="password"
@ -248,7 +253,7 @@
:loading="$resources.signupWithUsername.loading"
variant="solid"
type="submit"
:disabled="passwordMismatch && confirmPassword"
:disabled="(passwordMismatch && confirmPassword) || phoneNumberFormatError"
>
注册
</Button>
@ -435,6 +440,7 @@ export default {
passwordStrengthClass: '',
passwordStrengthText: '',
passwordStrengthTextClass: '',
phoneNumberFormatError: false,
};
},
mounted() {
@ -665,6 +671,14 @@ export default {
toast.error('用户名不能为空');
return;
}
if (!this.phoneNumber) {
toast.error('手机号不能为空');
return;
}
if (!this.isPhoneNumberValid) {
toast.error('请输入正确的手机号码格式');
return;
}
if (!this.signupPassword) {
toast.error('密码不能为空');
return;
@ -805,6 +819,14 @@ export default {
}
});
},
validatePhoneNumber() {
//
if (this.phoneNumber && this.phoneNumber.length > 0) {
this.phoneNumberFormatError = !this.isPhoneNumberValid;
} else {
this.phoneNumberFormatError = false;
}
},
},
computed: {
error() {
@ -928,6 +950,11 @@ export default {
/[A-Z]/.test(this.signupPassword) &&
/[0-9]/.test(this.signupPassword);
},
isPhoneNumberValid() {
// 1113-9
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(this.phoneNumber);
},
},
};
</script>