vue perm and some bugs

This commit is contained in:
caoqianming 2020-05-18 08:43:22 +08:00
parent e266347d47
commit 4123fe657b
15 changed files with 591 additions and 228 deletions

View File

@ -15,6 +15,7 @@
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
}, },
"dependencies": { "dependencies": {
"@riophae/vue-treeselect": "^0.4.0",
"axios": "0.18.1", "axios": "0.18.1",
"element-ui": "2.13.0", "element-ui": "2.13.0",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",

View File

@ -4,7 +4,7 @@ export function getDictTypeList(query) {
return request({ return request({
url: '/system/dicttype/', url: '/system/dicttype/',
method: 'get', method: 'get',
params:query params: query
}) })
} }
export function createDictType(data) { export function createDictType(data) {
@ -32,7 +32,7 @@ export function getDictList(query) {
return request({ return request({
url: '/system/dict/', url: '/system/dict/',
method: 'get', method: 'get',
params:query params: query
}) })
} }
export function createDict(data) { export function createDict(data) {
@ -54,4 +54,4 @@ export function deleteDict(id) {
url: `/system/dict/${id}/`, url: `/system/dict/${id}/`,
method: 'delete' method: 'delete'
}) })
} }

28
client/src/api/perm.js Normal file
View File

@ -0,0 +1,28 @@
import request from '@/utils/request'
export function getPermAll() {
return request({
url: '/system/permission/',
method: 'get'
})
}
export function createPerm(data) {
return request({
url: '/system/permission/',
method: 'post',
data
})
}
export function updatePerm(id, data) {
return request({
url: `/system/permission/${id}/`,
method: 'put',
data
})
}
export function deletePerm(id) {
return request({
url: `/system/permission/${id}/`,
method: 'delete'
})
}

View File

@ -24,7 +24,7 @@ export default {
}, },
data() { data() {
return { return {
title: 'Vue Admin Template', title: '认证系统',
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png' logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
} }
} }

View File

@ -78,7 +78,7 @@ export const asyncRoutes = [
path: 'organization', path: 'organization',
name: 'Organization', name: 'Organization',
component: () => import('@/views/system/organization'), component: () => import('@/views/system/organization'),
meta: { title: '部门管理', icon: 'tree', perms: ['organization_manage'] } meta: { title: '部门管理', icon: 'tree', perms: ['org_manage'] }
}, },
{ {
path: 'role', path: 'role',
@ -100,6 +100,41 @@ export const asyncRoutes = [
} }
] ]
}, },
{
path: '/develop',
component: Layout,
redirect: '/develop/perm',
name: 'Develop',
meta: { title: '开发配置', icon: 'example', perms: ['dev_set'] },
children: [
{
path: 'perm',
name: 'Perm',
component: () => import('@/views/system/perm'),
meta: { title: '权限菜单', icon: 'example', perms: ['perm_manage'] }
},
{
path: 'external-link',
component: Layout,
children: [
{
path: process.env.VUE_APP_BASE_API + '/docs/',
meta: { title: '接口文档', icon: 'link', perms: ['dev_docs'] }
}
]
},
{
path: 'external-link',
component: Layout,
children: [
{
path: process.env.VUE_APP_BASE_API + '/admin/',
meta: { title: 'Django后台', icon: 'link', perms: ['dev_admin'] }
}
]
}
]
},
// 404 page must be placed at the end !!! // 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true } { path: '*', redirect: '/404', hidden: true }
] ]

View File

