前端个人资料页面增加重置密码按钮
This commit is contained in:
parent
baf5183079
commit
25e196b38e
@ -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'">
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
@ -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() {
|
||||
// 中国大陆手机号验证:11位数字,以1开头,第二位为3-9
|
||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
||||
return phoneRegex.test(this.phoneNumber);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Loading…
x
Reference in New Issue
Block a user