前端个人资料页面增加重置密码按钮
This commit is contained in:
parent
baf5183079
commit
25e196b38e
@ -3,11 +3,11 @@
|
|||||||
<div class="prose prose-sm max-w-none">
|
<div class="prose prose-sm max-w-none">
|
||||||
<h1 class="text-2xl font-semibold">欢迎来到 今果 Jingrow</h1>
|
<h1 class="text-2xl font-semibold">欢迎来到 今果 Jingrow</h1>
|
||||||
<p>
|
<p>
|
||||||
今果 Jingrow是下一代智能工作平台,以"一切皆页面"的革新理念,让您像搭积木一样灵活配置业务系统。通过AI驱动的智能工作流、可视化数据建模、实时协作与权限管理,为您打造专属的工作空间。支持团队协作,让复杂的管理任务变得优雅而高效。
|
Jingrow是一站式通用数字化平台,以"一切皆页面"的革新理念,帮助您快速构建从个人工作到团队协作的一站式数字化解决方案。通过AI Agent智能体、可视化工作流编排、零代码可视化数据建模、自动化任务、团队协作、角色与权限管理、文件库、知识库、笔记,会议,待办事项,通知、智能报表等模块,引领数字化协作新趋势。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-6 text-base text-gray-800">
|
<p class="mt-6 text-base text-gray-800">
|
||||||
从这里开始,让工作更智能、更高效。
|
从这里开始,让一切数字化、系统化、智能化、自动化。
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6 space-y-6">
|
<div class="mt-6 space-y-6">
|
||||||
<!-- 步骤 1 - 账户已创建 -->
|
<!-- 步骤 1 - 账户已创建 -->
|
||||||
@ -155,7 +155,7 @@
|
|||||||
<div v-else class="rounded-md">
|
<div v-else class="rounded-md">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<TextInsideCircle>3</TextInsideCircle>
|
<TextInsideCircle>3</TextInsideCircle>
|
||||||
<div class="text-base font-medium">创建您的第一个智能工作空间</div>
|
<div class="text-base font-medium">创建您的第一个数字化平台</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button class="ml-7 mt-4" :route="'/sites/new'">
|
<Button class="ml-7 mt-4" :route="'/sites/new'">
|
||||||
|
|||||||
@ -66,6 +66,16 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
title="重置密码"
|
||||||
|
subtitle="更改您的账户登录密码"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<Button @click="showResetPasswordDialog = true">
|
||||||
|
重置密码
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
:title="teamEnabled ? '禁用账户' : '启用账户'"
|
:title="teamEnabled ? '禁用账户' : '启用账户'"
|
||||||
:subtitle="
|
:subtitle="
|
||||||
@ -188,6 +198,10 @@
|
|||||||
v-model="showAddPrepaidCreditsDialog"
|
v-model="showAddPrepaidCreditsDialog"
|
||||||
@success="reloadAccount"
|
@success="reloadAccount"
|
||||||
/>
|
/>
|
||||||
|
<ResetPasswordDialog
|
||||||
|
v-if="showResetPasswordDialog"
|
||||||
|
v-model="showResetPasswordDialog"
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<TFADialog v-model="show2FADialog" />
|
<TFADialog v-model="show2FADialog" />
|
||||||
</template>
|
</template>
|
||||||
@ -198,6 +212,7 @@ import { defineAsyncComponent, h } from 'vue';
|
|||||||
import FileUploader from '@/components/FileUploader.vue';
|
import FileUploader from '@/components/FileUploader.vue';
|
||||||
import { confirmDialog, renderDialog } from '../../../utils/components';
|
import { confirmDialog, renderDialog } from '../../../utils/components';
|
||||||
import TFADialog from './TFADialog.vue';
|
import TFADialog from './TFADialog.vue';
|
||||||
|
import ResetPasswordDialog from './ResetPasswordDialog.vue';
|
||||||
import router from '../../../router';
|
import router from '../../../router';
|
||||||
import AddPrepaidCreditsDialog from '../../billing/AddPrepaidCreditsDialog.vue';
|
import AddPrepaidCreditsDialog from '../../billing/AddPrepaidCreditsDialog.vue';
|
||||||
|
|
||||||
@ -205,12 +220,14 @@ export default {
|
|||||||
name: 'AccountProfile',
|
name: 'AccountProfile',
|
||||||
components: {
|
components: {
|
||||||
TFADialog,
|
TFADialog,
|
||||||
|
ResetPasswordDialog,
|
||||||
FileUploader,
|
FileUploader,
|
||||||
AddPrepaidCreditsDialog,
|
AddPrepaidCreditsDialog,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
show2FADialog: false,
|
show2FADialog: false,
|
||||||
|
showResetPasswordDialog: false,
|
||||||
disableAccount2FACode: '',
|
disableAccount2FACode: '',
|
||||||
showProfileEditDialog: false,
|
showProfileEditDialog: false,
|
||||||
showEnableAccountDialog: 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
|
<FormControl
|
||||||
label="手机号"
|
label="手机号"
|
||||||
type="tel"
|
type="tel"
|
||||||
placeholder="您的手机号码"
|
placeholder="请输入您的手机号码"
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
v-model="phoneNumber"
|
v-model="phoneNumber"
|
||||||
autocomplete="tel"
|
autocomplete="tel"
|
||||||
|
required
|
||||||
|
@blur="validatePhoneNumber"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="phoneNumberFormatError" class="mt-2 text-sm text-red-600">
|
||||||
|
请输入正确的手机号码格式
|
||||||
|
</div>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="密码"
|
label="密码"
|
||||||
type="password"
|
type="password"
|
||||||
@ -248,7 +253,7 @@
|
|||||||
:loading="$resources.signupWithUsername.loading"
|
:loading="$resources.signupWithUsername.loading"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="passwordMismatch && confirmPassword"
|
:disabled="(passwordMismatch && confirmPassword) || phoneNumberFormatError"
|
||||||
>
|
>
|
||||||
注册
|
注册
|
||||||
</Button>
|
</Button>
|
||||||
@ -435,6 +440,7 @@ export default {
|
|||||||
passwordStrengthClass: '',
|
passwordStrengthClass: '',
|
||||||
passwordStrengthText: '',
|
passwordStrengthText: '',
|
||||||
passwordStrengthTextClass: '',
|
passwordStrengthTextClass: '',
|
||||||
|
phoneNumberFormatError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -665,6 +671,14 @@ export default {
|
|||||||
toast.error('用户名不能为空');
|
toast.error('用户名不能为空');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this.phoneNumber) {
|
||||||
|
toast.error('手机号不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isPhoneNumberValid) {
|
||||||
|
toast.error('请输入正确的手机号码格式');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.signupPassword) {
|
if (!this.signupPassword) {
|
||||||
toast.error('密码不能为空');
|
toast.error('密码不能为空');
|
||||||
return;
|
return;
|
||||||
@ -805,6 +819,14 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
validatePhoneNumber() {
|
||||||
|
// 失焦时验证手机号格式
|
||||||
|
if (this.phoneNumber && this.phoneNumber.length > 0) {
|
||||||
|
this.phoneNumberFormatError = !this.isPhoneNumberValid;
|
||||||
|
} else {
|
||||||
|
this.phoneNumberFormatError = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
error() {
|
error() {
|
||||||
@ -928,6 +950,11 @@ export default {
|
|||||||
/[A-Z]/.test(this.signupPassword) &&
|
/[A-Z]/.test(this.signupPassword) &&
|
||||||
/[0-9]/.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>
|
</script>
|
||||||
Loading…
x
Reference in New Issue
Block a user