@ -1,6 +1,6 @@
import defaultSettings from '@/settings' import defaultSettings from '@/settings'
const title = defaultSettings.title || 'Vue Admin Template' const title = defaultSettings.title || '认证系统'
export default function getPageTitle(pageTitle) { export default function getPageTitle(pageTitle) {
if (pageTitle) { if (pageTitle) {

View File

@ -2,9 +2,12 @@
<div class="app-container"> <div class="app-container">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :md="6"> <el-col :md="6">
<div style="margin-top:10px"> <el-card>
<el-button type="primary" icon="el-icon-plus" @click="handleAddDictType">新增</el-button> <div slot="header" class="clearfix">
</div> <span>字典类型</span>
<el-button v-if="checkPermission(['dicttype_add'])" style="float: right; padding: 3px 0" type="text" icon="el-icon-plus" @click="handleAddDictType" >新增</el-button>
</div>
<el-tree <el-tree
ref="tree" ref="tree"
v-loading="treeLoding" v-loading="treeLoding"
@ -16,13 +19,36 @@
:filter-node-method="filterNode" :filter-node-method="filterNode"
style="margin-top:10px;" style="margin-top:10px;"
@node-click="handleDictTypeClick" @node-click="handleDictTypeClick"
/> >
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
type="text"
size="mini"
v-if="checkPermission(['dicttype_delete'])"
@click="handleEditDictType(data)">
编辑
</el-button>
<!-- <el-button
type="text"
size="mini"
@click="handleDeleteDictType">
删除
</el-button> -->
</span>
</span>
</el-tree>
</el-card>
</el-col> </el-col>
<el-col :md="18"> <el-col :md="18">
<div style="margin-top:10px"> <el-card>
<el-button type="primary" icon="el-icon-plus" @click="handleAddDict">新增</el-button> <div slot="header" class="clearfix">
</div> <span>字典详情</span>
</div>
<el-button v-show="listQuery.type" type="primary" icon="el-icon-plus" @click="handleAddDict" >新增</el-button>
<el-table <el-table
v-show="listQuery.type"
v-loading="listLoading" v-loading="listLoading"
:data="dictList.results" :data="dictList.results"
style="width: 100%;margin-top:10px;" style="width: 100%;margin-top:10px;"
@ -65,7 +91,7 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<p v-show="!listQuery.type">点击左侧类型查看字典</p>
<pagination <pagination
v-show="dictList.count>0" v-show="dictList.count>0"
:total="dictList.count" :total="dictList.count"
@ -73,6 +99,7 @@
:limit.sync="listQuery.page_size" :limit.sync="listQuery.page_size"
@pagination="getList" @pagination="getList"
/> />
</el-card>
</el-col> </el-col>
</el-row> </el-row>
@ -84,6 +111,9 @@
<el-form-item label="代号" prop="code"> <el-form-item label="代号" prop="code">
<el-input v-model="dicttype.code" placeholder="代号" /> <el-input v-model="dicttype.code" placeholder="代号" />
</el-form-item> </el-form-item>
<el-form-item label="父级" prop="pid">
<treeselect v-model="dicttype.pid" :multiple="false" :options="dictTypeData" placeholder="父级"/>
</el-form-item>
</el-form> </el-form>
<div style="text-align:right;"> <div style="text-align:right;">
<el-button type="danger" @click="dgV1=false">取消</el-button> <el-button type="danger" @click="dgV1=false">取消</el-button>
@ -106,11 +136,23 @@
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<style>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>
<script> <script>
import { getDictTypeList, getDictList, createDictType, createDict, updateDictType, updateDict, deleteDictType, deleteDict } from '@/api/dict' import { getDictTypeList, getDictList, createDictType, createDict, updateDictType, updateDict, deleteDictType, deleteDict } from '@/api/dict'
import { genTree } from '@/utils' import { genTree } from '@/utils'
import checkPermission from '@/utils/permission' import checkPermission from '@/utils/permission'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
const defaultDictType = { const defaultDictType = {
id: '', id: '',
@ -126,16 +168,16 @@ const defaultDict = {
type: null type: null
} }
export default { export default {
components: { Pagination }, components: { Pagination, Treeselect },
data() { data() {
return { return {
dicttype: defaultDictType, dicttype: defaultDictType,
dict: defaultDict, dict: defaultDict,
dictList: {count:0}, dictList: { count: 0 },
listLoading: true, listLoading: true,
listQuery: { listQuery: {
page: 1, page: 1,
page_size: 20, page_size: 20
}, },
dgV1: false, dgV1: false,
dgT1: 'new', dgT1: 'new',
@ -177,11 +219,11 @@ export default {
getList() { getList() {
this.listLoading = true this.listLoading = true
getDictList(this.listQuery).then(response => { getDictList(this.listQuery).then(response => {
if (response.code>=200){ if (response.code >= 200) {
if (response.data.results){ if (response.data.results) {
this.dictList = response.data this.dictList = response.data
} }
this.listLoading = false this.listLoading = false
} }
}) })
}, },
@ -202,42 +244,54 @@ export default {
}, },
handleAddDict() { handleAddDict() {
this.dict = Object.assign({}, defaultDict) this.dict = Object.assign({}, defaultDict)
this.dict.type = this.listQuery.type
this.dgT2 = 'new' this.dgT2 = 'new'
this.dgV2 = true this.dgV2 = true
this.$nextTick(() => { this.$nextTick(() => {
this.$refs['Form2'].clearValidate() this.$refs['Form2'].clearValidate()
}) })
}, },
// handleEdit(scope) { handleEdit(scope) {
// this.user = Object.assign({}, scope.row) // copy obj this.dict = Object.assign({}, scope.row) // copy obj
// this.dialogType = 'edit' this.dgT2 = 'edit'
// this.dialogVisible = true this.dgV2 = true
// this.$nextTick(() => { this.$nextTick(() => {
// this.$refs['Form'].clearValidate() this.$refs['Form2'].clearValidate()
// }) })
// }, },
// handleDelete(scope) { handleEditDictType(val) {
// this.$confirm('确认删除?', '警告', { this.dicttype = val // copy obj
// confirmButtonText: '确认', this.dgT1 = 'edit'
// cancelButtonText: '取消', this.dgV1 = true
// type: 'error' this.$nextTick(() => {
// }) this.$refs['Form1'].clearValidate()
// .then(async() => { })
// await deleteUser(scope.row.id)
// this.dictList.splice(scope.row.index, 1) },
// this.$message({ handleDelete(scope) {
// type: 'success', this.$confirm('确认删除?', '警告', {
// message: '成功删除!' confirmButtonText: '确认',
// }) cancelButtonText: '取消',
// }) type: 'error'
// .catch(err => { })
// console.error(err) .then(async() => {
// }) const { code } = await deleteDict(scope.row.id)
// }, if (code>=200){
this.dictList.results.splice(scope.row.index, 1)
this.$message({
type: 'success',
message: '成功删除!'
})
}
})
.catch(err => {
console.error(err)
})
},
async confirm1(form) { async confirm1(form) {
this.$refs[form].validate(valid => { this.$refs[form].validate(valid => {
if (valid) { if (valid) {
const isEdit = this.dialogType === 'edit' const isEdit = this.dgT1 === 'edit'
if (isEdit) { if (isEdit) {
updateDictType(this.dicttype.id, this.dicttype).then(res => { updateDictType(this.dicttype.id, this.dicttype).then(res => {
if (res.code >= 200) { if (res.code >= 200) {
@ -269,6 +323,42 @@ export default {
return false return false
} }
}) })
},
async confirm2(form) {
this.$refs[form].validate(valid => {
if (valid) {
const isEdit = this.dgT2 === 'edit'
if (isEdit) {
updateDict(this.dict.id, this.dict).then(res => {
if (res.code >= 200) {
this.getList()
this.dgV2 = false
this.$notify({
title: '成功',
message: '编辑成功',
type: 'success',
duration: 2000
})
}
})
} else {
createDict(this.dict).then(res => {
if (res.code >= 200) {
this.getList()
this.dgV2 = false
this.$notify({
title: '成功',
message: '新增成功',
type: 'success',
duration: 2000
})
}
})
}
} else {
return false
}
})
} }
} }
} }

View File

@ -60,13 +60,7 @@
<el-input v-model="org.name" placeholder="名称" /> <el-input v-model="org.name" placeholder="名称" />
</el-form-item> </el-form-item>
<el-form-item label="上级部门" prop="pid"> <el-form-item label="上级部门" prop="pid">
<el-cascader <treeselect v-model="org.pid" :multiple="false" :options="tableData" placeholder="父级"/>
v-model="org.pid"
:options="pidList"
:props="{ checkStrictly: true }"
clearable
style="width:100%"
/>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div style="text-align:right;"> <div style="text-align:right;">
@ -81,13 +75,16 @@
import { getOrgAll, createOrg, deleteOrg, updateOrg } from '@/api/org' import { getOrgAll, createOrg, deleteOrg, updateOrg } from '@/api/org'
import { genTree } from '@/utils' import { genTree } from '@/utils'
import checkPermission from '@/utils/permission' import checkPermission from '@/utils/permission'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
const defaultOrg = { const defaultOrg = {
id: '', id: null,
name: '', name: '',
pid: '' pid: null
} }
export default { export default {
components: { Treeselect },
data() { data() {
return { return {
org: { org: {
@ -98,7 +95,6 @@ export default {
search: '', search: '',
tableData: [], tableData: [],
orgList: [], orgList: [],
pidList: [],
listLoading: true, listLoading: true,
dialogVisible: false, dialogVisible: false,
dialogType: 'new', dialogType: 'new',
@ -120,7 +116,6 @@ export default {
this.orgList = response.data this.orgList = response.data
const data = genTree(response.data) const data = genTree(response.data)
this.tableData = data this.tableData = data
this.pidList = data
this.listLoading = false this.listLoading = false
}) })
}, },
@ -174,7 +169,6 @@ export default {
if (valid) { if (valid) {
const isEdit = this.dialogType === 'edit' const isEdit = this.dialogType === 'edit'
if (isEdit) { if (isEdit) {
this.org.pid = this.org.pid.pop()
updateOrg(this.org.id, this.org).then(() => { updateOrg(this.org.id, this.org).then(() => {
this.getList() this.getList()
this.dialogVisible = false this.dialogVisible = false
@ -186,7 +180,6 @@ export default {
}) })
}) })
} else { } else {
this.org.pid = this.org.pid.pop()
createOrg(this.org).then(res => { createOrg(this.org).then(res => {
// this.org = res.data // this.org = res.data
// this.tableData.unshift(this.org) // this.tableData.unshift(this.org)

View File

@ -0,0 +1,215 @@
<template>
<div class="app-container">
<div style="margin-top:10px">
<el-input
v-model="search"
placeholder="输入权限名称进行搜索"
style="width: 200px;"
class="filter-item"
@keyup.native="handleFilter"
/>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
</div>
<el-table
v-loading="listLoading"
:data="tableData.filter(data => !search || data.name.toLowerCase().includes(search.toLowerCase()))"
style="width: 100%;margin-top:10px;"
border
fit
stripe
highlight-current-row
max-height="600"
row-key="id"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
<el-table-column label="菜单名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="类型">
<template slot-scope="scope">{{ scope.row.type }}</template>
</el-table-column>
<el-table-column label="代号">
<template slot-scope="scope">{{ scope.row.method }}</template>
</el-table-column>
<el-table-column label="排序">
<template slot-scope="scope">{{ scope.row.sort }}</template>
</el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-button
type="primary"
size="small"
icon="el-icon-edit"
:disabled="!checkPermission(['perm_update'])"
@click="handleEdit(scope)"
/>
<el-button
type="danger"
size="small"
icon="el-icon-delete"
:disabled="!checkPermission(['perm_delete'])"
@click="handleDelete(scope)"
/>
</template>
</el-table-column>
</el-table>
<el-dialog :visible.sync="dialogVisible" :title="dialogType==='edit'?'编辑':'新增'">
<el-form ref="Form" :model="perm" label-width="80px" label-position="right" :rules="rule1">
<el-form-item label="类型">
<el-radio-group v-model="perm.type">
<el-radio label="目录"></el-radio>
<el-radio label="菜单"></el-radio>
<el-radio label="接口"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="perm.name" placeholder="名称" />
</el-form-item>
<el-form-item label="代号" prop="method">
<el-input v-model="perm.method" placeholder="代号" />
</el-form-item>
<el-form-item label="父级" prop="pid">
<treeselect v-model="perm.pid" :multiple="false" :options="tableData" placeholder="父级"/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="perm.sort" :min="1" label="排序"></el-input-number>
</el-form-item>
</el-form>
<div style="text-align:right;">
<el-button type="danger" @click="dialogVisible=false">取消</el-button>
<el-button type="primary" @click="confirmPerm('Form')">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getPermAll, createPerm, deletePerm, updatePerm } from '@/api/perm'
import { genTree } from '@/utils'
import checkPermission from '@/utils/permission'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
const defaultPerm = {
id: null,
name: '',
type: '目录',
method: '',
sort:1,
pid: null
}
export default {
components: { Treeselect },
data() {
return {
perm: defaultPerm,
search: '',
tableData: [],
permList: [],
listLoading: true,
dialogVisible: false,
dialogType: 'new',
rule1: {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
method: [{ required: true, message: '请输入代号', trigger: 'blur' }]
}
}
},
computed: {},
created() {
this.getList()
},
methods: {
checkPermission,
getList() {
this.listLoading = true
getPermAll().then(response => {
this.permList = response.data
const data = genTree(response.data)
this.tableData = data
this.listLoading = false
})
},
resetFilter() {
this.getList()
},
handleFilter() {
const newData = this.permList.filter(
data =>
!this.search ||
data.name.toLowerCase().includes(this.search.toLowerCase())
)
this.tableData = genTree(newData)
},
handleAdd() {
this.perm = Object.assign({}, defaultPerm)
this.dialogType = 'new'
this.dialogVisible = true
this.$nextTick(() => {
this.$refs['Form'].clearValidate()
})
},
handleEdit(scope) {
this.perm = Object.assign({}, scope.row) // copy obj
this.dialogType = 'edit'
this.dialogVisible = true
this.$nextTick(() => {
this.$refs['Form'].clearValidate()
})
},
handleDelete(scope) {
this.$confirm('确认删除?', '警告', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'error'
})
.then(async() => {
await deletePerm(scope.row.id)
this.getList()
this.$message({
type: 'success',
message: '成功删除!'
})
})
.catch(err => {
console.error(err)
})
},
async confirmPerm(form) {
this.$refs[form].validate(valid => {
if (valid) {
const isEdit = this.dialogType === 'edit'
if (isEdit) {
updatePerm(this.perm.id, this.perm).then(() => {
this.getList()
this.dialogVisible = false
this.$notify({
title: '成功',
message: '编辑成功',
type: 'success',
duration: 2000
})
})
} else {
createPerm(this.perm).then(res => {
// this.perm = res.data
// this.tableData.unshift(this.perm)
this.getList()
this.dialogVisible = false
this.$notify({
title: '成功',
message: '新增成功',
type: 'success',
duration: 2000
})
})
}
} else {
return false
}
})
}
}
}
</script>

View File

@ -1,8 +1,7 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="6">
<el-col :md="6">
<el-input v-model="filterOrgText" placeholder="输入部门名进行过滤" /> <el-input v-model="filterOrgText" placeholder="输入部门名进行过滤" />
<el-tree <el-tree
@ -18,7 +17,7 @@
@node-click="handleOrgClick" @node-click="handleOrgClick"
/> />
</el-col> </el-col>
<el-col :md="18"> <el-col :span="18">
<div> <div>
<el-select <el-select
v-model="listQuery.is_active" v-model="listQuery.is_active"
@ -138,13 +137,7 @@
<el-input v-model="user.username" placeholder="账户" /> <el-input v-model="user.username" placeholder="账户" />
</el-form-item> </el-form-item>
<el-form-item label="所属部门" prop="dept"> <el-form-item label="所属部门" prop="dept">
<el-cascader <treeselect v-model="user.dept" :multiple="false" :options="orgData" placeholder="所属部门"/>
v-model="user.dept"
:options="orgData"
:props="{ checkStrictly: true }"
clearable
style="width:100%"
/>
</el-form-item> </el-form-item>
<el-form-item label="角色" prop="roles"> <el-form-item label="角色" prop="roles">
<el-select v-model="user.roles" multiple placeholder="请选择" style="width:100%"> <el-select v-model="user.roles" multiple placeholder="请选择" style="width:100%">
@ -166,7 +159,7 @@
:before-upload="beforeAvatarUpload" :before-upload="beforeAvatarUpload"
:headers="myHeaders" :headers="myHeaders"
> >
<img v-if="user.avatar" :src="user.avatar" class="avatar"> <img v-if="user.avatar" :src="user.avatar" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon" /> <i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload> </el-upload>
</el-form-item> </el-form-item>
@ -204,36 +197,37 @@
} }
</style> </style>
<script> <script>
import { getUserList, createUser, deleteUser, updateUser } from '@/api/user' import { getUserList, createUser, deleteUser, updateUser } from "@/api/user";
import { getOrgAll } from '@/api/org' import { getOrgAll } from "@/api/org";
import { getRoleAll } from '@/api/role' import { getRoleAll } from "@/api/role";
import { genTree } from '@/utils' import { genTree } from "@/utils";
import checkPermission from '@/utils/permission' import checkPermission from "@/utils/permission";
import { uploadUrl } from '@/api/file' import { uploadUrl } from "@/api/file";
import { getToken } from '@/utils/auth' import { getToken } from "@/utils/auth";
import Pagination from '@/components/Pagination' // secondary package based on el-pagination import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
const defaultUser = { const defaultUser = {
id: '', id: "",
name: '', name: "",
username: '', username: "",
dept: null, dept: null,
avatar: '' avatar: "/media/default/avatar.png"
} };
export default { export default {
components: { Pagination }, components: { Pagination, Treeselect },
data() { data() {
return { return {
user: { user: {
id: '', id: "",
name: '', name: "",
username: '', username: "",
dept: null, dept: null,
avatar: '' avatar: ""
}, },
myHeaders: { Authorization: 'JWT ' + getToken() }, myHeaders: { Authorization: "JWT " + getToken() },
uploadUrl: uploadUrl(), uploadUrl: uploadUrl(),
userList: [], userList: {'count':0},
roles: [], roles: [],
listLoading: true, listLoading: true,
listQuery: { listQuery: {
@ -241,163 +235,161 @@ export default {
page_size: 20 page_size: 20
}, },
enabledOptions: [ enabledOptions: [
{ key: 'true', display_name: '激活' }, { key: "true", display_name: "激活" },
{ key: 'false', display_name: '禁用' } { key: "false", display_name: "禁用" }
], ],
dialogVisible: false, dialogVisible: false,
dialogType: 'new', dialogType: "new",
rule1: { rule1: {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }], name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
username: [{ required: true, message: '请输入账号', trigger: 'change' }] username: [{ required: true, message: "请输入账号", trigger: "change" }]
// password: [ // password: [
// { required: true, message: '请输入密码', trigger: 'change' } // { required: true, message: '请输入密码', trigger: 'change' }
// ], // ],
}, },
filterOrgText: '', filterOrgText: "",
treeLoding: false, treeLoding: false,
orgData: [] orgData: []
} };
}, },
computed: {}, computed: {},
watch: { watch: {
filterOrgText(val) { filterOrgText(val) {
this.$refs.tree.filter(val) this.$refs.tree.filter(val);
} }
}, },
created() { created() {
this.getList() this.getList();
this.getOrgAll() this.getOrgAll();
this.getRoleAll() this.getRoleAll();
}, },
methods: { methods: {
checkPermission, checkPermission,
handleAvatarSuccess(res, file) { handleAvatarSuccess(res, file) {
if (res.code === 200) { if (res.code === 200) {
this.user.avatar = res.data.path this.user.avatar = res.data.path;
} else { } else {
this.$message.error('头像上传失败!') this.$message.error("头像上传失败!");
} }
}, },
beforeAvatarUpload(file) { beforeAvatarUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2 const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) { if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!') this.$message.error("上传头像图片大小不能超过 2MB!");
} }
return isLt2M return isLt2M;
}, },
filterNode(value, data) { filterNode(value, data) {
if (!value) return true if (!value) return true;
return data.label.indexOf(value) !== -1 return data.label.indexOf(value) !== -1;
}, },
handleOrgClick(obj, node, vue) { handleOrgClick(obj, node, vue) {
this.listQuery.page = 1 this.listQuery.page = 1;
this.listQuery.dept = obj.id this.listQuery.dept = obj.id;
this.getList() this.getList();
}, },
getList() { getList() {
this.listLoading = true this.listLoading = true;
getUserList(this.listQuery).then(response => { getUserList(this.listQuery).then(response => {
this.userList = response.data if (response.data) {
this.listLoading = false this.userList = response.data
}) }
this.listLoading = false;
});
}, },
getOrgAll() { getOrgAll() {
this.treeLoding = true this.treeLoding = true;
getOrgAll().then(response => { getOrgAll().then(response => {
this.orgData = genTree(response.data) this.orgData = genTree(response.data);
this.treeLoding = false this.treeLoding = false;
}) });
}, },
getRoleAll() { getRoleAll() {
getRoleAll().then(response => { getRoleAll().then(response => {
this.roles = genTree(response.data) this.roles = genTree(response.data);
}) });
}, },
resetFilter() { resetFilter() {
this.listQuery = { this.listQuery = {
page: 1, page: 1,
page_size: 20 page_size: 20
} };
this.getList() this.getList();
}, },
handleFilter() { handleFilter() {
this.listQuery.page = 1 this.listQuery.page = 1;
this.getList() this.getList();
}, },
handleAddUser() { handleAddUser() {
this.user = Object.assign({}, defaultUser) this.user = Object.assign({}, defaultUser);
this.dialogType = 'new' this.dialogType = "new";
this.dialogVisible = true this.dialogVisible = true;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs['Form'].clearValidate() this.$refs["Form"].clearValidate();
}) });
}, },
handleEdit(scope) { handleEdit(scope) {
this.user = Object.assign({}, scope.row) // copy obj this.user = Object.assign({}, scope.row); // copy obj
this.dialogType = 'edit' this.dialogType = "edit";
this.dialogVisible = true this.dialogVisible = true;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs['Form'].clearValidate() this.$refs["Form"].clearValidate();
}) });
}, },
handleDelete(scope) { handleDelete(scope) {
this.$confirm('确认删除?', '警告', { this.$confirm("确认删除?", "警告", {
confirmButtonText: '确认', confirmButtonText: "确认",
cancelButtonText: '取消', cancelButtonText: "取消",
type: 'error' type: "error"
}) })
.then(async() => { .then(async () => {
await deleteUser(scope.row.id) await deleteUser(scope.row.id);
this.userList.splice(scope.row.index, 1) this.userList.splice(scope.row.index, 1);
this.$message({ this.$message({
type: 'success', type: "success",
message: '成功删除!' message: "成功删除!"
}) });
}) })
.catch(err => { .catch(err => {
console.error(err) console.error(err);
}) });
}, },
async confirm(form) { async confirm(form) {
this.$refs[form].validate(valid => { this.$refs[form].validate(valid => {
if (valid) { if (valid) {
const isEdit = this.dialogType === 'edit' const isEdit = this.dialogType === "edit";
if (isEdit) { if (isEdit) {
if (this.user.dept instanceof Array) {
this.user.dept = this.user.dept.pop()
}
updateUser(this.user.id, this.user).then(res => { updateUser(this.user.id, this.user).then(res => {
if (res.code >= 200) { if (res.code >= 200) {
this.getList() this.getList();
this.dialogVisible = false this.dialogVisible = false;
this.$notify({ this.$notify({
title: '成功', title: "成功",
message: '编辑成功', message: "编辑成功",
type: 'success', type: "success",
duration: 2000 duration: 2000
}) });
} }
}) });
} else { } else {
this.user.dept = this.user.dept.pop()
createUser(this.user).then(res => { createUser(this.user).then(res => {
if (res.code >= 200) { if (res.code >= 200) {
this.getList() this.getList();
this.dialogVisible = false this.dialogVisible = false;
this.$notify({ this.$notify({
title: '成功', title: "成功",
message: '新增成功', message: "新增成功",
type: 'success', type: "success",
duration: 2000 duration: 2000
}) });
} }
}) });
} }
} else { } else {
return false return false;
} }
}) });
} }
} }
} };
</script> </script>

View File

@ -0,0 +1,26 @@
# Generated by Django 3.0.5 on 2020-05-17 06:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('system', '0002_dict_dicttype'),
]
operations = [
migrations.AlterModelOptions(
name='dict',
options={'verbose_name': '字典', 'verbose_name_plural': '字典'},
),
migrations.AlterModelOptions(
name='dicttype',
options={'verbose_name': '字典类型', 'verbose_name_plural': '字典类型'},
),
migrations.AlterField(
model_name='permission',
name='method',
field=models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='方法/代号'),
),
]

View File

@ -14,7 +14,12 @@ class DictType(SoftModel):
code = models.CharField('代号', unique=True, max_length=30) code = models.CharField('代号', unique=True, max_length=30)
pid = models.ForeignKey('self', null=True, blank=True, pid = models.ForeignKey('self', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name='') on_delete=models.SET_NULL, verbose_name='')
class Meta:
verbose_name = '字典类型'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Dict(SoftModel): class Dict(SoftModel):
""" """
@ -28,6 +33,12 @@ class Dict(SoftModel):
pid = models.ForeignKey('self', null=True, blank=True, pid = models.ForeignKey('self', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name='') on_delete=models.SET_NULL, verbose_name='')
class Meta:
verbose_name = '字典'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Position(BaseModel): class Position(BaseModel):
""" """
@ -60,7 +71,7 @@ class Permission(SoftModel):
sort = models.IntegerField('排序标记', default=1) sort = models.IntegerField('排序标记', default=1)
pid = models.ForeignKey('self', null=True, blank=True, pid = models.ForeignKey('self', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name='') on_delete=models.SET_NULL, verbose_name='')
method = models.CharField('方法/代号', max_length=50, null=True, blank=True) method = models.CharField('方法/代号', max_length=50, unique=True, null=True, blank=True)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -47,9 +47,10 @@ class RbacPermission(BasePermission):
perms_map = view.perms_map perms_map = view.perms_map
_method = request._request.method.lower() _method = request._request.method.lower()
if perms_map: if perms_map:
for i in perms_map: for key in perms_map:
if (i[_method] or i['*']) in perms: if key == _method or key == '*':
return True if perms_map[key] in perms or perms_map[key] == '*':
return True
return False return False
else: else:
return False return False

View File

@ -16,7 +16,7 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.tokens import RefreshToken
from utils.queryset import get_child_queryset from utils.queryset import get_child_queryset
from .models import Organization, Permission, Position, Role, User, Dict, DictType from .models import Organization, Permission, Position, Role, User, DictType, Dict
from .permission import RbacPermission, get_permission_list from .permission import RbacPermission, get_permission_list
from .serializers import (OrganizationSerializer, PermissionSerializer, from .serializers import (OrganizationSerializer, PermissionSerializer,
PositionSerializer, RoleSerializer, PositionSerializer, RoleSerializer,
@ -36,16 +36,11 @@ class LogoutView(APIView):
def get(self, request, *args, **kwargs): # 可将token加入黑名单 def get(self, request, *args, **kwargs): # 可将token加入黑名单
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
class DictTypeViewSet(ModelViewSet): class DictTypeViewSet(ModelViewSet):
""" """
数据字典类型增删改查 数据字典类型增删改查
""" """
perms_map = [{'post': 'dicttype_create'}, perms_map = {'get': '*', 'post': 'dicttype_create', 'put': 'dicttype_update','delete': 'dicttype_delete'}
{'put': 'dicttype_update'},
{'delete': 'dicttype_delete'},
{'get': 'dicttype_view'},
]
queryset = DictType.objects.all() queryset = DictType.objects.all()
serializer_class = DictTypeSerializer serializer_class = DictTypeSerializer
pagination_class = None pagination_class = None
@ -58,28 +53,18 @@ class DictViewSet(ModelViewSet):
""" """
数据字典增删改查 数据字典增删改查
""" """
perms_map = [{'post': 'dict_create'}, perms_map = {'get': '*', 'post': 'dict_create', 'put': 'dict_update','delete': 'dict_delete'}
{'put': 'dict_update'},
{'delete': 'dict_delete'},
{'get': 'dict_view'},
]
queryset = Dict.objects.all() queryset = Dict.objects.all()
serializer_class = DictSerializer serializer_class = DictSerializer
pagination_class = None
search_fields = ['^name'] search_fields = ['^name']
ordering_fields = ['id'] ordering_fields = ['id']
ordering = 'id' ordering = 'id'
class PositionViewSet(ModelViewSet): class PositionViewSet(ModelViewSet):
""" """
岗位增删改查 岗位增删改查
""" """
perms_map = [{'post': 'position_create'}, perms_map = {'get': '*', 'post': 'position_create', 'put': 'position_update','delete': 'position_delete'}
{'put': 'position_update'},
{'delete': 'position_delete'},
{'get': 'position_view'},
]
queryset = Position.objects.all() queryset = Position.objects.all()
serializer_class = PositionSerializer serializer_class = PositionSerializer
pagination_class = None pagination_class = None
@ -89,7 +74,7 @@ class PositionViewSet(ModelViewSet):
class TestView(APIView): class TestView(APIView):
perms_map = [{'get': 'test_view'}] # 单个API控权 perms_map = {'get':'test_view'} # 单个API控权
pass pass
@ -97,11 +82,8 @@ class PermissionViewSet(ModelViewSet):
""" """
权限增删改查 权限增删改查
""" """
perms_map = [{'post': 'perm_create'}, # 视图类控权 perms_map = {'get': '*', 'post': 'perm_create', 'put': 'perm_update','delete': 'perm_delete'}
{'put': 'perm_update'}, queryset = Position.objects.all()
{'delete': 'perm_delete'},
{'get': 'perm_view'},
]
queryset = Permission.objects.all() queryset = Permission.objects.all()
serializer_class = PermissionSerializer serializer_class = PermissionSerializer
pagination_class = None pagination_class = None
@ -114,15 +96,11 @@ class OrganizationViewSet(ModelViewSet):
""" """
组织机构增删改查 组织机构增删改查
""" """
perms_map = [{'post': 'org_create'}, perms_map = {'get': '*', 'post': 'org_create', 'put': 'org_update','delete': 'org_delete'}
{'put': 'org_update'},
{'delete': 'org_delete'},
{'get': 'org_view'},
]
queryset = Organization.objects.all() queryset = Organization.objects.all()
serializer_class = OrganizationSerializer serializer_class = OrganizationSerializer
pagination_class = None pagination_class = None
search_fields = ['^name'] search_fields = ['^name','^method']
ordering_fields = ['id'] ordering_fields = ['id']
ordering = 'id' ordering = 'id'
@ -131,11 +109,7 @@ class RoleViewSet(ModelViewSet):
""" """
角色增删改查 角色增删改查
""" """
perms_map = [{'post': 'role_create'}, perms_map = {'get': '*', 'post': 'role_create', 'put': 'role_update','delete': 'role_delete'}
{'put': 'role_update'},
{'delete': 'role_delete'},
{'get': 'role_view'},
]
queryset = Role.objects.all() queryset = Role.objects.all()
serializer_class = RoleSerializer serializer_class = RoleSerializer
pagination_class = None pagination_class = None
@ -148,11 +122,7 @@ class UserViewSet(ModelViewSet):
""" """
用户管理增删改查 用户管理增删改查
""" """
perms_map = [{'post': 'user_create'}, perms_map = {'get': '*', 'post': 'user_create', 'put': 'user_update','delete': 'user_delete'}
{'put': 'user_update'},
{'delete': 'user_delete'},
{'get': 'user_view'},
]
queryset = User.objects.all().order_by('-id') queryset = User.objects.all().order_by('-id')
serializer_class = UserListSerializer serializer_class = UserListSerializer
filterset_class = UserFilter filterset_class = UserFilter
@ -180,17 +150,17 @@ class UserViewSet(ModelViewSet):
# 创建用户默认添加密码 # 创建用户默认添加密码
password = request.data['password'] if 'password' in request.data else None password = request.data['password'] if 'password' in request.data else None
if password: if password:
request.data['password'] = make_password(password) password = make_password(password)
else: else:
request.data['password'] = make_password('0000') password = make_password('0000')
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
self.perform_create(serializer) serializer.save(password = password)
return Response(serializer.data) return Response(serializer.data)
@action(methods=['post'], detail=True, permission_classes=[IsAuthenticated], @action(methods=['put'], detail=True, permission_classes=[IsAuthenticated],
url_name='change_passwd') url_name='change_password')
def change_passwd(self, request, pk=None): def password(self, request, pk=None):
""" """
修改密码 修改密码
""" """
@ -208,8 +178,8 @@ class UserViewSet(ModelViewSet):
else: else:
return Response('旧密码错误!', status=status.HTTP_400_BAD_REQUEST) return Response('旧密码错误!', status=status.HTTP_400_BAD_REQUEST)
# perms_map=[{'get':'my_info'}], 自定义action控权 # perms_map={'get':'*'}, 自定义action控权
@action(methods=['get'], detail=False, url_name='my_info', permission_classes=[IsAuthenticated]) @action(methods=['get'], detail=False, url_name='my_info', permission_classes=[IsAuthenticated])
def info(self, request, pk=None): def info(self, request, pk=None):
""" """
初始化用户信息 初始化用户信息

View File

@ -17,7 +17,7 @@ class SoftDeletableQuerySetMixin(object):
field to True) field to True)
''' '''
if soft: if soft:
self.update(is_delete=True) self.update(is_deleted=True)
else: else:
return super(SoftDeletableQuerySetMixin, self).delete() return super(SoftDeletableQuerySetMixin, self).delete()
@ -47,6 +47,7 @@ class SoftDeletableManagerMixin(object):
class SoftDeletableManager(SoftDeletableManagerMixin, models.Manager): class SoftDeletableManager(SoftDeletableManagerMixin, models.Manager):
pass pass
class BaseModel(models.Model): class BaseModel(models.Model):
create_time = models.DateTimeField( create_time = models.DateTimeField(
default=timezone.now, verbose_name='创建时间', help_text='创建时间') default=timezone.now, verbose_name='创建时间', help_text='创建时间')
@ -57,13 +58,13 @@ class BaseModel(models.Model):
class Meta: class Meta:
abstract = True abstract = True
class SoftModel(BaseModel):
class SoftModel(BaseModel):
class Meta: class Meta:
abstract = True abstract = True
objects = SoftDeletableManager() objects = SoftDeletableManager()
def delete(self, using=None, soft=True, *args, **kwargs): def delete(self, using=None, soft=True, *args, **kwargs):
''' '''
这里需要真删除的话soft=False即可 这里需要真删除的话soft=False即可