自定义用户管理

101 阅读1分钟
<?php

namespace app\admin\controller;

use ba\Random;
use Throwable;
use app\common\controller\Backend;
use think\facade\Db;

/**
 * 用户管理管理
 */
class Member extends Backend
{
    /**
     * Member模型对象
     * @var object
     * @phpstan-var \app\admin\model\Member
     */
    protected object $model;

    protected array|string $preExcludeFields = ['id', 'email', 'mobile', 'login_failure', 'last_login_time', 'last_login_ip', 'salt', 'motto', 'update_time', 'create_time', 'user_path'];

    protected array $withJoinTable = ['pidTable'];

    protected string|array $quickSearchField = ['username', 'nickname', 'id'];

    public function initialize(): void
    {
        parent::initialize();
        $this->model = new \app\admin\model\Member;
    }

    /**
     * 查看
     * @throws Throwable
     */
    public function index(): void
    {
        // 如果是 select 则转发到 select 方法,若未重写该方法,其实还是继续执行 index
        if ($this->request->param('select')) {
            $this->select();
        }

        /**
         * 1. withJoin 不可使用 alias 方法设置表别名,别名将自动使用关联模型名称(小写下划线命名规则)
         * 2. 以下的别名设置了主表别名,同时便于拼接查询参数等
         * 3. paginate 数据集可使用链式操作 each(function($item, $key) {}) 遍历处理
         */
        list($where, $alias, $limit, $order) = $this->queryBuilder();
        $res = $this->model
            ->withJoin($this->withJoinTable, $this->withJoinType)
            ->alias($alias)
            ->where($where)
            ->order($order)
            ->paginate($limit);
        $res->visible(['pidTable' => ['username']]);

        $this->success('', [
            'list'   => $res->items(),
            'total'  => $res->total(),
            'remark' => get_route_remark(),
        ]);
    }

    /**
     * 若需重写查看、编辑、删除等方法,请复制 @see \app\admin\library\traits\Backend 中对应的方法至此进行重写
     */

    /**
     * 重写添加和编辑
     */
    public function add(): void
    {
        if ($this->request->isPost()) {
            $data = $this->request->post();
            if (!$data) {
                $this->error(__('Parameter %s can not be empty', ['']));
            }
            $data = $this->excludeFields($data);
            if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
                $data[$this->dataLimitField] = $this->auth->id;
            }


            $result = false;
            $this->model->startTrans();
            try {
                // 模型验证
                if ($this->modelValidate) {
                    $validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
                    if (class_exists($validate)) {
                        $validate = new $validate;
                        if ($this->modelSceneValidate) $validate->scene('add');
                        $validate->check($data);
                    }
                }

                $salt   = Random::build('alnum', 16);
                $passwd = encrypt_password($data['password'], $salt);

                $insert = [];
                $insert['salt']       = $salt;
                $insert['password']   = $passwd;
                $insert['nickname']   = trim($data['nickname']);
                $insert['username']   = trim($data['username']);
                $insert['createtime'] = time();
                $result  = $this->model->save($insert); // 1.添加用户

                $group_arr  = [$data['group_id']]; // 格式化成权限需要的模式

                if ($group_arr) {
                    $groupAccess = [];
                    foreach ($group_arr as $data) {
                        $groupAccess[] = [
                            'uid'      => $this->model->id, // 新建用户ID
                            'group_id' => $data,
                        ];
                    }
                    // [0 =>  ["uid" => 6, "group_id" => "3"] ]
                    Db::name('admin_group_access')->insertAll($groupAccess); // 2.添加用户权限关联
                }
                $this->model->commit();
            } catch (Throwable $e) {
                $this->model->rollback();
                $this->error($e->getMessage());
            }
            if ($result !== false) {
                $this->success(__('Added successfully'));
            } else {
                $this->error(__('No rows were added'));
            }
        }

        $this->error(__('Parameter error'));
    }
}
<?php

namespace app\admin\validate;

use think\Validate;

