新功能: 密码重置功能

pull/49/head
猿小天 2022-04-15 19:56:28 +08:00
parent 48f18d7f1c
commit 7706d6db84
5 changed files with 202 additions and 211 deletions

View File

@ -41,6 +41,7 @@ urlpatterns = [
path('menu/web_router/', MenuViewSet.as_view({'get': 'web_router'})), path('menu/web_router/', MenuViewSet.as_view({'get': 'web_router'})),
path('user/user_info/', UserViewSet.as_view({'get': 'user_info', 'put': 'update_user_info'})), path('user/user_info/', UserViewSet.as_view({'get': 'user_info', 'put': 'update_user_info'})),
path('user/change_password/<int:pk>/', UserViewSet.as_view({'put': 'change_password'})), path('user/change_password/<int:pk>/', UserViewSet.as_view({'put': 'change_password'})),
path('user/reset_password/<int:pk>/', UserViewSet.as_view({'put': 'reset_password'})),
path('user/export/', UserViewSet.as_view({'post': 'export_data', })), path('user/export/', UserViewSet.as_view({'post': 'export_data', })),
path('user/import/',UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})), path('user/import/',UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})),
path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})), path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})),

View File

@ -11,6 +11,7 @@ import hashlib
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Users from dvadmin.system.models import Users
from dvadmin.utils.json_response import ErrorResponse, DetailResponse from dvadmin.utils.json_response import ErrorResponse, DetailResponse
@ -147,7 +148,7 @@ class UserViewSet(CustomModelViewSet):
'gender': '用户性别(男/女/未知)', 'gender': '用户性别(男/女/未知)',
'is_active': '帐号状态(启用/禁用)', 'password': '登录密码', 'dept': '部门ID', 'role': '角色ID'} 'is_active': '帐号状态(启用/禁用)', 'password': '登录密码', 'dept': '部门ID', 'role': '角色ID'}
@action(methods=['GET'], detail=True, permission_classes=[]) @action(methods=['GET'], detail=True, permission_classes=[IsAuthenticated])
def user_info(self, request): def user_info(self, request):
"""获取当前用户信息""" """获取当前用户信息"""
user = request.user user = request.user
@ -159,14 +160,14 @@ class UserViewSet(CustomModelViewSet):
} }
return DetailResponse(data=result, msg="获取成功") return DetailResponse(data=result, msg="获取成功")
@action(methods=['PUT'], detail=True, permission_classes=[]) @action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def update_user_info(self, request): def update_user_info(self, request):
"""修改当前用户信息""" """修改当前用户信息"""
user = request.user user = request.user
Users.objects.filter(id=user.id).update(**request.data) Users.objects.filter(id=user.id).update(**request.data)
return DetailResponse(data=None, msg="修改成功") return DetailResponse(data=None, msg="修改成功")
@action(methods=['PUT'], detail=True, permission_classes=[]) @action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def change_password(self, request, *args, **kwargs): def change_password(self, request, *args, **kwargs):
"""密码修改""" """密码修改"""
instance = Users.objects.filter(id=kwargs.get('pk')).first() instance = Users.objects.filter(id=kwargs.get('pk')).first()
@ -185,3 +186,22 @@ class UserViewSet(CustomModelViewSet):
return ErrorResponse(msg="旧密码不正确") return ErrorResponse(msg="旧密码不正确")
else: else:
return ErrorResponse(msg="未获取到用户") return ErrorResponse(msg="未获取到用户")
@action(methods=['PUT'], detail=True)
def reset_password(self, request, pk):
"""
密码重置
"""
instance = Users.objects.filter(id=pk).first()
data = request.data
new_pwd = data.get('newPassword')
new_pwd2 = data.get('newPassword2')
if instance:
if new_pwd != new_pwd2:
return ErrorResponse(msg="两次密码不匹配")
else:
instance.password = make_password(new_pwd)
instance.save()
return DetailResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="未获取到用户")

View File

@ -6,7 +6,7 @@
* 联系Qq:1638245306 * 联系Qq:1638245306
* @文件介绍: 用户接口 * @文件介绍: 用户接口
*/ */
import { request, downloadFile } from '@/api/service' import { request } from '@/api/service'
export const urlPrefix = '/api/system/user/' export const urlPrefix = '/api/system/user/'
@ -43,13 +43,15 @@ export function DelObj (id) {
} }
/** /**
* 导出 * 重置密码
* @param params * @param id
* @returns {*}
* @constructor
*/ */
export function exportData (params) { export function ResetPwd (obj) {
return downloadFile({ return request({
url: urlPrefix + 'export/', url: urlPrefix + 'reset_password/' + obj.id + '/',
params: params, method: 'put',
method: 'post' data: obj
}) })
} }

View File

