create admin

This commit is contained in:
caoqianming 2020-08-10 11:59:11 +08:00
parent 7949661044
commit 3924c85d25
19 changed files with 299 additions and 121 deletions

View File

@ -64,4 +64,12 @@ export function deleteUser(id, data) {
})
}
export function changePassword(data) {
return request({
url: '/rbac/user/password/',
method: 'put',
data
})
}

View File

@ -16,6 +16,11 @@
首页
</el-dropdown-item>
</router-link>
<router-link to="/system/user/password">
<el-dropdown-item divided>
修改密码
</el-dropdown-item>
</router-link>
<!-- <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
<el-dropdown-item>Github</el-dropdown-item>
</a>

View File

@ -41,19 +41,7 @@ export const constantRoutes = [
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}]
},
}
]
@ -63,10 +51,21 @@ export const constantRoutes = [
* the routes that need to be dynamically loaded based on user perms
*/
export const asyncRoutes = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard', perms:['admin'] }
}]
},
{
path: '/cms',
component: Layout,
redirect: '/cms/',
redirect: '/cms/materials',
name: 'CMS',
meta: { title: '资料文章', icon: 'documentation', perms: ['news_view'] },
children: [
@ -101,9 +100,9 @@ export const asyncRoutes = [
{
path: '/crm',
component: Layout,
redirect: '/crm/',
redirect: '/crm/consumer',
name: 'Crm',
meta: { title: '客户管理', icon: 'peoples', perms: ['custom_manage'] },
meta: { title: '客户管理', icon: 'peoples', perms: ['consumer_view'] },
children: [
{
path: 'company',
@ -115,7 +114,7 @@ export const asyncRoutes = [
path: 'consumer',
name: 'Consumer',
component: () => import('@/views/crm/consumer.vue'),
meta: { title: '学员列表', icon: '', perms: ['consumer_list'] }
meta: { title: '学员列表', icon: '', perms: ['consumer_view'] }
},
{
path: 'consumerrule',
@ -129,9 +128,9 @@ export const asyncRoutes = [
{
path: '/Qmanage',
component: Layout,
redirect: '/Qmanage/',
redirect: '/Qmanage/question',
name: 'Qmanage',
meta: { title: '题库管理', icon: 'table'},
meta: { title: '题库管理', icon: 'table', perms: []},
children: [
{
path: 'subject',
@ -170,9 +169,9 @@ export const asyncRoutes = [
{
path: '/sjmanage',
component: Layout,
redirect: '/sjmanage/',
redirect: '/sjmanage/workscope',
name: 'Sjmanage',
meta: { title: '出卷管理', icon: 'component'},
meta: { title: '出卷管理', icon: 'component', perms: []},
children: [
{
path: 'testrule',
@ -239,15 +238,15 @@ export const asyncRoutes = [
{
path: '/analyse',
component: Layout,
redirect: '/analyse/',
redirect: '/analyse/examtest',
name: 'Analyse',
meta: { title: '统计分析', icon: 'chart'},
meta: { title: '统计分析', icon: 'chart', perms: ['examtest_view']},
children: [
{
path: 'examtest',
name: 'ExamTest',
component: () => import('@/views/analyse/examtest.vue'),
meta: { title: '答卷列表', icon: '', perms: ['examtest_list'] }
meta: { title: '答卷列表', icon: '', perms: ['examtest_view'] }
},
{
path: 'chart',
@ -261,9 +260,9 @@ export const asyncRoutes = [
{
path: '/system',
component: Layout,
redirect: '/system/user',
redirect: '/system/admin',
name: 'System',
meta: { title: '系统管理', icon: 'tree', perms: ['system_manage'] },
meta: { title: '系统管理', icon: 'tree', perms: [] },
children: [
{
path: 'banner',
@ -277,6 +276,19 @@ export const asyncRoutes = [
component: () => import('@/views/system/admin.vue'),
meta: { title: '管理员', icon: '', perms: ['admin_manage'] }
},
{
path: 'role',
name: 'Role',
component: () => import('@/views/system/role'),
meta: { title: '管理员角色', icon: '', perms: ['role_manage'] }
},
{
path: 'user/password',
name: 'ChangePassword',
component: () => import('@/views/system/changepassword'),
meta: { title: '修改密码', noCache: true, icon: ''},
hidden: true
},
]
},
// 404 page must be placed at the end !!!

View File

@ -45,7 +45,14 @@ 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('您已退出,您可以停留在本页或者重新登陆.', '确认退出', {
if(res.msg.indexOf('No active account')!=-1){
Message({
message: '用户名或密码错误',
type: 'error',
duration: 3 * 1000
})
}else{
MessageBox.confirm('认证失败,请重新登陆.', '确认退出', {
confirmButtonText: '重新登陆',
cancelButtonText: '取消',
type: 'warning'
@ -56,18 +63,23 @@ service.interceptors.response.use(
})
}
// if the custom code is not 20000, it is judged as an error.
else if ( res.code == 400 || res.code > 401) {
} else if (res.code >= 400) {
var data = JSON.stringify(res.msg)
if(data.indexOf('无法使用提供的认证信息登录')!=-1){
Message({
message: '用户名或密码错误',
type: 'error',
duration: 3 * 1000
})
}else{
Message({
message: res.msg || '请求出错',
type: 'error',
duration: 5 * 1000
duration: 3 * 1000
})
return Promise.reject(new Error(res.msg || '请求出错'))
}
// return Promise.reject(new Error(res.error || '请求出错'))
} else {
return res
}

View File

@ -15,6 +15,7 @@
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查访问地址,或者点击按钮返回首页.</div>
<a href="" class="bullshit__return-home">首页</a>
<a class="bullshit__return-home" style="margin-left:2px" @click="goConsumer">学员列表页</a>
</div>
</div>
</div>
@ -28,6 +29,11 @@ export default {
message() {
return '您好像没有该页面的访问权限...'
}
},
methods:{
goConsumer(){
this.$router.push('/crm/consumer')
}
}
}
</script>

View File

@ -110,7 +110,7 @@ export default {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.$router.push({ path: this.redirect || '/crm/consumer' })
this.loading = false
}).catch(() => {
this.loading = false

View File

@ -14,6 +14,9 @@
<el-table-column align="header-center" label="账户">
<template slot-scope="scope">{{ scope.row.username }}</template>
</el-table-column>
<el-table-column align="header-center" label="姓名">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="创建日期">
<template slot-scope="scope">
<span>{{ scope.row.date_joined }}</span>
@ -21,12 +24,21 @@
</el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-button
type="primary"
size="small"
@click="handleUpdate(scope)"
icon="el-icon-edit"
:disabled="!checkPermission(['user_update'])"
v-if="!scope.row.is_superuser"
></el-button>
<el-button
type="danger"
size="small"
@click="handleDelete(scope)"
icon="el-icon-delete"
:disabled="!checkPermission(['user_delete'])"
v-if="!scope.row.is_superuser"
></el-button>
</template>
</el-table-column>
@ -40,7 +52,7 @@
@pagination="getList"
/>
<el-dialog :visible.sync="dialogVisible" title="新增管理员">
<el-dialog :visible.sync="dialogVisible" :title="dialogType==='update'?'编辑管理员':'新增管理员'">
<el-form
:model="user"
label-width="80px"
@ -51,8 +63,18 @@
<el-form-item label="账户" prop="username">
<el-input v-model="user.username" placeholder="账户" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="user.password" placeholder="密码" />
<el-form-item label="姓名" prop="name">
<el-input v-model="user.name" placeholder="姓名" />
</el-form-item>
<el-form-item label="角色" prop="roles">
<el-select v-model="user.roles" multiple placeholder="请选择" style="width:100%">
<el-option
v-for="item in roles"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<div style="text-align:right;">
@ -63,7 +85,8 @@
</div>
</template>
<script>
import { getUserList, createUser, deleteUser} from "@/api/user";
import { getUserList, createUser, deleteUser, updateUser} from "@/api/user";
import { getRoles } from "@/api/role"
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
@ -71,13 +94,14 @@ const defaultUser = {
id: "",
username: "",
password: "",
is_superuser:true
is_superuser:false
};
export default {
components: { Pagination },
data() {
return {
user: defaultUser,
roles:[],
userList: [],
total: 0,
listLoading: true,
@ -88,21 +112,26 @@ export default {
dialogVisible: false,
rule1: {
username: [{ required: true, message: "请输入账号", trigger: "change" }],
password: [
{ required: true, message: "请输入密码", trigger: "change" }
],
},
filterOrgText: "",
treeLoding: false,
orgData: []
orgData: [],
dialogType:'create',
dialogVisible:false
};
},
computed: {},
created() {
this.getList();
this.getRoles()
},
methods: {
checkPermission,
getRoles() {
getRoles().then(response => {
this.roles = response.data
});
},
getList() {
this.listLoading = true;
getUserList(this.listQuery).then(response => {
@ -114,10 +143,17 @@ export default {
handleAddUser() {
this.user = Object.assign({}, defaultUser);
this.dialogVisible = true;
this.dialogType = 'create'
this.$nextTick(() => {
this.$refs["userForm"].clearValidate();
});
},
handleUpdate(scope){
this.user = Object.assign({}, scope.row);
this.dialogVisible = true;
this.dialogType = 'update'
} ,
handleDelete(scope) {
this.$confirm("确认删除?", "警告", {
confirmButtonText: "确认",
@ -139,18 +175,26 @@ export default {
async confirmUser(form) {
this.$refs[form].validate(valid => {
if (valid) {
createUser(this.user).then(res => {
// this.user = res.data
// this.userList.unshift(this.user)
const isEdit = this.dialogType === "update";
if (isEdit) {
updateUser(this.user.id, this.user).then(res => {
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "新增成功",
this.$message({
message: "编辑成功",
type: "success",
duration: 2000
});
});
} else {
createUser(this.user).then(res => {
this.getList();
this.dialogVisible = false;
this.$message({
message: "新增成功",
type: "success"
});
});
}
} else {
return false;
}

View File

@ -0,0 +1,78 @@
<template>
<div class="app-container">
<el-form ref="elForm" :model="formData" :rules="rules" size="medium" label-width="100px">
<el-form-item label="旧密码" prop="old_password">
<el-input v-model="formData.old_password" placeholder="请输入旧密码" clearable show-password
:style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="new_password1">
<el-input v-model="formData.new_password1" placeholder="请输入新密码" clearable show-password
:style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="new_password2">
<el-input v-model="formData.new_password2" placeholder="请再次输入新密码" clearable show-password
:style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item size="large">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { changePassword } from "@/api/user"
export default {
components: {},
props: [],
data() {
return {
formData: {
old_password: undefined,
new_password1: undefined,
new_password2: undefined,
},
rules: {
old_password: [{
required: true,
message: '请输入旧密码',
trigger: 'blur'
}],
new_password1: [{
required: true,
message: '请输入新密码',
trigger: 'blur'
}],
new_password2: [{
required: true,
message: '请再次输入新密码',
trigger: 'blur'
}],
},
}
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {
submitForm() {
this.$refs['elForm'].validate(valid => {
if (!valid) return
// TODO
changePassword(this.formData).then(async(res)=>{
this.$message({
message: '密码修改成功,请重新登陆',
type: 'success'
})
await this.$store.dispatch('user/logout')
this.$router.push(`/login`)
})
})
},
resetForm() {
this.$refs['elForm'].resetFields()
},
}
}
</script>

View File

@ -169,15 +169,7 @@ export default {
const { desc, name } = this.role
this.dialogVisible = false
this.$notify({
title: 'Success',
dangerouslyUseHTMLString: true,
message: `
<div>角色名: ${name}</div>
<div>角色描述: ${desc}</div>
`,
type: 'success'
})
this.$message.success('成功')
},
}
}

View File

@ -271,7 +271,7 @@ export default {
computed: {},
created() {
this.getList();
this.getOrgList();
// this.getOrgList();
this.getRoles();
},
methods: {
@ -307,13 +307,13 @@ export default {
this.listLoading = false;
});
},
getOrgList() {
this.treeLoding = true;
getOrgList().then(response => {
this.orgData = genTree(response.data);
this.treeLoding = false;
});
},
// getOrgList() {
// this.treeLoding = true;
// getOrgList().then(response => {
// this.orgData = genTree(response.data);
// this.treeLoding = false;
// });
// },
getRoles() {
getRoles().then(response => {
this.roles = genTree(response.data);

View File

@ -18,8 +18,10 @@ Page({
duration: 1500,
nowWork:null,
msgList: [
{ title: "欢迎使用中科辐射学堂!" },
{ title: "如有疑问,请致电课程顾问" },]
{ title: "欢迎使用辐射学堂!" },
{ title: "QQ交流群:1072443859" },
{ title: "如有疑问,请致电课程顾问" },
]
},
imgH: function (e) {
let winWid = wx.getSystemInfoSync().windowWidth; //获取当前屏幕的宽度

View File

@ -11,7 +11,7 @@
</block>
</swiper>
</view>
<swiper class="swiper_container1" vertical="true" autoplay="true" circular="true" interval="3000">
<swiper class="swiper_container1" vertical="true" autoplay="true" circular="true" interval="6000">
<block wx:for="{{msgList}}" wx:key="title">
<swiper-item>
<view class="swiper_item1">{{item.title}}</view>
@ -23,7 +23,7 @@
<view class="weui-cell__hd">
<image src="/images/quota.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
</view>
<view class="weui-cell__bd">考名额监控</view>
<view class="weui-cell__bd">考名额监控</view>
<view class="weui-cell__ft weui-cell__ft_in-access" style="color:red">
<!-- <span wx:if="{{to_read}}">{{to_read}}条未读</span> -->
</view>
@ -32,7 +32,7 @@
<view class="weui-cell__hd">
<image src="/images/candidate.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
</view>
<view class="weui-cell__bd">证书查询</view>
<view class="weui-cell__bd">成绩报告单查询</view>
<view class="weui-cell__ft weui-cell__ft_in-access" style="color:red">
<!-- <span wx:if="{{to_read}}">{{to_read}}条未读</span> -->
</view>

View File

@ -18,15 +18,15 @@
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useIsolateContext": true,
"useCompilerModule": true,
"userConfirmedUseCompilerModuleSwitch": false
"useCompilerModule": false,
"userConfirmedUseCompilerModuleSwitch": false,
"compileHotReLoad": false,
"useIsolateContext": true
},
"compileType": "miniprogram",
"libVersion": "2.10.3",

View File

@ -0,0 +1,21 @@
# Generated by Django 3.0.4 on 2020-08-10 03:51
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('crm', '0019_auto_20200802_0931'),
]
operations = [
migrations.AddField(
model_name='consumer',
name='create_admin',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -1,6 +1,6 @@
from django.db import models
import django.utils.timezone as timezone
from rbac.models import SoftCommonModel, CommonModel
from rbac.models import SoftCommonModel, CommonModel, UserProfile
from question.models import Questioncat, Question
from examtest.models_paper import WorkScope
from django.contrib.postgres.fields import JSONField
@ -71,6 +71,8 @@ class Consumer(CommonModel):
ID_number = models.CharField('身份证号', max_length=100, null=True, blank=True)
realname = models.CharField('真实姓名', max_length=100, null=True, blank=True)
create_admin = models.ForeignKey(UserProfile, default=1, on_delete=models.DO_NOTHING)
class Meta:
verbose_name = '客户'

View File

@ -124,6 +124,8 @@ class ConsumerViewSet(ModelViewSet):
def get_queryset(self):
queryset = self.queryset
queryset = self.get_serializer_class().setup_eager_loading(queryset)
if not self.request.user.is_superuser:
queryset = queryset.filter(create_admin = self.request.user)
return queryset
def create(self, request, *args, **kwargs):

View File

@ -219,7 +219,7 @@ class ExamTestViewSet(ModelViewSet):
"""
考试记录列表和详情
"""
perms_map = [{'get': 'examtest_list'},{'post': '*'}]
perms_map = [{'get': 'examtest_view'},{'post': '*'}]
pagination_class = CommonPagination
queryset = ExamTest.objects.filter(is_delete=0).all()
serializer_class = ExamTestListSerializer
@ -244,6 +244,8 @@ class ExamTestViewSet(ModelViewSet):
queryset = queryset.filter(start_time__gte=self.request.query_params['start'] )
if self.request.query_params.get('end'):
queryset = queryset.filter(start_time__lte=self.request.query_params['end'])
if not self.request.user.is_superuser:
queryset = queryset.filter(consumer__create_admin = self.request.user)
return queryset
@action(methods=['get'], detail=False,url_path='self', url_name='selftest', perms_map = [{'*':'my_examtest'}])
def selftest(self, request, pk=None):

View File

@ -65,13 +65,13 @@ class UserViewSet(ModelViewSet):
"""
perms_map = ({'get': 'user_list'}, {'post': 'user_create'}, {'put': 'user_update'},
{'delete': 'user_delete'})
queryset = UserProfile.objects.filter(is_delete=0).all().order_by('-id')
queryset = UserProfile.objects.filter(is_delete=0).all()
serializer_class = UserListSerializer
pagination_class = CommonPagination
# filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = ('is_active',)
# search_fields = ('username', 'name', 'mobile', 'email')
ordering_fields = ('-id',)
ordering_fields = ('id',)
# authentication_classes = (JSONWebTokenAuthentication,)
# permission_classes = (RbacPermission,IsAuthenticated)
@ -107,28 +107,20 @@ class UserViewSet(ModelViewSet):
if password:
request.data['password'] = make_password(password)
else:
request.data['password'] = make_password('0000')
request.data['password'] = make_password('fs0000')
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, headers=headers)
@action(methods=['post'], detail=True, permission_classes=[IsAuthenticated],
url_path='change_passwd', url_name='change_passwd')
def set_password(self, request, pk=None):
perms = UserInfoView.get_permission_from_role(request)
user = UserProfile.objects.get(id=pk)
if 'admin' in perms or 'user_all' in perms or request.user.is_superuser:
new_password1 = request.data['new_password1']
new_password2 = request.data['new_password2']
if new_password1 == new_password2:
user.set_password(new_password2)
user.save()
return Response({"detail": '密码修改成功!'})
else:
return Response({"error": '新密码两次输入不一致!'})
else:
@action(methods=['put'], detail=False, permission_classes=[IsAuthenticated], # perms_map={'put':'change_password'}
url_name='change_password')
def password(self, request, pk=None):
"""
修改密码
"""
user = request.user
old_password = request.data['old_password']
if check_password(old_password, user.password):
new_password1 = request.data['new_password1']
@ -136,9 +128,9 @@ class UserViewSet(ModelViewSet):
if new_password1 == new_password2:
user.set_password(new_password2)
user.save()
return Response({"error": '密码修改成功!'})
return Response('密码修改成功!')
else:
return Response({"error": '新密码两次输入不一致!'})
return Response({'error': '新密码两次输入不一致!'})
else:
return Response({"error": '旧密码错误!'})
return Response({'error':'旧密码错误!'})

View File

@ -45,7 +45,7 @@ class FitJSONRenderer(JSONRenderer):
response = renderer_context.get("response")
response_body.code = response.status_code
if response_body.code >= 400: # drf异常
response_body.msg = response.data
response_body.msg = data['detail'] if 'detail' in data else data
elif data and 'error' in data and data['error']:# 自传异常,key为error
response_body.code = data.get("code",400)
response_body.msg = data.get("error", "")