新功能: 密码重置功能

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('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/reset_password/<int:pk>/', UserViewSet.as_view({'put': 'reset_password'})),
path('user/export/', UserViewSet.as_view({'post': 'export_data', })),
path('user/import/',UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})),
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 rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Users
from dvadmin.utils.json_response import ErrorResponse, DetailResponse
@ -147,7 +148,7 @@ class UserViewSet(CustomModelViewSet):
'gender': '用户性别(男/女/未知)',
'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):
"""获取当前用户信息"""
user = request.user
@ -159,14 +160,14 @@ class UserViewSet(CustomModelViewSet):
}
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):
"""修改当前用户信息"""
user = request.user
Users.objects.filter(id=user.id).update(**request.data)
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):
"""密码修改"""
instance = Users.objects.filter(id=kwargs.get('pk')).first()
@ -185,3 +186,22 @@ class UserViewSet(CustomModelViewSet):
return ErrorResponse(msg="旧密码不正确")
else:
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
* @文件介绍: 用户接口
*/
import { request, downloadFile } from '@/api/service'
import { request } from '@/api/service'
export const urlPrefix = '/api/system/user/'
@ -43,13 +43,15 @@ export function DelObj (id) {
}
/**
* 导出
* @param params
* 重置密码
* @param id
* @returns {*}
* @constructor
*/
export function exportData (params) {
return downloadFile({
url: urlPrefix + 'export/',
params: params,
method: 'post'
export function ResetPwd (obj) {
return request({
url: urlPrefix + 'reset_password/' + obj.id + '/',
method: 'put',
data: obj
})
}

View File

@ -2,7 +2,6 @@ import { request } from '@/api/service'
import { BUTTON_STATUS_BOOL } from '@/config/button'
import { urlPrefix as deptPrefix } from '../dept/api'
import util from '@/libs/util'
import XEUtils from 'xe-utils'
const uploadUrl = util.baseURL() + 'api/system/img/'
export const crudOptions = (vm) => {
@ -14,8 +13,7 @@ export const crudOptions = (vm) => {
height: '100%'
},
rowHandle: {
width: 140,
fixed: 'right',
width: 180,
view: {
thin: true,
text: '',
@ -36,7 +34,21 @@ export const crudOptions = (vm) => {
disabled () {
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: {
componentType: 'form'
@ -47,7 +59,7 @@ export const crudOptions = (vm) => {
indexRow: { // 或者直接传true,不显示title不居中
title: '序号',
align: 'center',
width: 80
width: 100
},
columns: [
{
@ -87,10 +99,7 @@ export const crudOptions = (vm) => {
type: 'input',
form: {
rules: [ // 表单校验规则
{
required: true,
message: '账号必填项'
}
{ required: true, message: '账号必填项' }
],
component: {
placeholder: '请输入账号'
@ -110,16 +119,12 @@ export const crudOptions = (vm) => {
title: '姓名',
key: 'name',
search: {
key: 'name__icontains',
disabled: false
},
type: 'input',
form: {
rules: [ // 表单校验规则
{
required: true,
message: '姓名必填项'
}
{ required: true, message: '姓名必填项' }
],
component: {
span: 12,
@ -132,7 +137,6 @@ export const crudOptions = (vm) => {
},
{
title: '部门',
width: 160,
key: 'dept',
search: {
disabled: true
@ -143,29 +147,18 @@ export const crudOptions = (vm) => {
url: deptPrefix,
value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名
isTree: true,
getData: (url, dict, {
_,
component
}) => {
return request({
url: url,
params: {
page: 1,
limit: 999,
status: 1
}
}).then(ret => {
getData: (url, dict, { form, component }) => {
return request({ url: url, params: { page: 1, limit: 10, status: 1 } }).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: '必填项'
}
{ required: true, message: '必填项' }
],
itemProps: {
class: { yxtInput: true }
@ -174,20 +167,14 @@ export const crudOptions = (vm) => {
span: 12,
props: { multiple: false },
elProps: {
treeConfig: {
transform: true,
rowField: 'id',
parentField: 'parent',
expandAll: true
},
pagination: true,
columns: [
{
field: 'name',
title: '部门名称',
treeNode: true
title: '部门名称'
},
{
field: 'status',
field: 'status_label',
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: '手机号码',
key: 'mobile',
width: 120,
search: {
disabled: true
},
type: 'input',
form: {
rules: [
{
max: 20,
message: '请输入正确的手机号码',
trigger: 'blur'
},
{
pattern: /^1[3|4|5|7|8]\d{9}$/,
message: '请输入正确的手机号码'
}
{ max: 20, message: '请输入正确的手机号码', trigger: 'blur' },
{ pattern: /^1[3|4|5|7|8]\d{9}$/, message: '请输入正确的手机号码' }
],
itemProps: {
class: { yxtInput: true }
},
component: {
placeholder: '请输入手机号码'
}
@ -290,14 +208,9 @@ export const crudOptions = (vm) => {
}, {
title: '邮箱',
key: 'email',
width: 120,
form: {
rules: [
{
type: 'email',
message: '请输入正确的邮箱地址',
trigger: ['blur', 'change']
}
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
],
component: {
placeholder: '请输入邮箱'
@ -307,57 +220,18 @@ export const crudOptions = (vm) => {
{
title: '性别',
key: 'gender',
type: 'select',
type: 'radio',
dict: {
data: [{
label: '',
value: 1
}, {
label: '',
value: 0
}]
data: [{ label: '', value: 1 }, { label: '', value: 0 }]
},
form: {
value: 1,
rules: [
{
required: true,
message: '性别必填项'
}
],
component: {
span: 12
},
itemProps: {
class: { yxtInput: true }
}
},
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: '状态',
key: 'is_active',
@ -380,7 +254,7 @@ export const crudOptions = (vm) => {
title: '头像',
key: 'avatar',
type: 'avatar-uploader',
width: 80,
width: 100,
align: 'left',
form: {
component: {
@ -393,13 +267,10 @@ export const crudOptions = (vm) => {
},
type: 'form',
successHandle (ret, option) {
if (ret.data == null || ret.data === '') {
if (ret.data === null || ret.data === '') {
throw new Error('上传失败')
}
return {
url: ret.data.data.url,
key: option.data.key
}
return { url: ret.data.data.url, key: option.data.key }
}
},
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>
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
<d2-crud-x
@ -13,6 +5,7 @@
v-bind="_crudProps"
v-on="_crudListeners"
crud.options.tableType="vxe-table"
@resetPwd="resetPwd"
>
<div slot="header">
<crud-search
@ -26,20 +19,8 @@
v-permission="'Create'"
type="primary"
@click="addRow"
><i class="el-icon-plus"/> 新增
</el-button
><i class="el-icon-plus" /> 新增</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>
<crud-toolbar
:search.sync="crud.searchOptions.show"
@ -50,6 +31,20 @@
/>
</div>
</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>
</template>
@ -60,10 +55,42 @@ import { d2CrudPlus } from 'd2-crud-plus'
export default {
name: 'user',
mixins: [d2CrudPlus.crud],
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: {
getCrudOptions () {
@ -76,19 +103,38 @@ export default {
return api.AddObj(row)
},
updateRequest (row) {
console.log('----', row)
return api.UpdateObj(row)
},
delRequest (row) {
return api.DelObj(row.id)
},
onExport () {
this.$confirm('是否确认导出所有数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(function () {
return api.exportData()
//
resetPwd ({ row }) {
this.dialogFormVisible = true
this.resetPwdForm.id = row.id
},
//
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('表单校验失败,请检查')
}
})
}
}