This commit is contained in:
caoqianming 2020-09-23 08:40:13 +08:00
parent 89482b4aed
commit ea28be2f5f
15 changed files with 444 additions and 105 deletions

View File

@ -9,3 +9,18 @@ export default {
name: 'App'
}
</script>
<style >
.el-table--medium td,   .el-table--medium th {
padding: 2px 0;
}
.el-form-item {
margin-bottom: 16px;
}
.el-card__body {
padding: 10px;
}
.el-card__header {
padding: 10px;
}
</style>

9
client/src/api/msg.js Normal file
View File

@ -0,0 +1,9 @@
import request from '@/utils/request'
export function sendMsg(data) {
return request({
url: '/system/sendmsg/',
method: 'post',
data
})
}

View File

@ -8,6 +8,15 @@ export function login(data) {
})
}
export function login2(data) {
//验证码登陆
return request({
url: '/token2/',
method: 'post',
data
})
}
export function logout() {
return request({
url: '/token/black/',

View File

@ -90,10 +90,10 @@ export default {
}
</script>
<style scoped>
<style>
.pagination-container {
background: #fff;
padding: 32px 16px;
padding: 0px 2px;
}
.pagination-container.hidden {
display: none;

View File

@ -1,4 +1,4 @@
import { login, logout, getInfo } from '@/api/user'
import { login, logout, getInfo, login2 } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
@ -47,7 +47,21 @@ const actions = {
})
})
},
// user code login
login2({ commit }, msginfo) {
const { mail, msg } = msginfo
return new Promise((resolve, reject) => {
login2({ mail: mail.trim(), msg: msg }).then(response => {
const { data } = response
commit('SET_TOKEN', data.access)
setToken(data.access)
resolve()
}).catch(error => {
reject(error)
})
})
},
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {

View File

@ -61,5 +61,43 @@ div:focus {
// main-container global css
.app-container {
padding: 20px;
padding: 10px;
}
.el-table--medium td,   .el-table--medium th {
padding: 2px 0;
}
.el-form-item {
margin-bottom: 16px;
}
.el-card, .el-message {
border-radius: 0px;
overflow: hidden;
}
.el-card__body {
padding: 6px;
}
.el-card__header {
padding: 6px;
}
.el-tabs--border-card>.el-tabs__content {
padding: 6px;
}
.el-dialog__header {
padding: 10px 10px 6px;
}
.el-dialog__body {
padding: 8px 12px;
}
// .el-form-item--medium .el-form-item__label {
// line-height: 16px;
// }
.el-form--label-top .el-form-item__label {
line-height: 16px;
}
.el-button+.el-button {
margin-left: 1px;
}
.el-button {
border-radius: 0px;
}

View File

@ -45,22 +45,30 @@ service.interceptors.response.use(
const res = response.data
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 401) {
MessageBox.confirm('您已退出,您可以停留在本页或者重新登陆.', '确认退出', {
confirmButtonText: '重新登陆',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
if(res.msg.indexOf('No active account')!=-1){
Message({
message: '用户名或密码错误',
type: 'error',
duration: 3 * 1000
})
})
}else{
MessageBox.confirm('认证失败,请重新登陆.', '确认退出', {
confirmButtonText: '重新登陆',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
} else if (res.code >= 400) {
Message({
message: res.msg || '请求出错',
type: 'error',
duration: 5 * 1000
duration: 3 * 1000
})
return Promise.reject(new Error(res.msg || '请求出错'))
} else {
return res

View File

@ -1,62 +1,125 @@
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<div class="login-form">
<div class="title-container">
<h3 class="title">CTC能力检索</h3>
<h3 class="title">ctc能力检索</h3>
</div>
<el-tabs v-model="activeName" :stretch="true">
<el-tab-pane label="验证码登陆" name="msg">
<el-form
ref="loginForm2"
:model="loginForm2"
:rules="loginRules2"
auto-complete="on"
label-position="left"
>
<el-form-item prop="mail">
<span class="svg-container">
<svg-icon icon-class="email" />
</span>
<el-input
ref="mail"
v-model="loginForm2.mail"
placeholder="邮箱号"
name="mail"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="账号"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="msg">
<span class="svg-container">
<svg-icon icon-class="message" />
</span>
<el-input
ref="msg"
v-model="loginForm2.msg"
type="text"
placeholder="验证码"
name="msg"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="sendMsg">
<el-button :disabled="disabled" plain type="primary">
<template>{{buttonmsg}}</template>
</el-button>
<!-- <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" /> -->
</span>
</el-form-item>
<el-button
:loading="loading2"
type="primary"
style="width:100%;margin-bottom:30px;"
@click.native.prevent="handleLogin2"
>验证登陆</el-button>
</el-form>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="密码"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登陆</el-button>
</el-tab-pane>
<el-tab-pane label="账户密码登陆" name="pwd">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
auto-complete="on"
label-position="left"
>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="账号"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<!-- <div class="tips">
<span style="margin-right:20px;">username: admin</span>
<span> password: any</span>
</div> -->
</el-form>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="密码"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
<el-button
:loading="loading"
type="primary"
style="width:100%;margin-bottom:30px;"
@click.native.prevent="handleLogin"
>登陆</el-button>
</el-form>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
// import { validUsername } from '@/utils/validate'
import { sendMsg } from '@/api/msg'
import { login2 } from "@/api/user"
export default {
name: 'Login',
name: "Login",
data() {
// const validateUsername = (rule, value, callback) => {
// if (!validUsername(value)) {
@ -67,70 +130,147 @@ export default {
// }
const validatePassword = (rule, value, callback) => {
if (value.length < 4) {
callback(new Error('密码长度小于4位!'))
callback(new Error("密码长度小于4位!"));
} else {
callback()
callback();
}
}
};
return {
timer:null,
disabled:false,
buttonmsg:'发送验证码',
count:20,
activeName: "pwd",
loginForm: {
username: '',
password: ''
username: "",
password: "",
},
loginForm2: {
mail: "",
msg: "",
},
loginRules: {
username: [{ required: true, trigger: 'blur', message: "请输入账户" }],
password: [{ required: true, trigger: 'blur', validator: validatePassword, message: "请输入密码" }]
username: [{ required: true, trigger: "blur", message: "请输入账户" }],
password: [
{
required: true,
trigger: "blur",
validator: validatePassword,
message: "请输入密码",
},
],
},
loginRules2: {
mail: [{ required: true, trigger: "blur", message: "请输入邮箱号" }],
msg: [
{
required: true,
trigger: "blur",
message: "请输入验证码",
},
],
},
loading2:false,
loading: false,
passwordType: 'password',
redirect: undefined
}
passwordType: "password",
redirect: undefined,
};
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect
handler: function (route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true
}
immediate: true,
},
},
methods: {
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
if (this.passwordType === "password") {
this.passwordType = "";
} else {
this.passwordType = 'password'
this.passwordType = "password";
}
this.$nextTick(() => {
this.$refs.password.focus()
})
this.$refs.password.focus();
});
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
sendMsg(){
if(this.loginForm2.mail.length){
this.getTimer()
sendMsg({'mail':this.loginForm2.mail}).then(res=>{
this.$message.success('验证码已发送至该邮箱,请注意查收')
}).catch(e=>{})
}else{
this.$message.warning('请输入邮箱号')
}
},
getTimer(){
const TIME_COUNT = 20;
if (!this.timer) {
this.count = TIME_COUNT;
this.disabled = true;
this.timer = setInterval(() => {
if (this.count > 0 && this.count <= TIME_COUNT) {
this.count--;
this.buttonmsg = this.count
} else {
console.log('error submit!!')
return false
this.disabled = false;
clearInterval(this.timer);
this.timer = null;
this.buttonmsg = '发送验证码'
}
})
}
}
}
}, 1000)
}
},
handleLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
this.$store
.dispatch("user/login", this.loginForm)
.then(() => {
this.$router.push({ path: this.redirect || "/" });
this.loading = false;
})
.catch(() => {
this.loading = false;
});
} else {
console.log("error submit!!");
return false;
}
});
},
handleLogin2() {
this.$refs.loginForm2.validate((valid) => {
if (valid) {
this.loading2 = true;
this.$store
.dispatch("user/login2", this.loginForm2)
.then(() => {
this.$router.push({ path: this.redirect || "/" });
this.loading2 = false;
})
.catch(() => {
this.loading2 = false;
});
} else {
console.log("error submit!!");
return false;
}
});
},
},
};
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443;
$light_gray:#fff;
$bg: #283443;
$light_gray: #fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
@ -169,13 +309,19 @@ $cursor: #fff;
border-radius: 5px;
color: #454545;
}
.el-tabs__item {
color: #fff;
}
.el-tabs__item.is-active {
color: #409eff;
}
}
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
.login-container {
min-height: 100%;

View File

@ -0,0 +1,28 @@
# Generated by Django 3.0.7 on 2020-09-22 08:52
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('system', '0003_auto_20200528_1716'),
]
operations = [
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('mail', models.CharField(max_length=200, verbose_name='发送邮箱')),
('msg', models.CharField(max_length=200, verbose_name='验证码')),
],
options={
'abstract': False,
},
),
]

