1
This commit is contained in:
187
app/common/command/Curd.php
Normal file
187
app/common/command/Curd.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\admin\service\console\CliEcho;
|
||||
use app\admin\service\curd\BuildCurd;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\Exception;
|
||||
|
||||
class Curd extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('curd')
|
||||
->addOption('table', 't', Option::VALUE_REQUIRED, '主表名', null)
|
||||
->addOption('controllerFilename', 'c', Option::VALUE_REQUIRED, '控制器文件名', null)
|
||||
->addOption('modelFilename', 'm', Option::VALUE_REQUIRED, '主表模型文件名', null)
|
||||
#
|
||||
->addOption('checkboxFieldSuffix', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '复选框字段后缀', null)
|
||||
->addOption('radioFieldSuffix', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '单选框字段后缀', null)
|
||||
->addOption('imageFieldSuffix', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '单图片字段后缀', null)
|
||||
->addOption('imagesFieldSuffix', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '多图片字段后缀', null)
|
||||
->addOption('fileFieldSuffix', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '单文件字段后缀', null)
|
||||
->addOption('filesFieldSuffix', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '多文件字段后缀', null)
|
||||
->addOption('dateFieldSuffix', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '时间字段后缀', null)
|
||||
->addOption('switchFields', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '开关的字段', null)
|
||||
->addOption('selectFields', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '下拉的字段', null)
|
||||
->addOption('editorFields', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '富文本的字段', null)
|
||||
->addOption('sortFields', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '排序的字段', null)
|
||||
->addOption('ignoreFields', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '忽略的字段', null)
|
||||
#
|
||||
->addOption('relationTable', 'r', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '关联表名', null)
|
||||
->addOption('foreignKey', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '关联外键', null)
|
||||
->addOption('primaryKey', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '关联主键', null)
|
||||
->addOption('relationModelFilename', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '关联模型文件名', null)
|
||||
->addOption('relationOnlyFields', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '关联模型中只显示的字段', null)
|
||||
->addOption('relationBindSelect', null, Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, '关联模型中的字段用于主表外键的表单下拉选择', null)
|
||||
#
|
||||
->addOption('force', 'f', Option::VALUE_REQUIRED, '强制覆盖模式', 0)
|
||||
->addOption('delete', 'd', Option::VALUE_REQUIRED, '删除模式', 0)
|
||||
->setDescription('一键curd命令服务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
|
||||
$table = $input->getOption('table');
|
||||
$controllerFilename = $input->getOption('controllerFilename');
|
||||
$modelFilename = $input->getOption('modelFilename');
|
||||
|
||||
$checkboxFieldSuffix = $input->getOption('checkboxFieldSuffix');
|
||||
$radioFieldSuffix = $input->getOption('radioFieldSuffix');
|
||||
$imageFieldSuffix = $input->getOption('imageFieldSuffix');
|
||||
$imagesFieldSuffix = $input->getOption('imagesFieldSuffix');
|
||||
$fileFieldSuffix = $input->getOption('fileFieldSuffix');
|
||||
$filesFieldSuffix = $input->getOption('filesFieldSuffix');
|
||||
$dateFieldSuffix = $input->getOption('dateFieldSuffix');
|
||||
$switchFields = $input->getOption('switchFields');
|
||||
$selectFields = $input->getOption('selectFields');
|
||||
$sortFields = $input->getOption('sortFields');
|
||||
$ignoreFields = $input->getOption('ignoreFields');
|
||||
|
||||
$relationTable = $input->getOption('relationTable');
|
||||
$foreignKey = $input->getOption('foreignKey');
|
||||
$primaryKey = $input->getOption('primaryKey');
|
||||
$relationModelFilename = $input->getOption('relationModelFilename');
|
||||
$relationOnlyFields = $input->getOption('relationOnlyFields');
|
||||
$relationBindSelect = $input->getOption('relationBindSelect');
|
||||
|
||||
$force = $input->getOption('force');
|
||||
$delete = $input->getOption('delete');
|
||||
|
||||
$relations = [];
|
||||
foreach ($relationTable as $key => $val) {
|
||||
$relations[] = [
|
||||
'table' => $val,
|
||||
'foreignKey' => $foreignKey[$key] ?? null,
|
||||
'primaryKey' => $primaryKey[$key] ?? null,
|
||||
'modelFilename' => $relationModelFilename[$key] ?? null,
|
||||
'onlyField' => isset($relationOnlyFields[$key]) ? explode(",", $relationOnlyFields[$key]) : [],
|
||||
'relationBindSelect' => $relationBindSelect[$key] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($table)) {
|
||||
if (PHP_SAPI == 'cli')
|
||||
CliEcho::error('请设置主表');
|
||||
else
|
||||
$output->writeln('请设置主表');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$build = (new BuildCurd())
|
||||
->setTable($table)
|
||||
->setForce($force);
|
||||
|
||||
!empty($controllerFilename) && $build = $build->setControllerFilename($controllerFilename);
|
||||
!empty($modelFilename) && $build = $build->setModelFilename($modelFilename);
|
||||
|
||||
!empty($checkboxFieldSuffix) && $build = $build->setCheckboxFieldSuffix($checkboxFieldSuffix);
|
||||
!empty($radioFieldSuffix) && $build = $build->setRadioFieldSuffix($radioFieldSuffix);
|
||||
!empty($imageFieldSuffix) && $build = $build->setImageFieldSuffix($imageFieldSuffix);
|
||||
!empty($imagesFieldSuffix) && $build = $build->setImagesFieldSuffix($imagesFieldSuffix);
|
||||
!empty($fileFieldSuffix) && $build = $build->setFileFieldSuffix($fileFieldSuffix);
|
||||
!empty($filesFieldSuffix) && $build = $build->setFilesFieldSuffix($filesFieldSuffix);
|
||||
!empty($dateFieldSuffix) && $build = $build->setDateFieldSuffix($dateFieldSuffix);
|
||||
!empty($switchFields) && $build = $build->setSwitchFields($switchFields);
|
||||
!empty($selectFields) && $build = $build->setselectFields($selectFields);
|
||||
!empty($sortFields) && $build = $build->setSortFields($sortFields);
|
||||
!empty($ignoreFields) && $build = $build->setIgnoreFields($ignoreFields);
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
$build = $build->setRelation($relation['table'], $relation['foreignKey'], $relation['primaryKey'], $relation['modelFilename'], $relation['onlyField'], $relation['relationBindSelect']);
|
||||
}
|
||||
|
||||
$build = $build->render();
|
||||
$fileList = $build->getFileList();
|
||||
|
||||
if (!$delete) {
|
||||
$result = $build->create();
|
||||
if ($force) {
|
||||
if (PHP_SAPI == 'cli') {
|
||||
$output->info(">>>>>>>>>>>>>>>");
|
||||
foreach ($fileList as $key => $val) {
|
||||
$output->info($key);
|
||||
}
|
||||
$output->info(">>>>>>>>>>>>>>>");
|
||||
$output->info("确定强制生成上方所有文件? 如果文件存在会直接覆盖。 请输入 'yes' 按回车键继续操作: ");
|
||||
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
|
||||
if (trim($line) != 'yes') {
|
||||
throw new Exception("取消文件CURD生成操作");
|
||||
}
|
||||
CliEcho::success('自动生成CURD成功');
|
||||
}else {
|
||||
$output->writeln('自动生成CURD成功');
|
||||
}
|
||||
}
|
||||
}else {
|
||||
if (PHP_SAPI == 'cli') {
|
||||
$output->info(">>>>>>>>>>>>>>>");
|
||||
foreach ($fileList as $key => $val) {
|
||||
$output->info($key);
|
||||
}
|
||||
$output->info(">>>>>>>>>>>>>>>");
|
||||
$output->info("确定删除上方所有文件? 请输入 'yes' 按回车键继续操作: ");
|
||||
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
|
||||
if (trim($line) != 'yes') {
|
||||
throw new Exception("取消删除文件操作");
|
||||
}
|
||||
$result = $build->delete();
|
||||
CliEcho::success('>>>>>>>>>>>>>>>');
|
||||
CliEcho::success('删除自动生成CURD文件成功');
|
||||
CliEcho::success('>>>>>>>>>>>>>>>');
|
||||
foreach ($result as $vo) {
|
||||
CliEcho::success($vo);
|
||||
}
|
||||
}else {
|
||||
$result = $build->delete();
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
$output->writeln('删除自动生成CURD文件成功');
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
foreach ($result as $vo) {
|
||||
$output->writeln($vo);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PHP_SAPI == 'cli') {
|
||||
$output->info(">>>>>>>>>>>>>>>");
|
||||
$output->info('执行成功');
|
||||
}else {
|
||||
$output->writeln('执行成功');
|
||||
}
|
||||
}catch (\Exception $e) {
|
||||
if (PHP_SAPI == 'cli')
|
||||
CliEcho::error($e->getMessage());
|
||||
else
|
||||
$output->writeln($e->getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
64
app/common/command/Node.php
Normal file
64
app/common/command/Node.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\admin\model\SystemNode;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use app\admin\service\NodeService;
|
||||
|
||||
class Node extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('node')
|
||||
->addOption('force', null, Option::VALUE_REQUIRED, '是否强制刷新', 0)
|
||||
->setDescription('系统节点刷新服务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$force = $input->getOption('force');
|
||||
$output->writeln("========正在刷新节点服务:=====" . date('Y-m-d H:i:s'));
|
||||
$check = $this->refresh($force);
|
||||
$check !== true && $output->writeln("节点刷新失败:" . $check);
|
||||
$output->writeln("刷新完成:" . date('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
protected function refresh($force)
|
||||
{
|
||||
$nodeList = (new NodeService())->getNodeList();
|
||||
if (empty($nodeList)) {
|
||||
return true;
|
||||
}
|
||||
$model = new SystemNode();
|
||||
try {
|
||||
if ($force == 1) {
|
||||
$updateNodeList = $model->whereIn('node', array_column($nodeList, 'node'))->select();
|
||||
$formatNodeList = array_format_key($nodeList, 'node');
|
||||
foreach ($updateNodeList as $vo) {
|
||||
isset($formatNodeList[$vo['node']]) && $model->where('id', $vo['id'])->update([
|
||||
'title' => $formatNodeList[$vo['node']]['title'],
|
||||
'is_auth' => $formatNodeList[$vo['node']]['is_auth'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
$existNodeList = $model->field('node,title,type,is_auth')->select();
|
||||
foreach ($nodeList as $key => $vo) {
|
||||
foreach ($existNodeList as $v) {
|
||||
if ($vo['node'] == $v->node) {
|
||||
unset($nodeList[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$model->insertAll($nodeList);
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
18
app/common/constants/AdminConstant.php
Normal file
18
app/common/constants/AdminConstant.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\constants;
|
||||
|
||||
/**
|
||||
* 管理员常量
|
||||
* Class AdminConstant
|
||||
* @package app\common\constants
|
||||
*/
|
||||
class AdminConstant
|
||||
{
|
||||
|
||||
/**
|
||||
* 超级管理员,不受权限控制
|
||||
*/
|
||||
const SUPER_ADMIN_ID = 1;
|
||||
|
||||
}
|
||||
23
app/common/constants/MenuConstant.php
Normal file
23
app/common/constants/MenuConstant.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\constants;
|
||||
|
||||
/**
|
||||
* 菜单常量
|
||||
* Class MenuConstant
|
||||
* @package app\common\constants
|
||||
*/
|
||||
class MenuConstant
|
||||
{
|
||||
|
||||
/**
|
||||
* 首页的PID
|
||||
*/
|
||||
const HOME_PID = 99999999;
|
||||
|
||||
/**
|
||||
* 模块名前缀
|
||||
*/
|
||||
const MODULE_PREFIX = 'easyadmin_';
|
||||
|
||||
}
|
||||
277
app/common/controller/AdminController.php
Normal file
277
app/common/controller/AdminController.php
Normal file
@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\admin\service\ConfigService;
|
||||
use app\admin\traits\Curd;
|
||||
use app\BaseController;
|
||||
use app\common\constants\AdminConstant;
|
||||
use app\common\traits\JumpTrait;
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
use think\helper\Str;
|
||||
use think\response\Json;
|
||||
|
||||
class AdminController extends BaseController
|
||||
{
|
||||
use Curd;
|
||||
use JumpTrait;
|
||||
|
||||
/**
|
||||
* 当前模型
|
||||
* @Model
|
||||
* @var mixed
|
||||
*/
|
||||
protected static mixed $model;
|
||||
|
||||
/**
|
||||
* 字段排序
|
||||
* @var array
|
||||
*/
|
||||
protected array $sort = [
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
/**
|
||||
* 允许修改的字段
|
||||
* @var array
|
||||
*/
|
||||
protected array $allowModifyFields = [
|
||||
'status',
|
||||
'sort',
|
||||
'remark',
|
||||
'is_delete',
|
||||
'is_auth',
|
||||
'title',
|
||||
];
|
||||
|
||||
/**
|
||||
* 过滤节点更新
|
||||
* @var array
|
||||
*/
|
||||
protected array $ignoreNode = [];
|
||||
|
||||
/**
|
||||
* 不导出的字段信息
|
||||
* @var array
|
||||
*/
|
||||
protected array $noExportFields = ['delete_time', 'update_time'];
|
||||
|
||||
/**
|
||||
* 下拉选择条件
|
||||
* @var array
|
||||
*/
|
||||
protected array $selectWhere = [];
|
||||
|
||||
/**
|
||||
* 是否关联查询
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $relationSearch = false;
|
||||
|
||||
/**
|
||||
* 模板布局, false取消
|
||||
* @var string|bool
|
||||
*/
|
||||
protected string|bool $layout = 'layout/default';
|
||||
|
||||
/**
|
||||
* 是否为演示环境
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $isDemo = false;
|
||||
|
||||
/**
|
||||
* @var int|string
|
||||
*/
|
||||
protected int|string $adminUid;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化方法
|
||||
*/
|
||||
protected function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$this->adminUid = request()->adminUserInfo['id'] ?? 0;
|
||||
$this->isDemo = env('EASYADMIN.IS_DEMO', false);
|
||||
$this->setOrder();
|
||||
$this->viewInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化排序
|
||||
* @return $this
|
||||
*/
|
||||
public function setOrder(): static
|
||||
{
|
||||
$tableOrder = $this->request->param('tableOrder/s', '');
|
||||
if (!empty($tableOrder)) {
|
||||
[$orderField, $orderType] = explode(' ', $tableOrder);
|
||||
$this->sort = [$orderField => $orderType];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板变量赋值
|
||||
* @param array|string $name 模板变量
|
||||
* @param mixed|null $value 变量值
|
||||
*/
|
||||
public function assign(array|string $name, mixed $value = null): void
|
||||
{
|
||||
View::assign($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析和获取模板内容 用于输出
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param bool $layout 是否需要自动布局
|
||||
* @return string
|
||||
*/
|
||||
public function fetch(string $template = '', array $vars = [], bool $layout = true): string
|
||||
{
|
||||
if ($layout) View::instance()->engine()->layout('/layout/default');
|
||||
View::assign($vars);
|
||||
return View::fetch($template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写验证规则
|
||||
* @param array $data
|
||||
* @param array|string $validate
|
||||
* @param array $message
|
||||
* @param bool $batch
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(array $data, $validate, array $message = [], bool $batch = false): bool
|
||||
{
|
||||
try {
|
||||
parent::validate($data, $validate, $message, $batch);
|
||||
}catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求参数
|
||||
* @param array $excludeFields 忽略构建搜索的字段
|
||||
* @return array
|
||||
*/
|
||||
protected function buildTableParams(array $excludeFields = []): array
|
||||
{
|
||||
$get = $this->request->get();
|
||||
$page = !empty($get['page']) ? $get['page'] : 1;
|
||||
$limit = !empty($get['limit']) ? $get['limit'] : 15;
|
||||
$filters = !empty($get['filter']) ? htmlspecialchars_decode($get['filter']) : '{}';
|
||||
$ops = !empty($get['op']) ? htmlspecialchars_decode($get['op']) : '{}';
|
||||
// json转数组
|
||||
$filters = json_decode($filters, true);
|
||||
$ops = json_decode($ops, true);
|
||||
$where = [];
|
||||
$excludes = [];
|
||||
// 判断是否关联查询
|
||||
$tableName = Str::snake(lcfirst((new self::$model)->getName()));
|
||||
foreach ($filters as $key => $val) {
|
||||
if (in_array($key, $excludeFields)) {
|
||||
$excludes[$key] = $val;
|
||||
continue;
|
||||
}
|
||||
$op = !empty($ops[$key]) ? $ops[$key] : '%*%';
|
||||
if ($this->relationSearch && count(explode('.', $key)) == 1) {
|
||||
$key = "{$tableName}.{$key}";
|
||||
}
|
||||
|
||||
switch (strtolower($op)) {
|
||||
case '=':
|
||||
$where[] = [$key, '=', $val];
|
||||
break;
|
||||
case '%*%':
|
||||
$where[] = [$key, 'LIKE', "%{$val}%"];
|
||||
break;
|
||||
case '*%':
|
||||
$where[] = [$key, 'LIKE', "{$val}%"];
|
||||
break;
|
||||
case '%*':
|
||||
$where[] = [$key, 'LIKE', "%{$val}"];
|
||||
break;
|
||||
case 'in':
|
||||
$where[] = [$key, 'IN', $val];
|
||||
break;
|
||||
case 'find_in_set':
|
||||
$where[] = ['', 'exp', Db::raw("FIND_IN_SET(:param,$key)", ['param' => $val])];
|
||||
break;
|
||||
case 'range':
|
||||
[$beginTime, $endTime] = explode(' - ', $val);
|
||||
$where[] = [$key, '>=', strtotime($beginTime)];
|
||||
$where[] = [$key, '<=', strtotime($endTime)];
|
||||
break;
|
||||
case 'datetime':
|
||||
[$beginTime, $endTime] = explode(' - ', $val);
|
||||
$where[] = [$key, '>=', $beginTime];
|
||||
$where[] = [$key, '<=', $endTime];
|
||||
break;
|
||||
default:
|
||||
$where[] = [$key, $op, "%{$val}"];
|
||||
}
|
||||
}
|
||||
return [(int)$page, (int)$limit, $where, $excludes];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉选择列表
|
||||
* @return Json
|
||||
*/
|
||||
public function selectList(): Json
|
||||
{
|
||||
$fields = input('selectFields');
|
||||
$data = self::$model::where($this->selectWhere)->field($fields)->select()->toArray();
|
||||
$this->success(null, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图参数
|
||||
*/
|
||||
private function viewInit(): void
|
||||
{
|
||||
$request = app()->request;
|
||||
list($thisModule, $thisController, $thisAction) = [app('http')->getName(), app()->request->controller(), $request->action()];
|
||||
list($thisControllerArr, $jsPath) = [explode('.', $thisController), null];
|
||||
foreach ($thisControllerArr as $vo) {
|
||||
empty($jsPath) ? $jsPath = parse_name($vo) : $jsPath .= '/' . parse_name($vo);
|
||||
}
|
||||
$autoloadJs = file_exists(root_path('public') . "static/{$thisModule}/js/{$jsPath}.js");
|
||||
$thisControllerJsPath = "{$thisModule}/js/{$jsPath}.js";
|
||||
$adminModuleName = config('admin.alias_name');
|
||||
$isSuperAdmin = $this->adminUid == AdminConstant::SUPER_ADMIN_ID;
|
||||
$data = [
|
||||
'isDemo' => $this->isDemo,
|
||||
'adminModuleName' => $adminModuleName,
|
||||
'thisController' => parse_name($thisController),
|
||||
'thisAction' => $thisAction,
|
||||
'thisRequest' => parse_name("{$thisModule}/{$thisController}/{$thisAction}"),
|
||||
'thisControllerJsPath' => "{$thisControllerJsPath}",
|
||||
'autoloadJs' => $autoloadJs,
|
||||
'isSuperAdmin' => $isSuperAdmin,
|
||||
'version' => env('APP_DEBUG') ? time() : ConfigService::getVersion(),
|
||||
'adminUploadUrl' => url('ajax/upload', [], false),
|
||||
'adminEditor' => sysConfig('site', 'editor_type') ?: 'wangEditor',
|
||||
'iframeOpenTop' => sysConfig('site', 'iframe_open_top') ?: 0,
|
||||
];
|
||||
View::assign($data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 严格校验接口是否为POST请求
|
||||
*/
|
||||
protected function checkPostRequest(): void
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error("当前请求不合法!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
app/common/entity/BaseEntity.php
Normal file
22
app/common/entity/BaseEntity.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\entity;
|
||||
|
||||
use think\Entity;
|
||||
use think\model\type\DateTime;
|
||||
|
||||
class BaseEntity extends Entity
|
||||
{
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
'create_time' => DateTime::class,
|
||||
'update_time' => DateTime::class,
|
||||
'delete_time' => DateTime::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
29
app/common/model/ArticleCates.php
Normal file
29
app/common/model/ArticleCates.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\model\relation\HasMany;
|
||||
|
||||
class ArticleCates extends TimeModel
|
||||
{
|
||||
protected array $rule = [
|
||||
'title' => 'require|max:50|unique:article_cates',
|
||||
'sort' => 'integer|egt:0',
|
||||
'status' => 'in:0,1'
|
||||
];
|
||||
|
||||
protected array $message = [
|
||||
'title.require' => '分类名称不能为空',
|
||||
'title.max' => '分类名称最多50个字符',
|
||||
'title.unique' => '分类名称已存在',
|
||||
'sort.integer' => '排序必须为整数',
|
||||
'sort.egt' => '排序不能小于0',
|
||||
'status.in' => '状态值错误'
|
||||
];
|
||||
|
||||
public function articles(): HasMany
|
||||
{
|
||||
return $this->hasMany(Articles::class, 'cate_id');
|
||||
}
|
||||
|
||||
}
|
||||
40
app/common/model/Articles.php
Normal file
40
app/common/model/Articles.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
class Articles extends TimeModel
|
||||
{
|
||||
protected array $rule = [
|
||||
'title' => 'require|max:50',
|
||||
'cate_id' => 'require|integer|gt:0',
|
||||
'cover' => 'max:255',
|
||||
'summary' => 'max:255',
|
||||
'author' => 'max:50',
|
||||
'sort' => 'integer|egt:0',
|
||||
'status' => 'in:0,1'
|
||||
];
|
||||
protected array $message = [
|
||||
'title.require' => '文章标题不能为空',
|
||||
'title.max' => '文章标题最多50个字符',
|
||||
'cate_id.require' => '分类ID不能为空',
|
||||
'cate_id.integer' => '分类ID必须为整数',
|
||||
'cate_id.gt' => '分类ID必须大于0',
|
||||
'cover.max' => '封面路径最多255个字符',
|
||||
'summary.max' => '简介最多255个字符',
|
||||
'author.max' => '作者最多50个字符',
|
||||
'sort.integer' => '排序必须为整数',
|
||||
'sort.egt' => '排序不能小于0',
|
||||
'status.in' => '状态值错误'
|
||||
];
|
||||
|
||||
public function cate()
|
||||
{
|
||||
return $this->belongsTo(ArticleCates::class, 'cate_id');
|
||||
}
|
||||
|
||||
|
||||
public function getCateNameAttr($value, $data)
|
||||
{
|
||||
return $this->cate ? $this->cate->title : '';
|
||||
}
|
||||
}
|
||||
42
app/common/model/TimeModel.php
Normal file
42
app/common/model/TimeModel.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 有关时间的模型
|
||||
* Class TimeModel
|
||||
* @package app\common\model
|
||||
*/
|
||||
class TimeModel extends Model
|
||||
{
|
||||
|
||||
public array $statusText = [
|
||||
1 => '启用',
|
||||
-1 => '禁用',
|
||||
0 => '未启用',
|
||||
];
|
||||
/**
|
||||
* 软删除
|
||||
*/
|
||||
use SoftDelete;
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'autoWriteTimestamp' => true,
|
||||
'createTime' => 'create_time',
|
||||
'updateTime' => 'update_time',
|
||||
'deleteTime' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public function getStatusTextAttr($value): string
|
||||
{
|
||||
return $this->statusText[$value] ?? '未知状态';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
232
app/common/service/AuthService.php
Normal file
232
app/common/service/AuthService.php
Normal file
@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\common\constants\AdminConstant;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 权限验证服务
|
||||
* Class AuthService
|
||||
* @package app\common\service
|
||||
*/
|
||||
class AuthService
|
||||
{
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
* @var null
|
||||
*/
|
||||
protected $adminId = null;
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'auth_on' => true, // 权限开关
|
||||
'system_admin' => 'system_admin', // 用户表
|
||||
'system_auth' => 'system_auth', // 权限表
|
||||
'system_node' => 'system_node', // 节点表
|
||||
'system_auth_node' => 'system_auth_node',// 权限-节点表
|
||||
];
|
||||
|
||||
/**
|
||||
* 管理员信息
|
||||
* @var array|\think\Model|null
|
||||
*/
|
||||
protected $adminInfo;
|
||||
|
||||
/**
|
||||
* 所有节点信息
|
||||
* @var array
|
||||
*/
|
||||
protected $nodeList;
|
||||
|
||||
/**
|
||||
* 管理员所有授权节点
|
||||
* @var array
|
||||
*/
|
||||
protected $adminNode;
|
||||
|
||||
/***
|
||||
* 构造方法
|
||||
* AuthService constructor.
|
||||
* @param null $adminId
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function __construct($adminId = null)
|
||||
{
|
||||
$this->adminId = $adminId;
|
||||
$this->adminInfo = $this->getAdminInfo();
|
||||
$this->nodeList = $this->getNodeList();
|
||||
$this->adminNode = $this->getAdminNode();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测检测权限
|
||||
* @param null $node
|
||||
* @return bool
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function checkNode($node = null)
|
||||
{
|
||||
// 判断是否为超级管理员
|
||||
if ($this->adminId == AdminConstant::SUPER_ADMIN_ID) {
|
||||
return true;
|
||||
}
|
||||
// 判断权限验证开关
|
||||
if ($this->config['auth_on'] == false) {
|
||||
return true;
|
||||
}
|
||||
// 验证是否为URL
|
||||
if (filter_var($node, FILTER_VALIDATE_URL)) {
|
||||
return true;
|
||||
}
|
||||
// 判断是否需要获取当前节点
|
||||
if (empty($node)) {
|
||||
$node = $this->getCurrentNode();
|
||||
}else {
|
||||
$node = $this->parseNodeStr($node);
|
||||
}
|
||||
// 判断是否加入节点控制,优先获取缓存信息
|
||||
if (!isset($this->nodeList[$node])) {
|
||||
return false;
|
||||
}
|
||||
$nodeInfo = $this->nodeList[$node];
|
||||
if ($nodeInfo['is_auth'] == 0) {
|
||||
return true;
|
||||
}
|
||||
// 用户验证,优先获取缓存信息
|
||||
if (empty($this->adminInfo) || $this->adminInfo['status'] != 1 || empty($this->adminInfo['auth_ids'])) {
|
||||
return false;
|
||||
}
|
||||
// 判断该节点是否允许访问
|
||||
if (in_array($node, $this->adminNode)) {
|
||||
return true;
|
||||
}
|
||||
if ($this->checkNodeAnnotationAttrAuth($node)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function checkNodeAnnotationAttrAuth(string $node): bool
|
||||
{
|
||||
$bool = false;
|
||||
$controller = request()->controller();
|
||||
try {
|
||||
$controllerExplode = explode('.', $controller);
|
||||
[$_name, $_controller] = $controllerExplode;
|
||||
$nodeExplode = explode('/', $node);
|
||||
$action = end($nodeExplode);
|
||||
$reflectionClass = new \ReflectionClass("app\admin\controller\\{$_name}\\{$_controller}");
|
||||
$attributes = $reflectionClass->getMethod($action)->getAttributes(NodeAnnotation::class);
|
||||
foreach ($attributes as $attribute) {
|
||||
$annotation = $attribute->newInstance();
|
||||
$bool = $annotation->auth === false;
|
||||
}
|
||||
}catch (\Throwable) {
|
||||
}
|
||||
return $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前节点
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrentNode()
|
||||
{
|
||||
$node = $this->parseNodeStr(request()->controller() . '/' . request()->action());
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前管理员所有节点
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getAdminNode()
|
||||
{
|
||||
$nodeList = [];
|
||||
$adminInfo = Db::name($this->config['system_admin'])
|
||||
->where([
|
||||
'id' => $this->adminId,
|
||||
'status' => 1,
|
||||
])->find();
|
||||
if (!empty($adminInfo) && !empty($adminInfo['auth_ids'])) {
|
||||
$buildAuthSql = Db::name($this->config['system_auth'])
|
||||
->distinct(true)
|
||||
->whereIn('id', $adminInfo['auth_ids'])
|
||||
->field('id')
|
||||
->buildSql(true);
|
||||
$buildAuthNodeSql = Db::name($this->config['system_auth_node'])
|
||||
->distinct(true)
|
||||
->where("auth_id IN {$buildAuthSql}")
|
||||
->field('node_id')
|
||||
->buildSql(true);
|
||||
$nodeList = Db::name($this->config['system_node'])
|
||||
->distinct(true)
|
||||
->where("id IN {$buildAuthNodeSql}")
|
||||
->column('node');
|
||||
}
|
||||
return $nodeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有节点信息
|
||||
* @time 2021-01-07
|
||||
* @return array
|
||||
* @author zhongshaofa <shaofa.zhong@happy-seed.com>
|
||||
*/
|
||||
public function getNodeList()
|
||||
{
|
||||
return Db::name($this->config['system_node'])
|
||||
->column('id,node,title,type,is_auth', 'node');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取管理员信息
|
||||
* @time 2021-01-07
|
||||
* @return array|\think\Model|null
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author zhongshaofa <shaofa.zhong@happy-seed.com>
|
||||
*/
|
||||
public function getAdminInfo()
|
||||
{
|
||||
return Db::name($this->config['system_admin'])
|
||||
->where('id', $this->adminId)
|
||||
->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰转下划线规则
|
||||
* @param string $node
|
||||
* @return string
|
||||
*/
|
||||
public function parseNodeStr($node)
|
||||
{
|
||||
$array = explode('/', $node);
|
||||
foreach ($array as $key => $val) {
|
||||
if ($key == 0) {
|
||||
$val = explode('.', $val);
|
||||
foreach ($val as &$vo) {
|
||||
$vo = \think\helper\Str::snake(lcfirst($vo));
|
||||
}
|
||||
$val = implode('.', $val);
|
||||
$array[$key] = $val;
|
||||
}
|
||||
}
|
||||
$node = implode('/', $array);
|
||||
return $node;
|
||||
}
|
||||
|
||||
}
|
||||
99
app/common/service/MenuService.php
Normal file
99
app/common/service/MenuService.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\constants\MenuConstant;
|
||||
use think\facade\Db;
|
||||
|
||||
class MenuService
|
||||
{
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
* @var integer
|
||||
*/
|
||||
protected $adminId;
|
||||
|
||||
public function __construct($adminId)
|
||||
{
|
||||
$this->adminId = $adminId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页信息
|
||||
* @return array|\think\Model|null
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getHomeInfo()
|
||||
{
|
||||
$data = Db::name('system_menu')
|
||||
->field('title,icon,href')
|
||||
->where("delete_time is null")
|
||||
->where('pid', MenuConstant::HOME_PID)
|
||||
->find();
|
||||
!empty($data) && $data['href'] = __url($data['href']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取后台菜单树信息
|
||||
* @return mixed
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getMenuTree()
|
||||
{
|
||||
/** @var AuthService $authService */
|
||||
$authServer = app(AuthService::class, ['adminId' => $this->adminId]);
|
||||
return $this->buildMenuChild(0, $this->getMenuData(), $authServer);
|
||||
}
|
||||
|
||||
private function buildMenuChild($pid, $menuList, AuthService $authServer)
|
||||
{
|
||||
$treeList = [];
|
||||
foreach ($menuList as &$v) {
|
||||
$check = empty($v['href']) || $authServer->checkNode($v['href']);
|
||||
!empty($v['href']) && $v['href'] = __url($v['href']);
|
||||
if ($pid == $v['pid'] && $check) {
|
||||
$node = $v;
|
||||
$child = $this->buildMenuChild($v['id'], $menuList, $authServer);
|
||||
if (!empty($child)) {
|
||||
$node['child'] = $child;
|
||||
}
|
||||
if (!empty($v['href']) || !empty($child)) {
|
||||
$treeList[] = $node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $treeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有菜单数据
|
||||
* @return \think\Collection
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
protected function getMenuData()
|
||||
{
|
||||
$menuData = Db::name('system_menu')
|
||||
->field('id,pid,title,icon,href,target')
|
||||
->where("delete_time is null")
|
||||
->where([
|
||||
['status', '=', '1'],
|
||||
['pid', '<>', MenuConstant::HOME_PID],
|
||||
])
|
||||
->order([
|
||||
'sort' => 'desc',
|
||||
'id' => 'asc',
|
||||
])
|
||||
->select();
|
||||
return $menuData;
|
||||
}
|
||||
|
||||
}
|
||||
161
app/common/tpl/dispatch_jump.tpl
Normal file
161
app/common/tpl/dispatch_jump.tpl
Normal file
@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>跳转提示</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Lantinghei SC, Open Sans, Arial, Hiragino Sans GB, Microsoft YaHei, "微软雅黑", STHeiti, WenQuanYi Micro Hei, SimSun, sans-serif;
|
||||
-webkit-font-smoothing: antialiased
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 70px 0;
|
||||
background: #edf1f4;
|
||||
font-weight: 400;
|
||||
font-size: 1pc;
|
||||
-webkit-text-size-adjust: none;
|
||||
color: #333
|
||||
}
|
||||
|
||||
a {
|
||||
outline: 0;
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.system-message {
|
||||
margin: 20px 5%;
|
||||
padding: 40px 20px;
|
||||
background: #fff;
|
||||
box-shadow: 1px 1px 1px hsla(0, 0%, 39%, .1);
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.system-message h1 {
|
||||
margin: 0;
|
||||
margin-bottom: 9pt;
|
||||
color: #444;
|
||||
font-weight: 400;
|
||||
font-size: 40px
|
||||
}
|
||||
|
||||
.system-message .jump, .system-message .image {
|
||||
margin: 20px 0;
|
||||
padding: 0;
|
||||
padding: 10px 0;
|
||||
font-weight: 400
|
||||
}
|
||||
|
||||
.system-message .jump {
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
.system-message .jump a {
|
||||
color: #333
|
||||
}
|
||||
|
||||
.system-message p {
|
||||
font-size: 9pt;
|
||||
line-height: 20px
|
||||
}
|
||||
|
||||
.system-message .btn {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
width: 138px;
|
||||
height: 2pc;
|
||||
border: 1px solid #44a0e8;
|
||||
border-radius: 30px;
|
||||
color: #44a0e8;
|
||||
text-align: center;
|
||||
font-size: 1pc;
|
||||
line-height: 2pc;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.success .btn {
|
||||
border-color: #69bf4e;
|
||||
color: #69bf4e
|
||||
}
|
||||
|
||||
.error .btn {
|
||||
border-color: #ff8992;
|
||||
color: #ff8992
|
||||
}
|
||||
|
||||
.info .btn {
|
||||
border-color: #3498db;
|
||||
color: #3498db
|
||||
}
|
||||
|
||||
.copyright p {
|
||||
width: 100%;
|
||||
color: #919191;
|
||||
text-align: center;
|
||||
font-size: 10px
|
||||
}
|
||||
|
||||
.system-message .btn-grey {
|
||||
border-color: #bbb;
|
||||
color: #bbb
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
content: "."
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.system-message h1 {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
$codeText = $code == 1 ? 'success' : ($code == 0 ? 'error' : 'info');
|
||||
?>
|
||||
<div class="system-message {$codeText}">
|
||||
<div class="image">
|
||||
<img src="/static/common/images/{$codeText}.svg" alt="" width="150"/>
|
||||
</div>
|
||||
<h1><?php echo(strip_tags($msg));?></h1>
|
||||
<p class="jump">
|
||||
页面将在 <span id="wait"><?php echo($wait);?></span> 秒后自动跳转
|
||||
</p>
|
||||
<p class="clearfix">
|
||||
<a href="#" onClick="history.back(-1);" class="btn btn-grey">返回上一页</a>
|
||||
<a id="href" href="{$url}" class="btn btn-primary">立即跳转</a>
|
||||
</p>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var wait = document.getElementById('wait'),
|
||||
href = document.getElementById('href').href;
|
||||
var interval = setInterval(function () {
|
||||
var time = --wait.innerHTML;
|
||||
if (time <= 0) {
|
||||
location.href = href;
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 1000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
101
app/common/tpl/think_exception.tpl
Normal file
101
app/common/tpl/think_exception.tpl
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
$cdnurl = function_exists('config') ? config('view_replace_str.__CDN__') : '';
|
||||
$publicurl = function_exists('config') ? config('view_replace_str.__PUBLIC__') : '/';
|
||||
$debug = function_exists('config') ? config('app_debug') : false;
|
||||
|
||||
$lang = [
|
||||
'An error occurred' => 'IP禁止充值',
|
||||
'Home' => '返回主页',
|
||||
'Feedback' => '禁止充值',
|
||||
'The page you are looking for is temporarily unavailable' => '您的IP已被禁止充值,如有疑问请联系管理员',
|
||||
'You can return to the previous page and try again' => '你可以返回上一页重试,或直接向我们反馈错误报告'
|
||||
];
|
||||
|
||||
$langSet = '';
|
||||
|
||||
if (isset($_GET['lang'])) {
|
||||
$langSet = strtolower($_GET['lang']);
|
||||
} elseif (isset($_COOKIE['think_var'])) {
|
||||
$langSet = strtolower($_COOKIE['think_var']);
|
||||
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
|
||||
$langSet = strtolower($matches[1]);
|
||||
}
|
||||
$langSet = $langSet && in_array($langSet, ['zh-cn', 'en']) ? $langSet : 'zh-cn';
|
||||
$langSet == 'en' && $lang = array_combine(array_keys($lang), array_keys($lang));
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title><?=$lang['An error occurred']?></title>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<link rel="shortcut icon" href="<?php echo $cdnurl;?>/assets/img/favicon.ico" />
|
||||
<style>
|
||||
* {-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;}
|
||||
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,caption,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video {margin:0;padding:0;border:0;outline:0;vertical-align:baseline;background:transparent;}
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section {display:block;}
|
||||
html {font-size:16px;line-height:24px;width:100%;height:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;overflow-y:scroll;overflow-x:hidden;}
|
||||
img {vertical-align:middle;max-width:100%;height:auto;border:0;-ms-interpolation-mode:bicubic;}
|
||||
body {min-height:100%;background:#edf1f4;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",微软雅黑,Arial,sans-serif;}
|
||||
.clearfix {clear:both;zoom:1;}
|
||||
.clearfix:before,.clearfix:after {content:"\0020";display:block;height:0;visibility:hidden;}
|
||||
.clearfix:after {clear:both;}
|
||||
body.error-page-wrapper,.error-page-wrapper.preview {background-position:center center;background-repeat:no-repeat;background-size:cover;position:relative;}
|
||||
.error-page-wrapper .content-container {border-radius:2px;text-align:center;box-shadow:1px 1px 1px rgba(99,99,99,0.1);padding:50px;background-color:#fff;width:100%;max-width:560px;position:absolute;left:50%;top:50%;margin-top:-220px;margin-left:-280px;}
|
||||
.error-page-wrapper .content-container.in {left:0px;opacity:1;}
|
||||
.error-page-wrapper .head-line {transition:color .2s linear;font-size:40px;line-height:60px;letter-spacing:-1px;margin-bottom:20px;color:#777;}
|
||||
.error-page-wrapper .subheader {transition:color .2s linear;font-size:32px;line-height:46px;color:#494949;}
|
||||
.error-page-wrapper .hr {height:1px;background-color:#eee;width:80%;max-width:350px;margin:25px auto;}
|
||||
.error-page-wrapper .context {transition:color .2s linear;font-size:16px;line-height:27px;color:#aaa;}
|
||||
.error-page-wrapper .context p {margin:0;}
|
||||
.error-page-wrapper .context p:nth-child(n+2) {margin-top:16px;}
|
||||
.error-page-wrapper .buttons-container {margin-top:35px;overflow:hidden;}
|
||||
.error-page-wrapper .buttons-container a {transition:text-indent .2s ease-out,color .2s linear,background-color .2s linear;text-indent:0px;font-size:14px;text-transform:uppercase;text-decoration:none;color:#fff;background-color:#2ecc71;border-radius:99px;padding:8px 0 8px;text-align:center;display:inline-block;overflow:hidden;position:relative;width:45%;}
|
||||
.error-page-wrapper .buttons-container a:hover {text-indent:15px;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(1) {float:left;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(2) {float:right;}
|
||||
@media screen and (max-width:580px) {
|
||||
.error-page-wrapper {padding:30px 5%;}
|
||||
.error-page-wrapper .content-container {padding:37px;position:static;left:0;margin-top:0;margin-left:0;}
|
||||
.error-page-wrapper .head-line {font-size:36px;}
|
||||
.error-page-wrapper .subheader {font-size:27px;line-height:37px;}
|
||||
.error-page-wrapper .hr {margin:30px auto;width:215px;}
|
||||
}
|
||||
@media screen and (max-width:450px) {
|
||||
.error-page-wrapper {padding:30px;}
|
||||
.error-page-wrapper .head-line {font-size:32px;}
|
||||
.error-page-wrapper .hr {margin:25px auto;width:180px;}
|
||||
.error-page-wrapper .context {font-size:15px;line-height:22px;}
|
||||
.error-page-wrapper .context p:nth-child(n+2) {margin-top:10px;}
|
||||
.error-page-wrapper .buttons-container {margin-top:29px;}
|
||||
.error-page-wrapper .buttons-container a {float:none !important;width:65%;margin:0 auto;font-size:13px;padding:9px 0;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(2) {margin-top:12px;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="error-page-wrapper">
|
||||
<div class="content-container">
|
||||
<div class="head-line">
|
||||
<img src="/static/common/images/error.svg" alt="" width="150" />
|
||||
</div>
|
||||
<div class="subheader">
|
||||
<?=$debug?$message:$lang['The page you are looking for is temporarily unavailable']?>
|
||||
</div>
|
||||
<div class="hr"></div>
|
||||
<div class="context">
|
||||
|
||||
<p>
|
||||
<?=$lang['You can return to the previous page and try again']?>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="buttons-container">
|
||||
<a href="/"><?=$lang['Home']?></a>
|
||||
<a href="/"><?=$lang['Feedback']?></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
131
app/common/traits/JumpTrait.php
Normal file
131
app/common/traits/JumpTrait.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits;
|
||||
|
||||
use think\exception\HttpResponseException;
|
||||
use think\Response;
|
||||
|
||||
/**
|
||||
* Trait JumpTrait
|
||||
* @package app\common\traits
|
||||
*/
|
||||
trait JumpTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* 操作成功跳转的快捷方法
|
||||
* @access protected
|
||||
* @param string|null $msg 提示信息
|
||||
* @param mixed|string $data 返回的数据
|
||||
* @param string|null $url 跳转的 URL 地址
|
||||
* @param int $wait 跳转等待时间
|
||||
* @param array $header 发送的 Header 信息
|
||||
* @return void
|
||||
*/
|
||||
protected function success(?string $msg = null, mixed $data = '', ?string $url = null, int $wait = 3, array $header = []): void
|
||||
{
|
||||
if (is_null($url)) {
|
||||
$url = app()->request->server('HTTP_REFERER');
|
||||
}elseif ($url) {
|
||||
$url = (strpos($url, '://') || str_starts_with($url, '/')) ? $url : app('route')->buildUrl($url)->__toString();
|
||||
}
|
||||
$result = [
|
||||
'code' => 1,
|
||||
'msg' => $msg,
|
||||
'data' => $data,
|
||||
'url' => $url,
|
||||
'wait' => $wait,
|
||||
'__token__' => request()->buildToken('__token__'),
|
||||
];
|
||||
|
||||
$type = $this->getResponseType();
|
||||
if ($type == 'html') {
|
||||
$response = view(config('app.dispatch_success_tmpl'), $result);
|
||||
}else {
|
||||
$response = json($result);
|
||||
}
|
||||
throw new HttpResponseException($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作错误跳转的快捷方法
|
||||
* @access protected
|
||||
* @param string|null $msg 提示信息
|
||||
* @param mixed $data 返回的数据
|
||||
* @param string|null $url 跳转的 URL 地址
|
||||
* @param int $wait 跳转等待时间
|
||||
* @param array $header 发送的 Header 信息
|
||||
* @return void
|
||||
*/
|
||||
protected function error(?string $msg = null, mixed $data = '', ?string $url = null, int $wait = 3, array $header = []): void
|
||||
{
|
||||
if (is_null($url)) {
|
||||
$url = request()->isAjax() ? '' : 'javascript:history.back(-1);';
|
||||
}elseif ($url) {
|
||||
$url = (strpos($url, '://') || str_starts_with($url, '/')) ? $url : app('route')->buildUrl($url)->__toString();
|
||||
}
|
||||
|
||||
$type = $this->getResponseType();
|
||||
$result = [
|
||||
'code' => 0,
|
||||
'msg' => $msg,
|
||||
'data' => $data,
|
||||
'url' => $url,
|
||||
'wait' => $wait,
|
||||
'__token__' => request()->buildToken('__token__'),
|
||||
];
|
||||
if ($type == 'html') {
|
||||
$response = view(config('app.dispatch_error_tmpl'), $result);
|
||||
}else {
|
||||
$response = json($result);
|
||||
}
|
||||
throw new HttpResponseException($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回封装后的 API 数据到客户端
|
||||
* @access protected
|
||||
* @param mixed $data 要返回的数据
|
||||
* @param int $code 返回的 code
|
||||
* @param string|null $msg 提示信息
|
||||
* @param string $type 返回数据格式
|
||||
* @param array $header 发送的 Header 信息
|
||||
* @return void
|
||||
*/
|
||||
protected function result(mixed $data, int $code = 0, ?string $msg = '', string $type = '', array $header = []): void
|
||||
{
|
||||
$result = [
|
||||
'code' => $code,
|
||||
'msg' => $msg,
|
||||
'time' => time(),
|
||||
'data' => $data,
|
||||
];
|
||||
$type = $type ?: $this->getResponseType();
|
||||
$response = Response::create($result, $type)->header($header);
|
||||
throw new HttpResponseException($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL 重定向
|
||||
* @access protected
|
||||
* @param string $url 跳转的 URL 表达式
|
||||
* @param int $code http code
|
||||
* @return void
|
||||
* @throws HttpResponseException
|
||||
*/
|
||||
protected function redirect(string $url = '', int $code = 302): void
|
||||
{
|
||||
$response = Response::create($url, 'redirect', $code);
|
||||
throw new HttpResponseException($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的 response 输出类型
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function getResponseType(): string
|
||||
{
|
||||
return (request()->isJson() || request()->isAjax() || request()->isPost()) ? 'json' : 'html';
|
||||
}
|
||||
}
|
||||
28
app/common/utils/Helper.php
Normal file
28
app/common/utils/Helper.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
class Helper
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取当前IP地址
|
||||
* @return string
|
||||
*/
|
||||
public static function getIp(): string
|
||||
{
|
||||
return request()->ip();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户ID
|
||||
* @return int|string
|
||||
*/
|
||||
public static function getAdminUid(): int|string
|
||||
{
|
||||
return session('admin.id') ?: 0;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user