class Member 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',
        'pid'   => 'require|integer'
    ];

    /**
     * 验证提示信息
     * @var array
     */
    protected $message = [
        'username.require' => '用户名不能为空',
        'username.regex' => '用户名必须以字母开头,只能包含字母、数字和下划线,长度在3-16之间',
        'username.unique' => '用户名已存在',
        'nickname.require' => '昵称不能为空',
        'password.require' => '密码不能为空',
        'password.regex' => '密码必须在6-32位之间,且不能包含特殊字符',
        'group_id.require' => '角色ID不能为空',
        'pid.require' => '父ID不能为空',
        'pid.integer' => '父ID必须是整数',
    ];

    /**
     * 字段描述
     */
    protected $field = [
        'username'  => '用户名',
        'nickname'  => '昵称',
        'password'  => '密码',
        'group_id' => '角色ID',
        'pid'       => '父ID',
    ];

    /**
     * 验证场景
     */
    protected $scene = [
        'add' => ['username', 'nickname', 'password', 'pid', 'group_id'],
    ];

    public function __construct()
    {
        parent::__construct();
    }
}

<?php

namespace app\admin\model;

use think\Model;

/**
 * Member
 */
class Member extends Model
{
    // 表名
    protected $name = 'admin';

    // 自动写入时间戳字段
    protected $autoWriteTimestamp = true;


    public function pidTable(): \think\model\relation\BelongsTo
    {
        return $this->belongsTo(\app\admin\model\Admin::class, 'pid', 'id');
    }
}
<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', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
            :quick-search-placeholder="t('Quick search placeholder', { fields: t('member.quick Search Fields') })"
        ></TableHeader>

        <!-- 表格 -->
        <!-- 表格列有多种自定义渲染方式,比如自定义组件、具名插槽等,参见文档 -->
        <!-- 要使用 el-table 组件原有的属性,直接加在 Table 标签上即可 -->
        <Table ref="tableRef"></Table>

        <!-- 表单 -->
        <PopupForm />
    </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 PopupForm from './popupForm.vue'
import Table from '/@/components/table/index.vue'
import TableHeader from '/@/components/table/header/index.vue'

defineOptions({
    name: 'member',
})

const { t } = useI18n()
const tableRef = ref()
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])

/**
 * baTable 内包含了表格的所有数据且数据具备响应性,然后通过 provide 注入给了后代组件
 */
const baTable = new baTableClass(
    new baTableApi('/admin/Member/'),
    {
        pk: 'id',
        column: [
            { type: 'selection', align: 'center', operator: false },
            // { label: t('member.id'), prop: 'id', align: 'center', width: 70, operator: false, sortable: 'custom' },
            { label: t('member.username'), prop: 'username', align: 'center', operatorPlaceholder: t('Fuzzy query'), operator: 'LIKE', sortable: false },
            { label: t('member.nickname'), prop: 'nickname', align: 'center', operatorPlaceholder: t('Fuzzy query'), operator: 'LIKE', sortable: false },
            { label: t('member.avatar'), prop: 'avatar', align: 'center', render: 'image', operator: false },
            { label: t('member.status'), prop: 'status', align: 'center', render: 'tag', operator: false, sortable: false, replaceValue: { '0': t('member.status 0'), '1': t('member.status 1') } },
            { label: "上级用户", prop: 'pidTable.username', align: 'center', operatorPlaceholder: t('Fuzzy query'), render: 'tags', operator: 'LIKE' },
            { label: t('member.pid'), prop: 'pid', align: 'center', operator: 'eq', show:false },
            { label: t('member.create_time'), prop: 'create_time', align: 'center', render: 'datetime', operator: false, sortable: 'custom', width: 160, timeFormat: 'yyyy-mm-dd hh:MM:ss' },
            { label: t('Operate'), align: 'center', width: 100, render: 'buttons', buttons: optButtons, operator: false },
        ],
        dblClickNotEditColumn: [undefined],
    },
    {
        defaultItems: { status: '1' },
    }
)

provide('baTable', baTable)

onMounted(() => {
    baTable.table.ref = tableRef.value
    baTable.mount()
    baTable.getIndex()?.then(() => {
        baTable.initSort()
        baTable.dragSort()
    })
})
</script>

<style scoped lang="scss"></style>