@ -2,7 +2,6 @@ import { request } from '@/api/service'
import { BUTTON_STATUS_BOOL } from '@/config/button' import { BUTTON_STATUS_BOOL } from '@/config/button'
import { urlPrefix as deptPrefix } from '../dept/api' import { urlPrefix as deptPrefix } from '../dept/api'
import util from '@/libs/util' import util from '@/libs/util'
import XEUtils from 'xe-utils'
const uploadUrl = util.baseURL() + 'api/system/img/' const uploadUrl = util.baseURL() + 'api/system/img/'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
@ -14,8 +13,7 @@ export const crudOptions = (vm) => {
height: '100%' height: '100%'
}, },
rowHandle: { rowHandle: {
width: 140, width: 180,
fixed: 'right',
view: { view: {
thin: true, thin: true,
text: '', text: '',
@ -36,7 +34,21 @@ export const crudOptions = (vm) => {
disabled () { disabled () {
return !vm.hasPermissions('Delete') return !vm.hasPermissions('Delete')
} }
} },
custom: [
{
thin: true,
text: '',
size: 'small',
type: 'warning',
icon: 'el-icon-refresh-left',
show () {
return vm.hasPermissions('ResetPwd')
},
emit: 'resetPwd'
}
]
}, },
viewOptions: { viewOptions: {
componentType: 'form' componentType: 'form'
@ -47,7 +59,7 @@ export const crudOptions = (vm) => {
indexRow: { // 或者直接传true,不显示title不居中 indexRow: { // 或者直接传true,不显示title不居中
title: '序号', title: '序号',
align: 'center', align: 'center',
width: 80 width: 100
}, },
columns: [ columns: [
{ {
@ -87,10 +99,7 @@ export const crudOptions = (vm) => {
type: 'input', type: 'input',
form: { form: {
rules: [ // 表单校验规则 rules: [ // 表单校验规则
{ { required: true, message: '账号必填项' }
required: true,
message: '账号必填项'
}
], ],
component: { component: {
placeholder: '请输入账号' placeholder: '请输入账号'
@ -110,16 +119,12 @@ export const crudOptions = (vm) => {
title: '姓名', title: '姓名',
key: 'name', key: 'name',
search: { search: {
key: 'name__icontains',
disabled: false disabled: false
}, },
type: 'input', type: 'input',
form: { form: {
rules: [ // 表单校验规则 rules: [ // 表单校验规则
{ { required: true, message: '姓名必填项' }
required: true,
message: '姓名必填项'
}
], ],
component: { component: {
span: 12, span: 12,
@ -132,7 +137,6 @@ export const crudOptions = (vm) => {
}, },
{ {
title: '部门', title: '部门',
width: 160,
key: 'dept', key: 'dept',
search: { search: {
disabled: true disabled: true
@ -143,29 +147,18 @@ export const crudOptions = (vm) => {
url: deptPrefix, url: deptPrefix,
value: 'id', // 数据字典中value字段的属性名 value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名 label: 'name', // 数据字典中label字段的属性名
isTree: true, getData: (url, dict, { form, component }) => {
getData: (url, dict, { return request({ url: url, params: { page: 1, limit: 10, status: 1 } }).then(ret => {
_, component._elProps.page = ret.data.page
component component._elProps.limit = ret.data.limit
}) => { component._elProps.total = ret.data.total
return request({
url: url,
params: {
page: 1,
limit: 999,
status: 1
}
}).then(ret => {
return ret.data.data return ret.data.data
}) })
} }
}, },
form: { form: {
rules: [ // 表单校验规则 rules: [ // 表单校验规则
{ { required: true, message: '必填项' }
required: true,
message: '必填项'
}
], ],
itemProps: { itemProps: {
class: { yxtInput: true } class: { yxtInput: true }
@ -174,20 +167,14 @@ export const crudOptions = (vm) => {
span: 12, span: 12,
props: { multiple: false }, props: { multiple: false },
elProps: { elProps: {
treeConfig: { pagination: true,
transform: true,
rowField: 'id',
parentField: 'parent',
expandAll: true
},
columns: [ columns: [
{ {
field: 'name', field: 'name',
title: '部门名称', title: '部门名称'
treeNode: true
}, },
{ {
field: 'status', field: 'status_label',
title: '状态' title: '状态'
}, },
{ {
@ -199,90 +186,21 @@ export const crudOptions = (vm) => {
} }
} }
}, },
{
title: '角色',
key: 'role',
width: 160,
search: {
disabled: true
},
type: 'table-selector',
dict: {
cache: false,
url: '/api/system/role/',
value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名
getData: (url, dict, {
form,
component
}) => {
return request({
url: url,
params: {
page: 1,
limit: 10
}
}).then(ret => {
component._elProps.page = ret.data.page
component._elProps.limit = ret.data.limit
component._elProps.total = ret.data.total
return ret.data.data
})
}
},
form: {
rules: [ // 表单校验规则
{
required: true,
message: '必填项'
}
],
itemProps: {
class: { yxtInput: true }
},
component: {
span: 12,
props: { multiple: true },
elProps: {
pagination: true,
columns: [
{
field: 'name',
title: '角色名称'
},
{
field: 'key',
title: '权限标识'
},
{
field: 'status',
title: '状态'
}
]
}
}
}
},
{ {
title: '手机号码', title: '手机号码',
key: 'mobile', key: 'mobile',
width: 120,
search: { search: {
disabled: true disabled: true
}, },
type: 'input', type: 'input',
form: { form: {
rules: [ rules: [
{ { max: 20, message: '请输入正确的手机号码', trigger: 'blur' },
max: 20, { pattern: /^1[3|4|5|7|8]\d{9}$/, message: '请输入正确的手机号码' }
message: '请输入正确的手机号码',
trigger: 'blur'
},
{
pattern: /^1[3|4|5|7|8]\d{9}$/,
message: '请输入正确的手机号码'
}
], ],
itemProps: {
class: { yxtInput: true }
},
component: { component: {
placeholder: '请输入手机号码' placeholder: '请输入手机号码'
} }
@ -290,14 +208,9 @@ export const crudOptions = (vm) => {
}, { }, {
title: '邮箱', title: '邮箱',
key: 'email', key: 'email',
width: 120,
form: { form: {
rules: [ rules: [
{ { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
type: 'email',
message: '请输入正确的邮箱地址',
trigger: ['blur', 'change']
}
], ],
component: { component: {
placeholder: '请输入邮箱' placeholder: '请输入邮箱'
@ -307,57 +220,18 @@ export const crudOptions = (vm) => {
{ {
title: '性别', title: '性别',
key: 'gender', key: 'gender',
type: 'select', type: 'radio',
dict: { dict: {
data: [{ data: [{ label: '', value: 1 }, { label: '', value: 0 }]
label: '',
value: 1
}, {
label: '',
value: 0
}]
}, },
form: { form: {
value: 1, value: 1,
rules: [
{
required: true,
message: '性别必填项'
}
],
component: { component: {
span: 12 span: 12
},
itemProps: {
class: { yxtInput: true }
} }
}, },
component: { props: { color: 'auto' } } // 自动染色 component: { props: { color: 'auto' } } // 自动染色
}, },
{
title: '用户类型',
key: 'user_type',
type: 'select',
width: 120,
search: {
key: 'user_type',
value: 0,
disabled: false
},
dict: {
data: [{
label: '前台用户',
value: 1
}, {
label: '后台用户',
value: 0
}]
},
form: {
disabled: true
},
component: { props: { color: 'auto' } } // 自动染色
},
{ {
title: '状态', title: '状态',
key: 'is_active', key: 'is_active',
@ -380,7 +254,7 @@ export const crudOptions = (vm) => {
title: '头像', title: '头像',
key: 'avatar', key: 'avatar',
type: 'avatar-uploader', type: 'avatar-uploader',
width: 80, width: 100,
align: 'left', align: 'left',
form: { form: {
component: { component: {
@ -393,13 +267,10 @@ export const crudOptions = (vm) => {
}, },
type: 'form', type: 'form',
successHandle (ret, option) { successHandle (ret, option) {
if (ret.data == null || ret.data === '') { if (ret.data === null || ret.data === '') {
throw new Error('上传失败') throw new Error('上传失败')
} }
return { return { url: ret.data.data.url, key: option.data.key }
url: ret.data.data.url,
key: option.data.key
}
} }
}, },
elProps: { // 与el-uploader 配置一致 elProps: { // 与el-uploader 配置一致
@ -432,7 +303,58 @@ export const crudOptions = (vm) => {
} }
} }
} }
},
{
title: '角色',
key: 'role',
search: {
disabled: true
},
type: 'table-selector',
dict: {
cache: false,
url: '/api/system/role/',
value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名
getData: (url, dict, { form, component }) => {
return request({ url: url, params: { page: 1, limit: 10 } }).then(ret => {
component._elProps.page = ret.data.page
component._elProps.limit = ret.data.limit
component._elProps.total = ret.data.total
return ret.data.data
})
}
},
form: {
rules: [ // 表单校验规则
{ required: true, message: '必填项' }
],
itemProps: {
class: { yxtInput: true }
},
component: {
span: 12,
props: { multiple: true },
elProps: {
pagination: true,
columns: [
{
field: 'name',
title: '角色名称'
},
{
field: 'key',
title: '权限标识'
},
{
field: 'status_label',
title: '状态'
}
]
}
}
}
} }
].concat(vm.commonEndColumns({ update_datetime: { showTable: false } })) ].concat(vm.commonEndColumns({ show_create_datetime: false }))
} }
} }

View File

@ -1,11 +1,3 @@
<!--
* @创建文件时间: 2021-06-01 22:41:21
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-07-29 19:27:10
* 联系Qq:1638245306
* @文件介绍: 用户管理
-->
<template> <template>
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }"> <d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
<d2-crud-x <d2-crud-x
@ -13,6 +5,7 @@
v-bind="_crudProps" v-bind="_crudProps"
v-on="_crudListeners" v-on="_crudListeners"
crud.options.tableType="vxe-table" crud.options.tableType="vxe-table"
@resetPwd="resetPwd"
> >
<div slot="header"> <div slot="header">
<crud-search <crud-search
@ -26,20 +19,8 @@
v-permission="'Create'" v-permission="'Create'"
type="primary" type="primary"
@click="addRow" @click="addRow"
><i class="el-icon-plus"/> 新增 ><i class="el-icon-plus" /> 新增</el-button
</el-button
> >
<el-button
size="small"
type="danger"
@click="onExport"
v-permission="'Export'"
><i class="el-icon-download"/> 导出
</el-button>
<importExcel
importApi="/api/system/user/import/"
v-permission="'Import'">导入
</importExcel>
</el-button-group> </el-button-group>
<crud-toolbar <crud-toolbar
:search.sync="crud.searchOptions.show" :search.sync="crud.searchOptions.show"
@ -50,6 +31,20 @@
/> />
</div> </div>
</d2-crud-x> </d2-crud-x>
<el-dialog title="密码重置" :visible.sync="dialogFormVisible" :close-on-click-modal="false">
<el-form :model="resetPwdForm" ref="resetPwdForm" :rules="passwordRules">
<el-form-item label="密码" prop="pwd">
<el-input v-model="resetPwdForm.pwd" type="password" show-password clearable autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="再次输入密码" prop="pwd2">
<el-input v-model="resetPwdForm.pwd2" type="password" show-password clearable autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false"> </el-button>
<el-button type="primary" @click="resetPwdSubmit"> </el-button>
</div>
</el-dialog>
</d2-container> </d2-container>
</template> </template>
@ -60,10 +55,42 @@ import { d2CrudPlus } from 'd2-crud-plus'
export default { export default {
name: 'user', name: 'user',
mixins: [d2CrudPlus.crud], mixins: [d2CrudPlus.crud],
data () { data () {
return {} var validatePass = (rule, value, callback) => {
const pwdRegex = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z]).{8,30}')
if (value === '') {
callback(new Error('请输入密码'))
} else if (!pwdRegex.test(value)) {
callback(new Error('您的密码复杂度太低(密码中必须包含字母、数字)'))
} else {
if (this.resetPwdForm.pwd2 !== '') {
this.$refs.resetPwdForm.validateField('pwd2')
}
callback()
}
}
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== this.resetPwdForm.pwd) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
return {
dialogFormVisible: false,
resetPwdForm: {
id: null,
pwd: null,
pwd2: null
},
passwordRules: {
pwd: [{ required: true, message: '必填项' }, { validator: validatePass, trigger: 'blur' }],
pwd2: [{ required: true, message: '必填项' }, { validator: validatePass2, trigger: 'blur' }]
}
}
}, },
methods: { methods: {
getCrudOptions () { getCrudOptions () {
@ -76,19 +103,38 @@ export default {
return api.AddObj(row) return api.AddObj(row)
}, },
updateRequest (row) { updateRequest (row) {
console.log('----', row)
return api.UpdateObj(row) return api.UpdateObj(row)
}, },
delRequest (row) { delRequest (row) {
return api.DelObj(row.id) return api.DelObj(row.id)
}, },
onExport () { //
this.$confirm('是否确认导出所有数据项?', '警告', { resetPwd ({ row }) {
confirmButtonText: '确定', this.dialogFormVisible = true
cancelButtonText: '取消', this.resetPwdForm.id = row.id
type: 'warning' },
}).then(function () { //
return api.exportData() resetPwdSubmit () {
const that = this
that.$refs.resetPwdForm.validate((valid) => {
if (valid) {
const params = {
id: that.resetPwdForm.id,
newPassword: that.$md5(that.resetPwdForm.pwd),
newPassword2: that.$md5(that.resetPwdForm.pwd2)
}
api.ResetPwd(params).then(res => {
that.dialogFormVisible = false
that.resetPwdForm = {
id: null,
pwd: null,
pwd2: null
}
that.$message.success('修改成功')
})
} else {
that.$message.error('表单校验失败,请检查')
}
}) })
} }
} }