View File

@ -215,4 +215,8 @@ class File(CommonAModel):
verbose_name_plural = verbose_name
def __str__(self):
return self.name
return self.name
class Message(BaseModel):
mail = models.CharField('发送邮箱', max_length=200)
msg = models.CharField('验证码', max_length=200)

View File

@ -4,6 +4,7 @@ from rest_framework import serializers
from .models import Organization, Permission, Role, User, Position, DictType, Dict, File
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File

View File

@ -1,5 +1,5 @@
from django.urls import path, include
from .views import UserViewSet, OrganizationViewSet, PermissionViewSet, RoleViewSet, PositionViewSet, TestView, DictTypeViewSet, DictViewSet
from .views import UserViewSet, OrganizationViewSet, PermissionViewSet, RoleViewSet, PositionViewSet, TestView, DictTypeViewSet, DictViewSet, sendMsg
from rest_framework import routers
@ -13,5 +13,6 @@ router.register('dicttype', DictTypeViewSet, basename="dicttype")
router.register('dict', DictViewSet, basename="dict")
urlpatterns = [
path('', include(router.urls)),
path('sendmsg/', sendMsg.as_view()),
path('test/', TestView.as_view())
]

View File

@ -1,10 +1,12 @@
import logging
from django.contrib.auth.hashers import check_password, make_password
from django.contrib.auth.models import UserManager
from django.core.cache import cache
from django.http import request
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.decorators import action, authentication_classes, permission_classes
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin,
ListModelMixin, RetrieveModelMixin,
@ -20,7 +22,7 @@ from utils.queryset import get_child_queryset2
from .filters import UserFilter
from .models import (Dict, DictType, Organization, Permission, Position, Role,
User, File)
User, File, Message)
from .permission import RbacPermission, get_permission_list
from .permission_data import RbacFilterSet
from .serializers import (DictSerializer, DictTypeSerializer,
@ -40,6 +42,69 @@ class LogoutView(APIView):
def get(self, request, *args, **kwargs): # 可将token加入黑名单
return Response(status=status.HTTP_200_OK)
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
from email.header import Header
import random
from rest_framework_simplejwt.tokens import RefreshToken
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
class Login2View(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
mail = request.data['mail']
msg = request.data['msg']
if not User.objects.filter(username=mail).exists():
return Response('账户不存在', status=status.HTTP_400_BAD_REQUEST)
if Message.objects.filter(mail=mail).last().msg == msg:
user = User.objects.get(username=mail)
return Response(get_tokens_for_user(user), status=status.HTTP_200_OK)
return Response('验证码错误', status=status.HTTP_400_BAD_REQUEST)
class sendMsg(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
code = random.randint(1000,9999)
my_sender = 'caoqianming@ctc.ac.cn'
my_user = request.data['mail']
my_pass = '9093QQww'
if not User.objects.filter(username=my_user).exists():
return Response('该账户不存在', status=status.HTTP_400_BAD_REQUEST)
try:
# 邮件内容
msg=MIMEText('ctc能力检索本次验证码为' + str(code),'plain','utf-8')
# 括号里的对应发件人邮箱昵称、发件人邮箱账号
msg['From']=formataddr(["国检集团能力检索",my_sender])
# 括号里的对应收件人邮箱昵称、收件人邮箱账号
msg['To']=formataddr(["国检集团",my_user])
# 邮件的主题
msg['Subject'] = Header(str(code), 'utf-8').encode()
# SMTP服务器腾讯企业邮箱端口是465腾讯邮箱支持SSL(不强制) 不支持TLS
# qq邮箱smtp服务器地址:smtp.qq.com,端口号456
# 163邮箱smtp服务器地址smtp.163.com端口号25
server=smtplib.SMTP_SSL("smtp.exmail.qq.com", 465)
# 登录服务器,括号中对应的是发件人邮箱账号、邮箱密码
server.login(my_sender, my_pass)
# 发送邮件,括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
server.sendmail(my_sender,[my_user,],msg.as_string())
# 关闭连接
server.quit()
Message.objects.create(mail=my_user, msg=code)
except:
return Response('验证码发送失败', status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_200_OK)
class DictTypeViewSet(ModelViewSet):
"""

Binary file not shown.

View File

@ -22,13 +22,14 @@ from rest_framework.documentation import include_docs_urls
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
from apps.system.views import FileViewSet, LogoutView
from apps.system.views import FileViewSet, LogoutView, Login2View
router = routers.DefaultRouter()
router.register('file', FileViewSet, basename="file")
urlpatterns = [
path('admin/', admin.site.urls),
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token2/', Login2View.as_view(), name='token_obtain_2'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('token/black/', LogoutView.as_view(), name='token_black'),
path('system/', include('apps.system.urls')),