项目初始化
This commit is contained in:
217
app/admin/controller/Ajax.php
Normal file
217
app/admin/controller/Ajax.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use Throwable;
|
||||
use ba\Terminal;
|
||||
use think\Response;
|
||||
use ba\TableManager;
|
||||
use think\facade\Db;
|
||||
use think\facade\Cache;
|
||||
use think\facade\Event;
|
||||
use app\admin\model\AdminLog;
|
||||
use app\common\library\Upload;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class Ajax extends Backend
|
||||
{
|
||||
protected array $noNeedPermission = ['*'];
|
||||
|
||||
/**
|
||||
* 无需登录的方法
|
||||
* terminal 内部自带验权
|
||||
*/
|
||||
protected array $noNeedLogin = ['terminal'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
}
|
||||
|
||||
public function upload(): void
|
||||
{
|
||||
AdminLog::instance()->setTitle(__('upload'));
|
||||
$file = $this->request->file('file');
|
||||
$driver = $this->request->param('driver', 'local');
|
||||
$topic = $this->request->param('topic', 'default');
|
||||
try {
|
||||
$upload = new Upload();
|
||||
$attachment = $upload
|
||||
->setFile($file)
|
||||
->setDriver($driver)
|
||||
->setTopic($topic)
|
||||
->upload(null, $this->auth->id);
|
||||
unset($attachment['create_time'], $attachment['quote']);
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$this->success(__('File uploaded successfully'), [
|
||||
'file' => $attachment ?? []
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取省市区数据
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function area(): void
|
||||
{
|
||||
$this->success('', get_area());
|
||||
}
|
||||
|
||||
public function buildSuffixSvg(): Response
|
||||
{
|
||||
$suffix = $this->request->param('suffix', 'file');
|
||||
$background = $this->request->param('background');
|
||||
$content = build_suffix_svg((string)$suffix, (string)$background);
|
||||
return response($content, 200, ['Content-Length' => strlen($content)])->contentType('image/svg+xml');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已脱敏的数据库连接配置列表
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function getDatabaseConnectionList(): void
|
||||
{
|
||||
$quickSearch = $this->request->get("quickSearch/s", '');
|
||||
$connections = config('database.connections');
|
||||
$desensitization = [];
|
||||
foreach ($connections as $key => $connection) {
|
||||
$connection = TableManager::getConnectionConfig($key);
|
||||
$desensitization[] = [
|
||||
'type' => $connection['type'],
|
||||
'database' => substr_replace($connection['database'], '****', 1, strlen($connection['database']) > 4 ? 2 : 1),
|
||||
'key' => $key,
|
||||
];
|
||||
}
|
||||
|
||||
if ($quickSearch) {
|
||||
$desensitization = array_filter($desensitization, function ($item) use ($quickSearch) {
|
||||
return preg_match("/$quickSearch/i", $item['key']);
|
||||
});
|
||||
$desensitization = array_values($desensitization);
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $desensitization,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表主键字段
|
||||
* @param ?string $table
|
||||
* @param ?string $connection
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function getTablePk(?string $table = null, ?string $connection = null): void
|
||||
{
|
||||
if (!$table) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$table = TableManager::tableName($table, true, $connection);
|
||||
if (!TableManager::phinxAdapter(false, $connection)->hasTable($table)) {
|
||||
$this->error(__('Data table does not exist'));
|
||||
}
|
||||
|
||||
$tablePk = Db::connect(TableManager::getConnection($connection))
|
||||
->table($table)
|
||||
->getPk();
|
||||
$this->success('', ['pk' => $tablePk]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据表列表
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function getTableList(): void
|
||||
{
|
||||
$quickSearch = $this->request->get("quickSearch/s", '');
|
||||
$connection = $this->request->request('connection');// 数据库连接配置标识
|
||||
$samePrefix = $this->request->request('samePrefix/b', true);// 是否仅返回项目数据表(前缀同项目一致的)
|
||||
$excludeTable = $this->request->request('excludeTable/a', []);// 要排除的数据表数组(表名无需带前缀)
|
||||
|
||||
$outTables = [];
|
||||
$dbConfig = TableManager::getConnectionConfig($connection);
|
||||
$tables = TableManager::getTableList($connection);
|
||||
|
||||
if ($quickSearch) {
|
||||
$tables = array_filter($tables, function ($comment) use ($quickSearch) {
|
||||
return preg_match("/$quickSearch/i", $comment);
|
||||
});
|
||||
}
|
||||
|
||||
$pattern = '/^' . $dbConfig['prefix'] . '/i';
|
||||
foreach ($tables as $table => $comment) {
|
||||
if ($samePrefix && !preg_match($pattern, $table)) continue;
|
||||
|
||||
$table = preg_replace($pattern, '', $table);
|
||||
if (!in_array($table, $excludeTable)) {
|
||||
$outTables[] = [
|
||||
'table' => $table,
|
||||
'comment' => $comment,
|
||||
'connection' => $connection,
|
||||
'prefix' => $dbConfig['prefix'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $outTables,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据表字段列表
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function getTableFieldList(): void
|
||||
{
|
||||
$table = $this->request->param('table');
|
||||
$clean = $this->request->param('clean', true);
|
||||
$connection = $this->request->request('connection');
|
||||
if (!$table) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
$connection = TableManager::getConnection($connection);
|
||||
$tablePk = Db::connect($connection)->name($table)->getPk();
|
||||
$this->success('', [
|
||||
'pk' => $tablePk,
|
||||
'fieldList' => TableManager::getTableColumns($table, $clean, $connection),
|
||||
]);
|
||||
}
|
||||
|
||||
public function changeTerminalConfig(): void
|
||||
{
|
||||
AdminLog::instance()->setTitle(__('Change terminal config'));
|
||||
if (Terminal::changeTerminalConfig()) {
|
||||
$this->success();
|
||||
} else {
|
||||
$this->error(__('Failed to modify the terminal configuration. Please modify the configuration file manually:%s', ['/config/buildadmin.php']));
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache(): void
|
||||
{
|
||||
AdminLog::instance()->setTitle(__('Clear cache'));
|
||||
$type = $this->request->post('type');
|
||||
if ($type == 'tp' || $type == 'all') {
|
||||
Cache::clear();
|
||||
} else {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
Event::trigger('cacheClearAfter', $this->app);
|
||||
$this->success(__('Cache cleaned~'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 终端
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function terminal(): void
|
||||
{
|
||||
(new Terminal())->exec();
|
||||
}
|
||||
}
|
||||
20
app/admin/controller/Dashboard.php
Normal file
20
app/admin/controller/Dashboard.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class Dashboard extends Backend
|
||||
{
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$this->success('', [
|
||||
'remark' => get_route_remark()
|
||||
]);
|
||||
}
|
||||
}
|
||||
133
app/admin/controller/Index.php
Normal file
133
app/admin/controller/Index.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use Throwable;
|
||||
use ba\ClickCaptcha;
|
||||
use think\facade\Config;
|
||||
use think\facade\Validate;
|
||||
use app\common\facade\Token;
|
||||
use app\admin\model\AdminLog;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class Index extends Backend
|
||||
{
|
||||
protected array $noNeedLogin = ['logout', 'login'];
|
||||
protected array $noNeedPermission = ['index'];
|
||||
|
||||
/**
|
||||
* 后台初始化请求
|
||||
* @return void
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
$adminInfo = $this->auth->getInfo();
|
||||
$adminInfo['super'] = $this->auth->isSuperAdmin();
|
||||
unset($adminInfo['token'], $adminInfo['refresh_token']);
|
||||
|
||||
$menus = $this->auth->getMenus();
|
||||
if (!$menus) {
|
||||
$this->error(__('No background menu, please contact super administrator!'));
|
||||
}
|
||||
$this->success('', [
|
||||
'adminInfo' => $adminInfo,
|
||||
'menus' => $menus,
|
||||
'siteConfig' => [
|
||||
'siteName' => get_sys_config('site_name'),
|
||||
'version' => get_sys_config('version'),
|
||||
'apiUrl' => Config::get('buildadmin.api_url'),
|
||||
'upload' => keys_to_camel_case(get_upload_config(), ['max_size', 'save_name', 'allowed_suffixes', 'allowed_mime_types']),
|
||||
'cdnUrl' => full_url(),
|
||||
'cdnUrlParams' => Config::get('buildadmin.cdn_url_params'),
|
||||
],
|
||||
'terminal' => [
|
||||
'phpDevelopmentServer' => str_contains($_SERVER['SERVER_SOFTWARE'], 'Development Server'),
|
||||
'npmPackageManager' => Config::get('terminal.npm_package_manager'),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
* @return void
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function login(): void
|
||||
{
|
||||
// 检查登录态
|
||||
if ($this->auth->isLogin()) {
|
||||
$this->success(__('You have already logged in. There is no need to log in again~'), [
|
||||
'type' => $this->auth::LOGGED_IN
|
||||
], $this->auth::LOGIN_RESPONSE_CODE);
|
||||
}
|
||||
|
||||
$captchaSwitch = Config::get('buildadmin.admin_login_captcha');
|
||||
|
||||
// 检查提交
|
||||
if ($this->request->isPost()) {
|
||||
$username = $this->request->post('username');
|
||||
$password = $this->request->post('password');
|
||||
$keep = $this->request->post('keep');
|
||||
|
||||
$rule = [
|
||||
'username|' . __('Username') => 'require|length:3,30',
|
||||
'password|' . __('Password') => 'require|regex:^(?!.*[&<>"\'\n\r]).{6,32}$',
|
||||
];
|
||||
$data = [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
];
|
||||
if ($captchaSwitch) {
|
||||
$rule['captchaId|' . __('CaptchaId')] = 'require';
|
||||
$rule['captchaInfo|' . __('Captcha')] = 'require';
|
||||
|
||||
$data['captchaId'] = $this->request->post('captchaId');
|
||||
$data['captchaInfo'] = $this->request->post('captchaInfo');
|
||||
}
|
||||
$validate = Validate::rule($rule);
|
||||
if (!$validate->check($data)) {
|
||||
$this->error($validate->getError());
|
||||
}
|
||||
|
||||
if ($captchaSwitch) {
|
||||
$captchaObj = new ClickCaptcha();
|
||||
if (!$captchaObj->check($data['captchaId'], $data['captchaInfo'])) {
|
||||
$this->error(__('Captcha error'));
|
||||
}
|
||||
}
|
||||
|
||||
AdminLog::instance()->setTitle(__('Login'));
|
||||
|
||||
$res = $this->auth->login($username, $password, (bool)$keep);
|
||||
if ($res === true) {
|
||||
$this->success(__('Login succeeded!'), [
|
||||
'userInfo' => $this->auth->getInfo()
|
||||
]);
|
||||
} else {
|
||||
$msg = $this->auth->getError();
|
||||
$msg = $msg ?: __('Incorrect user name or password!');
|
||||
$this->error($msg);
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'captcha' => $captchaSwitch
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员注销
|
||||
* @return void
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$refreshToken = $this->request->post('refreshToken', '');
|
||||
if ($refreshToken) Token::delete((string)$refreshToken);
|
||||
$this->auth->logout();
|
||||
$this->success();
|
||||
}
|
||||
}
|
||||
}
|
||||
137
app/admin/controller/Module.php
Normal file
137
app/admin/controller/Module.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use Throwable;
|
||||
use ba\Exception;
|
||||
use think\facade\Config;
|
||||
use app\admin\model\AdminLog;
|
||||
use app\admin\library\module\Server;
|
||||
use app\admin\library\module\Manage;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class Module extends Backend
|
||||
{
|
||||
protected array $noNeedPermission = ['state', 'dependentInstallComplete'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$this->success('', [
|
||||
'installed' => Server::installedList(root_path() . 'modules' . DIRECTORY_SEPARATOR),
|
||||
'sysVersion' => Config::get('buildadmin.version'),
|
||||
'nuxtVersion' => Server::getNuxtVersion(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function state(): void
|
||||
{
|
||||
$uid = $this->request->get("uid/s", '');
|
||||
if (!$uid) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
$this->success('', [
|
||||
'state' => Manage::instance($uid)->getInstallState()
|
||||
]);
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
AdminLog::instance()->setTitle(__('Install module'));
|
||||
$uid = $this->request->param("uid/s", '');
|
||||
$update = $this->request->param("update/b", false);
|
||||
if (!$uid) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
$res = [];
|
||||
try {
|
||||
$res = Manage::instance($uid)->install($update);
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (Throwable $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success('', [
|
||||
'data' => $res,
|
||||
]);
|
||||
}
|
||||
|
||||
public function dependentInstallComplete(): void
|
||||
{
|
||||
$uid = $this->request->get("uid/s", '');
|
||||
if (!$uid) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
try {
|
||||
Manage::instance($uid)->dependentInstallComplete('all');
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (Throwable $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
public function changeState(): void
|
||||
{
|
||||
AdminLog::instance()->setTitle(__('Change module state'));
|
||||
$uid = $this->request->post("uid/s", '');
|
||||
$state = $this->request->post("state/b", false);
|
||||
if (!$uid) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
$info = [];
|
||||
try {
|
||||
$info = Manage::instance($uid)->changeState($state);
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (Throwable $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success('', [
|
||||
'info' => $info,
|
||||
]);
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
AdminLog::instance()->setTitle(__('Unload module'));
|
||||
$uid = $this->request->get("uid/s", '');
|
||||
if (!$uid) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
try {
|
||||
Manage::instance($uid)->uninstall();
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (Throwable $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
public function upload(): void
|
||||
{
|
||||
AdminLog::instance()->setTitle(__('Upload install module'));
|
||||
$file = $this->request->get("file/s", '');
|
||||
$token = $this->request->get("token/s", '');
|
||||
if (!$file) $this->error(__('Parameter error'));
|
||||
if (!$token) $this->error(__('Please login to the official website account first'));
|
||||
|
||||
$info = [];
|
||||
try {
|
||||
$info = Manage::instance()->upload($token, $file);
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()), $e->getData(), $e->getCode());
|
||||
} catch (Throwable $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success('', [
|
||||
'info' => $info
|
||||
]);
|
||||
}
|
||||
}
|
||||
261
app/admin/controller/auth/Admin.php
Normal file
261
app/admin/controller/auth/Admin.php
Normal file
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use Throwable;
|
||||
use think\facade\Db;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\Admin as AdminModel;
|
||||
|
||||
class Admin extends Backend
|
||||
{
|
||||
/**
|
||||
* 模型
|
||||
* @var object
|
||||
* @phpstan-var AdminModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected array|string $preExcludeFields = ['create_time', 'update_time', 'password', 'salt', 'login_failure', 'last_login_time', 'last_login_ip'];
|
||||
|
||||
protected array|string $quickSearchField = ['username', 'nickname'];
|
||||
|
||||
/**
|
||||
* 开启数据限制
|
||||
*/
|
||||
protected string|int|bool $dataLimit = 'allAuthAndOthers';
|
||||
|
||||
protected string $dataLimitField = 'id';
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new AdminModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withoutField('login_failure,password,salt')
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
$this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
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 (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$passwd = $data['password']; // 密码将被排除不直接入库
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
if ($data['group_arr']) $this->checkGroupAuth($data['group_arr']);
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$result = $this->model->save($data);
|
||||
if ($data['group_arr']) {
|
||||
$groupAccess = [];
|
||||
foreach ($data['group_arr'] as $datum) {
|
||||
$groupAccess[] = [
|
||||
'uid' => $this->model->id,
|
||||
'group_id' => $datum,
|
||||
];
|
||||
}
|
||||
Db::name('admin_group_access')->insertAll($groupAccess);
|
||||
}
|
||||
$this->model->commit();
|
||||
|
||||
if (!empty($passwd)) {
|
||||
$this->model->resetPassword($this->model->id, $passwd);
|
||||
}
|
||||
} 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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
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('edit')->check($data);
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->auth->id == $data['id'] && $data['status'] == 'disable') {
|
||||
$this->error(__('Please use another administrator account to disable the current account!'));
|
||||
}
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$this->model->resetPassword($row->id, $data['password']);
|
||||
}
|
||||
|
||||
$groupAccess = [];
|
||||
if ($data['group_arr']) {
|
||||
$checkGroups = [];
|
||||
foreach ($data['group_arr'] as $datum) {
|
||||
if (!in_array($datum, $row->group_arr)) {
|
||||
$checkGroups[] = $datum;
|
||||
}
|
||||
$groupAccess[] = [
|
||||
'uid' => $id,
|
||||
'group_id' => $datum,
|
||||
];
|
||||
}
|
||||
$this->checkGroupAuth($checkGroups);
|
||||
}
|
||||
|
||||
Db::name('admin_group_access')
|
||||
->where('uid', $id)
|
||||
->delete();
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$result = $row->save($data);
|
||||
if ($groupAccess) Db::name('admin_group_access')->insertAll($groupAccess);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('Update successful'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
}
|
||||
|
||||
unset($row['salt'], $row['login_failure']);
|
||||
$row['password'] = '';
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function del(): void
|
||||
{
|
||||
$where = [];
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds) {
|
||||
$where[] = [$this->dataLimitField, 'in', $dataLimitAdminIds];
|
||||
}
|
||||
|
||||
$ids = $this->request->param('ids/a', []);
|
||||
$where[] = [$this->model->getPk(), 'in', $ids];
|
||||
$data = $this->model->where($where)->select();
|
||||
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $v) {
|
||||
if ($v->id != $this->auth->id) {
|
||||
$count += $v->delete();
|
||||
Db::name('admin_group_access')
|
||||
->where('uid', $v['id'])
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($count) {
|
||||
$this->success(__('Deleted successfully'));
|
||||
} else {
|
||||
$this->error(__('No rows were deleted'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查分组权限
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function checkGroupAuth(array $groups): void
|
||||
{
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
$authGroups = $this->auth->getAllAuthGroups('allAuthAndOthers');
|
||||
foreach ($groups as $group) {
|
||||
if (!in_array($group, $authGroups)) {
|
||||
$this->error(__('You have no permission to add an administrator to this group!'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/admin/controller/auth/AdminLog.php
Normal file
54
app/admin/controller/auth/AdminLog.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\AdminLog as AdminLogModel;
|
||||
|
||||
class AdminLog extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var AdminLogModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected string|array $preExcludeFields = ['create_time', 'admin_id', 'username'];
|
||||
|
||||
protected string|array $quickSearchField = ['title'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new AdminLogModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
$where[] = ['admin_id', '=', $this->auth->id];
|
||||
}
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
$this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
379
app/admin/controller/auth/Group.php
Normal file
379
app/admin/controller/auth/Group.php
Normal file
@@ -0,0 +1,379 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use ba\Tree;
|
||||
use Throwable;
|
||||
use think\facade\Db;
|
||||
use app\admin\model\AdminRule;
|
||||
use app\admin\model\AdminGroup;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class Group extends Backend
|
||||
{
|
||||
/**
|
||||
* 修改、删除分组时对操作管理员进行鉴权
|
||||
* 本管理功能部分场景对数据权限有要求,修改此值请额外确定以下的 absoluteAuth 实现的功能
|
||||
* allAuthAndOthers=管理员拥有该分组所有权限并拥有额外权限时允许
|
||||
*/
|
||||
protected string $authMethod = 'allAuthAndOthers';
|
||||
|
||||
/**
|
||||
* 数据模型
|
||||
* @var object
|
||||
* @phpstan-var AdminGroup
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected string|array $preExcludeFields = ['create_time', 'update_time'];
|
||||
|
||||
protected string|array $quickSearchField = 'name';
|
||||
|
||||
/**
|
||||
* @var Tree
|
||||
*/
|
||||
protected Tree $tree;
|
||||
|
||||
/**
|
||||
* 远程select初始化传值
|
||||
* @var array
|
||||
*/
|
||||
protected array $initValue;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
* @var string
|
||||
*/
|
||||
protected string $keyword;
|
||||
|
||||
/**
|
||||
* 是否组装Tree
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $assembleTree;
|
||||
|
||||
/**
|
||||
* 登录管理员的角色组
|
||||
* @var array
|
||||
*/
|
||||
protected array $adminGroups = [];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new AdminGroup();
|
||||
$this->tree = Tree::instance();
|
||||
|
||||
$isTree = $this->request->param('isTree', true);
|
||||
$this->initValue = $this->request->get("initValue/a", []);
|
||||
$this->initValue = array_filter($this->initValue);
|
||||
$this->keyword = $this->request->request("quickSearch", '');
|
||||
|
||||
// 有初始化值时不组装树状(初始化出来的值更好看)
|
||||
$this->assembleTree = $isTree && !$this->initValue;
|
||||
|
||||
$this->adminGroups = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $this->getGroups(),
|
||||
'group' => $this->adminGroups,
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @throws Throwable
|
||||
*/
|
||||
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);
|
||||
$data = $this->handleRules($data);
|
||||
|
||||
$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();
|
||||
$validate->scene('add')->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$this->checkAuth($id);
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->post();
|
||||
if (!$data) {
|
||||
$this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$adminGroup = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
|
||||
if (in_array($data['id'], $adminGroup)) {
|
||||
$this->error(__('You cannot modify your own management group!'));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$data = $this->handleRules($data);
|
||||
|
||||
$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();
|
||||
$validate->scene('edit')->check($data);
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('Update successful'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
}
|
||||
|
||||
// 读取所有pid,全部从节点数组移除,父级选择状态由子级决定
|
||||
$pidArr = AdminRule::field('pid')
|
||||
->distinct()
|
||||
->where('id', 'in', $row->rules)
|
||||
->select()
|
||||
->toArray();
|
||||
$rules = $row->rules ? explode(',', $row->rules) : [];
|
||||
foreach ($pidArr as $item) {
|
||||
$ruKey = array_search($item['pid'], $rules);
|
||||
if ($ruKey !== false) {
|
||||
unset($rules[$ruKey]);
|
||||
}
|
||||
}
|
||||
$row->rules = array_values($rules);
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function del(): void
|
||||
{
|
||||
$ids = $this->request->param('ids/a', []);
|
||||
$data = $this->model->where($this->model->getPk(), 'in', $ids)->select();
|
||||
foreach ($data as $v) {
|
||||
$this->checkAuth($v->id);
|
||||
}
|
||||
$subData = $this->model->where('pid', 'in', $ids)->column('pid', 'id');
|
||||
foreach ($subData as $key => $subDatum) {
|
||||
if (!in_array($key, $ids)) {
|
||||
$this->error(__('Please delete the child element first, or use batch deletion'));
|
||||
}
|
||||
}
|
||||
|
||||
$adminGroup = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $v) {
|
||||
if (!in_array($v['id'], $adminGroup)) {
|
||||
$count += $v->delete();
|
||||
}
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($count) {
|
||||
$this->success(__('Deleted successfully'));
|
||||
} else {
|
||||
$this->error(__('No rows were deleted'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程下拉
|
||||
* @return void
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function select(): void
|
||||
{
|
||||
$data = $this->getGroups([['status', '=', 1]]);
|
||||
|
||||
if ($this->assembleTree) {
|
||||
$data = $this->tree->assembleTree($this->tree->getTreeArray($data));
|
||||
}
|
||||
$this->success('', [
|
||||
'options' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限节点入库前处理
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function handleRules(array &$data): array
|
||||
{
|
||||
if (!empty($data['rules']) && is_array($data['rules'])) {
|
||||
$superAdmin = true;
|
||||
$checkedRules = [];
|
||||
$allRuleIds = AdminRule::column('id');
|
||||
|
||||
// 遍历检查权限ID是否存在(以免传递了可预测的未来权限ID号)
|
||||
foreach ($data['rules'] as $postRuleId) {
|
||||
if (in_array($postRuleId, $allRuleIds)) {
|
||||
$checkedRules[] = $postRuleId;
|
||||
}
|
||||
}
|
||||
|
||||
// 正在建立超管级分组?
|
||||
foreach ($allRuleIds as $ruleId) {
|
||||
if (!in_array($ruleId, $checkedRules)) {
|
||||
$superAdmin = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($superAdmin && $this->auth->isSuperAdmin()) {
|
||||
// 允许超管建立超管级分组
|
||||
$data['rules'] = '*';
|
||||
} else {
|
||||
// 当前管理员所拥有的权限节点
|
||||
$ownedRuleIds = $this->auth->getRuleIds();
|
||||
|
||||
// 禁止添加`拥有自己全部权限`的分组
|
||||
if (!array_diff($ownedRuleIds, $checkedRules)) {
|
||||
$this->error(__('Role group has all your rights, please contact the upper administrator to add or do not need to add!'));
|
||||
}
|
||||
|
||||
// 检查分组权限是否超出了自己的权限(超管的 $ownedRuleIds 为 ['*'],不便且可以不做此项检查)
|
||||
if (array_diff($checkedRules, $ownedRuleIds) && !$this->auth->isSuperAdmin()) {
|
||||
$this->error(__('The group permission node exceeds the range that can be allocated'));
|
||||
}
|
||||
|
||||
$data['rules'] = implode(',', $checkedRules);
|
||||
}
|
||||
} else {
|
||||
unset($data['rules']);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分组
|
||||
* @param array $where
|
||||
* @return array
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function getGroups(array $where = []): array
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$initKey = $this->request->get("initKey/s", $pk);
|
||||
|
||||
// 下拉选择时只获取:拥有所有权限并且有额外权限的分组
|
||||
$absoluteAuth = $this->request->get('absoluteAuth/b', false);
|
||||
|
||||
if ($this->keyword) {
|
||||
$keyword = explode(' ', $this->keyword);
|
||||
foreach ($keyword as $item) {
|
||||
$where[] = [$this->quickSearchField, 'like', '%' . $item . '%'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->initValue) {
|
||||
$where[] = [$initKey, 'in', $this->initValue];
|
||||
}
|
||||
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
$authGroups = $this->auth->getAllAuthGroups($this->authMethod, $where);
|
||||
if (!$absoluteAuth) $authGroups = array_merge($this->adminGroups, $authGroups);
|
||||
$where[] = ['id', 'in', $authGroups];
|
||||
}
|
||||
$data = $this->model->where($where)->select()->toArray();
|
||||
|
||||
// 获取第一个权限的名称供列表显示-s
|
||||
foreach ($data as &$datum) {
|
||||
if ($datum['rules']) {
|
||||
if ($datum['rules'] == '*') {
|
||||
$datum['rules'] = __('Super administrator');
|
||||
} else {
|
||||
$rules = explode(',', $datum['rules']);
|
||||
if ($rules) {
|
||||
$rulesFirstTitle = AdminRule::where('id', $rules[0])->value('title');
|
||||
$datum['rules'] = count($rules) == 1 ? $rulesFirstTitle : $rulesFirstTitle . '等 ' . count($rules) . ' 项';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$datum['rules'] = __('No permission');
|
||||
}
|
||||
}
|
||||
// 获取第一个权限的名称供列表显示-e
|
||||
|
||||
// 如果要求树状,此处先组装好 children
|
||||
return $this->assembleTree ? $this->tree->assembleChild($data) : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查权限
|
||||
* @param $groupId
|
||||
* @return void
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function checkAuth($groupId): void
|
||||
{
|
||||
$authGroups = $this->auth->getAllAuthGroups($this->authMethod, []);
|
||||
if (!$this->auth->isSuperAdmin() && !in_array($groupId, $authGroups)) {
|
||||
$this->error(__($this->authMethod == 'allAuth' ? 'You need to have all permissions of this group to operate this group~' : 'You need to have all the permissions of the group and have additional permissions before you can operate the group~'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
307
app/admin/controller/auth/Rule.php
Normal file
307
app/admin/controller/auth/Rule.php
Normal file
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use ba\Tree;
|
||||
use Throwable;
|
||||
use app\common\library\Menu;
|
||||
use app\admin\model\AdminRule;
|
||||
use app\admin\model\AdminGroup;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\library\crud\Helper;
|
||||
|
||||
class Rule extends Backend
|
||||
{
|
||||
protected string|array $preExcludeFields = ['create_time', 'update_time'];
|
||||
|
||||
protected string|array $defaultSortField = ['weigh' => 'desc'];
|
||||
|
||||
protected string|array $quickSearchField = 'title';
|
||||
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var AdminRule
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
/**
|
||||
* @var Tree
|
||||
*/
|
||||
protected Tree $tree;
|
||||
|
||||
/**
|
||||
* 远程select初始化传值
|
||||
* @var array
|
||||
*/
|
||||
protected array $initValue;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
* @var string
|
||||
*/
|
||||
protected string $keyword;
|
||||
|
||||
/**
|
||||
* 是否组装Tree
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $assembleTree;
|
||||
|
||||
/**
|
||||
* 开启模型验证
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $modelValidate = false;
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
// 防止 URL 中的特殊符号被默认的 filter 函数转义
|
||||
$this->request->filter('clean_xss');
|
||||
|
||||
$this->model = new AdminRule();
|
||||
$this->tree = Tree::instance();
|
||||
$isTree = $this->request->param('isTree', true);
|
||||
$this->initValue = $this->request->get('initValue/a', []);
|
||||
$this->initValue = array_filter($this->initValue);
|
||||
$this->keyword = $this->request->request('quickSearch', '');
|
||||
$this->assembleTree = $isTree && !$this->initValue; // 有初始化值时不组装树状(初始化出来的值更好看)
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $this->getMenus(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
|
||||
// 检查有那些分组可以拥有新增菜单的权限
|
||||
if (!empty($data['pid'])) {
|
||||
$this->autoAssignPermission($this->model->id, $data['pid']);
|
||||
}
|
||||
|
||||
// 创建子级权限节点
|
||||
if ($data['type'] == 'menu' && !empty($data['buttons'])) {
|
||||
$newButtons = [];
|
||||
foreach ($data['buttons'] as $button) {
|
||||
foreach (Helper::$menuChildren as $menuChild) {
|
||||
if ($menuChild['name'] == '/' . $button) {
|
||||
$menuChild['name'] = $data['name'] . $menuChild['name'];
|
||||
$newButtons[] = $menuChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($newButtons)) {
|
||||
// 创建子级权限节点
|
||||
Menu::create($newButtons, $this->model->id, 'ignore');
|
||||
|
||||
// 检查有那些分组可以拥有新增的子级权限
|
||||
$children = AdminRule::where('pid', $this->model->id)->select();
|
||||
foreach ($children as $child) {
|
||||
$this->autoAssignPermission($child['id'], $this->model->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$id = $this->request->param($this->model->getPk());
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->post();
|
||||
if (!$data) {
|
||||
$this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$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('edit');
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
if (isset($data['pid']) && $data['pid'] > 0) {
|
||||
// 满足意图并消除副作用
|
||||
$parent = $this->model->where('id', $data['pid'])->find();
|
||||
if ($parent['pid'] == $row['id']) {
|
||||
$parent->pid = 0;
|
||||
$parent->save();
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('Update successful'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function del(): void
|
||||
{
|
||||
$ids = $this->request->param('ids/a', []);
|
||||
|
||||
// 子级元素检查
|
||||
$subData = $this->model->where('pid', 'in', $ids)->column('pid', 'id');
|
||||
foreach ($subData as $key => $subDatum) {
|
||||
if (!in_array($key, $ids)) {
|
||||
$this->error(__('Please delete the child element first, or use batch deletion'));
|
||||
}
|
||||
}
|
||||
|
||||
parent::del();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写select方法
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function select(): void
|
||||
{
|
||||
$data = $this->getMenus([['type', 'in', ['menu_dir', 'menu']], ['status', '=', 1]]);
|
||||
|
||||
if ($this->assembleTree) {
|
||||
$data = $this->tree->assembleTree($this->tree->getTreeArray($data, 'title'));
|
||||
}
|
||||
$this->success('', [
|
||||
'options' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单列表
|
||||
* @throws Throwable
|
||||
*/
|
||||
protected function getMenus($where = []): array
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$initKey = $this->request->get("initKey/s", $pk);
|
||||
|
||||
$ids = $this->auth->getRuleIds();
|
||||
|
||||
// 如果没有 * 则只获取用户拥有的规则
|
||||
if (!in_array('*', $ids)) {
|
||||
$where[] = ['id', 'in', $ids];
|
||||
}
|
||||
|
||||
if ($this->keyword) {
|
||||
$keyword = explode(' ', $this->keyword);
|
||||
foreach ($keyword as $item) {
|
||||
$where[] = [$this->quickSearchField, 'like', '%' . $item . '%'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->initValue) {
|
||||
$where[] = [$initKey, 'in', $this->initValue];
|
||||
}
|
||||
|
||||
// 读取用户组所有权限规则
|
||||
$rules = $this->model
|
||||
->where($where)
|
||||
->order($this->queryOrderBuilder())
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 如果要求树状,此处先组装好 children
|
||||
return $this->assembleTree ? $this->tree->assembleChild($rules) : $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查所有非超管的分组是否应该拥有某个权限
|
||||
*/
|
||||
private function autoAssignPermission(int $id, int $pid): void
|
||||
{
|
||||
$groups = AdminGroup::where('rules', '<>', '*')->select();
|
||||
foreach ($groups as $group) {
|
||||
$rules = explode(',', $group->rules);
|
||||
if (in_array($pid, $rules) && !in_array($id, $rules)) {
|
||||
$rules[] = $id;
|
||||
$group->rules = implode(',', $rules);
|
||||
$group->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1002
app/admin/controller/crud/Crud.php
Normal file
1002
app/admin/controller/crud/Crud.php
Normal file
@@ -0,0 +1,1002 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\crud;
|
||||
|
||||
use Throwable;
|
||||
use ba\Exception;
|
||||
use ba\Filesystem;
|
||||
use think\facade\Db;
|
||||
use ba\TableManager;
|
||||
use app\admin\model\CrudLog;
|
||||
use app\common\library\Menu;
|
||||
use app\admin\model\AdminLog;
|
||||
use app\admin\model\AdminRule;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\library\crud\Helper;
|
||||
|
||||
class Crud extends Backend
|
||||
{
|
||||
/**
|
||||
* 模型文件数据
|
||||
* @var array
|
||||
*/
|
||||
protected array $modelData = [];
|
||||
|
||||
/**
|
||||
* 控制器文件数据
|
||||
* @var array
|
||||
*/
|
||||
protected array $controllerData = [];
|
||||
|
||||
/**
|
||||
* index.vue文件数据
|
||||
* @var array
|
||||
*/
|
||||
protected array $indexVueData = [];
|
||||
|
||||
/**
|
||||
* form.vue文件数据
|
||||
* @var array
|
||||
*/
|
||||
protected array $formVueData = [];
|
||||
|
||||
/**
|
||||
* 语言翻译前缀
|
||||
* @var string
|
||||
*/
|
||||
protected string $webTranslate = '';
|
||||
|
||||
/**
|
||||
* 语言包数据
|
||||
* @var array
|
||||
*/
|
||||
protected array $langTsData = [];
|
||||
|
||||
/**
|
||||
* 当designType为以下值时:
|
||||
* 1. 出入库字符串到数组转换
|
||||
* 2. 默认值转数组
|
||||
* @var array
|
||||
*/
|
||||
protected array $dtStringToArray = ['checkbox', 'selects', 'remoteSelects', 'city', 'images', 'files'];
|
||||
|
||||
protected array $noNeedPermission = ['logStart', 'getFileData', 'parseFieldData', 'generateCheck', 'uploadCompleted'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始生成
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function generate(): void
|
||||
{
|
||||
$type = $this->request->post('type', '');
|
||||
$table = $this->request->post('table', []);
|
||||
$fields = $this->request->post('fields', [], 'clean_xss,htmlspecialchars_decode_improve');
|
||||
|
||||
if (!$table || !$fields || !isset($table['name']) || !$table['name']) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
try {
|
||||
// 记录日志
|
||||
$crudLogId = Helper::recordCrudStatus([
|
||||
'table' => $table,
|
||||
'fields' => $fields,
|
||||
'status' => 'start',
|
||||
]);
|
||||
|
||||
// 表名称
|
||||
$tableName = TableManager::tableName($table['name'], false, $table['databaseConnection']);
|
||||
|
||||
if ($type == 'create' || $table['rebuild'] == 'Yes') {
|
||||
// 数据表存在则删除
|
||||
TableManager::phinxTable($tableName, [], true, $table['databaseConnection'])->drop()->save();
|
||||
}
|
||||
|
||||
// 处理表设计
|
||||
[$tablePk] = Helper::handleTableDesign($table, $fields);
|
||||
|
||||
// 表注释
|
||||
$tableComment = mb_substr($table['comment'], -1) == '表' ? mb_substr($table['comment'], 0, -1) . '管理' : $table['comment'];
|
||||
|
||||
// 生成文件信息解析
|
||||
$modelFile = Helper::parseNameData($table['isCommonModel'] ? 'common' : 'admin', $tableName, 'model', $table['modelFile']);
|
||||
$validateFile = Helper::parseNameData($table['isCommonModel'] ? 'common' : 'admin', $tableName, 'validate', $table['validateFile']);
|
||||
$controllerFile = Helper::parseNameData('admin', $tableName, 'controller', $table['controllerFile']);
|
||||
$webViewsDir = Helper::parseWebDirNameData($tableName, 'views', $table['webViewsDir']);
|
||||
$webLangDir = Helper::parseWebDirNameData($tableName, 'lang', $table['webViewsDir']);
|
||||
|
||||
// 语言翻译前缀
|
||||
$this->webTranslate = implode('.', $webLangDir['lang']) . '.';
|
||||
|
||||
// 快速搜索字段
|
||||
if (!in_array($tablePk, $table['quickSearchField'])) {
|
||||
$table['quickSearchField'][] = $tablePk;
|
||||
}
|
||||
$quickSearchFieldZhCnTitle = [];
|
||||
|
||||
// 模型数据
|
||||
$this->modelData['append'] = [];
|
||||
$this->modelData['methods'] = [];
|
||||
$this->modelData['fieldType'] = [];
|
||||
$this->modelData['createTime'] = '';
|
||||
$this->modelData['updateTime'] = '';
|
||||
$this->modelData['beforeInsertMixins'] = [];
|
||||
$this->modelData['beforeInsert'] = '';
|
||||
$this->modelData['afterInsert'] = '';
|
||||
$this->modelData['connection'] = $table['databaseConnection'];
|
||||
$this->modelData['name'] = $tableName;
|
||||
$this->modelData['className'] = $modelFile['lastName'];
|
||||
$this->modelData['namespace'] = $modelFile['namespace'];
|
||||
$this->modelData['relationMethodList'] = [];
|
||||
|
||||
// 控制器数据
|
||||
$this->controllerData['use'] = [];
|
||||
$this->controllerData['attr'] = [];
|
||||
$this->controllerData['methods'] = [];
|
||||
$this->controllerData['filterRule'] = '';
|
||||
$this->controllerData['className'] = $controllerFile['lastName'];
|
||||
$this->controllerData['namespace'] = $controllerFile['namespace'];
|
||||
$this->controllerData['tableComment'] = $tableComment;
|
||||
$this->controllerData['modelName'] = $modelFile['lastName'];
|
||||
$this->controllerData['modelNamespace'] = $modelFile['namespace'];
|
||||
|
||||
// index.vue数据
|
||||
$this->indexVueData['enableDragSort'] = false;
|
||||
$this->indexVueData['defaultItems'] = [];
|
||||
$this->indexVueData['tableColumn'] = [
|
||||
[
|
||||
'type' => 'selection',
|
||||
'align' => 'center',
|
||||
'operator' => 'false',
|
||||
],
|
||||
];
|
||||
$this->indexVueData['dblClickNotEditColumn'] = ['undefined'];
|
||||
$this->indexVueData['optButtons'] = ['edit', 'delete'];
|
||||
$this->indexVueData['defaultOrder'] = '';
|
||||
|
||||
// form.vue数据
|
||||
$this->formVueData['bigDialog'] = false;
|
||||
$this->formVueData['formFields'] = [];
|
||||
$this->formVueData['formValidatorRules'] = [];
|
||||
$this->formVueData['imports'] = [];
|
||||
|
||||
// 语言包数据
|
||||
$this->langTsData = [
|
||||
'en' => [],
|
||||
'zh-cn' => [],
|
||||
];
|
||||
|
||||
// 简化的字段数据
|
||||
$fieldsMap = [];
|
||||
|
||||
foreach ($fields as $key => $field) {
|
||||
|
||||
$fieldsMap[$field['name']] = $field['designType'];
|
||||
|
||||
// 分析字段
|
||||
Helper::analyseField($field);
|
||||
|
||||
Helper::getDictData($this->langTsData['en'], $field, 'en');
|
||||
Helper::getDictData($this->langTsData['zh-cn'], $field, 'zh-cn');
|
||||
|
||||
// 快速搜索字段
|
||||
if (in_array($field['name'], $table['quickSearchField'])) {
|
||||
$quickSearchFieldZhCnTitle[] = $this->langTsData['zh-cn'][$field['name']] ?? $field['name'];
|
||||
}
|
||||
|
||||
// 不允许双击编辑的字段
|
||||
if ($field['designType'] == 'switch') {
|
||||
$this->indexVueData['dblClickNotEditColumn'][] = $field['name'];
|
||||
}
|
||||
|
||||
// 列字典数据
|
||||
$columnDict = $this->getColumnDict($field);
|
||||
|
||||
// 表单项
|
||||
if (in_array($field['name'], $table['formFields'])) {
|
||||
$this->formVueData['formFields'][] = $this->getFormField($field, $columnDict, $table['databaseConnection']);
|
||||
}
|
||||
|
||||
// 表格列
|
||||
if (in_array($field['name'], $table['columnFields'])) {
|
||||
$this->indexVueData['tableColumn'][] = $this->getTableColumn($field, $columnDict);
|
||||
}
|
||||
|
||||
// 关联表数据解析
|
||||
if (in_array($field['designType'], ['remoteSelect', 'remoteSelects'])) {
|
||||
$this->parseJoinData($field, $table);
|
||||
}
|
||||
|
||||
// 模型方法
|
||||
$this->parseModelMethods($field, $this->modelData);
|
||||
|
||||
// 控制器/模型等文件的一些杂项属性解析
|
||||
$this->parseSundryData($field, $table);
|
||||
|
||||
if (!in_array($field['name'], $table['formFields'])) {
|
||||
$this->controllerData['attr']['preExcludeFields'][] = $field['name'];
|
||||
}
|
||||
}
|
||||
|
||||
// 快速搜索提示
|
||||
$this->langTsData['en']['quick Search Fields'] = implode(',', $table['quickSearchField']);
|
||||
$this->langTsData['zh-cn']['quick Search Fields'] = implode('、', $quickSearchFieldZhCnTitle);
|
||||
$this->controllerData['attr']['quickSearchField'] = $table['quickSearchField'];
|
||||
|
||||
// 开启字段排序
|
||||
$weighKey = array_search('weigh', $fieldsMap);
|
||||
if ($weighKey !== false) {
|
||||
$this->indexVueData['enableDragSort'] = true;
|
||||
$this->modelData['afterInsert'] = Helper::assembleStub('mixins/model/afterInsert', [
|
||||
'field' => $weighKey
|
||||
]);
|
||||
}
|
||||
|
||||
// 表格的操作列
|
||||
$this->indexVueData['tableColumn'][] = [
|
||||
'label' => "t('Operate')",
|
||||
'align' => 'center',
|
||||
'width' => $this->indexVueData['enableDragSort'] ? 140 : 100,
|
||||
'render' => 'buttons',
|
||||
'buttons' => 'optButtons',
|
||||
'operator' => 'false',
|
||||
];
|
||||
if ($this->indexVueData['enableDragSort']) {
|
||||
array_unshift($this->indexVueData['optButtons'], 'weigh-sort');
|
||||
}
|
||||
|
||||
// 写入语言包代码
|
||||
Helper::writeWebLangFile($this->langTsData, $webLangDir);
|
||||
|
||||
// 写入模型代码
|
||||
Helper::writeModelFile($tablePk, $fieldsMap, $this->modelData, $modelFile);
|
||||
|
||||
// 写入控制器代码
|
||||
Helper::writeControllerFile($this->controllerData, $controllerFile);
|
||||
|
||||
// 写入验证器代码
|
||||
$validateContent = Helper::assembleStub('mixins/validate/validate', [
|
||||
'namespace' => $validateFile['namespace'],
|
||||
'className' => $validateFile['lastName'],
|
||||
]);
|
||||
Helper::writeFile($validateFile['parseFile'], $validateContent);
|
||||
|
||||
// 写入index.vue代码
|
||||
$this->indexVueData['tablePk'] = $tablePk;
|
||||
$this->indexVueData['webTranslate'] = $this->webTranslate;
|
||||
Helper::writeIndexFile($this->indexVueData, $webViewsDir, $controllerFile);
|
||||
|
||||
// 写入form.vue代码
|
||||
Helper::writeFormFile($this->formVueData, $webViewsDir, $fields, $this->webTranslate);
|
||||
|
||||
// 生成菜单
|
||||
Helper::createMenu($webViewsDir, $tableComment);
|
||||
|
||||
Helper::recordCrudStatus([
|
||||
'id' => $crudLogId,
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
Helper::recordCrudStatus([
|
||||
'id' => $crudLogId ?? 0,
|
||||
'status' => 'error',
|
||||
]);
|
||||
$this->error($e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
Helper::recordCrudStatus([
|
||||
'id' => $crudLogId ?? 0,
|
||||
'status' => 'error',
|
||||
]);
|
||||
if (env('app_debug', false)) throw $e;
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'crudLog' => CrudLog::find($crudLogId),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从log开始
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function logStart(): void
|
||||
{
|
||||
$id = $this->request->post('id');
|
||||
$type = $this->request->post('type', '');
|
||||
|
||||
if ($type == 'Cloud history') {
|
||||
// 云端 历史记录
|
||||
$client = get_ba_client();
|
||||
$response = $client->request('GET', '/api/v6.Crud/info', [
|
||||
'query' => [
|
||||
'id' => $id,
|
||||
'server' => 1,
|
||||
'ba-user-token' => $this->request->post('token', ''),
|
||||
]
|
||||
]);
|
||||
$body = $response->getBody();
|
||||
$statusCode = $response->getStatusCode();
|
||||
$content = $body->getContents();
|
||||
if ($content == '' || stripos($content, '<title>系统发生错误</title>') !== false || $statusCode != 200) {
|
||||
$this->error(__('Failed to load cloud data'));
|
||||
}
|
||||
$json = json_decode($content, true);
|
||||
if (json_last_error() != JSON_ERROR_NONE) {
|
||||
$this->error(__('Failed to load cloud data'));
|
||||
}
|
||||
if (is_array($json)) {
|
||||
if ($json['code'] != 1) {
|
||||
$this->error($json['msg']);
|
||||
}
|
||||
|
||||
$info = $json['data']['info'];
|
||||
}
|
||||
} else {
|
||||
// 本地记录
|
||||
$info = CrudLog::find($id)->toArray();
|
||||
}
|
||||
|
||||
if (!isset($info) || !$info) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
// 数据表是否有数据
|
||||
$connection = TableManager::getConnection($info['table']['databaseConnection'] ?? '');
|
||||
$tableName = TableManager::tableName($info['table']['name'], false, $connection);
|
||||
$adapter = TableManager::phinxAdapter(true, $connection);
|
||||
if ($adapter->hasTable($tableName)) {
|
||||
$info['table']['empty'] = Db::connect($connection)
|
||||
->name($tableName)
|
||||
->limit(1)
|
||||
->select()
|
||||
->isEmpty();
|
||||
} else {
|
||||
$info['table']['empty'] = true;
|
||||
}
|
||||
|
||||
AdminLog::instance()->setTitle(__('Log start'));
|
||||
|
||||
$this->success('', [
|
||||
'table' => $info['table'],
|
||||
'fields' => $info['fields'],
|
||||
'sync' => $info['sync'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除CRUD记录和生成的文件
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$id = $this->request->post('id');
|
||||
$info = CrudLog::find($id)->toArray();
|
||||
if (!$info) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
$webLangDir = Helper::parseWebDirNameData($info['table']['name'], 'lang', $info['table']['webViewsDir']);
|
||||
$files = [
|
||||
$webLangDir['en'] . '.ts',
|
||||
$webLangDir['zh-cn'] . '.ts',
|
||||
$info['table']['webViewsDir'] . '/' . 'index.vue',
|
||||
$info['table']['webViewsDir'] . '/' . 'popupForm.vue',
|
||||
$info['table']['controllerFile'],
|
||||
$info['table']['modelFile'],
|
||||
$info['table']['validateFile'],
|
||||
];
|
||||
try {
|
||||
foreach ($files as &$file) {
|
||||
$file = Filesystem::fsFit(root_path() . $file);
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
Filesystem::delEmptyDir(dirname($file));
|
||||
}
|
||||
|
||||
// 删除菜单
|
||||
Menu::delete(Helper::getMenuName($webLangDir), true);
|
||||
|
||||
Helper::recordCrudStatus([
|
||||
'id' => $id,
|
||||
'status' => 'delete',
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success(__('Deleted successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件路径数据
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function getFileData(): void
|
||||
{
|
||||
$table = $this->request->get('table');
|
||||
$commonModel = $this->request->get('commonModel/b');
|
||||
|
||||
if (!$table) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
try {
|
||||
$modelFile = Helper::parseNameData($commonModel ? 'common' : 'admin', $table, 'model');
|
||||
$validateFile = Helper::parseNameData($commonModel ? 'common' : 'admin', $table, 'validate');
|
||||
$controllerFile = Helper::parseNameData('admin', $table, 'controller');
|
||||
$webViewsDir = Helper::parseWebDirNameData($table, 'views');
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
// 模型和控制器文件和文件列表
|
||||
$adminModelFiles = Filesystem::getDirFiles(root_path() . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'model' . DIRECTORY_SEPARATOR);
|
||||
$commonModelFiles = Filesystem::getDirFiles(root_path() . 'app' . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'model' . DIRECTORY_SEPARATOR);
|
||||
$adminControllerFiles = get_controller_list();
|
||||
|
||||
$modelFileList = [];
|
||||
$controllerFiles = [];
|
||||
foreach ($adminModelFiles as $item) {
|
||||
$item = Filesystem::fsFit('app/admin/model/' . $item);
|
||||
$modelFileList[$item] = $item;
|
||||
}
|
||||
foreach ($commonModelFiles as $item) {
|
||||
$item = Filesystem::fsFit('app/common/model/' . $item);
|
||||
$modelFileList[$item] = $item;
|
||||
}
|
||||
|
||||
$outExcludeController = [
|
||||
'Addon.php',
|
||||
'Ajax.php',
|
||||
'Dashboard.php',
|
||||
'Index.php',
|
||||
'Module.php',
|
||||
'Terminal.php',
|
||||
'routine/AdminInfo.php',
|
||||
'routine/Config.php',
|
||||
];
|
||||
foreach ($adminControllerFiles as $item) {
|
||||
if (in_array($item, $outExcludeController)) {
|
||||
continue;
|
||||
}
|
||||
$item = Filesystem::fsFit('app/admin/controller/' . $item);
|
||||
$controllerFiles[$item] = $item;
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'modelFile' => $modelFile['rootFileName'],
|
||||
'controllerFile' => $controllerFile['rootFileName'],
|
||||
'validateFile' => $validateFile['rootFileName'],
|
||||
'controllerFileList' => $controllerFiles,
|
||||
'modelFileList' => $modelFileList,
|
||||
'webViewsDir' => $webViewsDir['views'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已有CRUD记录
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function checkCrudLog(): void
|
||||
{
|
||||
$table = $this->request->get('table');
|
||||
$connection = $this->request->get('connection');
|
||||
$connection = $connection ?: config('database.default');
|
||||
|
||||
$crudLog = Db::name('crud_log')
|
||||
->where('table_name', $table)
|
||||
->where('connection', $connection)
|
||||
->order('create_time desc')
|
||||
->find();
|
||||
$this->success('', [
|
||||
'id' => ($crudLog && $crudLog['status'] == 'success') ? $crudLog['id'] : 0,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析字段数据
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function parseFieldData(): void
|
||||
{
|
||||
AdminLog::instance()->setTitle(__('Parse field data'));
|
||||
$type = $this->request->post('type');
|
||||
$table = $this->request->post('table');
|
||||
$connection = $this->request->post('connection');
|
||||
$connection = TableManager::getConnection($connection);
|
||||
|
||||
$table = TableManager::tableName($table, true, $connection);
|
||||
$connectionConfig = TableManager::getConnectionConfig($connection);
|
||||
|
||||
if ($type == 'db') {
|
||||
$sql = 'SELECT * FROM `information_schema`.`tables` '
|
||||
. 'WHERE TABLE_SCHEMA = ? AND table_name = ?';
|
||||
$tableInfo = Db::connect($connection)->query($sql, [$connectionConfig['database'], $table]);
|
||||
if (!$tableInfo) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
// 数据表是否有数据
|
||||
$adapter = TableManager::phinxAdapter(false, $connection);
|
||||
if ($adapter->hasTable($table)) {
|
||||
$empty = Db::connect($connection)
|
||||
->table($table)
|
||||
->limit(1)
|
||||
->select()
|
||||
->isEmpty();
|
||||
} else {
|
||||
$empty = true;
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'columns' => Helper::parseTableColumns($table, false, $connection),
|
||||
'comment' => $tableInfo[0]['TABLE_COMMENT'] ?? '',
|
||||
'empty' => $empty,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成前检查
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function generateCheck(): void
|
||||
{
|
||||
$table = $this->request->post('table');
|
||||
$connection = $this->request->post('connection');
|
||||
$webViewsDir = $this->request->post('webViewsDir', '');
|
||||
$controllerFile = $this->request->post('controllerFile', '');
|
||||
|
||||
if (!$table) {
|
||||
$this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
AdminLog::instance()->setTitle(__('Generate check'));
|
||||
|
||||
try {
|
||||
$webViewsDir = Helper::parseWebDirNameData($table, 'views', $webViewsDir);
|
||||
$controllerFile = Helper::parseNameData('admin', $table, 'controller', $controllerFile)['rootFileName'];
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
// 数据表是否存在
|
||||
$tableList = TableManager::getTableList($connection);
|
||||
$tableExist = array_key_exists(TableManager::tableName($table, true, $connection), $tableList);
|
||||
|
||||
// 控制器是否存在
|
||||
$controllerExist = file_exists(root_path() . $controllerFile);
|
||||
|
||||
// 菜单规则是否存在
|
||||
$menuName = Helper::getMenuName($webViewsDir);
|
||||
$menuExist = AdminRule::where('name', $menuName)->value('id');
|
||||
|
||||
if ($controllerExist || $tableExist || $menuExist) {
|
||||
$this->error('', [
|
||||
'menu' => $menuExist,
|
||||
'table' => $tableExist,
|
||||
'controller' => $controllerExist,
|
||||
], -1);
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* CRUD 设计记录上传成功标记
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function uploadCompleted(): void
|
||||
{
|
||||
$syncIds = $this->request->post('syncIds/a', []);
|
||||
$cancelSync = $this->request->post('cancelSync/b', false);
|
||||
$crudLogModel = new CrudLog();
|
||||
|
||||
if ($cancelSync) {
|
||||
$logData = $crudLogModel->where('id', 'in', array_keys($syncIds))->select();
|
||||
foreach ($logData as $logDatum) {
|
||||
if ($logDatum->sync == $syncIds[$logDatum->id]) {
|
||||
$logDatum->sync = 0;
|
||||
$logDatum->save();
|
||||
}
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
$saveData = [];
|
||||
foreach ($syncIds as $key => $syncId) {
|
||||
$saveData[] = [
|
||||
'id' => $key,
|
||||
'sync' => $syncId,
|
||||
];
|
||||
}
|
||||
$crudLogModel->saveAll($saveData);
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联表数据解析
|
||||
* @param $field
|
||||
* @param $table
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function parseJoinData($field, $table): void
|
||||
{
|
||||
$dictEn = [];
|
||||
$dictZhCn = [];
|
||||
|
||||
if ($field['form']['relation-fields'] && $field['form']['remote-table']) {
|
||||
$columns = Helper::parseTableColumns($field['form']['remote-table'], true, $table['databaseConnection']);
|
||||
$relationFields = explode(',', $field['form']['relation-fields']);
|
||||
$tableName = TableManager::tableName($field['form']['remote-table'], false, $table['databaseConnection']);
|
||||
$rnPattern = '/(.*)(_ids|_id)$/';
|
||||
if (preg_match($rnPattern, $field['name'])) {
|
||||
$relationName = parse_name(preg_replace($rnPattern, '$1', $field['name']), 1, false);
|
||||
} else {
|
||||
$relationName = parse_name($field['name'] . '_table', 1, false);
|
||||
}
|
||||
|
||||
// 建立关联模型代码文件
|
||||
if (!$field['form']['remote-model'] || !file_exists(root_path() . $field['form']['remote-model'])) {
|
||||
$joinModelFile = Helper::parseNameData('admin', $tableName, 'model', $field['form']['remote-model']);
|
||||
if (!file_exists(root_path() . $joinModelFile['rootFileName'])) {
|
||||
$joinModelData['append'] = [];
|
||||
$joinModelData['methods'] = [];
|
||||
$joinModelData['fieldType'] = [];
|
||||
$joinModelData['createTime'] = '';
|
||||
$joinModelData['updateTime'] = '';
|
||||
$joinModelData['beforeInsertMixins'] = [];
|
||||
$joinModelData['beforeInsert'] = '';
|
||||
$joinModelData['afterInsert'] = '';
|
||||
$joinModelData['connection'] = $table['databaseConnection'];
|
||||
$joinModelData['name'] = $tableName;
|
||||
$joinModelData['className'] = $joinModelFile['lastName'];
|
||||
$joinModelData['namespace'] = $joinModelFile['namespace'];
|
||||
$joinTablePk = 'id';
|
||||
$joinFieldsMap = [];
|
||||
foreach ($columns as $column) {
|
||||
$joinFieldsMap[$column['name']] = $column['designType'];
|
||||
$this->parseModelMethods($column, $joinModelData);
|
||||
if ($column['primaryKey']) $joinTablePk = $column['name'];
|
||||
}
|
||||
$weighKey = array_search('weigh', $joinFieldsMap);
|
||||
if ($weighKey !== false) {
|
||||
$joinModelData['afterInsert'] = Helper::assembleStub('mixins/model/afterInsert', [
|
||||
'field' => $joinFieldsMap[$weighKey]
|
||||
]);
|
||||
}
|
||||
Helper::writeModelFile($joinTablePk, $joinFieldsMap, $joinModelData, $joinModelFile);
|
||||
}
|
||||
$field['form']['remote-model'] = $joinModelFile['rootFileName'];
|
||||
}
|
||||
|
||||
if ($field['designType'] == 'remoteSelect') {
|
||||
// 关联预载入方法
|
||||
$this->controllerData['attr']['withJoinTable'][$relationName] = $relationName;
|
||||
|
||||
// 模型方法代码
|
||||
$relationData = [
|
||||
'relationMethod' => $relationName,
|
||||
'relationMode' => 'belongsTo',
|
||||
'relationPrimaryKey' => $field['form']['remote-pk'] ?? 'id',
|
||||
'relationForeignKey' => $field['name'],
|
||||
'relationClassName' => str_replace(['.php', '/'], ['', '\\'], '\\' . $field['form']['remote-model']) . "::class",
|
||||
];
|
||||
$this->modelData['relationMethodList'][$relationName] = Helper::assembleStub('mixins/model/belongsTo', $relationData);
|
||||
|
||||
// 查询时显示的字段
|
||||
if ($relationFields) {
|
||||
$this->controllerData['relationVisibleFieldList'][$relationData['relationMethod']] = $relationFields;
|
||||
}
|
||||
} elseif ($field['designType'] == 'remoteSelects') {
|
||||
$this->modelData['append'][] = $relationName;
|
||||
$this->modelData['methods'][] = Helper::assembleStub('mixins/model/getters/remoteSelectLabels', [
|
||||
'field' => parse_name($relationName, 1),
|
||||
'className' => str_replace(['.php', '/'], ['', '\\'], '\\' . $field['form']['remote-model']),
|
||||
'primaryKey' => $field['form']['remote-pk'] ?? 'id',
|
||||
'foreignKey' => $field['name'],
|
||||
'labelFieldName' => $field['form']['remote-field'] ?? 'name',
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($relationFields as $relationField) {
|
||||
if (!array_key_exists($relationField, $columns)) continue;
|
||||
$relationFieldPrefix = $relationName . '.';
|
||||
$relationFieldLangPrefix = strtolower($relationName) . '__';
|
||||
Helper::getDictData($dictEn, $columns[$relationField], 'en', $relationFieldLangPrefix);
|
||||
Helper::getDictData($dictZhCn, $columns[$relationField], 'zh-cn', $relationFieldLangPrefix);
|
||||
|
||||
// 不允许双击编辑的字段
|
||||
if ($columns[$relationField]['designType'] == 'switch') {
|
||||
$this->indexVueData['dblClickNotEditColumn'][] = $field['name'];
|
||||
}
|
||||
|
||||
// 列字典数据
|
||||
$columnDict = $this->getColumnDict($columns[$relationField], $relationFieldLangPrefix);
|
||||
|
||||
// 表格列
|
||||
$columns[$relationField]['designType'] = $field['designType'];
|
||||
$columns[$relationField]['form'] = $field['form'] + $columns[$relationField]['form'];
|
||||
$columns[$relationField]['table'] = $field['table'] + $columns[$relationField]['table'];
|
||||
|
||||
// 公共搜索渲染为远程下拉时,远程下拉组件的必填属性
|
||||
$remoteAttr = [
|
||||
'pk' => $this->getRemoteSelectPk($field),
|
||||
'field' => $field['form']['remote-field'] ?? 'name',
|
||||
'remoteUrl' => $this->getRemoteSelectUrl($field),
|
||||
];
|
||||
|
||||
if ($columns[$relationField]['table']['comSearchRender'] == 'remoteSelect') {
|
||||
// 生成为已关闭公共搜索的表格列
|
||||
$renderColumn = $columns[$relationField];
|
||||
$renderColumn['table']['operator'] = 'false';
|
||||
unset($renderColumn['table']['comSearchRender']);
|
||||
$this->indexVueData['tableColumn'][] = $this->getTableColumn($renderColumn, $columnDict, $relationFieldPrefix, $relationFieldLangPrefix);
|
||||
|
||||
// 额外生成一个公共搜索渲染为远程下拉的列,关闭表格列表显示
|
||||
$columns[$relationField]['table']['show'] = 'false';
|
||||
$columns[$relationField]['table']['label'] = "t('" . $this->webTranslate . $relationFieldLangPrefix . $columns[$relationField]['name'] . "')";
|
||||
$columns[$relationField]['name'] = $field['name'];
|
||||
|
||||
// 标记多选
|
||||
if ($field['designType'] == 'remoteSelects') {
|
||||
$remoteAttr['multiple'] = 'true';
|
||||
}
|
||||
|
||||
$columnData = $this->getTableColumn($columns[$relationField], $columnDict, '', $relationFieldLangPrefix);
|
||||
$columnData['comSearchInputAttr'] = array_merge($remoteAttr, $columnData['comSearchInputAttr'] ?? []);
|
||||
} else {
|
||||
$columnData = $this->getTableColumn($columns[$relationField], $columnDict, $relationFieldPrefix, $relationFieldLangPrefix);
|
||||
}
|
||||
$this->indexVueData['tableColumn'][] = $columnData;
|
||||
}
|
||||
}
|
||||
$this->langTsData['en'] = array_merge($this->langTsData['en'], $dictEn);
|
||||
$this->langTsData['zh-cn'] = array_merge($this->langTsData['zh-cn'], $dictZhCn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析模型方法(设置器、获取器等)
|
||||
*/
|
||||
private function parseModelMethods($field, &$modelData): void
|
||||
{
|
||||
// fieldType
|
||||
if ($field['designType'] == 'array') {
|
||||
$modelData['fieldType'][$field['name']] = 'json';
|
||||
} elseif (!in_array($field['name'], ['create_time', 'update_time', 'updatetime', 'createtime']) && $field['designType'] == 'datetime' && (in_array($field['type'], ['int', 'bigint']))) {
|
||||
$modelData['fieldType'][$field['name']] = 'timestamp:Y-m-d H:i:s';
|
||||
}
|
||||
|
||||
// beforeInsertMixins
|
||||
if ($field['designType'] == 'spk') {
|
||||
$modelData['beforeInsertMixins']['snowflake'] = Helper::assembleStub('mixins/model/mixins/beforeInsertWithSnowflake', []);
|
||||
}
|
||||
|
||||
// methods
|
||||
$fieldName = parse_name($field['name'], 1);
|
||||
if (in_array($field['designType'], $this->dtStringToArray)) {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/stringToArray', [
|
||||
'field' => $fieldName
|
||||
]);
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/setters/arrayToString', [
|
||||
'field' => $fieldName
|
||||
]);
|
||||
} elseif ($field['designType'] == 'array') {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/jsonDecode', [
|
||||
'field' => $fieldName
|
||||
]);
|
||||
} elseif ($field['designType'] == 'time') {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/setters/time', [
|
||||
'field' => $fieldName
|
||||
]);
|
||||
} elseif ($field['designType'] == 'editor') {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/htmlDecode', [
|
||||
'field' => $fieldName
|
||||
]);
|
||||
} elseif ($field['designType'] == 'spk') {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/string', [
|
||||
'field' => $fieldName
|
||||
]);
|
||||
} elseif (in_array($field['type'], ['float', 'decimal', 'double'])) {
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/float', [
|
||||
'field' => $fieldName
|
||||
]);
|
||||
}
|
||||
|
||||
if ($field['designType'] == 'city') {
|
||||
$modelData['append'][] = $field['name'] . '_text';
|
||||
$modelData['methods'][] = Helper::assembleStub('mixins/model/getters/cityNames', [
|
||||
'field' => $fieldName . 'Text',
|
||||
'originalFieldName' => $field['name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制器/模型等文件的一些杂项属性解析
|
||||
*/
|
||||
private function parseSundryData($field, $table): void
|
||||
{
|
||||
if ($field['designType'] == 'editor') {
|
||||
$this->formVueData['bigDialog'] = true; // 加宽 dialog
|
||||
$this->controllerData['filterRule'] = "\n" . Helper::tab(2) . '$this->request->filter(\'clean_xss\');'; // 修改变量过滤规则
|
||||
}
|
||||
|
||||
// 默认排序字段
|
||||
if ($table['defaultSortField'] && $table['defaultSortType']) {
|
||||
$defaultSortField = "{$table['defaultSortField']},{$table['defaultSortType']}";
|
||||
if ($defaultSortField == 'id,desc') {
|
||||
$this->controllerData['attr']['defaultSortField'] = '';
|
||||
} else {
|
||||
$this->controllerData['attr']['defaultSortField'] = $defaultSortField;
|
||||
$this->indexVueData['defaultOrder'] = Helper::buildDefaultOrder($table['defaultSortField'], $table['defaultSortType']);
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义了权重字段名称
|
||||
if ($field['originalDesignType'] == 'weigh' && $field['name'] != 'weigh') {
|
||||
$this->controllerData['attr']['weighField'] = $field['name'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装前台表单的数据
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function getFormField($field, $columnDict, ?string $dbConnection = null): array
|
||||
{
|
||||
// 表单项属性
|
||||
$formField = [
|
||||
':label' => 't(\'' . $this->webTranslate . $field['name'] . '\')',
|
||||
'type' => $field['designType'],
|
||||
'v-model' => 'baTable.form.items!.' . $field['name'],
|
||||
'prop' => $field['name'],
|
||||
];
|
||||
|
||||
// 不同输入框的属性处理
|
||||
if ($columnDict || in_array($field['designType'], ['radio', 'checkbox', 'select', 'selects'])) {
|
||||
$formField[':input-attr']['content'] = $columnDict;
|
||||
} elseif ($field['designType'] == 'textarea') {
|
||||
$formField[':input-attr']['rows'] = (int)($field['form']['rows'] ?? 3);
|
||||
$formField['@keyup.enter.stop'] = '';
|
||||
$formField['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)';
|
||||
} elseif ($field['designType'] == 'remoteSelect' || $field['designType'] == 'remoteSelects') {
|
||||
$formField[':input-attr']['pk'] = $this->getRemoteSelectPk($field);
|
||||
$formField[':input-attr']['field'] = $field['form']['remote-field'] ?? 'name';
|
||||
$formField[':input-attr']['remoteUrl'] = $this->getRemoteSelectUrl($field);
|
||||
} elseif ($field['designType'] == 'number') {
|
||||
$formField[':input-attr']['step'] = (int)($field['form']['step'] ?? 1);
|
||||
} elseif ($field['designType'] == 'icon') {
|
||||
$formField[':input-attr']['placement'] = 'top';
|
||||
} elseif ($field['designType'] == 'editor') {
|
||||
$formField['@keyup.enter.stop'] = '';
|
||||
$formField['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)';
|
||||
}
|
||||
|
||||
// placeholder
|
||||
if (!in_array($field['designType'], ['image', 'images', 'file', 'files', 'switch'])) {
|
||||
if (in_array($field['designType'], ['radio', 'checkbox', 'datetime', 'year', 'date', 'time', 'select', 'selects', 'remoteSelect', 'remoteSelects', 'city', 'icon'])) {
|
||||
$formField[':placeholder'] = "t('Please select field', { field: t('" . $this->webTranslate . $field['name'] . "') })";
|
||||
} else {
|
||||
$formField[':placeholder'] = "t('Please input field', { field: t('" . $this->webTranslate . $field['name'] . "') })";
|
||||
}
|
||||
}
|
||||
|
||||
// 默认值
|
||||
if ($field['defaultType'] == 'INPUT') {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = $field['default'];
|
||||
}
|
||||
|
||||
// 部分生成类型的默认值需要额外处理
|
||||
if ($field['designType'] == 'editor') {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = ($field['defaultType'] == 'INPUT' && $field['default']) ? $field['default'] : '';
|
||||
} elseif ($field['designType'] == 'array') {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = "[]";
|
||||
} elseif ($field['defaultType'] == 'INPUT' && in_array($field['designType'], $this->dtStringToArray) && str_contains($field['default'], ',')) {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = Helper::buildSimpleArray(explode(',', $field['default']));
|
||||
} elseif ($field['defaultType'] == 'INPUT' && in_array($field['designType'], ['number', 'float'])) {
|
||||
$this->indexVueData['defaultItems'][$field['name']] = (float)$field['default'];
|
||||
}
|
||||
|
||||
// 无意义的默认值
|
||||
if (isset($field['default']) && in_array($field['designType'], ['switch', 'number', 'float', 'remoteSelect']) && $field['default'] == 0) {
|
||||
unset($this->indexVueData['defaultItems'][$field['name']]);
|
||||
}
|
||||
|
||||
return $formField;
|
||||
}
|
||||
|
||||
private function getRemoteSelectPk($field): string
|
||||
{
|
||||
$pk = $field['form']['remote-pk'] ?? 'id';
|
||||
if (!str_contains($pk, '.')) {
|
||||
if ($field['form']['remote-source-config-type'] == 'crud' && $field['form']['remote-model']) {
|
||||
$alias = parse_name(basename(str_replace('\\', '/', $field['form']['remote-model']), '.php'));
|
||||
} else {
|
||||
$alias = $field['form']['remote-primary-table-alias'] ?? '';
|
||||
}
|
||||
}
|
||||
return !empty($alias) ? "$alias.$pk" : $pk;
|
||||
}
|
||||
|
||||
private function getRemoteSelectUrl($field): string
|
||||
{
|
||||
if ($field['form']['remote-source-config-type'] == 'crud' && $field['form']['remote-controller']) {
|
||||
$pathArr = [];
|
||||
$controller = explode(DIRECTORY_SEPARATOR, $field['form']['remote-controller']);
|
||||
$controller = str_replace('.php', '', $controller);
|
||||
$redundantDir = [
|
||||
'app' => 0,
|
||||
'admin' => 1,
|
||||
'controller' => 2,
|
||||
];
|
||||
foreach ($controller as $key => $item) {
|
||||
if (!array_key_exists($item, $redundantDir) || $key !== $redundantDir[$item]) {
|
||||
$pathArr[] = $item;
|
||||
}
|
||||
}
|
||||
$url = count($pathArr) > 1 ? implode('.', $pathArr) : $pathArr[0];
|
||||
return '/admin/' . $url . '/index';
|
||||
}
|
||||
return $field['form']['remote-url'];
|
||||
}
|
||||
|
||||
private function getTableColumn($field, $columnDict, $fieldNamePrefix = '', $translationPrefix = ''): array
|
||||
{
|
||||
$column = [
|
||||
'label' => "t('" . $this->webTranslate . $translationPrefix . $field['name'] . "')",
|
||||
'prop' => $fieldNamePrefix . $field['name'] . ($field['designType'] == 'city' ? '_text' : ''),
|
||||
'align' => 'center',
|
||||
];
|
||||
|
||||
// 模糊搜索增加一个placeholder
|
||||
if (isset($field['table']['operator']) && $field['table']['operator'] == 'LIKE') {
|
||||
$column['operatorPlaceholder'] = "t('Fuzzy query')";
|
||||
}
|
||||
|
||||
// 合并前端预设的字段表格属性
|
||||
if (!empty($field['table'])) {
|
||||
$column = array_merge($column, $field['table']);
|
||||
$column['comSearchInputAttr'] = str_attr_to_array($column['comSearchInputAttr'] ?? '');
|
||||
}
|
||||
|
||||
// 需要值替换的渲染类型
|
||||
$columnReplaceValue = ['tag', 'tags', 'switch'];
|
||||
if (!in_array($field['designType'], ['remoteSelect', 'remoteSelects']) && ($columnDict || (isset($field['table']['render']) && in_array($field['table']['render'], $columnReplaceValue)))) {
|
||||
$column['replaceValue'] = $columnDict;
|
||||
}
|
||||
|
||||
if (isset($column['render']) && $column['render'] == 'none') {
|
||||
unset($column['render']);
|
||||
}
|
||||
return $column;
|
||||
}
|
||||
|
||||
private function getColumnDict($column, $translationPrefix = ''): array
|
||||
{
|
||||
$dict = [];
|
||||
// 确保字典中无翻译也可以识别到该值
|
||||
if (in_array($column['type'], ['enum', 'set'])) {
|
||||
$dataType = str_replace(' ', '', $column['dataType']);
|
||||
$columnData = substr($dataType, stripos($dataType, '(') + 1, -1);
|
||||
$columnData = explode(',', str_replace(["'", '"'], '', $columnData));
|
||||
foreach ($columnData as $columnDatum) {
|
||||
$dict[$columnDatum] = $column['name'] . ' ' . $columnDatum;
|
||||
}
|
||||
}
|
||||
$dictData = [];
|
||||
Helper::getDictData($dictData, $column, 'zh-cn', $translationPrefix);
|
||||
if ($dictData) {
|
||||
unset($dictData[$translationPrefix . $column['name']]);
|
||||
foreach ($dictData as $key => $item) {
|
||||
$keyName = str_replace($translationPrefix . $column['name'] . ' ', '', $key);
|
||||
$dict[$keyName] = "t('" . $this->webTranslate . $key . "')";
|
||||
}
|
||||
}
|
||||
return $dict;
|
||||
}
|
||||
}
|
||||
37
app/admin/controller/crud/Log.php
Normal file
37
app/admin/controller/crud/Log.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\crud;
|
||||
|
||||
use app\admin\model\CrudLog;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* crud记录
|
||||
*
|
||||
*/
|
||||
class Log extends Backend
|
||||
{
|
||||
/**
|
||||
* Log模型对象
|
||||
* @var object
|
||||
* @phpstan-var CrudLog
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected string|array $preExcludeFields = ['id', 'create_time'];
|
||||
|
||||
protected string|array $quickSearchField = ['id', 'table_name', 'comment'];
|
||||
|
||||
protected array $noNeedPermission = ['index'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new CrudLog();
|
||||
|
||||
if (!$this->auth->check('crud/crud/index')) {
|
||||
$this->error(__('You have no permission'), [], 401);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
90
app/admin/controller/routine/AdminInfo.php
Normal file
90
app/admin/controller/routine/AdminInfo.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\routine;
|
||||
|
||||
use Throwable;
|
||||
use app\admin\model\Admin;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class AdminInfo extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var Admin
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected string|array $preExcludeFields = ['username', 'last_login_time', 'password', 'salt', 'status'];
|
||||
|
||||
protected array $authAllowFields = ['id', 'username', 'nickname', 'avatar', 'email', 'mobile', 'motto', 'last_login_time'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->auth->setAllowFields($this->authAllowFields);
|
||||
$this->model = $this->auth->getAdmin();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$info = $this->auth->getInfo();
|
||||
$this->success('', [
|
||||
'info' => $info
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$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', ['']));
|
||||
}
|
||||
|
||||
if (!empty($data['avatar'])) {
|
||||
$row->avatar = $data['avatar'];
|
||||
if ($row->save()) {
|
||||
$this->success(__('Avatar modified successfully!'));
|
||||
}
|
||||
}
|
||||
|
||||
// 数据验证
|
||||
if ($this->modelValidate) {
|
||||
try {
|
||||
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
$validate = new $validate();
|
||||
$validate->scene('info')->check($data);
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$this->model->resetPassword($this->auth->id, $data['password']);
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('Update successful'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
app/admin/controller/routine/Attachment.php
Normal file
59
app/admin/controller/routine/Attachment.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\routine;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
use app\common\model\Attachment as AttachmentModel;
|
||||
|
||||
class Attachment extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var AttachmentModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected string|array $quickSearchField = 'name';
|
||||
|
||||
protected array $withJoinTable = ['admin', 'user'];
|
||||
|
||||
protected string|array $defaultSortField = 'last_upload_time,desc';
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new AttachmentModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function del(): void
|
||||
{
|
||||
$where = [];
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds) {
|
||||
$where[] = [$this->dataLimitField, 'in', $dataLimitAdminIds];
|
||||
}
|
||||
|
||||
$ids = $this->request->param('ids/a', []);
|
||||
$where[] = [$this->model->getPk(), 'in', $ids];
|
||||
$data = $this->model->where($where)->select();
|
||||
|
||||
$count = 0;
|
||||
try {
|
||||
foreach ($data as $v) {
|
||||
$count += $v->delete();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->error(__('%d records and files have been deleted', [$count]) . $e->getMessage());
|
||||
}
|
||||
if ($count) {
|
||||
$this->success(__('%d records and files have been deleted', [$count]));
|
||||
} else {
|
||||
$this->error(__('No rows were deleted'));
|
||||
}
|
||||
}
|
||||
}
|
||||
246
app/admin/controller/routine/Config.php
Normal file
246
app/admin/controller/routine/Config.php
Normal file
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\routine;
|
||||
|
||||
use Throwable;
|
||||
use ba\Filesystem;
|
||||
use app\common\library\Email;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\Config as ConfigModel;
|
||||
use PHPMailer\PHPMailer\Exception as PHPMailerException;
|
||||
|
||||
class Config extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var ConfigModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected array $filePath = [
|
||||
'appConfig' => 'config/app.php',
|
||||
'webAdminBase' => 'web/src/router/static/adminBase.ts',
|
||||
'backendEntranceStub' => 'app/admin/library/stubs/backendEntrance.stub',
|
||||
];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new ConfigModel();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$configGroup = get_sys_config('config_group');
|
||||
$config = $this->model->order('weigh desc')->select()->toArray();
|
||||
|
||||
$list = [];
|
||||
$newConfigGroup = [];
|
||||
foreach ($configGroup as $item) {
|
||||
$list[$item['key']]['name'] = $item['key'];
|
||||
$list[$item['key']]['title'] = __($item['value']);
|
||||
$newConfigGroup[$item['key']] = $list[$item['key']]['title'];
|
||||
}
|
||||
foreach ($config as $item) {
|
||||
if (array_key_exists($item['group'], $newConfigGroup)) {
|
||||
$item['title'] = __($item['title']);
|
||||
$list[$item['group']]['list'][] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $list,
|
||||
'remark' => get_route_remark(),
|
||||
'configGroup' => $newConfigGroup ?? [],
|
||||
'quickEntrance' => get_sys_config('config_quick_entrance'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$all = $this->model->select();
|
||||
foreach ($all as $item) {
|
||||
if ($item['type'] == 'editor') {
|
||||
$this->request->filter('clean_xss');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$this->modelValidate = false;
|
||||
$data = $this->request->post();
|
||||
if (!$data) {
|
||||
$this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
|
||||
$configValue = [];
|
||||
foreach ($all as $item) {
|
||||
if (array_key_exists($item->name, $data)) {
|
||||
$configValue[] = [
|
||||
'id' => $item->id,
|
||||
'type' => $item->getData('type'),
|
||||
'value' => $data[$item->name]
|
||||
];
|
||||
|
||||
// 自定义后台入口
|
||||
if ($item->name == 'backend_entrance') {
|
||||
$backendEntrance = get_sys_config('backend_entrance');
|
||||
if ($backendEntrance == $data[$item->name]) continue;
|
||||
|
||||
if (!preg_match("/^\/[a-zA-Z0-9]+$/", $data[$item->name])) {
|
||||
$this->error(__('Backend entrance rule'));
|
||||
}
|
||||
|
||||
// 修改 adminBaseRoutePath
|
||||
$adminBaseFilePath = Filesystem::fsFit(root_path() . $this->filePath['webAdminBase']);
|
||||
$adminBaseContent = @file_get_contents($adminBaseFilePath);
|
||||
if (!$adminBaseContent) $this->error(__('Configuration write failed: %s', [$this->filePath['webAdminBase']]));
|
||||
|
||||
$adminBaseContent = str_replace("export const adminBaseRoutePath = '$backendEntrance'", "export const adminBaseRoutePath = '{$data[$item->name]}'", $adminBaseContent);
|
||||
$result = @file_put_contents($adminBaseFilePath, $adminBaseContent);
|
||||
if (!$result) $this->error(__('Configuration write failed: %s', [$this->filePath['webAdminBase']]));
|
||||
|
||||
// 去除后台入口开头的斜杠
|
||||
$oldBackendEntrance = ltrim($backendEntrance, '/');
|
||||
$newBackendEntrance = ltrim($data[$item->name], '/');
|
||||
|
||||
// 设置应用别名映射
|
||||
$appMap = config('app.app_map');
|
||||
$adminMapKey = array_search('admin', $appMap);
|
||||
if ($adminMapKey !== false) {
|
||||
unset($appMap[$adminMapKey]);
|
||||
}
|
||||
if ($newBackendEntrance != 'admin') {
|
||||
$appMap[$newBackendEntrance] = 'admin';
|
||||
}
|
||||
$appConfigFilePath = Filesystem::fsFit(root_path() . $this->filePath['appConfig']);
|
||||
$appConfigContent = @file_get_contents($appConfigFilePath);
|
||||
if (!$appConfigContent) $this->error(__('Configuration write failed: %s', [$this->filePath['appConfig']]));
|
||||
|
||||
$appMapStr = '';
|
||||
foreach ($appMap as $newAppName => $oldAppName) {
|
||||
$appMapStr .= "'$newAppName' => '$oldAppName', ";
|
||||
}
|
||||
$appMapStr = rtrim($appMapStr, ', ');
|
||||
$appMapStr = "[$appMapStr]";
|
||||
|
||||
$appConfigContent = preg_replace("/'app_map'(\s+)=>(\s+)(.*)\/\/ 域名/s", "'app_map'\$1=>\$2$appMapStr,\n // 域名", $appConfigContent);
|
||||
$result = @file_put_contents($appConfigFilePath, $appConfigContent);
|
||||
if (!$result) $this->error(__('Configuration write failed: %s', [$this->filePath['appConfig']]));
|
||||
|
||||
// 建立API入口文件
|
||||
$oldBackendEntranceFile = Filesystem::fsFit(public_path() . $oldBackendEntrance . '.php');
|
||||
$newBackendEntranceFile = Filesystem::fsFit(public_path() . $newBackendEntrance . '.php');
|
||||
if (file_exists($oldBackendEntranceFile)) @unlink($oldBackendEntranceFile);
|
||||
|
||||
if ($newBackendEntrance != 'admin') {
|
||||
$backendEntranceStub = @file_get_contents(Filesystem::fsFit(root_path() . $this->filePath['backendEntranceStub']));
|
||||
if (!$backendEntranceStub) $this->error(__('Configuration write failed: %s', [$this->filePath['backendEntranceStub']]));
|
||||
|
||||
$result = @file_put_contents($newBackendEntranceFile, $backendEntranceStub);
|
||||
if (!$result) $this->error(__('Configuration write failed: %s', [$newBackendEntranceFile]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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('edit');
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->saveAll($configValue);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('The current page configuration item was updated successfully'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件测试
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function sendTestMail(): void
|
||||
{
|
||||
$data = $this->request->post();
|
||||
$mail = new Email();
|
||||
try {
|
||||
$mail->Host = $data['smtp_server'];
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $data['smtp_user'];
|
||||
$mail->Password = $data['smtp_pass'];
|
||||
$mail->SMTPSecure = $data['smtp_verification'] == 'SSL' ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS;
|
||||
$mail->Port = $data['smtp_port'];
|
||||
|
||||
$mail->setFrom($data['smtp_sender_mail'], $data['smtp_user']);
|
||||
|
||||
$mail->isSMTP();
|
||||
$mail->addAddress($data['testMail']);
|
||||
$mail->isHTML();
|
||||
$mail->setSubject(__('This is a test email') . '-' . get_sys_config('site_name'));
|
||||
$mail->Body = __('Congratulations, receiving this email means that your email service has been configured correctly');
|
||||
$mail->send();
|
||||
} catch (PHPMailerException) {
|
||||
$this->error($mail->ErrorInfo);
|
||||
}
|
||||
$this->success(__('Test mail sent successfully~'));
|
||||
}
|
||||
}
|
||||
150
app/admin/controller/security/DataRecycle.php
Normal file
150
app/admin/controller/security/DataRecycle.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\security;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\DataRecycle as DataRecycleModel;
|
||||
|
||||
class DataRecycle extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var DataRecycleModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
// 排除字段
|
||||
protected string|array $preExcludeFields = ['update_time', 'create_time'];
|
||||
|
||||
protected string|array $quickSearchField = 'name';
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new DataRecycleModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @throws Throwable
|
||||
*/
|
||||
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);
|
||||
$data['controller_as'] = str_ireplace('.php', '', $data['controller'] ?? '');
|
||||
$data['controller_as'] = strtolower(str_ireplace(['\\', '.'], '/', $data['controller_as']));
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$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'));
|
||||
}
|
||||
}
|
||||
|
||||
// 放在add方法内,就不需要额外添加权限节点了
|
||||
$this->success('', [
|
||||
'controllers' => $this->getControllerList(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$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);
|
||||
$data['controller_as'] = str_ireplace('.php', '', $data['controller'] ?? '');
|
||||
$data['controller_as'] = strtolower(str_ireplace(['\\', '.'], '/', $data['controller_as']));
|
||||
|
||||
$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('edit');
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('Update successful'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getControllerList(): array
|
||||
{
|
||||
$outExcludeController = [
|
||||
'Addon.php',
|
||||
'Ajax.php',
|
||||
'Module.php',
|
||||
'Terminal.php',
|
||||
'Dashboard.php',
|
||||
'Index.php',
|
||||
'routine/AdminInfo.php',
|
||||
'user/MoneyLog.php',
|
||||
'user/ScoreLog.php',
|
||||
];
|
||||
$outControllers = [];
|
||||
$controllers = get_controller_list();
|
||||
foreach ($controllers as $key => $controller) {
|
||||
if (!in_array($controller, $outExcludeController)) {
|
||||
$outControllers[$key] = $controller;
|
||||
}
|
||||
}
|
||||
return $outControllers;
|
||||
}
|
||||
}
|
||||
106
app/admin/controller/security/DataRecycleLog.php
Normal file
106
app/admin/controller/security/DataRecycleLog.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\security;
|
||||
|
||||
use Throwable;
|
||||
use ba\TableManager;
|
||||
use think\facade\Db;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\DataRecycleLog as DataRecycleLogModel;
|
||||
|
||||
class DataRecycleLog extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var DataRecycleLogModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
// 排除字段
|
||||
protected string|array $preExcludeFields = [];
|
||||
|
||||
protected string|array $quickSearchField = 'recycle.name';
|
||||
|
||||
protected array $withJoinTable = ['recycle', 'admin'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new DataRecycleLogModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function restore(): void
|
||||
{
|
||||
$ids = $this->request->param('ids/a', []);
|
||||
$data = $this->model->where('id', 'in', $ids)->select();
|
||||
if (!$data) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $row) {
|
||||
$recycleData = json_decode($row['data'], true);
|
||||
if (is_array($recycleData) && Db::connect(TableManager::getConnection($row->connection))->name($row->data_table)->insert($recycleData)) {
|
||||
$row->delete();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
if ($count) {
|
||||
$this->success();
|
||||
} else {
|
||||
$this->error(__('No rows were restore'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function info(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$row = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->where('data_recycle_log.id', $id)
|
||||
->find();
|
||||
if (!$row) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
$data = $this->jsonToArray($row['data']);
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $key => $item) {
|
||||
$data[$key] = $this->jsonToArray($item);
|
||||
}
|
||||
}
|
||||
$row['data'] = $data;
|
||||
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
protected function jsonToArray($value = '')
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
$data = json_decode($value, true);
|
||||
if (($data && is_object($data)) || (is_array($data) && !empty($data))) {
|
||||
return $data;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
204
app/admin/controller/security/SensitiveData.php
Normal file
204
app/admin/controller/security/SensitiveData.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\security;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\SensitiveData as SensitiveDataModel;
|
||||
|
||||
class SensitiveData extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var SensitiveDataModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
// 排除字段
|
||||
protected string|array $preExcludeFields = ['update_time', 'create_time'];
|
||||
|
||||
protected string|array $quickSearchField = 'controller';
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new SensitiveDataModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
foreach ($res->items() as $item) {
|
||||
if ($item->data_fields) {
|
||||
$fields = [];
|
||||
foreach ($item->data_fields as $key => $field) {
|
||||
$fields[] = $field ?: $key;
|
||||
}
|
||||
$item->data_fields = $fields;
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加重写
|
||||
* @throws Throwable
|
||||
*/
|
||||
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);
|
||||
$data['controller_as'] = str_ireplace('.php', '', $data['controller'] ?? '');
|
||||
$data['controller_as'] = strtolower(str_ireplace(['\\', '.'], '/', $data['controller_as']));
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($data['fields'])) {
|
||||
$data['data_fields'] = [];
|
||||
foreach ($data['fields'] as $field) {
|
||||
$data['data_fields'][$field['name']] = $field['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->model->save($data);
|
||||
$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'));
|
||||
}
|
||||
}
|
||||
|
||||
// 放在add方法内,就不需要额外添加权限节点了
|
||||
$this->success('', [
|
||||
'controllers' => $this->getControllerList(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑重写
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$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);
|
||||
$data['controller_as'] = str_ireplace('.php', '', $data['controller'] ?? '');
|
||||
$data['controller_as'] = strtolower(str_ireplace(['\\', '.'], '/', $data['controller_as']));
|
||||
|
||||
$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('edit');
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($data['fields'])) {
|
||||
$data['data_fields'] = [];
|
||||
foreach ($data['fields'] as $field) {
|
||||
$data['data_fields'][$field['name']] = $field['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('Update successful'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'row' => $row,
|
||||
'controllers' => $this->getControllerList(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getControllerList(): array
|
||||
{
|
||||
$outExcludeController = [
|
||||
'Addon.php',
|
||||
'Ajax.php',
|
||||
'Dashboard.php',
|
||||
'Index.php',
|
||||
'Module.php',
|
||||
'Terminal.php',
|
||||
'auth/AdminLog.php',
|
||||
'routine/AdminInfo.php',
|
||||
'routine/Config.php',
|
||||
'user/MoneyLog.php',
|
||||
'user/ScoreLog.php',
|
||||
];
|
||||
$outControllers = [];
|
||||
$controllers = get_controller_list();
|
||||
foreach ($controllers as $key => $controller) {
|
||||
if (!in_array($controller, $outExcludeController)) {
|
||||
$outControllers[$key] = $controller;
|
||||
}
|
||||
}
|
||||
return $outControllers;
|
||||
}
|
||||
}
|
||||
117
app/admin/controller/security/SensitiveDataLog.php
Normal file
117
app/admin/controller/security/SensitiveDataLog.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\security;
|
||||
|
||||
use Throwable;
|
||||
use ba\TableManager;
|
||||
use think\facade\Db;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\SensitiveDataLog as SensitiveDataLogModel;
|
||||
|
||||
class SensitiveDataLog extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var SensitiveDataLogModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
// 排除字段
|
||||
protected string|array $preExcludeFields = [];
|
||||
|
||||
protected string|array $quickSearchField = 'sensitive.name';
|
||||
|
||||
protected array $withJoinTable = ['sensitive', 'admin'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new SensitiveDataLogModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
foreach ($res->items() as $item) {
|
||||
$item->id_value = $item['primary_key'] . '=' . $item->id_value;
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function info(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$row = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->where('sensitive_data_log.id', $id)
|
||||
->find();
|
||||
if (!$row) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function rollback(): void
|
||||
{
|
||||
$ids = $this->request->param('ids/a', []);
|
||||
$data = $this->model->where('id', 'in', $ids)->select();
|
||||
if (!$data) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
foreach ($data as $row) {
|
||||
if (Db::connect(TableManager::getConnection($row->connection))->name($row->data_table)->where($row->primary_key, $row->id_value)->update([
|
||||
$row->data_field => $row->before
|
||||
])) {
|
||||
$row->delete();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
if ($count) {
|
||||
$this->success();
|
||||
} else {
|
||||
$this->error(__('No rows were rollback'));
|
||||
}
|
||||
}
|
||||
}
|
||||
163
app/admin/controller/user/Group.php
Normal file
163
app/admin/controller/user/Group.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use Throwable;
|
||||
use app\admin\model\UserRule;
|
||||
use app\admin\model\UserGroup;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class Group extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var UserGroup
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
// 排除字段
|
||||
protected string|array $preExcludeFields = ['update_time', 'create_time'];
|
||||
|
||||
protected string|array $quickSearchField = 'name';
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new UserGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @throws Throwable
|
||||
*/
|
||||
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);
|
||||
$data = $this->handleRules($data);
|
||||
|
||||
$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();
|
||||
$validate->scene('add')->check($data);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$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);
|
||||
$data = $this->handleRules($data);
|
||||
|
||||
$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();
|
||||
$validate->scene('edit')->check($data);
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('Update successful'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
}
|
||||
|
||||
// 读取所有pid,全部从节点数组移除,父级选择状态由子级决定
|
||||
$pidArr = UserRule::field('pid')
|
||||
->distinct(true)
|
||||
->where('id', 'in', $row->rules)
|
||||
->select()
|
||||
->toArray();
|
||||
$rules = $row->rules ? explode(',', $row->rules) : [];
|
||||
foreach ($pidArr as $item) {
|
||||
$ruKey = array_search($item['pid'], $rules);
|
||||
if ($ruKey !== false) {
|
||||
unset($rules[$ruKey]);
|
||||
}
|
||||
}
|
||||
$row->rules = array_values($rules);
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限规则入库前处理
|
||||
* @param array $data 接受到的数据
|
||||
* @return array
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function handleRules(array &$data): array
|
||||
{
|
||||
if (is_array($data['rules']) && $data['rules']) {
|
||||
$rules = UserRule::select();
|
||||
$super = true;
|
||||
foreach ($rules as $rule) {
|
||||
if (!in_array($rule['id'], $data['rules'])) {
|
||||
$super = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($super) {
|
||||
$data['rules'] = '*';
|
||||
} else {
|
||||
$data['rules'] = implode(',', $data['rules']);
|
||||
}
|
||||
} else {
|
||||
unset($data['rules']);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
50
app/admin/controller/user/MoneyLog.php
Normal file
50
app/admin/controller/user/MoneyLog.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use Throwable;
|
||||
use app\admin\model\User;
|
||||
use app\admin\model\UserMoneyLog;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class MoneyLog extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var UserMoneyLog
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected array $withJoinTable = ['user'];
|
||||
|
||||
// 排除字段
|
||||
protected string|array $preExcludeFields = ['create_time'];
|
||||
|
||||
protected string|array $quickSearchField = ['user.username', 'user.nickname'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new UserMoneyLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @param int $userId
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function add(int $userId = 0): void
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
parent::add();
|
||||
}
|
||||
|
||||
$user = User::where('id', $userId)->find();
|
||||
if (!$user) {
|
||||
$this->error(__("The user can't find it"));
|
||||
}
|
||||
$this->success('', [
|
||||
'user' => $user
|
||||
]);
|
||||
}
|
||||
}
|
||||
260
app/admin/controller/user/Rule.php
Normal file
260
app/admin/controller/user/Rule.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use ba\Tree;
|
||||
use Throwable;
|
||||
use app\admin\model\UserRule;
|
||||
use app\admin\model\UserGroup;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class Rule extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var UserRule
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
/**
|
||||
* @var Tree
|
||||
*/
|
||||
protected Tree $tree;
|
||||
|
||||
protected string|array $preExcludeFields = ['create_time', 'update_time'];
|
||||
|
||||
protected string|array $defaultSortField = ['weigh' => 'desc'];
|
||||
|
||||
protected string|array $quickSearchField = 'title';
|
||||
|
||||
/**
|
||||
* 远程select初始化传值
|
||||
* @var array
|
||||
*/
|
||||
protected array $initValue;
|
||||
|
||||
/**
|
||||
* 是否组装Tree
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $assembleTree;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
* @var string
|
||||
*/
|
||||
protected string $keyword;
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
// 防止 URL 中的特殊符号被默认的 filter 函数转义
|
||||
$this->request->filter('clean_xss');
|
||||
|
||||
$this->model = new UserRule();
|
||||
$this->tree = Tree::instance();
|
||||
$isTree = $this->request->param('isTree', true);
|
||||
$this->initValue = $this->request->get("initValue/a", []);
|
||||
$this->initValue = array_filter($this->initValue);
|
||||
$this->keyword = $this->request->request('quickSearch', '');
|
||||
$this->assembleTree = $isTree && !$this->initValue; // 有初始化值时不组装树状(初始化出来的值更好看)
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $this->getRules(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
|
||||
if (!empty($data['pid'])) {
|
||||
$groups = UserGroup::where('rules', '<>', '*')->select();
|
||||
foreach ($groups as $group) {
|
||||
$rules = explode(',', $group->rules);
|
||||
if (in_array($data['pid'], $rules) && !in_array($this->model->id, $rules)) {
|
||||
$rules[] = $this->model->id;
|
||||
$group->rules = implode(',', $rules);
|
||||
$group->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$id = $this->request->param($this->model->getPk());
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$dataLimitAdminIds = $this->getDataLimitAdminIds();
|
||||
if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->post();
|
||||
if (!$data) {
|
||||
$this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$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('edit');
|
||||
$validate->check($data);
|
||||
}
|
||||
}
|
||||
if (isset($data['pid']) && $data['pid'] > 0) {
|
||||
// 满足意图并消除副作用
|
||||
$parent = $this->model->where('id', $data['pid'])->find();
|
||||
if ($parent['pid'] == $row['id']) {
|
||||
$parent->pid = 0;
|
||||
$parent->save();
|
||||
}
|
||||
}
|
||||
$result = $row->save($data);
|
||||
$this->model->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->model->rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->success(__('Update successful'));
|
||||
} else {
|
||||
$this->error(__('No rows updated'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function del(): void
|
||||
{
|
||||
$ids = $this->request->param('ids/a', []);
|
||||
|
||||
// 子级元素检查
|
||||
$subData = $this->model->where('pid', 'in', $ids)->column('pid', 'id');
|
||||
foreach ($subData as $key => $subDatum) {
|
||||
if (!in_array($key, $ids)) {
|
||||
$this->error(__('Please delete the child element first, or use batch deletion'));
|
||||
}
|
||||
}
|
||||
|
||||
parent::del();
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程下拉
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function select(): void
|
||||
{
|
||||
$data = $this->getRules([['status', '=', 1]]);
|
||||
|
||||
if ($this->assembleTree) {
|
||||
$data = $this->tree->assembleTree($this->tree->getTreeArray($data, 'title'));
|
||||
}
|
||||
$this->success('', [
|
||||
'options' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单规则
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function getRules(array $where = []): array
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$initKey = $this->request->get("initKey/s", $pk);
|
||||
|
||||
if ($this->keyword) {
|
||||
$keyword = explode(' ', $this->keyword);
|
||||
foreach ($keyword as $item) {
|
||||
$where[] = [$this->quickSearchField, 'like', '%' . $item . '%'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->initValue) {
|
||||
$where[] = [$initKey, 'in', $this->initValue];
|
||||
}
|
||||
|
||||
$data = $this->model
|
||||
->where($where)
|
||||
->order($this->queryOrderBuilder())
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $this->assembleTree ? $this->tree->assembleChild($data) : $data;
|
||||
}
|
||||
|
||||
}
|
||||
50
app/admin/controller/user/ScoreLog.php
Normal file
50
app/admin/controller/user/ScoreLog.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use Throwable;
|
||||
use app\admin\model\User;
|
||||
use app\admin\model\UserScoreLog;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
class ScoreLog extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var UserScoreLog
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected array $withJoinTable = ['user'];
|
||||
|
||||
// 排除字段
|
||||
protected string|array $preExcludeFields = ['create_time'];
|
||||
|
||||
protected string|array $quickSearchField = ['user.username', 'user.nickname'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new UserScoreLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @param int $userId
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function add(int $userId = 0): void
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
parent::add();
|
||||
}
|
||||
|
||||
$user = User::where('id', $userId)->find();
|
||||
if (!$user) {
|
||||
$this->error(__("The user can't find it"));
|
||||
}
|
||||
$this->success('', [
|
||||
'user' => $user
|
||||
]);
|
||||
}
|
||||
}
|
||||
156
app/admin/controller/user/User.php
Normal file
156
app/admin/controller/user/User.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\model\User as UserModel;
|
||||
|
||||
class User extends Backend
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
* @phpstan-var UserModel
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
protected array $withJoinTable = ['userGroup'];
|
||||
|
||||
// 排除字段
|
||||
protected string|array $preExcludeFields = ['last_login_time', 'login_failure', 'password', 'salt'];
|
||||
|
||||
protected string|array $quickSearchField = ['username', 'nickname', 'id'];
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new UserModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
if ($this->request->param('select')) {
|
||||
$this->select();
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withoutField('password,salt')
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
$this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->post();
|
||||
if (!$data) {
|
||||
$this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$passwd = $data['password']; // 密码将被排除不直接入库
|
||||
$data = $this->excludeFields($data);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
$result = $this->model->save($data);
|
||||
$this->model->commit();
|
||||
|
||||
if (!empty($passwd)) {
|
||||
$this->model->resetPassword($this->model->id, $passwd);
|
||||
}
|
||||
} 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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$id = $this->request->param($pk);
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
$this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
$password = $this->request->post('password', '');
|
||||
if ($password) {
|
||||
$this->model->resetPassword($id, $password);
|
||||
}
|
||||
parent::edit();
|
||||
}
|
||||
|
||||
unset($row->salt);
|
||||
$row->password = '';
|
||||
$this->success('', [
|
||||
'row' => $row
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写select
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function select(): void
|
||||
{
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$res = $this->model
|
||||
->withoutField('password,salt')
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
foreach ($res as $re) {
|
||||
$re->nickname_text = $re->username . '(ID:' . $re->id . ')';
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user