u
This commit is contained in:
1 parent
9a35a56a97
commit
1fea531918
16 files changed
+1026
-184
No files matched your search
+2
-1
@@ -6,5 +6,6 @@ package cn.link56.auth.code.model;
|
|||||||
public enum AuthCodePurpose {
|
public enum AuthCodePurpose {
|
||||||
REGISTER, // 注册
|
REGISTER, // 注册
|
||||||
LOGIN, // 登录
|
LOGIN, // 登录
|
||||||
RESET_PASSWORD // 找回密码
|
RESET_PASSWORD, // 找回密码
|
||||||
|
UPDATE_EMAIL // 更新邮箱
|
||||||
}
|
}
|
||||||
+5
-2
@@ -23,12 +23,15 @@ public class EmailAuthCodeSender {
|
|||||||
if (purpose.equals(AuthCodePurpose.LOGIN)){
|
if (purpose.equals(AuthCodePurpose.LOGIN)){
|
||||||
subject = "登录";
|
subject = "登录";
|
||||||
templateHtml = "Login.html";
|
templateHtml = "Login.html";
|
||||||
}else if(purpose.equals(AuthCodePurpose.REGISTER)){
|
} else if (purpose.equals(AuthCodePurpose.REGISTER)){
|
||||||
subject = "注册";
|
subject = "注册";
|
||||||
templateHtml = "Register.html";
|
templateHtml = "Register.html";
|
||||||
}else if(purpose.equals(AuthCodePurpose.RESET_PASSWORD)){
|
} else if (purpose.equals(AuthCodePurpose.RESET_PASSWORD)){
|
||||||
subject = "找回密码";
|
subject = "找回密码";
|
||||||
templateHtml = "ForgetPassword.html";
|
templateHtml = "ForgetPassword.html";
|
||||||
|
} else if (purpose.equals(AuthCodePurpose.UPDATE_EMAIL)){
|
||||||
|
subject = "更新认证邮箱";
|
||||||
|
templateHtml = "UpdateEmail.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.put("Code",code);
|
payload.put("Code",code);
|
||||||
|
|||||||
@@ -480,7 +480,11 @@
|
|||||||
<td class="content-cell">
|
<td class="content-cell">
|
||||||
<div class="f-fallback">
|
<div class="f-fallback">
|
||||||
<h1>您好</h1>
|
<h1>您好</h1>
|
||||||
<p>您正在登录我们的 link56 平台,系统已为您生成了登录验证码。<strong>该验证码仅在 10 分钟内有效。</strong></p>
|
<p>
|
||||||
|
您正在 <strong>link56</strong> 平台进行<strong>登录</strong>操作,
|
||||||
|
系统已为您生成邮箱验证码。
|
||||||
|
<strong>该验证码仅在 10 分钟内有效。</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
<p class="body-action">
|
<p class="body-action">
|
||||||
[[${Code}]]
|
[[${Code}]]
|
||||||
|
|||||||
@@ -480,7 +480,12 @@
|
|||||||
<td class="content-cell">
|
<td class="content-cell">
|
||||||
<div class="f-fallback">
|
<div class="f-fallback">
|
||||||
<h1>您好</h1>
|
<h1>您好</h1>
|
||||||
<p>您正在注册我们的 link56 平台,系统已为您生成了注册验证码。<strong>该验证码仅在 10 分钟内有效。</strong></p>
|
<p>
|
||||||
|
您正在 <strong>link56</strong> 平台进行<strong>注册</strong>操作,
|
||||||
|
系统已为您生成邮箱验证码。
|
||||||
|
<strong>该验证码仅在 10 分钟内有效。</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
<p class="body-action">
|
<p class="body-action">
|
||||||
[[${Code}]]
|
[[${Code}]]
|
||||||
|
|||||||
@@ -0,0 +1,526 @@
|
|||||||
|
<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="x-apple-disable-message-reformatting" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<meta name="supported-color-schemes" content="light dark" />
|
||||||
|
<title></title>
|
||||||
|
<style type="text/css" rel="stylesheet" media="all">
|
||||||
|
/* Base ------------------------------ */
|
||||||
|
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #3869D4;
|
||||||
|
}
|
||||||
|
|
||||||
|
a img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preheader {
|
||||||
|
display: none !important;
|
||||||
|
visibility: hidden;
|
||||||
|
mso-hide: all;
|
||||||
|
font-size: 1px;
|
||||||
|
line-height: 1px;
|
||||||
|
max-height: 0;
|
||||||
|
max-width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Type ------------------------------ */
|
||||||
|
|
||||||
|
body,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
blockquote {
|
||||||
|
margin: .4em 0 1.1875em;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.625;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.sub {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities ------------------------------ */
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-margin-bottom-none {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons ------------------------------ */
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: #3869D4;
|
||||||
|
border-top: 10px solid #3869D4;
|
||||||
|
border-right: 18px solid #3869D4;
|
||||||
|
border-bottom: 10px solid #3869D4;
|
||||||
|
border-left: 18px solid #3869D4;
|
||||||
|
display: inline-block;
|
||||||
|
color: #FFF;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--green {
|
||||||
|
background-color: #22BC66;
|
||||||
|
border-top: 10px solid #22BC66;
|
||||||
|
border-right: 18px solid #22BC66;
|
||||||
|
border-bottom: 10px solid #22BC66;
|
||||||
|
border-left: 18px solid #22BC66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--red {
|
||||||
|
background-color: #FF6136;
|
||||||
|
border-top: 10px solid #FF6136;
|
||||||
|
border-right: 18px solid #FF6136;
|
||||||
|
border-bottom: 10px solid #FF6136;
|
||||||
|
border-left: 18px solid #FF6136;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 500px) {
|
||||||
|
.button {
|
||||||
|
width: 100% !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attribute list ------------------------------ */
|
||||||
|
|
||||||
|
.attributes {
|
||||||
|
margin: 0 0 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes_content {
|
||||||
|
background-color: #F4F4F7;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes_item {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Related Items ------------------------------ */
|
||||||
|
|
||||||
|
.related {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 25px 0 0 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related_item {
|
||||||
|
padding: 10px 0;
|
||||||
|
color: #CBCCCF;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related_item-title {
|
||||||
|
display: block;
|
||||||
|
margin: .5em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related_item-thumb {
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related_heading {
|
||||||
|
border-top: 1px solid #CBCCCF;
|
||||||
|
text-align: center;
|
||||||
|
padding: 25px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Discount Code ------------------------------ */
|
||||||
|
|
||||||
|
.discount {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 24px;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
background-color: #F4F4F7;
|
||||||
|
border: 2px dashed #CBCCCF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount_heading {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount_body {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Social Icons ------------------------------ */
|
||||||
|
|
||||||
|
.social {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social td {
|
||||||
|
padding: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social_icon {
|
||||||
|
height: 20px;
|
||||||
|
margin: 0 8px 10px 8px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data table ------------------------------ */
|
||||||
|
|
||||||
|
.purchase {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 35px 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purchase_content {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 25px 0 0 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purchase_item {
|
||||||
|
padding: 10px 0;
|
||||||
|
color: #51545E;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purchase_heading {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #EAEAEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purchase_heading p {
|
||||||
|
margin: 0;
|
||||||
|
color: #85878E;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purchase_footer {
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #EAEAEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purchase_total {
|
||||||
|
margin: 0;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purchase_total--label {
|
||||||
|
padding: 0 15px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #F2F4F6;
|
||||||
|
color: #51545E;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #51545E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
background-color: #F2F4F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-content {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Masthead ----------------------- */
|
||||||
|
|
||||||
|
.email-masthead {
|
||||||
|
padding: 25px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-masthead_logo {
|
||||||
|
width: 94px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-masthead_name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #A8AAAF;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 0 1px 0 white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body ------------------------------ */
|
||||||
|
|
||||||
|
.email-body {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body_inner {
|
||||||
|
width: 570px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
-premailer-width: 570px;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer {
|
||||||
|
width: 570px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
-premailer-width: 570px;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer p {
|
||||||
|
color: #A8AAAF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-action {
|
||||||
|
width: 100%;
|
||||||
|
margin: 30px auto;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 42px;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-sub {
|
||||||
|
margin-top: 25px;
|
||||||
|
padding-top: 25px;
|
||||||
|
border-top: 1px solid #EAEAEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-cell {
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Media Queries ------------------------------ */
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
|
||||||
|
.email-body_inner,
|
||||||
|
.email-footer {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|
||||||
|
body,
|
||||||
|
.email-body,
|
||||||
|
.email-body_inner,
|
||||||
|
.email-content,
|
||||||
|
.email-wrapper,
|
||||||
|
.email-masthead,
|
||||||
|
.email-footer {
|
||||||
|
background-color: #333333 !important;
|
||||||
|
color: #FFF !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
blockquote,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
span,
|
||||||
|
.purchase_item {
|
||||||
|
color: #FFF !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes_content,
|
||||||
|
.discount {
|
||||||
|
background-color: #222 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-masthead_name {
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
supported-color-schemes: light dark;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--[if mso]>
|
||||||
|
<style type="text/css">
|
||||||
|
.f-fallback {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<span class="preheader">Use this link to reset your password. The link is only valid for 24 hours.</span>
|
||||||
|
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td class="email-masthead">
|
||||||
|
<a href="https://example.com" class="f-fallback email-masthead_name">
|
||||||
|
link56
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Email Body -->
|
||||||
|
<tr>
|
||||||
|
<td class="email-body" width="570" cellpadding="0" cellspacing="0">
|
||||||
|
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0"
|
||||||
|
role="presentation">
|
||||||
|
<!-- Body content -->
|
||||||
|
<tr>
|
||||||
|
<td class="content-cell">
|
||||||
|
<div class="f-fallback">
|
||||||
|
<h1>您好</h1>
|
||||||
|
<p>
|
||||||
|
您正在 <strong>link56</strong> 平台进行<strong>认证邮箱修改</strong>操作,
|
||||||
|
系统已为您生成邮箱验证码。
|
||||||
|
<strong>该验证码仅在 10 分钟内有效。</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="body-action">
|
||||||
|
[[${Code}]]
|
||||||
|
</p>
|
||||||
|
<p>如果不是您本人操作,请忽略此邮件,或如有疑问,请 <a
|
||||||
|
href="{{SupportUrl}}">联系客服</a>。</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
感谢,<br>
|
||||||
|
link56 团队
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0"
|
||||||
|
role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td class="content-cell" align="center">
|
||||||
|
<p class="f-fallback sub align-center">
|
||||||
|
宁波江东远通计算机有限公司
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
import type { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { authCodeSendApi } from '@/request/api/auth/code'
|
||||||
|
import { validateEmail } from '@/utils/validate'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
type: String,
|
||||||
|
default: '发送验证码',
|
||||||
|
},
|
||||||
|
countdownSeconds: {
|
||||||
|
type: Number,
|
||||||
|
default: 60,
|
||||||
|
},
|
||||||
|
channel: {
|
||||||
|
type: String as PropType<AuthCodeChannel>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
purpose: {
|
||||||
|
type: String as PropType<AuthCodePurpose>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: string): void
|
||||||
|
(e: 'sendSuccess'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const code = ref(props.modelValue)
|
||||||
|
watch(() => props.modelValue, val => code.value = val)
|
||||||
|
watch(code, val => emit('update:modelValue', val))
|
||||||
|
|
||||||
|
const isEmailValid = computed(() => validateEmail(props.email))
|
||||||
|
|
||||||
|
const sending = ref(false)
|
||||||
|
const countdown = ref(0)
|
||||||
|
let timer: number | undefined
|
||||||
|
|
||||||
|
const inputClass = computed(() => (sending.value || countdown.value > 0 || !isEmailValid.value) ? 'disabled' : '')
|
||||||
|
|
||||||
|
function startCountdown(seconds = props.countdownSeconds || 60) {
|
||||||
|
countdown.value = seconds
|
||||||
|
timer = window.setInterval(() => {
|
||||||
|
countdown.value -= 1
|
||||||
|
if (countdown.value <= 0) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = undefined
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSendCode() {
|
||||||
|
if (sending.value || countdown.value > 0 || !isEmailValid.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
sending.value = true
|
||||||
|
try {
|
||||||
|
const res = await authCodeSendApi({
|
||||||
|
channel: props.channel,
|
||||||
|
purpose: props.purpose,
|
||||||
|
target: props.email,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.isSuccess) {
|
||||||
|
ElMessage.success('发送成功')
|
||||||
|
emit('sendSuccess')
|
||||||
|
startCountdown()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ElMessage.error(res.message || '发送失败,请检查邮箱是否正确或稍后再试!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
ElMessage.error('发送失败,请稍后再试')
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
sending.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-input
|
||||||
|
v-model="code"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:class="inputClass"
|
||||||
|
class="code"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button
|
||||||
|
:disabled="sending || countdown > 0 || !isEmailValid"
|
||||||
|
@click="handleSendCode"
|
||||||
|
>
|
||||||
|
<template v-if="sending">
|
||||||
|
发送中...
|
||||||
|
</template>
|
||||||
|
<template v-else-if="countdown > 0">
|
||||||
|
{{ countdown }}s
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ buttonText }}
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.code {
|
||||||
|
:deep(.el-input-group__append) {
|
||||||
|
background-color: #409eff;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
:deep(.el-input-group__append) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { CompanyUser, QueryCompanyUserParams } from '@/types/company/user'
|
import type { AddCompanyUserParams, CompanyUser, QueryCompanyUserParams } from '@/types/company/user'
|
||||||
import { httpGet, httpPostJson } from '@/request/utils'
|
import { httpGet, httpPostJson } from '@/request/utils'
|
||||||
|
|
||||||
export function queryCompanyUserApi(params: QueryCompanyUserParams) {
|
export function queryCompanyUserApi(params: QueryCompanyUserParams) {
|
||||||
return httpPostJson<BasePageResult<CompanyUser>>('/company/user/query', params)
|
return httpPostJson<BasePageResult<CompanyUser>>('/company/user/query', params)
|
||||||
}
|
}
|
||||||
|
export function addCompanyUserApi(params: AddCompanyUserParams) {
|
||||||
|
return httpPostJson<BasePageResult<void>>('/company/user/add', params)
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/info/user',
|
path: '/settings/info/user',
|
||||||
component: () => import('@/views/settings/info/user.vue'),
|
component: () => import('@/views/settings/info/user/index.vue'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export enum AuthCodePurpose {
|
|||||||
REGISTER = 'REGISTER',
|
REGISTER = 'REGISTER',
|
||||||
RESET_PASSWORD = 'RESET_PASSWORD',
|
RESET_PASSWORD = 'RESET_PASSWORD',
|
||||||
LOGIN = 'LOGIN',
|
LOGIN = 'LOGIN',
|
||||||
|
UPDATE_EMAIL = 'UPDATE_EMAIL',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthCodeSendParams {
|
export interface AuthCodeSendParams {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
AnchorMenu: typeof import('./../components/anchor-menu/index.vue')['default']
|
AnchorMenu: typeof import('./../components/anchor-menu/index.vue')['default']
|
||||||
AreaSelect: typeof import('./../components/AreaSelect.vue')['default']
|
AreaSelect: typeof import('./../components/AreaSelect.vue')['default']
|
||||||
Directory: typeof import('./../components/wang-editor/modules/directory.vue')['default']
|
Directory: typeof import('./../components/wang-editor/modules/directory.vue')['default']
|
||||||
|
EmailCodeInput: typeof import('./../components/EmailCodeInput.vue')['default']
|
||||||
HelloWorld: typeof import('./../components/HelloWorld.vue')['default']
|
HelloWorld: typeof import('./../components/HelloWorld.vue')['default']
|
||||||
ICarbonAccessibility: typeof import('~icons/carbon/accessibility')['default']
|
ICarbonAccessibility: typeof import('~icons/carbon/accessibility')['default']
|
||||||
'IFa7Solid:userShield': typeof import('~icons/fa7-solid/user-shield')['default']
|
'IFa7Solid:userShield': typeof import('~icons/fa7-solid/user-shield')['default']
|
||||||
|
|||||||
@@ -19,3 +19,19 @@ export interface CompanyUser {
|
|||||||
phone: string
|
phone: string
|
||||||
canuse: string
|
canuse: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CompanyUserForm extends CompanyUser {
|
||||||
|
password: string
|
||||||
|
emailCode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddCompanyUserParams {
|
||||||
|
companyId: number
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
|
||||||
|
name: string
|
||||||
|
position: string
|
||||||
|
phone: string
|
||||||
|
canuse: string
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import type { FormInstance } from 'element-plus'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { computed, reactive, ref } from 'vue'
|
import { computed, reactive, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import EmailCodeInput from '@/components/EmailCodeInput.vue'
|
||||||
import { loginByEmailCodeApi, loginByPasswordApi } from '@/request/api/auth'
|
import { loginByEmailCodeApi, loginByPasswordApi } from '@/request/api/auth'
|
||||||
import { authCodeSendApi } from '@/request/api/auth/code'
|
import { authCodeSendApi } from '@/request/api/auth/code'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
@@ -52,22 +53,6 @@ const carouselList = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// --- 发送验证码状态 & 倒计时 ---
|
|
||||||
const sending = ref(false)
|
|
||||||
const countdown = ref(0)
|
|
||||||
let timer: number | undefined
|
|
||||||
|
|
||||||
function startCountdown(seconds = 60) {
|
|
||||||
countdown.value = seconds
|
|
||||||
timer = window.setInterval(() => {
|
|
||||||
countdown.value -= 1
|
|
||||||
if (countdown.value <= 0) {
|
|
||||||
clearInterval(timer)
|
|
||||||
timer = undefined
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const form = reactive<{
|
const form = reactive<{
|
||||||
email: string
|
email: string
|
||||||
password: string
|
password: string
|
||||||
@@ -116,39 +101,8 @@ const formRules = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEmailValid = computed(() => validateEmail(form.email))
|
|
||||||
|
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
|
|
||||||
function handleSendEmailCode() {
|
|
||||||
if (sending.value || countdown.value > 0 || !isEmailValid.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
sending.value = true
|
|
||||||
|
|
||||||
authCodeSendApi({
|
|
||||||
channel: AuthCodeChannel.EMAIL,
|
|
||||||
purpose: AuthCodePurpose.LOGIN,
|
|
||||||
target: form.email,
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.isSuccess) {
|
|
||||||
ElMessage.success('发送成功')
|
|
||||||
startCountdown()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sending.value = false
|
|
||||||
ElMessage.error(res.message || '发送失败,请检查邮箱是否正确或稍后再试!')
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
|
|
||||||
ElMessage.error('发送失败,请稍后再试')
|
|
||||||
sending.value = false
|
|
||||||
}).finally(() => {
|
|
||||||
sending.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submitForm(formEl: FormInstance | undefined) {
|
async function submitForm(formEl: FormInstance | undefined) {
|
||||||
if (!formEl)
|
if (!formEl)
|
||||||
return
|
return
|
||||||
@@ -301,30 +255,14 @@ function navigateTo(path: string) {
|
|||||||
<span class="forgot" @click="navigateTo('/auth/forget')">忘记密码?</span>
|
<span class="forgot" @click="navigateTo('/auth/forget')">忘记密码?</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<el-input
|
<EmailCodeInput
|
||||||
v-model="form.code"
|
v-model="form.code"
|
||||||
placeholder="请输入邮箱验证码"
|
|
||||||
class="email-code"
|
|
||||||
size="large"
|
size="large"
|
||||||
:class="(sending || countdown > 0 || !isEmailValid) ? 'disabled' : ''"
|
:email="form.email"
|
||||||
>
|
:channel="AuthCodeChannel.EMAIL"
|
||||||
<template #append>
|
:purpose="AuthCodePurpose.LOGIN"
|
||||||
<el-button
|
placeholder="请输入邮箱验证码"
|
||||||
:disabled="sending || countdown > 0 || !isEmailValid"
|
/>
|
||||||
@click="handleSendEmailCode"
|
|
||||||
>
|
|
||||||
<template v-if="sending">
|
|
||||||
发送中...
|
|
||||||
</template>
|
|
||||||
<template v-else-if="countdown > 0">
|
|
||||||
{{ countdown }}s
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
发送验证码
|
|
||||||
</template>
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-checkbox v-model="form.rememberMe" label="记住我" class="remeber" />
|
<el-checkbox v-model="form.rememberMe" label="记住我" class="remeber" />
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
import type { FormInstance } from 'element-plus'
|
import type { FormInstance } from 'element-plus'
|
||||||
import type { RegisterAdminForm } from '@/types/auth'
|
import type { RegisterAdminForm } from '@/types/auth'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { computed, onUnmounted, reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { authCodeSendApi, authCodeVerifyApi } from '@/request/api/auth/code'
|
import EmailCodeInput from '@/components/EmailCodeInput.vue'
|
||||||
|
|
||||||
|
import { authCodeVerifyApi } from '@/request/api/auth/code'
|
||||||
import { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
|
import { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
|
||||||
import { validateEmail, validatePhone } from '@/utils/validate'
|
import { validatePhone } from '@/utils/validate'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'prev'): void
|
(e: 'prev'): void
|
||||||
@@ -73,61 +75,6 @@ function phoneValidator(rule: any, value: any, callback: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEmailValid = computed(() => validateEmail(form.email))
|
|
||||||
|
|
||||||
// --- 发送验证码状态 & 倒计时 ---
|
|
||||||
const sending = ref(false)
|
|
||||||
const countdown = ref(0)
|
|
||||||
let timer: number | undefined
|
|
||||||
|
|
||||||
function startCountdown(seconds = 60) {
|
|
||||||
countdown.value = seconds
|
|
||||||
timer = window.setInterval(() => {
|
|
||||||
countdown.value -= 1
|
|
||||||
if (countdown.value <= 0) {
|
|
||||||
clearInterval(timer)
|
|
||||||
timer = undefined
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSendEmailCode() {
|
|
||||||
if (sending.value || countdown.value > 0 || !isEmailValid.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
sending.value = true
|
|
||||||
|
|
||||||
authCodeSendApi({
|
|
||||||
channel: AuthCodeChannel.EMAIL,
|
|
||||||
purpose: AuthCodePurpose.REGISTER,
|
|
||||||
target: form.email,
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.isSuccess) {
|
|
||||||
ElMessage.success('发送成功')
|
|
||||||
startCountdown()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sending.value = false
|
|
||||||
ElMessage.error(res.message || '发送失败,请检查邮箱是否正确或稍后再试!')
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
|
|
||||||
ElMessage.error('发送失败,请稍后再试')
|
|
||||||
sending.value = false
|
|
||||||
}).finally(() => {
|
|
||||||
sending.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (timer) {
|
|
||||||
clearInterval(timer)
|
|
||||||
timer = undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function handleSubmit(formEl: FormInstance | undefined) {
|
async function handleSubmit(formEl: FormInstance | undefined) {
|
||||||
if (!formEl)
|
if (!formEl)
|
||||||
return
|
return
|
||||||
@@ -206,28 +153,13 @@ function handlePrev() {
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="邮箱验证码" prop="emailCode">
|
<el-form-item label="邮箱验证码" prop="emailCode">
|
||||||
<el-input
|
<EmailCodeInput
|
||||||
v-model="form.emailCode" placeholder="请输入邮箱验证码"
|
v-model="form.emailCode"
|
||||||
class="email-code"
|
:email="form.email"
|
||||||
:class="(sending || countdown > 0 || !isEmailValid) ? 'disabled' : ''"
|
:channel="AuthCodeChannel.EMAIL"
|
||||||
>
|
:purpose="AuthCodePurpose.REGISTER"
|
||||||
<template #append>
|
placeholder="请输入邮箱验证码"
|
||||||
<el-button
|
/>
|
||||||
:disabled="sending || countdown > 0 || !isEmailValid"
|
|
||||||
@click="handleSendEmailCode"
|
|
||||||
>
|
|
||||||
<template v-if="sending">
|
|
||||||
发送中...
|
|
||||||
</template>
|
|
||||||
<template v-else-if="countdown > 0">
|
|
||||||
{{ countdown }}s
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
发送验证码
|
|
||||||
</template>
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -274,21 +206,6 @@ function handlePrev() {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-code {
|
|
||||||
:deep(.el-input-group__append) {
|
|
||||||
background-color: #409eff;
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
:deep(.el-input-group__append) {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
color: #c0c4cc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -95,12 +95,12 @@ async function handleSubmit(formEl: FormInstance | undefined) {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="所属行业" prop="industry">
|
<el-form-item label="所属行业" prop="industries">
|
||||||
<IndustrySelect v-model="form.industries" placeholder="请输入所属行业" />
|
<IndustrySelect v-model="form.industries" placeholder="请输入所属行业" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="公司地址" prop="address">
|
<el-form-item label="公司地址" prop="areas">
|
||||||
<AreaSelect
|
<AreaSelect
|
||||||
v-model="form.areas"
|
v-model="form.areas"
|
||||||
placeholder="请输入公司地址"
|
placeholder="请输入公司地址"
|
||||||
|
|||||||
+274
@@ -0,0 +1,274 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
import type { CompanyUser, CompanyUserForm } from '@/types/company/user'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import EmailCodeInput from '@/components/EmailCodeInput.vue'
|
||||||
|
import { authCodeVerifyApi } from '@/request/api/auth/code'
|
||||||
|
import { addCompanyUserApi } from '@/request/api/company/user'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
|
||||||
|
import { validatePhone } from '@/utils/validate'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
user: {
|
||||||
|
type: Object as PropType<CompanyUser | null>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const roleOptions = [
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
label: '系统管理员',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: '普通成员',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const originEmail = ref('')
|
||||||
|
|
||||||
|
const form = ref<CompanyUserForm>({
|
||||||
|
id: 0,
|
||||||
|
role: 1,
|
||||||
|
name: '',
|
||||||
|
position: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
canuse: '1',
|
||||||
|
password: '',
|
||||||
|
emailCode: '',
|
||||||
|
})
|
||||||
|
watch(
|
||||||
|
() => props.user,
|
||||||
|
(user) => {
|
||||||
|
if (!user)
|
||||||
|
return
|
||||||
|
|
||||||
|
form.value = { ...user, password: '', emailCode: '' }
|
||||||
|
originEmail.value = user.email
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
const isUpdate = computed(() => !!props.user && !!props.user.id)
|
||||||
|
|
||||||
|
const isEmailChanged = computed(() => {
|
||||||
|
return form.value.email !== originEmail.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const formRules = {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '名称不能为空', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
email: [
|
||||||
|
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
|
||||||
|
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] },
|
||||||
|
],
|
||||||
|
emailCode: [
|
||||||
|
{
|
||||||
|
validator: (rule: any, value: any, callback: any) => {
|
||||||
|
if (!isEmailChanged.value) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
callback(new Error('邮箱验证码不能为空'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
},
|
||||||
|
trigger: ['blur', 'change'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
validator: (rule: any, value: any, callback: any) => {
|
||||||
|
// 更新时允许不填
|
||||||
|
if (isUpdate.value && !value) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
callback(new Error('密码不能为空'))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
role: [
|
||||||
|
{ required: true, message: '角色不能为空', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
canuse: [
|
||||||
|
{ required: true, message: '启用不能为空', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
phone: [{ validator: phoneValidator, trigger: 'blur' }],
|
||||||
|
}
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
|
||||||
|
function phoneValidator(rule: any, value: any, callback: any) {
|
||||||
|
// 未填写:直接通过
|
||||||
|
if (!value) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validatePhone(value)) {
|
||||||
|
callback(new Error('手机号格式不正确'))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit(formEl: FormInstance | undefined) {
|
||||||
|
if (!formEl)
|
||||||
|
return
|
||||||
|
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (isEmailChanged.value) {
|
||||||
|
submitLoading.value = true
|
||||||
|
|
||||||
|
authCodeVerifyApi({
|
||||||
|
channel: AuthCodeChannel.EMAIL,
|
||||||
|
purpose: isUpdate.value ? AuthCodePurpose.UPDATE_EMAIL : AuthCodePurpose.REGISTER,
|
||||||
|
target: form.value.email,
|
||||||
|
code: form.value.emailCode,
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.isSuccess) {
|
||||||
|
addCompanyUserApi({
|
||||||
|
companyId: userStore.userInfo.company.id,
|
||||||
|
email: form.value.email,
|
||||||
|
password: form.value.password,
|
||||||
|
name: form.value.name,
|
||||||
|
position: form.value.position,
|
||||||
|
phone: form.value.phone,
|
||||||
|
canuse: form.value.canuse,
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.isSuccess) {
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ElMessage.error(res.message || '新增失败,请稍后再试')
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
ElMessage.error('新增失败,请稍后再试')
|
||||||
|
}).finally(() => {
|
||||||
|
submitLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
submitLoading.value = false
|
||||||
|
|
||||||
|
ElMessage.error(res.message || '验证码无效或已过期,请重新获取')
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
submitLoading.value = false
|
||||||
|
|
||||||
|
console.log(err)
|
||||||
|
ElMessage.error('邮箱验证码校验失败,请稍后再试')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('===验证失败===')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-drawer v-bind="$attrs" title="成员编辑" resizable>
|
||||||
|
<el-alert
|
||||||
|
title="提示"
|
||||||
|
style="margin-bottom: 24px;"
|
||||||
|
:closable="false"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>• 修改邮箱需要进行邮箱验证码验证</p>
|
||||||
|
<p>• 出于安全考虑,密码仅支持重置修改,无法查看原密码</p>
|
||||||
|
<p>• 角色无法更改</p>
|
||||||
|
</div>
|
||||||
|
</el-alert>
|
||||||
|
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="auto"
|
||||||
|
label-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item label="名称" prop="name">
|
||||||
|
<el-input v-model="form.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="职位" prop="position">
|
||||||
|
<el-input v-model="form.position" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="角色" prop="role">
|
||||||
|
<el-select v-model="form.role" disabled>
|
||||||
|
<el-option
|
||||||
|
v-for="item in roleOptions "
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="手机号" prop="phone">
|
||||||
|
<el-input v-model="form.phone" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="邮箱号" prop="email">
|
||||||
|
<el-input v-model="form.email" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="isEmailChanged"
|
||||||
|
label="邮箱验证码"
|
||||||
|
prop="emailCode"
|
||||||
|
>
|
||||||
|
<EmailCodeInput
|
||||||
|
v-model="form.emailCode"
|
||||||
|
:email="form.email"
|
||||||
|
:channel=" AuthCodeChannel.EMAIL"
|
||||||
|
:purpose="isUpdate ? AuthCodePurpose.UPDATE_EMAIL : AuthCodePurpose.REGISTER"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码" prop="password">
|
||||||
|
<el-input v-model="form.password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="启用" prop="canuse">
|
||||||
|
<el-switch v-model="form.canuse" active-value="1" inactive-value="0" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div style="flex: auto">
|
||||||
|
<el-button>
|
||||||
|
取消
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" :loading="submitLoading" @click="handleSubmit(formRef)">
|
||||||
|
确认
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
//
|
||||||
|
</style>
|
||||||
+25
-11
@@ -5,6 +5,7 @@ import { ElMessage } from 'element-plus'
|
|||||||
import { onMounted, reactive, ref } from 'vue'
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
import { queryCompanyUserApi } from '@/request/api/company/user'
|
import { queryCompanyUserApi } from '@/request/api/company/user'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
|
import EditDrawer from './components/EditDrawer.vue'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const activeCollapseNames = ref(['1'])
|
const activeCollapseNames = ref(['1'])
|
||||||
@@ -43,6 +44,9 @@ const canuseOptions = [
|
|||||||
]
|
]
|
||||||
const tableData = ref<CompanyUser[]>([])
|
const tableData = ref<CompanyUser[]>([])
|
||||||
|
|
||||||
|
const showEdit = ref(false)
|
||||||
|
const editUser = ref<CompanyUser | null>(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getCompanyUser()
|
getCompanyUser()
|
||||||
})
|
})
|
||||||
@@ -65,6 +69,11 @@ function getCompanyUser() {
|
|||||||
ElMessage.error('查询失败,请稍后再试')
|
ElMessage.error('查询失败,请稍后再试')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleShowEditDrawer(user: CompanyUser | null) {
|
||||||
|
editUser.value = user
|
||||||
|
showEdit.value = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -121,12 +130,8 @@ function getCompanyUser() {
|
|||||||
</el-collapse>
|
</el-collapse>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
<div class="table-title">
|
|
||||||
成员列表
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-actions">
|
<div class="table-actions">
|
||||||
<el-button type="primary" plain :icon="Plus">
|
<el-button type="primary" plain :icon="Plus" @click="handleShowEditDrawer(null)">
|
||||||
新增
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" plain :icon="Delete">
|
<el-button type="danger" plain :icon="Delete">
|
||||||
@@ -140,7 +145,19 @@ function getCompanyUser() {
|
|||||||
<div class="table-body-wrapper">
|
<div class="table-body-wrapper">
|
||||||
<el-table :data="tableData" border stripe>
|
<el-table :data="tableData" border stripe>
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column prop="name" label="名称" align="center" />
|
<el-table-column prop="name" label="名称" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.role === 1">
|
||||||
|
{{ scope.row.name }}
|
||||||
|
</template>
|
||||||
|
<div v-else>
|
||||||
|
<div>
|
||||||
|
{{ scope.row.name }}
|
||||||
|
</div>
|
||||||
|
<el-tag>超级管理员</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="position" label="职位" align="center" />
|
<el-table-column prop="position" label="职位" align="center" />
|
||||||
<el-table-column prop="email" label="邮箱" align="center" />
|
<el-table-column prop="email" label="邮箱" align="center" />
|
||||||
<el-table-column prop="phone" label="电话" align="center" />
|
<el-table-column prop="phone" label="电话" align="center" />
|
||||||
@@ -180,6 +197,8 @@ function getCompanyUser() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<EditDrawer v-model="showEdit" :user="editUser" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -207,7 +226,6 @@ function getCompanyUser() {
|
|||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -227,10 +245,6 @@ function getCompanyUser() {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
.table-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
:deep(.el-table__header) {
|
:deep(.el-table__header) {
|
||||||
.el-table__cell {
|
.el-table__cell {
|
||||||
Reference in new issue
Block a user