一.说明
- 1.这里主要是将之前改造的用户管理的实现代码贴一下
- 2.目前这里使用的大量的后端接口来实现下拉列表, 可以考虑优化
二.步骤
- 1.新建一个一级菜单, 使用生成CURD的方式, 表格就选择用户表
ba_admin(也可以添加一个菜单目录, 之前没做, 可以扩展) - 2.之前的路由是
admin/adminUser, 也就是adminUser
三.后端代码
- 1.控制器
app\admin\controller\AdminUser.php
namespace app\admin\controller;
use app\common\controller\Backend;
use ba\Random;
use think\db\exception\PDOException;
use think\exception\ValidateException;
use Exception;
use think\facade\Config;
use think\facade\Db;
use app\admin\model\AdminUser as AdminUserModel;
class AdminUser extends Backend
{
protected $model = null;
protected $preExcludeFields = ['id', 'username', 'avatar', 'createtime', 'status', 'pid'];
protected $quickSearchField = ['id', 'username', 'nickname'];
public function initialize()
{
parent::initialize();
$this->model = new \app\admin\model\AdminUser;
}
public function index()
{
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->param('select')) {
$this->select();
}
$field = ['id', 'username', 'nickname', 'avatar', 'mobile', 'createtime', 'status', 'pid'];
list($where, $alias, $limit, $order) = $this->queryBuilder();
$query = $this->model
->field($field)
->withJoin($this->withJoinTable, $this->withJoinType)
->alias($alias)
->where($where);
if (! $this->auth->isSuperAdmin()) {
$uidArr = $this->model->getChildArr($this->auth->getAdmin()->id);
$query->whereIn('id', $uidArr);
}
$res = $query->order($order)->paginate($limit);
$list = $res->items();
foreach ($list as &$user) {
$user['pid'] = $this->model->getNickname($user['pid']);
}
$this->success('', [
'list' => $list,
'total' => $res->total(),
'remark' => get_route_remark(),
]);
}
public function CreateUser()
{
if ($this->request->isPost()) {
$data = $this->request->post();
if (!$data) {
$this->error(__('Parameter %s can not be empty', ['']));
}
/**
* 由于有密码字段-对方法进行重写
* 数据验证
*/
if ($this->modelValidate) {
try {
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
$validate = new $validate;
$validate->scene('add')->check($data);
} catch (ValidateException $e) {
$this->error($e->getMessage());
}
}
$salt = Random::build('alnum', 16);
$passwd = encrypt_password($data['password'], $salt);
// 上级用户
if ($this->auth->isSuperAdmin()) {
$pid = trim($data['pid']);
} else {
$pid = $this->auth->getAdmin()->id; // 上级用户
}
$username = trim($data['username']);
$data = $this->excludeFields($data);
$result = false;
Db::startTrans();
try {
$data['salt'] = $salt;
$data['password'] = $passwd;
$data['group_arr'] = [$data['group_id']]; // 格式化成权限需要的模式
$data['username'] = $username;
$data['createtime'] = time();
$data['pid'] = $pid;
/**
* array:7 [
* "status" => "1"
* "username" => "zhangsan"
* "nickname" => "张三"
* "group_arr" => array:1 [
* 0 => "3"
* ]
* "mobile" => "13800138000"
* "salt" => "RpUoGcwnvTl68myO"
* "password" => "32488ff98fcdae4d3a125490d1c81146"
* ]
*/
$result = $this->model->save($data); // 1.添加用户
if ($data['group_arr']) {
$groupAccess = [];
foreach ($data['group_arr'] as $datum) {
$groupAccess[] = [
'uid' => $this->model->id,
'group_id' => $datum,
];
}
/**
* array:1 [
* 0 => array:2 ["uid" => 6, "group_id" => "3"]
* ]
*/
Db::name('admin_group_access')->insertAll($groupAccess); // 2.添加用户权限关联
}
Db::commit();
} catch (ValidateException|PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($result !== false) {
$this->success("添加用户成功");
} else {
$this->error("添加失败");
}
} else {
$this->error("ERROR");
}
}
/**
* 根据登录用户的角色, 展示添加列表时的可选角色
*/
public function getUserGroup()
{
$group_id = $this->auth->getAdminGroupId(); //获取用户组ID
$group = $this->model->getGroupSelect($group_id);
return $this->success('', $group);
}
/**
* 获取上级用户列表
*/
public function getPidUser()
{
$isSuper = $this->auth->isSuperAdmin();
if ($isSuper) {
$list = $this->model->getPidUser();
} else {
$list = [];
}
$this->success('', $list);
}
}
- 2.模型
app\admin\model\AdminUser.php
<?php
namespace app\admin\model;
use think\Model;
use app\admin\library\Auth;
/**
* AdminUser
*/
class AdminUser extends Model
{
// 表名
protected $name = 'admin';
// 自动写入时间戳字段
protected $autoWriteTimestamp = false;
const GROUP_SUPER = 1;
const GROUP_LEVEL1 = 2;
const GROUP_LEVEL2 = 3;
const GROUP_LEVEL3 = 4;
public $group = [
self::GROUP_SUPER => '超级管理员',
self::GROUP_LEVEL1 => '一级管理员',
self::GROUP_LEVEL2 => '二级管理员',
self::GROUP_LEVEL3 => '三级管理员',
];
// 超管可选的用户组, 但是如果不是选择的L1, 需要指定上级
public $group_super = [
self::GROUP_LEVEL1,
self::GROUP_LEVEL2,
self::GROUP_LEVEL3,
];
// 一级可选的用户组
public $group_level1 = [
self::GROUP_LEVEL2,
self::GROUP_LEVEL3,
];
// 二级可选的用户组
public $group_level2 = [
self::GROUP_LEVEL2,
self::GROUP_LEVEL3,
];
/**
* 获取指定角色可获取角色组
*/
public function getGroupSelect($group_id)
{
switch($group_id) {
case self::GROUP_SUPER:
$group = $this->group_super;
break;
case self::GROUP_LEVEL1:
$group = $this->group_level1;
break;
case self::GROUP_LEVEL2:
$group = $this->group_level2;
break;
default:
$group = [];
break;
}
$result = [];
foreach ($group as $gid) {
$result[] = [
'id' => $gid,
'title' => $this->group[$gid]
];
}
return $result;
}
/**
* 获取上级用户列表
*/
public function getPidUser()
{
$auth = new Auth();
$uidArr = $auth->getGroupAdmins([self::GROUP_LEVEL1, self::GROUP_LEVEL2]);
$list = $this->field(['id','nickname as title'])->whereIn('id', $uidArr)->select()->toArray();
return $list;
}
// 获取用户名
public function getNickname($uid)
{
return $this->where("id", $uid)->value('nickname');
}
/**
* 获取所有下级用户ID
*/
function getChildArr($id) {
$ids = [$id];
$subIds = self::where('pid', $id)->column('id');
foreach ($subIds as $subId) {
$ids = array_merge($ids, $this->getChildArr($subId));
}
return $ids;
}
/********************** 从Admin模型中转移过来的方法 ************************ */
// 获取树形用户
public function getUserTree($isSuper, $uid)
{
if ($isSuper) {
$res = $this->field(['id','username label','pid'])->where("pid", 0)->select()->toArray();
} else {
$res = $this->field(['id','username label','pid'])->where("id", $uid)->select()->toArray();
}
$list = [];
foreach ($res as $item) {
$list[] = [
'id' => $item['id'],
'pid' => $item['pid'],
'label' => $item['label'],
];
}
foreach ($list as &$user) {
$child = $this->getChildren($user['id']);
$user['children'] = $child;
}
return $list;
}
// 无限极方法
public function getChildren($id)
{
$children = $this->hidden(['group_arr'])->field(['id','username label','pid'])->where('pid', $id)->select()->toArray();
$list = [];
foreach ($children as $item) {
$list[] = [
'id' => $item['id'],
'pid' => $item['pid'],
'label' => $item['label'],
];
}
foreach ($list as &$child) {
$child['children'] = $this->getChildren($child['id']);
}
return $list;
}
// 获取用户列表
public function getUserList()
{
$list = $this->hidden(['group_arr'])->field(['id','nickname title'])->select()->toArray();
return $list;
}
}
- 3.扩展权限类
app\admin\library\Auth.php, 增加自定义方法(看看能不能迁移到其他地方,不修改系统源码)
/**************************** 自定义方法 *******************************/
/**
* 获取管理员组ID(角色ID)
*/
public function getAdminGroupId()
{
$groupId = $this->getAdmin()->group_arr;
if ($groupId && count($groupId) == 1) {
return $groupId[0];
} else {
return 0;
}
}
/**
* 获取管理员UID
*/
public function getUid()
{
return $this->getAdmin()->id;
}
- 4.后端验证器
namespace app\admin\validate;
use think\Validate;
class AdminUser extends Validate
{
protected $failException = true;
protected $rule = [
'username' => 'require|regex:^[a-zA-Z][a-zA-Z0-9_]{2,15}$|unique:admin',
'nickname' => 'require',
'password' => 'require|regex:^(?!.*[&<>"\'\n\r]).{6,32}$',
'group_id' => 'require',
];
/**
* 验证提示信息
* @var array
*/
protected $message = [];
/**
* 字段描述
*/
protected $field = [
];
/**
* 验证场景
*/
protected $scene = [
'add' => ['username', 'nickname', 'password', 'group_id'],
];
public function __construct()
{
$this->field = [
'username' => __('Username'),
'nickname' => __('Nickname'),
'password' => __('Password'),
'group_id' => __('Group Name Arr'),
];
$this->message = array_merge($this->message, [
'username.regex' => __('Please input correct username'),
'password.regex' => __('Please input correct password'),
]);
parent::__construct();
}
}
四.前端代码
-
前端主要的代码都在:
web\src\views\backend\adminUser- index.vue 列表页面
- popupForm.vue 弹窗和编辑页面
- addUser.vue 添加用户页面
- roleEdit.vue 这个是用户自定义权限的, 暂时忽略
-
另外还有一些接口文件之类的
- web\src\api\backend\adminapi.ts
-
- index.vue
<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
:buttons="['refresh', 'comSearch', 'quickSearch', 'columnDisplay']"
:quick-search-placeholder="t('quick Search Placeholder', { fields: t('adminUser.quick Search Fields') })"
>
<el-button type="primal" @click="isOpen = true">添加用户</el-button>
</TableHeader>
<!-- 表格 -->
<!-- 要使用`el-table`组件原有的属性,直接加在Table标签上即可 -->
<Table ref="tableRef" />
<addUser/>
<roleEdit/>
</div>
</template>
<script setup lang="ts">
import { ref, provide, onMounted } from 'vue'
import baTableClass from '/@/utils/baTable'
import { defaultOptButtons } from '/@/components/table'
import { baTableApi } from '/@/api/common'
import { useI18n } from 'vue-i18n'
import Table from '/@/components/table/index.vue'
import TableHeader from '/@/components/table/header/index.vue'
import addUser from './addUser.vue'
import roleEdit from './roleEdit.vue'
const { t } = useI18n()
const tableRef = ref()
let optButtons = defaultOptButtons([])
// 自定义一个新的按钮
let newButton: OptButton[] = [
{
render: 'tipButton',
text: '权限',
type: 'primary',
icon: 'fa fa-search-plus',
disabledTip: false,
// 自定义点击事件
click: (row: TableRow, field: TableColumn) => {
console.log('禁用'+row.id);
isOpenRole.value = true
},
},
]
// 新按钮合入到默认的按钮数组
optButtons = newButton.concat(optButtons)
const baTable = new baTableClass(
new baTableApi('/admin/AdminUser/'),
{
pk: 'id',
column: [
{ type: 'selection', align: 'center', operator: false },
{ label: t('adminUser.id'), prop: 'id', align: 'center', width: 70, operator: 'RANGE', sortable: 'custom' },
{ label: t('adminUser.username'), prop: 'username', align: 'center', operatorPlaceholder: t('Fuzzy query'), operator: 'LIKE', sortable: false },
{ label: t('adminUser.nickname'), prop: 'nickname', align: 'center', operatorPlaceholder: t('Fuzzy query'), operator: 'LIKE', sortable: false },
{ label: t('adminUser.avatar'), prop: 'avatar', align: 'center', render: 'image', operator: false },
{ label: t('adminUser.createtime'), prop: 'createtime', align: 'center', render: 'datetime', operator: 'RANGE', sortable: 'custom', width: 160, timeFormat: 'yyyy-mm-dd hh:MM:ss' },
{ label: t('adminUser.status'), prop: 'status', align: 'center', render: 'tag', operator: '=', sortable: false, replaceValue: { 1: t('adminUser.status 1'), 0: t('adminUser.status 0') } },
{ label: '上级', prop: 'pid', align: 'center', operator: 'RANGE', sortable: false },
{
label: t('operate'),
align: 'center',
width: 100,
render: 'buttons',
buttons: optButtons,
operator: false
},
],
dblClickNotEditColumn: [undefined],
},
{
defaultItems: {},
}
)
provide('baTable', baTable)
onMounted(() => {
baTable.table.ref = tableRef.value
baTable.mount()
baTable.getIndex()?.then(() => {
baTable.initSort()
baTable.dragSort()
})
})
// 弹窗开关
const isOpen = ref(false)
provide('isOpen', isOpen)
// 设置权限弹窗开关
const isOpenRole = ref(false)
provide('isOpenRole', isOpenRole)
</script>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'adminUser',
})
</script>
<style scoped lang="scss"></style>
- 2.popupForm.vue 弹窗和编辑页面
<template>
<!-- 对话框表单 -->
<el-dialog
class="ba-operate-dialog"
:close-on-click-modal="false"
:model-value="['add', 'edit'].includes(baTable.form.operate!)"
@close="baTable.toggleForm"
width="50%"
>
<template #header>
<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>
<el-scrollbar v-loading="baTable.form.loading" class="ba-table-form-scrollbar">
<div
class="ba-operate-form"
:class="'ba-' + baTable.form.operate + '-form'"
:style="'width: calc(100% - ' + baTable.form.labelWidth! / 2 + 'px)'"
>
<el-form
v-if="!baTable.form.loading"
ref="formRef"
@submit.prevent=""
@keyup.enter="baTable.onSubmit(formRef)"
:model="baTable.form.items"
label-position="right"
:label-width="baTable.form.labelWidth + 'px'"
:rules="rules"
>
<FormItem :label="t('adminUser.nickname')" type="string" v-model="baTable.form.items!.nickname" prop="nickname" :placeholder="t('Please input field', { field: t('adminUser.nickname') })" />
</el-form>
</div>
</el-scrollbar>
<template #footer>
<div :style="'width: calc(100% - ' + baTable.form.labelWidth! / 1.8 + 'px)'">
<el-button @click="baTable.toggleForm('')">{{ t('Cancel') }}</el-button>
<el-button v-blur :loading="baTable.form.submitLoading" @click="baTable.onSubmit(formRef)" type="primary">
{{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') : t('Save') }}
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref, inject } from 'vue'
import { useI18n } from 'vue-i18n'
import type baTableClass from '/@/utils/baTable'
import FormItem from '/@/components/formItem/index.vue'
import type { ElForm, FormItemRule } from 'element-plus'
import { buildValidatorData } from '/@/utils/validate'
const formRef = ref<InstanceType<typeof ElForm>>()
const baTable = inject('baTable') as baTableClass
const { t } = useI18n()
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
createtime: [buildValidatorData({ name: 'date', title: t('adminUser.createtime') })],
pid: [buildValidatorData({ name: 'number', title: t('adminUser.pid') })],
})
</script>
<style scoped lang="scss"></style>
- 3.addUser.vue 添加用户界面
<template>
<div>
<el-dialog v-model="isOpen" title="添加用户">
<el-form :model="form" :rules="rules">
<el-form-item label="登录名" prop="username">
<el-input v-model="form.username" autocomplete="off" placeholder="由英文和数字组成,不可输入中文" />
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input v-model="form.nickname" autocomplete="off" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" autocomplete="off" />
</el-form-item>
<el-form-item label="用户组" prop="group_id">
<el-select v-model="form.group_id" placeholder="请选择用户组">
<el-option :label="item.title" :value="item.id" :key="item.id" v-for="item in group" />
</el-select>
</el-form-item>
<el-form-item label="上级用户" prop="pid" v-show="userLiserShow">
<el-select v-model="form.pid" placeholder="上级用户">
<el-option :label="item.title" :value="item.id" :key="item.id" v-for="item in pidList" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="isOpen = false">取消</el-button>
<el-button type="primary" @click="onSubmit"> 确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang='ts'>
import { useAdminInfo } from '/@/stores/adminInfo'
import { reactive, ref, watchEffect, inject } from 'vue'
import { ElMessage, FormRules } from 'element-plus'
import { CreateUser, getUserGroup, getPidUser } from '/@/api/backend/adminapi'
// el-upload中的属性data, 用来传值, 这里名称要对应
const form = reactive({
username: '',
nickname:'',
password:'',
group_id:'',
pid:'',
})
const isOpen = inject('isOpen')
// const adminInfo = useAdminInfo()
// 表单验证
const rules = reactive<FormRules>({
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 4, max: 12, message: '请输入4-12个字符', trigger: 'blur' },
],
nickname: [
{ required: true, message: '请输入昵称', trigger: 'blur' },
{ min: 2, max: 12, message: '请输入2-12个字符', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 12, message: '请输入6-12个字符', trigger: 'blur' },
],
group_id: [
{ required: true, message: '请选择用户组', trigger: 'blur' }
]
})
// 获取下拉用户列表
const group = reactive([]);
getUserGroup().then((res)=> {
group.push(...res.data)
})
// 获取上级用户列表
const pidList = reactive([]);
let userLiserShow = ref(false);
getPidUser().then((res)=> {
console.log(res);
pidList.push(...res.data)
if (res.data.length > 0) {
userLiserShow.value = true;
}
})
// 表单提交
const onSubmit = () => {
console.log('submit!', form)
CreateUser(form).then((res)=> {
console.log(res);
if (res.code === 1) {
ElMessage({message: '添加成功',type: 'success', onClose: () => {
isOpen.value = false
}})
} else {
ElMessage({message: '添加失败',type: 'warning'})
}
})
}
</script>
-
4.roleEdit.vue 角色编辑界面? 忽略
-
adminapi.ts
import { getUrl} from '/@/utils/axios'
import { useAdminInfo } from '/@/stores/adminInfo'
import createAxios from '/@/utils/axios'
// 获取导入的URL, 记得调用时要写()
// 引用: import { getUserTree } from '/@/api/backend/adminapi'
// 使用: getUserTree()
export function getUserTree() {
let url = getUrl() + '/admin/api/getUserTree' + '?server=1'
return createAxios({
url:url,
method: 'get',
})
}
// 添加用户
export function CreateUser(data: anyObj) {
let url = getUrl() + '/admin/AdminUser/CreateUser' + '?server=1'
return createAxios(
{
url: url,
method: 'post',
data: data,
}
)
}
// 获取可选用户组
export function getUserGroup() {
let url = getUrl() + '/admin/AdminUser/getUserGroup' + '?server=1'
return createAxios({
url:url,
method: 'get',
})
}
// 获取用户列表(用于选择上级用户)
export function getPidUser() {
let url = getUrl() + '/admin/AdminUser/getPidUser' + '?server=1'
return createAxios({
url:url,
method: 'get',
})
}
// 获取用户列表(用于选择上级用户)
export function download(type: any) {
const adminInfo = useAdminInfo()
let url = getUrl() + '/admin/api/download' + '?server=1' + '&type=' + type + '&batoken=' + adminInfo.getToken()
return url;
}
// remoteMenuUrl
export function remoteMenuUrl() {
const adminInfo = useAdminInfo()
let url = getUrl() + '/admin/api/remoteMenuUrl'
return url;
}
// 获取远程用户
export function remoteUrlUser() {
const adminInfo = useAdminInfo()
let url = getUrl() + '/admin/api/remoteUrlUser'
return url;
}
// 获取远程URL: 用户级菜单列表
export function remoteUrlUserMenu() {
const adminInfo = useAdminInfo()
let url = getUrl() + '/admin/api/remoteUrlUserMenu'
return url;
}