mirror of
https://gitee.com/zoujingli/ThinkAdmin.git
synced 2026-06-07 20:48:09 +08:00
refactor(plugin): 迁移 v8 插件化组件体系
将 v6 中直接放在本地 app 的后台与微信能力迁移为 v8 插件组件,并把运行时基础能力沉淀到独立插件包。 主要内容: - 新增 think-library、system、worker、static、install 等基础插件包。 - 新增 account、payment、wechat-client、wechat-service、wemall、wuma 等业务插件包。 - 移除 v6 的 app/admin 与 app/wechat 本地应用实现,改由插件分发接管。 - 将 Helper 能力彻底并入 System,统一为 plugin\system\helper\* 命名空间。 - 同步插件迁移发布清单与根 route 占位,保证安装发布流程可复现。
This commit is contained in:
parent
f891251e54
commit
e634118a22
@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin;
|
||||
|
||||
use think\admin\Plugin;
|
||||
|
||||
/**
|
||||
* 插件服务注册.
|
||||
* @class Service
|
||||
*/
|
||||
class Service extends Plugin
|
||||
{
|
||||
/**
|
||||
* 定义插件名称.
|
||||
* @var string
|
||||
*/
|
||||
protected $appName = '系统管理';
|
||||
|
||||
/**
|
||||
* 定义安装包名.
|
||||
* @var string
|
||||
*/
|
||||
protected $package = 'zoujingli/think-plugs-admin';
|
||||
|
||||
/**
|
||||
* 定义插件中心菜单.
|
||||
*/
|
||||
public static function menu(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => '系统配置',
|
||||
'subs' => [
|
||||
['name' => '系统参数配置', 'icon' => 'layui-icon layui-icon-set', 'node' => 'admin/config/index'],
|
||||
['name' => '系统任务管理', 'icon' => 'layui-icon layui-icon-log', 'node' => 'admin/queue/index'],
|
||||
['name' => '系统日志管理', 'icon' => 'layui-icon layui-icon-form', 'node' => 'admin/oplog/index'],
|
||||
['name' => '数据字典管理', 'icon' => 'layui-icon layui-icon-code-circle', 'node' => 'admin/base/index'],
|
||||
['name' => '系统文件管理', 'icon' => 'layui-icon layui-icon-carousel', 'node' => 'admin/file/index'],
|
||||
['name' => '系统菜单管理', 'icon' => 'layui-icon layui-icon-layouts', 'node' => 'admin/menu/index'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => '权限管理',
|
||||
'subs' => [
|
||||
['name' => '系统权限管理', 'icon' => 'layui-icon layui-icon-vercode', 'node' => 'admin/auth/index'],
|
||||
['name' => '系统用户管理', 'icon' => 'layui-icon layui-icon-username', 'node' => 'admin/user/index'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,141 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\helper\QueryHelper;
|
||||
use think\admin\model\SystemAuth;
|
||||
use think\admin\model\SystemNode;
|
||||
use think\admin\Plugin;
|
||||
use think\admin\service\AdminService;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
/**
|
||||
* 系统权限管理.
|
||||
* @class Auth
|
||||
*/
|
||||
class Auth extends Controller
|
||||
{
|
||||
/**
|
||||
* 系统权限管理.
|
||||
* @auth true
|
||||
* @menu true
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
SystemAuth::mQuery()->layTable(function () {
|
||||
$this->title = '系统权限管理';
|
||||
}, static function (QueryHelper $query) {
|
||||
$query->like('title,desc')->equal('status,utype')->dateBetween('create_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改权限状态
|
||||
* @auth true
|
||||
*/
|
||||
public function state()
|
||||
{
|
||||
SystemAuth::mSave($this->_vali([
|
||||
'status.in:0,1' => '状态值范围异常!',
|
||||
'status.require' => '状态值不能为空!',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除系统权限.
|
||||
* @auth true
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
SystemAuth::mDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统权限.
|
||||
* @auth true
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
SystemAuth::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑系统权限.
|
||||
* @auth true
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
SystemAuth::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单后置数据处理.
|
||||
*/
|
||||
protected function _form_filter(array $data)
|
||||
{
|
||||
if ($this->request->isGet()) {
|
||||
$this->title = empty($data['title']) ? '添加访问授权' : "编辑【{$data['title']}】授权";
|
||||
} elseif ($this->request->post('action') === 'json') {
|
||||
if ($this->app->isDebug()) {
|
||||
AdminService::clear();
|
||||
}
|
||||
$ztree = AdminService::getTree(empty($data['id']) ? [] : SystemNode::mk()->where(['auth' => $data['id']])->column('node'));
|
||||
usort($ztree, static function ($a, $b) {
|
||||
if (explode('-', $a['node'])[0] !== explode('-', $b['node'])[0]) {
|
||||
if (stripos($a['node'], 'plugin-') === 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return $a['node'] === $b['node'] ? 0 : ($a['node'] > $b['node'] ? 1 : -1);
|
||||
});
|
||||
[$ps, $cs] = [Plugin::get(), (array)$this->app->config->get('app.app_names', [])];
|
||||
foreach ($ztree as &$n) {
|
||||
$n['title'] = lang($cs[$n['node']] ?? (($ps[$n['node']] ?? [])['name'] ?? $n['title']));
|
||||
}
|
||||
$this->success('获取权限节点成功!', $ztree);
|
||||
} elseif (empty($data['nodes'])) {
|
||||
$this->error('未配置功能节点!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点更新处理.
|
||||
*/
|
||||
protected function _form_result(bool $state, array $post)
|
||||
{
|
||||
if ($state && $this->request->post('action') === 'save') {
|
||||
[$map, $data] = [['auth' => $post['id']], []];
|
||||
foreach ($post['nodes'] ?? [] as $node) {
|
||||
$data[] = $map + ['node' => $node];
|
||||
}
|
||||
SystemNode::mk()->where($map)->delete();
|
||||
count($data) > 0 && SystemNode::mk()->insertAll($data);
|
||||
sysoplog('系统权限管理', "配置系统权限[{$map['auth']}]授权成功");
|
||||
$this->success('权限修改成功!', 'javascript:history.back()');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\helper\QueryHelper;
|
||||
use think\admin\model\SystemBase;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
/**
|
||||
* 数据字典管理.
|
||||
* @class Base
|
||||
*/
|
||||
class Base extends Controller
|
||||
{
|
||||
/**
|
||||
* 数据字典管理.
|
||||
* @auth true
|
||||
* @menu true
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
SystemBase::mQuery()->layTable(function () {
|
||||
$this->title = '数据字典管理';
|
||||
$this->types = SystemBase::types();
|
||||
$this->type = $this->get['type'] ?? ($this->types[0] ?? '-');
|
||||
}, static function (QueryHelper $query) {
|
||||
$query->where(['deleted' => 0])->equal('type');
|
||||
$query->like('code,name,status')->dateBetween('create_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加数据字典.
|
||||
* @auth true
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
SystemBase::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑数据字典.
|
||||
* @auth true
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
SystemBase::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据状态
|
||||
* @auth true
|
||||
*/
|
||||
public function state()
|
||||
{
|
||||
SystemBase::mSave($this->_vali([
|
||||
'status.in:0,1' => '状态值范围异常!',
|
||||
'status.require' => '状态值不能为空!',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据记录.
|
||||
* @auth true
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
SystemBase::mDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单数据处理.
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function _form_filter(array &$data)
|
||||
{
|
||||
if ($this->request->isGet()) {
|
||||
$this->types = SystemBase::types();
|
||||
$this->types[] = '--- ' . lang('新增类型') . ' ---';
|
||||
$this->type = $this->get['type'] ?? ($this->types[0] ?? '-');
|
||||
} else {
|
||||
$map = [];
|
||||
$map[] = ['deleted', '=', 0];
|
||||
$map[] = ['code', '=', $data['code']];
|
||||
$map[] = ['type', '=', $data['type']];
|
||||
$map[] = ['id', '<>', $data['id'] ?? 0];
|
||||
if (SystemBase::mk()->where($map)->count() > 0) {
|
||||
$this->error('数据编码已经存在!');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\Plugin;
|
||||
use think\admin\service\AdminService;
|
||||
use think\admin\service\ModuleService;
|
||||
use think\admin\service\RuntimeService;
|
||||
use think\admin\service\SystemService;
|
||||
use think\admin\Storage;
|
||||
use think\admin\storage\AliossStorage;
|
||||
use think\admin\storage\QiniuStorage;
|
||||
use think\admin\storage\TxcosStorage;
|
||||
|
||||
/**
|
||||
* 系统参数配置.
|
||||
* @class Config
|
||||
*/
|
||||
class Config extends Controller
|
||||
{
|
||||
public const themes = [
|
||||
'default' => '默认色0',
|
||||
'white' => '简约白0',
|
||||
'red-1' => '玫瑰红1',
|
||||
'blue-1' => '深空蓝1',
|
||||
'green-1' => '小草绿1',
|
||||
'black-1' => '经典黑1',
|
||||
'red-2' => '玫瑰红2',
|
||||
'blue-2' => '深空蓝2',
|
||||
'green-2' => '小草绿2',
|
||||
'black-2' => '经典黑2',
|
||||
];
|
||||
|
||||
/**
|
||||
* 系统参数配置.
|
||||
* @auth true
|
||||
* @menu true
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->title = '系统参数配置';
|
||||
$this->files = Storage::types();
|
||||
$this->plugins = Plugin::get(null, true);
|
||||
$this->issuper = AdminService::isSuper();
|
||||
$this->systemid = ModuleService::getRunVar('uni');
|
||||
$this->framework = ModuleService::getLibrarys('topthink/framework');
|
||||
$this->thinkadmin = ModuleService::getLibrarys('zoujingli/think-library');
|
||||
if (AdminService::isSuper() && $this->app->session->get('user.password') === md5('admin')) {
|
||||
$url = url('admin/index/pass', ['id' => AdminService::getUserId()]);
|
||||
$this->showErrorMessage = lang("超级管理员账号的密码未修改,建议立即<a data-modal='%s'>修改密码</a>!", [$url]);
|
||||
}
|
||||
uasort($this->plugins, static function ($a, $b) {
|
||||
if ($a['space'] === $b['space']) {
|
||||
return 0;
|
||||
}
|
||||
return $a['space'] > $b['space'] ? 1 : -1;
|
||||
});
|
||||
$this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改系统参数.
|
||||
* @auth true
|
||||
* @throws \think\admin\Exception
|
||||
*/
|
||||
public function system()
|
||||
{
|
||||
if ($this->request->isGet()) {
|
||||
$this->title = '修改系统参数';
|
||||
$this->themes = static::themes;
|
||||
$this->fetch();
|
||||
} else {
|
||||
$post = $this->request->post();
|
||||
// 修改网站后台入口路径
|
||||
if (!empty($post['xpath'])) {
|
||||
if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $post['xpath'])) {
|
||||
$this->error('后台入口格式错误!');
|
||||
}
|
||||
if ($post['xpath'] !== 'admin') {
|
||||
if (is_dir(syspath("app/{$post['xpath']}")) || !empty(Plugin::get($post['xpath']))) {
|
||||
$this->error(lang('已存在 %s 应用!', [$post['xpath']]));
|
||||
}
|
||||
}
|
||||
RuntimeService::set(null, [$post['xpath'] => 'admin']);
|
||||
}
|
||||
// 修改网站 ICON 图标,替换 public/favicon.ico
|
||||
if (preg_match('#^https?://#', $post['site_icon'] ?? '')) {
|
||||
try {
|
||||
SystemService::setFavicon($post['site_icon'] ?? '');
|
||||
} catch (\Exception $exception) {
|
||||
trace_file($exception);
|
||||
}
|
||||
}
|
||||
// 数据数据到系统配置表
|
||||
foreach ($post as $k => $v) {
|
||||
sysconf($k, $v);
|
||||
}
|
||||
sysoplog('系统配置管理', '修改系统参数成功');
|
||||
$this->success('数据保存成功!', admuri('admin/config/index'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改文件存储.
|
||||
* @auth true
|
||||
* @throws \think\admin\Exception
|
||||
*/
|
||||
public function storage()
|
||||
{
|
||||
$this->_applyFormToken();
|
||||
if ($this->request->isGet()) {
|
||||
$this->type = input('type', 'local');
|
||||
if ($this->type === 'alioss') {
|
||||
$this->points = AliossStorage::region();
|
||||
} elseif ($this->type === 'qiniu') {
|
||||
$this->points = QiniuStorage::region();
|
||||
} elseif ($this->type === 'txcos') {
|
||||
$this->points = TxcosStorage::region();
|
||||
}
|
||||
$this->fetch("storage-{$this->type}");
|
||||
} else {
|
||||
$post = $this->request->post();
|
||||
if (!empty($post['storage']['allow_exts'])) {
|
||||
$deny = ['sh', 'asp', 'bat', 'cmd', 'exe', 'php'];
|
||||
$exts = array_unique(str2arr(strtolower($post['storage']['allow_exts'])));
|
||||
if (count(array_intersect($deny, $exts)) > 0) {
|
||||
$this->error('禁止上传可执行的文件!');
|
||||
}
|
||||
$post['storage']['allow_exts'] = join(',', $exts);
|
||||
}
|
||||
foreach ($post as $name => $value) {
|
||||
sysconf($name, $value);
|
||||
}
|
||||
sysoplog('系统配置管理', '修改系统存储参数');
|
||||
$this->success('修改文件存储成功!');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,118 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\helper\QueryHelper;
|
||||
use think\admin\model\SystemFile;
|
||||
use think\admin\service\AdminService;
|
||||
use think\admin\Storage;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
/**
|
||||
* 系统文件管理.
|
||||
* @class File
|
||||
*/
|
||||
class File extends Controller
|
||||
{
|
||||
/**
|
||||
* 存储类型.
|
||||
* @var array
|
||||
*/
|
||||
protected $types;
|
||||
|
||||
/**
|
||||
* 系统文件管理.
|
||||
* @auth true
|
||||
* @menu true
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
SystemFile::mQuery()->layTable(function () {
|
||||
$this->title = '系统文件管理';
|
||||
$this->xexts = SystemFile::mk()->distinct()->column('xext');
|
||||
}, static function (QueryHelper $query) {
|
||||
$query->like('name,hash,xext')->equal('type')->dateBetween('create_at');
|
||||
$query->where(['issafe' => 0, 'status' => 2, 'uuid' => AdminService::getUserId()]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑系统文件.
|
||||
* @auth true
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
SystemFile::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除系统文件.
|
||||
* @auth true
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
if (!AdminService::isSuper()) {
|
||||
$where = ['uuid' => AdminService::getUserId()];
|
||||
}
|
||||
SystemFile::mDelete('', $where ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理重复文件.
|
||||
* @auth true
|
||||
* @throws DbException
|
||||
*/
|
||||
public function distinct()
|
||||
{
|
||||
$map = ['issafe' => 0, 'uuid' => AdminService::getUserId()];
|
||||
// 使用派生表包装子查询,避免直接引用同一表
|
||||
$keepSubQuery = SystemFile::mk()->fieldRaw('MAX(id) AS id')->where($map)->group('type, xkey')->buildSql();
|
||||
// 使用 whereNotExists 配合派生表子查询删除,避免 1093 错误和 whereIn
|
||||
SystemFile::mk()->where($map)->whereNotExists(function ($query) use ($keepSubQuery) {
|
||||
$query->table("({$keepSubQuery})")->alias('f2')->whereRaw('f2.id = system_file.id');
|
||||
})->delete();
|
||||
$this->success('清理重复文件成功!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制器初始化.
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
$this->types = Storage::types();
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据列表处理.
|
||||
*/
|
||||
protected function _page_filter(array &$data)
|
||||
{
|
||||
foreach ($data as &$vo) {
|
||||
$vo['ctype'] = $this->types[$vo['type']] ?? $vo['type'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,161 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\Exception;
|
||||
use think\admin\model\SystemUser;
|
||||
use think\admin\service\AdminService;
|
||||
use think\admin\service\MenuService;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
/**
|
||||
* 后台界面入口.
|
||||
* @class Index
|
||||
*/
|
||||
class Index extends Controller
|
||||
{
|
||||
/**
|
||||
* 显示后台首页.
|
||||
* @throws Exception
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
/* ! 根据运行模式刷新权限 */
|
||||
AdminService::apply($this->app->isDebug());
|
||||
/* ! 读取当前用户权限菜单树 */
|
||||
$this->menus = MenuService::getTree();
|
||||
/* ! 判断当前用户的登录状态 */
|
||||
$this->login = AdminService::isLogin();
|
||||
/* ! 菜单为空且未登录跳转到登录页 */
|
||||
if (empty($this->menus) && empty($this->login)) {
|
||||
$this->redirect(sysuri('admin/login/index'));
|
||||
} else {
|
||||
$this->title = '系统管理后台';
|
||||
$this->super = AdminService::isSuper();
|
||||
$this->theme = AdminService::getUserTheme();
|
||||
$this->fetch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台主题切换.
|
||||
* @login true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theme()
|
||||
{
|
||||
if ($this->request->isGet()) {
|
||||
$this->theme = AdminService::getUserTheme();
|
||||
$this->themes = Config::themes;
|
||||
$this->fetch();
|
||||
} else {
|
||||
$data = $this->_vali(['site_theme.require' => '主题名称不能为空!']);
|
||||
if (AdminService::setUserTheme($data['site_theme'])) {
|
||||
$this->success('主题配置保存成功!');
|
||||
} else {
|
||||
$this->error('主题配置保存失败!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户资料.
|
||||
* @login true
|
||||
*/
|
||||
public function info()
|
||||
{
|
||||
$id = $this->request->param('id');
|
||||
if (AdminService::getUserId() == intval($id)) {
|
||||
SystemUser::mForm('user/form', 'id', [], ['id' => $id]);
|
||||
} else {
|
||||
$this->error('只能修改自己的资料!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改当前用户密码
|
||||
* @login true
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function pass()
|
||||
{
|
||||
$id = $this->request->param('id');
|
||||
if (AdminService::getUserId() !== intval($id)) {
|
||||
$this->error('禁止修改他人密码!');
|
||||
}
|
||||
if ($this->app->request->isGet()) {
|
||||
$this->verify = true;
|
||||
SystemUser::mForm('user/pass', 'id', [], ['id' => $id]);
|
||||
} else {
|
||||
$data = $this->_vali([
|
||||
'password.require' => '登录密码不能为空!',
|
||||
'repassword.require' => '重复密码不能为空!',
|
||||
'oldpassword.require' => '旧的密码不能为空!',
|
||||
'password.confirm:repassword' => '两次输入的密码不一致!',
|
||||
]);
|
||||
$user = SystemUser::mk()->find($id);
|
||||
if (empty($user)) {
|
||||
$this->error('用户不存在!');
|
||||
}
|
||||
if (md5($data['oldpassword']) !== $user['password']) {
|
||||
$this->error('旧密码验证失败,请重新输入!');
|
||||
}
|
||||
if ($user->save(['password' => md5($data['password'])])) {
|
||||
sysoplog('系统用户管理', "修改用户[{$user['id']}]密码成功");
|
||||
// 修改密码同步事件处理
|
||||
$this->app->event->trigger('PluginAdminChangePassword', [
|
||||
'uuid' => intval($user['id']), 'pass' => $data['password'],
|
||||
]);
|
||||
$this->success('密码修改成功,下次请使用新密码登录!', '');
|
||||
} else {
|
||||
$this->error('密码修改失败,请稍候再试!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资料修改表单处理.
|
||||
*/
|
||||
protected function _info_form_filter(array &$data)
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
unset($data['username'], $data['authorize']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资料修改结果处理.
|
||||
*/
|
||||
protected function _info_form_result(bool $status)
|
||||
{
|
||||
if ($status) {
|
||||
$this->success('用户资料修改成功!', 'javascript:location.reload()');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\Exception;
|
||||
use think\admin\extend\CodeExtend;
|
||||
use think\admin\model\SystemUser;
|
||||
use think\admin\service\AdminService;
|
||||
use think\admin\service\CaptchaService;
|
||||
use think\admin\service\RuntimeService;
|
||||
use think\admin\service\SystemService;
|
||||
|
||||
/**
|
||||
* 用户登录管理.
|
||||
* @class Login
|
||||
*/
|
||||
class Login extends Controller
|
||||
{
|
||||
/**
|
||||
* 后台登录入口.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->app->request->isGet()) {
|
||||
if (AdminService::isLogin()) {
|
||||
$this->redirect(sysuri('admin/index/index'));
|
||||
} else {
|
||||
// 加载登录模板
|
||||
$this->title = '系统登录';
|
||||
// 登录验证令牌
|
||||
$this->captchaType = 'LoginCaptcha';
|
||||
$this->captchaToken = CodeExtend::uuid();
|
||||
// 当前运行模式
|
||||
$this->runtimeMode = RuntimeService::check();
|
||||
// 后台背景处理
|
||||
$images = str2arr(sysconf('login_image|raw') ?: '', '|');
|
||||
if (empty($images)) {
|
||||
$images = [
|
||||
SystemService::uri('/static/theme/img/login/bg1.jpg'),
|
||||
SystemService::uri('/static/theme/img/login/bg2.jpg'),
|
||||
];
|
||||
}
|
||||
$this->loginStyle = sprintf('style="background-image:url(%s)" data-bg-transition="%s"', $images[0], join(',', $images));
|
||||
// 更新后台主域名,用于部分无法获取域名的场景调用
|
||||
if ($this->request->domain() !== sysconf('base.site_host|raw')) {
|
||||
sysconf('base.site_host', $this->request->domain());
|
||||
}
|
||||
$this->fetch();
|
||||
}
|
||||
} else {
|
||||
$data = $this->_vali([
|
||||
'username.require' => '登录账号不能为空!',
|
||||
'username.min:4' => '账号不能少于4位字符!',
|
||||
'password.require' => '登录密码不能为空!',
|
||||
'password.min:4' => '密码不能少于4位字符!',
|
||||
'verify.require' => '图形验证码不能为空!',
|
||||
'uniqid.require' => '图形验证标识不能为空!',
|
||||
]);
|
||||
if (!CaptchaService::instance()->check($data['verify'], $data['uniqid'])) {
|
||||
$this->error('图形验证码验证失败,请重新输入!');
|
||||
}
|
||||
/* ! 用户信息验证 */
|
||||
$map = ['username' => $data['username'], 'is_deleted' => 0];
|
||||
$user = SystemUser::mk()->where($map)->findOrEmpty();
|
||||
if ($user->isEmpty()) {
|
||||
$this->app->session->set('LoginInputSessionError', true);
|
||||
$this->error('登录账号或密码错误,请重新输入!');
|
||||
}
|
||||
if (empty($user['status'])) {
|
||||
$this->app->session->set('LoginInputSessionError', true);
|
||||
$this->error('账号已经被禁用,请联系管理员!');
|
||||
}
|
||||
if (md5("{$user['password']}{$data['uniqid']}") !== $data['password']) {
|
||||
$this->app->session->set('LoginInputSessionError', true);
|
||||
$this->error('登录账号或密码错误,请重新输入!');
|
||||
}
|
||||
$user->hidden(['sort', 'status', 'password', 'is_deleted']);
|
||||
$this->app->session->set('user', $user->toArray());
|
||||
$this->app->session->delete('LoginInputSessionError');
|
||||
// 更新登录次数
|
||||
$user->where(['id' => $user->getAttr('id')])->inc('login_num')->update([
|
||||
'login_at' => date('Y-m-d H:i:s'), 'login_ip' => $this->app->request->ip(),
|
||||
]);
|
||||
// 刷新用户权限
|
||||
AdminService::apply(true);
|
||||
sysoplog('系统用户登录', '登录系统后台成功');
|
||||
$this->success('登录成功', sysuri('admin/index/index'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
public function captcha()
|
||||
{
|
||||
$input = $this->_vali([
|
||||
'type.require' => '类型不能为空!',
|
||||
'token.require' => '标识不能为空!',
|
||||
]);
|
||||
$image = CaptchaService::instance()->initialize();
|
||||
$captcha = ['image' => $image->getData(), 'uniqid' => $image->getUniqid()];
|
||||
// 未发生异常时,直接返回验证码内容
|
||||
if (!$this->app->session->get('LoginInputSessionError')) {
|
||||
$captcha['code'] = $image->getCode();
|
||||
}
|
||||
$this->success('生成验证码成功', $captcha);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录.
|
||||
*/
|
||||
public function out()
|
||||
{
|
||||
$this->app->session->destroy();
|
||||
$this->success('退出登录成功!', sysuri('admin/login/index'));
|
||||
}
|
||||
}
|
||||
@ -1,189 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\extend\DataExtend;
|
||||
use think\admin\model\SystemMenu;
|
||||
use think\admin\service\AdminService;
|
||||
use think\admin\service\MenuService;
|
||||
use think\admin\service\NodeService;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
/**
|
||||
* 系统菜单管理.
|
||||
* @class Menu
|
||||
*/
|
||||
class Menu extends Controller
|
||||
{
|
||||
/**
|
||||
* 系统菜单管理.
|
||||
* @auth true
|
||||
* @menu true
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->title = '系统菜单管理';
|
||||
$this->type = $this->get['type'] ?? 'index';
|
||||
// 获取顶级菜单ID
|
||||
$this->pid = $this->get['pid'] ?? '';
|
||||
|
||||
// 查询顶级菜单集合
|
||||
$this->menupList = SystemMenu::mk()->where(['pid' => 0, 'status' => 1])->order('sort desc,id asc')->column('id,pid,title', 'id');
|
||||
|
||||
SystemMenu::mQuery()->layTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统菜单.
|
||||
* @auth true
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$this->_applyFormToken();
|
||||
SystemMenu::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑系统菜单.
|
||||
* @auth true
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$this->_applyFormToken();
|
||||
SystemMenu::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改菜单状态
|
||||
* @auth true
|
||||
*/
|
||||
public function state()
|
||||
{
|
||||
SystemMenu::mSave($this->_vali([
|
||||
'status.in:0,1' => '状态值范围异常!',
|
||||
'status.require' => '状态值不能为空!',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除系统菜单.
|
||||
* @auth true
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
SystemMenu::mDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表数据处理.
|
||||
*/
|
||||
protected function _index_page_filter(array &$data)
|
||||
{
|
||||
$data = DataExtend::arr2tree($data);
|
||||
// 回收站过滤有效菜单
|
||||
if ($this->type === 'recycle') {
|
||||
foreach ($data as $k1 => &$p1) {
|
||||
if (!empty($p1['sub'])) {
|
||||
foreach ($p1['sub'] as $k2 => &$p2) {
|
||||
if (!empty($p2['sub'])) {
|
||||
foreach ($p2['sub'] as $k3 => $p3) {
|
||||
if ($p3['status'] > 0) {
|
||||
unset($p2['sub'][$k3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($p2['sub']) && ($p2['url'] === '#' or $p2['status'] > 0)) {
|
||||
unset($p1['sub'][$k2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($p1['sub']) && ($p1['url'] === '#' or $p1['status'] > 0)) {
|
||||
unset($data[$k1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 菜单数据树数据变平化
|
||||
$data = DataExtend::arr2table($data);
|
||||
|
||||
// 过滤非当前顶级菜单的下级菜单,并重新索引数组
|
||||
if ($this->type === 'index' && $this->pid) {
|
||||
$data = array_values(array_filter($data, function ($item) {
|
||||
return strpos($item['spp'], ",{$this->pid},") !== false;
|
||||
}));
|
||||
}
|
||||
|
||||
foreach ($data as &$vo) {
|
||||
if ($vo['url'] !== '#' && !preg_match('/^(https?:)?(\/\/|\\\)/i', $vo['url'])) {
|
||||
$vo['url'] = trim(url($vo['url']) . ($vo['params'] ? "?{$vo['params']}" : ''), '\/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单数据处理.
|
||||
*/
|
||||
protected function _form_filter(array &$vo)
|
||||
{
|
||||
if ($this->request->isGet()) {
|
||||
$debug = $this->app->isDebug();
|
||||
/* 清理权限节点 */
|
||||
$debug && AdminService::clear();
|
||||
/* 读取系统功能节点 */
|
||||
$this->auths = [];
|
||||
$this->nodes = MenuService::getList($debug);
|
||||
foreach (NodeService::getMethods($debug) as $node => $item) {
|
||||
if ($item['isauth'] && substr_count($node, '/') >= 2) {
|
||||
$this->auths[] = ['node' => $node, 'title' => $item['title']];
|
||||
}
|
||||
}
|
||||
/* 选择自己上级菜单 */
|
||||
$vo['pid'] = $vo['pid'] ?? input('pid', '0');
|
||||
/* 列出可选上级菜单 */
|
||||
$menus = SystemMenu::mk()->order('sort desc,id asc')->column('id,pid,icon,url,node,title,params', 'id');
|
||||
$this->menus = DataExtend::arr2table(array_merge($menus, [['id' => '0', 'pid' => '-1', 'url' => '#', 'title' => '顶部菜单']]));
|
||||
if (isset($vo['id'])) {
|
||||
foreach ($this->menus as $menu) {
|
||||
if ($menu['id'] === $vo['id']) {
|
||||
$vo = $menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($this->menus as $key => $menu) {
|
||||
if ($menu['spt'] >= 3 || $menu['url'] !== '#') {
|
||||
unset($this->menus[$key]);
|
||||
}
|
||||
}
|
||||
if (isset($vo['spt'], $vo['spc']) && in_array($vo['spt'], [1, 2]) && $vo['spc'] > 0) {
|
||||
foreach ($this->menus as $key => $menu) {
|
||||
if ($vo['spt'] <= $menu['spt']) {
|
||||
unset($this->menus[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\helper\QueryHelper;
|
||||
use think\admin\model\SystemQueue;
|
||||
use think\admin\service\AdminService;
|
||||
use think\admin\service\ProcessService;
|
||||
use think\admin\service\QueueService;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\exception\HttpResponseException;
|
||||
|
||||
/**
|
||||
* 系统任务管理.
|
||||
* @class Queue
|
||||
*/
|
||||
class Queue extends Controller
|
||||
{
|
||||
/**
|
||||
* 系统任务管理.
|
||||
* @auth true
|
||||
* @menu true
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
SystemQueue::mQuery()->layTable(function () {
|
||||
$this->title = '系统任务管理';
|
||||
$this->iswin = ProcessService::iswin();
|
||||
if ($this->super = AdminService::isSuper()) {
|
||||
$this->command = ProcessService::think('xadmin:queue start');
|
||||
if (!$this->iswin && !empty($_SERVER['USER'])) {
|
||||
$this->command = "sudo -u {$_SERVER['USER']} {$this->command}";
|
||||
}
|
||||
}
|
||||
}, static function (QueryHelper $query) {
|
||||
$query->equal('status')->like('code|title#title,command');
|
||||
$query->timeBetween('enter_time,exec_time')->dateBetween('create_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启系统任务
|
||||
* @auth true
|
||||
*/
|
||||
public function redo()
|
||||
{
|
||||
try {
|
||||
$data = $this->_vali(['code.require' => '任务编号不能为空!']);
|
||||
$queue = QueueService::instance()->initialize($data['code'])->reset();
|
||||
$queue->progress(1, '>>> 任务重置成功 <<<', '0.00');
|
||||
$this->success('任务重置成功!', $queue->code);
|
||||
} catch (HttpResponseException $exception) {
|
||||
throw $exception;
|
||||
} catch (\Exception $exception) {
|
||||
trace_file($exception);
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理运行数据.
|
||||
* @auth true
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
$this->_queue('定时清理系统运行数据', 'xadmin:queue clean', 0, [], 0, 3600);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除系统任务
|
||||
* @auth true
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
SystemQueue::mDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页数据回调处理.
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
protected function _index_page_filter(array $data, array &$result)
|
||||
{
|
||||
$result['extra'] = ['dos' => 0, 'pre' => 0, 'oks' => 0, 'ers' => 0];
|
||||
SystemQueue::mk()->field('status,count(1) count')->group('status')->select()->map(static function ($item) use (&$result) {
|
||||
if (intval($item['status']) === 1) {
|
||||
$result['extra']['pre'] = $item['count'];
|
||||
}
|
||||
if (intval($item['status']) === 2) {
|
||||
$result['extra']['dos'] = $item['count'];
|
||||
}
|
||||
if (intval($item['status']) === 3) {
|
||||
$result['extra']['oks'] = $item['count'];
|
||||
}
|
||||
if (intval($item['status']) === 4) {
|
||||
$result['extra']['ers'] = $item['count'];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,186 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
use think\admin\helper\QueryHelper;
|
||||
use think\admin\model\SystemAuth;
|
||||
use think\admin\model\SystemBase;
|
||||
use think\admin\model\SystemUser;
|
||||
use think\admin\service\AdminService;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
/**
|
||||
* 系统用户管理.
|
||||
* @class User
|
||||
*/
|
||||
class User extends Controller
|
||||
{
|
||||
/**
|
||||
* 系统用户管理.
|
||||
* @auth true
|
||||
* @menu true
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->type = $this->get['type'] ?? 'index';
|
||||
SystemUser::mQuery()->layTable(function () {
|
||||
$this->title = '系统用户管理';
|
||||
$this->bases = SystemBase::items('身份权限');
|
||||
}, function (QueryHelper $query) {
|
||||
// 加载对应数据列表
|
||||
$query->where(['is_deleted' => 0, 'status' => intval($this->type === 'index')]);
|
||||
|
||||
// 关联用户身份资料
|
||||
/* @var \think\model\Relation|\think\db\Query $query */
|
||||
$query->with(['userinfo' => static function ($query) {
|
||||
$query->field('code,name,content');
|
||||
}]);
|
||||
|
||||
// 数据列表搜索过滤
|
||||
$query->equal('status,usertype')->dateBetween('login_at,create_at');
|
||||
$query->like('username|nickname#username,contact_phone#phone,contact_mail#mail');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统用户.
|
||||
* @auth true
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
SystemUser::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑系统用户.
|
||||
* @auth true
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
SystemUser::mForm('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户密码
|
||||
* @auth true
|
||||
*/
|
||||
public function pass()
|
||||
{
|
||||
$this->_applyFormToken();
|
||||
if ($this->request->isGet()) {
|
||||
$this->verify = false;
|
||||
SystemUser::mForm('pass');
|
||||
} else {
|
||||
$data = $this->_vali([
|
||||
'id.require' => '用户ID不能为空!',
|
||||
'password.require' => '登录密码不能为空!',
|
||||
'repassword.require' => '重复密码不能为空!',
|
||||
'repassword.confirm:password' => '两次输入的密码不一致!',
|
||||
]);
|
||||
$user = SystemUser::mk()->findOrEmpty($data['id']);
|
||||
if ($user->isExists() && $user->save(['password' => md5($data['password'])])) {
|
||||
// 修改密码同步事件处理
|
||||
$this->app->event->trigger('PluginAdminChangePassword', [
|
||||
'uuid' => $data['id'], 'pass' => $data['password'],
|
||||
]);
|
||||
sysoplog('系统用户管理', "修改用户[{$data['id']}]密码成功");
|
||||
$this->success('密码修改成功,请使用新密码登录!', '');
|
||||
} else {
|
||||
$this->error('密码修改失败,请稍候再试!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户状态
|
||||
* @auth true
|
||||
*/
|
||||
public function state()
|
||||
{
|
||||
$this->_checkInput();
|
||||
SystemUser::mSave($this->_vali([
|
||||
'status.in:0,1' => '状态值范围异常!',
|
||||
'status.require' => '状态值不能为空!',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除系统用户.
|
||||
* @auth true
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->_checkInput();
|
||||
SystemUser::mDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单数据处理.
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
protected function _form_filter(array &$data)
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
// 检查资料是否完整
|
||||
empty($data['username']) && $this->error('登录账号不能为空!');
|
||||
if ($data['username'] !== AdminService::getSuperName()) {
|
||||
empty($data['authorize']) && $this->error('未配置权限!');
|
||||
}
|
||||
// 处理上传的权限格式
|
||||
$data['authorize'] = arr2str($data['authorize'] ?? []);
|
||||
if (empty($data['id'])) {
|
||||
// 检查账号是否重复
|
||||
$map = ['username' => $data['username'], 'is_deleted' => 0];
|
||||
if (SystemUser::mk()->where($map)->count() > 0) {
|
||||
$this->error('账号已经存在,请使用其它账号!');
|
||||
}
|
||||
// 新添加的用户密码与账号相同
|
||||
$data['password'] = md5($data['username']);
|
||||
} else {
|
||||
unset($data['username']);
|
||||
}
|
||||
} else {
|
||||
// 权限绑定处理
|
||||
$data['authorize'] = str2arr($data['authorize'] ?? '');
|
||||
$this->auths = SystemAuth::items();
|
||||
$this->bases = SystemBase::items('身份权限');
|
||||
$this->super = AdminService::getSuperName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查输入变量.
|
||||
*/
|
||||
private function _checkInput()
|
||||
{
|
||||
if (in_array('10000', str2arr(input('id', '')))) {
|
||||
$this->error('系统超级账号禁止删除!');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\admin\controller\api;
|
||||
|
||||
use Psr\Log\NullLogger;
|
||||
use think\admin\Controller;
|
||||
use think\admin\model\SystemQueue;
|
||||
use think\admin\service\AdminService;
|
||||
use think\exception\HttpResponseException;
|
||||
|
||||
/**
|
||||
* 任务监听服务管理.
|
||||
* @class Queue
|
||||
*/
|
||||
class Queue extends Controller
|
||||
{
|
||||
/**
|
||||
* 停止监听服务
|
||||
* @login true
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
if (AdminService::isSuper()) {
|
||||
try {
|
||||
$message = $this->app->console->call('xadmin:queue', ['stop'])->fetch();
|
||||
if (stripos($message, 'sent end signal to process')) {
|
||||
sysoplog('系统运维管理', '尝试停止任务监听服务');
|
||||
$this->success('停止任务监听服务成功!');
|
||||
} elseif (stripos($message, 'processes to stop')) {
|
||||
$this->success('没有找到需要停止的服务!');
|
||||
} else {
|
||||
$this->error(nl2br($message));
|
||||
}
|
||||
} catch (HttpResponseException $exception) {
|
||||
throw $exception;
|
||||
} catch (\Exception $exception) {
|
||||
trace_file($exception);
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
} else {
|
||||
$this->error('请使用超管账号操作!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动监听服务
|
||||
* @login true
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
if (AdminService::isSuper()) {
|
||||
try {
|
||||
$message = $this->app->console->call('xadmin:queue', ['start'])->fetch();
|
||||
if (stripos($message, 'daemons started successfully for pid')) {
|
||||
sysoplog('系统运维管理', '尝试启动任务监听服务');
|
||||
$this->success('任务监听服务启动成功!');
|
||||
} elseif (stripos($message, 'daemons already exist for pid')) {
|
||||
$this->success('任务监听服务已经启动!');
|
||||
} else {
|
||||
$this->error(nl2br($message));
|
||||
}
|
||||
} catch (HttpResponseException $exception) {
|
||||
throw $exception;
|
||||
} catch (\Exception $exception) {
|
||||
trace_file($exception);
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
} else {
|
||||
$this->error('请使用超管账号操作!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查监听服务
|
||||
* @login true
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
if (AdminService::isSuper()) {
|
||||
try {
|
||||
$message = $this->app->console->call('xadmin:queue', ['status'])->fetch();
|
||||
if (preg_match('/process.*?\d+.*?running/', $message)) {
|
||||
echo "<span class='color-green pointer' data-tips-text='{$message}'>{$this->app->lang->get('已启动')}</span>";
|
||||
} else {
|
||||
echo "<span class='color-red pointer' data-tips-text='{$message}'>{$this->app->lang->get('未启动')}</span>";
|
||||
}
|
||||
} catch (\Error|\Exception $exception) {
|
||||
echo "<span class='color-red pointer' data-tips-text='{$exception->getMessage()}'>{$this->app->lang->get('异 常')}</span>";
|
||||
}
|
||||
} else {
|
||||
$message = lang('只有超级管理员才能操作!');
|
||||
echo "<span class='color-red pointer' data-tips-text='{$message}'>{$this->app->lang->get('无权限')}</span>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务进度.
|
||||
* @login true
|
||||
*/
|
||||
public function progress()
|
||||
{
|
||||
$input = $this->_vali(['code.require' => '任务编号不能为空!']);
|
||||
$this->app->db->setLog(new NullLogger()); /* 关闭数据库请求日志 */
|
||||
$message = SystemQueue::mk()->where($input)->value('message', '');
|
||||
$this->success('获取任务进度成功d!', json_decode($message, true));
|
||||
}
|
||||
}
|
||||
@ -1,396 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
$extra = [];
|
||||
$extra['开发人员或在功能调试时使用,系统异常时会显示详细的错误信息,同时还会记录操作日志及数据库 SQL 语句信息。'] = 'Developers may use it during functional debugging. When there are system exceptions, detailed error messages will be displayed, and operation logs and database SQL statement information will also be recorded.';
|
||||
$extra['项目正式部署上线后使用,系统异常时统一显示 “%s”,只记录重要的异常日志信息,强烈推荐上线后使用此模式。'] = 'After the project is officially deployed and launched, it will be used. When there are system exceptions, " %s " will be displayed uniformly, and only important exception log information will be recorded. It is strongly recommended to use this mode after launch.';
|
||||
$extra['旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。'] = 'The old version of the editor is compatible with browsers, but the content editing experience is slightly insufficient.';
|
||||
$extra['新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。'] = 'The new version of the editor only supports the new feature browser and has a good experience in content editing. It is recommended to use it.';
|
||||
$extra['国产优质富文本编辑器,对于小程序及App内容支持会更友好,推荐使用。'] = 'A high-quality Chinese rich text editor that provides better support for mini-programs and App content. Recommended for use.';
|
||||
$extra['优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。'] = 'Priority should be given to using the new version of the editor. If the browser does not support the new version, it will automatically be downgraded to the old version of the editor.';
|
||||
$extra['文件上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。'] = 'Uploading files to the `static/upload` directory of the local server does not support uploading large files, occupying server disk space, and consuming server bandwidth traffic during access.';
|
||||
$extra['文件上传到 Alist 存储的服务器或云存储空间,根据服务配置可支持大文件上传,不占用本身服务器空间及服务器带宽流量。'] = 'Files can be uploaded to the Alist storage server or Cloud storage space. According to the service configuration, large file upload can be supported without occupying the server space and server bandwidth traffic.';
|
||||
$extra['文件上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。'] = 'Files can be uploaded to Qiniu Cloud storage space. It supports large file upload, does not occupy server space and server bandwidth traffic, and supports CDN accelerated access. It is recommended when there is a large amount of access.';
|
||||
$extra['文件上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。'] = "Uploading files to Upyun Cloud's USS storage space supports large file uploads without occupying server space or bandwidth traffic. It supports CDN accelerated access and is recommended for high traffic.";
|
||||
$extra['文件上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。'] = "Uploading files to Aliyun Cloud's OSS storage space supports large file uploads without occupying server space or bandwidth traffic. It supports CDN accelerated access and is recommended for high traffic.";
|
||||
$extra['文件上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。'] = "Uploading files to Tencent Cloud's COS storage space supports large file uploads without occupying server space or bandwidth traffic. It supports CDN accelerated access and is recommended for high traffic.";
|
||||
$extra['网站名称及网站图标,将显示在浏览器的标签上。'] = "The website name and icon will be displayed on the browser's label.";
|
||||
$extra['管理程序名称,将显示在后台左上角标题。'] = 'The name of the management program will be displayed in the header in the upper left corner of the background.';
|
||||
$extra['管理程序版本,将显示在后台左上角标题。'] = 'The management program version will be displayed in the top left corner of the background with a title.';
|
||||
$extra['网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统。'] = 'Website copyright information is displayed on the backend login page and linked to the information filing management system.';
|
||||
$extra['网站备案号,可以在 %s 查询获取,将显示在登录页面下面。'] = 'The website registration number can be found at %s and will be displayed below the login page.';
|
||||
$extra['公安备案号,可以在 %s 查询获取,将在登录页面下面显示。'] = 'The public security registration number can be obtained by searching at %s and will be displayed below the login page.';
|
||||
$extra['点击可复制【服务启动指令】'] = "Click to copy the 'Service Start Command'";
|
||||
$extra['待处理 %s 个任务,处理中 %s 个任务,已完成 %s 个任务,已失败 %s 个任务。'] = 'There are %s tasks to be processed, %s tasks in progress, %s tasks completed, and %s tasks failed.';
|
||||
$extra['确定要切换到生产模式运行吗?'] = 'Are you sure you want to switch to Production mode?';
|
||||
$extra['确定要切换到开发模式运行吗?'] = 'Are you sure you want to switch to Development mode?';
|
||||
$extra["超级管理员账号的密码未修改,建议立即<a data-modal='%s'>修改密码</a>!"] = "The super administrator password has not been changed. Suggest <a data-modal='%s'>changing password</a>.";
|
||||
|
||||
$extra['等待处理'] = 'Pending';
|
||||
$extra['正在处理'] = 'Processing';
|
||||
$extra['处理完成'] = 'Completed';
|
||||
$extra['处理失败'] = 'Failed';
|
||||
|
||||
$extra['条件搜索'] = 'Search';
|
||||
$extra['批量删除'] = 'Batch Delete';
|
||||
|
||||
$extra['上传进度 %s'] = 'Upload progress %s';
|
||||
$extra['文件上传出错!'] = 'File upload error.';
|
||||
$extra['文件上传失败!'] = 'File upload failed.';
|
||||
$extra['大小超出限制!'] = 'Size exceeds limit.';
|
||||
$extra['文件秒传成功!'] = 'Successfully transmitted the file in seconds.';
|
||||
$extra['上传接口异常!'] = 'Abnormal upload interface.';
|
||||
$extra['文件上传成功!'] = 'File uploaded successfully.';
|
||||
$extra['图片压缩失败!'] = 'Image compression failed.';
|
||||
$extra['无效的文件上传对象!'] = 'Invalid file upload object.';
|
||||
|
||||
return array_merge($extra, [
|
||||
// 系统操作
|
||||
'基本资料' => 'Basic information',
|
||||
'安全设置' => 'Security setting',
|
||||
'缓存加速' => 'Cache acceleration',
|
||||
'清理缓存' => 'Clean cache',
|
||||
'配色方案' => 'Color scheme',
|
||||
'立即登录' => 'Login',
|
||||
'退出登录' => 'Logout',
|
||||
'系统提示:' => 'System Notify: ',
|
||||
'清空日志缓存成功!' => 'Successfully cleared the log cache.',
|
||||
'获取任务进度成功!' => 'Successfully obtained task progress.',
|
||||
'网站缓存加速成功!' => 'Website cache acceleration successful.',
|
||||
'请使用超管账号操作!' => 'Please use a super managed account to operate.',
|
||||
'停止任务监听服务成功!' => 'Successfully stopped task listening service.',
|
||||
'任务监听服务启动成功!' => 'Task monitoring service started successfully.',
|
||||
'任务监听服务已经启动!' => 'The task monitoring service has started.',
|
||||
'没有找到需要停止的服务!' => 'No services found that need to be stopped.',
|
||||
'已切换后台编辑器!' => 'Switched to background editor.',
|
||||
// 其他搜索器提示
|
||||
'请选择登录时间' => 'Please select the Login time',
|
||||
'请选择创建时间' => 'Please select the creation time',
|
||||
'请输入账号或名称' => 'Please enter an account or name',
|
||||
'请输入权限名称' => 'Please enter the permission name',
|
||||
'请输入数据编码' => 'Please enter the data code',
|
||||
'请输入数据名称' => 'Please enter the data name',
|
||||
'请输入文件名称' => 'Please enter the file name',
|
||||
'请输入文件哈希' => 'Please enter the file hash',
|
||||
'请输入操作节点' => 'Please enter the operate node',
|
||||
'请输入操作内容' => 'Please enter the operate content',
|
||||
'请输入访问地址' => 'Please enter the access Geoip',
|
||||
// 系统配置
|
||||
'运行模式' => 'Running Mode',
|
||||
'生产模式' => 'Production mode',
|
||||
'开发模式' => 'Development mode',
|
||||
'以开发模式运行' => 'Running in Development mode',
|
||||
'以生产模式运行' => 'Running in Production mode',
|
||||
'清理无效配置' => 'Clean up Invalid Configurations',
|
||||
'修改系统参数' => 'Modify System Parameters',
|
||||
'清理系统配置成功!' => 'Successfully cleaned.',
|
||||
'自适应模式' => 'Adaptive Mode',
|
||||
'富编辑器' => 'RichText Editor',
|
||||
'存储引擎' => 'Storage Engine',
|
||||
'系统参数' => 'System Parameter',
|
||||
'网站名称' => 'Site Name',
|
||||
'管理程序名称' => 'Program Name',
|
||||
'管理程序版本' => 'Program Version',
|
||||
'公安备案号' => 'Public security registration number',
|
||||
'网站备案号' => 'Website registration number',
|
||||
'网站版权信息' => 'Website copyright information',
|
||||
'系统信息' => 'System Information',
|
||||
'应用插件' => 'Plugin Information',
|
||||
'核心框架' => 'Core Framework',
|
||||
'平台框架' => 'Platform Framework',
|
||||
'操作系统' => 'Operating System',
|
||||
'运行环境' => 'Runtime Environment',
|
||||
'仅开发模式可见' => 'Visible only in Development mode',
|
||||
'仅生产模式可见' => 'Visible only in Production mode',
|
||||
'插件名称' => 'Plugin Name',
|
||||
'应用名称' => 'App Name',
|
||||
'插件包名' => 'Package Name',
|
||||
'插件版本' => 'Plugin Version',
|
||||
'授权协议' => 'License',
|
||||
'文件默认存储方式' => 'Default storage method for file upload',
|
||||
'当前系统配置参数' => 'Current system configuration parameters',
|
||||
'仅超级管理员可配置' => 'Only super administrators can configure',
|
||||
|
||||
// 系统任务管理
|
||||
'优化数据库' => 'Optimize Database',
|
||||
'开启服务' => 'Start Service',
|
||||
'关闭服务' => 'Shutdown Service',
|
||||
'定时清理' => 'Regular cleaning',
|
||||
'服务状态' => 'Service',
|
||||
'任务统计' => 'Total',
|
||||
'编号名称' => 'Name',
|
||||
'任务指令' => 'Command',
|
||||
'任务状态' => 'Status',
|
||||
'计划时间' => 'scheduled time',
|
||||
'任务名称' => 'Name',
|
||||
'检查中' => 'Checking',
|
||||
'任务计划' => 'Scheduled',
|
||||
'重 置' => 'Reset',
|
||||
'日 志' => 'Logs',
|
||||
'异 常' => 'Abnormal',
|
||||
'无权限' => 'Denied',
|
||||
'已启动' => 'Started',
|
||||
'未启动' => 'Stopped',
|
||||
// 数据字典管理
|
||||
'数据编码' => 'Code',
|
||||
'数据名称' => 'Name',
|
||||
'操作账号' => 'User',
|
||||
'操作节点' => 'Node',
|
||||
'操作行为' => 'Action',
|
||||
'操作内容' => 'Content',
|
||||
'访问地址' => 'Geo IP',
|
||||
'网络服务商' => 'ISP.',
|
||||
'日志清理成功!' => 'Logger Clear Complate.',
|
||||
'成功清理所有日志' => 'Successfully cleared all logs.',
|
||||
// 系统文件管理
|
||||
'文件名称' => 'Name',
|
||||
'文件哈希' => 'HASH',
|
||||
'文件大小' => 'Size',
|
||||
'文件后缀' => 'Exts',
|
||||
'存储方式' => 'Storage Type',
|
||||
'清理重复' => 'Clear Replace',
|
||||
'上传方式' => 'Upload Type',
|
||||
'查看文件' => 'View',
|
||||
'文件链接' => 'Link',
|
||||
'秒传' => 'Speedy',
|
||||
'普通' => 'Normal',
|
||||
// 系统菜单管理
|
||||
'图 标' => 'Icon',
|
||||
'添加菜单' => 'Add',
|
||||
'禁用菜单' => 'Forbid',
|
||||
'激活菜单' => 'Resume',
|
||||
'系统菜单' => 'Menus',
|
||||
'菜单名称' => 'Name',
|
||||
'跳转链接' => 'Link',
|
||||
'上级菜单' => 'Parent',
|
||||
'菜单链接' => 'Link',
|
||||
'链接参数' => 'Params',
|
||||
'权限节点' => 'Node',
|
||||
'菜单图标' => 'Icon',
|
||||
'选择图标' => 'Select Icon',
|
||||
// 系统权限管理
|
||||
'授 权' => 'Auth',
|
||||
'添加权限' => 'Add',
|
||||
'权限名称' => 'Name',
|
||||
'权限描述' => 'Description',
|
||||
'请输入权限描述' => 'Please enter a permission description',
|
||||
// 系统用户管理
|
||||
'账号名称' => 'Username',
|
||||
'添加用户' => 'Add User',
|
||||
'最后登录' => 'Last Login Time',
|
||||
'头像' => 'Head',
|
||||
'登录账号' => 'Username',
|
||||
'用户名称' => 'Nickname',
|
||||
'登录次数' => 'Login Times',
|
||||
'系统用户' => 'System User',
|
||||
'密 码' => 'Password',
|
||||
'系统用户管理' => 'Users',
|
||||
|
||||
// 通用操作
|
||||
'回 收 站' => 'Recycle Bin',
|
||||
'排序权重' => 'Sort Weight',
|
||||
'使用状态' => 'Status',
|
||||
'操作面板' => 'Actions',
|
||||
'已激活' => 'Activated',
|
||||
'已禁用' => 'Disabled',
|
||||
'已启用' => 'Enabled',
|
||||
'添 加' => 'Add',
|
||||
'编 辑' => 'Edit',
|
||||
'删 除' => 'Delete',
|
||||
'全部' => 'All',
|
||||
'搜 索' => 'Search',
|
||||
'导 出' => 'Export',
|
||||
'保存数据' => 'Save Data',
|
||||
'取消编辑' => 'Cancel Edit',
|
||||
'操作日志' => 'Operation Log',
|
||||
'创建时间' => 'Create Time',
|
||||
|
||||
// 用户管理
|
||||
'批量禁用' => 'Batch Disable',
|
||||
'批量恢复' => 'Batch Restore',
|
||||
'编辑用户' => 'Edit User',
|
||||
'设置密码' => 'Set Password',
|
||||
'角色身份' => 'Role Identity',
|
||||
'全部菜单' => 'All Menus',
|
||||
|
||||
// 权限管理
|
||||
'确定要批量删除权限吗?' => 'Are you sure you want to batch delete permissions?',
|
||||
'确定要删除权限吗?' => 'Are you sure you want to delete the permission?',
|
||||
'功能节点' => 'Function Node',
|
||||
'访问权限名称需要保持不重复,在给用户授权时需要根据名称选择!' => 'Access permission names must be unique. When authorizing users, select based on the name!',
|
||||
'已禁用记录' => 'Disabled Records',
|
||||
'已激活记录' => 'Activated Records',
|
||||
|
||||
// 菜单管理
|
||||
'确定要删除菜单吗?' => 'Are you sure you want to delete the menu?',
|
||||
'添加系统菜单' => 'Add System Menu',
|
||||
'编辑系统菜单' => 'Edit System Menu',
|
||||
|
||||
// 文件管理
|
||||
'确定删除这些记录吗?' => 'Are you sure you want to delete these records?',
|
||||
'播放视频' => 'Play Video',
|
||||
'播放音频' => 'Play Audio',
|
||||
'查看下载' => 'View/Download',
|
||||
'编辑文件信息' => 'Edit File Info',
|
||||
|
||||
// 任务管理
|
||||
'确定批量删除记录吗?' => 'Are you sure you want to batch delete records?',
|
||||
'确定要重置该任务吗?' => 'Are you sure you want to reset this task?',
|
||||
'确定要删除该记录吗?' => 'Are you sure you want to delete this record?',
|
||||
'请选择计划时间' => 'Please select scheduled time',
|
||||
'请输入名称或编号' => 'Please enter name or code',
|
||||
'请输入任务指令' => 'Please enter task command',
|
||||
|
||||
// 确认提示
|
||||
'确定要永久删除吗?' => 'Are you sure you want to permanently delete?',
|
||||
'确定要取消编辑吗?' => 'Are you sure you want to cancel editing?',
|
||||
'确定要取消修改吗?' => 'Are you sure you want to cancel the modification?',
|
||||
'确定要禁用这些用户吗?' => 'Are you sure you want to disable these users?',
|
||||
'确定要恢复这些账号吗?' => 'Are you sure you want to restore these accounts?',
|
||||
'确定永久删除这些账号吗?' => 'Are you sure you want to permanently delete these accounts?',
|
||||
|
||||
// 系统提示
|
||||
'新增类型' => 'New Type',
|
||||
'只有超级管理员才能操作!' => 'Only super administrators can operate!',
|
||||
'日志清理失败,%s' => 'Log cleanup failed, %s',
|
||||
'已存在 %s 应用!' => 'Application %s already exists!',
|
||||
|
||||
// 演示环境提示
|
||||
'演示环境禁止修改用户密码!' => 'Demo environment prohibits modifying user passwords!',
|
||||
'演示环境禁止修改系统配置!' => 'Demo environment prohibits modifying system configuration!',
|
||||
'演示环境禁止给菜单排序!' => 'Demo environment prohibits menu sorting!',
|
||||
'演示环境禁止添加菜单!' => 'Demo environment prohibits adding menus!',
|
||||
'演示环境禁止编辑菜单!' => 'Demo environment prohibits editing menus!',
|
||||
'演示环境禁止禁用菜单!' => 'Demo environment prohibits disabling menus!',
|
||||
'演示环境禁止删除菜单!' => 'Demo environment prohibits deleting menus!',
|
||||
'演示环境禁止修改密码!' => 'Demo environment prohibits modifying passwords!',
|
||||
|
||||
// 表单字段
|
||||
'用户账号' => 'User Account',
|
||||
'用户权限' => 'User Permissions',
|
||||
'用户资料' => 'User Profile',
|
||||
'登录账号' => 'Login Account',
|
||||
'用户名称' => 'User Name',
|
||||
'角色身份' => 'Role Identity',
|
||||
'访问权限' => 'Access Permissions',
|
||||
'联系邮箱' => 'Contact Email',
|
||||
'联系手机' => 'Contact Mobile',
|
||||
'联系QQ' => 'Contact QQ',
|
||||
'用户描述' => 'User Description',
|
||||
'登录用户账号' => 'Login Username',
|
||||
'旧的登录密码' => 'Old Password',
|
||||
'新的登录密码' => 'New Password',
|
||||
'重复登录密码' => 'Repeat Password',
|
||||
'验证密码' => 'Verify Password',
|
||||
'登录密码' => 'Login Password',
|
||||
'重复密码' => 'Repeat Password',
|
||||
|
||||
// 表单提示
|
||||
'登录账号不能少于4位字符,创建后不能再次修改.' => 'Login account must be at least 4 characters and cannot be modified after creation.',
|
||||
'用于区分用户数据的用户名称,请尽量不要重复.' => 'User name used to distinguish user data, please try not to duplicate.',
|
||||
'超级用户拥所有访问权限,不需要配置权限。' => 'Super users have all access permissions and do not need to configure permissions.',
|
||||
'可选,请填写用户常用的电子邮箱' => 'Optional, please fill in the user\'s commonly used email address',
|
||||
'可选,请填写用户常用的联系手机号' => 'Optional, please fill in the user\'s commonly used mobile phone number',
|
||||
'可选,请填写用户常用的联系QQ号' => 'Optional, please fill in the user\'s commonly used QQ number',
|
||||
'请输入用户描述' => 'Please enter user description',
|
||||
'登录用户账号创建后,不允许再次修改。' => 'Login username cannot be modified after creation.',
|
||||
'请输入旧密码来验证修改权限,旧密码不限制格式。' => 'Please enter the old password to verify modification permission. The old password format is not restricted.',
|
||||
'密码必须包含大小写字母、数字、符号的任意两者组合。' => 'Password must contain any combination of uppercase letters, lowercase letters, numbers, and symbols.',
|
||||
'请重复输入登录密码' => 'Please repeat the login password',
|
||||
|
||||
// 系统配置表单
|
||||
'登录表单标题' => 'Login Form Title',
|
||||
'后台登录入口' => 'Backend Login Entry',
|
||||
'后台默认配色' => 'Backend Default Theme',
|
||||
'登录背景图片' => 'Login Background Image',
|
||||
'JWT 接口密钥' => 'JWT API Key',
|
||||
'浏览器小图标' => 'Browser Icon',
|
||||
'后台程序名称' => 'Backend Program Name',
|
||||
'后台程序版本' => 'Backend Program Version',
|
||||
'公安安备号' => 'Public Security Registration Number',
|
||||
'保存配置' => 'Save Configuration',
|
||||
'取消修改' => 'Cancel Modification',
|
||||
'登录标题' => 'Login Title',
|
||||
'登录入口' => 'Login Entry',
|
||||
'接口密钥' => 'API Key',
|
||||
'图标文件' => 'Icon File',
|
||||
'程序名称' => 'Program Name',
|
||||
'版权信息' => 'Copyright Information',
|
||||
|
||||
// 菜单表单提示
|
||||
'必选' => 'Required',
|
||||
'可选' => 'Optional',
|
||||
'请选择上级菜单或顶级菜单 ( 目前最多支持三级菜单 )' => 'Please select parent menu or top-level menu (currently supports up to 3 levels)',
|
||||
'请填写菜单名称 ( 如:系统管理 ),建议字符不要太长,一般 4-6 个汉字' => 'Please fill in the menu name (e.g., System Management), it is recommended not to be too long, generally 4-6 Chinese characters',
|
||||
'请填写链接地址或选择系统节点 ( 如:https://domain.com/admin/user/index.html 或 admin/user/index )' => 'Please fill in the link address or select a system node (e.g., https://domain.com/admin/user/index.html or admin/user/index)',
|
||||
'当填写链接地址时,以下面的 "权限节点" 来判断菜单自动隐藏或显示,注意未填写 "权限节点" 时将不会隐藏该菜单哦' => 'When filling in the link address, use the "Permission Node" below to determine whether the menu is automatically hidden or displayed. Note that if the "Permission Node" is not filled in, the menu will not be hidden',
|
||||
'设置菜单链接的 GET 访问参数 ( 如:name=1&age=3 )' => 'Set GET access parameters for menu links (e.g., name=1&age=3)',
|
||||
'请填写系统权限节点 ( 如:admin/user/index ),未填写时默认解释"菜单链接"判断是否拥有访问权限;' => 'Please fill in the system permission node (e.g., admin/user/index). If not filled in, the "Menu Link" will be used by default to determine access permissions',
|
||||
'设置菜单选项前置图标,目前支持 layui 字体图标及 iconfont 定制字体图标。' => 'Set the prefix icon for menu options. Currently supports layui font icons and iconfont custom font icons.',
|
||||
'请输入或选择图标' => 'Please enter or select an icon',
|
||||
'请输入菜单名称' => 'Please enter menu name',
|
||||
'请输入菜单链接' => 'Please enter menu link',
|
||||
'请输入链接参数' => 'Please enter link parameters',
|
||||
'请输入权限节点' => 'Please enter permission node',
|
||||
'请输入登录账号' => 'Please enter login account',
|
||||
'请输入用户名称' => 'Please enter user name',
|
||||
'请输入联系电子邮箱' => 'Please enter contact email',
|
||||
'请输入用户联系手机' => 'Please enter user contact mobile',
|
||||
'请输入常用的联系QQ' => 'Please enter commonly used contact QQ',
|
||||
'请输入旧的登录密码' => 'Please enter old login password',
|
||||
'请输入新的登录密码' => 'Please enter new login password',
|
||||
|
||||
// 系统配置表单提示
|
||||
'请输入登录页面的表单标题' => 'Please enter login form title',
|
||||
'后台登录入口是由英文字母开头,且不能有相同名称的模块,设置之后原地址不能继续访问,请谨慎配置 ~' => 'Backend login entry must start with English letters and cannot have modules with the same name. After setting, the original address cannot be accessed. Please configure carefully.',
|
||||
'请输入32位JWT接口密钥' => 'Please enter 32-bit JWT API key',
|
||||
'请输入 32 位 JWT 接口密钥,在使用 JWT 接口时需要使用此密钥进行加密及签名!' => 'Please enter a 32-bit JWT API key. This key is required for encryption and signing when using JWT interfaces!',
|
||||
'请上传浏览器图标' => 'Please upload browser icon',
|
||||
'建议上传 128x128 或 256x256 的 JPG,PNG,JPEG 图片,保存后会自动生成 48x48 的 ICO 文件 ~' => 'It is recommended to upload JPG, PNG, or JPEG images of 128x128 or 256x256. After saving, a 48x48 ICO file will be automatically generated.',
|
||||
'请输入网站名称' => 'Please enter site name',
|
||||
'网站名称将显示在浏览器的标签上 ~' => 'Site name will be displayed on the browser tab.',
|
||||
'请输入程序名称' => 'Please enter program name',
|
||||
'管理程序名称显示在后台左上标题处 ~' => 'Management program name is displayed in the top left title of the backend.',
|
||||
'请输入程序版本' => 'Please enter program version',
|
||||
'管理程序版本显示在后台左上标题处 ~' => 'Management program version is displayed in the top left title of the backend.',
|
||||
'请输入公安安备号' => 'Please enter public security registration number',
|
||||
'请输入网站备案号' => 'Please enter website registration number',
|
||||
'请输入版权信息' => 'Please enter copyright information',
|
||||
'网站备案号和公安备案号可以在<a target="_blank" href="https://beian.miit.gov.cn">备案管理中心</a>查询并获取,网站上线时必需配置备案号,备案号会链接到信息备案管理系统 ~' => 'Website registration number and public security registration number can be queried and obtained at the <a target="_blank" href="https://beian.miit.gov.cn">Registration Management Center</a>. Registration numbers must be configured when the website goes online, and will be linked to the information registration management system.',
|
||||
|
||||
// 数据字典表单提示
|
||||
'数据类型' => 'Data Type',
|
||||
'请选择数据类型,数据创建后不能再次修改哦 ~' => 'Please select data type. Data type cannot be modified after creation.',
|
||||
'请输入数据类型' => 'Please enter data type',
|
||||
'请输入新的数据类型,数据创建后不能再次修改哦 ~' => 'Please enter new data type. Data type cannot be modified after creation.',
|
||||
'数据编码' => 'Data Code',
|
||||
'请输入新的数据编码,数据创建后不能再次修改,同种数据类型的数据编码不能出现重复 ~' => 'Please enter new data code. Data code cannot be modified after creation, and duplicate codes are not allowed for the same data type.',
|
||||
'数据名称' => 'Data Name',
|
||||
'请输入当前数据名称,请尽量保持名称的唯一性,数据名称尽量不要出现重复 ~' => 'Please enter data name. Try to keep the name unique and avoid duplicates.',
|
||||
'数据内容' => 'Data Content',
|
||||
'请输入数据内容' => 'Please enter data content',
|
||||
|
||||
// 数据字典列表
|
||||
'添加数据' => 'Add Data',
|
||||
'确定要批量删除数据吗?' => 'Are you sure you want to batch delete data?',
|
||||
'数据状态' => 'Data Status',
|
||||
'数据操作' => 'Actions',
|
||||
'编辑数据' => 'Edit Data',
|
||||
'确定要删除数据吗?' => 'Are you sure you want to delete data?',
|
||||
]);
|
||||
@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
use think\admin\Library;
|
||||
use think\admin\service\RuntimeService;
|
||||
|
||||
/* ! 演示环境禁止操作路由绑定 */
|
||||
if (RuntimeService::check('demo')) {
|
||||
Library::$sapp->route->post('index/pass', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止修改用户密码!')]);
|
||||
});
|
||||
Library::$sapp->route->post('config/system', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止修改系统配置!')]);
|
||||
});
|
||||
Library::$sapp->route->post('config/storage', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止修改系统配置!')]);
|
||||
});
|
||||
Library::$sapp->route->post('menu', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止给菜单排序!')]);
|
||||
});
|
||||
Library::$sapp->route->post('menu/index', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止给菜单排序!')]);
|
||||
});
|
||||
Library::$sapp->route->post('menu/add', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止添加菜单!')]);
|
||||
});
|
||||
Library::$sapp->route->post('menu/edit', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止编辑菜单!')]);
|
||||
});
|
||||
Library::$sapp->route->post('menu/state', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止禁用菜单!')]);
|
||||
});
|
||||
Library::$sapp->route->post('menu/remove', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止删除菜单!')]);
|
||||
});
|
||||
Library::$sapp->route->post('user/pass', static function () {
|
||||
return json(['code' => 0, 'info' => lang('演示环境禁止修改密码!')]);
|
||||
});
|
||||
}
|
||||
@ -1,134 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{block name="title"}{$title|default=''}{if !empty($title)} · {/if}{:sysconf('site_name')}{/block}</title>
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
|
||||
<link rel="stylesheet" href="__ROOT__/static/theme/css/iconfont.css?at={:date('md')}">
|
||||
<link rel="stylesheet" href="__ROOT__/static/plugs/layui/css/layui.css?v={:date('ymd')}">
|
||||
{if file_exists(syspath("public/static/extra/icon/iconfont.css"))}
|
||||
<link rel="stylesheet" href="__ROOT__/static/extra/icon/iconfont.css?at={:date('md')}">
|
||||
{/if}
|
||||
<style>
|
||||
::-webkit-input-placeholder {
|
||||
color: #aaa
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #ccc
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #666
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #ec494e;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background-color: #ec494e;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
:-webkit-autofill {
|
||||
-webkit-box-shadow: 0 0 0 1000px white inset;
|
||||
-webkit-text-fill-color: #333
|
||||
}
|
||||
|
||||
ul li {
|
||||
width: 20%;
|
||||
height: 65px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: -1px;
|
||||
margin-left: -2px;
|
||||
margin-bottom: -2px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
padding: 15px 0 10px 0;
|
||||
border: 1px solid #e2e2e2;
|
||||
background-color: #efefef;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
transition: all .2s linear;
|
||||
-o-transition: all .2s linear;
|
||||
-moz-transition: all .2s linear;
|
||||
-webkit-transition: all .2s linear
|
||||
}
|
||||
|
||||
ul li:hover {
|
||||
color: #fff;
|
||||
background-color: #563d7c
|
||||
}
|
||||
|
||||
ul li:hover i, ul li:hover div {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
ul li i {
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
font-size: 30px !important
|
||||
}
|
||||
|
||||
ul li div {
|
||||
color: #333;
|
||||
height: 35px;
|
||||
font-size: 13px;
|
||||
line-height: 35px;
|
||||
white-space: nowrap
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<ul>
|
||||
{foreach $extraIcons??[] as $icon}
|
||||
<li>
|
||||
<i class="iconfont {$icon}"></i>
|
||||
<div class="icon-title">{$icon}</div>
|
||||
</li>
|
||||
{/foreach}
|
||||
{foreach $layuiIcons??[] as $icon}
|
||||
<li>
|
||||
<i class="layui-icon {$icon}"></i>
|
||||
<div class="icon-title">{$icon}</div>
|
||||
</li>
|
||||
{/foreach}
|
||||
{foreach $thinkIcons??[] as $icon}
|
||||
<li>
|
||||
<i class="iconfont {$icon}"></i>
|
||||
<div class="icon-title">{$icon}</div>
|
||||
</li>
|
||||
{/foreach}
|
||||
</ul>
|
||||
|
||||
<script src="__ROOT__/static/plugs/jquery/jquery.min.js" type="text/javascript"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$('li').on('click', function (className) {
|
||||
if ((className = $(this).find('i').get(0).className)) {
|
||||
top.$('[name="{$field}"]').val(className).trigger('change');
|
||||
top.layer.close(top.layer.getFrameIndex(window.name));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,161 +0,0 @@
|
||||
<div class="image-dialog" id="ImageDialog">
|
||||
<div class="image-dialog-head">
|
||||
<label class="pull-left flex">
|
||||
<input class="layui-input margin-right-5" v-model="keys" style="height:30px;line-height:30px" placeholder="{:lang('请输入搜索关键词')}">
|
||||
<a class="layui-btn layui-btn-sm layui-btn-normal" @click="search">{:lang('搜 索')}</a>
|
||||
</label>
|
||||
<div class="pull-right">
|
||||
<a class="layui-btn layui-btn-sm layui-btn-normal" @click="uploadImage">{:lang('上传图片')}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-dialog-body">
|
||||
<div class="image-dialog-item" v-for="x in list" @click="setItem(x)" style="display:none" v-show="show" :class="{'image-dialog-checked':x.checked}">
|
||||
<div class="uploadimage" :style="x.style"></div>
|
||||
<p class="image-dialog-item-name layui-elip" v-text="x.name"></p>
|
||||
<div class="image-dialog-item-tool">
|
||||
<span class="image-dialog-item-type">{{x.xext.toUpperCase()}}</span>
|
||||
<span class="image-dialog-item-size">{{formatSize(x.size)}}</span>
|
||||
{if auth('admin/file/remove')}
|
||||
<span class="layui-icon layui-icon-close image-dialog-item-close" @click.stop="remove(x)"></span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-dialog-foot">
|
||||
<div id="ImageDialogPage" class="image-dialog-page"></div>
|
||||
<div id="ImageDialogButton layui-hide" class="image-dialog-button layui-btn layui-btn-normal" v-if="data.length>0" @click="confirm">
|
||||
{php} $tag = '{{data.length}}'; {/php}
|
||||
{:lang('已选 %s 张,确认', [$tag])}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
require(['vue'], function (vue) {
|
||||
var app = new vue({
|
||||
el: '#ImageDialog',
|
||||
data: {
|
||||
didx: 0,
|
||||
page: 1, limit: 15, show: false, mult: false,
|
||||
keys: '', list: [], data: [], idxs: {}, urls: [],
|
||||
},
|
||||
created: function () {
|
||||
this.didx = $.msg.mdx.pop();
|
||||
this.$btn = $('#{$get.id|default=""}');
|
||||
this.$ups = $('#ImageDialogUploadLayout [data-file]');
|
||||
this.mult = "{$get.file|default='image'}" === 'images';
|
||||
this.loadPage(), setTimeout(function () {
|
||||
$('#ImageDialogButton').removeClass('layui-hide');
|
||||
}, 1000);
|
||||
},
|
||||
methods: {
|
||||
// 搜索刷新数据
|
||||
search: function () {
|
||||
this.page = 1;
|
||||
this.loadPage();
|
||||
},
|
||||
// 确认选择数据
|
||||
confirm: function () {
|
||||
this.urls = [];
|
||||
this.data.forEach(function (file) {
|
||||
app.setValue(file.xurl);
|
||||
});
|
||||
this.setInput();
|
||||
},
|
||||
// 删除指定的图片
|
||||
remove: function (x) {
|
||||
$.msg.confirm('确认要移除这张图片吗?', function () {
|
||||
$.form.load('{:url("admin/file/remove")}', {id: x.id}, 'POST', function (ret) {
|
||||
ret.code > 0 ? app.loadPage() : $.msg.error(ret.info);
|
||||
return app.$forceUpdate(), false;
|
||||
})
|
||||
})
|
||||
},
|
||||
// 格式文件大小
|
||||
formatSize: function (size) {
|
||||
return $.formatFileSize(size);
|
||||
},
|
||||
// 设置单项数据
|
||||
setItem: function (item) {
|
||||
if (!this.mult) {
|
||||
this.setValue(item.xurl).setInput();
|
||||
} else if ((item.checked = !this.idxs[item.id])) {
|
||||
(this.idxs[item.id] = item) && this.data.push(item);
|
||||
} else {
|
||||
delete this.idxs[item.id];
|
||||
this.data.forEach(function (temp, idx) {
|
||||
temp.id === item.id && app.data.splice(idx, 1);
|
||||
});
|
||||
}
|
||||
},
|
||||
// 更新列表数据
|
||||
setList: function (items, count) {
|
||||
this.list = items;
|
||||
this.list.forEach(function (item) {
|
||||
item.checked = !!app.idxs[item.id]
|
||||
item.style = 'background-image:url(' + item.xurl + ')';
|
||||
});
|
||||
this.addPage(count);
|
||||
},
|
||||
// 设置选择数据
|
||||
setValue: function (xurl) {
|
||||
$.msg.close(this.didx);
|
||||
this.urls.push(xurl) && this.$btn.triggerHandler('push', xurl);
|
||||
return this;
|
||||
},
|
||||
// 设置输入表单
|
||||
setInput: function () {
|
||||
if (this.$btn.data('input')) {
|
||||
$(this.$btn.data('input')).val(this.urls.join('|')).trigger('change');
|
||||
}
|
||||
},
|
||||
// 创建分页工具条
|
||||
addPage: function (count) {
|
||||
this.show = true;
|
||||
layui.laypage.render({
|
||||
curr: this.page, count: count, limit: app.limit,
|
||||
layout: ['count', 'prev', 'page', 'next', 'refresh'],
|
||||
elem: 'ImageDialogPage', jump: function (obj, first) {
|
||||
if (!first) app.loadPage(app.page = obj.curr);
|
||||
},
|
||||
});
|
||||
},
|
||||
// 加载页面数据
|
||||
loadPage: function () {
|
||||
this.params = {page: this.page, limit: this.limit, output: 'layui.table', name: this.keys || ''};
|
||||
this.params.type = '{$get.type|default="gif,png,jpg,jpeg"}';
|
||||
$.form.load('{:url("image",[],false,true)}', this.params, 'get', function (ret) {
|
||||
return app.setList(ret.data, ret.count), false;
|
||||
});
|
||||
},
|
||||
// 上传图片文件
|
||||
uploadImage: function () {
|
||||
this.urls = [];
|
||||
this.$ups.off('push').on('push', function (e, xurl) {
|
||||
app.setValue(xurl);
|
||||
}).off('upload.complete').on('upload.complete', function () {
|
||||
app.setInput();
|
||||
}).click();
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<label class="layui-hide" id="ImageDialogUploadLayout">
|
||||
<!-- 图片上传组件 开始 -->
|
||||
{if isset($get.file) && $get.file eq 'image'}
|
||||
<button data-file="one" data-type="{$get.type|default='gif,png,jpg,jpeg'}"
|
||||
data-path="{$get.path|default=''}" data-size="{$get.size|default=0}"
|
||||
data-cut-width="{$get.cutWidth|default=0}" data-cut-height="{$get.cutHeight|default=0}"
|
||||
data-max-width="{$get.maxWidth|default=0}" data-max-height="{$get.maxHeight|default=0}"
|
||||
></button>
|
||||
{else}
|
||||
<button data-file="mul" data-type="{$get.type|default='gif,png,jpg,jpeg'}"
|
||||
data-path="{$get.path|default=''}" data-size="{$get.size|default=0}"
|
||||
data-cut-width="{$get.cutWidth|default=0}" data-cut-height="{$get.cutHeight|default=0}"
|
||||
data-max-width="{$get.maxWidth|default=0}" data-max-height="{$get.maxHeight|default=0}"
|
||||
></button>
|
||||
{/if}
|
||||
<!-- 图片上传组件 结束 -->
|
||||
</label>
|
||||
@ -1,143 +0,0 @@
|
||||
{extend name='main'}
|
||||
|
||||
{block name="button"}
|
||||
<button data-target-submit class='layui-btn layui-btn-sm'>{:lang('保存数据')}</button>
|
||||
<button data-target-backup class="layui-btn layui-btn-sm layui-btn-danger">{:lang('取消编辑')}</button>
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<div class="think-box-shadow">
|
||||
<form method="post" id="RoleForm" class="layui-form layui-card">
|
||||
<div class="layui-card-body">
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('权限名称')}</b>Auth Name</span>
|
||||
<input maxlength="100" class="layui-input" name="title" value='{$vo.title|default=""}' required vali-name="{:lang('权限名称')}" placeholder="{:lang('请输入权限名称')}">
|
||||
<span class="help-block">{:lang('访问权限名称需要保持不重复,在给用户授权时需要根据名称选择!')}</span>
|
||||
</label>
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('权限描述')}</b>Auth Remark</span>
|
||||
<textarea placeholder="{:lang('请输入权限描述')}" maxlength="200" class="layui-textarea" name="desc">{$vo.desc|default=""}</textarea>
|
||||
</label>
|
||||
<div class="layui-form-item">
|
||||
<span class="help-label label-required-prev"><b>{:lang('功能节点')}</b>Auth Nodes</span>
|
||||
<ul id="zTree" class="ztree notselect"></ul>
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
{notempty name='vo.id'}<input name="id" value="{$vo.id}" type="hidden"/>{/notempty}
|
||||
<div class="layui-form-item text-center">
|
||||
<button data-target-submit class="layui-btn">{:lang('保存数据')}</button>
|
||||
<button data-target-backup class="layui-btn layui-btn-danger" type="button">{:lang('取消编辑')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block name="script"}
|
||||
<script>
|
||||
require(['jquery.ztree'], function () {
|
||||
new function () {
|
||||
let that = this;
|
||||
this.data = {}, this.ztree = null, this.setting = {
|
||||
view: {showLine: false, showIcon: false, dblClickExpand: false},
|
||||
check: {enable: true, nocheck: false, chkboxType: {"Y": "ps", "N": "ps"}}, callback: {
|
||||
beforeClick: function (id, node) {
|
||||
node.children.length < 1 ? that.ztree.checkNode(node, !node.checked, true, true) : that.ztree.expandNode(node);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.renderChildren = function (list, level) {
|
||||
let childrens = [];
|
||||
for (let i in list) childrens.push({
|
||||
open: true, node: list[i].node, name: list[i].title || list[i].node,
|
||||
checked: list[i].checked || false, children: this.renderChildren(list[i]._sub_, level + 1)
|
||||
});
|
||||
return childrens;
|
||||
};
|
||||
this.syncData = function () {
|
||||
$.form.load('{:sysuri()}', {id: '{$vo.id|default=0}', action: 'json'}, 'post', function (ret) {
|
||||
return (that.data = that.renderChildren(ret.data, 1)), that.showTree(), false;
|
||||
});
|
||||
};
|
||||
this.showTree = function () {
|
||||
this.ztree = $.fn.zTree.init($("#zTree"), this.setting, this.data);
|
||||
while (true) {
|
||||
let nodes = this.ztree.getNodesByFilter(function (node) {
|
||||
return (!node.node && node.children.length < 1);
|
||||
});
|
||||
if (nodes.length < 1) break;
|
||||
for (let i in nodes) this.ztree.removeNode(nodes[i]);
|
||||
}
|
||||
};
|
||||
// 刷新数据
|
||||
this.syncData();
|
||||
// 监听表单提交
|
||||
$('#RoleForm').vali(function (form) {
|
||||
let data = that.ztree.getCheckedNodes(true);
|
||||
Object.assign(form, {nodes: [], action: 'save'})
|
||||
for (let i in data) if (data[i].node) form.nodes.push(data[i].node);
|
||||
$.form.load('{:sysuri()}', form, 'post');
|
||||
});
|
||||
};
|
||||
});
|
||||
</script>
|
||||
{/block}
|
||||
|
||||
{block name="style"}
|
||||
<style>
|
||||
ul.ztree li {
|
||||
line-height: 24px;
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
ul.ztree li span.button.switch {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
ul.ztree ul ul li {
|
||||
display: inline-block;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
ul.ztree > li {
|
||||
border: 1px solid rgba(0, 0, 0, 0.10);
|
||||
padding: 15px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
ul.ztree > li > ul {
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
ul.ztree > li > ul > li {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
ul.ztree > li > a > span {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
ul.ztree .level2 .button.level2 {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
ul.ztree li span.button.noline_open {
|
||||
background-position-y: -73px;
|
||||
}
|
||||
|
||||
ul.ztree li span.button.noline_close {
|
||||
background-position-y: -73px;
|
||||
}
|
||||
|
||||
ul.ztree .level1 > .node_name {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
{/block}
|
||||
@ -1,76 +0,0 @@
|
||||
{extend name='table'}
|
||||
|
||||
{block name="button"}
|
||||
<!--{if auth("add")}-->
|
||||
<button data-open='{:url("add")}' data-table-id="RoleTable" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('添加权限')}</button>
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if auth("remove")}-->
|
||||
<button data-action='{:url("remove")}' data-rule="id#{id}" data-table-id="RoleTable" data-confirm="{:lang('确定要批量删除权限吗?')}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量删除')}</button>
|
||||
<!--{/if}-->
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<div class="think-box-shadow">
|
||||
{include file='auth/index_search'}
|
||||
<table id="RoleTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block name='script'}
|
||||
<script>
|
||||
$(function () {
|
||||
// 初始化表格组件
|
||||
$('#RoleTable').layTable({
|
||||
even: true, height: 'full',
|
||||
sort: {field: 'sort desc,id', type: 'desc'},
|
||||
cols: [[
|
||||
{checkbox: true, fixed: true},
|
||||
{field: 'sort', title: '{:lang("排序权重")}', align: 'center', width: 100, sort: true, templet: '#SortInputRoleTableTpl'},
|
||||
{field: 'title', title: '{:lang("权限名称")}', align: 'center', minWidth: 140},
|
||||
{field: 'desc', title: '{:lang("权限描述")}', align: 'center', minWidth: 110, templet: '<div>{{d.desc||"-"}}</div>'},
|
||||
{field: 'status', title: '{:lang("使用状态")}', align: 'center', minWidth: 110, templet: '#StatusSwitchRoleTableTpl'},
|
||||
{field: 'create_at', title: '{:lang("创建时间")}', align: 'center', minWidth: 170, sort: true},
|
||||
{toolbar: '#ToolbarRoleTableTpl', title: '{:lang("操作面板")}', align: 'center', minWidth: 210, fixed: 'right'},
|
||||
]]
|
||||
});
|
||||
|
||||
// 数据状态切换操作
|
||||
layui.form.on('switch(StatusSwitchRoleTable)', function (obj) {
|
||||
let data = {id: obj.value, status: obj.elem.checked > 0 ? 1 : 0};
|
||||
$.form.load("{:url('state')}", data, 'post', function (ret) {
|
||||
if (ret.code < 1) $.msg.error(ret.info, 3, function () {
|
||||
$('#RoleTable').trigger('reload');
|
||||
});
|
||||
return false;
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<!-- 列表排序权重模板 -->
|
||||
<script type="text/html" id="SortInputRoleTableTpl">
|
||||
<input type="number" min="0" data-blur-number="0" data-action-blur="{:request()->url()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
|
||||
</script>
|
||||
|
||||
<!-- 数据状态切换模板 -->
|
||||
<script type="text/html" id="StatusSwitchRoleTableTpl">
|
||||
<!--{if auth("state")}-->
|
||||
<input type="checkbox" value="{{d.id}}" lay-skin="switch" lay-text="{:lang('已激活')}|{:lang('已禁用')}" lay-filter="StatusSwitchRoleTable" {{-d.status>0?'checked':''}}>
|
||||
<!--{else}-->
|
||||
{{-d.status ? '<b class="color-green">{:lang("已启用")}</b>' : '<b class="color-red">{:lang("已禁用")}</b>'}}
|
||||
<!--{/if}-->
|
||||
</script>
|
||||
|
||||
<!-- 数据操作工具条模板 -->
|
||||
<script type="text/html" id="ToolbarRoleTableTpl">
|
||||
<!--{if auth('edit')}-->
|
||||
<a class="layui-btn layui-btn-primary layui-btn-sm" data-open='{:url("edit")}?id={{d.id}}'>{:lang("编 辑")}</a>
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if auth("remove")}-->
|
||||
<a class="layui-btn layui-btn-danger layui-btn-sm" data-action="{:url('remove')}" data-value="id#{{d.id}}" data-confirm="{:lang('确定要删除权限吗?')}">{:lang("删 除")}</a>
|
||||
<!--{/if}-->
|
||||
</script>
|
||||
{/block}
|
||||
@ -1,68 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card" data-table-id="BaseTable">
|
||||
|
||||
<div class="layui-card-body padding-left-40">
|
||||
|
||||
<div class="layui-form-item label-required-prev">
|
||||
<div class="help-label"><b>{:lang('数据类型')}</b>Data Type</div>
|
||||
{if isset($vo.type)}
|
||||
<label><input readonly value="{$vo.type|default=''}" class="layui-input think-bg-gray"></label>
|
||||
{else}
|
||||
<select class="layui-select" lay-filter="DataType">
|
||||
{foreach $types as $type}{if (isset($vo.type) and $type eq $vo.type) or ($type eq input('get.type'))}
|
||||
<option selected value="{$type}">{$type}</option>
|
||||
{else}
|
||||
<option value="{$type}">{$type}</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
<script>
|
||||
(function (callable) {
|
||||
layui.form.on('select(DataType)', callable);
|
||||
callable({value: "{$vo.type|default=''}" || $('[lay-filter=DataType]').val()});
|
||||
})(function (data) {
|
||||
if (data.value === '--- 新增类型 ---') {
|
||||
$('#DataTypeInput').removeClass('layui-hide').find('input').val('').focus();
|
||||
} else {
|
||||
$('#DataTypeInput').addClass('layui-hide').find('input').val(data.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/if}
|
||||
<p class="help-block">{:lang('请选择数据类型,数据创建后不能再次修改哦 ~')}</p>
|
||||
<div id="DataTypeInput" class="layui-hide relative">
|
||||
<input class="layui-input" maxlength="20" name="type" required vali-name="{:lang('数据类型')}" placeholder="{:lang('请输入数据类型')}" value="{$vo.type|default=''}">
|
||||
<p class="help-block">{:lang('请输入新的数据类型,数据创建后不能再次修改哦 ~')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('数据编码')}</b>Data Code</span>
|
||||
{if isset($vo.code)}
|
||||
<input readonly maxlength="100" class="layui-input think-bg-gray" name="code" value='{$vo.code|default=""}' required placeholder="{:lang('请输入数据编码')}">
|
||||
{else}
|
||||
<input maxlength="100" class="layui-input" name="code" value='{$vo.code|default=""}' required vali-name="{:lang('数据编码')}" placeholder="{:lang('请输入数据编码')}">
|
||||
{/if}
|
||||
<span class="help-block">{:lang('请输入新的数据编码,数据创建后不能再次修改,同种数据类型的数据编码不能出现重复 ~')}</span>
|
||||
</label>
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('数据名称')}</b>Data Name</span>
|
||||
<input maxlength="500" class="layui-input" name="name" value='{$vo.name|default=""}' required vali-name="{:lang('数据名称')}" placeholder="{:lang('请输入数据名称')}">
|
||||
<span class="help-block">{:lang('请输入当前数据名称,请尽量保持名称的唯一性,数据名称尽量不要出现重复 ~')}</span>
|
||||
</label>
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('数据内容')}</b>Data Content</span>
|
||||
<textarea name="content" class="layui-textarea" placeholder="{:lang('请输入数据内容')}">{$vo.content|default=''}</textarea>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
|
||||
|
||||
<div class="layui-form-item text-center">
|
||||
<button class="layui-btn" type='submit'>{:lang('保存数据')}</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="{:lang('确定要取消编辑吗?')}" data-close>{:lang('取消编辑')}</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@ -1,86 +0,0 @@
|
||||
{extend name='table'}
|
||||
|
||||
{block name="button"}
|
||||
<!--{if auth("add")}-->
|
||||
<button data-table-id="BaseTable" data-modal='{:url("add")}?type={$type|default=""}' class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('添加数据')}</button>
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if auth("remove")}-->
|
||||
<button data-table-id="BaseTable" data-action='{:url("remove")}' data-rule="id#{id}" data-confirm="{:lang('确定要批量删除数据吗?')}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量删除')}</button>
|
||||
<!--{/if}-->
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<div class="layui-tab layui-tab-card">
|
||||
<ul class="layui-tab-title">
|
||||
{foreach $types as $t}{if isset($type) and $type eq $t}
|
||||
<li class="layui-this" data-open="{:sysuri()}?type={$t}">{$t}</li>
|
||||
{else}
|
||||
<li data-open="{:sysuri()}?type={$t}">{$t}</li>
|
||||
{/if}{/foreach}
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
{include file='base/index_search'}
|
||||
<table id="BaseTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block name='script'}
|
||||
<script>
|
||||
$(function () {
|
||||
// 初始化表格组件
|
||||
$('#BaseTable').layTable({
|
||||
even: true, height: 'full',
|
||||
sort: {field: 'sort desc,id', type: 'asc'},
|
||||
where: {type: '{$type|default=""}'},
|
||||
cols: [[
|
||||
{checkbox: true, fixed: true},
|
||||
{field: 'sort', title: '{:lang("排序权重")}', width: 100, align: 'center', sort: true, templet: '#SortInputTpl'},
|
||||
// {field: 'type', title: '数据类型', minWidth: 140, align: 'center'},
|
||||
{field: 'code', title: '{:lang("数据编码")}', width: '20%', align: 'left'},
|
||||
{field: 'name', title: '{:lang("数据名称")}', width: '30%', align: 'left'},
|
||||
{field: 'status', title: '{:lang("数据状态")}', minWidth: 110, align: 'center', templet: '#StatusSwitchTpl'},
|
||||
{field: 'create_at', title: '{:lang("创建时间")}', minWidth: 170, align: 'center', sort: true},
|
||||
{toolbar: '#toolbar', align: 'center', minWidth: 150, title: '{:lang("数据操作")}', fixed: 'right'},
|
||||
]]
|
||||
});
|
||||
|
||||
// 数据状态切换操作
|
||||
layui.form.on('switch(StatusSwitch)', function (obj) {
|
||||
var data = {id: obj.value, status: obj.elem.checked > 0 ? 1 : 0};
|
||||
$.form.load("{:url('state')}", data, 'post', function (ret) {
|
||||
if (ret.code < 1) $.msg.error(ret.info, 3, function () {
|
||||
$('#BaseTable').trigger('reload');
|
||||
});
|
||||
return false;
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 列表排序权重模板 -->
|
||||
<script type="text/html" id="SortInputTpl">
|
||||
<input type="number" min="0" data-blur-number="0" data-action-blur="{:sysuri()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
|
||||
</script>
|
||||
|
||||
<!-- 数据状态切换模板 -->
|
||||
<script type="text/html" id="StatusSwitchTpl">
|
||||
<!--{if auth("state")}-->
|
||||
<input type="checkbox" value="{{d.id}}" lay-skin="switch" lay-text="{:lang('已激活')}|{:lang('已禁用')}" lay-filter="StatusSwitch" {{-d.status>0?'checked':''}}>
|
||||
<!--{else}-->
|
||||
{{-d.status ? '<b class="color-green">{:lang("已启用")}</b>' : '<b class="color-red">{:lang("已禁用")}</b>'}}
|
||||
<!--{/if}-->
|
||||
</script>
|
||||
|
||||
<!-- 数据操作工具条模板 -->
|
||||
<script type="text/html" id="toolbar">
|
||||
<!--{if auth('edit')}-->
|
||||
<a class="layui-btn layui-btn-primary layui-btn-sm" data-event-dbclick data-title="{:lang('编辑数据')}" data-modal='{:url("edit")}?id={{d.id}}'>{:lang('编 辑')}</a>
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if auth("remove")}-->
|
||||
<a class="layui-btn layui-btn-danger layui-btn-sm" data-confirm="{:lang('确定要删除数据吗?')}" data-action="{:url('remove')}" data-value="id#{{d.id}}">{:lang('删 除')}</a>
|
||||
<!--{/if}-->
|
||||
</script>
|
||||
{/block}
|
||||
@ -1,42 +0,0 @@
|
||||
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('数据编码')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<input name="code" value="{$get.code|default=''}" placeholder="{:lang('请输入数据编码')}" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('数据名称')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<input name="name" value="{$get.name|default=''}" placeholder="{:lang('请输入数据名称')}" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('使用状态')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<select class="layui-select" name="status">
|
||||
<option value=''>-- {:lang('全部')} --</option>
|
||||
{foreach [lang('已禁用记录'),lang('已激活记录')] as $k=>$v}
|
||||
{if isset($get.status) and $get.status eq $k.""}
|
||||
<option selected value="{$k}">{$v}</option>
|
||||
{else}
|
||||
<option value="{$k}">{$v}</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('创建时间')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<input data-date-range name="create_at" value="{$get.create_at|default=''}" placeholder="{:lang('请选择创建时间')}" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<button class="layui-btn layui-btn-primary"><i class="layui-icon"></i> {:lang('搜 索')}</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,234 +0,0 @@
|
||||
{extend name="main"}
|
||||
|
||||
{block name="button"}
|
||||
<!--{if isset($issuper) and $issuper}-->
|
||||
<a class="layui-btn layui-btn-sm layui-btn-primary" data-load="{:url('admin/api.system/config')}">{:lang('清理无效配置')}</a>
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if auth('system')}-->
|
||||
<a class="layui-btn layui-btn-sm layui-btn-primary" data-modal="{:url('system')}">{:lang('修改系统参数')}</a>
|
||||
<!--{/if}-->
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<!--{notempty name='issuper'}-->
|
||||
<div class="layui-card padding-20 shadow">
|
||||
<div class="layui-card-header notselect">
|
||||
<span class="help-label">
|
||||
<b style="color:#333!important;">{:lang('运行模式')}</b>( {:lang('仅超级管理员可配置')} )
|
||||
</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-btn-group shadow-mini nowrap">
|
||||
<!--{if $app->isDebug()}-->
|
||||
<a class="layui-btn layui-btn-sm layui-btn-active">{:lang('以开发模式运行')}</a>
|
||||
<a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="{:lang('确定要切换到生产模式运行吗?')}" data-load="{:url('admin/api.system/debug')}?state=1">{:lang('以生产模式运行')}</a>
|
||||
<!--{else}-->
|
||||
<a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="{:lang('确定要切换到开发模式运行吗?')}" data-load="{:url('admin/api.system/debug')}?state=0">{:lang('以开发模式运行')}</a>
|
||||
<a class="layui-btn layui-btn-sm layui-btn-active">{:lang('以生产模式运行')}</a>
|
||||
<!--{/if}-->
|
||||
</div>
|
||||
<div class="margin-top-20">
|
||||
<p><b>{:lang('开发模式')}</b>:{:lang('开发人员或在功能调试时使用,系统异常时会显示详细的错误信息,同时还会记录操作日志及数据库 SQL 语句信息。')}</p>
|
||||
<p><b>{:lang('生产模式')}</b>:{:lang('项目正式部署上线后使用,系统异常时统一显示 “%s”,只记录重要的异常日志信息,强烈推荐上线后使用此模式。',[config('app.error_message')])}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card padding-20 shadow">
|
||||
<div class="layui-card-header notselect">
|
||||
<span class="help-label">
|
||||
<b style="color:#333!important;">{:lang('富编辑器')}</b>( {:lang('仅超级管理员可配置')} )
|
||||
</span>
|
||||
</div>
|
||||
<div class="layui-card-body layui-clear">
|
||||
<div class="layui-btn-group shadow-mini">
|
||||
{if !in_array(sysconf('base.editor'),['ckeditor4','ckeditor5','wangEditor','auto'])}{php}sysconf('base.editor','ckeditor4');{/php}{/if}
|
||||
{foreach ['ckeditor4'=>'CKEditor4','ckeditor5'=>'CKEditor5','wangEditor'=>'wangEditor','auto'=>lang('自适应模式')] as $k => $v}{if sysconf('base.editor') eq $k}
|
||||
{if auth('storage')}<a data-title="配置{$v}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
|
||||
{else}
|
||||
{if auth('storage')}<a data-title="配置{$v}" data-action="{:url('admin/api.system/editor')}" data-value="editor#{$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
|
||||
{/if}{/foreach}
|
||||
</div>
|
||||
<div class="margin-top-20 full-width pull-left">
|
||||
<p><b>CKEditor4</b>:{:lang('旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。')}</p>
|
||||
<p><b>CKEditor5</b>:{:lang('新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。')}</p>
|
||||
<p><b>wangEditor</b>:{:lang('国产优质富文本编辑器,对于小程序及App内容支持会更友好,推荐使用。')}</p>
|
||||
<p><b>{:lang('自适应模式')}</b>:{:lang('优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--{/notempty}-->
|
||||
|
||||
<div class="layui-card padding-20 shadow">
|
||||
<div class="layui-card-header notselect">
|
||||
<span class="help-label">
|
||||
<b style="color:#333!important;">{:lang('存储引擎')}</b>( {:lang('文件默认存储方式')} )
|
||||
</span>
|
||||
</div>
|
||||
<!-- 初始化存储配置 -->
|
||||
{if !sysconf('storage.type')}{php}sysconf('storage.type','local');{/php}{/if}
|
||||
{if !sysconf('storage.link_type')}{php}sysconf('storage.link_type','none');{/php}{/if}
|
||||
{if !sysconf('storage.name_type')}{php}sysconf('storage.name_type','xmd5');{/php}{/if}
|
||||
{if !sysconf('storage.allow_exts')}{php}sysconf('storage.allow_exts','doc,gif,ico,jpg,mp3,mp4,p12,pem,png,rar,xls,xlsx');{/php}{/if}
|
||||
{if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','follow');{/php}{/if}
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-btn-group shadow-mini nowrap">
|
||||
{foreach $files as $k => $v}{if sysconf('storage.type') eq $k}
|
||||
{if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
|
||||
{else}
|
||||
{if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
|
||||
{/if}{/foreach}
|
||||
</div>
|
||||
<div class="margin-top-20 full-width">
|
||||
<p><b>{:lang('本地服务器存储')}</b>:{:lang('文件上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。')}</p>
|
||||
<p><b>{:lang('自建Alist存储')}</b>:{:lang('文件上传到 Alist 存储的服务器或云存储空间,根据服务配置可支持大文件上传,不占用本身服务器空间及服务器带宽流量。')}</p>
|
||||
<p><b>{:lang('七牛云对象存储')}</b>:{:lang('文件上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
|
||||
<p><b>{:lang('又拍云USS存储')}</b>:{:lang('文件上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
|
||||
<p><b>{:lang('阿里云OSS存储')}</b>:{:lang('文件上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
|
||||
<p><b>{:lang('腾讯云COS存储')}</b>:{:lang('文件上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card padding-20 shadow">
|
||||
<div class="layui-card-header notselect">
|
||||
<span class="help-label">
|
||||
<b style="color:#333!important;">{:lang('系统参数')}</b>( {:lang('当前系统配置参数')} )
|
||||
</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-form-item">
|
||||
<div class="help-label"><b>{:lang('网站名称')}</b>Website</div>
|
||||
<label class="relative block">
|
||||
<input readonly value="{:sysconf('site_name')}" class="layui-input layui-bg-gray">
|
||||
<a data-copy="{:sysconf('site_name')}" class="layui-icon layui-icon-release input-right-icon"></a>
|
||||
</label>
|
||||
<div class="help-block">{:lang('网站名称及网站图标,将显示在浏览器的标签上。')}</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="help-label"><b>{:lang('管理程序名称')}</b>Name</div>
|
||||
<label class="relative block">
|
||||
<input readonly value="{:sysconf('app_name')}" class="layui-input layui-bg-gray">
|
||||
<a data-copy="{:sysconf('app_name')}" class="layui-icon layui-icon-release input-right-icon"></a>
|
||||
</label>
|
||||
<div class="help-block">{:lang('管理程序名称,将显示在后台左上角标题。')}</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="help-label"><b>{:lang('管理程序版本')}</b>Version</div>
|
||||
<label class="relative block">
|
||||
<input readonly value="{:sysconf('app_version')}" class="layui-input layui-bg-gray">
|
||||
<a data-copy="{:sysconf('app_version')}" class="layui-icon layui-icon-release input-right-icon"></a>
|
||||
</label>
|
||||
<div class="help-block">{:lang('管理程序版本,将显示在后台左上角标题。')}</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="help-label"><b>{:lang('公安备案号')}</b>Beian</div>
|
||||
<label class="relative block">
|
||||
<input readonly value="{:sysconf('beian')?:'-'}" class="layui-input layui-bg-gray">
|
||||
<a data-copy="{:sysconf('beian')}" class="layui-icon layui-icon-release input-right-icon"></a>
|
||||
</label>
|
||||
<p class="help-block">
|
||||
{:lang('公安备案号,可以在 %s 查询获取,将在登录页面下面显示。',['<a target="_blank" href="https://www.beian.gov.cn/portal/registerSystemInfo">www.beian.gov.cn</a>'])}
|
||||
</p>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="help-label"><b>{:lang('网站备案号')}</b>Miitbeian</div>
|
||||
<label class="relative block">
|
||||
<input readonly value="{:sysconf('miitbeian')?:'-'}" class="layui-input layui-bg-gray">
|
||||
<a data-copy="{:sysconf('miitbeian')}" class="layui-icon layui-icon-release input-right-icon"></a>
|
||||
</label>
|
||||
<div class="help-block">
|
||||
{:lang('网站备案号,可以在 %s 查询获取,将显示在登录页面下面。',['<a target="_blank" href="https://beian.miit.gov.cn">beian.miit.gov.cn</a>'])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="help-label"><b>{:lang('网站版权信息')}</b>Copyright</div>
|
||||
<label class="relative block">
|
||||
<input readonly value="{:sysconf('site_copy')}" class="layui-input layui-bg-gray">
|
||||
<a data-copy="{:sysconf('site_copy')}" class="layui-icon layui-icon-release input-right-icon"></a>
|
||||
</label>
|
||||
<div class="help-block">{:lang('网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统。')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--{if $app->isDebug()}-->
|
||||
<div class="layui-card padding-20 shadow">
|
||||
<div class="layui-card-header notselect">
|
||||
<span class="help-label">
|
||||
<b style="color:#333!important;">{:lang('系统信息')}</b>( {:lang('仅开发模式可见')} )
|
||||
</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-table" lay-even>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="nowrap text-center">{:lang('核心框架')}</th>
|
||||
<td><a target="_blank" href="https://www.thinkphp.cn">ThinkPHP Version {$framework.version|default='None'}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="nowrap text-center">{:lang('平台框架')}</th>
|
||||
<td><a target="_blank" href="https://thinkadmin.top">ThinkAdmin Version {$thinkadmin.version|default='6.0.0'}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="nowrap text-center">{:lang('操作系统')}</th>
|
||||
<td>{:php_uname()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="nowrap text-center">{:lang('运行环境')}</th>
|
||||
<td>{:ucfirst($request->server('SERVER_SOFTWARE',php_sapi_name()))} & PHP {$Think.const.PHP_VERSION} & {:ucfirst(app()->db->connect()->getConfig('type'))}</td>
|
||||
</tr>
|
||||
<!-- {notempty name='systemid'} -->
|
||||
<tr>
|
||||
<th class="nowrap text-center">{:lang('系统序号')}</th>
|
||||
<td>{$systemid|default=''}</td>
|
||||
</tr>
|
||||
<!-- {/notempty} -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{notempty name='plugins'}
|
||||
<div class="layui-card padding-20 shadow">
|
||||
<div class="layui-card-header notselect">
|
||||
<span class="help-label">
|
||||
<b style="color:#333!important;">{:lang('应用插件')}</b>( {:lang('仅开发模式可见')} )
|
||||
</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-table" lay-even>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="nowrap text-center">{:lang('应用名称')}</th>
|
||||
<th class="nowrap text-center">{:lang('插件名称')}</th>
|
||||
<th class="nowrap text-left">{:lang('插件包名')}</th>
|
||||
<th class="nowrap text-center">{:lang('插件版本')}</th>
|
||||
<th class="nowrap text-center">{:lang('授权协议')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach $plugins as $key=>$plugin}
|
||||
<tr>
|
||||
<td class="nowrap text-center">{$key}</td>
|
||||
<td class="nowrap text-center">{$plugin.name|lang}</td>
|
||||
<td class="nowrap text-left">
|
||||
{if empty($plugin.install.document)}{$plugin.package}
|
||||
{else}<a target="_blank" href="{$plugin.install.document}">{$plugin.package}</a>{/if}
|
||||
</td>
|
||||
<td class="nowrap text-center">{$plugin.install.version|default='unknow'}</td>
|
||||
<td class="nowrap text-center">
|
||||
{if empty($plugin.install.license)} -
|
||||
{elseif is_array($plugin.install.license)}{$plugin.install.license|join='、',###}
|
||||
{else}{$plugin.install.license|default='-'}{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{/notempty}
|
||||
<!--{/if}-->
|
||||
{/block}
|
||||
@ -1,49 +0,0 @@
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required">
|
||||
<b class="color-green">命名方式</b><br><span class="nowrap color-desc">NameType</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-input help-checks">
|
||||
{foreach ['xmd5'=>'文件哈希值 ( 支持秒传 )','date'=>'日期+随机 ( 普通上传 )'] as $k=>$v}
|
||||
<label class="think-radio notselect">
|
||||
{if sysconf('storage.name_type') eq $k}
|
||||
<input checked type="radio" name="storage.name_type" value="{$k}" lay-ignore> {$v}
|
||||
{else}
|
||||
<input type="radio" name="storage.name_type" value="{$k}" lay-ignore> {$v}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
<p class="help-block">类型为“文件哈希”时可以实现文件秒传功能,同一个文件只需上传一次节省存储空间,推荐使用。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required">
|
||||
<b class="color-green">链接类型</b><br><span class="nowrap color-desc">LinkType</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-input help-checks">
|
||||
{foreach ['none'=>'简洁链接','full'=>'完整链接','none+compress'=>'简洁并压缩图片','full+compress'=>'完整并压缩图片'] as $k=>$v}
|
||||
<label class="think-radio notselect">
|
||||
{if sysconf('storage.link_type') eq $k}
|
||||
<input checked type="radio" name="storage.link_type" value="{$k}" lay-ignore> {$v}
|
||||
{else}
|
||||
<input type="radio" name="storage.link_type" value="{$k}" lay-ignore> {$v}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
<p class="help-block">类型为“简洁链接”时链接将只返回 hash 地址,而“完整链接”将携带参数保留文件名,图片压缩功能云平台会单独收费。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.allow_exts">
|
||||
<b class="color-green">允许类型</b><br><span class="nowrap color-desc">AllowExts</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.allow_exts" type="text" name="storage.allow_exts" value="{:sysconf('storage.allow_exts')}" required vali-name="文件后缀" placeholder="请输入系统文件上传后缀" class="layui-input">
|
||||
<p class="help-block">设置系统允许上传的文件后缀,多个以英文逗号隔开如:png,jpg,rar,doc,未包含在设置内的文件后缀将不被允许上传。</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,98 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
|
||||
<div class="layui-card-body padding-top-20">
|
||||
|
||||
<div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
|
||||
<p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://www.aliyun.com/minisite/goods?taskCode=shareNew2205&recordId=3646641&userCode=ztlqlu4v">阿里云</a> OSS 存储,需要配置 OSS 公开访问及跨域策略</p>
|
||||
<p>配置跨域访问 CORS 规则,设置:来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *</p>
|
||||
</div>
|
||||
|
||||
{include file='config/storage-0'}
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required">
|
||||
<b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
{if !sysconf('storage.alioss_http_protocol')}{php}sysconf('storage.alioss_http_protocol','http');{/php}{/if}
|
||||
<div class="layui-input help-checks">
|
||||
{foreach ['http'=>'HTTP','https'=>'HTTPS','auto'=>"AUTO"] as $protocol=>$remark}
|
||||
<label class="think-radio">
|
||||
{if sysconf('storage.alioss_http_protocol') eq $protocol}
|
||||
<input checked type="radio" name="storage.alioss_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{else}
|
||||
<input type="radio" name="storage.alioss_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
<p class="help-block">阿里云OSS存储访问协议,其中 HTTPS 需要配置证书才能使用(AUTO 为相对协议)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">
|
||||
<b class="color-green">存储区域</b><br><span class="nowrap color-desc label-required">Region</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<select class="layui-select" name="storage.alioss_point" lay-search>
|
||||
{foreach $points as $point => $title}
|
||||
{if sysconf('storage.alioss_point') eq $point}
|
||||
<option selected value="{$point}">{$title}( {$point} )</option>
|
||||
{else}
|
||||
<option value="{$point}">{$title}( {$point} )</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
<p class="help-block">阿里云OSS存储空间所在区域,需要严格对应储存所在区域才能上传文件</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.alioss_bucket">
|
||||
<b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.alioss_bucket" type="text" name="storage.alioss_bucket" value="{:sysconf('storage.alioss_bucket')}" required vali-name="空间名称" placeholder="请输入阿里云OSS存储 Bucket (空间名称)" class="layui-input">
|
||||
<p class="help-block">填写阿里云OSS存储空间名称,如:think-admin-oss(需要是全区唯一的值,不存在时会自动创建)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.alioss_http_domain">
|
||||
<b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.alioss_http_domain" type="text" name="storage.alioss_http_domain" value="{:sysconf('storage.alioss_http_domain')}" required vali-name="访问域名" placeholder="请输入阿里云OSS存储 Domain (访问域名)" class="layui-input">
|
||||
<p class="help-block">填写阿里云OSS存储外部访问域名,不需要填写访问协议,如:static.alioss.thinkadmin.top</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.alioss_access_key">
|
||||
<b class="color-green">访问密钥</b><br><span class="nowrap color-desc">AccessKey</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.alioss_access_key" type="text" name="storage.alioss_access_key" value="{:sysconf('storage.alioss_access_key')}" required vali-name="访问密钥" placeholder="请输入阿里云OSS存储 AccessKey (访问密钥)" class="layui-input">
|
||||
<p class="help-block">可以在 [ 阿里云 > 个人中心 ] 设置并获取到访问密钥</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.alioss_secret_key">
|
||||
<b class="color-green">安全密钥</b><br><span class="nowrap color-desc">SecretKey</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.alioss_secret_key" type="text" name="storage.alioss_secret_key" value="{:sysconf('storage.alioss_secret_key')}" maxlength="43" required vali-name="安全密钥" placeholder="请输入阿里云OSS存储 SecretKey (安全密钥)" class="layui-input">
|
||||
<p class="help-block">可以在 [ 阿里云 > 个人中心 ] 设置并获取到安全密钥</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed margin-left-40"></div>
|
||||
<input type="hidden" name="storage.type" value="alioss">
|
||||
|
||||
<div class="layui-form-item text-center padding-left-40">
|
||||
<button class="layui-btn" type="submit">保存配置</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
@ -1,81 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
|
||||
<div class="layui-card-body padding-top-20">
|
||||
|
||||
<div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
|
||||
<p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://alist.nn.ci/zh/">Alist</a> 自建存储,需要自行搭建 <a target="_blank" href="https://alist.nn.ci/zh/">Alist</a> 存储服务器。</p>
|
||||
<p>Alist 是一个支持多种存储的文件列表程序,可将各种云盘及本地磁盘资源进行整合。</p>
|
||||
<p>建议不要开放匿名用户访问,尽量使用独立账号管理,需要关闭 “签名所有” 让文件可以直接访问。</p>
|
||||
</div>
|
||||
|
||||
{include file='config/storage-0'}
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required">
|
||||
<b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
{if !sysconf('storage.alist_http_protocol')}{php}sysconf('storage.alist_http_protocol','http');{/php}{/if}
|
||||
<div class="layui-input help-checks">
|
||||
{foreach ['http'=>'HTTP','https'=>'HTTPS','auto'=>"AUTO"] as $protocol=>$remark}
|
||||
<label class="think-radio">
|
||||
{if sysconf('storage.alist_http_protocol') eq $protocol}
|
||||
<input checked type="radio" name="storage.alist_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{else}
|
||||
<input type="radio" name="storage.alist_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
<p class="help-block">请选择 Alist 存储访问协议,其中 HTTPS 需要配置证书才能使用( AUTO 为相对协议 )</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.alist_http_domain">
|
||||
<b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.alist_http_domain" type="text" name="storage.alist_http_domain" value="{:sysconf('storage.alist_http_domain')}" required vali-name="访问域名" placeholder="请输入 Alist 存储的访问域名" class="layui-input">
|
||||
<p class="help-block">请填写 Alist 存储访问域名,不需要填写访问协议,如:storage.thinkadmin.top</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.alist_savepath">
|
||||
<b class="color-green">存储目录</b><br><span class="nowrap color-desc">Directory</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.alist_savepath" type="text" name="storage.alist_savepath" value="{:sysconf('storage.alist_savepath')}" required vali-name="存储目录" placeholder="请输入 Alist 存储目录" class="layui-input">
|
||||
<p class="help-block">请填写 Alist 用户基本目录的相对存储位置,填写 / 表示用户基本目录( 需要拥有读写权限 )</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.alist_username">
|
||||
<b class="color-green">用户账号</b><br><span class="nowrap color-desc">Username</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.alist_username" name="storage.alist_username" value="{:sysconf('storage.alist_username')}" maxlength="100" required vali-name="用户账号" placeholder="请输入 Alist 存储的用户账号" class="layui-input">
|
||||
<p class="help-block">请填写 Alist 用户账号,注意此账号需要拥有上面存储目录的访问权限。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.alist_password">
|
||||
<b class="color-green">用户密码</b><br><span class="nowrap color-desc">Password</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.alist_password" name="storage.alist_password" value="{:sysconf('storage.alist_password')}" maxlength="100" required vali-name="用户密码" placeholder="请输入 Alist 存储的用户密码" class="layui-input">
|
||||
<p class="help-block">请填写 Alist 用户登录密码,用于生成文件上传的接口认证令牌,如果填写错误将无法上传文件。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed margin-left-40"></div>
|
||||
<input type="hidden" name="storage.type" value="alist">
|
||||
|
||||
<div class="layui-form-item text-center padding-left-40">
|
||||
<button class="layui-btn" type="submit">保存配置</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,51 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
|
||||
<div class="layui-card-body padding-top-20">
|
||||
|
||||
<div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
|
||||
<p class="margin-bottom-5 font-w7">文件将存储在本地服务器,默认保存在 public/upload 目录,文件以 HASH 命名。</p>
|
||||
<p>文件存储的目录需要有读写权限,有足够的存储空间。<span class="color-red">特别注意,本地存储暂不支持图片压缩!</span></p>
|
||||
</div>
|
||||
|
||||
{include file='config/storage-0'}
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required">
|
||||
<b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
{if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','follow');{/php}{/if}
|
||||
<div class="layui-input help-checks">
|
||||
{foreach ['follow'=>'FOLLOW','http'=>'HTTP','https'=>'HTTPS','path'=>'PATH','auto'=>'AUTO'] as $protocol=>$remark}
|
||||
<label class="think-radio">
|
||||
{if sysconf('storage.local_http_protocol') eq $protocol}
|
||||
<input checked type="radio" name="storage.local_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{else}
|
||||
<input type="radio" name="storage.local_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
<p class="help-block">本地存储访问协议,其中 HTTPS 需要配置证书才能使用( FOLLOW 跟随系统,PATH 文件路径,AUTO 相对协议 )</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.local_http_domain">
|
||||
<b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.local_http_domain" type="text" name="storage.local_http_domain" value="{:sysconf('storage.local_http_domain')}" placeholder="请输入上传后的访问域名 (非必填项)" class="layui-input">
|
||||
<p class="help-block">填写上传后的访问域名(不指定时根据当前访问地址自动计算),不需要填写访问协议,如:static.thinkadmin.top</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed margin-left-40"></div>
|
||||
<input type="hidden" name="storage.type" value="local">
|
||||
|
||||
<div class="layui-form-item text-center padding-left-40">
|
||||
<button class="layui-btn" type="submit">保存配置</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
@ -1,97 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
|
||||
<div class="layui-card-body padding-top-20">
|
||||
|
||||
<div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
|
||||
<p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://s.qiniu.com/rYr26v">七牛云</a> 存储,对象存储需要配置为公开访问的 Bucket 空间</p>
|
||||
完成实名认证后可获得 10G 免费存储空间哦!<a target="_blank" href="https://s.qiniu.com/rYr26v">我要免费申请</a>
|
||||
</div>
|
||||
|
||||
{include file='config/storage-0'}
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required">
|
||||
<b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
{if !sysconf('storage.qiniu_http_protocol')}{php}sysconf('storage.qiniu_http_protocol','http');{/php}{/if}
|
||||
<div class="layui-input help-checks">
|
||||
{foreach ['http'=>'HTTP','https'=>'HTTPS','auto'=>"AUTO"] as $protocol=>$remark}
|
||||
<label class="think-radio">
|
||||
{if sysconf('storage.qiniu_http_protocol') eq $protocol}
|
||||
<input checked type="radio" name="storage.qiniu_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{else}
|
||||
<input type="radio" name="storage.qiniu_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
<p class="help-block">七牛云存储访问协议,其中 HTTPS 需要配置证书才能使用( AUTO 为相对协议 )</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">
|
||||
<b class="color-green">存储区域</b><br><span class="nowrap color-desc label-required">Region</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<select class="layui-select" name="storage.qiniu_region" lay-search>
|
||||
{foreach $points as $point => $title}
|
||||
{if sysconf('storage.qiniu_region') eq $point}
|
||||
<option selected value="{$point}">{$title}( {$point} )</option>
|
||||
{else}
|
||||
<option value="{$point}">{$title}( {$point} )</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
<p class="help-block">七牛云存储空间所在区域,需要严格对应储存所在区域才能上传文件</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.qiniu_bucket">
|
||||
<b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.qiniu_bucket" type="text" name="storage.qiniu_bucket" value="{:sysconf('storage.qiniu_bucket')}" required vali-name="空间名称" placeholder="请输入七牛云存储 Bucket (空间名称)" class="layui-input">
|
||||
<p class="help-block">填写七牛云存储空间名称,如:static</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.qiniu_http_domain">
|
||||
<b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.qiniu_http_domain" type="text" name="storage.qiniu_http_domain" value="{:sysconf('storage.qiniu_http_domain')}" required vali-name="访问域名" placeholder="请输入七牛云存储 Domain (访问域名)" class="layui-input">
|
||||
<p class="help-block">填写七牛云存储访问域名,不需要填写访问协议,如:static.qiniu.thinkadmin.top</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.qiniu_access_key">
|
||||
<b class="color-green">访问密钥</b><br><span class="nowrap color-desc">AccessKey</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.qiniu_access_key" type="text" name="storage.qiniu_access_key" value="{:sysconf('storage.qiniu_access_key')}" required vali-name="访问密钥" placeholder="请输入七牛云授权 AccessKey (访问密钥)" class="layui-input">
|
||||
<p class="help-block">可以在 [ 七牛云 > 个人中心 ] 设置并获取到访问密钥</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.qiniu_secret_key">
|
||||
<b class="color-green">安全密钥</b><br><span class="nowrap color-desc">SecretKey</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.qiniu_secret_key" type="text" name="storage.qiniu_secret_key" value="{:sysconf('storage.qiniu_secret_key')}" maxlength="43" required vali-name="安全密钥" placeholder="请输入七牛云授权 SecretKey (安全密钥)" class="layui-input">
|
||||
<p class="help-block">可以在 [ 七牛云 > 个人中心 ] 设置并获取到安全密钥</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed margin-left-40"></div>
|
||||
<input type="hidden" name="storage.type" value="qiniu">
|
||||
|
||||
<div class="layui-form-item text-center padding-left-40">
|
||||
<button class="layui-btn" type="submit">保存配置</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,96 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
|
||||
<div class="layui-card-body padding-top-20">
|
||||
|
||||
<div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
|
||||
<p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://curl.qcloud.com/4t0Mbw2K">腾讯云</a> COS 存储,需要配置 COS 公有读私有写访问权限及跨域策略</p>
|
||||
<p>配置跨域访问 CORS 规则,设置来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *</p>
|
||||
</div>
|
||||
|
||||
{include file='config/storage-0'}
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required">
|
||||
<b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
{if !sysconf('storage.txcos_http_protocol')}{php}sysconf('storage.txcos_http_protocol','http');{/php}{/if}
|
||||
{foreach ['http'=>'HTTP','https'=>'HTTPS','auto'=>"AUTO"] as $protocol=>$remark}
|
||||
<label class="think-radio">
|
||||
{if sysconf('storage.txcos_http_protocol') eq $protocol}
|
||||
<input checked type="radio" name="storage.txcos_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{else}
|
||||
<input type="radio" name="storage.txcos_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
<p class="help-block">腾讯云COS存储访问协议,其中 HTTPS 需要配置证书才能使用( AUTO 为相对协议 )</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">
|
||||
<b class="color-green">存储区域</b><br><span class="nowrap color-desc label-required">Region</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<select class="layui-select" name="storage.txcos_point" lay-search>
|
||||
{foreach $points as $point => $title}
|
||||
{if sysconf('storage.txcos_point') eq $point}
|
||||
<option selected value="{$point}">{$title}( {$point} )</option>
|
||||
{else}
|
||||
<option value="{$point}">{$title}( {$point} )</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
<p class="help-block">腾讯云COS存储空间所在区域,需要严格对应储存所在区域才能上传文件</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.txcos_bucket">
|
||||
<b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.txcos_bucket" type="text" name="storage.txcos_bucket" value="{:sysconf('storage.txcos_bucket')}" required vali-name="空间名称" placeholder="请输入腾讯云COS存储 Bucket" class="layui-input">
|
||||
<p class="help-block">填写腾讯云COS存储空间名称,如:thinkadmin-1251143395</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.txcos_http_domain">
|
||||
<b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.txcos_http_domain" type="text" name="storage.txcos_http_domain" value="{:sysconf('storage.txcos_http_domain')}" required vali-name="访问域名" placeholder="请输入腾讯云COS存储 Domain" class="layui-input">
|
||||
<p class="help-block">填写腾讯云COS存储外部访问域名,不需要填写访问协议,如:static.txcos.thinkadmin.top</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.txcos_access_key">
|
||||
<b class="color-green">访问密钥</b><br><span class="nowrap color-desc">AccessKey</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.txcos_access_key" type="text" name="storage.txcos_access_key" value="{:sysconf('storage.txcos_access_key')}" required vali-name="访问密钥" placeholder="请输入腾讯云COS存储 AccessKey" class="layui-input">
|
||||
<p class="help-block">可以在 [ 腾讯云 > 个人中心 ] 设置并获取到访问密钥</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.txcos_secret_key">
|
||||
<b class="color-green">安全密钥</b><br><span class="nowrap color-desc">SecretKey</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.txcos_secret_key" type="text" name="storage.txcos_secret_key" value="{:sysconf('storage.txcos_secret_key')}" maxlength="43" required vali-name="安全密钥" placeholder="请输入腾讯云COS存储 SecretKey" class="layui-input">
|
||||
<p class="help-block">可以在 [ 腾讯云 > 个人中心 ] 设置并获取到安全密钥</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed margin-left-40"></div>
|
||||
<input type="hidden" name="storage.type" value="txcos">
|
||||
|
||||
<div class="layui-form-item text-center padding-left-40">
|
||||
<button class="layui-btn" type="submit">保存配置</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
@ -1,81 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
|
||||
<div class="layui-card-body padding-top-20">
|
||||
|
||||
<div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
|
||||
<p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://console.upyun.com/register/?invite=PN1cRmjRb">又拍云</a> USS 存储,需要配置 USS 公开访问及跨域策略</p>
|
||||
<p>配置跨域访问 CORS 规则,设置来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *</p>
|
||||
</div>
|
||||
|
||||
{include file='config/storage-0'}
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required">
|
||||
<b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
{if !sysconf('storage.upyun_http_protocol')}{php}sysconf('storage.upyun_http_protocol','http');{/php}{/if}
|
||||
<div class="layui-input help-checks">
|
||||
{foreach ['http'=>'HTTP','https'=>'HTTPS','auto'=>"AUTO"] as $protocol=>$remark}
|
||||
<label class="think-radio">
|
||||
{if sysconf('storage.upyun_http_protocol') eq $protocol}
|
||||
<input checked type="radio" name="storage.upyun_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{else}
|
||||
<input type="radio" name="storage.upyun_http_protocol" value="{$protocol}" lay-ignore> {$remark}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
<p class="help-block">又拍云存储访问协议,其中 HTTPS 需要配置证书才能使用(AUTO 为相对协议)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.upyun_bucket">
|
||||
<b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.upyun_bucket" name="storage.upyun_bucket" value="{:sysconf('storage.upyun_bucket')}" required vali-name="空间名称" placeholder="请输入又拍云存储 Bucket (空间名称)" class="layui-input">
|
||||
<p class="help-block">填写又拍云存储空间名称,如:think-admin-uss(需要是全区唯一的值,不存在时会自动创建)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.upyun_http_domain">
|
||||
<b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.upyun_http_domain" name="storage.upyun_http_domain" value="{:sysconf('storage.upyun_http_domain')}" required vali-name="访问域名" placeholder="请输入又拍云存储 Domain (访问域名)" class="layui-input">
|
||||
<p class="help-block">填写又拍云存储外部访问域名,不需要填写访问协议,如:static.uss.thinkadmin.top</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.upyun_access_key">
|
||||
<b class="color-green">操作账号</b><br><span class="nowrap color-desc">Username</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.upyun_access_key" name="storage.upyun_access_key" value="{:sysconf('storage.upyun_access_key')}" maxlength="100" required vali-name="操作员账号" placeholder="请输入又拍云存储 Username (操作员账号)" class="layui-input">
|
||||
<p class="help-block">可以在 [ 账户管理 > 操作员 ] 设置操作员账号并将空间给予授权。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" for="storage.upyun_secret_key">
|
||||
<b class="color-green">操作密码</b><br><span class="nowrap color-desc">Password</span>
|
||||
</label>
|
||||
<div class="layui-input-block">
|
||||
<input id="storage.upyun_secret_key" name="storage.upyun_secret_key" value="{:sysconf('storage.upyun_secret_key')}" maxlength="100" required vali-name="操作员密码" placeholder="请输入又拍云存储 Password (操作员密码)" class="layui-input">
|
||||
<p class="help-block">可以在 [ 账户管理 > 操作员 ] 设置操作员密码并将空间给予授权</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed margin-left-40"></div>
|
||||
<input type="hidden" name="storage.type" value="upyun">
|
||||
|
||||
<div class="layui-form-item text-center padding-left-40">
|
||||
<button class="layui-btn" type="submit">保存配置</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
@ -1,129 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
|
||||
<div class="layui-card-body padding-left-40">
|
||||
|
||||
<div class="layui-row layui-col-space15 margin-bottom-5">
|
||||
<div class="layui-col-xs4 padding-bottom-0">
|
||||
<label class="relative block">
|
||||
<span class="help-label"><b>{:lang('登录表单标题')}</b>Login Name</span>
|
||||
<input name="login_name" required placeholder="{:lang('请输入登录页面的表单标题')}" vali-name="{:lang('登录标题')}" value="{:sysconf('login_name')?:'系统管理'}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4 padding-bottom-0">
|
||||
<div class="help-label label-required-prev"><b>{:lang('后台登录入口')}</b>Login Entry</div>
|
||||
<label class="layui-input relative block nowrap label-required-null">
|
||||
<span>{:sysuri('@',[],false,true)}</span>
|
||||
<input autofocus required pattern="[a-zA-Z_][a-zA-Z0-9_]*" vali-name="{:lang('登录入口')}" placeholder="{:lang('请输入后台登录入口')}" class="layui-input inline-block padding-0 border-0" style="width:100px;background:none" value="{:substr(sysuri('admin/index/index',[],false), strlen(sysuri('@')))}" name="xpath">
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4 padding-bottom-0">
|
||||
<div class="help-label label-required-prev"><b>{:lang('后台默认配色')}</b>Theme Style</div>
|
||||
<select class="layui-select" name="site_theme" lay-filter="SiteTheme">
|
||||
{foreach $themes as $k=>$v}{if sysconf('base.site_theme') eq $k}
|
||||
<option selected value="{$k}">{$v}</option>
|
||||
{else}
|
||||
<option value="{$k}">{$v}</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-col-xs12 padding-top-0 padding-bottom-0">
|
||||
<span class="help-block">{:lang('后台登录入口是由英文字母开头,且不能有相同名称的模块,设置之后原地址不能继续访问,请谨慎配置 ~')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item margin-bottom-5">
|
||||
<div class="help-label"><b>{:lang('登录背景图片')}</b>Background Image</div>
|
||||
<div class="layui-textarea help-images">
|
||||
<input type="hidden" value="{:sysconf('login_image')}" name="login_image">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item margin-bottom-5">
|
||||
<div class="help-label label-required-prev"><b>{:lang('JWT 接口密钥')}</b>Jwt Key</div>
|
||||
<label class="relative block label-required-null">
|
||||
<input class="layui-input" pattern=".{32}" maxlength="32" required vali-name="{:lang('接口密钥')}" placeholder="{:lang('请输入32位JWT接口密钥')}" name="data.jwtkey" value="{:sysconf('data.jwtkey')?:md5(uniqid(strval(rand(1000,9999)),true))}">
|
||||
<a class="input-right-icon layui-icon layui-icon-refresh" id="RefreshJwtKey"></a>
|
||||
</label>
|
||||
<div class="help-block sub-span-blue">
|
||||
{:lang('请输入 32 位 JWT 接口密钥,在使用 JWT 接口时需要使用此密钥进行加密及签名!')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item margin-bottom-5">
|
||||
<div class="help-label label-required-prev"><b>{:lang('浏览器小图标')}</b>Browser Icon</div>
|
||||
<div class="relative block label-required-null">
|
||||
<input class="layui-input" required pattern="url" vali-name="{:lang('图标文件')}" data-tips-image data-tips-hover placeholder="{:lang('请上传浏览器图标')}" value="{:sysconf('site_icon')}" name="site_icon">
|
||||
<a class="input-right-icon layui-icon layui-icon-upload-drag" data-file="btn" data-type="png,jpg,jpeg" data-field="site_icon"></a>
|
||||
</div>
|
||||
<div class="help-block sub-span-blue">
|
||||
{:lang('建议上传 128x128 或 256x256 的 JPG,PNG,JPEG 图片,保存后会自动生成 48x48 的 ICO 文件 ~')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-row layui-col-space15 margin-bottom-5">
|
||||
<div class="layui-col-xs4 padding-bottom-0">
|
||||
<label class="layui-form-item margin-bottom-5 relative block">
|
||||
<span class="help-label"><b>{:lang('网站名称')}</b>Site Name</span>
|
||||
<input name="site_name" required placeholder="{:lang('请输入网站名称')}" vali-name="{:lang('网站名称')}" value="{:sysconf('site_name')}" class="layui-input">
|
||||
<span class="help-block">{:lang('网站名称将显示在浏览器的标签上 ~')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4 padding-bottom-0">
|
||||
<label class="layui-form-item margin-bottom-5 relative block">
|
||||
<span class="help-label"><b>{:lang('后台程序名称')}</b>App Name</span>
|
||||
<input name="app_name" required placeholder="{:lang('请输入程序名称')}" vali-name="{:lang('程序名称')}" value="{:sysconf('app_name')}" class="layui-input">
|
||||
<span class="help-block">{:lang('管理程序名称显示在后台左上标题处 ~')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4 padding-bottom-0">
|
||||
<label class="layui-form-item margin-bottom-5 relative block">
|
||||
<span class="help-label"><b>{:lang('后台程序版本')}</b>App Version</span>
|
||||
<input name="app_version" placeholder="{:lang('请输入程序版本')}" value="{:sysconf('app_version')}" class="layui-input">
|
||||
<span class="help-block">{:lang('管理程序版本显示在后台左上标题处 ~')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4 padding-top-0 padding-bottom-0">
|
||||
<label class="relative block">
|
||||
<span class="help-label"><b>{:lang('公安安备号')}</b>Beian</span>
|
||||
<input name="beian" placeholder="{:lang('请输入公安安备号')}" value="{:sysconf('beian')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4 padding-top-0 padding-bottom-0">
|
||||
<label class="relative block">
|
||||
<span class="help-label"><b>{:lang('网站备案号')}</b>Miitbeian</span>
|
||||
<input name="miitbeian" placeholder="{:lang('请输入网站备案号')}" value="{:sysconf('miitbeian')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4 padding-top-0 padding-bottom-0">
|
||||
<label class="relative block">
|
||||
<span class="help-label"><b>{:lang('网站版权信息')}</b>Copyright</span>
|
||||
<input name="site_copy" required placeholder="{:lang('请输入版权信息')}" vali-name="{:lang('版权信息')}" value="{:sysconf('site_copy')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs12 help-block padding-top-0">
|
||||
{:lang('网站备案号和公安备案号可以在<a target="_blank" href="https://beian.miit.gov.cn">备案管理中心</a>查询并获取,网站上线时必需配置备案号,备案号会链接到信息备案管理系统 ~')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
<button class="layui-btn" type="submit">{:lang('保存配置')}</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="{:lang('确定要取消修改吗?')}" data-close>{:lang('取消修改')}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$('[name=login_image]').uploadMultipleImage();
|
||||
require(['md5'], function (md5) {
|
||||
$('body').off('click', '#RefreshJwtKey').on('click', '#RefreshJwtKey', function () {
|
||||
$(this).parent().find('input').val(md5.hash(Date.toString() + Math.random()));
|
||||
});
|
||||
});
|
||||
layui.form.on('select(SiteTheme)', function (data) {
|
||||
var alls = '', prox = 'layui-layout-theme-', curt = prox + data.value;
|
||||
$(data.elem.options).map(function () {
|
||||
if (this.value !== data.value) alls += ' ' + prox + this.value;
|
||||
});
|
||||
$('.layui-layout-body').removeClass(alls).addClass(curt)
|
||||
});
|
||||
</script>
|
||||
@ -1,40 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card" data-table-id="FileTable">
|
||||
|
||||
<div class="layui-card-body padding-left-40">
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('文件名称')}</b>Name</span>
|
||||
<input maxlength="100" class="layui-input" name="name" value='{$vo.name|default=""}' required vali-name="文件名称" placeholder="请输入文件名称">
|
||||
</label>
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('文件大小')}</b>Size</span>
|
||||
<input maxlength="100" class="layui-input layui-bg-gray" value='{$vo.size|default=0|format_bytes}' readonly>
|
||||
</label>
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('存储方式')}</b>Type</span>
|
||||
<input maxlength="100" class="layui-input layui-bg-gray" value='{$types[$vo.type]??""}' readonly>
|
||||
</label>
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('文件哈希')}</b>Hash</span>
|
||||
<input maxlength="100" class="layui-input layui-bg-gray" value='{$vo.hash|default=""}' readonly>
|
||||
</label>
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('文件链接')}</b>Link</span>
|
||||
<input maxlength="100" class="layui-input layui-bg-gray" value='{$vo.xurl|default=""}' readonly>
|
||||
</label>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
|
||||
|
||||
<div class="layui-form-item text-center">
|
||||
<button class="layui-btn" type='submit'>{:lang('保存数据')}</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="{:lang('确定要取消编辑吗?')}" data-close>{:lang('取消编辑')}</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,64 +0,0 @@
|
||||
{extend name='table'}
|
||||
|
||||
{block name="button"}
|
||||
<!--{if auth("distinct")}-->
|
||||
<a data-table-id="FileTable" data-load='{:url("distinct")}' class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('清理重复')}</a>
|
||||
<!--{/if}-->
|
||||
<!--{if auth("remove")}-->
|
||||
<a data-confirm="{:lang('确定删除这些记录吗?')}" data-table-id="FileTable" data-action='{:url("remove")}' data-rule="id#{id}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量删除')}</a>
|
||||
<!--{/if}-->
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<div class="think-box-shadow">
|
||||
{include file='file/index_search'}
|
||||
<table id="FileTable" data-url="{:sysuri('index')}" data-target-search="form.form-search"></table>
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
$('#FileTable').layTable({
|
||||
even: true, height: 'full',
|
||||
sort: {field: 'id', type: 'desc'},
|
||||
cols: [[
|
||||
{checkbox: true, fixed: true},
|
||||
{field: 'id', title: 'ID', width: 80, align: 'center', sort: true},
|
||||
{field: 'name', title: '{:lang("文件名称")}', width: '12%', align: 'center'},
|
||||
{field: 'hash', title: '{:lang("文件哈希")}', width: '15%', align: 'center', templet: '<div><code>{{d.hash}}</code></div>'},
|
||||
{field: 'size', title: '{:lang("文件大小")}', align: 'center', width: '7%', sort: true, templet: '<div>{{-$.formatFileSize(d.size)}}</div>'},
|
||||
{field: 'xext', title: '{:lang("文件后缀")}', align: 'center', width: '7%', sort: true},
|
||||
{
|
||||
field: 'xurl', title: '{:lang("查看文件")}', width: '7%', align: 'center', templet: function (d) {
|
||||
if (typeof d.mime === 'string' && /^image\//.test(d.mime)) {
|
||||
return laytpl('<div><a target="_blank" data-tips-hover data-tips-image="{{d.xurl}}"><i class="layui-icon layui-icon-picture"></i></a></div>').render(d)
|
||||
}
|
||||
if (typeof d.mime === 'string' && /^video\//.test(d.mime)) {
|
||||
return laytpl('<div><a target="_blank" data-video-player="{{d.xurl}}" data-tips-text="{:lang(\'播放视频\')}"><i class="layui-icon layui-icon-video"></i></a></div>').render(d);
|
||||
}
|
||||
if (typeof d.mime === 'string' && /^audio\//.test(d.mime)) {
|
||||
return laytpl('<div><a target="_blank" data-video-player="{{d.xurl}}" data-tips-text="{:lang(\'播放音频\')}"><i class="layui-icon layui-icon-headset"></i></a></div>').render(d);
|
||||
}
|
||||
return laytpl('<div><a target="_blank" href="{{d.xurl}}" data-tips-text="{:lang(\'查看下载\')}"><i class="layui-icon layui-icon-file"></i></a></div>').render(d);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'isfast', title: '{:lang("上传方式")}', align: 'center', width: '8%', templet: function (d) {
|
||||
return d.isfast ? '<b class="color-green">{:lang("秒传")}</b>' : '<b class="color-blue">{:lang("普通")}</b>';
|
||||
}
|
||||
},
|
||||
{field: 'ctype', title: '{:lang("存储方式")}', align: 'center', width: '10%'},
|
||||
{field: 'create_at', title: '{:lang("创建时间")}', align: 'center', width: '15%', sort: true},
|
||||
{toolbar: '#toolbar', title: '{:lang("操作面板")}', align: 'center', minWidth: 150, fixed: 'right'}
|
||||
]]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="toolbar">
|
||||
<!--{if auth("edit")}-->
|
||||
<a class="layui-btn layui-btn-sm" data-modal="{:url('edit')}?id={{d.id}}" data-title="编辑文件信息">{:lang("编 辑")}</a>
|
||||
<!--{/if}-->
|
||||
<!--{if auth("remove")}-->
|
||||
<a class="layui-btn layui-btn-sm layui-btn-danger" data-action="{:url('remove')}" data-value="id#{{d.id}}">{:lang("删 除")}</a>
|
||||
<!--{/if}-->
|
||||
</script>
|
||||
{/block}
|
||||
@ -1,58 +0,0 @@
|
||||
<fieldset>
|
||||
<legend>{:lang('条件搜索')}</legend>
|
||||
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('文件名称')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input name="name" value="{$get.name|default=''}" placeholder="{:lang('请输入文件名称')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('文件哈希')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input name="hash" value="{$get.hash|default=''}" placeholder="{:lang('请输入文件哈希')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('文件后缀')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="xext" lay-search class="layui-select">
|
||||
<option value=''>-- {:lang('全部')} --</option>
|
||||
{foreach $xexts as $v}{if isset($get.xext) and $k eq $get.xext}
|
||||
<option selected value="{$v}">{$v}</option>
|
||||
{else}
|
||||
<option value="{$v}">{$v}</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('存储方式')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="type" lay-search class="layui-select">
|
||||
<option value=''>-- {:lang('全部')} --</option>
|
||||
{foreach $types as $k=>$v}{if isset($get.type) and $k eq $get.type}
|
||||
<option selected value="{$k}">{$v}</option>
|
||||
{else}
|
||||
<option value="{$k}">{$v}</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('创建时间')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<input data-date-range name="create_at" value="{$get.create_at|default=''}" placeholder="{:lang('请选择创建时间')}" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<button class="layui-btn layui-btn-primary"><i class="layui-icon"></i> {:lang('搜 索')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
@ -1,35 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<title>{block name="title"}{$title|default=''}{if !empty($title)} · {/if}{:sysconf('site_name')}{/block}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=0.4">
|
||||
<link rel="stylesheet" href="__ROOT__/static/plugs/layui/css/layui.css?at={:date('md')}">
|
||||
<link rel="stylesheet" href="__ROOT__/static/theme/css/iconfont.css?at={:date('md')}">
|
||||
<link rel="stylesheet" href="__ROOT__/static/theme/css/console.css?at={:date('md')}">
|
||||
<link rel="stylesheet" href="__ROOT__/static/extra/style.css?at={:date('md')}">
|
||||
{if file_exists(syspath("public/static/extra/icon/iconfont.css"))}
|
||||
<link rel="stylesheet" href="__ROOT__/static/extra/icon/iconfont.css?at={:date('md')}">
|
||||
{/if}
|
||||
{block name="style"}{/block}
|
||||
<script src="__ROOT__/static/plugs/jquery/pace.min.js"></script>
|
||||
<script src="{:url('admin/api.plugs/script',[],false,false)}"></script>
|
||||
</head>
|
||||
<body class="layui-layout-body">
|
||||
{block name='body'}
|
||||
<div class="layui-layout layui-layout-admin layui-layout-left-hide">
|
||||
<div class="layui-body think-bg-white margin-0 padding-0" style="top:0">{block name='content'}{/block}</div>
|
||||
</div>
|
||||
{/block}
|
||||
<script src="__ROOT__/static/plugs/layui/layui.js"></script>
|
||||
<script src="__ROOT__/static/plugs/require/require.js"></script>
|
||||
<script src="__ROOT__/static/admin.js"></script>
|
||||
<script src="__ROOT__/static/extra/script.js"></script>
|
||||
{block name='script'}{/block}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,44 +0,0 @@
|
||||
<div class="layui-header">
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a class="text-center" data-target-menu-type>
|
||||
<i class="layui-icon layui-icon-spread-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a class="layui-logo-hide layui-elip" href="{:sysuri('@')}" title="{:sysconf('app_name')}">
|
||||
<span class="headimg headimg-no headimg-xs" data-lazy-src="{:sysconf('site_icon')}"></span>
|
||||
</a>
|
||||
</li>
|
||||
{foreach $menus as $one}
|
||||
<li class="layui-nav-item">
|
||||
<a data-menu-node="m-{$one.id}" data-open="{$one.url}"><span>{$one.title|default=''}</span></a>
|
||||
</li>
|
||||
{/foreach}
|
||||
</ul>
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<li lay-unselect class="layui-nav-item"><a data-reload><i class="layui-icon layui-icon-refresh-3"></i></a></li>
|
||||
{if session('user.username')}
|
||||
<li class="layui-nav-item">
|
||||
<dl class="layui-nav-child">
|
||||
<dd lay-unselect><a data-modal="{:sysuri('admin/index/info',['id'=>session('user.id')])}"><i class="layui-icon layui-icon-set-fill"></i> {:lang('基本资料')}</a></dd>
|
||||
<dd lay-unselect><a data-modal="{:sysuri('admin/index/pass',['id'=>session('user.id')])}"><i class="layui-icon layui-icon-component"></i> {:lang('安全设置')}</a></dd>
|
||||
{if isset($super) and $super}
|
||||
<dd lay-unselect><a data-load="{:sysuri('admin/api.system/push')}"><i class="layui-icon layui-icon-template-1"></i> {:lang('缓存加速')}</a></dd>
|
||||
<dd lay-unselect><a data-load="{:sysuri('admin/api.system/clear')}"><i class="layui-icon layui-icon-fonts-clear"></i> {:lang('清理缓存')}</a></dd>
|
||||
{/if}
|
||||
<dd lay-unselect><a data-width="520px" data-modal="{:sysuri('admin/index/theme')}"><i class="layui-icon layui-icon-theme"></i> {:lang('配色方案')}</a></dd>
|
||||
<dd lay-unselect><a data-load="{:sysuri('admin/login/out')}" data-confirm="{:lang('确定要退出登录吗?')}"><i class="layui-icon layui-icon-release"></i> {:lang('退出登录')}</a></dd>
|
||||
</dl>
|
||||
<a class="layui-elip">
|
||||
<span class="headimg" data-lazy-src="{:htmlentities(session('user.headimg'))}"></span>
|
||||
<span>{:htmlentities(lang(session('user.nickname')?:session('user.username')))}</span>
|
||||
</a>
|
||||
</li>
|
||||
{else}
|
||||
<li class="layui-nav-item">
|
||||
<a data-href="{:sysuri('admin/login/index')}"><i class="layui-icon layui-icon-username"></i> {:lang('立即登录')}</a>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
@ -1,36 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card" id="theme">
|
||||
<div class="layui-card-body padding-left-40">
|
||||
|
||||
<div class="layui-form-item margin-bottom-5 label-required-prev">
|
||||
<div class="help-label"><b>后台配色方案</b>Theme Style</div>
|
||||
<div class="layui-textarea think-bg-gray" style="min-height:unset">
|
||||
{foreach $themes as $k=>$v}
|
||||
<label class="think-radio">
|
||||
{if isset($theme) and $theme eq $k}
|
||||
<input name="site_theme" type="radio" value="{$k}" lay-ignore checked> {$v}
|
||||
{else}
|
||||
<input name="site_theme" type="radio" value="{$k}" lay-ignore> {$v}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
<p class="help-block">切换配色方案,需要保存成功后配色方案才会永久生效,下次登录也会有效哦 ~</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
<button class="layui-btn" type="submit">保存配置</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-close>取消修改</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$('form#theme input[name=site_theme]').on('click', function () {
|
||||
var alls = '', that = this, prox = 'layui-layout-theme-', curt = prox + that.value;
|
||||
$('form#theme input[name=site_theme]').map(function () {
|
||||
if (this.value !== that.value) alls += ' ' + prox + this.value;
|
||||
});
|
||||
$('.layui-layout-body').removeClass(alls).addClass(curt)
|
||||
});
|
||||
</script>
|
||||
@ -1,57 +0,0 @@
|
||||
{extend name="index/index"}
|
||||
|
||||
{block name='style'}
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
|
||||
<script>if (location.href.indexOf('#') > -1) location.replace(location.href.split('#')[0])</script>
|
||||
<link rel="stylesheet" href="__ROOT__/static/theme/css/login.css">
|
||||
{/block}
|
||||
|
||||
{block name="body"}
|
||||
<div class="login-container" {$loginStyle|raw}>
|
||||
<div class="header notselect layui-hide-xs">
|
||||
<a href="{:url('@')}" class="title">{:sysconf('app_name')}<span>{:sysconf('app_version')}</span></a>
|
||||
{notempty name='runtimeMode'}
|
||||
<a class="pull-right layui-anim layui-anim-fadein" target="_blank" href='https://gitee.com/zoujingli/ThinkAdmin'>
|
||||
<img src='https://gitee.com/zoujingli/ThinkAdmin/widgets/widget_1.svg' alt='Fork me on Gitee'>
|
||||
</a>
|
||||
{/notempty}
|
||||
</div>
|
||||
<form data-login-form onsubmit="return false" method="post" class="layui-anim layui-anim-upbit" autocomplete="off">
|
||||
<h2 class="notselect">{:sysconf('login_name')?:'系统管理'}</h2>
|
||||
<ul>
|
||||
<li class="username">
|
||||
<label class="label-required-null">
|
||||
<i class="layui-icon layui-icon-username"></i>
|
||||
<input class="layui-input" required pattern="^\S{4,}$" vali-name="登录账号" name="username" autofocus autocomplete="off" placeholder="登录账号">
|
||||
</label>
|
||||
</li>
|
||||
<li class="password">
|
||||
<label class="label-required-null">
|
||||
<i class="layui-icon layui-icon-password"></i>
|
||||
<input class="layui-input" required pattern="^\S{4,}$" vali-name="登录密码" name="password" maxlength="32" type="password" autocomplete="off" placeholder="登录密码" lay-affix="eye">
|
||||
</label>
|
||||
</li>
|
||||
<li class="verify layui-hide">
|
||||
<label class="inline-block relative label-required-null">
|
||||
<i class="layui-icon layui-icon-picture-fine"></i>
|
||||
<input class="layui-input" required pattern="^\S{4,}$" name="verify" maxlength="4" autocomplete="off" vali-name="验证码" placeholder="验证码">
|
||||
</label>
|
||||
<label data-captcha="{:url('admin/login/captcha',[],false)}" data-field-verify="verify" data-field-uniqid="uniqid" data-captcha-type="{$captchaType}" data-captcha-token="{$captchaToken}"></label>
|
||||
</li>
|
||||
<li class="text-center padding-top-20">
|
||||
<button type="submit" class="layui-btn layui-disabled full-width" data-form-loaded="立即登入">正在载入</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
<div class="footer notselect">
|
||||
<p class="layui-hide-xs">推荐使用 <a target="_blank" href="https://www.google.cn/chrome">Google Chrome</a> 或 <a target="_blank" href="https://www.microsoft.com/zh-cn/edge#platform">Microsoft Edge</a> 浏览器访问</p>
|
||||
{:sysconf('site_copy')}
|
||||
{if sysconf('beian')}<span class="padding-5">|</span><a target="_blank" href="https://www.beian.gov.cn/portal/registerSystemInfo">{:sysconf('beian')}</a>{/if}
|
||||
{if sysconf('miitbeian')}<span class="padding-5">|</span><a target="_blank" href="https://beian.miit.gov.cn/">{:sysconf('miitbeian')}</a>{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block name='script'}
|
||||
<script src="__ROOT__/static/login.js"></script>
|
||||
{/block}
|
||||
@ -1,97 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card" data-table-id="MenuTable">
|
||||
|
||||
<div class="layui-card-body">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label label-required-next">{:lang('上级菜单')}</label>
|
||||
<div class="layui-input-block">
|
||||
<select name='pid' class='layui-select' lay-search>
|
||||
{foreach $menus as $menu}{eq name='menu.id' value='$vo.pid|default=0'}
|
||||
<option selected value='{$menu.id}'>{$menu.spl|raw}{$menu.title}</option>
|
||||
{else}
|
||||
<option value='{$menu.id}'>{$menu.spl|raw}{$menu.title}</option>
|
||||
{/eq}{/foreach}
|
||||
</select>
|
||||
<p class="help-block"><b>{:lang('必选')}</b>,{:lang('请选择上级菜单或顶级菜单 ( 目前最多支持三级菜单 )')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{:lang('菜单名称')}</label>
|
||||
<div class="layui-input-block">
|
||||
<input name="title" value='{$vo.title|default=""}' required vali-name="{:lang('菜单名称')}" placeholder="{:lang('请输入菜单名称')}" class="layui-input">
|
||||
<p class="help-block"><b>{:lang('必选')}</b>,{:lang('请填写菜单名称 ( 如:系统管理 ),建议字符不要太长,一般 4-6 个汉字')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{:lang('菜单链接')}</label>
|
||||
<div class="layui-input-block">
|
||||
<input onblur="this.value=this.value === ''?'#':this.value" name="url" required vali-name="{:lang('菜单链接')}" placeholder="{:lang('请输入菜单链接')}" value="{$vo.url|default='#'}" class="layui-input">
|
||||
<p class="help-block">
|
||||
<b>{:lang('必选')}</b>,{:lang('请填写链接地址或选择系统节点 ( 如:https://domain.com/admin/user/index.html 或 admin/user/index )')}
|
||||
<br>{:lang('当填写链接地址时,以下面的 "权限节点" 来判断菜单自动隐藏或显示,注意未填写 "权限节点" 时将不会隐藏该菜单哦')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{:lang('链接参数')}</label>
|
||||
<div class="layui-input-block">
|
||||
<input name="params" placeholder="{:lang('请输入链接参数')}" value="{$vo.params|default=''}" class="layui-input">
|
||||
<p class="help-block"><b>{:lang('可选')}</b>,{:lang('设置菜单链接的 GET 访问参数 ( 如:name=1&age=3 )')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{:lang('权限节点')}</label>
|
||||
<div class="layui-input-block">
|
||||
<input name="node" placeholder="{:lang('请输入权限节点')}" value="{$vo.node|default=''}" class="layui-input">
|
||||
<p class="help-block"><b>{:lang('可选')}</b>,{:lang('请填写系统权限节点 ( 如:admin/user/index ),未填写时默认解释"菜单链接"判断是否拥有访问权限;')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{:lang('菜单图标')}</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-input-inline">
|
||||
<input placeholder="{:lang('请输入或选择图标')}" name="icon" value='{$vo.icon|default=""}' class="layui-input">
|
||||
</div>
|
||||
<span style="padding:0 12px;min-width:45px" class='layui-btn layui-btn-primary'>
|
||||
<i style="font-size:1.2em;margin:0;float:none" class='{$vo.icon|default=""}'></i>
|
||||
</span>
|
||||
<button data-icon='icon' type='button' class='layui-btn layui-btn-primary'>{:lang('选择图标')}</button>
|
||||
<p class="help-block"><b>{:lang('可选')}</b>,{:lang('设置菜单选项前置图标,目前支持 layui 字体图标及 iconfont 定制字体图标。')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
|
||||
|
||||
<div class="layui-form-item text-center">
|
||||
<button class="layui-btn" type='submit'>{:lang('保存数据')}</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="{:lang('确定要取消编辑吗?')}" data-close>{:lang('取消编辑')}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
require(['jquery.autocompleter'], function () {
|
||||
$('[name="icon"]').on('change', function () {
|
||||
$(this).parent().next().find('i').get(0).className = this.value
|
||||
}), $('input[name=url]').autocompleter({
|
||||
limit: 6, highlightMatches: true, template: '{{ label }} <span> {{ title }} </span>', callback: function (node) {
|
||||
$('input[name=node]').val(node);
|
||||
}, source: (function (subjects, data) {
|
||||
for (var i in subjects) data.push({value: subjects[i].node, label: subjects[i].node, title: subjects[i].title});
|
||||
return data;
|
||||
})(JSON.parse('{$nodes|raw|json_encode}'), [])
|
||||
}), $('input[name=node]').autocompleter({
|
||||
limit: 5, highlightMatches: true, template: '{{ label }} <span> {{ title }} </span>', source: (function (subjects, data) {
|
||||
for (var i in subjects) data.push({value: subjects[i].node, label: subjects[i].node, title: subjects[i].title});
|
||||
return data;
|
||||
})(JSON.parse('{$auths|raw|json_encode}'), [])
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -1,119 +0,0 @@
|
||||
{extend name='table'}
|
||||
|
||||
{block name="button"}
|
||||
<!--{if $type eq 'index' and auth("add")}-->
|
||||
<button data-modal='{:url("add")}' data-table-id="MenuTable" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('添加菜单')}</button>
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if $type eq 'index' and auth("state")}-->
|
||||
<button data-action='{:url("state")}' data-table-id="MenuTable" data-rule="id#{sps};status#0" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('禁用菜单')}</button>
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if $type eq 'recycle' and auth("state")}-->
|
||||
<button data-action='{:url("state")}' data-table-id="MenuTable" data-rule="id#{spp};status#1" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('激活菜单')}</button>
|
||||
<!--{/if}-->
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<div class="layui-tab layui-tab-card">
|
||||
<ul class="layui-tab-title">
|
||||
{foreach ['index'=>lang('全部菜单'),'recycle'=>lang('回 收 站')] as $k=>$v}
|
||||
{if isset($type) and $type eq $k and isset($pid) and $pid == ''}
|
||||
<li class="layui-this" data-open="{:url('index')}?type={$k}">{$v}</li>
|
||||
{else}
|
||||
<li data-open="{:url('index')}?type={$k}">{$v}</li>
|
||||
{/if}{/foreach}
|
||||
|
||||
<!--顶级菜单-->
|
||||
<!--{foreach $menupList as $k=>$v}-->
|
||||
<li data-open="{:url('index')}?type=index&pid={$v.id}" {if isset($pid) and $pid eq $v.id}class="layui-this"{/if} >{$v.title}</li>
|
||||
<!--{/foreach}-->
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<table id="MenuTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$('#MenuTable').layTable({
|
||||
even: true, height: 'full', page: false,
|
||||
sort: {field: 'sort desc,id', type: 'asc'},
|
||||
where: {type: '{$type|default="index"}', pid: '{$pid|default=""}'},
|
||||
filter: function (items) {
|
||||
var type = this.where.type;
|
||||
return items.filter(function (item) {
|
||||
return !(type === 'index' && parseInt(item.status) === 0);
|
||||
});
|
||||
},
|
||||
cols: [[
|
||||
{checkbox: true, field: 'sps'},
|
||||
{field: 'sort', title: '{:lang("排序权重")}', width: 100, align: 'center', templet: '#SortInputTpl'},
|
||||
{field: 'icon', title: '{:lang("图 标")}', width: 80, align: 'center', templet: '<div><i class="{{d.icon}} font-s18"></i></div>'},
|
||||
{field: 'title', title: '{:lang("菜单名称")}', minWidth: 220, templet: '<div><span class="color-desc">{{d.spl}}</span>{{d.title}}</div>'},
|
||||
{field: 'url', title: '{:lang("跳转链接")}', minWidth: 200},
|
||||
{field: 'status', title: '{:lang("使用状态")}', minWidth: 120, align: 'center', templet: '#StatusSwitchTpl'},
|
||||
{toolbar: '#toolbar', title: '{:lang("操作面板")}', minWidth: 150, align: 'center', fixed: 'right'},
|
||||
]]
|
||||
});
|
||||
|
||||
// 数据状态切换操作
|
||||
layui.form.on('switch(StatusSwitch)', function (object) {
|
||||
object.data = {status: object.elem.checked > 0 ? 1 : 0};
|
||||
object.data.id = object.value.split('|')[object.data.status] || object.value;
|
||||
$.form.load("{:url('state')}", object.data, 'post', function (ret) {
|
||||
if (ret.code < 1) $.msg.error(ret.info, 3, function () {
|
||||
$('#MenuTable').trigger('reload');
|
||||
}); else {
|
||||
$('#MenuTable').trigger('reload');
|
||||
}
|
||||
return false;
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 数据状态切换模板 -->
|
||||
<script type="text/html" id="StatusSwitchTpl">
|
||||
<!--{if auth("state")}-->
|
||||
{{# if( "{$type|default='index'}"==='index' || (d.spc<1 || d.status<1)){ }}
|
||||
<input type="checkbox" value="{{d.sps}}|{{d.spp}}" lay-text="{:lang('已激活')}|{:lang('已禁用')}" lay-filter="StatusSwitch" lay-skin="switch" {{-d.status>0?'checked':''}}>
|
||||
{{# }else{ }}
|
||||
{{-d.status ? '<b class="color-green">{:lang('已激活')}</b>' : '<b class="color-red">{:lang('已禁用')}</b>'}}
|
||||
{{# } }}
|
||||
<!--{else}-->
|
||||
{{-d.status ? '<b class="color-green">{:lang('已激活')}</b>' : '<b class="color-red">{:lang('已禁用')}</b>'}}
|
||||
<!--{/if}-->
|
||||
</script>
|
||||
|
||||
<!-- 列表排序权重模板 -->
|
||||
<script type="text/html" id="SortInputTpl">
|
||||
<input type="number" min="0" data-blur-number="0" data-action-blur="{:sysuri()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
|
||||
</script>
|
||||
|
||||
<!-- 操控面板的模板 -->
|
||||
<script type="text/html" id="toolbar">
|
||||
|
||||
<!-- {if isset($type) and $type eq 'index'} -->
|
||||
<!-- {if auth('add')} -->
|
||||
{{# if(d.spt<2){ }}
|
||||
<a class="layui-btn layui-btn-sm layui-btn-primary" data-title="添加系统菜单" data-modal='{:url("add")}?pid={{d.id}}'>{:lang('添 加')}</a>
|
||||
{{# }else{ }}
|
||||
<a class="layui-btn layui-btn-sm layui-btn-disabled">{:lang('添 加')}</a>
|
||||
{{# } }}
|
||||
<!-- {/if} -->
|
||||
{if auth('edit')}
|
||||
<a class="layui-btn layui-btn-sm" data-event-dbclick data-title="编辑系统菜单" data-modal='{:url("edit")}?id={{d.id}}'>{:lang('编 辑')}</a>
|
||||
{/if}
|
||||
<!-- {else} -->
|
||||
{if auth('remove')}
|
||||
{{# if( (d.spc<1 || d.status<1)){ }}
|
||||
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="{:lang('确定要删除菜单吗?')}" data-action="{:url('remove')}" data-value="id#{{d.sps}}">{:lang('删 除')}</a>
|
||||
{{# }else{ }}
|
||||
<a class="layui-btn layui-btn-disabled layui-btn-sm">{:lang('删 除')}</a>
|
||||
{{# } }}
|
||||
{/if}
|
||||
<!-- {/if} -->
|
||||
|
||||
</script>
|
||||
{/block}
|
||||
@ -1,47 +0,0 @@
|
||||
{extend name='table'}
|
||||
|
||||
{block name="button"}
|
||||
<!--{if auth("remove")}-->
|
||||
<button data-table-id="OplogTable" data-action='{:url("remove")}' data-rule="id#{id}" data-confirm="确定要删除选中的日志吗?" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量删除')}</button>
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if auth("clear")}-->
|
||||
<button data-table-id="OplogTable" data-load='{:url("clear")}' data-confirm="确定要清空所有日志吗?" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('清空数据')}</button>
|
||||
<!--{/if}-->
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<div class="think-box-shadow">
|
||||
{include file='oplog/index_search'}
|
||||
<table id="OplogTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block name='script'}
|
||||
<script>
|
||||
$(function () {
|
||||
$('#OplogTable').layTable({
|
||||
even: true, height: 'full',
|
||||
sort: {field: 'id', type: 'desc'},
|
||||
cols: [[
|
||||
{checkbox: true},
|
||||
{field: 'id', title: 'ID', width: 80, sort: true, align: 'center'},
|
||||
{field: 'username', title: '{:lang("操作账号")}', minWidth: 100, width: '8%', sort: true, align: 'center'},
|
||||
{field: 'node', title: '{:lang("操作节点")}', minWidth: 120},
|
||||
{field: 'action', title: '{:lang("操作行为")}', minWidth: 120},
|
||||
{field: 'content', title: '{:lang("操作内容")}', minWidth: 150},
|
||||
{field: 'geoip', title: '{:lang("访问地址")}', minWidth: 100, width: '10%'},
|
||||
{field: 'geoisp', title: '{:lang("网络服务商")}', minWidth: 100},
|
||||
{field: 'create_at', title: '{:lang("创建时间")}', minWidth: 170, align: 'center', sort: true},
|
||||
{toolbar: '#toolbar', title: '{:lang("操作面板")}', align: 'center', minWidth: 80, width: '8%', fixed: 'right'}
|
||||
]]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="toolbar">
|
||||
<!--{if auth('remove')}-->
|
||||
<a data-action='{:url("remove")}' data-value="id#{{d.id}}" data-confirm="确认要删除这条记录吗?" class="layui-btn layui-btn-sm layui-btn-danger">{:lang('删 除')}</a>
|
||||
<!--{/if}-->
|
||||
</script>
|
||||
{/block}
|
||||
@ -1,87 +0,0 @@
|
||||
<fieldset>
|
||||
<legend>{:lang('条件搜索')}</legend>
|
||||
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('操作账号')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name='username' lay-search class="layui-select">
|
||||
<option value=''>-- {:lang('全部')} --</option>
|
||||
{foreach $users as $user}{if $user eq input('get.username')}
|
||||
<option selected value="{$user}">{$user}</option>
|
||||
{else}
|
||||
<option value="{$user}">{$user}</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('操作行为')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="action" lay-search class="layui-select">
|
||||
<option value=''>-- {:lang('全部')} --</option>
|
||||
{foreach $actions as $action}{if $action eq input('get.action')}
|
||||
<option selected value="{$action}">{$action}</option>
|
||||
{else}
|
||||
<option value="{$action}">{$action}</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('操作节点')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input name="node" value="{$get.node|default=''}" placeholder="{:lang('请输入操作节点')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('操作内容')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input name="content" value="{$get.content|default=''}" placeholder="{:lang('请输入操作内容')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('访问地址')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input name="geoip" value="{$get.geoip|default=''}" placeholder="{:lang('请输入访问地址')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('创建时间')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input data-date-range name="create_at" value="{$get.create_at|default=''}" placeholder="{:lang('请选择创建时间')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<button type="submit" class="layui-btn layui-btn-primary"><i class="layui-icon"></i> {:lang('搜 索')}</button>
|
||||
<button type="button" data-form-export="{:url('index')}?type={$type|default=''}" class="layui-btn layui-btn-primary">
|
||||
<i class="layui-icon layui-icon-export"></i> {:lang('导 出')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
|
||||
<script>
|
||||
require(['excel'], function (excel) {
|
||||
excel.bind(function (data) {
|
||||
|
||||
// 设置表格内容
|
||||
data.forEach(function (item, index) {
|
||||
data[index] = [item.id, item.username, item.node, item.geoip, item.geoisp, item.action, item.content, item.create_at];
|
||||
});
|
||||
|
||||
// 设置表头内容
|
||||
data.unshift(['ID', '{:lang("操作账号")}', '{:lang("操作节点")}', '{:lang("访问地址")}', '{:lang("网络服务商")}', '{:lang("操作行为")}', '{:lang("操作内容")}', '{:lang("创建时间")}']);
|
||||
|
||||
// 应用表格样式
|
||||
return this.withStyle(data, {A: 60, B: 80, C: 99, E: 120, G: 120});
|
||||
|
||||
}, '{:lang("操作日志")}' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));
|
||||
});
|
||||
</script>
|
||||
@ -1,131 +0,0 @@
|
||||
{extend name='table'}
|
||||
|
||||
{block name="button"}
|
||||
|
||||
{if isset($super) and $super}
|
||||
|
||||
<a data-table-id="QueueTable" class="layui-btn layui-btn-sm layui-btn-primary" data-queue="{:url('admin/api.plugs/optimize')}">{:lang('优化数据库')}</a>
|
||||
|
||||
{if isset($iswin) and ($iswin or php_sapi_name() eq 'cli')}
|
||||
<button data-load='{:url("admin/api.queue/start")}' class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('开启服务')}</button>
|
||||
<button data-load='{:url("admin/api.queue/stop")}' class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('关闭服务')}</button>
|
||||
{/if}
|
||||
|
||||
{if auth("clean")}
|
||||
<button data-table-id="QueueTable" data-queue='{:url("clean")}' class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('定时清理')}</button>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
|
||||
{if auth("remove")}
|
||||
<button data-table-id="QueueTable" data-action='{:url("remove")}' data-rule="id#{id}" data-confirm="{:lang('确定批量删除记录吗?')}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量删除')}</button>
|
||||
{/if}
|
||||
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<div class="think-box-notify" type="info">
|
||||
|
||||
<!--{if isset($super) and $super}-->
|
||||
<b>{:lang('服务状态')}:</b><b class="margin-right-5" data-queue-message><span class="color-desc">{:lang('检查中')}</span></b>
|
||||
<b data-tips-text="{:lang('点击可复制【服务启动指令】')}" class="layui-icon pointer margin-right-20" data-copy="{$command|default=''}"></b>
|
||||
<script>$('[data-queue-message]').load('{:sysuri("admin/api.queue/status")}');</script>
|
||||
<!--{/if}-->
|
||||
|
||||
<b>{:lang('任务统计')}:</b>{:lang('待处理 %s 个任务,处理中 %s 个任务,已完成 %s 个任务,已失败 %s 个任务。', [
|
||||
'<b class="color-text" data-extra="pre">..</b>',
|
||||
'<b class="color-blue" data-extra="dos">..</b>',
|
||||
'<b class="color-green" data-extra="oks">..</b>',
|
||||
'<b class="color-red" data-extra="ers">..</b>'
|
||||
])}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="think-box-shadow">
|
||||
{include file='queue/index_search'}
|
||||
<table id="QueueTable" data-line="2" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block name='script'}
|
||||
<script>
|
||||
$(function () {
|
||||
$('#QueueTable').layTable({
|
||||
even: true, height: 'full',
|
||||
sort: {field: 'loops_time desc,id', type: 'desc'},
|
||||
// 扩展数据处理,需要返回原 items 数据
|
||||
filter: function (items, result) {
|
||||
return result && result.extra && $('[data-extra]').map(function () {
|
||||
this.innerHTML = result.extra[this.dataset.extra] || 0;
|
||||
}), items;
|
||||
},
|
||||
cols: [[
|
||||
{checkbox: true, fixed: 'left'},
|
||||
{
|
||||
field: 'id', title: '{:lang("任务名称")}', width: '25%', sort: true, templet: function (d) {
|
||||
if (d.loops_time > 0) {
|
||||
d.one = '<span class="layui-badge think-bg-blue">循</span>';
|
||||
} else {
|
||||
d.one = '<span class="layui-badge think-bg-red">次</span>';
|
||||
}
|
||||
if (parseInt(d.rscript) === 1) {
|
||||
d.two = '<span class="layui-badge layui-bg-green">复</span>';
|
||||
} else {
|
||||
d.two = '<span class="layui-badge think-bg-violet">单</span>';
|
||||
}
|
||||
return laytpl('{{-d.one}}任务编号:<b>{{d.code}}</b><br>{{-d.two}}任务名称:{{d.title}}').render(d);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'exec_time', title: '{:lang("任务计划")}', width: '25%', templet: function (d) {
|
||||
d.html = '执行指令:' + d.command + '<br>计划执行:' + d.exec_time;
|
||||
if (d.loops_time > 0) {
|
||||
return d.html + ' ( 每 <b class="color-blue">' + d.loops_time + '</b> 秒 ) ';
|
||||
} else {
|
||||
return d.html + ' <span class="color-desc">( 单次任务 )</span> ';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'loops_time', title: '{:lang("任务状态")}', width: '30%', templet: function (d) {
|
||||
d.html = ([
|
||||
'<span class="pull-left layui-badge layui-badge-middle layui-bg-gray">未知</span>',
|
||||
'<span class="pull-left layui-badge layui-badge-middle layui-bg-black">等待</span>',
|
||||
'<span class="pull-left layui-badge layui-badge-middle layui-bg-blue">执行</span>',
|
||||
'<span class="pull-left layui-badge layui-badge-middle layui-bg-green">完成</span>',
|
||||
'<span class="pull-left layui-badge layui-badge-middle layui-bg-red">失败</span>',
|
||||
][d.status] || '') + '执行时间:';
|
||||
d.enter_time = d.enter_time || '';
|
||||
d.outer_time = d.outer_time || '0.0000';
|
||||
if (d.enter_time.length > 12) {
|
||||
d.html += d.enter_time.substring(12) + '<span class="color-desc"> ( ' + d.outer_time + ' ) </span>';
|
||||
d.html += ' 已执行 <b class="color-blue">' + (d.attempts || 0) + '</b> 次';
|
||||
} else {
|
||||
d.html += '<span class="color-desc">任务未执行</span>'
|
||||
}
|
||||
return d.html + '<br>执行结果:<span class="color-blue">' + (d.exec_desc || '<span class="color-desc">未获取到执行结果</span>') + '</span>';
|
||||
}
|
||||
},
|
||||
{toolbar: '#toolbar', title: '{:lang("操作面板")}', align: 'center', minWidth: 210, fixed: 'right'}
|
||||
]]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="toolbar">
|
||||
|
||||
<!--{if auth('redo')}-->
|
||||
{{# if(d.status===4||d.status===3){ }}
|
||||
<a class="layui-btn layui-btn-sm" data-confirm="确定要重置该任务吗?" data-queue="{:url('redo')}?code={{d.code}}">{:lang('重 置')}</a>
|
||||
{{# }else{ }}
|
||||
<a class="layui-btn layui-btn-sm layui-btn-disabled">{:lang('重 置')}</a>
|
||||
{{# } }}
|
||||
<!--{/if}-->
|
||||
|
||||
<!--{if auth('remove')}-->
|
||||
<a class='layui-btn layui-btn-sm layui-btn-danger' data-confirm="{:lang('确定要删除该记录吗?')}" data-action='{:url("remove")}' data-value="id#{{d.id}}">{:lang('删 除')}</a>
|
||||
<!--{/if}-->
|
||||
|
||||
<a class='layui-btn layui-btn-sm layui-btn-normal' onclick="$.loadQueue('{{d.code}}',false,this)">{:lang('日 志')}</a>
|
||||
</script>
|
||||
{/block}
|
||||
@ -1,45 +0,0 @@
|
||||
<fieldset>
|
||||
<legend>{:lang('条件搜索')}</legend>
|
||||
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('编号名称')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input name="title" value="{$get.title|default=''}" placeholder="{:lang('请输入名称或编号')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('任务指令')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input name="command" value="{$get.command|default=''}" placeholder="{:lang('请输入任务指令')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('任务状态')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<select name="status" class="layui-select">
|
||||
<option value=''>-- {:lang('全部')} --</option>
|
||||
{foreach ['1'=>lang('等待处理'),'2'=>lang('正在处理'),'3'=>lang('处理完成'),'4'=>lang('处理失败')] as $k=>$v}
|
||||
{if isset($get.status) and $get.status eq $k}
|
||||
<option selected value="{$k}">{$v}</option>
|
||||
{else}
|
||||
<option value="{$k}">{$v}</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('计划时间')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input data-date-range name="exec_time" value="{$get.exec_time|default=''}" placeholder="{:lang('请选择计划时间')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<button class="layui-btn layui-btn-primary"><i class="layui-icon"></i> {:lang('搜 索')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
@ -1,114 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card" data-table-id="UserTable">
|
||||
<div class="layui-card-body padding-left-40">
|
||||
|
||||
<fieldset class="layui-bg-gray">
|
||||
<legend><b class="layui-badge think-bg-violet">{:lang('用户账号')}</b></legend>
|
||||
|
||||
<div class="layui-row layui-col-space15">
|
||||
<div class="layui-col-xs2 text-center padding-top-15">
|
||||
<input type="hidden" data-cut-width="500" data-cut-height="500" data-max-width="500" data-max-height="500" name="headimg" value="{$vo.headimg|default=''}">
|
||||
<script>$('[name=headimg]').uploadOneImage();</script>
|
||||
</div>
|
||||
<div class="layui-col-xs5">
|
||||
<label class="block relative">
|
||||
<span class="help-label"><b>{:lang('登录账号')}</b>User Name</span>
|
||||
{if isset($vo) and isset($vo.username)}
|
||||
<input name="username" value='{$vo.username|default=""}' required readonly class="layui-input think-bg-gray">
|
||||
{else}
|
||||
<input name="username" value='{$vo.username|default=""}' required pattern="^.{4,}$" vali-name="{:lang('登录账号')}" placeholder="{:lang('请输入登录账号')}" class="layui-input">
|
||||
{/if}
|
||||
<span class="help-block">{:lang('登录账号不能少于4位字符,创建后不能再次修改.')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs5">
|
||||
<label class="block relative">
|
||||
<span class="help-label"><b>{:lang('用户名称')}</b>Nick Name</span>
|
||||
<input name="nickname" value='{$vo.nickname|default=""}' required vali-name="{:lang('用户名称')}" placeholder="{:lang('请输入用户名称')}" class="layui-input">
|
||||
<span class="help-block">{:lang('用于区分用户数据的用户名称,请尽量不要重复.')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
{if !empty($bases) || !empty($auths)}
|
||||
<fieldset class="layui-bg-gray">
|
||||
<legend><b class="layui-badge think-bg-violet">{:lang('用户权限')}</b></legend>
|
||||
{if !empty($bases)}
|
||||
<div class="layui-form-item">
|
||||
<div class="help-label"><b>{:lang('角色身份')}</b>Role Identity</div>
|
||||
<div class="layui-textarea help-checks">
|
||||
{foreach $bases as $base}
|
||||
<label class="think-checkbox">
|
||||
{if isset($vo.usertype) and $vo.usertype eq $base.code}
|
||||
<input type="radio" checked name="usertype" value="{$base.code}" lay-ignore>{$base.name}
|
||||
{else}
|
||||
<input type="radio" name="usertype" value="{$base.code}" lay-ignore>{$base.name}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{if !empty($auths)}
|
||||
<div class="layui-form-item">
|
||||
<div class="help-label"><b>{:lang('访问权限')}</b>Role Permission</div>
|
||||
<div class="layui-textarea help-checks">
|
||||
{if isset($vo.username) and $vo.username eq $super}
|
||||
<span class="color-desc padding-left-5">{:lang('超级用户拥所有访问权限,不需要配置权限。')}</span>
|
||||
{else}{foreach $auths as $authorize}
|
||||
<label class="think-checkbox">
|
||||
{if in_array($authorize.id, $vo.authorize)}
|
||||
<input type="checkbox" checked name="authorize[]" value="{$authorize.id}" lay-ignore>{$authorize.title}
|
||||
{else}
|
||||
<input type="checkbox" name="authorize[]" value="{$authorize.id}" lay-ignore>{$authorize.title}
|
||||
{/if}
|
||||
</label>
|
||||
{/foreach}{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</fieldset>
|
||||
{/if}
|
||||
|
||||
<fieldset class="layui-bg-gray">
|
||||
<legend><b class="layui-badge think-bg-violet">{:lang('用户资料')}</b></legend>
|
||||
<div class="layui-row layui-col-space15">
|
||||
<div class="layui-col-xs4">
|
||||
<label class="relative block">
|
||||
<span class="help-label"><b>{:lang('联系邮箱')}</b>Contact Email</span>
|
||||
<input name="contact_mail" value='{$vo.contact_mail|default=""}' pattern="email" vali-name="{:lang('联系邮箱')}" placeholder="{:lang('请输入联系电子邮箱')}" class="layui-input">
|
||||
<span class="color-desc">{:lang('可选,请填写用户常用的电子邮箱')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<label class="relative block">
|
||||
<span class="help-label"><b>{:lang('联系手机')}</b>Contact Mobile</span>
|
||||
<input type="tel" maxlength="11" name="contact_phone" value='{$vo.contact_phone|default=""}' pattern="phone" vali-name="{:lang('联系手机')}" placeholder="{:lang('请输入用户联系手机')}" class="layui-input">
|
||||
<span class="color-desc">{:lang('可选,请填写用户常用的联系手机号')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<label class="relative block">
|
||||
<span class="help-label"><b>{:lang('联系QQ')}</b>Contact QQ</span>
|
||||
<input name="contact_qq" maxlength="11" value='{$vo.contact_qq|default=""}' pattern="qq" vali-name="{:lang('联系QQ')}" placeholder="{:lang('请输入常用的联系QQ')}" class="layui-input">
|
||||
<span class="color-desc">{:lang('可选,请填写用户常用的联系QQ号')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label class="layui-form-item block relative margin-top-10">
|
||||
<span class="help-label"><b>{:lang('用户描述')}</b>User Remark</span>
|
||||
<textarea placeholder="{:lang('请输入用户描述')}" class="layui-textarea" name="describe">{$vo.describe|default=""}</textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
|
||||
|
||||
<div class="layui-form-item text-center">
|
||||
<button class="layui-btn" type='submit'>{:lang('保存数据')}</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="{:lang('确定要取消编辑吗?')}" data-close>{:lang('取消编辑')}</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,44 +0,0 @@
|
||||
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('账号名称')}</label>
|
||||
<label class="layui-input-inline">
|
||||
<input name="username" value="{$get.username|default=''}" placeholder="{:lang('请输入账号或名称')}" class="layui-input">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!--{notempty name='bases'}-->
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('角色身份')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="usertype" lay-search class="layui-select">
|
||||
<option value=''>-- {:lang('全部')} --</option>
|
||||
{foreach $bases as $base}{if $base.code eq input('get.usertype')}
|
||||
<option selected value="{$base.code|default=''}">{$base.name|default=''} ( {$base.code|default=''} )</option>
|
||||
{else}
|
||||
<option value="{$base.code|default=''}">{$base.name|default=''} ( {$base.code|default=''} )</option>
|
||||
{/if}{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!--{/notempty}-->
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('最后登录')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<input data-date-range name="login_at" value="{$get.login_at|default=''}" placeholder="{:lang('请选择登录时间')}" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<label class="layui-form-label">{:lang('创建时间')}</label>
|
||||
<div class="layui-input-inline">
|
||||
<input data-date-range name="create_at" value="{$get.create_at|default=''}" placeholder="{:lang('请选择创建时间')}" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-inline">
|
||||
<input type="hidden" name="type" value="{$type|default='index'}">
|
||||
<button class="layui-btn layui-btn-primary"><i class="layui-icon"></i> {:lang('搜 索')}</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,40 +0,0 @@
|
||||
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card" data-table-id="UserTable">
|
||||
<div class="layui-card-body padding-left-40">
|
||||
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('登录用户账号')}</b>Username</span>
|
||||
<input disabled value='{$vo.username|default=""}' class="layui-input think-bg-gray">
|
||||
<span class="help-block">{:lang('登录用户账号创建后,不允许再次修改。')}</span>
|
||||
</label>
|
||||
|
||||
<!--{if $verify}-->
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('旧的登录密码')}</b>Old Password</span>
|
||||
<input type="password" autofocus name="oldpassword" value='' pattern="^\S{1,}$" required vali-name="{:lang('验证密码')}" placeholder="{:lang('请输入旧的登录密码')}" class="layui-input">
|
||||
<span class="color-desc">{:lang('请输入旧密码来验证修改权限,旧密码不限制格式。')}</span>
|
||||
</label>
|
||||
<!--{/if}-->
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('新的登录密码')}</b>New Password</span>
|
||||
<input type="password" name="password" maxlength="32" pattern="^(?![\d]+$)(?![a-zA-Z]+$)(?![^\da-zA-Z]+$).{6,32}$" required vali-name="{:lang('登录密码')}" placeholder="{:lang('请输入新的登录密码')}" class="layui-input">
|
||||
<span class="color-desc">{:lang('密码必须包含大小写字母、数字、符号的任意两者组合。')}</span>
|
||||
</label>
|
||||
|
||||
<label class="layui-form-item relative block">
|
||||
<span class="help-label"><b>{:lang('重复登录密码')}</b>Repeat Password</span>
|
||||
<input type="password" name="repassword" maxlength="32" pattern="^(?![\d]+$)(?![a-zA-Z]+$)(?![^\da-zA-Z]+$).{6,32}$" required vali-name="{:lang('重复密码')}" placeholder="{:lang('请重复输入登录密码')}" class="layui-input">
|
||||
<span class="color-desc">{:lang('密码必须包含大小写字母、数字、符号的任意两者组合。')}</span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
|
||||
|
||||
<div class="layui-form-item text-center">
|
||||
<button class="layui-btn" type='submit'>{:lang('保存数据')}</button>
|
||||
<button class="layui-btn layui-btn-danger" type='button' data-confirm="{:lang('确定要取消编辑吗?')}" data-close>{:lang('取消编辑')}</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,109 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\wechat;
|
||||
|
||||
use app\wechat\command\Auto;
|
||||
use app\wechat\command\Clear;
|
||||
use app\wechat\command\Fans;
|
||||
use app\wechat\service\AutoService;
|
||||
use app\wechat\service\PaymentService;
|
||||
use think\admin\extend\CodeExtend;
|
||||
use think\admin\Plugin;
|
||||
use think\Request;
|
||||
|
||||
/**
|
||||
* 组件注册服务
|
||||
* @class Service
|
||||
*/
|
||||
class Service extends Plugin
|
||||
{
|
||||
/**
|
||||
* 定义插件名称.
|
||||
* @var string
|
||||
*/
|
||||
protected $appName = '微信管理';
|
||||
|
||||
/**
|
||||
* 定义安装包名.
|
||||
* @var string
|
||||
*/
|
||||
protected $package = 'zoujingli/think-plugs-wechat';
|
||||
|
||||
/**
|
||||
* 注册组件服务
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// 注册模块指令
|
||||
$this->commands([Fans::class, Auto::class, Clear::class]);
|
||||
|
||||
// 注册粉丝关注事件
|
||||
$this->app->event->listen('WechatFansSubscribe', static function ($openid) {
|
||||
AutoService::register($openid);
|
||||
});
|
||||
|
||||
// 注册支付通知路由
|
||||
$this->app->route->any('/plugin-wxpay-notify/:vars', static function (Request $request) {
|
||||
try {
|
||||
$data = json_decode(CodeExtend::deSafe64($request->param('vars')), true);
|
||||
return PaymentService::notify($data);
|
||||
} catch (\Error|\Exception $exception) {
|
||||
return "Error: {$exception->getMessage()}";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加微信配置.
|
||||
* @return array[]
|
||||
*/
|
||||
public static function menu(): array
|
||||
{
|
||||
$code = app(static::class)->appCode;
|
||||
// 设置插件菜单
|
||||
return [
|
||||
[
|
||||
'name' => '微信管理',
|
||||
'subs' => [
|
||||
['name' => '微信接口配置', 'icon' => 'layui-icon layui-icon-set', 'node' => "{$code}/config/options"],
|
||||
['name' => '微信支付配置', 'icon' => 'layui-icon layui-icon-rmb', 'node' => "{$code}/config/payment"],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => '微信定制',
|
||||
'subs' => [
|
||||
['name' => '微信粉丝管理', 'icon' => 'layui-icon layui-icon-username', 'node' => "{$code}/fans/index"],
|
||||
['name' => '微信图文管理', 'icon' => 'layui-icon layui-icon-template-1', 'node' => "{$code}/news/index"],
|
||||
['name' => '微信菜单配置', 'icon' => 'layui-icon layui-icon-cellphone', 'node' => "{$code}/menu/index"],
|
||||
['name' => '回复规则管理', 'icon' => 'layui-icon layui-icon-engine', 'node' => "{$code}/keys/index"],
|
||||
['name' => '关注自动回复', 'icon' => 'layui-icon layui-icon-release', 'node' => "{$code}/auto/index"],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => '微信支付',
|
||||
'subs' => [
|
||||
['name' => '微信支付行为', 'icon' => 'layui-icon layui-icon-rmb', 'node' => "{$code}/payment.record/index"],
|
||||
['name' => '微信退款管理', 'icon' => 'layui-icon layui-icon-engine', 'node' => "{$code}/payment.refund/index"],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\wechat\model;
|
||||
|
||||
use think\admin\Model;
|
||||
|
||||
/**
|
||||
* 微信自动回复模型.
|
||||
*
|
||||
* @property int $create_by 创建人
|
||||
* @property int $id
|
||||
* @property int $news_id 图文ID
|
||||
* @property int $status 状态(0禁用,1启用)
|
||||
* @property string $appid 公众号APPID
|
||||
* @property string $code 消息编号
|
||||
* @property string $content 文本内容
|
||||
* @property string $create_at 创建时间
|
||||
* @property string $image_url 图片链接
|
||||
* @property string $music_desc 音乐描述
|
||||
* @property string $music_image 缩略图片
|
||||
* @property string $music_title 音乐标题
|
||||
* @property string $music_url 音乐链接
|
||||
* @property string $time 延迟时间
|
||||
* @property string $type 类型(text,image,news)
|
||||
* @property string $video_desc 视频描述
|
||||
* @property string $video_title 视频标题
|
||||
* @property string $video_url 视频URL
|
||||
* @property string $voice_url 语音链接
|
||||
* @class WechatAuto
|
||||
*/
|
||||
class WechatAuto extends Model
|
||||
{
|
||||
/**
|
||||
* 格式化创建时间.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function getCreateAtAttr($value): string
|
||||
{
|
||||
return format_datetime($value);
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\wechat\model;
|
||||
|
||||
use think\admin\Model;
|
||||
|
||||
/**
|
||||
* 微信回复关键词模型.
|
||||
*
|
||||
* @property int $create_by 创建人
|
||||
* @property int $id
|
||||
* @property int $news_id 图文ID
|
||||
* @property int $sort 排序字段
|
||||
* @property int $status 状态(0禁用,1启用)
|
||||
* @property string $appid 公众号APPID
|
||||
* @property string $content 文本内容
|
||||
* @property string $create_at 创建时间
|
||||
* @property string $image_url 图片链接
|
||||
* @property string $keys 关键字
|
||||
* @property string $music_desc 音乐描述
|
||||
* @property string $music_image 缩略图片
|
||||
* @property string $music_title 音乐标题
|
||||
* @property string $music_url 音乐链接
|
||||
* @property string $type 类型(text,image,news)
|
||||
* @property string $video_desc 视频描述
|
||||
* @property string $video_title 视频标题
|
||||
* @property string $video_url 视频URL
|
||||
* @property string $voice_url 语音链接
|
||||
* @class WechatKeys
|
||||
*/
|
||||
class WechatKeys extends Model
|
||||
{
|
||||
/**
|
||||
* 格式化创建时间.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function getCreateAtAttr($value): string
|
||||
{
|
||||
return format_datetime($value);
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\wechat\model;
|
||||
|
||||
use think\admin\Model;
|
||||
|
||||
/**
|
||||
* 微信图文主模型.
|
||||
*
|
||||
* @property int $create_by 创建人
|
||||
* @property int $id
|
||||
* @property int $is_deleted 删除状态(0未删除,1已删除)
|
||||
* @property string $article_id 关联图文ID(用英文逗号做分割)
|
||||
* @property string $create_at 创建时间
|
||||
* @property string $local_url 永久素材外网URL
|
||||
* @property string $media_id 永久素材MediaID
|
||||
* @class WechatNews
|
||||
*/
|
||||
class WechatNews extends Model {}
|
||||
@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\wechat\model;
|
||||
|
||||
use app\wechat\service\PaymentService;
|
||||
use think\admin\Model;
|
||||
use think\model\relation\HasOne;
|
||||
|
||||
/**
|
||||
* 微信支付行为模型.
|
||||
*
|
||||
* @property float $order_amount 原订单金额
|
||||
* @property float $payment_amount 实际到账金额
|
||||
* @property float $refund_amount 退款金额
|
||||
* @property int $id
|
||||
* @property int $payment_status 支付状态(0未付,1已付,2取消)
|
||||
* @property int $refund_status 退款状态(0未退,1已退)
|
||||
* @property string $appid 发起APPID
|
||||
* @property string $code 发起支付号
|
||||
* @property string $create_time 创建时间
|
||||
* @property string $openid 用户OPENID
|
||||
* @property string $order_code 原订单编号
|
||||
* @property string $order_name 原订单标题
|
||||
* @property string $payment_bank 支付银行类型
|
||||
* @property string $payment_notify 支付结果通知
|
||||
* @property string $payment_remark 支付状态备注
|
||||
* @property string $payment_time 支付完成时间
|
||||
* @property string $payment_trade 平台交易编号
|
||||
* @property string $type 交易方式
|
||||
* @property string $update_time 更新时间
|
||||
* @property WechatFans $bind_fans
|
||||
* @property WechatFans $fans
|
||||
* @class WechatPaymentRecord
|
||||
*/
|
||||
class WechatPaymentRecord extends Model
|
||||
{
|
||||
/**
|
||||
* 关联用户粉丝数据.
|
||||
*/
|
||||
public function fans(): HasOne
|
||||
{
|
||||
return $this->hasOne(WechatFans::class, 'openid', 'openid');
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定用户粉丝数据.
|
||||
*/
|
||||
public function bindFans(): HasOne
|
||||
{
|
||||
return $this->fans()->bind([
|
||||
'fans_headimg' => 'headimgurl',
|
||||
'fans_nickname' => 'nickname',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出时间格式.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function getCreateTimeAttr($value): string
|
||||
{
|
||||
return $value ? format_datetime($value) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出时间格式.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function getUpdateTimeAttr($value): string
|
||||
{
|
||||
return $value ? format_datetime($value) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出时间格式.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function getPaymentTimeAttr($value): string
|
||||
{
|
||||
return $value ? format_datetime($value) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换数据类型.
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = parent::toArray();
|
||||
$data['type_name'] = PaymentService::tradeTypeNames[$data['type']] ?? $data['type'];
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace app\wechat\model;
|
||||
|
||||
use think\admin\Model;
|
||||
use think\model\relation\HasOne;
|
||||
|
||||
/**
|
||||
* 微信支付退款模型.
|
||||
*
|
||||
* @property float $refund_amount 实际到账金额
|
||||
* @property int $id
|
||||
* @property int $refund_status 支付状态(0未付,1已付,2取消)
|
||||
* @property string $code 发起支付号
|
||||
* @property string $create_time 创建时间
|
||||
* @property string $record_code 子支付编号
|
||||
* @property string $refund_account 退款目标账号
|
||||
* @property string $refund_notify 退款交易通知
|
||||
* @property string $refund_remark 支付状态备注
|
||||
* @property string $refund_scode 退款状态码
|
||||
* @property string $refund_time 支付完成时间
|
||||
* @property string $refund_trade 平台交易编号
|
||||
* @property string $update_time 更新时间
|
||||
* @property WechatPaymentRecord $record
|
||||
* @class WechatPaymentRefund
|
||||
*/
|
||||
class WechatPaymentRefund extends Model
|
||||
{
|
||||
/**
|
||||
* 关联支付订单.
|
||||
*/
|
||||
public function record(): HasOne
|
||||
{
|
||||
return $this->hasOne(WechatPaymentRecord::class, 'code', 'record_code')->with('bindfans');
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出时间格式.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function getCreateTimeAttr($value): string
|
||||
{
|
||||
return $value ? format_datetime($value) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出时间格式.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function getUpdateTimeAttr($value): string
|
||||
{
|
||||
return $value ? format_datetime($value) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出时间格式.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function getRefundTimeAttr($value): string
|
||||
{
|
||||
return $value ? format_datetime($value) : '';
|
||||
}
|
||||
}
|
||||
@ -1,367 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
// | Wechat Plugin for ThinkAdmin
|
||||
// +----------------------------------------------------------------------
|
||||
// | 版权所有 2014~2025 Anyon <zoujingli@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网站: https://thinkadmin.top
|
||||
// +----------------------------------------------------------------------
|
||||
// | 开源协议 ( https://mit-license.org )
|
||||
// | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
// +----------------------------------------------------------------------
|
||||
// || github 代码仓库:https://github.com/zoujingli/think-plugs-wechat
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\wechat\service;
|
||||
|
||||
use think\admin\Exception;
|
||||
use think\admin\extend\JsonRpcClient;
|
||||
use think\admin\Library;
|
||||
use think\admin\Service;
|
||||
use think\admin\storage\LocalStorage;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\Response;
|
||||
use WeChat\Exceptions\InvalidResponseException;
|
||||
use WeChat\Exceptions\LocalCacheException;
|
||||
|
||||
/**
|
||||
* 微信接口调度服务
|
||||
* @class WechatService
|
||||
*
|
||||
* @method \WeChat\Card WeChatCard() static 微信卡券管理
|
||||
* @method \WeChat\Custom WeChatCustom() static 微信客服消息
|
||||
* @method \WeChat\Limit WeChatLimit() static 接口调用频次限制
|
||||
* @method \WeChat\Media WeChatMedia() static 微信素材管理
|
||||
* @method \WeChat\Draft WeChatDraft() static 微信草稿箱管理
|
||||
* @method \WeChat\Menu WeChatMenu() static 微信菜单管理
|
||||
* @method \WeChat\Oauth WeChatOauth() static 微信网页授权
|
||||
* @method \WeChat\Pay WeChatPay() static 微信支付商户
|
||||
* @method \WeChat\Product WeChatProduct() static 微信商店管理
|
||||
* @method \WeChat\Qrcode WeChatQrcode() static 微信二维码管理
|
||||
* @method \WeChat\Receive WeChatReceive() static 微信推送管理
|
||||
* @method \WeChat\Scan WeChatScan() static 微信扫一扫接入管理
|
||||
* @method \WeChat\Script WeChatScript() static 微信前端支持
|
||||
* @method \WeChat\Shake WeChatShake() static 微信揺一揺周边
|
||||
* @method \WeChat\Tags WeChatTags() static 微信用户标签管理
|
||||
* @method \WeChat\Template WeChatTemplate() static 微信模板消息
|
||||
* @method \WeChat\User WeChatUser() static 微信粉丝管理
|
||||
* @method \WeChat\Wifi WeChatWifi() static 微信门店WIFI管理
|
||||
* @method \WeChat\Freepublish WeChatFreepublish() static 发布能力
|
||||
*
|
||||
* ----- WeMini -----
|
||||
* @method \WeMini\Account WeMiniAccount() static 小程序账号管理
|
||||
* @method \WeMini\Basic WeMiniBasic() static 小程序基础信息设置
|
||||
* @method \WeMini\Code WeMiniCode() static 小程序代码管理
|
||||
* @method \WeMini\Domain WeMiniDomain() static 小程序域名管理
|
||||
* @method \WeMini\Tester WeMinitester() static 小程序成员管理
|
||||
* @method \WeMini\User WeMiniUser() static 小程序帐号管理
|
||||
* --------------------
|
||||
* @method \WeMini\Crypt WeMiniCrypt() static 小程序数据加密处理
|
||||
* @method \WeMini\Delivery WeMiniDelivery() static 小程序即时配送
|
||||
* @method \WeMini\Guide WeMiniGuide() static 小程序导购助手
|
||||
* @method \WeMini\Image WeMiniImage() static 小程序图像处理
|
||||
* @method \WeMini\Live WeMiniLive() static 小程序直播接口
|
||||
* @method \WeMini\Logistics WeMiniLogistics() static 小程序物流助手
|
||||
* @method \WeMini\Newtmpl WeMiniNewtmpl() static 公众号小程序订阅消息支持
|
||||
* @method \WeMini\Message WeMiniMessage() static 小程序动态消息
|
||||
* @method \WeMini\Operation WeMiniOperation() static 小程序运维中心
|
||||
* @method \WeMini\Ocr WeMiniOcr() static 小程序ORC服务
|
||||
* @method \WeMini\Plugs WeMiniPlugs() static 小程序插件管理
|
||||
* @method \WeMini\Poi WeMiniPoi() static 小程序地址管理
|
||||
* @method \WeMini\Qrcode WeMiniQrcode() static 小程序二维码管理
|
||||
* @method \WeMini\Security WeMiniSecurity() static 小程序内容安全
|
||||
* @method \WeMini\Soter WeMiniSoter() static 小程序生物认证
|
||||
* @method \WeMini\Template WeMiniTemplate() static 小程序模板消息支持
|
||||
* @method \WeMini\Total WeMiniTotal() static 小程序数据接口
|
||||
* @method \WeMini\Scheme WeMiniScheme() static 小程序URL-Scheme
|
||||
* @method \WeMini\Search WeMiniSearch() static 小程序搜索
|
||||
* @method \WeMini\Shipping WeMiniShipping() static 小程序发货信息管理服务
|
||||
*
|
||||
* ----- WePay -----
|
||||
* @method \WePay\Bill WePayBill() static 微信商户账单及评论
|
||||
* @method \WePay\Order WePayOrder() static 微信商户订单
|
||||
* @method \WePay\Refund WePayRefund() static 微信商户退款
|
||||
* @method \WePay\Coupon WePayCoupon() static 微信商户代金券
|
||||
* @method \WePay\Custom WePayCustom() static 微信扩展上报海关
|
||||
* @method \WePay\ProfitSharing WePayProfitSharing() static 微信分账
|
||||
* @method \WePay\Redpack WePayRedpack() static 微信红包支持
|
||||
* @method \WePay\Transfers WePayTransfers() static 微信商户打款到零钱
|
||||
* @method \WePay\TransfersBank WePayTransfersBank() static 微信商户打款到银行卡
|
||||
*
|
||||
* ----- WePayV3 -----
|
||||
* @method \WePayV3\Order WePayV3Order() static 直连商户|订单支付接口
|
||||
* @method \WePayV3\Transfers WePayV3Transfers() static 微信商家转账到零钱
|
||||
* @method \WePayV3\ProfitSharing WePayV3ProfitSharing() static 微信商户分账
|
||||
*
|
||||
* ----- WeOpen -----
|
||||
* @method \WeOpen\Login WeOpenLogin() static 第三方微信登录
|
||||
* @method \WeOpen\Service WeOpenService() static 第三方服务
|
||||
*
|
||||
* ----- ThinkService -----
|
||||
* @method mixed ThinkServiceConfig() static 平台服务配置
|
||||
*/
|
||||
class WechatService extends Service
|
||||
{
|
||||
/**
|
||||
* 静态初始化对象
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function __callStatic(string $name, array $arguments)
|
||||
{
|
||||
[$type, $base, $class] = static::parseName($name);
|
||||
if ("{$type}{$base}" !== $name) {
|
||||
throw new Exception("抱歉,实例 {$name} 不符合规则!");
|
||||
}
|
||||
if (sysconf('wechat.type') === 'api' || in_array($type, ['WePay', 'WePayV3'])) {
|
||||
if (class_exists($class)) {
|
||||
return new $class($type === 'WeMini' ? static::getWxconf() : static::getConfig());
|
||||
}
|
||||
throw new Exception("抱歉,接口模式无法实例 {$class} 对象!");
|
||||
} else {
|
||||
[$appid, $appkey] = [sysconf('wechat.thr_appid'), sysconf('wechat.thr_appkey')];
|
||||
$data = ['class' => $name, 'appid' => $appid, 'time' => time(), 'nostr' => uniqid()];
|
||||
$data['sign'] = md5("{$data['class']}#{$appid}#{$appkey}#{$data['time']}#{$data['nostr']}");
|
||||
// 创建远程连接,默认使用 JSON-RPC 方式调用接口
|
||||
$token = enbase64url(json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||
$jsonrpc = sysconf('wechat.service_jsonrpc|raw') ?: 'https://open.cuci.cc/plugin-wechat-service/api.client/jsonrpc?token=TOKEN';
|
||||
return new JsonRpcClient(str_replace('token=TOKEN', "token={$token}", $jsonrpc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前微信APPID.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getAppid(): string
|
||||
{
|
||||
if (static::getType() === 'api') {
|
||||
return sysconf('wechat.appid');
|
||||
}
|
||||
return sysconf('wechat.thr_appid');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取接口授权模式.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getType(): string
|
||||
{
|
||||
$type = strtolower(sysconf('wechat.type'));
|
||||
if (in_array($type, ['api', 'thr'])) {
|
||||
return $type;
|
||||
}
|
||||
throw new Exception('请在后台配置微信对接授权模式');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公众号配置参数.
|
||||
* @param bool $ispay 获取支付参数
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getConfig(bool $ispay = false): array
|
||||
{
|
||||
$config = [
|
||||
'appid' => static::getAppid(),
|
||||
'token' => sysconf('wechat.token'),
|
||||
'appsecret' => sysconf('wechat.appsecret'),
|
||||
'encodingaeskey' => sysconf('wechat.encodingaeskey'),
|
||||
'cache_path' => syspath('runtime/wechat'),
|
||||
];
|
||||
return $ispay ? static::withWxpayCert($config) : $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取小程序配置参数.
|
||||
* @param bool $ispay 获取支付参数
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getWxconf(bool $ispay = false): array
|
||||
{
|
||||
$wxapp = sysdata('plugin.wechat.wxapp');
|
||||
$config = [
|
||||
'appid' => $wxapp['appid'] ?? '',
|
||||
'appsecret' => $wxapp['appkey'] ?? '',
|
||||
'cache_path' => syspath('runtime/wechat'),
|
||||
];
|
||||
return $ispay ? static::withWxpayCert($config) : $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付证书配置.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function withWxpayCert(array $options): array
|
||||
{
|
||||
// 文本模式主要是为了解决分布式部署
|
||||
$data = sysdata('plugin.wechat.payment');
|
||||
if (empty($data['mch_id'])) {
|
||||
throw new Exception('无效的支付配置!');
|
||||
}
|
||||
$name1 = sprintf('wxpay/%s_%s_cer.pem', $data['mch_id'], md5($data['ssl_cer_text']));
|
||||
$name2 = sprintf('wxpay/%s_%s_key.pem', $data['mch_id'], md5($data['ssl_key_text']));
|
||||
$local = LocalStorage::instance();
|
||||
if ($local->has($name1, true) && $local->has($name2, true)) {
|
||||
$sslCer = $local->path($name1, true);
|
||||
$sslKey = $local->path($name2, true);
|
||||
} else {
|
||||
$sslCer = $local->set($name1, $data['ssl_cer_text'], true)['file'];
|
||||
$sslKey = $local->set($name2, $data['ssl_key_text'], true)['file'];
|
||||
}
|
||||
$options['mch_id'] = $data['mch_id'];
|
||||
$options['mch_key'] = $data['mch_key'];
|
||||
$options['mch_v3_key'] = $data['mch_v3_key'];
|
||||
$options['ssl_cer'] = $sslCer;
|
||||
$options['ssl_key'] = $sslKey;
|
||||
$options['cert_public'] = $sslCer;
|
||||
$options['cert_private'] = $sslKey;
|
||||
$options['mp_cert_serial'] = $data['mch_pay_sid'] ?? '';
|
||||
$options['mp_cert_content'] = $data['ssl_pay_text'] ?? '';
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话名称.
|
||||
*/
|
||||
public static function getSsid(): string
|
||||
{
|
||||
$conf = Library::$sapp->session->getConfig();
|
||||
$ssid = Library::$sapp->request->get($conf['name'] ?? 'ssid');
|
||||
return empty($ssid) ? Library::$sapp->session->getId() : $ssid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过网页授权获取粉丝信息.
|
||||
* @param string $source 回跳URL地址
|
||||
* @param int $isfull 获取资料模式
|
||||
* @param bool $redirect 是否直接跳转
|
||||
* @throws InvalidResponseException
|
||||
* @throws LocalCacheException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getWebOauthInfo(string $source, int $isfull = 0, bool $redirect = true): array
|
||||
{
|
||||
[$ssid, $appid] = [static::getSsid(), static::getAppid()];
|
||||
$openid = Library::$sapp->cache->get("{$ssid}_openid");
|
||||
$userinfo = Library::$sapp->cache->get("{$ssid}_fansinfo");
|
||||
if ((empty($isfull) && !empty($openid)) || (!empty($isfull) && !empty($openid) && !empty($userinfo))) {
|
||||
empty($userinfo) || FansService::set($userinfo, $appid);
|
||||
return ['openid' => $openid, 'fansinfo' => $userinfo];
|
||||
}
|
||||
if (static::getType() === 'api') {
|
||||
// 解析 GET 参数
|
||||
parse_str(parse_url($source, PHP_URL_QUERY), $params);
|
||||
$getVars = [
|
||||
'code' => $params['code'] ?? input('code', ''),
|
||||
'rcode' => $params['rcode'] ?? input('rcode', ''),
|
||||
'state' => $params['state'] ?? input('state', ''),
|
||||
];
|
||||
$wechat = static::WeChatOauth();
|
||||
if ($getVars['state'] !== $appid || empty($getVars['code'])) {
|
||||
$params['rcode'] = enbase64url($source);
|
||||
$location = strstr("{$source}?", '?', true) . '?' . http_build_query($params);
|
||||
$oauthurl = $wechat->getOauthRedirect($location, $appid, $isfull ? 'snsapi_userinfo' : 'snsapi_base');
|
||||
throw new HttpResponseException(static::createRedirect($oauthurl, $redirect));
|
||||
}
|
||||
if (($token = $wechat->getOauthAccessToken($getVars['code'])) && isset($token['openid'])) {
|
||||
$openid = $token['openid'];
|
||||
// 如果是虚拟账号,不保存会话信息,下次重新授权
|
||||
if (empty($token['is_snapshotuser'])) {
|
||||
Library::$sapp->cache->set("{$ssid}_openid", $openid, 3600);
|
||||
}
|
||||
if ($isfull && isset($token['access_token'])) {
|
||||
$userinfo = $wechat->getUserInfo($token['access_token'], $openid);
|
||||
// 如果是虚拟账号,不保存会话信息,下次重新授权
|
||||
if (empty($token['is_snapshotuser'])) {
|
||||
$userinfo['is_snapshotuser'] = 0;
|
||||
// 缓存用户信息
|
||||
Library::$sapp->cache->set("{$ssid}_fansinfo", $userinfo, 3600);
|
||||
empty($userinfo) || FansService::set($userinfo, $appid);
|
||||
} else {
|
||||
$userinfo['is_snapshotuser'] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($getVars['rcode']) {
|
||||
throw new HttpResponseException(static::createRedirect(debase64url($getVars['rcode']), $redirect));
|
||||
}
|
||||
if ((empty($isfull) && !empty($openid)) || (!empty($isfull) && !empty($openid) && !empty($userinfo))) {
|
||||
return ['openid' => $openid, 'fansinfo' => $userinfo];
|
||||
}
|
||||
throw new Exception('Query params [rcode] not find.');
|
||||
} else {
|
||||
$result = static::ThinkServiceConfig()->oauth(self::getSsid(), $source, $isfull);
|
||||
[$openid, $userinfo] = [$result['openid'] ?? '', $result['fans'] ?? []];
|
||||
// 如果是虚拟账号,不保存会话信息,下次重新授权
|
||||
if (empty($result['token']['is_snapshotuser'])) {
|
||||
Library::$sapp->cache->set("{$ssid}_openid", $openid, 3600);
|
||||
Library::$sapp->cache->set("{$ssid}_fansinfo", $userinfo, 3600);
|
||||
}
|
||||
if ((empty($isfull) && !empty($openid)) || (!empty($isfull) && !empty($openid) && !empty($userinfo))) {
|
||||
empty($result['token']['is_snapshotuser']) && empty($userinfo) || FansService::set($userinfo, $appid);
|
||||
return ['openid' => $openid, 'fansinfo' => $userinfo];
|
||||
}
|
||||
throw new HttpResponseException(static::createRedirect($result['url'], $redirect));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信网页JSSDK签名参数.
|
||||
* @param null|string $location 签名地址
|
||||
* @throws InvalidResponseException
|
||||
* @throws LocalCacheException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getWebJssdkSign(?string $location = null): array
|
||||
{
|
||||
$location = $location ?: Library::$sapp->request->url(true);
|
||||
if (static::getType() === 'api') {
|
||||
return static::WeChatScript()->getJsSign($location);
|
||||
}
|
||||
return static::ThinkServiceConfig()->jsSign($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析调用对象名称.
|
||||
*/
|
||||
private static function parseName(string $name): array
|
||||
{
|
||||
foreach (['WeChat', 'WeMini', 'WeOpen', 'WePayV3', 'WePay', 'ThinkService'] as $type) {
|
||||
if (strpos($name, $type) === 0) {
|
||||
[, $base] = explode($type, $name);
|
||||
return [$type, $base, "\\{$type}\\{$base}"];
|
||||
}
|
||||
}
|
||||
return ['-', '-', $name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页授权链接跳转.
|
||||
* @param string $location 跳转链接
|
||||
* @param bool $redirect 强制跳转
|
||||
*/
|
||||
private static function createRedirect(string $location, bool $redirect = true): Response
|
||||
{
|
||||
return $redirect ? redirect($location) : response(join(";\n", [
|
||||
sprintf("sessionStorage.setItem('wechat.session','%s')", self::getSsid()),
|
||||
sprintf("location.replace('%s')", $location), '',
|
||||
]));
|
||||
}
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
{extend name="main"}
|
||||
|
||||
{block name="button"}
|
||||
<!--{if auth('options_wxapp')}-->
|
||||
<button data-modal="{:url('options_wxapp')}" data-width="600px" data-title="{:lang('绑定小程序')}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('绑定小程序')}</button>
|
||||
<!--{/if}-->
|
||||
<!--{if auth('options_jsonrpc')}-->
|
||||
<button data-modal="{:url('options_jsonrpc')}" data-width="600px" data-title="{:lang('开放平台接口配置')}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('开放平台接口')}</button>
|
||||
<!--{/if}-->
|
||||
<!--{if auth("options_test")}-->
|
||||
<button data-modal="{:url('options_test')}" data-width="600px" data-title="{:lang('微信授权测试( 扫码 )')}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('微信授权测试')}</button>
|
||||
<!--{/if}-->
|
||||
{/block}
|
||||
|
||||
{block name="content"}
|
||||
<div class="think-box-shadow layui-form">
|
||||
<div style="margin-left:99px;max-width:750px">
|
||||
{foreach ['api'=>lang('微信公众平台直接模式'),'thr'=>lang('微信开放平台授权模式')] as $k=>$v}
|
||||
<label class="think-radio">
|
||||
<input type="radio" data-wechat-type="{$k}" name="wechat.type" value="{$k}" title="{$v}" lay-ignore> {$v}
|
||||
</label>
|
||||
{/foreach}
|
||||
<div class="help-block">{:lang('请选择微信对接方式,其中微信开放平台授权模式需要微信开放平台支持,还需要搭建第三方服务平台托管系统!')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="think-box-shadow margin-top-15">
|
||||
<div class="padding-top-20" style="max-width:850px">
|
||||
<div class="layui-anim layui-anim-fadein" data-type="api">{include file='config/options_form_api'}</div>
|
||||
<div class="layui-anim layui-anim-fadein" data-type="thr">{include file='config/options_form_thr'}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block name='script'}
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
$('body').off('change', '[data-wechat-type]').on('change', '[data-wechat-type]', apply);
|
||||
|
||||
// 初始化选择器
|
||||
let value = '{:sysconf("wechat.type")}' || 'api';
|
||||
$("[data-wechat-type='" + value + "']").trigger('click')
|
||||
|
||||
function apply() {
|
||||
if ((value = $('input[data-wechat-type]:checked').val())) {
|
||||
$('[data-type="' + value + '"]').show().siblings('[data-type]').hide();
|
||||
} else {
|
||||
$("[data-wechat-type]:frist").trigger('click')
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/block}
|
||||
@ -1,21 +0,0 @@
|
||||
<div class='layui-card shadow-none margin-0'>
|
||||
<div class="layui-card-body padding-40">
|
||||
<div class="text-center test-qrcode-box">
|
||||
<div class="inline-block padding-right-40 notselect">
|
||||
<img alt="img" class="notselect" data-tips-image src="{:url('api.test/oauthQrc')}">
|
||||
<p class="text-center">OAUTH 网页授权</p>
|
||||
</div>
|
||||
<div class="inline-block padding-left-40">
|
||||
<img alt="img" class="notselect" data-tips-image src="{:url('api.test/jssdkQrc')}">
|
||||
<p class="text-center">JSSDK 接口签名</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.test-qrcode-box img {
|
||||
width: 200px;
|
||||
height: 200px
|
||||
}
|
||||
</style>
|
||||
@ -1,40 +0,0 @@
|
||||
<div class='layui-card shadow-none margin-0'>
|
||||
<div class="layui-card-body padding-40">
|
||||
<div class="layui-bg-gray padding-20 border-radius-5">
|
||||
<div class="margin-bottom-15">
|
||||
<p class="font-w7 color-green">微信开放平台授权</p>
|
||||
<p>JSSDK 签名测试需要在开放平台配置当前的授权域名:<a data-copy="{:request()->host()}">{:request()->host()}</a></p>
|
||||
</div>
|
||||
<div class="margin-bottom-15">
|
||||
<p class="font-w7 color-green">公众号平台域名授权</p>
|
||||
<p>网页授权及 JSSDK 签名都需要在公众号平台授权域名:<a data-copy="{:request()->host()}">{:request()->host()}</a></p>
|
||||
</div>
|
||||
<div class="margin-bottom-0">
|
||||
<p class="font-w7 color-green">微信商户支付测试配置</p>
|
||||
<p>JSAPI 支付测试需要在微信商户平台配置支付目录:<a data-copy="{:dirname(url('api.test/index',[],'',true))}/">{:dirname(url('api.test/index',[],'',true))}/</a></p>
|
||||
<p>扫码支付①需要在微信商户平台配置支付通知地址:<a data-copy="{:url('api.test/scanOneNotify',[],'',true)}">{:url('api.test/scanOneNotify',[],'',true)}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center margin-top-20 test-qrcode-box">
|
||||
<div class="inline-block">
|
||||
<img alt="img" onerror="this.src='__ROOT__/static/theme/img/wechat/qrc_pay_error.jpg'" class="notselect" data-tips-image src="{:url('api.test/jsapiQrc')}">
|
||||
<p class="text-center">微信 JSAPI 支付</p>
|
||||
</div>
|
||||
<div class="inline-block margin-left-40 margin-right-40">
|
||||
<img alt="img" onerror="this.src='__ROOT__/static/theme/img/wechat/qrc_pay_error.jpg'" class="notselect" data-tips-image src="{:url('api.test/scanOneQrc')}">
|
||||
<p class="text-center">微信扫码支付①</p>
|
||||
</div>
|
||||
<div class="inline-block">
|
||||
<img alt="img" onerror="this.src='__ROOT__/static/theme/img/wechat/qrc_pay_error.jpg'" class="notselect" data-tips-image src="{:url('api.test/scanTwoQrc')}">
|
||||
<p class="text-center">微信扫码支付②</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.test-qrcode-box img {
|
||||
width: 200px;
|
||||
height: 200px
|
||||
}
|
||||
</style>
|
||||
34
database/migrations/.published.json
Normal file
34
database/migrations/.published.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"20241010000005_install_account20241010.php": {
|
||||
"source": "plugin/think-plugs-account/stc/database/20241010000005_install_account20241010.php",
|
||||
"mtime": 1778063923
|
||||
},
|
||||
"20241010000006_install_payment20241010.php": {
|
||||
"source": "plugin/think-plugs-payment/stc/database/20241010000006_install_payment20241010.php",
|
||||
"mtime": 1778063923
|
||||
},
|
||||
"20241010000001_install_system20241010.php": {
|
||||
"source": "plugin/think-plugs-system/stc/database/20241010000001_install_system20241010.php",
|
||||
"mtime": 1778063812
|
||||
},
|
||||
"20241010000003_install_wechat20241010.php": {
|
||||
"source": "plugin/think-plugs-wechat-client/stc/database/20241010000003_install_wechat20241010.php",
|
||||
"mtime": 1778063923
|
||||
},
|
||||
"20241010000009_install_wechat_service20241010.php": {
|
||||
"source": "plugin/think-plugs-wechat-service/stc/database/20241010000009_install_wechat_service20241010.php",
|
||||
"mtime": 1778063923
|
||||
},
|
||||
"20241010000007_install_wemall20241010.php": {
|
||||
"source": "plugin/think-plugs-wemall/stc/database/20241010000007_install_wemall20241010.php",
|
||||
"mtime": 1778063923
|
||||
},
|
||||
"20241010000008_install_worker20241010.php": {
|
||||
"source": "plugin/think-plugs-worker/stc/database/20241010000008_install_worker20241010.php",
|
||||
"mtime": 1774236276
|
||||
},
|
||||
"20241010000010_install_wuma20241010.php": {
|
||||
"source": "plugin/think-plugs-wuma/stc/database/20241010000010_install_wuma20241010.php",
|
||||
"mtime": 1778063923
|
||||
}
|
||||
}
|
||||
75
plugin/think-library/.github/workflows/release.yml
vendored
Normal file
75
plugin/think-library/.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
####### 可解析的提交前缀 ########
|
||||
# ci: 持续集成
|
||||
# fix: 修改
|
||||
# feat: 新增
|
||||
# refactor: 重构
|
||||
# docs: 文档
|
||||
# style: 样式
|
||||
# chore: 其他
|
||||
# build: 构建
|
||||
# pref: 优化
|
||||
# test: 测试
|
||||
###############################
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # 仅匹配 v* 版本标签,如 v1.0、v20.15.10
|
||||
|
||||
name: Create Release
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install -g gen-git-log
|
||||
|
||||
- name: Find Last Tag
|
||||
id: last_tag
|
||||
run: |
|
||||
# 获取所有标签,按版本号降序排序
|
||||
all_tags=$(git tag --list --sort=-version:refname)
|
||||
|
||||
# 获取最新的标签
|
||||
LATEST_TAG=$(echo "$all_tags" | head -n 1)
|
||||
|
||||
# 获取倒数第二个标签(如果有)
|
||||
SECOND_LATEST_TAG=$(echo "$all_tags" | sed -n '2p')
|
||||
|
||||
# 如果没有任何标签,默认 v1.0.0
|
||||
LATEST_TAG=${LATEST_TAG:-v1.0.0}
|
||||
SECOND_LATEST_TAG=${SECOND_LATEST_TAG:-v1.0.0}
|
||||
|
||||
# 设置环境变量
|
||||
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
||||
echo "SECOND_LATEST_TAG=$SECOND_LATEST_TAG" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate Release Notes
|
||||
run: |
|
||||
rm -rf log
|
||||
mkdir -p log
|
||||
git-log -m tag -f -S $SECOND_LATEST_TAG -v ${LATEST_TAG#v}
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.LATEST_TAG }}
|
||||
release_name: Release ${{ env.LATEST_TAG }}
|
||||
body_path: log/${{ env.LATEST_TAG }}.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
8
plugin/think-library/.gitignore
vendored
Normal file
8
plugin/think-library/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.git
|
||||
.svn
|
||||
.idea
|
||||
*.cache
|
||||
/vendor
|
||||
/composer.lock
|
||||
!.gitignore
|
||||
!composer.json
|
||||
120
plugin/think-library/.php-cs-fixer.php
Normal file
120
plugin/think-library/.php-cs-fixer.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | 官方网站: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | 开源协议 ( https://mit-license.org )
|
||||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
* +----------------------------------------------------------------------
|
||||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
use PhpCsFixer\Config;
|
||||
use PhpCsFixer\Finder;
|
||||
use PhpCsFixer\Runner\Parallel\ParallelConfig;
|
||||
|
||||
$header = <<<'EOF'
|
||||
+----------------------------------------------------------------------
|
||||
| ThinkAdmin Plugin for ThinkAdmin
|
||||
+----------------------------------------------------------------------
|
||||
| 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
+----------------------------------------------------------------------
|
||||
| 官方网站: https://thinkadmin.top
|
||||
+----------------------------------------------------------------------
|
||||
| 开源协议 ( https://mit-license.org )
|
||||
| 免责声明 ( https://thinkadmin.top/disclaimer )
|
||||
| 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||||
+----------------------------------------------------------------------
|
||||
| gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||||
| github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||||
+----------------------------------------------------------------------
|
||||
EOF;
|
||||
|
||||
$config = new Config();
|
||||
$config->setRiskyAllowed(true)->setParallelConfig(new ParallelConfig(8, 24));
|
||||
$finder = Finder::create()->in(__DIR__)->exclude(['vendor', 'public', 'runtime']);
|
||||
return $config->setFinder($finder)->setUsingCache(false)->setRules([
|
||||
'@PSR2' => true,
|
||||
'@Symfony' => true,
|
||||
'@DoctrineAnnotation' => true,
|
||||
'@PhpCsFixer' => true,
|
||||
'header_comment' => [
|
||||
'comment_type' => 'PHPDoc',
|
||||
'header' => $header,
|
||||
'separate' => 'none',
|
||||
'location' => 'after_declare_strict',
|
||||
],
|
||||
'array_syntax' => [
|
||||
'syntax' => 'short',
|
||||
],
|
||||
'list_syntax' => [
|
||||
'syntax' => 'short',
|
||||
],
|
||||
'blank_line_before_statement' => [
|
||||
'statements' => [
|
||||
'declare',
|
||||
],
|
||||
],
|
||||
'general_phpdoc_annotation_remove' => [
|
||||
'annotations' => [
|
||||
'author',
|
||||
],
|
||||
],
|
||||
'ordered_imports' => [
|
||||
'imports_order' => [
|
||||
'class', 'function', 'const',
|
||||
],
|
||||
'sort_algorithm' => 'alpha',
|
||||
],
|
||||
'single_line_comment_style' => [
|
||||
'comment_types' => [
|
||||
],
|
||||
],
|
||||
'yoda_style' => [
|
||||
'always_move_variable' => false,
|
||||
'equal' => false,
|
||||
'identical' => false,
|
||||
],
|
||||
'phpdoc_align' => [
|
||||
'align' => 'left',
|
||||
],
|
||||
'multiline_whitespace_before_semicolons' => [
|
||||
'strategy' => 'no_multi_line',
|
||||
],
|
||||
'constant_case' => [
|
||||
'case' => 'lower',
|
||||
],
|
||||
'encoding' => true, // PHP代码必须只使用没有BOM的UTF-8
|
||||
'line_ending' => true, // 所有的PHP文件编码必须一致
|
||||
'single_quote' => true, // 简单字符串应该使用单引号代替双引号
|
||||
'no_empty_statement' => true, // 不应该存在空的结构体
|
||||
'standardize_not_equals' => true, // 使用 <> 代替 !=
|
||||
'blank_line_after_namespace' => true, // 命名空间之后空一行
|
||||
'no_empty_phpdoc' => true, // 不应该存在空的 phpdoc
|
||||
'no_empty_comment' => true, // 不应该存在空注释
|
||||
'no_singleline_whitespace_before_semicolons' => true, // 禁止在关闭分号前使用单行空格
|
||||
'concat_space' => ['spacing' => 'one'], // 连接字符是否需要空格,可选配置项 none:不需要 one:一个空格
|
||||
'no_leading_import_slash' => true, // use 语句中取消前置斜杠
|
||||
'cast_spaces' => ['space' => 'none'],
|
||||
'class_attributes_separation' => true,
|
||||
'combine_consecutive_unsets' => true,
|
||||
'declare_strict_types' => true,
|
||||
'lowercase_static_reference' => true,
|
||||
'linebreak_after_opening_tag' => true,
|
||||
'multiline_comment_opening_closing' => true,
|
||||
'no_useless_else' => true,
|
||||
'no_unused_imports' => true,
|
||||
'not_operator_with_successor_space' => false,
|
||||
'not_operator_with_space' => false,
|
||||
'ordered_class_elements' => true,
|
||||
'php_unit_strict' => false,
|
||||
'phpdoc_separation' => false,
|
||||
]);
|
||||
60
plugin/think-library/composer.json
Normal file
60
plugin/think-library/composer.json
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "zoujingli/think-library",
|
||||
"version": "8.0.x-dev",
|
||||
"license": "MIT",
|
||||
"homepage": "https://thinkadmin.top",
|
||||
"description": "Library for ThinkAdmin",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Anyon",
|
||||
"email": "zoujingli@qq.com"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "zoujingli@qq.com",
|
||||
"wiki": "https://thinkadmin.top",
|
||||
"forum": "https://thinkadmin.top",
|
||||
"source": "https://gitee.com/zoujingli/ThinkLibrary",
|
||||
"issues": "https://gitee.com/zoujingli/ThinkLibrary/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"ext-gd": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-zlib": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-mbstring": "*",
|
||||
"topthink/think-orm": "^4.0",
|
||||
"topthink/framework": "^8.1"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/common.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"think\\admin\\": "src"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5|^10.0"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"think\\admin\\tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"think": {
|
||||
"services": [
|
||||
"think\\admin\\Library"
|
||||
]
|
||||
}
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"minimum-stability": "dev",
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
21
plugin/think-library/license
Normal file
21
plugin/think-library/license
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014~2025 邹景立 <zoujingli@qq.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
19
plugin/think-library/phpunit.xml.dist
Normal file
19
plugin/think-library/phpunit.xml.dist
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
colors="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
stopOnError="false"
|
||||
backupGlobals="false"
|
||||
stopOnFailure="false"
|
||||
processIsolation="false"
|
||||
backupStaticProperties="false"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
beStrictAboutTestsThatDoNotTestAnything="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="ThinkAdmin Test Suite">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
386
plugin/think-library/readme.md
Normal file
386
plugin/think-library/readme.md
Normal file
@ -0,0 +1,386 @@
|
||||
# ThinkLibrary
|
||||
|
||||
核心基础库,为 ThinkAdmin 提供运行时基础设施。
|
||||
|
||||
## 功能定位
|
||||
|
||||
- 提供运行时服务、认证会话、路由适配、队列契约等核心能力
|
||||
- 定义标准控制器、模型、命令等基础类型
|
||||
- 提供 Helper 工具集(查询、表单、页面构建器)
|
||||
- 实现 JWT 令牌、CacheSession 等认证机制
|
||||
- 提供 Storage 门面和标准契约
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
composer require zoujingli/think-library
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
在 `composer.json` 中注册服务:
|
||||
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"think": {
|
||||
"services": ["think\\admin\\Library"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 运行时服务
|
||||
|
||||
- **RuntimeService**: 运行时环境配置同步,处理 PHAR 兼容、目录初始化
|
||||
- **AppService**: 应用管理、插件发现、服务注册、配置管理
|
||||
- **NodeService**: 节点管理、权限判断、菜单节点处理
|
||||
- **QueueService**: 队列门面服务(真实实现在 Worker 插件)
|
||||
|
||||
### 2. 认证会话
|
||||
|
||||
- **JwtToken**: JWT 令牌生成、验证、刷新
|
||||
- **CacheSession**: 基于 Token SID 的缓存会话管理
|
||||
- **RequestTokenService**: 请求级令牌识别服务
|
||||
- **SystemContext**: 系统上下文接口(运行时实现)
|
||||
- **NullSystemContext**: 空实现上下文(未认证状态)
|
||||
|
||||
### 3. 路由适配
|
||||
|
||||
- **Route**: 自定义路由对象,支持插件路由注册
|
||||
- **Url**: URL 构建工具,支持插件 URL 生成
|
||||
- **MultAccess**: 多应用访问中间件,处理插件前缀切换
|
||||
|
||||
### 4. 基础类型
|
||||
|
||||
- **Controller**: 标准控制器基类,提供通用控制器方法
|
||||
- **Model**: 标准模型基类,扩展软删除、时间戳等能力
|
||||
- **Command**: 标准命令基类,提供命令通用方法
|
||||
- **Plugin**: 插件管理类,处理插件元数据加载
|
||||
- **Service**: 服务基类,提供基础服务方法
|
||||
- **Exception**: 框架异常类,统一异常处理
|
||||
|
||||
### 5. Helper 工具
|
||||
|
||||
- **QueryHelper**: 数据查询构建器,支持分页、筛选、排序
|
||||
- **FormBuilder**: 表单构建器,支持表单元素快速生成
|
||||
- **PageBuilder**: 列表页面构建器,支持表格、筛选、操作列
|
||||
- **ValidateHelper**: 数据验证器,支持规则验证、错误提示
|
||||
|
||||
### 6. 扩展工具
|
||||
|
||||
- **CodeToolkit**: 编码工具(加密/解密/Base64/Hash)
|
||||
- **FileTools**: 文件操作工具(目录创建、文件复制、权限管理)
|
||||
- **HttpClient**: HTTP 客户端(cURL 封装、请求构建)
|
||||
- **ArrayTree**: 数组树工具(树形结构构建、扁平化)
|
||||
- **FaviconBuilder**: 网站图标生成器
|
||||
- **ImageSliderVerify**: 图片滑块验证码
|
||||
- **JsonRpcHttpClient**: JSON-RPC HTTP 客户端
|
||||
- **JsonRpcHttpServer**: JSON-RPC HTTP 服务端
|
||||
|
||||
## 使用示例
|
||||
|
||||
### JWT 令牌
|
||||
|
||||
```php
|
||||
use think\admin\service\JwtToken;
|
||||
|
||||
// 生成令牌
|
||||
$token = JwtToken::token([
|
||||
'user_id' => 1,
|
||||
'username' => 'admin',
|
||||
'exp' => time() + 7200
|
||||
]);
|
||||
|
||||
// 验证令牌
|
||||
try {
|
||||
$data = JwtToken::verify($token);
|
||||
// $data['user_id'], $data['username']
|
||||
} catch (\think\admin\Exception $e) {
|
||||
// 验证失败
|
||||
}
|
||||
```
|
||||
|
||||
### CacheSession
|
||||
|
||||
```php
|
||||
use think\admin\service\CacheSession;
|
||||
|
||||
// 写入会话
|
||||
CacheSession::set('key', 'value', 3600);
|
||||
|
||||
// 读取会话
|
||||
$value = CacheSession::get('key', 'default');
|
||||
|
||||
// 批量写入
|
||||
CacheSession::put([
|
||||
'key1' => 'value1',
|
||||
'key2' => 'value2'
|
||||
], 3600);
|
||||
|
||||
// 删除会话
|
||||
CacheSession::delete('key');
|
||||
|
||||
// 清空会话
|
||||
CacheSession::clear();
|
||||
```
|
||||
|
||||
### 控制器基类
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace app\index\controller;
|
||||
|
||||
use think\admin\Controller;
|
||||
|
||||
class Index extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// 返回成功
|
||||
$this->success('操作成功', ['id' => 1]);
|
||||
|
||||
// 返回失败
|
||||
$this->error('操作失败');
|
||||
|
||||
// 返回视图
|
||||
$this->fetch('index/index', ['title' => '首页']);
|
||||
|
||||
// 数据验证
|
||||
$data = $this->_vali([
|
||||
'username.require' => '用户名不能为空',
|
||||
'email.email' => '邮箱格式不正确'
|
||||
], 'post');
|
||||
|
||||
// 创建异步任务
|
||||
$this->_queue('导出数据', 'php think export:users');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 查询构建器
|
||||
|
||||
```php
|
||||
use think\admin\helper\QueryHelper;
|
||||
|
||||
// 基础查询
|
||||
$query = QueryHelper::init(User::class)
|
||||
->where('status', 1)
|
||||
->order('id', 'DESC')
|
||||
->paginate(20);
|
||||
|
||||
// 分页查询
|
||||
$page = $query->getPage();
|
||||
$list = $query->getList();
|
||||
|
||||
// 条件筛选
|
||||
$query->addWhere($request->get('keyword'), 'username', 'like');
|
||||
$query->addFilter($request->get('status'), 'status');
|
||||
```
|
||||
|
||||
### 表单构建器
|
||||
|
||||
```php
|
||||
use think\admin\helper\FormBuilder;
|
||||
|
||||
$form = FormBuilder::create();
|
||||
|
||||
// 文本输入
|
||||
$form->text('username', '用户名')
|
||||
->required()
|
||||
->placeholder('请输入用户名');
|
||||
|
||||
// 下拉选择
|
||||
$form->select('status', '状态')
|
||||
->options([1 => '正常', 0 => '禁用'])
|
||||
->default(1);
|
||||
|
||||
// 日期选择
|
||||
$form->date('birthday', '生日');
|
||||
|
||||
// 文件上传
|
||||
$form->file('avatar', '头像')
|
||||
->image()
|
||||
->size(2); // 限制 2MB
|
||||
|
||||
// 保存数据
|
||||
if ($form->isPost()) {
|
||||
$data = $form->getData();
|
||||
// 保存逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
think-library/
|
||||
├── src/
|
||||
│ ├── contract/ # 标准契约接口
|
||||
│ │ ├── SystemContextInterface.php
|
||||
│ │ └── ...
|
||||
│ ├── extend/ # 扩展工具类
|
||||
│ │ ├── ArrayTree.php
|
||||
│ │ ├── CodeToolkit.php
|
||||
│ │ ├── FileTools.php
|
||||
│ │ └── HttpClient.php
|
||||
│ ├── helper/ # 构建器工具
|
||||
│ │ ├── FormBuilder.php
|
||||
│ │ ├── PageBuilder.php
|
||||
│ │ ├── QueryHelper.php
|
||||
│ │ └── ValidateHelper.php
|
||||
│ ├── middleware/ # 中间件
|
||||
│ │ └── MultAccess.php
|
||||
│ ├── model/ # 模型扩展
|
||||
│ │ └── QueryFactory.php
|
||||
│ ├── route/ # 路由相关
|
||||
│ │ ├── Route.php
|
||||
│ │ └── Url.php
|
||||
│ ├── runtime/ # 运行时上下文
|
||||
│ │ ├── RequestContext.php
|
||||
│ │ ├── RequestTokenService.php
|
||||
│ │ ├── SystemContext.php
|
||||
│ │ └── NullSystemContext.php
|
||||
│ ├── service/ # 核心服务
|
||||
│ │ ├── AppService.php
|
||||
│ │ ├── CacheSession.php
|
||||
│ │ ├── JwtToken.php
|
||||
│ │ ├── NodeService.php
|
||||
│ │ ├── QueueService.php
|
||||
│ │ └── RuntimeService.php
|
||||
│ ├── common.php # 全局函数
|
||||
│ ├── Controller.php # 标准控制器
|
||||
│ ├── Model.php # 标准模型
|
||||
│ ├── Command.php # 标准命令
|
||||
│ ├── Plugin.php # 插件管理
|
||||
│ ├── Service.php # 服务基类
|
||||
│ ├── Library.php # 服务注册类
|
||||
│ └── Exception.php # 异常类
|
||||
└── tests/ # 单元测试
|
||||
```
|
||||
|
||||
## 全局函数
|
||||
|
||||
ThinkLibrary 提供以下全局函数:
|
||||
|
||||
### 全局函数
|
||||
|
||||
ThinkLibrary 提供以下全局函数:
|
||||
|
||||
#### 应用相关
|
||||
|
||||
- `syspath($path)`: 获取系统根目录路径(PHAR 环境下返回包内路径)
|
||||
- `runpath($path)`: 获取运行时目录路径(PHAR 环境下返回外部可写目录)
|
||||
- `sysconf($name, $default = null)`: 读取系统配置
|
||||
- `sysvar($key, $value = null)`: 读写系统变量
|
||||
- `isOnline()`: 判断是否生产环境(非调试模式)
|
||||
|
||||
#### URL 相关
|
||||
|
||||
- `sysuri($node, $vars = [], $suffix = true)`: 生成系统 URL(后台页面)
|
||||
- `apiuri($node, $vars = [], $suffix = true)`: 生成 API URL(接口调用)
|
||||
- `plguri($node, $vars = [], $suffix = true)`: 生成插件工作台 URL(由 ThinkPlugsSystem 提供)
|
||||
|
||||
#### 认证相关
|
||||
|
||||
- `auth($node)`: 检查权限(判断当前用户是否有指定节点权限)
|
||||
- `admin_user()`: 获取当前管理员信息(已认证的系统用户)
|
||||
- `tsession($name = null, $default = null)`: 读写 Token 会话(基于 CacheSession)
|
||||
|
||||
#### 工具函数
|
||||
|
||||
- `xss_safe($str)`: XSS 安全过滤(过滤危险 HTML/JS 标签)
|
||||
- `str2arr($str)`: 字符串转数组(支持逗号、分号、换行分隔)
|
||||
- `arr2str($arr)`: 数组转字符串(逗号连接)
|
||||
- `data_save($dbQuery, $data, $key = 'id', $where = [])`: 数据保存(新增或更新)
|
||||
- `normalize($text)`: 文本标准化(全角转半角、统一空格等)
|
||||
|
||||
## 依赖要求
|
||||
|
||||
- PHP >= 8.1
|
||||
- ThinkPHP >= 8.1
|
||||
- 扩展:gd, curl, json, zlib, iconv, openssl, mbstring, fileinfo
|
||||
- 推荐:redis (用于 CacheSession 和缓存驱动)
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 命名空间
|
||||
|
||||
所有类使用 `think\admin\` 命名空间
|
||||
|
||||
### 代码风格
|
||||
|
||||
遵循 PSR-12 规范,使用 PHP-CS-Fixer 统一代码风格
|
||||
|
||||
```bash
|
||||
# 运行代码风格修复
|
||||
vendor/bin/php-cs-fixer fix
|
||||
```
|
||||
|
||||
### 测试规范
|
||||
|
||||
每个核心功能都应该有对应的单元测试
|
||||
|
||||
```bash
|
||||
# 运行 ThinkLibrary 测试
|
||||
vendor/bin/phpunit plugin/think-library/tests/
|
||||
```
|
||||
|
||||
### 静态分析
|
||||
|
||||
使用 PHPStan 进行静态代码分析
|
||||
|
||||
```bash
|
||||
# 运行代码分析
|
||||
composer analyse
|
||||
```
|
||||
|
||||
## 认证说明
|
||||
|
||||
### JWT 令牌
|
||||
|
||||
- 后台认证统一使用 `Authorization: Bearer <JWT>`
|
||||
- JWT 有效期由 `config/app.php` 中的 `system_token_expire` 配置
|
||||
- JWT 内包含 `sid` 用于绑定 CacheSession
|
||||
- 不再使用标准 PHP Session 承载后台登录态
|
||||
|
||||
### Token Session
|
||||
|
||||
- 基于 `CacheSession` 实现
|
||||
- 统一入口为 `tsession()` 函数
|
||||
- 支持 file 和 redis 等多种缓存驱动
|
||||
- 临时用户态绑定到 Token 的 `sid`
|
||||
|
||||
## PHAR 兼容性
|
||||
|
||||
当使用 PHAR 打包运行时:
|
||||
|
||||
- `syspath()` 返回 PHAR 包内路径(只读资源)
|
||||
- `runpath()` 返回 PHAR 外部路径(可写目录)
|
||||
- 字体、SQLite、缓存等落盘操作必须使用 `runpath()`
|
||||
- GD 的 `imagettftext()` 不支持 `phar://` 路径
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
当前测试覆盖包括:
|
||||
|
||||
- 代码加密解密工具测试
|
||||
- JWT 令牌生成验证测试
|
||||
- 通用函数测试
|
||||
- 架构边界测试
|
||||
- 插件依赖边界测试
|
||||
- 迁移归属测试
|
||||
- 表单/页面构建器测试
|
||||
- 请求令牌服务测试
|
||||
- 多应用访问调度测试
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
## 相关链接
|
||||
|
||||
- 官网文档:https://thinkadmin.top
|
||||
- Gitee: https://gitee.com/zoujingli/ThinkLibrary
|
||||
- Github: https://github.com/zoujingli/ThinkLibrary
|
||||
71
plugin/think-library/src/Builder.php
Normal file
71
plugin/think-library/src/Builder.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\admin\builder\form\FormBuilder;
|
||||
|
||||
/**
|
||||
* 兼容旧版 `Builder::mk()` 调用入口。
|
||||
* 旧后台仍在少量页面直接链式构建表单,这里转接到新的 FormBuilder。
|
||||
*/
|
||||
class Builder
|
||||
{
|
||||
private FormBuilder $builder;
|
||||
|
||||
private function __construct(FormBuilder $builder)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
public static function mk(string $type = 'form', string $mode = 'modal'): self
|
||||
{
|
||||
return new self(FormBuilder::make($type, $mode));
|
||||
}
|
||||
|
||||
public function addTextInput(string $name, string $title, string $substr = '', bool $required = false, string $remark = '', ?string $pattern = null, array $attrs = []): self
|
||||
{
|
||||
$this->builder->addTextInput($name, $title, $substr, $required, $remark, $pattern, $attrs);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addSubmitButton(string $name = '保存数据', string $confirm = '', array $attrs = [], string $class = ''): self
|
||||
{
|
||||
$this->builder->addSubmitButton($name, $confirm, $attrs, $class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCancelButton(string $name = '取消编辑', string $confirm = '确定要取消编辑吗?', array $attrs = [], string $class = 'layui-btn-danger'): self
|
||||
{
|
||||
$this->builder->addCancelButton($name, $confirm, $attrs, $class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetch(array $vars = []): mixed
|
||||
{
|
||||
return $this->builder->fetch($vars);
|
||||
}
|
||||
|
||||
public function __call(string $name, array $arguments): mixed
|
||||
{
|
||||
$result = $this->builder->{$name}(...$arguments);
|
||||
return $result === $this->builder ? $this : $result;
|
||||
}
|
||||
}
|
||||
112
plugin/think-library/src/Command.php
Normal file
112
plugin/think-library/src/Command.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\admin\contract\QueueManagerInterface;
|
||||
use think\admin\service\QueueService;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
|
||||
/**
|
||||
* Shared command base class.
|
||||
*/
|
||||
abstract class Command extends \think\console\Command
|
||||
{
|
||||
protected QueueManagerInterface $queue;
|
||||
|
||||
/**
|
||||
* 设置队列消息.
|
||||
* @param int $total 总数
|
||||
* @param int $count 当前进度
|
||||
* @param string $message 消息内容
|
||||
* @param int $backline 回滚行数
|
||||
*/
|
||||
public function setQueueMessage(int $total, int $count, string $message = '', int $backline = 0): static
|
||||
{
|
||||
$this->queue->message($total, $count, $message, $backline);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command initialization.
|
||||
*/
|
||||
protected function initialize(Input $input, Output $output): static
|
||||
{
|
||||
$this->queue = QueueService::instance();
|
||||
if (($code = QueueService::currentCode()) !== '' && $this->queue->getCode() !== $code) {
|
||||
$this->queue->initialize($code);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置队列错误消息.
|
||||
*/
|
||||
protected function setQueueError(string $message): void
|
||||
{
|
||||
if (QueueService::inContext()) {
|
||||
$this->queue->error($message);
|
||||
} else {
|
||||
$this->writeConsoleMessage($message);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置队列成功消息.
|
||||
*/
|
||||
protected function setQueueSuccess(string $message): void
|
||||
{
|
||||
if (QueueService::inContext()) {
|
||||
$this->queue->success($message);
|
||||
} else {
|
||||
$this->writeConsoleMessage($message);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置队列进度消息.
|
||||
*/
|
||||
protected function setQueueProgress(?string $message = null, ?string $progress = null, int $backline = 0): static
|
||||
{
|
||||
if (QueueService::inContext()) {
|
||||
$this->queue->progress(QueueService::STATE_LOCK, $message, $progress, $backline);
|
||||
} elseif (is_string($message)) {
|
||||
$this->writeConsoleMessage($message, $backline);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出控制台消息.
|
||||
*/
|
||||
protected function writeConsoleMessage(string $message, int $backline = 0): void
|
||||
{
|
||||
while ($backline-- > 0) {
|
||||
$message = "\033[1A\r\033[K{$message}";
|
||||
}
|
||||
|
||||
$this->output->write($message . PHP_EOL);
|
||||
}
|
||||
}
|
||||
348
plugin/think-library/src/Controller.php
Normal file
348
plugin/think-library/src/Controller.php
Normal file
@ -0,0 +1,348 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\admin\builder\form\FormBuilder;
|
||||
use think\admin\builder\page\PageBuilder;
|
||||
use think\admin\helper\ValidateHelper;
|
||||
use think\admin\runtime\SystemContext;
|
||||
use think\admin\service\JwtToken;
|
||||
use think\admin\service\NodeService;
|
||||
use think\admin\service\QueueService;
|
||||
use think\admin\service\ResponseModeService;
|
||||
use think\admin\service\AppService;
|
||||
use think\App;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\Request;
|
||||
|
||||
/**
|
||||
* 标准控制器基类.
|
||||
* @class Controller
|
||||
*/
|
||||
class Controller extends \stdClass
|
||||
{
|
||||
/**
|
||||
* 应用容器.
|
||||
*/
|
||||
public App $app;
|
||||
|
||||
/**
|
||||
* 请求GET参数.
|
||||
*/
|
||||
public array $get = [];
|
||||
|
||||
/**
|
||||
* 当前功能节点.
|
||||
*/
|
||||
public string $node = '';
|
||||
|
||||
/**
|
||||
* 请求参数对象
|
||||
*/
|
||||
public Request $request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
if (in_array($app->request->action(), get_class_methods(__CLASS__))) {
|
||||
$this->error('禁止访问内置方法!');
|
||||
}
|
||||
$this->get = $app->request->get();
|
||||
$this->app = $app->bind('think\admin\Controller', $this);
|
||||
$this->node = NodeService::getCurrent();
|
||||
$this->request = $this->app->request;
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回失败的内容.
|
||||
* @param mixed $info 消息内容
|
||||
* @param mixed $data 返回数据
|
||||
* @param mixed $code 返回代码
|
||||
*/
|
||||
public function error(mixed $info, mixed $data = '{-null-}', mixed $code = 500): never
|
||||
{
|
||||
$this->respond($info, $data, $code, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功的内容.
|
||||
* @param mixed $info 消息内容
|
||||
* @param mixed $data 返回数据
|
||||
* @param mixed $code 返回代码
|
||||
*/
|
||||
public function success(mixed $info, mixed $data = '{-null-}', mixed $code = 200): never
|
||||
{
|
||||
$this->respond($info, $data, $code, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出标准 JSON 响应.
|
||||
* @param mixed $info 消息内容
|
||||
* @param mixed $data 返回数据
|
||||
* @param mixed $code 返回代码
|
||||
* @param int $default 默认状态
|
||||
*/
|
||||
protected function respond(mixed $info, mixed $data, mixed $code, int $default): never
|
||||
{
|
||||
if ($data === '{-null-}') {
|
||||
$data = new \stdClass();
|
||||
}
|
||||
$status = $this->normalizeResponseCode($code, $default);
|
||||
$result = [
|
||||
'code' => $status,
|
||||
'info' => is_string($info) ? lang($info) : $info,
|
||||
'data' => $data,
|
||||
];
|
||||
if (JwtToken::isRejwt()) {
|
||||
$result['token'] = JwtToken::token();
|
||||
} elseif ($token = SystemContext::instance()->buildToken()) {
|
||||
$result['token'] = $token;
|
||||
SystemContext::instance()->syncTokenCookie($token);
|
||||
}
|
||||
throw new HttpResponseException(json($result)->code(200));
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧版 1/0 返回码,并收敛到业务状态码.
|
||||
*/
|
||||
protected function normalizeResponseCode(mixed $code, int $default): int
|
||||
{
|
||||
$status = intval($code);
|
||||
if ($status === 1) {
|
||||
return 200;
|
||||
}
|
||||
if ($status === 0) {
|
||||
return 500;
|
||||
}
|
||||
if ($status < 100 || $status > 599) {
|
||||
return $default;
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL重定向.
|
||||
* @param string $url 跳转链接
|
||||
* @param int $code 跳转代码
|
||||
*/
|
||||
public function redirect(string $url, int $code = 302): void
|
||||
{
|
||||
throw new HttpResponseException(redirect($url, $code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回视图内容.
|
||||
* @param string $tpl 模板名称
|
||||
* @param array $vars 模板变量
|
||||
* @param null|string $node 授权节点
|
||||
*/
|
||||
public function fetch(string $tpl = '', array $vars = [], ?string $node = null): void
|
||||
{
|
||||
if (function_exists('system_view_context')) {
|
||||
$vars = array_merge(system_view_context(), $vars);
|
||||
}
|
||||
foreach (get_object_vars($this) as $name => $value) {
|
||||
$vars[$name] = $value;
|
||||
}
|
||||
$vars['staticRoot'] = strval($vars['staticRoot'] ?? AppService::uri('static'));
|
||||
if (!isset($vars['pageTitle']) || !is_scalar($vars['pageTitle']) || strval($vars['pageTitle']) === '') {
|
||||
$vars['pageTitle'] = isset($vars['title']) && is_scalar($vars['title']) ? strval($vars['title']) : '';
|
||||
}
|
||||
throw new HttpResponseException(view($tpl, $vars));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前控制器表现层模式.
|
||||
*/
|
||||
public function presentationMode(): string
|
||||
{
|
||||
return ResponseModeService::resolve($this->request, static::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前控制器是否走 API 模式.
|
||||
*/
|
||||
public function usesApiPresentation(): bool
|
||||
{
|
||||
return ResponseModeService::prefersApi($this->request, static::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧版控制器在表单页显式启用 token 的调用。
|
||||
* 新 Builder 已内建请求防重逻辑,这里保留空实现避免老入口报错。
|
||||
*/
|
||||
protected function _applyFormToken(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应页面 Builder.
|
||||
* @param array<string, mixed> $context
|
||||
* @param array<string, mixed> $payload
|
||||
* @param null|callable(PageBuilder,array<string,mixed>):void $view
|
||||
*/
|
||||
protected function respondWithPageBuilder(PageBuilder $builder, array $context = [], ?callable $view = null, array $payload = []): void
|
||||
{
|
||||
$mode = $this->presentationMode();
|
||||
if ($mode === ResponseModeService::MODE_API) {
|
||||
$this->success('获取页面成功!', array_merge([
|
||||
'driver' => 'builder',
|
||||
'scene' => 'page',
|
||||
'mode' => $mode,
|
||||
'token' => ['header' => ResponseModeService::apiHeader()],
|
||||
'builder' => [
|
||||
'type' => 'page',
|
||||
'schema' => $builder->toArray(),
|
||||
],
|
||||
'context' => $context,
|
||||
], $payload));
|
||||
}
|
||||
|
||||
if (is_callable($view)) {
|
||||
$view($builder, $context);
|
||||
return;
|
||||
}
|
||||
|
||||
$builder->fetch($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应表单 Builder.
|
||||
* @param array<string, mixed> $context
|
||||
* @param array<string, mixed> $data
|
||||
* @param array<string, mixed> $payload
|
||||
* @param null|callable(FormBuilder,array<string,mixed>,array<string,mixed>):void $view
|
||||
*/
|
||||
protected function respondWithFormBuilder(FormBuilder $builder, array $context = [], array $data = [], ?callable $view = null, array $payload = []): void
|
||||
{
|
||||
$mode = $this->presentationMode();
|
||||
if ($mode === ResponseModeService::MODE_API) {
|
||||
$this->success('获取表单成功!', array_merge([
|
||||
'driver' => 'builder',
|
||||
'scene' => 'form',
|
||||
'mode' => $mode,
|
||||
'token' => ['header' => ResponseModeService::apiHeader()],
|
||||
'builder' => [
|
||||
'type' => 'form',
|
||||
'schema' => $builder->toArray(),
|
||||
'rules' => $builder->getValidateRules(),
|
||||
],
|
||||
'context' => $context,
|
||||
'data' => $data,
|
||||
], $payload));
|
||||
}
|
||||
|
||||
if (is_callable($view)) {
|
||||
$view($builder, $context, $data);
|
||||
return;
|
||||
}
|
||||
|
||||
$builder->fetch(array_merge($context, ['vo' => $data]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板变量赋值
|
||||
* @param mixed $name 要显示的模板变量
|
||||
* @param mixed $value 变量的值
|
||||
* @return $this
|
||||
*/
|
||||
public function assign(mixed $name, mixed $value = ''): static
|
||||
{
|
||||
if (is_string($name)) {
|
||||
$this->{$name} = $value;
|
||||
} elseif (is_array($name)) {
|
||||
foreach ($name as $k => $v) {
|
||||
if (is_string($k)) {
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据回调处理机制.
|
||||
* @param string $name 回调方法名称
|
||||
* @param mixed $one 回调引用参数1
|
||||
* @param mixed $two 回调引用参数2
|
||||
* @param mixed $thr 回调引用参数3
|
||||
*/
|
||||
public function callback(string $name, mixed &$one = [], mixed &$two = [], mixed &$thr = []): bool
|
||||
{
|
||||
if (is_callable($name)) {
|
||||
return call_user_func($name, $this, $one, $two, $thr);
|
||||
}
|
||||
foreach (["_{$this->app->request->action()}{$name}", $name] as $method) {
|
||||
if (method_exists($this, $method) && $this->{$method}($one, $two, $thr) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制器初始化.
|
||||
*/
|
||||
protected function initialize() {}
|
||||
|
||||
/**
|
||||
* 快捷输入并验证( 支持 规则 # 别名 ).
|
||||
* @param array $rules 验证规则( 验证信息数组 )
|
||||
* @param array|string $type 输入方式 ( post. 或 get. )
|
||||
* @param null|callable $callable 异常处理操作
|
||||
*/
|
||||
protected function _vali(array $rules, array|string $type = '', ?callable $callable = null): array
|
||||
{
|
||||
return ValidateHelper::instance()->init($rules, $type, $callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建异步任务并返回任务编号.
|
||||
* @param string $title 任务名称
|
||||
* @param string $command 执行内容
|
||||
* @param int $later 延时执行时间
|
||||
* @param array $data 任务附加数据
|
||||
* @param int $loops 循环等待时间
|
||||
* @param ?int $legacyLoops 兼容旧调用的循环等待时间参数
|
||||
*/
|
||||
protected function _queue(string $title, string $command, int $later = 0, array $data = [], int $loops = 0, ?int $legacyLoops = null): void
|
||||
{
|
||||
try {
|
||||
$queue = QueueService::register($title, $command, $later, $data, $loops, $legacyLoops);
|
||||
$this->success('创建任务成功!', $queue->getCode());
|
||||
} catch (Exception $exception) {
|
||||
$code = $exception->getData();
|
||||
if (is_string($code) && stripos($code, 'Q') === 0) {
|
||||
$this->success('任务已经存在,无需再次创建!', $code);
|
||||
} else {
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
} catch (HttpResponseException $exception) {
|
||||
throw $exception;
|
||||
} catch (\Exception $exception) {
|
||||
trace_file($exception);
|
||||
$this->error(lang('创建任务失败,%s', [$exception->getMessage()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
63
plugin/think-library/src/Exception.php
Normal file
63
plugin/think-library/src/Exception.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
/**
|
||||
* 自定义数据异常.
|
||||
* @class Exception
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
/**
|
||||
* 异常数据对象
|
||||
*/
|
||||
protected mixed $data = [];
|
||||
|
||||
/**
|
||||
* Exception constructor.
|
||||
* @param mixed $data
|
||||
*/
|
||||
public function __construct(string $message = '', int $code = 0, $data = [])
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->code = $code;
|
||||
$this->data = $data;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常停止数据.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置异常停止数据.
|
||||
* @param mixed $data
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
82
plugin/think-library/src/Helper.php
Normal file
82
plugin/think-library/src/Helper.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\App;
|
||||
use think\Container;
|
||||
|
||||
/**
|
||||
* 控制器助手基类.
|
||||
*
|
||||
* 为控制器提供通用的辅助功能,包括表单构建、查询构建、页面构建等
|
||||
* 所有 Helper 类都继承自此类
|
||||
*
|
||||
* @class Helper
|
||||
*/
|
||||
abstract class Helper
|
||||
{
|
||||
/**
|
||||
* 应用容器.
|
||||
*/
|
||||
public App $app;
|
||||
|
||||
/**
|
||||
* 控制器实例.
|
||||
*/
|
||||
public Controller $class;
|
||||
|
||||
/**
|
||||
* 当前请求方式.
|
||||
*/
|
||||
public string $method;
|
||||
|
||||
/**
|
||||
* 自定输出格式.
|
||||
*/
|
||||
public string $output;
|
||||
|
||||
/**
|
||||
* Helper 构造函数.
|
||||
*
|
||||
* @param App $app 应用实例
|
||||
* @param Controller $class 控制器实例
|
||||
*/
|
||||
public function __construct(App $app, Controller $class)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->class = $class;
|
||||
// 计算指定输出格式
|
||||
$output = strval($app->request->request('output', 'default'));
|
||||
$method = $app->request->method() ?: ($app->runningInConsole() ? 'cli' : 'nil');
|
||||
$this->method = strtolower($method);
|
||||
$this->output = "{$this->method}." . strtolower($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化 Helper 对象(支持依赖注入).
|
||||
*
|
||||
* @param mixed ...$args 构造函数参数
|
||||
*/
|
||||
public static function instance(...$args): static
|
||||
{
|
||||
return Container::getInstance()->invokeClass(static::class, $args);
|
||||
}
|
||||
}
|
||||
158
plugin/think-library/src/Library.php
Normal file
158
plugin/think-library/src/Library.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\admin\extend\FileTools;
|
||||
use think\admin\middleware\MultAccess;
|
||||
use think\admin\runtime\RequestContext;
|
||||
use think\admin\service\RuntimeService;
|
||||
use think\App;
|
||||
use think\exception\HttpException;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\Request;
|
||||
use think\Response;
|
||||
use think\Service;
|
||||
|
||||
/**
|
||||
* 模块注册服务
|
||||
* @class Library
|
||||
*/
|
||||
class Library extends Service
|
||||
{
|
||||
public static App $sapp;
|
||||
|
||||
/**
|
||||
* 启动服务
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// 静态应用赋值
|
||||
static::$sapp = $this->app;
|
||||
|
||||
// 动态应用运行参数
|
||||
RuntimeService::apply();
|
||||
|
||||
// 请求初始化处理
|
||||
$this->app->event->listen('HttpRun', function (Request $request) {
|
||||
// Worker 常驻模式下,请求开始前先清理上次上下文。
|
||||
RequestContext::clear();
|
||||
|
||||
// 运行环境配置同步
|
||||
RuntimeService::sync();
|
||||
|
||||
// 配置默认输入过滤
|
||||
$request->filter([static function ($value) {
|
||||
return is_string($value) ? xss_safe($value) : $value;
|
||||
}]);
|
||||
|
||||
// 判断访问模式兼容处理
|
||||
if ($this->app->runningInConsole()) {
|
||||
// 兼容 CLI 访问控制器
|
||||
if (empty($_SERVER['REQUEST_URI']) && isset($_SERVER['argv'][1])) {
|
||||
$request->setPathinfo($_SERVER['argv'][1]);
|
||||
}
|
||||
} else {
|
||||
// 兼容 HTTP 调用 Console 后 URL 问题
|
||||
$request->setHost($request->host());
|
||||
}
|
||||
|
||||
// 注册多应用中间键
|
||||
$this->app->middleware->add(MultAccess::class);
|
||||
});
|
||||
|
||||
// 请求结束后处理
|
||||
$this->app->event->listen('HttpEnd', static function () {
|
||||
RequestContext::clear();
|
||||
function_exists('sysvar') && sysvar('', '');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化服务
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// 动态加载全局配置
|
||||
[$dir, $ext] = [$this->app->getBasePath(), $this->app->getConfigExt()];
|
||||
FileTools::find($dir, 2, function (\SplFileInfo $info) use ($ext) {
|
||||
$info->isFile() && $info->getBasename() === "sys{$ext}" && Library::load($info->getPathname());
|
||||
});
|
||||
if (is_file($file = "{$dir}common{$ext}")) {
|
||||
Library::load($file);
|
||||
}
|
||||
if (is_file($file = "{$dir}provider{$ext}")) {
|
||||
$this->app->bind(include $file);
|
||||
}
|
||||
if (is_file($file = "{$dir}event{$ext}")) {
|
||||
$this->app->loadEvent(include $file);
|
||||
}
|
||||
if (is_file($file = "{$dir}middleware{$ext}")) {
|
||||
$this->app->middleware->import(include $file, 'app');
|
||||
}
|
||||
|
||||
// 终端 HTTP 访问时特殊处理
|
||||
if (!$this->app->runningInConsole()) {
|
||||
// 动态注释 CORS 跨域处理
|
||||
$this->app->middleware->add(function (Request $request, \Closure $next): Response {
|
||||
$header = ['X-Frame-Options' => $this->app->config->get('app.cors_frame') ?: 'sameorigin'];
|
||||
// HTTP.CORS 跨域规则配置
|
||||
if ($this->app->config->get('app.cors_on', true) && ($origin = $request->header('origin', '-')) !== '-') {
|
||||
if (is_string($hosts = $this->app->config->get('app.cors_host', []))) {
|
||||
$hosts = str2arr($hosts);
|
||||
}
|
||||
if (empty($hosts) || in_array(parse_url(strtolower($origin), PHP_URL_HOST), $hosts)) {
|
||||
$headers = str2arr(strval($this->app->config->get('app.cors_headers', 'X-Device-Code,X-Device-Type')));
|
||||
$headers = array_values(array_filter(array_unique(array_map('trim', $headers))));
|
||||
$header['Access-Control-Allow-Origin'] = $origin;
|
||||
$header['Access-Control-Allow-Methods'] = $this->app->config->get('app.cors_methods', 'GET,PUT,POST,PATCH,DELETE');
|
||||
$allow = array_merge(['Authorization', 'Content-Type', 'If-Match', 'If-Modified-Since', 'If-None-Match', 'If-Unmodified-Since', 'X-Requested-With'], $headers);
|
||||
$header['Access-Control-Allow-Headers'] = join(',', array_unique($allow));
|
||||
if ($this->app->config->get('app.cors_credentials', false)) {
|
||||
$header['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
if (!empty($headers)) {
|
||||
$header['Access-Control-Expose-Headers'] = join(',', $headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 跨域预请求状态处理
|
||||
if ($request->isOptions()) {
|
||||
throw new HttpResponseException(response()->code(204)->header($header));
|
||||
}
|
||||
return $next($request)->header($header);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态加载文件.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function load(string $file)
|
||||
{
|
||||
try {
|
||||
return include $file;
|
||||
} catch (\Error|\Throwable $error) {
|
||||
trace_file($error);
|
||||
throw new HttpException(500, $error->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
138
plugin/think-library/src/Model.php
Normal file
138
plugin/think-library/src/Model.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\admin\helper\QueryHelper;
|
||||
use think\admin\model\QueryFactory;
|
||||
use think\db\BaseQuery;
|
||||
|
||||
/**
|
||||
* 基础模型类。
|
||||
*
|
||||
* @class Model
|
||||
* @phpstan-consistent-constructor
|
||||
* @mixin \think\db\Query
|
||||
* @method static bool mSave(array $data = [], string $field = '', mixed $where = [])
|
||||
* @method static bool mDelete(string $field = '', mixed $where = [])
|
||||
* @method static bool|array mForm(string $template = '', string $field = '', mixed $where = [], array $data = [])
|
||||
* @method static bool|int mUpdate(array $data = [], string $field = '', mixed $where = [])
|
||||
* @method static QueryHelper mQuery($input = null, callable $callable = null)
|
||||
* @method $this onAdminSave(string $changeIds)
|
||||
* @method $this onAdminUpdate(string $changeIds)
|
||||
* @method $this onAdminInsert(string $changeIds)
|
||||
* @method $this onAdminDelete(string $changeIds)
|
||||
*/
|
||||
abstract class Model extends \think\Model
|
||||
{
|
||||
/**
|
||||
* 日志过滤。
|
||||
* @var callable
|
||||
*/
|
||||
public static $oplogCall;
|
||||
|
||||
protected string $autoWriteTimestamp = 'datetime';
|
||||
|
||||
protected $createTime = 'create_time';
|
||||
|
||||
protected $updateTime = 'update_time';
|
||||
|
||||
/**
|
||||
* 日志类型。
|
||||
* @var string
|
||||
*/
|
||||
protected $oplogType;
|
||||
|
||||
/**
|
||||
* 日志名称。
|
||||
* @var string
|
||||
*/
|
||||
protected $oplogName;
|
||||
|
||||
/**
|
||||
* 静态魔术方法。
|
||||
* @param string $method 方法名称
|
||||
* @param array $args 调用参数
|
||||
* @return false|int|mixed|QueryHelper
|
||||
*/
|
||||
public static function __callStatic($method, $args)
|
||||
{
|
||||
return QueryHelper::make(static::class, $method, $args, function ($method, $args) {
|
||||
return parent::__callStatic($method, $args);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用魔术方法。
|
||||
* @param string $method 方法名称
|
||||
* @param array $args 调用参数
|
||||
* @return $this|false|mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$oplogs = [
|
||||
'onAdminSave' => '修改%s[%s]状态',
|
||||
'onAdminUpdate' => '更新%s[%s]记录',
|
||||
'onAdminInsert' => '增加%s[%s]成功',
|
||||
'onAdminDelete' => '删除%s[%s]成功',
|
||||
];
|
||||
if (isset($oplogs[$method])) {
|
||||
if ($this->oplogType && $this->oplogName) {
|
||||
$changeIds = $args[0] ?? '';
|
||||
if (is_callable(static::$oplogCall)) {
|
||||
$changeIds = call_user_func(static::$oplogCall, $method, $changeIds, $this);
|
||||
}
|
||||
sysoplog($this->oplogType, lang($oplogs[$method], [lang($this->oplogName), $changeIds]));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
return parent::__call($method, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建查询实例。
|
||||
*/
|
||||
public static function mq(array $data = []): BaseQuery
|
||||
{
|
||||
return QueryFactory::build(static::mk($data)->newQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模型实例。
|
||||
*/
|
||||
public static function mk(array $data = []): static
|
||||
{
|
||||
return new static($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加模型数据并标记为待持久化变更。
|
||||
*/
|
||||
public function appendData(array $data, bool $overwrite = false): static
|
||||
{
|
||||
foreach ($data as $name => $value) {
|
||||
if ($overwrite || !$this->hasData($name)) {
|
||||
$this->setAttr($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
423
plugin/think-library/src/Plugin.php
Normal file
423
plugin/think-library/src/Plugin.php
Normal file
@ -0,0 +1,423 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\admin\service\AppService;
|
||||
use think\admin\service\NodeService;
|
||||
use think\App;
|
||||
use think\Service;
|
||||
|
||||
/**
|
||||
* 插件注册服务
|
||||
*
|
||||
* @class Plugin
|
||||
*/
|
||||
abstract class Plugin extends Service
|
||||
{
|
||||
/**
|
||||
* 插件类型.
|
||||
*/
|
||||
protected string $appType = '';
|
||||
|
||||
/**
|
||||
* 必填,插件包名.
|
||||
*/
|
||||
protected string $package = '';
|
||||
|
||||
/**
|
||||
* 必填,插件编码
|
||||
*/
|
||||
protected string $appCode = '';
|
||||
|
||||
/**
|
||||
* 必填,插件名称.
|
||||
*/
|
||||
protected string $appName = '';
|
||||
|
||||
/**
|
||||
* 可选,插件目录.
|
||||
*/
|
||||
protected string $appPath = '';
|
||||
|
||||
/**
|
||||
* 可选,插件别名.
|
||||
*/
|
||||
protected string $appAlias = '';
|
||||
|
||||
/**
|
||||
* 可选,主访问前缀.
|
||||
*/
|
||||
protected string $appPrefix = '';
|
||||
|
||||
/**
|
||||
* 可选,访问前缀集合.
|
||||
*/
|
||||
protected array $appPrefixes = [];
|
||||
|
||||
/**
|
||||
* 可选,命名空间.
|
||||
*/
|
||||
protected string $appSpace = '';
|
||||
|
||||
/**
|
||||
* 可选,注册服务
|
||||
*/
|
||||
protected string $appService = '';
|
||||
|
||||
/**
|
||||
* 可选,文档地址.
|
||||
*/
|
||||
protected string $appDocument = '';
|
||||
|
||||
/**
|
||||
* 可选,插件说明.
|
||||
*/
|
||||
protected string $appDescription = '';
|
||||
|
||||
/**
|
||||
* 可选,支持平台.
|
||||
*/
|
||||
protected array $appPlatforms = [];
|
||||
|
||||
/**
|
||||
* 可选,协议列表.
|
||||
*/
|
||||
protected array $appLicense = [];
|
||||
|
||||
/**
|
||||
* 可选,版本号.
|
||||
*/
|
||||
protected string $appVersion = '';
|
||||
|
||||
/**
|
||||
* 可选,主页地址.
|
||||
*/
|
||||
protected string $appHomepage = '';
|
||||
|
||||
/**
|
||||
* 可选,菜单根节点配置.
|
||||
*/
|
||||
protected array $appMenuRoot = [];
|
||||
|
||||
/**
|
||||
* 可选,菜单存在检测条件.
|
||||
*/
|
||||
protected array $appMenuExists = [];
|
||||
|
||||
/**
|
||||
* 可选,是否在插件中心直接显示。
|
||||
*/
|
||||
protected bool $appMenuShow = true;
|
||||
|
||||
/**
|
||||
* 可选,插件菜单项配置。
|
||||
*/
|
||||
protected array $appMenus = [];
|
||||
|
||||
/**
|
||||
* Composer 配置.
|
||||
*/
|
||||
protected array $composer = [];
|
||||
|
||||
/**
|
||||
* 插件配置.
|
||||
*/
|
||||
private static array $addons = [];
|
||||
|
||||
/**
|
||||
* 自动注册插件.
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
|
||||
// 获取基础服务类
|
||||
$ref = new \ReflectionClass(static::class);
|
||||
$this->composer = $this->resolveComposerManifest($ref);
|
||||
$this->hydrateComposerManifest($this->composer);
|
||||
|
||||
// 应用服务注册类
|
||||
if (empty($this->appService)) {
|
||||
$this->appService = static::class;
|
||||
}
|
||||
|
||||
// 应用命名空间名
|
||||
if (empty($this->appSpace)) {
|
||||
$this->appSpace = $ref->getNamespaceName();
|
||||
}
|
||||
|
||||
// 应用插件路径计算
|
||||
if (empty($this->appPath) || !is_dir($this->appPath)) {
|
||||
$this->appPath = dirname($ref->getFileName());
|
||||
}
|
||||
|
||||
// 应用插件包名计算
|
||||
if ($this->appAlias !== '' && $this->appCode === $this->appAlias) {
|
||||
$this->appAlias = '';
|
||||
}
|
||||
|
||||
if (is_dir($this->appPath)) {
|
||||
$prefixes = $this->normalizePrefixes();
|
||||
// 解析插件路径:phar 内 realpath 常为 false,需回退为原路径并保证末尾分隔符
|
||||
$resolved = realpath($this->appPath);
|
||||
$path = ($resolved !== false ? $resolved : rtrim(str_replace('\\', '/', $this->appPath), '/')) . DIRECTORY_SEPARATOR;
|
||||
// 写入插件参数信息
|
||||
self::$addons[$this->appCode] = [
|
||||
'name' => $this->appName,
|
||||
'type' => 'plugin',
|
||||
'path' => $path,
|
||||
'alias' => $this->appAlias,
|
||||
'prefix' => $prefixes[0] ?? '',
|
||||
'prefixes' => $prefixes,
|
||||
'space' => $this->appSpace ?: NodeService::space($this->appCode),
|
||||
'package' => $this->package,
|
||||
'service' => $this->appService,
|
||||
'document' => $this->appDocument,
|
||||
'description' => $this->appDescription,
|
||||
'platforms' => $this->normalizeArray($this->appPlatforms),
|
||||
'license' => $this->normalizeArray($this->appLicense),
|
||||
'version' => $this->appVersion,
|
||||
'homepage' => $this->appHomepage,
|
||||
'show' => $this->appMenuShow,
|
||||
];
|
||||
AppService::clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件编号。
|
||||
*/
|
||||
public static function getAppCode(): string
|
||||
{
|
||||
return static::plugin()->appCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件名称。
|
||||
*/
|
||||
public static function getAppName(): string
|
||||
{
|
||||
return static::plugin()->appName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件路径。
|
||||
*/
|
||||
public static function getAppPath(): string
|
||||
{
|
||||
return static::plugin()->appPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件命名空间。
|
||||
*/
|
||||
public static function getAppSpace(): string
|
||||
{
|
||||
return static::plugin()->appSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件安装包名。
|
||||
*/
|
||||
public static function getAppPackage(): string
|
||||
{
|
||||
return static::plugin()->package;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件主访问前缀。
|
||||
*/
|
||||
public static function getAppPrefix(): string
|
||||
{
|
||||
return static::plugin()->appPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件全部访问前缀。
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAppPrefixes(): array
|
||||
{
|
||||
return static::plugin()->appPrefixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件菜单根节点配置。
|
||||
*/
|
||||
public static function getMenuRoot(): array
|
||||
{
|
||||
return static::plugin()->appMenuRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件菜单存在检测条件。
|
||||
*/
|
||||
public static function getMenuExists(): array
|
||||
{
|
||||
return static::plugin()->appMenuExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件菜单显示配置。
|
||||
*/
|
||||
public static function getMenuShow(): bool
|
||||
{
|
||||
return static::plugin()->appMenuShow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件菜单项配置。
|
||||
*/
|
||||
public static function getMenus(): array
|
||||
{
|
||||
return static::plugin()->appMenus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件及安装信息.
|
||||
* @param ?string $code 指定插件编号
|
||||
* @param bool $append 关联安装数据
|
||||
*/
|
||||
public static function get(?string $code = null, bool $append = false): ?array
|
||||
{
|
||||
// 读取插件原始信息
|
||||
$data = empty($code) ? self::$addons : (self::$addons[$code] ?? null);
|
||||
if (empty($data) || empty($append)) {
|
||||
return $data;
|
||||
}
|
||||
// 关联插件安装信息
|
||||
$versions = AppService::getPluginLibrarys();
|
||||
return empty($code) ? array_map(static function ($item) use ($versions) {
|
||||
$item['install'] = $versions[$item['package']] ?? [];
|
||||
if (empty($item['name'])) {
|
||||
$item['name'] = $item['install']['name'] ?? '';
|
||||
}
|
||||
return $item;
|
||||
}, $data) : $data + ['install' => $versions[$data['package']] ?? []];
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册应用启动.
|
||||
*/
|
||||
public function boot(): void {}
|
||||
|
||||
/**
|
||||
* 获取当前插件服务实例。
|
||||
*/
|
||||
protected static function plugin(): static
|
||||
{
|
||||
return app(static::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Composer 配置.
|
||||
*/
|
||||
private function resolveComposerManifest(\ReflectionClass $ref): array
|
||||
{
|
||||
if (!($path = $ref->getFileName())) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for ($level = 1; $level <= 3; ++$level) {
|
||||
$file = dirname($path, $level) . DIRECTORY_SEPARATOR . 'composer.json';
|
||||
if (is_file($file)) {
|
||||
return json_decode(file_get_contents($file), true) ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步 Composer 元数据.
|
||||
*/
|
||||
private function hydrateComposerManifest(array $manifest): void
|
||||
{
|
||||
$app = (array)($manifest['extra']['xadmin']['app'] ?? []);
|
||||
$menu = (array)($manifest['extra']['xadmin']['menu'] ?? []);
|
||||
|
||||
$this->package = strval($manifest['name'] ?? '');
|
||||
$this->appCode = strval($app['code'] ?? '');
|
||||
$this->appName = strval($app['name'] ?? '');
|
||||
$this->appAlias = strval($app['alias'] ?? '');
|
||||
$this->appPrefix = strval($app['prefix'] ?? '');
|
||||
$this->appPrefixes = array_key_exists('prefixes', $app) ? (array)$app['prefixes'] : [];
|
||||
$this->appSpace = array_key_exists('space', $app) ? strval($app['space']) : '';
|
||||
$this->appType = 'plugin';
|
||||
$this->appDocument = strval($app['document'] ?? '');
|
||||
$this->appDescription = array_key_exists('description', $app) ? strval($app['description']) : strval($manifest['description'] ?? '');
|
||||
$this->appPlatforms = array_key_exists('platforms', $app) ? (array)$app['platforms'] : [];
|
||||
$this->appLicense = array_key_exists('license', $app) ? (array)$app['license'] : (array)($manifest['license'] ?? []);
|
||||
$this->appVersion = strval($manifest['version'] ?? '');
|
||||
$this->appHomepage = strval($manifest['homepage'] ?? '');
|
||||
$this->appMenuRoot = array_key_exists('root', $menu) ? (array)$menu['root'] : [];
|
||||
$this->appMenuExists = array_key_exists('exists', $menu) ? (array)$menu['exists'] : [];
|
||||
$this->appMenuShow = array_key_exists('show', $menu) ? boolval($menu['show']) : true;
|
||||
$this->appMenus = array_key_exists('items', $menu) ? (array)$menu['items'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标准化前缀列表。
|
||||
* @return string[]
|
||||
*/
|
||||
private function normalizePrefixes(): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ([$this->appPrefix, $this->appPrefixes, $this->appAlias, $this->appCode] as $value) {
|
||||
foreach ((array)$value as $prefix) {
|
||||
$prefix = trim((string)$prefix, " \t\n\r\0\x0B\\/");
|
||||
if ($prefix === '') {
|
||||
continue;
|
||||
}
|
||||
if (strpos($prefix, '/')) {
|
||||
$prefix = strstr($prefix, '/', true) ?: $prefix;
|
||||
}
|
||||
if (strpos($prefix, '.')) {
|
||||
$prefix = strstr($prefix, '.', true) ?: $prefix;
|
||||
}
|
||||
if ($prefix !== '' && !in_array($prefix, $items, true)) {
|
||||
$items[] = $prefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->appPrefix = $items[0] ?? '';
|
||||
$this->appPrefixes = $items;
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化字符串数组.
|
||||
* @return string[]
|
||||
*/
|
||||
private function normalizeArray(array $items): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($items as $item) {
|
||||
$value = trim(strval($item));
|
||||
if ($value !== '' && !in_array($value, $result, true)) {
|
||||
$result[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
60
plugin/think-library/src/Service.php
Normal file
60
plugin/think-library/src/Service.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\App;
|
||||
use think\Container;
|
||||
|
||||
/**
|
||||
* 自定义服务基类.
|
||||
* @class Service
|
||||
*/
|
||||
abstract class Service
|
||||
{
|
||||
/**
|
||||
* 应用实例.
|
||||
*/
|
||||
protected App $app;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态实例对象
|
||||
* @param array $var 实例参数
|
||||
* @param bool $new 创建新实例
|
||||
*/
|
||||
public static function instance(array $var = [], bool $new = false): static
|
||||
{
|
||||
return Container::getInstance()->make(static::class, $var, $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化服务
|
||||
*/
|
||||
protected function initialize() {}
|
||||
}
|
||||
235
plugin/think-library/src/Storage.php
Normal file
235
plugin/think-library/src/Storage.php
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | ThinkAdmin Plugin for ThinkAdminDeveloper
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Official Website: https://thinkadmin.top
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed: https://mit-license.org
|
||||
* | Disclaimer: https://thinkadmin.top/disclaimer
|
||||
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
||||
* +----------------------------------------------------------------------
|
||||
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
||||
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace think\admin;
|
||||
|
||||
use think\admin\contract\StorageManagerInterface;
|
||||
use think\admin\contract\StorageInterface;
|
||||
use think\Container;
|
||||
|
||||
/**
|
||||
* 文件存储引擎管理.
|
||||
* @class Storage
|
||||
* @method static array info($name, $safe = false, $attname = null) 文件存储信息
|
||||
* @method static array set($name, $file, $safe = false, $attname = null) 储存文件
|
||||
* @method static string url($name, $safe = false, $attname = null) 获取文件链接
|
||||
* @method static string get($name, $safe = false) 读取文件内容
|
||||
* @method static string path($name, $safe = false) 文件存储路径
|
||||
* @method static boolean del($name, $safe = false) 删除存储文件
|
||||
* @method static boolean has($name, $safe = false) 检查是否存在
|
||||
* @method static string upload() 获取上传地址
|
||||
*/
|
||||
abstract class Storage
|
||||
{
|
||||
/**
|
||||
* 静态访问启用.
|
||||
* @param string $method 方法名称
|
||||
* @param array $arguments 调用参数
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function __callStatic(string $method, array $arguments)
|
||||
{
|
||||
if (method_exists($storage = static::instance(), $method)) {
|
||||
return call_user_func_array([$storage, $method], $arguments);
|
||||
}
|
||||
throw new Exception('method not exists: ' . get_class($storage) . "->{$method}()");
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化存储操作对象
|
||||
* @param ?string $name 驱动名称
|
||||
* @param ?string $class 驱动类名
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function instance(?string $name = null, ?string $class = null): StorageInterface
|
||||
{
|
||||
try {
|
||||
if (is_null($class)) {
|
||||
$class = static::manager()->driverClass($name);
|
||||
}
|
||||
if (class_exists($class)) {
|
||||
/* @var StorageInterface */
|
||||
return Container::getInstance()->make($class);
|
||||
}
|
||||
throw new Exception("Storage driver [{$class}] does not exist.");
|
||||
} catch (Exception $exception) {
|
||||
throw $exception;
|
||||
} catch (\Exception $exception) {
|
||||
throw new Exception($exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件到本地.
|
||||
* @param string $url 文件URL地址
|
||||
* @param bool $force 是否强制下载
|
||||
* @param int $expire 文件保留时间
|
||||
*/
|
||||
public static function down(string $url, bool $force = false, int $expire = 0): array
|
||||
{
|
||||
try {
|
||||
/** @var StorageInterface $local */
|
||||
$local = static::instance('local');
|
||||
$filename = static::name($url, '', 'down/');
|
||||
if (empty($force) && $local->has($filename)) {
|
||||
if ($expire < 1 || filemtime($local->path($filename)) + $expire > time()) {
|
||||
return $local->info($filename);
|
||||
}
|
||||
}
|
||||
return $local->set($filename, static::curlGet($url));
|
||||
} catch (\Exception $exception) {
|
||||
return ['url' => $url, 'hash' => md5($url), 'key' => $url, 'file' => $url];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件相对名称.
|
||||
* @param string $url 文件访问链接
|
||||
* @param string $ext 文件后缀名称
|
||||
* @param string $pre 文件存储前缀
|
||||
* @param string $fun 名称规则方法
|
||||
*/
|
||||
public static function name(string $url, string $ext = '', string $pre = '', string $fun = 'md5'): string
|
||||
{
|
||||
[$hah, $ext] = [$fun($url), trim($ext ?: pathinfo($url, 4), '.\/')];
|
||||
$attr = [trim($pre, '.\/'), substr($hah, 0, 2), substr($hah, 2, 30)];
|
||||
return trim(join('/', $attr), '/') . '.' . strtolower($ext ?: 'tmp');
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用CURL读取网络资源.
|
||||
* @param string $url 资源地址
|
||||
*/
|
||||
public static function curlGet(string $url): string
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
$body = curl_exec($ch) ?: '';
|
||||
curl_close($ch);
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取后缀类型.
|
||||
* @param array|string $exts 文件后缀
|
||||
* @param array $mime 文件信息
|
||||
*/
|
||||
public static function mime(array|string $exts, array $mime = []): string
|
||||
{
|
||||
$mimes = static::mimes();
|
||||
foreach (str2arr($exts) as $ext) {
|
||||
$mime[] = $mimes[strtolower($ext)] ?? 'application/octet-stream';
|
||||
}
|
||||
return join(',', array_unique($mime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有类型.
|
||||
*/
|
||||
public static function mimes(): array
|
||||
{
|
||||
return static::manager()->mimes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储类型.
|
||||
*/
|
||||
public static function types(): array
|
||||
{
|
||||
return static::manager()->types();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取驱动区域列表.
|
||||
*/
|
||||
public static function regions(string $name): array
|
||||
{
|
||||
return static::manager()->regions($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取驱动配置模板名.
|
||||
*/
|
||||
public static function template(string $name): string
|
||||
{
|
||||
return static::manager()->template($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端上传授权参数.
|
||||
*/
|
||||
public static function authorize(string $name, string $key, bool $safe = false, ?string $attname = null, string $hash = ''): array
|
||||
{
|
||||
return static::manager()->authorize($name, $key, $safe, $attname, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片数据存储.
|
||||
* @param string $base64 图片内容
|
||||
* @param string $prefix 保存前缀
|
||||
* @param bool $safemode 安全模式
|
||||
* @return array [ url => URL ]
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function saveImage(string $base64, string $prefix = 'image', bool $safemode = false): array
|
||||
{
|
||||
if (preg_match('|^data:image/(.*?);base64,|i', $base64)) {
|
||||
[$ext, $img] = explode('|||', preg_replace('|^data:image/(.*?);base64,|i', '$1|||', $base64));
|
||||
$name = static::name($img, $ext, $prefix);
|
||||
if (empty($ext) || !in_array(strtolower($ext), ['gif', 'png', 'jpg', 'jpeg'])) {
|
||||
throw new Exception('内容格式异常!');
|
||||
}
|
||||
if ($safemode) {
|
||||
return static::instance('local')->set($name, base64_decode($img), true);
|
||||
}
|
||||
return static::instance()->set($name, base64_decode($img));
|
||||
}
|
||||
return ['url' => $base64];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储组件管理器.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function manager(): StorageManagerInterface
|
||||
{
|
||||
$container = Container::getInstance();
|
||||
if ($container->bound(StorageManagerInterface::class)) {
|
||||
/** @var StorageManagerInterface */
|
||||
return $container->make(StorageManagerInterface::class);
|
||||
}
|
||||
$class = '';
|
||||
if ($container->bound('app')) {
|
||||
$app = $container->make('app');
|
||||
$class = strval($app->config->get('app.storage_manager_class', ''));
|
||||
}
|
||||
if ($class !== '' && class_exists($class)) {
|
||||
/** @var StorageManagerInterface */
|
||||
return $container->make($class);
|
||||
}
|
||||
throw new Exception('System storage manager is not available.');
|
||||
}
|
||||
}
|
||||
129
plugin/think-library/src/builder/BuilderLang.php
Normal file
129
plugin/think-library/src/builder/BuilderLang.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder;
|
||||
|
||||
/**
|
||||
* Builder 文案翻译辅助类。
|
||||
* @class BuilderLang
|
||||
*/
|
||||
class BuilderLang
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const TRANSLATABLE_ATTRS = [
|
||||
'placeholder',
|
||||
'title',
|
||||
'data-title',
|
||||
'data-confirm',
|
||||
'data-tips-text',
|
||||
'required-error',
|
||||
'pattern-error',
|
||||
'lay-text',
|
||||
];
|
||||
|
||||
public static function text(string $text): string
|
||||
{
|
||||
if ($text === '' || self::looksLikeHtml($text) || !function_exists('lang')) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
try {
|
||||
return strval(lang($text));
|
||||
} catch (\Throwable) {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $vars
|
||||
*/
|
||||
public static function format(string $text, array $vars = []): string
|
||||
{
|
||||
if ($text === '') {
|
||||
return '';
|
||||
}
|
||||
if (!function_exists('lang')) {
|
||||
return self::fallbackFormat($text, $vars);
|
||||
}
|
||||
|
||||
try {
|
||||
return strval(lang($text, $vars));
|
||||
} catch (\Throwable) {
|
||||
return self::fallbackFormat($text, $vars);
|
||||
}
|
||||
}
|
||||
|
||||
public static function pipeText(string $text, string $separator = '|'): string
|
||||
{
|
||||
if ($text === '' || !str_contains($text, $separator)) {
|
||||
return self::text($text);
|
||||
}
|
||||
|
||||
return join($separator, array_map(
|
||||
static fn(string $item): string => self::text(trim($item)),
|
||||
explode($separator, $text)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function attrs(array $attrs): array
|
||||
{
|
||||
foreach ($attrs as $name => $value) {
|
||||
if (!is_string($name) || !is_string($value) || !in_array($name, self::TRANSLATABLE_ATTRS, true)) {
|
||||
continue;
|
||||
}
|
||||
$attrs[$name] = $name === 'lay-text' ? self::pipeText($value) : self::text($value);
|
||||
}
|
||||
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function options(array $options): array
|
||||
{
|
||||
$translated = [];
|
||||
foreach ($options as $value => $label) {
|
||||
if (is_string($label)) {
|
||||
$translated[$value] = self::text($label);
|
||||
continue;
|
||||
}
|
||||
if (is_array($label)) {
|
||||
$translated[$value] = self::options($label);
|
||||
continue;
|
||||
}
|
||||
$translated[$value] = $label;
|
||||
}
|
||||
|
||||
return $translated;
|
||||
}
|
||||
|
||||
private static function looksLikeHtml(string $text): bool
|
||||
{
|
||||
return str_contains($text, '<') && str_contains($text, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $vars
|
||||
*/
|
||||
private static function fallbackFormat(string $text, array $vars): string
|
||||
{
|
||||
if (count($vars) < 1) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
try {
|
||||
return vsprintf($text, $vars);
|
||||
} catch (\Throwable) {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
}
|
||||
174
plugin/think-library/src/builder/base/BuilderAttributeBag.php
Normal file
174
plugin/think-library/src/builder/base/BuilderAttributeBag.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base;
|
||||
|
||||
use think\admin\builder\base\render\BuilderAttributes;
|
||||
|
||||
/**
|
||||
* Builder 属性对象.
|
||||
* @class BuilderAttributeBag
|
||||
*/
|
||||
class BuilderAttributeBag
|
||||
{
|
||||
/**
|
||||
* @var null|callable(array<string, mixed>): array<string, mixed>
|
||||
*/
|
||||
private $syncHandler = null;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function __construct(
|
||||
private mixed $owner = null,
|
||||
private array $attrs = [],
|
||||
private bool $detachedClass = false,
|
||||
private string $className = ''
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(array<string, mixed>): array<string, mixed> $syncHandler
|
||||
*/
|
||||
public function attach(callable $syncHandler): self
|
||||
{
|
||||
$this->syncHandler = $syncHandler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function attr(string $name, mixed $value = null): self
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name === '') {
|
||||
return $this;
|
||||
}
|
||||
if ($name === 'class') {
|
||||
return $this->assignClass($value);
|
||||
}
|
||||
$this->attrs[$name] = $value;
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function attrs(array $attrs): self
|
||||
{
|
||||
foreach ($attrs as $name => $value) {
|
||||
if (is_string($name)) {
|
||||
$this->attr($name, $value);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function class(string|array $class): self
|
||||
{
|
||||
if ($this->detachedClass) {
|
||||
$this->className = BuilderAttributes::mergeClassNames($this->className, $class);
|
||||
} else {
|
||||
$this->attrs = BuilderAttributes::make($this->attrs)->class($class)->all();
|
||||
}
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
public function removeClass(string|array $class): self
|
||||
{
|
||||
if ($this->detachedClass) {
|
||||
$attrs = BuilderAttributes::make(['class' => $this->className])->removeClass($class)->all();
|
||||
$this->className = trim(strval($attrs['class'] ?? ''));
|
||||
} else {
|
||||
$this->attrs = BuilderAttributes::make($this->attrs)->removeClass($class)->all();
|
||||
}
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
public function toggleClass(string|array $class, ?bool $force = null): self
|
||||
{
|
||||
if ($this->detachedClass) {
|
||||
$attrs = BuilderAttributes::make(['class' => $this->className])->toggleClass($class, $force)->all();
|
||||
$this->className = trim(strval($attrs['class'] ?? ''));
|
||||
} else {
|
||||
$this->attrs = BuilderAttributes::make($this->attrs)->toggleClass($class, $force)->all();
|
||||
}
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
public function data(string $name, mixed $value = null): self
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$this->attr('data-' . ltrim($name, '-'), $value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function id(string $id): self
|
||||
{
|
||||
return $this->attr('id', $id);
|
||||
}
|
||||
|
||||
public function remove(string $name): self
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name === '') {
|
||||
return $this;
|
||||
}
|
||||
if ($name === 'class') {
|
||||
if ($this->detachedClass) {
|
||||
$this->className = '';
|
||||
} else {
|
||||
unset($this->attrs['class']);
|
||||
}
|
||||
return $this->sync();
|
||||
}
|
||||
if (array_key_exists($name, $this->attrs)) {
|
||||
unset($this->attrs[$name]);
|
||||
$this->sync();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function export(): array
|
||||
{
|
||||
$state = ['attrs' => $this->attrs];
|
||||
if ($this->detachedClass) {
|
||||
$state['class'] = $this->className;
|
||||
}
|
||||
return $state;
|
||||
}
|
||||
|
||||
public function end(): mixed
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
private function assignClass(mixed $value): self
|
||||
{
|
||||
$class = is_array($value) ? BuilderAttributes::mergeClassNames('', $value) : trim(strval($value));
|
||||
if ($this->detachedClass) {
|
||||
$this->className = $class;
|
||||
} elseif ($class === '') {
|
||||
unset($this->attrs['class']);
|
||||
} else {
|
||||
$this->attrs['class'] = $class;
|
||||
}
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
private function sync(): self
|
||||
{
|
||||
if (is_callable($this->syncHandler)) {
|
||||
$state = ($this->syncHandler)($this->export());
|
||||
$this->attrs = is_array($state['attrs'] ?? null) ? $state['attrs'] : $this->attrs;
|
||||
if ($this->detachedClass) {
|
||||
$this->className = trim(strval($state['class'] ?? $this->className));
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
118
plugin/think-library/src/builder/base/BuilderModule.php
Normal file
118
plugin/think-library/src/builder/base/BuilderModule.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base;
|
||||
|
||||
/**
|
||||
* Builder 模块对象.
|
||||
* @class BuilderModule
|
||||
*/
|
||||
class BuilderModule
|
||||
{
|
||||
private ?int $index = null;
|
||||
|
||||
/**
|
||||
* @var null|callable(int, array<string, mixed>): array<string, mixed>
|
||||
*/
|
||||
private $syncHandler = null;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private array $config = [],
|
||||
private mixed $owner = null
|
||||
) {
|
||||
$this->name = trim($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $module
|
||||
* @param callable(int, array<string, mixed>): array<string, mixed> $syncHandler
|
||||
*/
|
||||
public function attach(int $index, array $module, callable $syncHandler): self
|
||||
{
|
||||
$this->index = $index;
|
||||
$this->syncHandler = $syncHandler;
|
||||
$this->name = trim(strval($module['name'] ?? $this->name));
|
||||
$this->config = is_array($module['config'] ?? null) ? $module['config'] : $this->config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function name(string $name): self
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$this->name = $name;
|
||||
$this->sync();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
public function config(array $config): self
|
||||
{
|
||||
$this->config = $config;
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
public function option(string $name, mixed $value): self
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$this->config[$name] = $value;
|
||||
$this->sync();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
public function options(array $config): self
|
||||
{
|
||||
foreach ($config as $name => $value) {
|
||||
if (is_string($name) && trim($name) !== '') {
|
||||
$this->config[trim($name)] = $value;
|
||||
}
|
||||
}
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
public function remove(string $name): self
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '' && array_key_exists($name, $this->config)) {
|
||||
unset($this->config[$name]);
|
||||
$this->sync();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function export(): array
|
||||
{
|
||||
return ['name' => $this->name, 'config' => $this->config];
|
||||
}
|
||||
|
||||
public function end(): mixed
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
private function sync(): self
|
||||
{
|
||||
if ($this->index !== null && is_callable($this->syncHandler)) {
|
||||
$module = ($this->syncHandler)($this->index, $this->export());
|
||||
$this->name = trim(strval($module['name'] ?? $this->name));
|
||||
$this->config = is_array($module['config'] ?? null) ? $module['config'] : $this->config;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
429
plugin/think-library/src/builder/base/BuilderNode.php
Normal file
429
plugin/think-library/src/builder/base/BuilderNode.php
Normal file
@ -0,0 +1,429 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base;
|
||||
|
||||
use think\admin\builder\base\render\BuilderAttributes;
|
||||
|
||||
/**
|
||||
* Builder 通用节点基类.
|
||||
* @class BuilderNode
|
||||
*/
|
||||
abstract class BuilderNode
|
||||
{
|
||||
/**
|
||||
* 节点属性.
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $attrs = [];
|
||||
|
||||
/**
|
||||
* 模块配置.
|
||||
* @var array<int, array<string, mixed>>
|
||||
*/
|
||||
protected array $modules = [];
|
||||
|
||||
/**
|
||||
* 子节点.
|
||||
* @var array<int, object>
|
||||
*/
|
||||
protected array $children = [];
|
||||
|
||||
/**
|
||||
* 原始 HTML.
|
||||
*/
|
||||
protected string $html = '';
|
||||
|
||||
/**
|
||||
* 父级节点.
|
||||
*/
|
||||
protected ?self $parentNode = null;
|
||||
|
||||
public function __construct(
|
||||
protected object $builder,
|
||||
protected string $type = 'element',
|
||||
protected string $tag = 'div'
|
||||
) {
|
||||
}
|
||||
|
||||
public function attr(string $name, mixed $value = null): static
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$this->attrs[$name] = $value;
|
||||
$this->afterMutate();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAttr(string $name): static
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '' && array_key_exists($name, $this->attrs)) {
|
||||
unset($this->attrs[$name]);
|
||||
$this->afterMutate();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function attrs(array $attrs): static
|
||||
{
|
||||
foreach ($attrs as $name => $value) {
|
||||
if ($name === 'class') {
|
||||
$this->class($value);
|
||||
} else {
|
||||
$this->attr(strval($name), $value);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function class(string|array $class): static
|
||||
{
|
||||
$this->attrs = BuilderAttributes::make($this->attrs)->class($class)->all();
|
||||
$this->afterMutate();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeClass(string|array $class): static
|
||||
{
|
||||
$this->attrs = BuilderAttributes::make($this->attrs)->removeClass($class)->all();
|
||||
$this->afterMutate();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toggleClass(string|array $class, ?bool $force = null): static
|
||||
{
|
||||
$this->attrs = BuilderAttributes::make($this->attrs)->toggleClass($class, $force)->all();
|
||||
$this->afterMutate();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function data(string $name, mixed $value = null): static
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$this->attr('data-' . ltrim($name, '-'), $value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeData(string $name): static
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$this->removeAttr('data-' . ltrim($name, '-'));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function id(string $id): static
|
||||
{
|
||||
return $this->attr('id', $id);
|
||||
}
|
||||
|
||||
public function attrsItem(): BuilderAttributeBag
|
||||
{
|
||||
return $this->attachAttributes($this->createAttributes());
|
||||
}
|
||||
|
||||
public function module(string $name, array $config = []): static
|
||||
{
|
||||
$this->attachModule($this->createModule($name, $config));
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function moduleItem(string $name, array $config = []): BuilderModule
|
||||
{
|
||||
return $this->attachModule($this->createModule($name, $config));
|
||||
}
|
||||
|
||||
public function clear(): static
|
||||
{
|
||||
foreach ($this->children as $child) {
|
||||
if ($child instanceof self) {
|
||||
$child->parentNode = null;
|
||||
$child->onDetached();
|
||||
}
|
||||
$this->onChildDetached($child);
|
||||
}
|
||||
$this->children = [];
|
||||
$this->afterMutate();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function parentNode(): ?self
|
||||
{
|
||||
return $this->parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function children(): array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function firstChild(): ?object
|
||||
{
|
||||
return $this->children[0] ?? null;
|
||||
}
|
||||
|
||||
public function lastChild(): ?object
|
||||
{
|
||||
return $this->children[count($this->children) - 1] ?? null;
|
||||
}
|
||||
|
||||
public function appendNode(object $node): object
|
||||
{
|
||||
return $this->appendChild($node);
|
||||
}
|
||||
|
||||
public function prependNode(object $node): object
|
||||
{
|
||||
return $this->prependChild($node);
|
||||
}
|
||||
|
||||
public function beforeNode(object $node): object
|
||||
{
|
||||
return $this->insertSibling($node, false);
|
||||
}
|
||||
|
||||
public function afterNode(object $node): object
|
||||
{
|
||||
return $this->insertSibling($node, true);
|
||||
}
|
||||
|
||||
public function remove(): static
|
||||
{
|
||||
return $this->detachFromParent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加子节点.
|
||||
*/
|
||||
protected function appendChild(object $node): object
|
||||
{
|
||||
if (!$this->canAttachNode($node)) {
|
||||
return $node;
|
||||
}
|
||||
if ($node instanceof self) {
|
||||
$node->detachFromParent(true);
|
||||
$node->parentNode = $this;
|
||||
}
|
||||
$this->children[] = $node;
|
||||
$this->afterMutate();
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前置插入子节点.
|
||||
*/
|
||||
protected function prependChild(object $node): object
|
||||
{
|
||||
if (!$this->canAttachNode($node)) {
|
||||
return $node;
|
||||
}
|
||||
if ($node instanceof self) {
|
||||
$node->detachFromParent(true);
|
||||
$node->parentNode = $this;
|
||||
}
|
||||
array_unshift($this->children, $node);
|
||||
$this->afterMutate();
|
||||
return $node;
|
||||
}
|
||||
|
||||
protected function createAttributes(): BuilderAttributeBag
|
||||
{
|
||||
return new BuilderAttributeBag($this, $this->attrs);
|
||||
}
|
||||
|
||||
protected function attachAttributes(BuilderAttributeBag $attributes): BuilderAttributeBag
|
||||
{
|
||||
return $attributes->attach(fn(array $state): array => $this->replaceAttributes($state));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $state
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function replaceAttributes(array $state): array
|
||||
{
|
||||
$this->attrs = is_array($state['attrs'] ?? null) ? BuilderAttributes::make($state['attrs'])->all() : [];
|
||||
$this->afterMutate();
|
||||
return ['attrs' => $this->attrs];
|
||||
}
|
||||
|
||||
protected function createModule(string $name, array $config = []): BuilderModule
|
||||
{
|
||||
return new BuilderModule($name, $config, $this);
|
||||
}
|
||||
|
||||
protected function attachModule(BuilderModule $module): BuilderModule
|
||||
{
|
||||
$normalized = $this->normalizeModule($module->export());
|
||||
if ($normalized['name'] === '') {
|
||||
return $module;
|
||||
}
|
||||
$index = count($this->modules);
|
||||
$this->modules[$index] = $normalized;
|
||||
$this->afterMutate();
|
||||
return $module->attach($index, $normalized, fn(int $index, array $module): array => $this->replaceModule($index, $module));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $module
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function replaceModule(int $index, array $module): array
|
||||
{
|
||||
$normalized = $this->normalizeModule($module);
|
||||
if ($normalized['name'] !== '') {
|
||||
$this->modules[$index] = $normalized;
|
||||
$this->afterMutate();
|
||||
}
|
||||
return $this->modules[$index] ?? $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 HTML 节点数组.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function exportHtmlNode(): array
|
||||
{
|
||||
return ['type' => 'html', 'html' => $this->html];
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出普通节点数组.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function exportElementNode(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'tag' => $this->tag,
|
||||
'attrs' => $this->buildAttrs(),
|
||||
'modules' => $this->modules,
|
||||
'children' => $this->exportChildren(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出子节点数组.
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function exportChildren(): array
|
||||
{
|
||||
return array_map(static fn(object $node) => $node->export(), $this->children);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点属性.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function buildAttrs(): array
|
||||
{
|
||||
return BuilderAttributes::make($this->attrs)->modules($this->modules)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $module
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function normalizeModule(array $module): array
|
||||
{
|
||||
return [
|
||||
'name' => trim(strval($module['name'] ?? '')),
|
||||
'config' => is_array($module['config'] ?? null) ? $module['config'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
protected function removeChild(object $node, bool $moving = false): bool
|
||||
{
|
||||
foreach ($this->children as $index => $child) {
|
||||
if ($child === $node) {
|
||||
if ($child instanceof self) {
|
||||
$child->parentNode = null;
|
||||
if (!$moving) {
|
||||
$child->onDetached();
|
||||
}
|
||||
}
|
||||
array_splice($this->children, $index, 1);
|
||||
$this->onChildDetached($child);
|
||||
$this->afterMutate();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function insertSibling(object $node, bool $after = true): object
|
||||
{
|
||||
if (!$this->parentNode instanceof self) {
|
||||
return $node;
|
||||
}
|
||||
return $this->parentNode->insertChildAround($this, $node, $after);
|
||||
}
|
||||
|
||||
protected function insertChildAround(object $pivot, object $node, bool $after = true): object
|
||||
{
|
||||
if (!$this->canAttachNode($node)) {
|
||||
return $node;
|
||||
}
|
||||
foreach ($this->children as $index => $child) {
|
||||
if ($child === $pivot) {
|
||||
if ($node instanceof self) {
|
||||
$node->detachFromParent(true);
|
||||
$node->parentNode = $this;
|
||||
}
|
||||
array_splice($this->children, $after ? $index + 1 : $index, 0, [$node]);
|
||||
$this->afterMutate();
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
return $after ? $this->appendChild($node) : $this->prependChild($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点变更后同步.
|
||||
*/
|
||||
protected function afterMutate(): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function onDetached(): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function onChildDetached(object $child): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function detachFromParent(bool $moving = false): static
|
||||
{
|
||||
if ($this->parentNode instanceof self) {
|
||||
$this->parentNode->removeChild($this, $moving);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function canAttachNode(object $node): bool
|
||||
{
|
||||
return !$node instanceof self || ($node !== $this && !$this->isDescendantOf($node));
|
||||
}
|
||||
|
||||
private function isDescendantOf(self $node): bool
|
||||
{
|
||||
$parent = $this->parentNode;
|
||||
while ($parent instanceof self) {
|
||||
if ($parent === $node) {
|
||||
return true;
|
||||
}
|
||||
$parent = $parent->parentNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
106
plugin/think-library/src/builder/base/BuilderOptionSource.php
Normal file
106
plugin/think-library/src/builder/base/BuilderOptionSource.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base;
|
||||
|
||||
/**
|
||||
* Builder 选项源对象.
|
||||
* @class BuilderOptionSource
|
||||
*/
|
||||
class BuilderOptionSource
|
||||
{
|
||||
/**
|
||||
* @var null|callable(array<string, mixed>): array<string, mixed>
|
||||
*/
|
||||
private $syncHandler = null;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
private string $sourceKey = 'source',
|
||||
private array $options = [],
|
||||
private string $source = '',
|
||||
private mixed $owner = null
|
||||
) {
|
||||
$this->sourceKey = trim($this->sourceKey) ?: 'source';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(array<string, mixed>): array<string, mixed> $syncHandler
|
||||
*/
|
||||
public function attach(callable $syncHandler): static
|
||||
{
|
||||
$this->syncHandler = $syncHandler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function source(string $source): static
|
||||
{
|
||||
$this->source = trim($source);
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
public function variable(string $source): static
|
||||
{
|
||||
return $this->source($source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function options(array $options): static
|
||||
{
|
||||
$this->options = $options;
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
public function option(string|int $value, mixed $label): static
|
||||
{
|
||||
$this->options[(string)$value] = $label;
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
public function removeOption(string|int $value): static
|
||||
{
|
||||
$key = (string)$value;
|
||||
if (array_key_exists($key, $this->options)) {
|
||||
unset($this->options[$key]);
|
||||
$this->sync();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clearOptions(): static
|
||||
{
|
||||
$this->options = [];
|
||||
return $this->sync();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function export(): array
|
||||
{
|
||||
return [
|
||||
'options' => $this->options,
|
||||
$this->sourceKey => $this->source,
|
||||
];
|
||||
}
|
||||
|
||||
public function end(): mixed
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
private function sync(): static
|
||||
{
|
||||
if (is_callable($this->syncHandler)) {
|
||||
$state = ($this->syncHandler)($this->export());
|
||||
$this->options = is_array($state['options'] ?? null) ? $state['options'] : $this->options;
|
||||
$this->source = trim(strval($state[$this->sourceKey] ?? $this->source));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 动作节点渲染器.
|
||||
* @class BuilderActionRenderer
|
||||
*/
|
||||
class BuilderActionRenderer
|
||||
{
|
||||
private BuilderAttributesRenderer $attributesRenderer;
|
||||
|
||||
public function __construct(?BuilderAttributesRenderer $attributesRenderer = null)
|
||||
{
|
||||
$this->attributesRenderer = $attributesRenderer ?? new BuilderAttributesRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function render(string $label, array $attrs = [], string $tag = 'a'): string
|
||||
{
|
||||
$tag = trim($tag) ?: 'a';
|
||||
$attrsHtml = $this->attributesRenderer->render($attrs);
|
||||
return sprintf('<%s%s>%s</%s>', $tag, $attrsHtml === '' ? '' : ' ' . $attrsHtml, $label, $tag);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 属性对象.
|
||||
* @class BuilderAttributes
|
||||
*/
|
||||
class BuilderAttributes
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function __construct(private array $attrs = [])
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public static function make(array $attrs = []): self
|
||||
{
|
||||
return new self($attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function merge(array $attrs): self
|
||||
{
|
||||
foreach ($attrs as $name => $value) {
|
||||
if ($name === 'class') {
|
||||
$this->class(is_array($value) ? $value : strval($value));
|
||||
} else {
|
||||
$this->attrs[$name] = $value;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function class(string|array $class): self
|
||||
{
|
||||
$merged = self::mergeClassNames(strval($this->attrs['class'] ?? ''), $class);
|
||||
if ($merged === '') {
|
||||
unset($this->attrs['class']);
|
||||
} else {
|
||||
$this->attrs['class'] = $merged;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeClass(string|array $class): self
|
||||
{
|
||||
$current = self::normalizeClasses(strval($this->attrs['class'] ?? ''));
|
||||
$remove = self::normalizeClasses($class);
|
||||
if ($current === [] || $remove === []) {
|
||||
return $this;
|
||||
}
|
||||
$merged = array_values(array_diff($current, $remove));
|
||||
if ($merged === []) {
|
||||
unset($this->attrs['class']);
|
||||
} else {
|
||||
$this->attrs['class'] = join(' ', $merged);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toggleClass(string|array $class, ?bool $force = null): self
|
||||
{
|
||||
$classes = self::normalizeClasses($class);
|
||||
if ($classes === []) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$current = self::normalizeClasses(strval($this->attrs['class'] ?? ''));
|
||||
$active = count(array_intersect($current, $classes)) === count($classes);
|
||||
$enabled = $force ?? !$active;
|
||||
|
||||
return $enabled ? $this->class($classes) : $this->removeClass($classes);
|
||||
}
|
||||
|
||||
public function default(string $name, mixed $value): self
|
||||
{
|
||||
if (!array_key_exists($name, $this->attrs)) {
|
||||
$this->attrs[$name] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $modules
|
||||
*/
|
||||
public function modules(array $modules): self
|
||||
{
|
||||
if (count($modules) > 0) {
|
||||
$this->attrs['data-builder-modules'] = json_encode($modules, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$attrs = $this->attrs;
|
||||
if (isset($attrs['class']) && trim(strval($attrs['class'])) === '') {
|
||||
unset($attrs['class']);
|
||||
}
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
public function html(): string
|
||||
{
|
||||
$html = '';
|
||||
foreach ($this->all() as $key => $value) {
|
||||
$name = self::escape((string)$key);
|
||||
$html .= is_null($value)
|
||||
? sprintf(' %s', $name)
|
||||
: sprintf(' %s="%s"', $name, self::escape((string)$value));
|
||||
}
|
||||
return ltrim($html);
|
||||
}
|
||||
|
||||
public static function escape(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
public static function mergeClassNames(string|array $origin, string|array $append): string
|
||||
{
|
||||
$classes = [];
|
||||
foreach ([self::normalizeClasses($origin), self::normalizeClasses($append)] as $items) {
|
||||
foreach ($items as $class) {
|
||||
if ($class !== '' && !in_array($class, $classes, true)) {
|
||||
$classes[] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
return join(' ', $classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function normalizeClasses(string|array $classes): array
|
||||
{
|
||||
if (is_array($classes)) {
|
||||
$items = array_map('strval', $classes);
|
||||
} else {
|
||||
$items = preg_split('/\s+/', trim($classes)) ?: [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($items as $class) {
|
||||
$class = trim($class);
|
||||
if ($class !== '') {
|
||||
$result[] = $class;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 属性渲染上下文.
|
||||
* @class BuilderAttributesRenderContext
|
||||
*/
|
||||
abstract class BuilderAttributesRenderContext
|
||||
{
|
||||
/**
|
||||
* @param callable(array<string, mixed>): string $attrsRenderer
|
||||
*/
|
||||
public function __construct(
|
||||
private $attrsRenderer,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function attrs(array $attrs): string
|
||||
{
|
||||
return ($this->attrsRenderer)($attrs);
|
||||
}
|
||||
|
||||
public function escape(string $value): string
|
||||
{
|
||||
return BuilderAttributes::escape($value);
|
||||
}
|
||||
|
||||
public function mergeClass(string|array $origin, string|array $append): string
|
||||
{
|
||||
return BuilderAttributes::mergeClassNames($origin, $append);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 属性 HTML 渲染器.
|
||||
* @class BuilderAttributesRenderer
|
||||
*/
|
||||
class BuilderAttributesRenderer
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function render(array $attrs): string
|
||||
{
|
||||
return BuilderAttributes::make($attrs)->html();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 回调代理节点渲染基类.
|
||||
* @class BuilderCallbackNodeRenderer
|
||||
*/
|
||||
abstract class BuilderCallbackNodeRenderer
|
||||
{
|
||||
protected function invoke(callable $callback, mixed ...$args): string
|
||||
{
|
||||
return strval($callback(...$args));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 普通元素节点渲染基类.
|
||||
* @class BuilderElementNodeRenderer
|
||||
*/
|
||||
abstract class BuilderElementNodeRenderer
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $node
|
||||
*/
|
||||
protected function renderElement(array $node, BuilderNodeRenderContext $context): string
|
||||
{
|
||||
$tag = trim(strval($node['tag'] ?? 'div')) ?: 'div';
|
||||
$attrs = is_array($node['attrs'] ?? null) ? $node['attrs'] : [];
|
||||
$children = is_array($node['children'] ?? null) ? $node['children'] : [];
|
||||
return $this->wrapElement($tag, $attrs, $context->renderChildren($children), $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
protected function wrapElement(string $tag, array $attrs, string $content, BuilderNodeRenderContext $context): string
|
||||
{
|
||||
$attrsHtml = count($attrs) > 0 ? ' ' . ltrim($context->attrs($attrs)) : '';
|
||||
return sprintf('<%s%s>%s</%s>', $tag, $attrsHtml, $content, $tag);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
use think\admin\builder\BuilderLang;
|
||||
|
||||
/**
|
||||
* Builder 原始 HTML 节点渲染基类.
|
||||
* @class BuilderHtmlNodeRenderer
|
||||
*/
|
||||
abstract class BuilderHtmlNodeRenderer
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $node
|
||||
*/
|
||||
protected function renderHtml(array $node): string
|
||||
{
|
||||
return BuilderLang::text(strval($node['html'] ?? ''));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 节点渲染通用上下文.
|
||||
* @class BuilderNodeRenderContext
|
||||
*/
|
||||
abstract class BuilderNodeRenderContext
|
||||
extends BuilderAttributesRenderContext
|
||||
{
|
||||
/**
|
||||
* @param callable(array<int, array<string, mixed>>): string $contentRenderer
|
||||
* @param callable(array<string, mixed>): string $attrsRenderer
|
||||
*/
|
||||
public function __construct(
|
||||
private $contentRenderer,
|
||||
callable $attrsRenderer,
|
||||
) {
|
||||
parent::__construct($attrsRenderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $nodes
|
||||
*/
|
||||
public function renderChildren(array $nodes): string
|
||||
{
|
||||
return ($this->contentRenderer)($nodes);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 节点渲染器工厂基类.
|
||||
* @class BuilderNodeRendererFactory
|
||||
*/
|
||||
abstract class BuilderNodeRendererFactory
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $node
|
||||
*/
|
||||
public function create(array $node): object
|
||||
{
|
||||
$class = $this->resolveRendererClass($node);
|
||||
return new $class();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $node
|
||||
*/
|
||||
protected function resolveRendererClass(array $node): string
|
||||
{
|
||||
$type = strval($node['type'] ?? '');
|
||||
return $this->rendererMap()[$type] ?? $this->fallbackRendererClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, class-string>
|
||||
*/
|
||||
abstract protected function rendererMap(): array;
|
||||
|
||||
/**
|
||||
* @return class-string
|
||||
*/
|
||||
abstract protected function fallbackRendererClass(): string;
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 渲染流水线基类.
|
||||
* @class BuilderRenderPipeline
|
||||
*/
|
||||
class BuilderRenderPipeline
|
||||
{
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $nodes
|
||||
*/
|
||||
protected function renderNodeContent(array $nodes, BuilderRenderState $state, string $separator = "\n"): string
|
||||
{
|
||||
$html = [];
|
||||
$context = $state->nodeRenderContext();
|
||||
$factory = $state->nodeRendererFactory();
|
||||
foreach ($nodes as $node) {
|
||||
if (!is_array($node)) {
|
||||
continue;
|
||||
}
|
||||
$renderer = $factory->create($node);
|
||||
if (!method_exists($renderer, 'render')) {
|
||||
continue;
|
||||
}
|
||||
$item = $renderer->render($node, $context);
|
||||
if ($item !== '') {
|
||||
$html[] = $item;
|
||||
}
|
||||
}
|
||||
return join($separator, $html);
|
||||
}
|
||||
|
||||
protected function renderSchemaScript(BuilderRenderState $state, string $className): string
|
||||
{
|
||||
return (new JsonScriptRenderer())->render($state->schema(), $className);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* Builder 渲染状态基类.
|
||||
* @class BuilderRenderState
|
||||
*/
|
||||
class BuilderRenderState
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $schema
|
||||
*/
|
||||
public function __construct(
|
||||
private array $schema,
|
||||
private BuilderNodeRendererFactory $nodeRendererFactory,
|
||||
private BuilderNodeRenderContext $nodeRenderContext,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function schema(): array
|
||||
{
|
||||
return $this->schema;
|
||||
}
|
||||
|
||||
public function nodeRendererFactory(): BuilderNodeRendererFactory
|
||||
{
|
||||
return $this->nodeRendererFactory;
|
||||
}
|
||||
|
||||
public function nodeRenderContext(): BuilderNodeRenderContext
|
||||
{
|
||||
return $this->nodeRenderContext;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* 通用脚本标签渲染器.
|
||||
* @class InlineScriptRenderer
|
||||
*/
|
||||
class InlineScriptRenderer
|
||||
{
|
||||
/**
|
||||
* @param array<int, string> $scripts
|
||||
*/
|
||||
public function render(array $scripts): string
|
||||
{
|
||||
if (count($scripts) < 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$html = '';
|
||||
foreach ($scripts as $script) {
|
||||
$html .= "\n<script>\n{$script}\n</script>";
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\base\render;
|
||||
|
||||
/**
|
||||
* 通用 JSON Script 渲染器.
|
||||
* @class JsonScriptRenderer
|
||||
*/
|
||||
class JsonScriptRenderer
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
public function render(array $payload, string $className): string
|
||||
{
|
||||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
|
||||
return $json ? sprintf('<script type="application/json" class="%s">%s</script>', BuilderAttributes::escape($className), $json) : '';
|
||||
}
|
||||
}
|
||||
18
plugin/think-library/src/builder/form/FormActionBar.php
Normal file
18
plugin/think-library/src/builder/form/FormActionBar.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\form;
|
||||
|
||||
/**
|
||||
* 表单动作条节点.
|
||||
* @class FormActionBar
|
||||
*/
|
||||
class FormActionBar extends FormNode
|
||||
{
|
||||
public function __construct(FormBuilder $builder)
|
||||
{
|
||||
parent::__construct($builder, 'actions', 'div');
|
||||
$this->class('layui-form-item text-center');
|
||||
}
|
||||
}
|
||||
40
plugin/think-library/src/builder/form/FormActions.php
Normal file
40
plugin/think-library/src/builder/form/FormActions.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\form;
|
||||
|
||||
/**
|
||||
* 表单动作定义器.
|
||||
* @class FormActions
|
||||
*/
|
||||
class FormActions
|
||||
{
|
||||
public function __construct(private FormBuilder $builder, private FormActionBar $parent)
|
||||
{
|
||||
}
|
||||
|
||||
public function submit(string $name = '保存数据', string $confirm = '', array $attrs = [], string $class = ''): self
|
||||
{
|
||||
$this->builder->addSubmitButtonToNode($this->parent, $name, $confirm, $attrs, $class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cancel(string $name = '取消编辑', string $confirm = '确定要取消编辑吗?', array $attrs = [], string $class = 'layui-btn-danger'): self
|
||||
{
|
||||
$this->builder->addCancelButtonToNode($this->parent, $name, $confirm, $attrs, $class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function button(string $name, string $type = 'button', string $confirm = '', array $attrs = [], string $class = ''): self
|
||||
{
|
||||
$this->builder->addActionButtonToNode($this->parent, $name, $type, $confirm, $attrs, $class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function html(string $html, array $schema = []): self
|
||||
{
|
||||
$this->builder->addButtonHtmlToNode($this->parent, $html, $schema);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
114
plugin/think-library/src/builder/form/FormBlocks.php
Normal file
114
plugin/think-library/src/builder/form/FormBlocks.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\form;
|
||||
|
||||
use think\admin\builder\BuilderLang;
|
||||
|
||||
/**
|
||||
* 表单结构通用块。
|
||||
* @class FormBlocks
|
||||
*/
|
||||
class FormBlocks
|
||||
{
|
||||
public static function fieldset(FormNode $parent, string $title, callable $callback, array $attrs = []): FormNode
|
||||
{
|
||||
$node = $parent->fieldset();
|
||||
$node->class('layui-bg-gray')->attrs($attrs);
|
||||
$node->node('legend')->html(sprintf('<b class="layui-badge think-bg-violet">%s</b>', self::escape($title)));
|
||||
$callback($node);
|
||||
return $node;
|
||||
}
|
||||
|
||||
public static function row(FormNode $parent, callable $callback, string $class = 'layui-row layui-col-space15'): FormNode
|
||||
{
|
||||
$node = $parent->div()->class($class);
|
||||
$callback($node);
|
||||
return $node;
|
||||
}
|
||||
|
||||
public static function col(FormNode $parent, string $class, callable $callback): FormNode
|
||||
{
|
||||
$node = $parent->div()->class($class);
|
||||
$callback($node);
|
||||
return $node;
|
||||
}
|
||||
|
||||
public static function selectFilter(FormNode $parent, string $name, string $filter, array $groups, string $title, string $subtitle, string $remark): FormNode
|
||||
{
|
||||
$node = $parent->div()->class('layui-form-item');
|
||||
$node->div()->class('help-label')->html(sprintf('<b>%s</b>%s', self::escape($title), self::escape($subtitle)));
|
||||
|
||||
$bar = $node->div()->class('mb10');
|
||||
$options = [sprintf('<option value="">%s</option>', self::escape('全部插件'))];
|
||||
foreach ($groups as $group) {
|
||||
$code = self::escape(strval($group['code'] ?? ''));
|
||||
$nameText = self::escape(strval($group['name'] ?? $code));
|
||||
$options[] = sprintf('<option value="%s">%s</option>', $code, $nameText);
|
||||
}
|
||||
$bar->div()->class('layui-input-inline')->html(sprintf(
|
||||
'<select name="%s" lay-filter="%s">%s</select>',
|
||||
self::escape($name),
|
||||
self::escape($filter),
|
||||
implode('', $options)
|
||||
));
|
||||
$bar->div()->class('layui-form-mid color-desc')->html(self::escape($remark));
|
||||
return $node;
|
||||
}
|
||||
|
||||
public static function groupedTemplateChoices(
|
||||
FormNode $parent,
|
||||
array $groups,
|
||||
string $type,
|
||||
string $name,
|
||||
string $groupClass,
|
||||
string $dataAttribute,
|
||||
string $selectedField
|
||||
): FormNode {
|
||||
$node = $parent->div()->class('layui-textarea help-checks');
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$groupNode = $node->div()->class($groupClass)->data($dataAttribute, strval($group['code'] ?? ''));
|
||||
$groupNode->div()->class('pl5 mb5')->html(sprintf(
|
||||
'<span class="layui-badge layui-bg-blue">%s</span>',
|
||||
self::escape(strval($group['name'] ?? ''))
|
||||
));
|
||||
|
||||
foreach ((array)($group['items'] ?? []) as $item) {
|
||||
$value = strval($item['code'] ?? $item['id'] ?? '');
|
||||
$label = strval($item['name'] ?? $item['title'] ?? $value);
|
||||
$pluginText = trim(strval($item['plugin_text'] ?? ''));
|
||||
$extra = '';
|
||||
if ($pluginText !== '' && intval($item['plugin_count'] ?? 0) > 1) {
|
||||
$extra = sprintf('<span class="color-desc">(%s)</span>', self::escape($pluginText));
|
||||
}
|
||||
|
||||
if ($type === 'radio') {
|
||||
$checked = sprintf('{if isset($vo.%s) and $vo.%s eq \'%s\'} checked{/if}', $selectedField, $selectedField, addslashes($value));
|
||||
$inputName = $name;
|
||||
} else {
|
||||
$checked = sprintf('{if in_array(\'%s\', $vo.%s)} checked{/if}', addslashes($value), $selectedField);
|
||||
$inputName = $name . '[]';
|
||||
}
|
||||
|
||||
$groupNode->html(sprintf(
|
||||
'<label class="think-checkbox"><input type="%s" name="%s" value="%s" lay-ignore%s>%s%s</label>',
|
||||
self::escape($type),
|
||||
self::escape($inputName),
|
||||
self::escape($value),
|
||||
$checked,
|
||||
self::escape($label),
|
||||
$extra === '' ? '' : (' ' . $extra)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
private static function escape(string $content): string
|
||||
{
|
||||
return htmlentities(BuilderLang::text($content), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
1874
plugin/think-library/src/builder/form/FormBuilder.php
Normal file
1874
plugin/think-library/src/builder/form/FormBuilder.php
Normal file
File diff suppressed because it is too large
Load Diff
33
plugin/think-library/src/builder/form/FormButton.php
Normal file
33
plugin/think-library/src/builder/form/FormButton.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\form;
|
||||
|
||||
/**
|
||||
* 表单按钮节点.
|
||||
* @class FormButton
|
||||
*/
|
||||
class FormButton extends FormNode
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $button
|
||||
*/
|
||||
public function __construct(FormBuilder $builder, private array $button, private string $buttonHtml)
|
||||
{
|
||||
parent::__construct($builder, 'button', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出节点数组.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function export(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'button',
|
||||
'button' => $this->button,
|
||||
'html' => $this->buttonHtml,
|
||||
];
|
||||
}
|
||||
}
|
||||
17
plugin/think-library/src/builder/form/FormChoiceField.php
Normal file
17
plugin/think-library/src/builder/form/FormChoiceField.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\form;
|
||||
|
||||
/**
|
||||
* 单选/复选字段节点.
|
||||
* @class FormChoiceField
|
||||
*/
|
||||
class FormChoiceField extends FormField
|
||||
{
|
||||
public function source(string $name): static
|
||||
{
|
||||
return $this->optionsItem()->source($name)->end();
|
||||
}
|
||||
}
|
||||
52
plugin/think-library/src/builder/form/FormComponents.php
Normal file
52
plugin/think-library/src/builder/form/FormComponents.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\form;
|
||||
|
||||
use think\admin\builder\form\component\IntroComponent;
|
||||
use think\admin\builder\form\component\NoteComponent;
|
||||
use think\admin\builder\form\component\PickerFieldComponent;
|
||||
use think\admin\builder\form\component\ReadonlyFieldComponent;
|
||||
use think\admin\builder\form\component\SectionComponent;
|
||||
use think\admin\builder\form\component\ThemePaletteComponent;
|
||||
|
||||
/**
|
||||
* 表单组件工厂。
|
||||
* @class FormComponents
|
||||
*/
|
||||
class FormComponents
|
||||
{
|
||||
public static function intro(): IntroComponent
|
||||
{
|
||||
return IntroComponent::make();
|
||||
}
|
||||
|
||||
public static function section(): SectionComponent
|
||||
{
|
||||
return SectionComponent::make();
|
||||
}
|
||||
|
||||
public static function note(string $text = ''): NoteComponent
|
||||
{
|
||||
return NoteComponent::make()->text($text);
|
||||
}
|
||||
|
||||
public static function readonlyField(): ReadonlyFieldComponent
|
||||
{
|
||||
return ReadonlyFieldComponent::make();
|
||||
}
|
||||
|
||||
public static function pickerField(): PickerFieldComponent
|
||||
{
|
||||
return PickerFieldComponent::make();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, mixed>> $themes
|
||||
*/
|
||||
public static function themePalette(array $themes = [], string $current = ''): ThemePaletteComponent
|
||||
{
|
||||
return ThemePaletteComponent::make()->themes($themes)->current($current);
|
||||
}
|
||||
}
|
||||
472
plugin/think-library/src/builder/form/FormField.php
Normal file
472
plugin/think-library/src/builder/form/FormField.php
Normal file
@ -0,0 +1,472 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\form;
|
||||
|
||||
use think\admin\builder\base\render\BuilderAttributes;
|
||||
use think\admin\builder\base\render\BuilderAttributesRenderer;
|
||||
|
||||
/**
|
||||
* 表单字段节点.
|
||||
* @class FormField
|
||||
*/
|
||||
class FormField extends FormNode
|
||||
{
|
||||
/**
|
||||
* 字段子节点.
|
||||
* @var array<string, FormFieldPart>
|
||||
*/
|
||||
private array $parts = [];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $field
|
||||
*/
|
||||
public function __construct(FormBuilder $builder, private FormNode $parent, protected array $field)
|
||||
{
|
||||
parent::__construct($builder, 'field', '');
|
||||
}
|
||||
|
||||
public function name(string $name): static
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$this->field['name'] = $name;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function title(string $title): static
|
||||
{
|
||||
$this->field['title'] = trim($title);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function subtitle(string $subtitle): static
|
||||
{
|
||||
$this->field['subtitle'] = $subtitle;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function remark(string $remark): static
|
||||
{
|
||||
$this->field['remark'] = $remark;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function variable(string $name): static
|
||||
{
|
||||
$this->field['vname'] = trim($name);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function source(string $name): static
|
||||
{
|
||||
return $this->optionsItem()->source($name)->end();
|
||||
}
|
||||
|
||||
public function search(bool $enabled = true): static
|
||||
{
|
||||
return $enabled ? $this->setFieldAttr('lay-search', null) : $this->unsetFieldAttr('lay-search');
|
||||
}
|
||||
|
||||
public function options(array $options): static
|
||||
{
|
||||
return $this->optionsItem()->options($options)->end();
|
||||
}
|
||||
|
||||
public function option(string|int $value, string $label): static
|
||||
{
|
||||
return $this->optionsItem()->option($value, $label)->end();
|
||||
}
|
||||
|
||||
public function optionsItem(): FormFieldOptions
|
||||
{
|
||||
$options = is_array($this->field['options'] ?? null) ? $this->field['options'] : [];
|
||||
$source = trim(strval($this->field['vname'] ?? ''));
|
||||
return (new FormFieldOptions($this, $options, $source))
|
||||
->attach(fn(array $state): array => $this->replaceOptionsState($state));
|
||||
}
|
||||
|
||||
public function required(bool $required = true, string $message = ''): static
|
||||
{
|
||||
$this->field['required'] = $required;
|
||||
if ($message !== '') {
|
||||
$this->setFieldAttr('required-error', $message);
|
||||
} elseif (!$required) {
|
||||
$this->unsetFieldAttr('required-error');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function pattern(?string $pattern, string $message = ''): static
|
||||
{
|
||||
$this->field['pattern'] = $pattern === null || trim($pattern) === '' ? null : trim($pattern);
|
||||
if ($message !== '') {
|
||||
$this->setFieldAttr('pattern-error', $message);
|
||||
} elseif ($this->field['pattern'] === null) {
|
||||
$this->unsetFieldAttr('pattern-error');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function rule(string $rule, string $message): static
|
||||
{
|
||||
$rule = trim($rule);
|
||||
if ($rule !== '') {
|
||||
$this->field['rules'] = is_array($this->field['rules'] ?? null) ? $this->field['rules'] : [];
|
||||
$this->field['rules'][$rule] = $message;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function rules(array $rules): static
|
||||
{
|
||||
foreach ($rules as $rule => $message) {
|
||||
if (is_string($rule)) {
|
||||
$this->rule($rule, strval($message));
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function placeholder(string $placeholder): static
|
||||
{
|
||||
return $this->setFieldAttr('placeholder', $placeholder);
|
||||
}
|
||||
|
||||
public function maxlength(int $length): static
|
||||
{
|
||||
return $this->setFieldAttr('maxlength', $length);
|
||||
}
|
||||
|
||||
public function inputRightIcon(string $iconClass, array $attrs = [], string|array $inputClass = 'pr40'): static
|
||||
{
|
||||
$iconClass = trim($iconClass);
|
||||
if ($iconClass === '') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$renderer = new BuilderAttributesRenderer();
|
||||
$attrs['onmousedown'] = $this->mergeEventHandler(strval($attrs['onmousedown'] ?? ''), 'event.preventDefault();event.stopPropagation();');
|
||||
$attrs['ontouchstart'] = $this->mergeEventHandler(strval($attrs['ontouchstart'] ?? ''), 'event.preventDefault();event.stopPropagation();');
|
||||
$attrs = BuilderAttributes::make($attrs)
|
||||
->class(trim("input-right-icon layui-icon {$iconClass}"))
|
||||
->all();
|
||||
|
||||
$this->body()->class('relative');
|
||||
$this->input()->class($inputClass)->html(sprintf('<a %s></a>', $renderer->render($attrs)));
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function defaultValue(mixed $value): static
|
||||
{
|
||||
$this->field['default'] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function readonly(bool $readonly = true): static
|
||||
{
|
||||
return $readonly ? $this->setFieldAttr('readonly', null) : $this->unsetFieldAttr('readonly');
|
||||
}
|
||||
|
||||
public function disabled(bool $disabled = true): static
|
||||
{
|
||||
return $disabled ? $this->setFieldAttr('disabled', null) : $this->unsetFieldAttr('disabled');
|
||||
}
|
||||
|
||||
public function types(string $types): static
|
||||
{
|
||||
$this->uploadConfig()->types($types);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function previewOnly(): static
|
||||
{
|
||||
$this->uploadConfig()->previewOnly();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function inputTrigger(): static
|
||||
{
|
||||
$this->uploadConfig()->inputTrigger();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function uploadConfig(): FormUploadConfig
|
||||
{
|
||||
$config = is_array($this->field['upload'] ?? null) ? $this->field['upload'] : [];
|
||||
return (new FormUploadConfig($this, $config))
|
||||
->attach(fn(array $state): array => $this->replaceUploadConfig($state));
|
||||
}
|
||||
|
||||
public function label(?callable $callback = null): FormFieldPart
|
||||
{
|
||||
return $this->part('label', $callback);
|
||||
}
|
||||
|
||||
public function input(?callable $callback = null): FormFieldPart
|
||||
{
|
||||
return $this->part('input', $callback);
|
||||
}
|
||||
|
||||
public function control(?callable $callback = null): FormFieldPart
|
||||
{
|
||||
return $this->part('input', $callback);
|
||||
}
|
||||
|
||||
public function body(?callable $callback = null): FormFieldPart
|
||||
{
|
||||
return $this->part('body', $callback);
|
||||
}
|
||||
|
||||
public function remarkNode(?callable $callback = null): FormFieldPart
|
||||
{
|
||||
return $this->part('remark', $callback);
|
||||
}
|
||||
|
||||
public function field(array $field): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, $field);
|
||||
}
|
||||
|
||||
public function text(string $name, string $title, string $subtitle = '', bool $required = false, string $remark = '', ?string $pattern = null, array $attrs = []): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, [
|
||||
'type' => $attrs['type'] ?? 'text',
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'required' => $required,
|
||||
'remark' => $remark,
|
||||
'pattern' => $pattern,
|
||||
'attrs' => $attrs,
|
||||
]);
|
||||
}
|
||||
|
||||
public function textarea(string $name, string $title, string $subtitle = '', bool $required = false, string $remark = '', array $attrs = []): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, [
|
||||
'type' => 'textarea',
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'required' => $required,
|
||||
'remark' => $remark,
|
||||
'attrs' => $attrs,
|
||||
]);
|
||||
}
|
||||
|
||||
public function password(string $name, string $title, string $subtitle = '', bool $required = false, string $remark = '', ?string $pattern = null, array $attrs = []): self
|
||||
{
|
||||
$attrs['type'] = 'password';
|
||||
return $this->text($name, $title, $subtitle, $required, $remark, $pattern, $attrs);
|
||||
}
|
||||
|
||||
public function select(string $name, string $title, string $subtitle = '', bool $required = false, string $remark = '', array $options = [], string $variable = '', array $attrs = []): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, [
|
||||
'type' => 'select',
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'required' => $required,
|
||||
'remark' => $remark,
|
||||
'options' => $options,
|
||||
'vname' => $variable,
|
||||
'attrs' => $attrs,
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkbox(string $name, string $title, string $subtitle, string $variable, bool $required = false, array $attrs = []): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, [
|
||||
'type' => 'checkbox',
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'required' => $required,
|
||||
'attrs' => $attrs,
|
||||
'vname' => $variable,
|
||||
]);
|
||||
}
|
||||
|
||||
public function radio(string $name, string $title, string $subtitle, string $variable, bool $required = false, array $attrs = []): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, [
|
||||
'type' => 'radio',
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'required' => $required,
|
||||
'attrs' => $attrs,
|
||||
'vname' => $variable,
|
||||
]);
|
||||
}
|
||||
|
||||
public function image(string $name, string $title, string $subtitle = '', bool $required = false, array $attrs = []): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, [
|
||||
'type' => 'image',
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'required' => $required,
|
||||
'attrs' => $attrs,
|
||||
]);
|
||||
}
|
||||
|
||||
public function video(string $name, string $title, string $subtitle = '', bool $required = false, array $attrs = []): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, [
|
||||
'type' => 'video',
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'required' => $required,
|
||||
'attrs' => $attrs,
|
||||
]);
|
||||
}
|
||||
|
||||
public function images(string $name, string $title, string $subtitle = '', bool $required = false, array $attrs = []): self
|
||||
{
|
||||
return $this->builder->addFieldToNode($this->parent, [
|
||||
'type' => 'images',
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'required' => $required,
|
||||
'attrs' => $attrs,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出节点数组.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function export(): array
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($this->parts as $name => $part) {
|
||||
if ($part->configured()) {
|
||||
$parts[$name] = $part->export();
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'field',
|
||||
'attrs' => $this->buildAttrs(),
|
||||
'modules' => $this->modules,
|
||||
'parts' => $parts,
|
||||
'field' => $this->field,
|
||||
];
|
||||
}
|
||||
|
||||
private function part(string $name, ?callable $callback = null): FormFieldPart
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name === '') {
|
||||
throw new \InvalidArgumentException('FormField 子节点名称不能为空');
|
||||
}
|
||||
$part = $this->parts[$name] ?? new FormFieldPart($this, $name);
|
||||
$this->parts[$name] = $part;
|
||||
if (is_callable($callback)) {
|
||||
$callback($part);
|
||||
}
|
||||
return $part;
|
||||
}
|
||||
|
||||
protected function setFieldAttr(string $name, mixed $value = null): static
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$attrs = is_array($this->field['attrs'] ?? null) ? $this->field['attrs'] : [];
|
||||
$attrs[$name] = $value;
|
||||
$this->field['attrs'] = $attrs;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function unsetFieldAttr(string $name): static
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name !== '' && is_array($this->field['attrs'] ?? null) && array_key_exists($name, $this->field['attrs'])) {
|
||||
unset($this->field['attrs'][$name]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $state
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function replaceOptionsState(array $state): array
|
||||
{
|
||||
$this->field['options'] = is_array($state['options'] ?? null) ? $state['options'] : [];
|
||||
$this->field['vname'] = trim(strval($state['vname'] ?? ''));
|
||||
return [
|
||||
'options' => is_array($this->field['options'] ?? null) ? $this->field['options'] : [],
|
||||
'vname' => trim(strval($this->field['vname'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function replaceUploadConfig(array $config): array
|
||||
{
|
||||
$this->field['upload'] = $this->normalizeUploadConfig($config);
|
||||
return $this->field['upload'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function normalizeUploadConfig(array $config): array
|
||||
{
|
||||
$config = array_merge([
|
||||
'types' => '',
|
||||
'display' => '',
|
||||
'trigger' => [],
|
||||
'runtime' => [],
|
||||
], $config);
|
||||
|
||||
$config['types'] = trim(strval($config['types']));
|
||||
$config['display'] = strtolower(trim(strval($config['display'])));
|
||||
if (!in_array($config['display'], ['', 'input', 'preview'], true)) {
|
||||
$config['display'] = '';
|
||||
}
|
||||
$config['trigger'] = is_array($config['trigger']) ? $config['trigger'] : [];
|
||||
$config['runtime'] = is_array($config['runtime']) ? $config['runtime'] : [];
|
||||
$config['trigger']['attrs'] = is_array($config['trigger']['attrs'] ?? null) ? $config['trigger']['attrs'] : [];
|
||||
if (isset($config['trigger']['class'])) {
|
||||
$config['trigger']['class'] = trim(strval($config['trigger']['class']));
|
||||
}
|
||||
if (isset($config['trigger']['icon'])) {
|
||||
$config['trigger']['icon'] = trim(strval($config['trigger']['icon']));
|
||||
}
|
||||
if (isset($config['runtime']['method'])) {
|
||||
$config['runtime']['method'] = trim(strval($config['runtime']['method']));
|
||||
}
|
||||
if (isset($config['runtime']['selector'])) {
|
||||
$config['runtime']['selector'] = trim(strval($config['runtime']['selector']));
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function mergeEventHandler(string $origin, string $append): string
|
||||
{
|
||||
$origin = trim($origin);
|
||||
$append = trim($append);
|
||||
if ($origin === '') {
|
||||
return $append;
|
||||
}
|
||||
if ($append === '') {
|
||||
return $origin;
|
||||
}
|
||||
return rtrim($append, ';') . ';' . ltrim($origin, ';');
|
||||
}
|
||||
}
|
||||
22
plugin/think-library/src/builder/form/FormFieldOptions.php
Normal file
22
plugin/think-library/src/builder/form/FormFieldOptions.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\admin\builder\form;
|
||||
|
||||
use think\admin\builder\base\BuilderOptionSource;
|
||||
|
||||
/**
|
||||
* 表单字段选项源对象.
|
||||
* @class FormFieldOptions
|
||||
*/
|
||||
class FormFieldOptions extends BuilderOptionSource
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(FormField $owner, array $options = [], string $source = '')
|
||||
{
|
||||
parent::__construct('vname', $options, $source, $owner);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user