feat:角色组管理

This commit is contained in:
妙码生花 2022-03-21 04:11:23 +08:00
parent 436156e0d9
commit 508fde0aa1
5 changed files with 490 additions and 4 deletions

View File

@ -2,8 +2,14 @@
namespace app\admin\controller\auth;
use app\admin\model\MenuRule;
use ba\Tree;
use app\common\controller\Backend;
use app\admin\model\AdminGroup;
use Exception;
use think\db\exception\PDOException;
use think\exception\ValidateException;
use think\facade\Db;
class Group extends Backend
{
@ -16,10 +22,206 @@ class Group extends Backend
protected $quickSearchField = 'name';
/**
* @var Tree
*/
protected $tree = null;
protected $keyword = false;
public function initialize()
{
parent::initialize();
$this->model = new AdminGroup();
$this->tree = Tree::instance();
}
public function index()
{
if ($this->request->param('select')) {
$this->select();
}
$this->keyword = $this->request->request("quick_search");
$this->success('', [
'list' => $this->getGroups(),
'remark' => get_route_remark(),
]);
}
public function add()
{
if ($this->request->isPost()) {
$data = $this->request->post();
if (!$data) {
$this->error(__('Parameter %s can not be empty', ['']));
}
$data = $this->excludeFields($data);
if (is_array($data['rules']) && $data['rules']) {
$menuRuleModel = new MenuRule();
$rules = $menuRuleModel->select();
$superAdmin = true;
foreach ($rules as $rule) {
if (!in_array($rule['id'], $data['rules'])) {
$superAdmin = false;
}
}
if ($superAdmin) {
$data['rules'] = '*';
} else {
$data['rules'] = implode(',', $data['rules']);
}
} else {
unset($data['rules']);
}
$result = false;
Db::startTrans();
try {
// 模型验证
if ($this->modelValidate) {
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
if (class_exists($validate)) {
$validate = new $validate;
$validate->scene('add')->check($data);
}
}
$result = $this->model->save($data);
Db::commit();
} catch (ValidateException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (PDOException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($result !== false) {
$this->success(__('Added successfully'));
} else {
$this->error(__('No rows were added'));
}
}
$this->error(__('Parameter error'));
}
public function edit($id = null)
{
$row = $this->model->find($id);
if (!$row) {
$this->error(__('Record not found'));
}
if ($this->request->isPost()) {
$data = $this->request->post();
if (!$data) {
$this->error(__('Parameter %s can not be empty', ['']));
}
$data = $this->excludeFields($data);
if (is_array($data['rules']) && $data['rules']) {
$menuRuleModel = new MenuRule();
$rules = $menuRuleModel->select();
$superAdmin = true;
foreach ($rules as $rule) {
if (!in_array($rule['id'], $data['rules'])) {
$superAdmin = false;
}
}
if ($superAdmin) {
$data['rules'] = '*';
} else {
$data['rules'] = implode(',', $data['rules']);
}
} else {
unset($data['rules']);
}
$result = false;
Db::startTrans();
try {
// 模型验证
if ($this->modelValidate) {
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
if (class_exists($validate)) {
$validate = new $validate;
$validate->scene('edit')->check($data);
}
}
$result = $row->save($data);
Db::commit();
} catch (ValidateException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (PDOException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($result !== false) {
$this->success(__('Update successful'));
} else {
$this->error(__('No rows updated'));
}
}
$row->pid = $row->pid ? $this->model->where('id', $row->pid)->value('name') : '';
$row->rules = $row->rules ? explode(',', $row->rules) : [];
$this->success('', [
'row' => $row
]);
}
public function select()
{
$isTree = $this->request->param('isTree');
$this->keyword = $this->request->request("quick_search");
$data = $this->getGroups();
if ($isTree && !$this->keyword) {
$data = $this->tree->assembleTree($this->tree->getTreeArray($data, 'name'));
}
$this->success('', [
'options' => $data
]);
}
public function getGroups()
{
$where = [];
if ($this->keyword) {
$keyword = explode(' ', $this->keyword);
foreach ($keyword as $item) {
$where[] = [$this->quickSearchField, 'like', '%' . $item . '%'];
}
}
$data = $this->model->where('status', '1')->where($where)->select();
// 获取第一个权限的名称
foreach ($data as $datum) {
if ($datum->rules) {
if ($datum->rules == '*') {
$datum->rules = '超级管理员';
} else {
$rules = explode(',', $datum->rules);
if ($rules) {
$rulesFirstTitle = MenuRule::where('id', $rules[0])->value('title');
$datum->rules = count($rules) == 1 ? $rulesFirstTitle : $rulesFirstTitle . '等 ' . count($rules) . ' 项权限';
}
}
} else {
$datum->rules = '无权限';
}
}
return $this->tree->assembleChild($data);
}
}

View File

@ -18,6 +18,8 @@ class Tree
*/
public static $icon = array('│', '├', '└');
protected $childrens = [];
/**
* 初始化
@ -83,4 +85,37 @@ class Tree
}
return $arr;
}
/**
* 根据指定字段组装childrens数组
* @param array $data 数据源
* @param string $pid 存储上级id的字段
* @return array
*/
public function assembleChild($data, $pid = 'pid')
{
if (!$data) {
return [];
}
// 以pid组成数组
foreach ($data as $item) {
$this->childrens[$item[$pid]][] = $item;
}
if (isset($this->childrens[0])) {
return $this->getChildren($this->childrens[0]);
} else {
return $data;
}
}
protected function getChildren($data): array
{
foreach ($data as $key => $item) {
if (array_key_exists($item['id'], $this->childrens)) {
$data[$key]['children'] = $this->getChildren($this->childrens[$item['id']]);
}
}
return $data;
}
}

View File

@ -0,0 +1,9 @@
import createAxios from '/@/utils/axios'
import { authMenu } from '/@/api/controllerUrls'
export function getMenuRules() {
return createAxios({
url: authMenu + 'index',
method: 'get',
})
}

View File

@ -0,0 +1,158 @@
<template>
<!-- 对话框表单 -->
<el-dialog
custom-class="ba-operate-dialog"
top="10vh"
:close-on-click-modal="false"
:model-value="baTable.form.operate ? true : false"
@close="baTable.toggleForm"
>
<template #title>
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
{{ baTable.form.operate ? t(baTable.form.operate) : '' }}
</div>
</template>
<div
class="ba-operate-form"
:class="'ba-' + baTable.form.operate + '-form'"
:style="'width: calc(100% - ' + baTable.form.labelWidth! / 2 + 'px)'"
>
<el-form
ref="formRef"
@keyup.enter="baTable.onSubmit(formRef)"
:model="baTable.form.items"
label-position="right"
:label-width="baTable.form.labelWidth + 'px'"
:rules="rules"
>
<el-form-item label="上级分组">
<remoteSelect
:params="{ isTree: true }"
field="name"
:remote-url="baTable.api.actionUrl.get('index')"
v-model="baTable.form.items!.pid"
placeholder="点击选择"
/>
</el-form-item>
<el-form-item prop="name" label="分组名称">
<el-input v-model="baTable.form.items!.name" type="string" placeholder="请输入分组名称"></el-input>
</el-form-item>
<el-form-item prop="auth" label="权限">
<el-tree
ref="treeRef"
:key="state.treeKey"
:default-checked-keys="state.defaultCheckedKeys"
:default-expand-all="true"
show-checkbox
node-key="id"
:props="{ children: 'children', label: 'title' }"
:data="state.menuRules"
/>
</el-form-item>
<el-form-item label="状态">
<el-radio v-model="baTable.form.items!.status" label="0" :border="true">禁用</el-radio>
<el-radio v-model="baTable.form.items!.status" label="1" :border="true">启用</el-radio>
</el-form-item>
</el-form>
</div>
<template #footer>
<div :style="'width: calc(100% - ' + baTable.form.labelWidth! / 1.8 + 'px)'">
<el-button @click="baTable.toggleForm('')">取消</el-button>
<el-button v-blur :loading="baTable.form.submitLoading" @click="baTable.onSubmit(formRef)" type="primary">
{{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? '保存并编辑下一项' : '保存' }}
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type baTableClass from '/@/utils/baTable'
import remoteSelect from '/@/components/remoteSelect/index.vue'
import { getMenuRules } from '/@/api/backend/auth/group'
import { FormItemRule } from 'element-plus/es/components/form/src/form.type'
import type { ElForm, ElTree } from 'element-plus'
import { uuid } from '/@/utils/random'
interface MenuRules {
id: number
title: string
children: MenuRules[]
}
const treeRef = ref<InstanceType<typeof ElTree>>()
const formRef = ref<InstanceType<typeof ElForm>>()
const props = defineProps<{
baTable: baTableClass
}>()
const { t } = useI18n()
const state: {
treeKey: string
defaultCheckedKeys: number[]
menuRules: MenuRules[]
} = reactive({
treeKey: uuid(),
defaultCheckedKeys: [],
menuRules: [],
})
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
name: [
{
required: true,
message: '请输入分组名称',
trigger: 'blur',
},
],
auth: [
{
validator: (rule: any, val: string, callback: Function) => {
let ids = getCheckeds()
if (ids.length <= 0) {
return callback(new Error('请选择权限'))
}
return callback()
},
},
],
})
getMenuRules().then((res) => {
state.menuRules = res.data.list
})
const getCheckeds = () => {
return treeRef.value!.getCheckedKeys()
}
defineExpose({
getCheckeds,
})
watch(
() => props.baTable.form.items!.rules,
() => {
if (props.baTable.form.items!.rules && props.baTable.form.items!.rules.length) {
if (props.baTable.form.items!.rules.includes('*')) {
let arr: number[] = []
for (const key in state.menuRules) {
arr.push(state.menuRules[key].id)
}
state.defaultCheckedKeys = arr
} else {
state.defaultCheckedKeys = props.baTable.form.items!.rules
}
} else {
state.defaultCheckedKeys = []
}
state.treeKey = uuid()
}
)
</script>
<style scoped lang="scss"></style>

View File

@ -1,12 +1,94 @@
<template></template>
<template>
<div class="default-main ba-table-box">
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
<!-- 表格顶部菜单 -->
<TableHeader
:field="baTable.table.column"
:buttons="['refresh', 'add', 'edit', 'delete', 'unfold']"
:enable-batch-opt="baTable.table.selection!.length > 0 ? true : false"
:unfold="baTable.table.expandAll"
:quick-search-placeholder="'通过组名模糊搜索'"
@action="baTable.onTableHeaderAction"
/>
<!-- 表格 -->
<!-- 要使用`el-table`组件原有的属性直接加在Table标签上即可 -->
<Table
ref="tableRef"
:default-expand-all="baTable.table.expandAll"
:data="baTable.table.data"
:field="baTable.table.column"
:row-key="baTable.table.pk"
:loading="baTable.table.loading"
:pagination="false"
@action="baTable.onTableAction"
@row-dblclick="baTable.onTableDblclick"
/>
<Form ref="formRef" :ba-table="baTable" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import baTableClass from '/@/utils/baTable'
import { authGroup } from '/@/api/controllerUrls'
import { baTableApi } from '/@/api/common'
import { authGroup } from '/@/api/controllerUrls'
import Table from '/@/components/table/index.vue'
import TableHeader from '/@/components/table/header/index.vue'
import Form from './form.vue'
import { defaultOptButtons } from '/@/components/table'
import { useI18n } from 'vue-i18n'
const baTable = new baTableClass(new baTableApi(authGroup), {
column: [],
const formRef = ref()
const tableRef = ref()
const { t } = useI18n()
const baTable = new baTableClass(
new baTableApi(authGroup),
{
expandAll: true,
dblClickNotEditColumn: [undefined, 'status'],
column: [
{ type: 'selection', align: 'center' },
{ label: '组别名称', prop: 'name', align: 'left' },
{ label: '权限', prop: 'rules', align: 'center' },
{ label: '状态', prop: 'status', align: 'center', width: '80', render: 'switch' },
{ label: '更新时间', prop: 'updatetime', align: 'center', width: '160', render: 'datetime' },
{ label: '创建时间', prop: 'createtime', align: 'center', width: '160', render: 'datetime' },
{
label: '操作',
align: 'center',
width: '130',
render: 'buttons',
buttons: defaultOptButtons(['edit', 'delete']),
},
],
},
{
defaultItems: {
status: '1',
},
},
{
//
onSubmit: () => {
baTable.form.items!.rules = formRef.value.getCheckeds()
if (baTable.form.items?.pid == baTable.form.items?.pidebak) {
delete baTable.form.items?.pid
}
},
},
{
//
requestEdit: () => {
baTable.form.items!['pidebak'] = baTable.form.items!.pid
},
}
)
onMounted(() => {
baTable.table.ref = tableRef.value
baTable.mount()
baTable.getIndex()
})
</script>