初始化提交

This commit is contained in:
maticarmy
2025-02-10 10:39:00 +08:00
commit 59cd2c19d1
491 changed files with 54545 additions and 0 deletions

69
app/admin/Service.php Normal file
View File

@@ -0,0 +1,69 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin;
use think\admin\Plugin;
/**
* 插件服务注册
* @class Service
* @package app\admin
*/
class Service extends Plugin
{
/**
* 定义插件名称
* @var string
*/
protected $appName = '系统管理';
/**
* 定义安装包名
* @var string
*/
protected $package = 'zoujingli/think-plugs-admin';
/**
* 定义插件中心菜单
* @return array
*/
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'],
],
],
];
}
}

View File

@@ -0,0 +1,134 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
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;
/**
* 系统权限管理
* @class Auth
* @package app\admin\controller
*/
class Auth extends Controller
{
/**
* 系统权限管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\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');
}
/**
* 表单后置数据处理
* @param array $data
*/
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('未配置功能节点!');
}
}
/**
* 节点更新处理
* @param boolean $state
* @param array $post
* @return void
*/
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()');
}
}
}

View File

@@ -0,0 +1,113 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemBase;
/**
* 数据字典管理
* @class Base
* @package app\admin\controller
*/
class Base extends Controller
{
/**
* 数据字典管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\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');
}
/**
* 表单数据处理
* @param array $data
* @throws \think\db\exception\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("数据编码已经存在!");
}
}
}
/**
* 修改数据状态
* @auth true
*/
public function state()
{
SystemBase::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除数据记录
* @auth true
*/
public function remove()
{
SystemBase::mDelete();
}
}

View File

@@ -0,0 +1,146 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
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
* @package app\admin\controller
*/
class Config extends Controller
{
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('修改文件存储成功!');
}
}
}

View File

@@ -0,0 +1,118 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
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;
/**
* 系统文件管理
* @class File
* @package app\admin\controller
*/
class File extends Controller
{
/**
* 存储类型
* @var array
*/
protected $types;
/**
* 控制器初始化
* @return void
*/
protected function initialize()
{
$this->types = Storage::types();
}
/**
* 系统文件管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\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()]);
});
}
/**
* 数据列表处理
* @param array $data
* @return void
*/
protected function _page_filter(array &$data)
{
foreach ($data as &$vo) {
$vo['ctype'] = $this->types[$vo['type']] ?? $vo['type'];
}
}
/**
* 编辑系统文件
* @auth true
* @return void
*/
public function edit()
{
SystemFile::mForm('form');
}
/**
* 删除系统文件
* @auth true
* @return void
*/
public function remove()
{
if (!AdminService::isSuper()) {
$where = ['uuid' => AdminService::getUserId()];
}
SystemFile::mDelete('', $where ?? []);
}
/**
* 清理重复文件
* @auth true
* @return void
* @throws \think\db\exception\DbException
*/
public function distinct()
{
$map = ['uuid' => AdminService::getUserId()];
$db1 = SystemFile::mk()->fieldRaw('max(id) id')->where($map)->group('type,xkey');
$db2 = $this->app->db->table($db1->buildSql())->alias('dt')->field('id');
SystemFile::mk()->whereRaw("id not in {$db2->buildSql()}")->delete();
SystemFile::mk()->where($map)->where(['status' => 1])->delete();
$this->success('清理重复文件成功!');
}
}

View File

@@ -0,0 +1,157 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\model\SystemUser;
use think\admin\service\AdminService;
use think\admin\service\MenuService;
/**
* 后台界面入口
* @class Index
* @package app\admin\controller
*/
class Index extends Controller
{
/**
* 显示后台首页
* @throws \think\admin\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\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
* @return void
* @throws \think\admin\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
* @param mixed $id 用户ID
*/
public function info($id = 0)
{
$this->_applyFormToken();
if (AdminService::getUserId() === intval($id)) {
SystemUser::mForm('user/form', 'id', [], ['id' => $id]);
} else {
$this->error('只能修改自己的资料!');
}
}
/**
* 资料修改表单处理
* @param array $data
*/
protected function _info_form_filter(array &$data)
{
if ($this->request->isPost()) {
unset($data['username'], $data['authorize']);
}
}
/**
* 资料修改结果处理
* @param bool $status
*/
protected function _info_form_result(bool $status)
{
if ($status) {
$this->success('用户资料修改成功!', 'javascript:location.reload()');
}
}
/**
* 修改当前用户密码
* @login true
* @param mixed $id
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function pass($id = 0)
{
$this->_applyFormToken();
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('密码修改失败,请稍候再试!');
}
}
}
}

View File

@@ -0,0 +1,137 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
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
* @package app\admin\controller
*/
class Login extends Controller
{
/**
* 后台登录入口
* @return void
* @throws \think\admin\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'));
}
}
/**
* 生成验证码
* @return void
*/
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);
}
/**
* 退出登录
* @return void
*/
public function out()
{
$this->app->session->destroy();
$this->success('退出登录成功!', sysuri('admin/login/index'));
}
}

View File

@@ -0,0 +1,147 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
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;
/**
* 系统菜单管理
* @class Menu
* @package app\admin\controller
*/
class Menu extends Controller
{
/**
* 系统菜单管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
$this->title = '系统菜单管理';
$this->type = $this->get['type'] ?? 'index';
SystemMenu::mQuery()->layTable();
}
/**
* 列表数据处理
* @param array $data
*/
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);
foreach ($data as &$vo) {
if ($vo['url'] !== '#' && !preg_match('/^(https?:)?(\/\/|\\\\)/i', $vo['url'])) {
$vo['url'] = trim(url($vo['url']) . ($vo['params'] ? "?{$vo['params']}" : ''), '\\/');
}
}
}
/**
* 添加系统菜单
* @auth true
*/
public function add()
{
$this->_applyFormToken();
SystemMenu::mForm('form');
}
/**
* 编辑系统菜单
* @auth true
*/
public function edit()
{
$this->_applyFormToken();
SystemMenu::mForm('form');
}
/**
* 表单数据处理
* @param array $vo
*/
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']) && isset($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]);
}
}
}
/**
* 修改菜单状态
* @auth true
*/
public function state()
{
SystemMenu::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除系统菜单
* @auth true
*/
public function remove()
{
SystemMenu::mDelete();
}
}

View File

@@ -0,0 +1,95 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller;
use Ip2Region;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemOplog;
use think\exception\HttpResponseException;
/**
* 系统日志管理
* @class Oplog
* @package app\admin\controller
*/
class Oplog extends Controller
{
/**
* 系统日志管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
SystemOplog::mQuery()->layTable(function () {
$this->title = '系统日志管理';
$columns = SystemOplog::mk()->column('action,username', 'id');
$this->users = array_unique(array_column($columns, 'username'));
$this->actions = array_unique(array_column($columns, 'action'));
}, static function (QueryHelper $query) {
$query->dateBetween('create_at')->equal('username,action')->like('content,geoip,node');
});
}
/**
* 列表数据处理
* @param array $data
* @throws \Exception
*/
protected function _index_page_filter(array &$data)
{
$region = new Ip2Region();
foreach ($data as &$vo) try {
$vo['geoisp'] = $region->simple($vo['geoip']);
} catch (\Exception $exception) {
$vo['geoip'] = $exception->getMessage();
}
}
/**
* 清理系统日志
* @auth true
*/
public function clear()
{
try {
SystemOplog::mQuery()->empty();
sysoplog('系统运维管理', '成功清理所有日志');
$this->success('日志清理成功!');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error(lang("日志清理失败,%s", [$exception->getMessage()]));
}
}
/**
* 删除系统日志
* @auth true
*/
public function remove()
{
SystemOplog::mDelete();
}
}

View File

@@ -0,0 +1,117 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
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\exception\HttpResponseException;
/**
* 系统任务管理
* @class Queue
* @package app\admin\controller
*/
class Queue extends Controller
{
/**
* 系统任务管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\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');
});
}
/**
* 分页数据回调处理
* @param array $data
* @param array $result
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\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'];
});
}
/**
* 重启系统任务
* @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();
}
}

View File

@@ -0,0 +1,182 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
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;
/**
* 系统用户管理
* @class User
* @package app\admin\controller
*/
class User extends Controller
{
/**
* 系统用户管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\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('密码修改失败,请稍候再试!');
}
}
}
/**
* 表单数据处理
* @param array $data
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\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();
}
}
/**
* 修改用户状态
* @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();
}
/**
* 检查输入变量
*/
private function _checkInput()
{
if (in_array('10000', str2arr(input('id', '')))) {
$this->error('系统超级账号禁止删除!');
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
declare (strict_types = 1);
namespace app\admin\controller\api;
use think\admin\Controller;
/**
* 邮箱配置接口
*/
class Mail extends Controller
{
/**
* 邮箱配置列表
*/
private $mailConfigs = [
[
'name' => '默认配置1',
'config' => [
'DOMAIN' => '586vip.cn',
'TEMP_MAIL' => 'ademyyk',
'TEMP_MAIL_EXT' => '@mailto.plus',
'BROWSER_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36',
// 'BROWSER_PROXY' => 'http://127.0.0.1:2080',
// 'BROWSER_HEADLESS' => 'True',
'MAIL_SERVER' => 'https://tempmail.plus'
]
],
[
'name' => '备用配置1',
'config' => [
'DOMAIN' => 'nosqli.com',
'TEMP_MAIL' => 'ademyyk',
'TEMP_MAIL_EXT' => '@mailto.plus',
'BROWSER_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36',
'MAIL_SERVER' => 'https://tempmail.plus'
]
]
// ,
// [
// 'name' => 'IMAP配置',
// 'config' => [
// 'DOMAIN' => 'wuen.site',
// 'TEMP_MAIL' => null,
// 'IMAP_SERVER' => 'imap.163.com',
// 'IMAP_PORT' => 993,
// 'IMAP_USER' => 'maticarmy@163.com',
// 'IMAP_PASS' => 'LQer6rsSWan6vtuz',
// 'BROWSER_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36',
// 'MAIL_SERVER' => 'https://tempmail.plus'
// ]
// ]
];
/**
* 获取所有配置
*/
public function getAll()
{
return json([
'code' => 0,
'msg' => '获取成功',
'data' => $this->mailConfigs
]);
}
/**
* 获取随机配置
*/
public function getRandom()
{
// 随机获取一个配置
$config = $this->mailConfigs[array_rand($this->mailConfigs)];
return json([
'code' => 0,
'msg' => '获取成功',
'data' => [
'name' => $config['name'],
'env' => $config['config']
]
]);
}
/**
* 获取指定配置
*/
public function getConfig()
{
$name = input('name', '');
// 如果未指定名称,返回随机配置
if (empty($name)) {
return $this->getRandom();
}
// 查找指定配置
foreach ($this->mailConfigs as $config) {
if ($config['name'] === $name) {
return json([
'code' => 0,
'msg' => '获取成功',
'data' => [
'name' => $config['name'],
'env' => $config['config']
]
]);
}
}
// 未找到指定配置,返回随机配置
return $this->getRandom();
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare (strict_types = 1);
namespace app\admin\controller\api;
use app\manager\model\Member as MemberModel;
use think\admin\Controller;
/**
* 会员接口管理
*/
class Member extends Controller
{
/**
* 验证会员状态
*/
public function check()
{
// 接收参数
$keyword = trim(input('keyword', ''));
if (empty($keyword)) {
return json([
'code' => 1,
'msg' => '请输入查询关键字'
]);
}
// 查询会员信息 (email = xxx OR order_id = xxx)
$member = MemberModel::whereOr('email', '=', $keyword)
->whereOr('order_id', '=', $keyword)
->find();
if (empty($member)) {
return json([
'code' => 1,
'msg' => '会员不存在'
]);
}
// 检查会员状态
if ($member['status'] != 1) {
return json([
'code' => 1,
'msg' => '会员已被禁用'
]);
}
// 检查有效期
if (strtotime($member['expire_time']) < time()) {
return json([
'code' => 1,
'msg' => '会员已过期'
]);
}
// 检查使用次数
if ($member['usage_limit'] > 0 && $member['used_count'] >= $member['usage_limit']) {
return json([
'code' => 1,
'msg' => '使用次数已达上限'
]);
}
// 更新使用次数和最后登录信息
$member->used_count = $member->used_count + 1;
$member->last_login_time = date('Y-m-d H:i:s');
$member->last_login_ip = $this->request->ip();
$member->save();
// 返回成功
return json([
'code' => 0,
'msg' => '验证通过',
'data' => [
'email' => $member['email'],
'order_id' => $member['order_id'],
'expire_time' => $member['expire_time'],
'usage_limit' => $member['usage_limit'],
'used_count' => $member['used_count'],
'last_login_time' => $member['last_login_time']
]
]);
}
}

View File

@@ -0,0 +1,91 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller\api;
use think\admin\Controller;
use think\admin\service\AdminService;
use think\Response;
/**
* 扩展插件管理
* @class Plugs
* @package app\admin\controller\api
*/
class Plugs extends Controller
{
/**
* 图标选择器
* @login true
*/
public function icon()
{
$this->title = '图标选择器';
// 读取 layui 字体图标
if (empty($this->layuiIcons = $this->app->cache->get('LayuiIcons', []))) {
$style = file_get_contents(syspath('public/static/plugs/layui/css/layui.css'));
if (preg_match_all('#\.(layui-icon-[\w-]+):#', $style, $matches)) {
if (count($this->layuiIcons = $matches[1]) > 0) {
$this->app->cache->set('LayuiIcons', $this->layuiIcons, 60);
}
}
}
// 读取自定义字体图标
if (empty($this->thinkIcons = $this->app->cache->get('ThinkAdminSelfIcons', []))) {
$style = file_get_contents(syspath('public/static/theme/css/iconfont.css'));
if (preg_match_all('#\.(iconfont-[\w-]+):#', $style, $matches)) {
if (count($this->thinkIcons = $matches[1]) > 0) {
$this->app->cache->set('ThinkAdminSelfIcons', $this->thinkIcons, 60);
}
}
}
$this->field = $this->app->request->get('field', 'icon');
$this->fetch(dirname(__DIR__, 2) . '/view/api/icon.html');
}
/**
* 前端脚本变量
* @return \think\Response
* @throws \think\admin\Exception
*/
public function script(): Response
{
$token = $this->request->get('uptoken', '');
$domain = boolval(AdminService::withUploadUnid($token));
return response(join("\r\n", [
sprintf("window.taDebug = %s;", $this->app->isDebug() ? 'true' : 'false'),
sprintf("window.taAdmin = '%s';", sysuri('admin/index/index', [], false, $domain)),
sprintf("window.taEditor = '%s';", sysconf('base.editor|raw') ?: 'ckeditor4'),
]))->contentType('application/javascript');
}
/**
* 优化数据库
* @login true
*/
public function optimize()
{
if (AdminService::isSuper()) {
sysoplog('系统运维管理', '创建数据库优化任务');
$this->_queue('优化数据库所有数据表', 'xadmin:database optimize');
} else {
$this->error('请使用超管账号操作!');
}
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare (strict_types = 1);
namespace app\admin\controller\api;
use think\admin\Controller;
/**
* 程序更新接口
*/
class Program extends Controller
{
/**
* 程序目录配置
*/
private $programPath = 'program/';
/**
* 获取程序信息
*/
public function info()
{
$path = public_path() . $this->programPath;
if (!is_dir($path)) {
return json([
'code' => 1,
'msg' => '程序目录不存在'
]);
}
// 获取目录中最新的文件
$files = glob($path . '*');
if (empty($files)) {
return json([
'code' => 1,
'msg' => '暂无可用程序'
]);
}
// 获取最新文件
$latest = array_reduce($files, function($carry, $file) {
return (!$carry || filemtime($file) > filemtime($carry)) ? $file : $carry;
});
$name = basename($latest);
$size = filesize($latest);
$md5 = md5_file($latest);
return json([
'code' => 0,
'msg' => '获取成功',
'data' => [
'name' => $name,
'size' => $size,
'md5' => $md5,
'url' => request()->domain() . '/' . $this->programPath . $name,
'time' => date('Y-m-d H:i:s', filemtime($latest))
]
]);
}
/**
* 下载程序
*/
public function down()
{
$path = public_path() . $this->programPath;
if (!is_dir($path)) {
return json([
'code' => 1,
'msg' => '程序目录不存在'
]);
}
// 获取目录中最新的文件
$files = glob($path . '*');
if (empty($files)) {
return json([
'code' => 1,
'msg' => '暂无可用程序'
]);
}
// 获取最新文件
$latest = array_reduce($files, function($carry, $file) {
return (!$carry || filemtime($file) > filemtime($carry)) ? $file : $carry;
});
return download($latest, basename($latest));
}
}

View File

@@ -0,0 +1,118 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
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
* @package app\admin\controller\api
*/
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));
}
}

View File

@@ -0,0 +1,138 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller\api;
use think\admin\Controller;
use think\admin\model\SystemConfig;
use think\admin\service\AdminService;
use think\admin\service\RuntimeService;
use think\exception\HttpResponseException;
/**
* 系统运行管理
* @class System
* @package app\admin\controller\api
*/
class System extends Controller
{
/**
* 网站压缩发布
* @login true
*/
public function push()
{
if (AdminService::isSuper()) try {
RuntimeService::push() && sysoplog('系统运维管理', '刷新发布运行缓存');
$this->success('网站缓存加速成功!', 'javascript:location.reload()');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 清理运行缓存
* @login true
*/
public function clear()
{
if (AdminService::isSuper()) try {
RuntimeService::clear() && sysoplog('系统运维管理', '清理网站日志缓存');
$this->success('清空日志缓存成功!', 'javascript:location.reload()');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 当前运行模式
* @login true
*/
public function debug()
{
if (AdminService::isSuper()) if (input('state')) {
RuntimeService::set('product');
sysoplog('系统运维管理', '开发模式切换为生产模式');
$this->success('已切换为生产模式!', 'javascript:location.reload()');
} else {
RuntimeService::set('debug');
sysoplog('系统运维管理', '生产模式切换为开发模式');
$this->success('已切换为开发模式!', 'javascript:location.reload()');
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 修改富文本编辑器
* @return void
* @throws \think\admin\Exception
*/
public function editor()
{
if (AdminService::isSuper()) {
$editor = input('editor', 'auto');
sysconf('base.editor', $editor);
sysoplog('系统运维管理', "切换编辑器为{$editor}");
$this->success('已切换后台编辑器!', 'javascript:location.reload()');
} else {
$this->error('请使用超管账号操作!');
}
}
/**
* 清理系统配置
* @login true
*/
public function config()
{
if (AdminService::isSuper()) try {
[$tmpdata, $newdata] = [[], []];
foreach (SystemConfig::mk()->order('type,name asc')->cursor() as $item) {
$tmpdata[$item['type']][$item['name']] = $item['value'];
}
foreach ($tmpdata as $type => $items) foreach ($items as $name => $value) {
$newdata[] = ['type' => $type, 'name' => $name, 'value' => $value];
}
$this->app->db->transaction(static function () use ($newdata) {
SystemConfig::mQuery()->empty()->insertAll($newdata);
});
$this->app->cache->delete('SystemConfig');
sysoplog('系统运维管理', '清理系统配置参数');
$this->success('清理系统配置成功!', 'javascript:location.reload()');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
} else {
$this->error('请使用超管账号操作!');
}
}
}

View File

@@ -0,0 +1,336 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\admin\controller\api;
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\admin\storage\AliossStorage;
use think\admin\storage\AlistStorage;
use think\admin\storage\LocalStorage;
use think\admin\storage\QiniuStorage;
use think\admin\storage\TxcosStorage;
use think\admin\storage\UpyunStorage;
use think\exception\HttpResponseException;
use think\file\UploadedFile;
use think\Response;
/**
* 文件上传接口
* @class Upload
* @package app\admin\controller\api
*/
class Upload extends Controller
{
/**
* 文件上传脚本
* @return Response
* @throws \think\admin\Exception
*/
public function index(): Response
{
$data = ['exts' => []];
[$uuid, $unid, $exts] = $this->initUnid(false);
$allows = str2arr(sysconf('storage.allow_exts|raw'));
if (empty($uuid) && $unid > 0) $allows = array_intersect($exts, $allows);
foreach ($allows as $ext) $data['exts'][$ext] = Storage::mime($ext);
$data['exts'] = json_encode($data['exts'], JSON_UNESCAPED_UNICODE);
$data['nameType'] = sysconf('storage.name_type|raw') ?: 'xmd5';
return view(dirname(__DIR__, 2) . '/view/api/upload.js', $data)->contentType('application/x-javascript');
}
/**
* 文件选择器
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function image()
{
[$uuid, $unid] = $this->initUnid();
SystemFile::mQuery()->layTable(function () {
$this->title = '文件选择器';
}, function (QueryHelper $query) use ($unid, $uuid) {
if ($unid && $uuid) $query->where(function ($query) use ($uuid, $unid) {
/** @var \think\db\Query $query */
$query->whereOr([['uuid', '=', $uuid], ['unid', '=', $unid]]);
}); else {
$query->where($unid ? ['unid' => $unid] : ['uuid' => $uuid]);
}
$query->where(['status' => 2, 'issafe' => 0])->in('xext#type');
$query->like('name,hash')->dateBetween('create_at')->order('id desc');
});
}
/**
* 文件上传检查
*/
public function state()
{
try {
[$uuid, $unid] = $this->initUnid();
[$name, $safe] = [input('name'), $this->getSafe()];
$data = ['uptype' => $this->getType(), 'safe' => intval($safe), 'key' => input('key')];
$file = SystemFile::mk()->data($this->_vali([
'xkey.value' => $data['key'],
'type.value' => $this->getType(),
'uuid.value' => $uuid,
'unid.value' => $unid,
'name.require' => '名称不能为空!',
'hash.require' => '哈希不能为空!',
'xext.require' => '后缀不能为空!',
'size.require' => '大小不能为空!',
'mime.default' => '',
'status.value' => 1,
]));
$mime = $file->getAttr('mime');
if (empty($mime)) $file->setAttr('mime', Storage::mime($file->getAttr('xext')));
$info = Storage::instance($data['uptype'])->info($data['key'], $safe, $name);
if (isset($info['url']) && isset($info['key'])) {
$file->save(['xurl' => $info['url'], 'isfast' => 1, 'issafe' => $data['safe']]);
$extr = ['id' => $file->id ?? 0, 'url' => $info['url'], 'key' => $info['key']];
$this->success('文件已经上传', array_merge($data, $extr), 200);
} elseif ('local' === $data['uptype']) {
$local = LocalStorage::instance();
$data['url'] = $local->url($data['key'], $safe, $name);
$data['server'] = $local->upload();
} elseif ('qiniu' === $data['uptype']) {
$qiniu = QiniuStorage::instance();
$data['url'] = $qiniu->url($data['key'], $safe, $name);
$data['token'] = $qiniu->token($data['key'], 3600, $name);
$data['server'] = $qiniu->upload();
} elseif ('alioss' === $data['uptype']) {
$alioss = AliossStorage::instance();
$token = $alioss->token($data['key'], 3600, $name);
$data['url'] = $token['siteurl'];
$data['policy'] = $token['policy'];
$data['signature'] = $token['signature'];
$data['OSSAccessKeyId'] = $token['keyid'];
$data['server'] = $alioss->upload();
} elseif ('txcos' === $data['uptype']) {
$txcos = TxcosStorage::instance();
$token = $txcos->token($data['key'], 3600, $name);
$data['url'] = $token['siteurl'];
$data['q-ak'] = $token['q-ak'];
$data['policy'] = $token['policy'];
$data['q-key-time'] = $token['q-key-time'];
$data['q-signature'] = $token['q-signature'];
$data['q-sign-algorithm'] = $token['q-sign-algorithm'];
$data['server'] = $txcos->upload();
} elseif ('upyun' === $data['uptype']) {
$upyun = UpyunStorage::instance();
$token = $upyun->token($data['key'], 3600, $name, input('hash', ''));
$data['url'] = $token['siteurl'];
$data['policy'] = $token['policy'];
$data['server'] = $upyun->upload();
$data['authorization'] = $token['authorization'];
} elseif ('alist' === $data['uptype']) {
$alist = AlistStorage::instance();
$data['url'] = $alist->url($data['key']);
$data['server'] = $alist->upload();
$data['filepath'] = $alist->real($data['key']);
$data['authorization'] = $alist->token();
} else {
$this->error('未知的存储引擎!');
}
$file->save(['xurl' => $data['url'], 'isfast' => 0, 'issafe' => $data['safe']]);
$this->success('获取上传授权参数', array_merge($data, ['id' => $file->id ?? 0]), 404);
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
/**
* 更新文件状态
* @return void
*/
public function done()
{
[$uuid, $unid] = $this->initUnid();
$data = $this->_vali([
'id.require' => '编号不能为空!',
'hash.require' => '哈希不能为空!',
'uuid.value' => $uuid,
'unid.value' => $unid,
]);
$file = SystemFile::mk()->where($data)->findOrEmpty();
if ($file->isEmpty()) $this->error('文件不存在!');
if ($file->save(['status' => 2])) {
$this->success('更新成功!');
} else {
$this->error('更新失败!');
}
}
/**
* 文件上传入口
* @throws \think\admin\Exception
*/
public function file()
{
[$uuid, $unid, $unexts] = $this->initUnid();
// 开始处理文件上传
$file = $this->getFile();
$extension = strtolower($file->getOriginalExtension());
$saveFileName = input('key') ?: Storage::name($file->getPathname(), $extension, '', 'md5_file');
// 检查文件名称是否合法
if (strpos($saveFileName, '..') !== false) {
$this->error('文件路径不能出现跳级操作!');
}
// 检查文件后缀是否被恶意修改
if (strtolower(pathinfo(parse_url($saveFileName, PHP_URL_PATH), PATHINFO_EXTENSION)) !== $extension) {
$this->error('文件后缀异常,请重新上传文件!');
}
// 屏蔽禁止上传指定后缀的文件
if (!in_array($extension, str2arr(sysconf('storage.allow_exts|raw')))) {
$this->error('文件类型受限,请在后台配置规则!');
}
// 前端用户上传后缀检查处理
if (empty($uuid) && $unid > 0 && !in_array($extension, $unexts)) {
$this->error('文件类型受限,请上传允许的文件类型!');
}
if (in_array($extension, ['sh', 'asp', 'bat', 'cmd', 'exe', 'php'])) {
$this->error('文件安全保护,禁止上传可执行文件!');
}
try {
$safeMode = $this->getSafe();
if (($type = $this->getType()) === 'local') {
$local = LocalStorage::instance();
$distName = $local->path($saveFileName, $safeMode);
if (PHP_SAPI === 'cli') {
is_dir(dirname($distName)) || mkdir(dirname($distName), 0777, true);
rename($file->getPathname(), $distName);
} else {
$file->move(dirname($distName), basename($distName));
}
$info = $local->info($saveFileName, $safeMode, $file->getOriginalName());
if (in_array($extension, ['jpg', 'gif', 'png', 'bmp', 'jpeg', 'wbmp'])) {
if ($this->imgNotSafe($distName) && $local->del($saveFileName)) {
$this->error('图片未通过安全检查!');
}
[$width, $height] = getimagesize($distName);
if (($width < 1 || $height < 1) && $local->del($saveFileName)) {
$this->error('读取图片的尺寸失败!');
}
}
} else {
$bina = file_get_contents($file->getPathname());
$info = Storage::instance($type)->set($saveFileName, $bina, $safeMode, $file->getOriginalName());
}
if (isset($info['url'])) {
$this->success('文件上传成功!', ['url' => $safeMode ? $saveFileName : $info['url']]);
} else {
$this->error('文件处理失败,请稍候再试!');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
}
}
/**
* 获取上传类型
* @return boolean
*/
private function getSafe(): bool
{
return boolval(input('safe', '0'));
}
/**
* 获取上传方式
* @return string
* @throws \think\admin\Exception
*/
private function getType(): string
{
$type = strtolower(input('uptype', ''));
if (in_array($type, array_keys(Storage::types()))) {
return $type;
} else {
return strtolower(sysconf('storage.type|raw'));
}
}
/**
* 获取文件对象
* @return UploadedFile|void
*/
private function getFile(): UploadedFile
{
try {
$file = $this->request->file('file');
if ($file instanceof UploadedFile) {
return $file;
} else {
$this->error('读取临时文件失败!');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error(lang($exception->getMessage()));
}
}
/**
* 初始化用户状态
* @param boolean $check
* @return array
*/
private function initUnid(bool $check = true): array
{
$uuid = AdminService::getUserId();
[$unid, $exts] = AdminService::withUploadUnid();
if ($check && empty($uuid) && empty($unid)) {
$this->error('未登录,禁止使用文件上传!');
} else {
return [$uuid, $unid, $exts];
}
}
/**
* 检查图片是否安全
* @param string $filename
* @return boolean
*/
private function imgNotSafe(string $filename): bool
{
$source = fopen($filename, 'rb');
if (($size = filesize($filename)) > 512) {
$hexs = bin2hex(fread($source, 512));
fseek($source, $size - 512);
$hexs .= bin2hex(fread($source, 512));
} else {
$hexs = bin2hex(fread($source, $size));
}
if (is_resource($source)) fclose($source);
$bins = hex2bin($hexs);
/* 匹配十六进制中的 <% ( ) %> 或 <? ( ) ?> 或 <script | /script> */
foreach (['<?php ', '<% ', '<script '] as $key) if (stripos($bins, $key) !== false) return true;
$result = preg_match("/(3c25.*?28.*?29.*?253e)|(3c3f.*?28.*?29.*?3f3e)|(3C534352495054)|(2F5343524950543E)|(3C736372697074)|(2F7363726970743E)/is", $hexs);
return $result === false || $result > 0;
}
}

200
app/admin/lang/en-us.php Normal file
View File

@@ -0,0 +1,200 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
$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['优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。'] = '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',
]);

52
app/admin/route/demo.php Normal file
View File

@@ -0,0 +1,52 @@
<?php
// +----------------------------------------------------------------------
// | Admin Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-admin
// | github 代码仓库https://github.com/zoujingli/think-plugs-admin
// +----------------------------------------------------------------------
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('演示环境禁止修改密码!')]);
});
}

View File

@@ -0,0 +1,125 @@
<!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="apple-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')}">
<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 $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>

View File

@@ -0,0 +1,327 @@
define(['md5', 'notify'], function (SparkMD5, Notify, allowMime) {
allowMime = JSON.parse('{$exts|raw}');
function UploadAdapter(elem, done) {
return new (function (elem, done) {
let that = this;
/*! 初始化变量 */
this.option = {elem: $(elem), exts: [], mimes: []};
this.option.size = this.option.elem.data('size') || 0;
this.option.safe = this.option.elem.data('safe') ? 1 : 0;
this.option.hide = this.option.elem.data('hload') ? 1 : 0;
this.option.mult = this.option.elem.data('multiple') > 0;
this.option.path = (this.option.elem.data('path') || '').replace(/\W/g, '');
this.option.type = this.option.safe ? 'local' : this.option.elem.attr('data-uptype') || '';
this.option.quality = parseFloat(this.option.elem.data('quality') || '1.0');
this.option.maxWidth = parseInt(this.option.elem.data('max-width') || '0');
this.option.maxHeight = parseInt(this.option.elem.data('max-height') || '0');
this.option.cutWidth = parseInt(this.option.elem.data('cut-width') || '0');
this.option.cutHeight = parseInt(this.option.elem.data('cut-height') || '0');
/*! 查找表单元素, 如果没有找到将不会自动写值 */
if (this.option.elem.data('input')) {
this.option.input = $(this.option.elem.data('input'))
} else if (this.option.elem.data('field')) {
this.option.input = $('input[name="' + this.option.elem.data('field') + '"]:not([type=file])');
this.option.elem.data('input', this.option.input.size() > 0 ? this.option.input.get(0) : null);
}
/*! 文件选择筛选,使用 MIME 规则过滤文件列表 */
$((this.option.elem.data('type') || '').split(',')).map(function (i, e) {
if (allowMime[e]) that.option.exts.push(e), that.option.mimes.push(allowMime[e]);
});
/*! 初始化上传组件 */
this.adapter = new Adapter(this.option, layui.upload.render({
url: '{:url("admin/api.upload/file",[],false,true)}', auto: false, elem: elem, accept: 'file', multiple: this.option.mult, exts: this.option.exts.join('|'), acceptMime: this.option.mimes.join(','), choose: function (obj) {
obj.items = [], obj.files = obj.pushFile();
layui.each(obj.files, function (idx, file) {
obj.items.push(file);
file.path = that.option.path;
file.quality = that.option.quality;
file.maxWidth = that.option.maxWidth;
file.maxHeight = that.option.maxHeight;
file.cutWidth = that.option.cutWidth;
file.cutHeight = that.option.cutHeight;
});
that.adapter.event('upload.choose', obj.items);
that.adapter.upload(obj.items, done);
layui.each(obj.files, function (idx) {
delete obj.files[idx];
});
}
}));
})(elem, done)
}
// 创建对象
UploadAdapter.adapter = window.AdminUploadAdapter = Adapter;
// 上传文件
function Adapter(option, uploader) {
this.uploader = uploader, this.config = function (option) {
return (this.option = Object.assign({}, this.option || {}, option || {})), this;
}, this.init = function (option) {
this.uploader && this.uploader.config.elem.next().val('');
this.files = {}, this.loader = 0, this.count = {total: 0, error: 0, success: 0};
return this.config(option).config({safe: this.option.safe || 0, type: this.option.type || ''});
}, this.init(option);
}
// 文件推送
Adapter.prototype.upload = function (files, done) {
let that = this.init();
layui.each(files, function (index, file) {
that.count.total++, file.index = index, that.files[index] = file;
if (!that.option.hide && !file.notify) {
file.notify = new NotifyExtend(file);
}
if (that.option.size && file.size > that.option.size) {
that.event('upload.error', {file: file}, file, '{:lang("大小超出限制!")}');
}
});
layui.each(files, function (index, file) {
// 禁传异常状态文件
if (typeof file.xstate === 'number' && file.xstate === -1) return;
// 图片限宽限高压缩
let isGif = /^image\/gif/.test(file.type);
if (!isGif && /^image\//.test(file.type) && (file.maxWidth + file.maxHeight + file.cutWidth + file.cutHeight > 0 || file.quality !== 1)) {
require(['compressor'], function (Compressor) {
let options = {quality: file.quality, resize: 'cover'};
if (file.cutWidth) options.width = file.cutWidth;
if (file.cutHeight) options.height = file.cutHeight;
if (file.maxWidth) options.maxWidth = file.maxWidth;
if (file.maxHeight) options.maxHeight = file.maxHeight;
new Compressor(file, Object.assign(options, {
success(blob) {
blob.index = file.index, blob.notify = file.notify, blob.path = file.path, files[index] = blob;
that.hash(files[index]).then(function (file) {
that.event('upload.hash', file).request(file, done);
});
}, error: function () {
that.event('upload.error', {file: file}, file, '{:lang("图片压缩失败!")}');
}
}));
});
} else {
that.hash(file).then(function (file) {
that.event('upload.hash', file).request(file, done);
});
}
});
};
// 文件上传
Adapter.prototype.request = function (file, done) {
let that = this, data = {key: file.xkey, safe: that.option.safe, uptype: that.option.type};
data.size = file.size, data.name = file.name, data.hash = file.xmd5, data.mime = file.type, data.xext = file.xext;
jQuery.ajax("{:url('admin/api.upload/state',[],false,true)}", {
data: data, method: 'post', success: function (ret) {
file.id = ret.data.id || 0, file.xurl = ret.data.url;
file.xsafe = ret.data.safe, file.xpath = ret.data.key, file.xtype = ret.data.uptype;
if (parseInt(ret.code) === 404) {
let uploader = {};
uploader.uptype = ret.data.uptype;
uploader.url = ret.data.server;
uploader.head = {};
uploader.form = new FormData();
uploader.form.append('key', ret.data.key);
uploader.form.append('safe', ret.data.safe);
uploader.form.append('uptype', ret.data.uptype);
if (ret.data.uptype === 'qiniu') {
uploader.form.append('token', ret.data.token);
} else if (ret.data.uptype === 'alist') {
uploader.type = 'put';
uploader.head['file-path'] = ret.data['filepath'];
uploader.head['authorization'] = ret.data['authorization'];
uploader.form = new FormData();
} else if (ret.data.uptype === 'alioss') {
uploader.form.append('policy', ret.data['policy']);
uploader.form.append('signature', ret.data['signature']);
uploader.form.append('OSSAccessKeyId', ret.data['OSSAccessKeyId']);
uploader.form.append('success_action_status', '200');
uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
} else if (ret.data.uptype === 'txcos') {
uploader.form.append('q-ak', ret.data['q-ak']);
uploader.form.append('policy', ret.data['policy']);
uploader.form.append('q-key-time', ret.data['q-key-time']);
uploader.form.append('q-signature', ret.data['q-signature']);
uploader.form.append('q-sign-algorithm', ret.data['q-sign-algorithm']);
uploader.form.append('success_action_status', '200');
uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
} else if (ret.data.uptype === 'upyun') {
uploader.form.delete('key');
uploader.form.delete('safe');
uploader.form.delete('uptype');
uploader.form.append('save-key', ret.data['key']);
uploader.form.append('policy', ret.data['policy']);
uploader.form.append('authorization', ret.data['authorization']);
uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
}
uploader.form.append('file', file, file.name), jQuery.ajax({
xhrFields: {withCredentials: ret.data.uptype === 'local'}, headers: uploader.head, url: uploader.url, data: uploader.form, type: uploader.type || 'post', xhr: function (xhr) {
xhr = new XMLHttpRequest();
return xhr.upload.addEventListener('progress', function (event) {
file.xtotal = event.total, file.xloaded = event.loaded || 0;
that.progress((file.xloaded / file.xtotal * 100).toFixed(2), file)
}), xhr;
}, contentType: false, error: function () {
that.event('upload.error', {file: file}, file, '{:lang("上传接口异常!")}');
}, processData: false, success: function (ret) {
// 兼容数据格式
if (typeof ret === 'string' && ret.length > 0) try {
ret = JSON.parse(ret) || ret;
} catch (e) {
console.log(e)
}
if (typeof ret !== 'object') {
ret = {code: 1, url: file.xurl, info: '{:lang("文件上传成功!")}'};
}
/*! 检查单个文件上传返回的结果 */
if (typeof ret === 'object' && ret.code < 1) {
that.event('upload.error', {file: file}, file, ret.info || '{:lang("文件上传失败!")}');
} else if (uploader.uptype === 'alist' && parseInt(ret.code) !== 200) {
that.event('upload.error', {file: file}, file, ret.message || '{:lang("文件上传失败!")}');
} else {
that.done(ret, file.index, file, done, '{:lang("文件上传成功!")}');
}
}
});
} else if (parseInt(ret.code) === 200) {
(file.xurl = ret.data.url), that.progress('100.00', file);
that.done({code: 1, url: file.xurl, info: file.xstats, data: {code: 200, url: file.xurl}}, file.index, file, done, '{:lang("文件秒传成功!")}');
} else {
that.event('upload.error', {file: file}, file, ret.info || ret.error.message || '{:lang("文件上传出错!")}');
}
}
});
};
// 上传进度
Adapter.prototype.progress = function (number, file) {
this.event('upload.progress', {number: number, file: file});
if (file.notify) file.notify.setProgress(number);
};
// 上传结果
Adapter.prototype.done = function (ret, idx, file, done, message) {
/*! 检查单个文件上传返回的结果 */
if (ret.code < 1) return $.msg.tips(ret.info || '{:lang("文件上传失败!")}');
if (typeof file.xurl !== 'string') return $.msg.tips('{:lang("无效的文件上传对象!")}');
jQuery.post("{:url('admin/api.upload/done',[],false,true)}", {id: file.id, hash: file.xmd5});
/*! 单个文件上传成功结果处理 */
if (typeof done === 'function') {
done.call(this.option.elem, file.xurl, this.files['id']);
} else if (this.option.mult < 1 && this.option.elem.data('input')) {
$(this.option.elem.data('input')).val(file.xurl).trigger('change', file);
}
// 文件上传成功事件
this.event('push', file.xurl).event('upload.done', {file: file, data: ret}, file, message);
/*! 所有文件上传完成后结果处理 */
if (this.count.success + this.count.error >= this.count.total) {
this.option.hide || $.msg.close(this.loader);
if (this.option.mult > 0 && this.option.elem.data('input')) {
let urls = this.option.elem.data('input').value || [];
if (typeof urls === 'string') urls = urls.split('|');
for (let i in this.files) urls.push(this.files[i].xurl);
$(this.option.elem.data('input')).val(urls.join('|')).trigger('change', this.files);
}
this.event('upload.complete', {file: this.files}, file).init().uploader && this.uploader.reload();
}
};
/*! 触发事件过程 */
Adapter.prototype.event = function (name, data, file, message) {
if (name === 'upload.error') {
this.count.error++, file.xstate = -1, file.xstats = message;
if (file.notify) file.notify.setError(message || file.xstats || '');
} else if (name === 'upload.done') {
this.count.success++, file.xstate = 1, file.xstats = message;
if (file.notify) file.notify.setSuccess(message || file.xstats || '')
}
if (this.option.elem) {
this.option.elem.triggerHandler(name, data);
if (this.option.input) this.option.input.triggerHandler(name, data);
}
return this;
};
/**
* 计算文件 HASH 值
* @param {File} file 文件对象
* @return {Promise}
*/
Adapter.prototype.hash = function (file) {
let defer = jQuery.Deferred();
file.xext = file.name.indexOf('.') > -1 ? file.name.split('.').pop() : 'tmp';
/*! 兼容不能计算文件 HASH 的情况 */
let IsDate = '{$nameType|default=""}'.indexOf('date') > -1;
if (!window.FileReader || IsDate) return jQuery.when((function (xmd5, chars) {
while (xmd5.length < 32) xmd5 += chars.charAt(Math.floor(Math.random() * chars.length));
return SetFileXdata(file, xmd5, 6), defer.promise();
})(layui.util.toDateString(Date.now(), 'yyyyMMddHHmmss-'), '0123456789'));
/*! 读取文件并计算 HASH 值 */
return new LoadNextChunk(file).ReadAsChunk();
function SetFileXdata(file, xmd5, slice) {
file.xmd5 = xmd5, file.xstate = 0, file.xstats = '';
file.xkey = file.xmd5.substring(0, slice || 2) + '/' + file.xmd5.substring(slice || 2) + '.' + file.xext;
if (file.path) file.xkey = file.path + '/' + file.xkey;
return defer.resolve(file, file.xmd5, file.xkey), file;
}
function LoadNextChunk(file) {
let that = this, reader = new FileReader(), spark = new SparkMD5.ArrayBuffer();
let slice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
this.chunkIdx = 0, this.chunkSize = 2097152, this.chunkTotal = Math.ceil(file.size / this.chunkSize);
reader.onload = function (event) {
spark.append(event.target.result);
++that.chunkIdx < that.chunkTotal ? that.ReadAsChunk() : SetFileXdata(file, spark.end());
}, reader.onerror = function () {
defer.reject();
}, this.ReadAsChunk = function () {
this.start = that.chunkIdx * that.chunkSize;
this.loaded = this.start + that.chunkSize >= file.size ? file.size : this.start + that.chunkSize;
reader.readAsArrayBuffer(slice.call(file, this.start, this.loaded));
defer.notify(file, (this.loaded / file.size * 100).toFixed(2));
return defer.promise();
};
}
};
return UploadAdapter;
/**
* 上传状态提示扩展插件
* @param {File} file 文件对象
* @constructor
*/
function NotifyExtend(file) {
let that = this, message = "{:lang('上传进度 %s', ['<span data-upload-progress>0%</span>'])}";
this.notify = Notify.notify({width: 260, title: file.name, showProgress: true, description: message, type: 'default', position: 'top-right', closeTimeout: 0});
this.$elem = $(this.notify.notification.nodes);
this.$elem.find('.growl-notification__progress').addClass('is-visible');
this.$elem.find('.growl-notification__progress-bar').addClass('transition');
this.setProgress = function (number) {
this.$elem.find('[data-upload-progress]').html(number + '%');
this.$elem.find('.growl-notification__progress-bar').css({width: number + '%'});
return this;
}, this.setError = function (message) {
this.$elem.find('.growl-notification__desc').html(message || '{:lang("文件上传失败!")}');
this.$elem.removeClass('growl-notification--default').addClass('growl-notification--error')
return this.close();
}, this.setSuccess = function (message) {
this.setProgress('100.00');
this.$elem.find('.growl-notification__desc').html(message || '{:lang("文件上传成功!")}');
this.$elem.removeClass('growl-notification--default').addClass('growl-notification--success');
return this.close();
}, this.close = function (timeout) {
return setTimeout(function () {
that.notify.close();
}, timeout || 2000), this;
};
}
});

View File

@@ -0,0 +1,161 @@
<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>

View File

@@ -0,0 +1,143 @@
{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}

View File

@@ -0,0 +1,76 @@
{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}

View File

@@ -0,0 +1,45 @@
<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">
<input name="title" value="{$get.title|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="desc" value="{$get.desc|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">&#xe615;</i> {:lang('搜 索')}</button>
</div>
</form>
</fieldset>

View File

@@ -0,0 +1,68 @@
<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>数据类型</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">请选择数据类型,数据创建后不能再次修改哦 ~</p>
<div id="DataTypeInput" class="layui-hide relative">
<input class="layui-input" maxlength="20" name="type" required vali-name="数据类型" placeholder="请输入数据类型" value="{$vo.type|default=''}">
<p class="help-block">请输入新的数据类型,数据创建后不能再次修改哦 ~</p>
</div>
</div>
<label class="layui-form-item relative block">
<span class="help-label"><b>数据编码</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="请输入数据编码">
{else}
<input maxlength="100" class="layui-input" name="code" value='{$vo.code|default=""}' required vali-name="数据编码" placeholder="请输入数据编码">
{/if}
<span class="help-block">请输入新的数据编码,数据创建后不能再次修改,同种数据类型的数据编码不能出现重复 ~</span>
</label>
<label class="layui-form-item relative block">
<span class="help-label"><b>数据名称</b>Data Name</span>
<input maxlength="500" class="layui-input" name="name" value='{$vo.name|default=""}' required vali-name="数据名称" placeholder="请输入数据名称">
<span class="help-block">请输入当前数据名称,请尽量保持名称的唯一性,数据名称尽量不要出现重复 ~</span>
</label>
<label class="layui-form-item relative block">
<span class="help-label"><b>数据内容</b>Data Content</span>
<textarea name="content" class="layui-textarea" placeholder="请输入数据内容">{$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'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
</div>
</form>

View File

@@ -0,0 +1,86 @@
{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'>添加数据</button>
<!--{/if}-->
<!--{if auth("remove")}-->
<button data-table-id="BaseTable" data-action='{:url("remove")}' data-rule="id#{id}" data-confirm="确定要批量删除数据吗?" class='layui-btn layui-btn-sm layui-btn-primary'>批量删除</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: '数据编码', width: '20%', align: 'left'},
{field: 'name', title: '数据名称', width: '30%', align: 'left'},
{field: 'status', title: '数据状态', minWidth: 110, align: 'center', templet: '#StatusSwitchTpl'},
{field: 'create_at', title: '创建时间', minWidth: 170, align: 'center', sort: true},
{toolbar: '#toolbar', align: 'center', minWidth: 150, title: '数据操作', 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="已激活|已禁用" lay-filter="StatusSwitch" {{-d.status>0?'checked':''}}>
<!--{else}-->
{{-d.status ? '<b class="color-green">已启用</b>' : '<b class="color-red">已禁用</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="编辑数据" data-modal='{:url("edit")}?id={{d.id}}'> </a>
<!--{/if}-->
<!--{if auth("remove")}-->
<a class="layui-btn layui-btn-danger layui-btn-sm" data-confirm="确定要删除数据吗?" data-action="{:url('remove')}" data-value="id#{{d.id}}"> </a>
<!--{/if}-->
</script>
{/block}

View File

@@ -0,0 +1,42 @@
<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">&#xe615;</i> {:lang('搜 索')}</button>
</div>
</form>

View File

@@ -0,0 +1,234 @@
{extend name="main"}
{block name="button"}
<!--{if isset($super) and $super}-->
<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}

View File

@@ -0,0 +1,49 @@
<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>

View File

@@ -0,0 +1,98 @@
<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>

View File

@@ -0,0 +1,81 @@
<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>

View File

@@ -0,0 +1,51 @@
<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>

View File

@@ -0,0 +1,97 @@
<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>

View File

@@ -0,0 +1,96 @@
<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>

View File

@@ -0,0 +1,81 @@
<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>

View File

@@ -0,0 +1,129 @@
<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>登录表单标题</b>Login Name</span>
<input name="login_name" required placeholder="请输入登录页面的表单标题" vali-name="登录标题" 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>后台登录入口</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="登录入口" placeholder="请输入后台登录入口" 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>后台默认配色</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">后台登录入口是由英文字母开头,且不能有相同名称的模块,设置之后原地址不能继续访问,请谨慎配置 ~</span>
</div>
</div>
<div class="layui-form-item margin-bottom-5">
<div class="help-label"><b>登录背景图片</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>JWT 接口密钥</b>Jwt Key</div>
<label class="relative block label-required-null">
<input class="layui-input" pattern=".{32}" maxlength="32" required vali-name="接口密钥" placeholder="请输入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">
请输入 <span>32</span> <span>JWT</span> 接口密钥,在使用 <span>JWT</span> 接口时需要使用此密钥进行加密及签名!
</div>
</div>
<div class="layui-form-item margin-bottom-5">
<div class="help-label label-required-prev"><b>浏览器小图标</b>Browser Icon</div>
<div class="relative block label-required-null">
<input class="layui-input" required pattern="url" vali-name="图标文件" data-tips-image data-tips-hover placeholder="请上传浏览器图标" 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">
建议上传 <span>128x128</span> <span>256x256</span> <span>JPG</span>,<span>PNG</span>,<span>JPEG</span> 图片,保存后会自动生成 <span>48x48</span> <span>ICO</span> 文件 ~
</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>网站名称</b>Site Name</span>
<input name="site_name" required placeholder="请输入网站名称" vali-name="网站名称" value="{:sysconf('site_name')}" class="layui-input">
<span class="help-block">网站名称将显示在浏览器的标签上 ~</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>后台程序名称</b>App Name</span>
<input name="app_name" required placeholder="请输入程序名称" vali-name="程序名称" value="{:sysconf('app_name')}" class="layui-input">
<span class="help-block">管理程序名称显示在后台左上标题处 ~</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>后台程序版本</b>App Version</span>
<input name="app_version" placeholder="请输入程序版本" value="{:sysconf('app_version')}" class="layui-input">
<span class="help-block">管理程序版本显示在后台左上标题处 ~</span>
</label>
</div>
<div class="layui-col-xs4 padding-top-0 padding-bottom-0">
<label class="relative block">
<span class="help-label"><b>公安安备号</b>Beian</span>
<input name="beian" placeholder="请输入公安安备号" 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>网站备案号</b>Miitbeian</span>
<input name="miitbeian" placeholder="请输入网站备案号" 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>网站版权信息</b>Copyright</span>
<input name="site_copy" required placeholder="请输入版权信息" vali-name="版权信息" value="{:sysconf('site_copy')}" class="layui-input">
</label>
</div>
<div class="layui-col-xs12 help-block padding-top-0">
网站备案号和公安备案号可以在<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">保存配置</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</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>

568
app/admin/view/error.php Normal file
View File

@@ -0,0 +1,568 @@
<?php
if (!function_exists('parse_padding')) {
function parse_padding($source)
{
$length = strlen(strval(count($source['source']) + $source['first']));
return 40 + ($length - 1) * 8;
}
}
if (!function_exists('parse_class')) {
function parse_class($name): string
{
$names = explode('\\', $name);
return '<abbr title="' . $name . '">' . end($names) . '</abbr>';
}
}
if (!function_exists('parse_file')) {
function parse_file($file, $line): string
{
return '<a class="toggle" title="' . "{$file} line {$line}" . '">' . basename($file) . " line {$line}" . '</a>';
}
}
if (!function_exists('parse_args')) {
function parse_args($args): string
{
$result = [];
foreach ($args as $key => $item) {
switch (true) {
case is_object($item):
$value = sprintf('<em>object</em>(%s)', parse_class(get_class($item)));
break;
case is_array($item):
if (count($item) > 3) {
$value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3)));
} else {
$value = sprintf('[%s]', parse_args($item));
}
break;
case is_string($item):
if (strlen($item) > 20) {
$value = sprintf(
'\'<a class="toggle" title="%s">%s...</a>\'',
htmlentities($item),
htmlentities(substr($item, 0, 20))
);
} else {
$value = sprintf("'%s'", htmlentities($item));
}
break;
case is_int($item):
case is_float($item):
$value = $item;
break;
case is_null($item):
$value = '<em>null</em>';
break;
case is_bool($item):
$value = '<em>' . ($item ? 'true' : 'false') . '</em>';
break;
case is_resource($item):
$value = '<em>resource</em>';
break;
default:
$value = htmlentities(str_replace("\n", '', var_export(strval($item), true)));
break;
}
$result[] = is_int($key) ? $value : "'{$key}' => {$value}";
}
return implode(', ', $result);
}
}
if (!function_exists('echo_value')) {
function echo_value($val)
{
if (is_array($val) || is_object($val)) {
echo htmlentities(json_encode($val, JSON_PRETTY_PRINT));
} elseif (is_bool($val)) {
echo $val ? 'true' : 'false';
} elseif (is_scalar($val)) {
echo htmlentities($val);
} else {
echo 'Resource';
}
}
}
?>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>系统发生错误</title>
<meta name="robots" content="noindex,nofollow"/>
<style>
/* Base */
body {
color: #333;
font: 16px Verdana, "Helvetica Neue", helvetica, Arial, 'Microsoft YaHei', sans-serif;
margin: 0;
padding: 0 20px 20px;
}
h1 {
margin: 10px 0 0;
font-size: 28px;
font-weight: 500;
line-height: 32px;
}
h2 {
color: #4288ce;
font-weight: 400;
padding: 6px 0;
margin: 6px 0 0;
font-size: 18px;
border-bottom: 1px solid #eee;
}
h3 {
margin: 12px;
font-size: 16px;
font-weight: bold;
}
abbr {
cursor: help;
text-decoration: underline;
text-decoration-style: dotted;
}
a {
color: #868686;
cursor: pointer;
}
a:hover {
text-decoration: underline;
}
.line-error {
background: #f8cbcb;
}
.echo table {
width: 100%;
}
.echo pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border: 0;
border-radius: 3px;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.echo pre > pre {
padding: 0;
margin: 0;
}
/* Exception Info */
.exception {
margin-top: 20px;
}
.exception .message {
padding: 12px;
border: 1px solid #ddd;
border-bottom: 0 none;
line-height: 18px;
font-size: 16px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
font-family: Consolas, "Liberation Mono", Courier, Verdana, "微软雅黑", serif;
}
.exception .code {
float: left;
text-align: center;
color: #fff;
margin-right: 12px;
padding: 16px;
border-radius: 4px;
background: #999;
}
.exception .source-code {
padding: 6px;
border: 1px solid #ddd;
background: #f9f9f9;
overflow-x: auto;
}
.exception .source-code pre {
margin: 0;
}
.exception .source-code pre ol {
margin: 0;
color: #4288ce;
display: inline-block;
min-width: 100%;
box-sizing: border-box;
font-size: 14px;
font-family: "Century Gothic", Consolas, "Liberation Mono", Courier, Verdana, serif;
padding-left: <?php echo (isset($source) && ! empty($source)) ? parse_padding($source): 40;?> px;
}
.exception .source-code pre li {
border-left: 1px solid #ddd;
height: 18px;
line-height: 18px;
}
.exception .source-code pre code {
color: #333;
height: 100%;
display: inline-block;
border-left: 1px solid #fff;
font-size: 14px;
font-family: Consolas, "Liberation Mono", Courier, Verdana, "微软雅黑", serif;
}
.exception .trace {
padding: 6px;
border: 1px solid #ddd;
border-top: 0 none;
line-height: 16px;
font-size: 14px;
font-family: Consolas, "Liberation Mono", Courier, Verdana, "微软雅黑", serif;
}
.exception .trace h2:hover {
text-decoration: underline;
cursor: pointer;
}
.exception .trace ol {
margin: 12px;
}
.exception .trace ol li {
padding: 2px 4px;
}
.exception div:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
/* Exception Variables */
.exception-var table {
width: 100%;
margin: 12px 0;
box-sizing: border-box;
table-layout: fixed;
word-wrap: break-word;
}
.exception-var table caption {
text-align: left;
font-size: 16px;
font-weight: bold;
padding: 6px 0;
}
.exception-var table caption small {
font-weight: 300;
display: inline-block;
margin-left: 10px;
color: #ccc;
}
.exception-var table tbody {
font-size: 13px;
font-family: Consolas, "Liberation Mono", Courier, "微软雅黑", serif;
}
.exception-var table td {
padding: 0 6px;
vertical-align: top;
word-break: break-all;
}
.exception-var table td:first-child {
width: 28%;
font-weight: bold;
white-space: nowrap;
}
.exception-var table td pre {
margin: 0;
}
/* Copyright Info */
.copyright {
margin-top: 24px;
padding: 12px 0;
border-top: 1px solid #eee;
}
/* SPAN elements with the classes below are added by prettyprint. */
pre.prettyprint .pln {
color: #000
}
/* plain text */
pre.prettyprint .str {
color: #080
}
/* string content */
pre.prettyprint .kwd {
color: #008
}
/* a keyword */
pre.prettyprint .com {
color: #800
}
/* a comment */
pre.prettyprint .typ {
color: #606
}
/* a type name */
pre.prettyprint .lit {
color: #066
}
/* a literal value */
/* punctuation, lisp open bracket, lisp close bracket */
pre.prettyprint .pun, pre.prettyprint .opn, pre.prettyprint .clo {
color: #660
}
pre.prettyprint .tag {
color: #008
}
/* a markup tag name */
pre.prettyprint .atn {
color: #606
}
/* a markup attribute name */
pre.prettyprint .atv {
color: #080
}
/* a markup attribute value */
pre.prettyprint .dec, pre.prettyprint .var {
color: #606
}
/* a declaration; a variable name */
pre.prettyprint .fun {
color: red
}
/* a function name */
</style>
</head>
<body>
<?php if (\think\facade\App::isDebug()) { ?>
<?php foreach ($traces as $index => $trace) { ?>
<div class="exception">
<div class="message">
<div class="info">
<div>
<h2><?php echo "#{$index} [{$trace['code']}]" . sprintf('%s in %s', parse_class($trace['name']), parse_file($trace['file'], $trace['line'])); ?></h2>
</div>
<div><h1><?php echo nl2br(htmlentities($trace['message'])); ?></h1></div>
</div>
</div>
<?php if (!empty($trace['source'])) { ?>
<div class="source-code">
<pre class="prettyprint lang-php">
<ol start="<?php echo $trace['source']['first']; ?>"><!--<?php foreach ((array)$trace['source']['source'] as $key => $value) { ?>--><li class="line-<?php echo " {$index}-" . ($key + $trace['source']['first']) . ($trace['line'] === $key + $trace['source']['first'] ? ' line-error' : ''); ?>"><code><?php echo htmlentities($value); ?></code></li><!--<?php } ?>--></ol>
</pre>
</div>
<?php } ?>
<div class="trace">
<h2 data-expand="<?php echo 0 === $index ? '1' : '0'; ?>">Call Stack</h2>
<ol>
<li><?php echo sprintf('in %s', parse_file($trace['file'], $trace['line'])); ?></li>
<?php foreach ((array)$trace['trace'] as $value) { ?>
<li>
<?php
// Show Function
if ($value['function']) {
echo sprintf('at %s%s%s(%s)', isset($value['class']) ? parse_class($value['class']) : '', $value['type'] ?? '', $value['function'], isset($value['args']) ? parse_args($value['args']) : '');
}
// Show line
if (isset($value['file']) && isset($value['line'])) {
echo sprintf(' in %s', parse_file($value['file'], $value['line']));
}
?>
</li>
<?php } ?>
</ol>
</div>
</div>
<?php } ?>
<?php } else { ?>
<div class="exception">
<div class="info"><h1><?php echo htmlentities(isset($message) ? $message : ''); ?></h1></div>
</div>
<?php } ?>
<?php if (!empty($datas)) { ?>
<div class="exception-var">
<h2>Exception Datas</h2>
<?php foreach ((array)$datas as $label => $value) { ?>
<table>
<?php if (empty($value)) { ?>
<caption><?php echo $label; ?><small>empty</small></caption>
<?php } else { ?>
<caption><?php echo $label; ?></caption>
<tbody>
<?php foreach ((array)$value as $key => $val) { ?>
<tr>
<td><?php echo htmlentities($key); ?></td>
<td><?php echo_value($val); ?></td>
</tr>
<?php } ?>
</tbody>
<?php } ?>
</table>
<?php } ?>
</div>
<?php } ?>
<?php if (!empty($tables)) { ?>
<div class="exception-var">
<h2>Environment Variables</h2>
<?php foreach ((array)$tables as $label => $value) { ?>
<table>
<?php if (empty($value)) { ?>
<caption><?php echo $label; ?><small>empty</small></caption>
<?php } else { ?>
<caption><?php echo $label; ?></caption>
<tbody>
<?php foreach ((array)$value as $key => $val) { ?>
<tr>
<td><?php echo htmlentities($key); ?></td>
<td><?php echo_value($val); ?></td>
</tr>
<?php } ?>
</tbody>
<?php } ?>
</table>
<?php } ?>
</div>
<?php } ?>
<?php if (\think\facade\App::isDebug()) { ?>
<script>
function $(selector, node) {
var elements;
node = node || document;
if (document.querySelectorAll) {
elements = node.querySelectorAll(selector);
} else {
switch (selector.substr(0, 1)) {
case '#':
elements = [node.getElementById(selector.substr(1))];
break;
case '.':
if (document.getElementsByClassName) {
elements = node.getElementsByClassName(selector.substr(1));
} else {
elements = get_elements_by_class(selector.substr(1), node);
}
break;
default:
elements = node.getElementsByTagName();
}
}
return elements;
function get_elements_by_class(search_class, node, tag) {
var elements = [], eles,
pattern = new RegExp('(^|\\s)' + search_class + '(\\s|$)');
node = node || document;
tag = tag || '*';
eles = node.getElementsByTagName(tag);
for (var i = 0; i < eles.length; i++) {
if (pattern.test(eles[i].className)) {
elements.push(eles[i])
}
}
return elements;
}
}
$.getScript = function (src, func) {
var script = document.createElement('script');
script.async = 'async';
script.src = src;
script.onload = func || function () {
};
$('head')[0].appendChild(script);
}
;(function () {
var files = $('.toggle');
var ol = $('ol', $('.prettyprint')[0]);
var li = $('li', ol[0]);
// 短路径和长路径变换
for (var i = 0; i < files.length; i++) {
files[i].ondblclick = function () {
var title = this.title;
this.title = this.innerHTML;
this.innerHTML = title;
}
}
(function () {
var expand = function (dom, expand) {
var ol = $('ol', dom.parentNode)[0];
expand = undefined === expand ? dom.attributes['data-expand'].value === '0' : undefined;
if (expand) {
dom.attributes['data-expand'].value = '1';
ol.style.display = 'none';
dom.innerText = 'Call Stack (展开)';
} else {
dom.attributes['data-expand'].value = '0';
ol.style.display = 'block';
dom.innerText = 'Call Stack (折叠)';
}
};
var traces = $('.trace');
for (var i = 0; i < traces.length; i++) {
var h2 = $('h2', traces[i])[0];
expand(h2);
h2.onclick = function () {
expand(this);
};
}
})();
$.getScript('//cdn.bootcss.com/prettify/r298/prettify.min.js', function () {
prettyPrint();
});
})();
</script>
<?php } ?>
</body>
</html>

View File

@@ -0,0 +1,40 @@
<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>

View File

@@ -0,0 +1,64 @@
{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}

View File

@@ -0,0 +1,58 @@
<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">&#xe615;</i> {:lang('搜 索')}</button>
</div>
</form>
</fieldset>

32
app/admin/view/full.html Normal file
View File

@@ -0,0 +1,32 @@
<!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="apple-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')}">
{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>

View File

@@ -0,0 +1,50 @@
<div class="layui-side">
<a class="layui-side-target" data-target-menu-type></a>
<a class="layui-logo layui-elip" href="{:sysuri('@')}" title="{:sysconf('app_name')}">
<span class="headimg headimg-no headimg-xs" data-lazy-src="{:sysconf('site_icon')}"></span>
<span class="headtxt">{:sysconf('app_name')} {if sysconf('app_version')}<sup>{:sysconf('app_version')}</sup>{/if}</span>
</a>
<div class="layui-side-scroll">
<div class="layui-side-icon">
{foreach $menus as $one}
<div>
<a data-menu-node="m-{$one.id}" data-open="{$one.url}" data-target-tips="{$one.title|default=''}">
{notempty name='one.icon'}<i class="{$one.icon|default=''}"></i>{/notempty}
<span>{$one.title|default=''}</span>
</a>
</div>
{/foreach}
</div>
<div class="layui-side-tree">
{foreach $menus as $one}{notempty name='one.sub'}
<ul class="layui-nav layui-nav-tree layui-hide" data-menu-layout="m-{$one.id}">
{foreach $one.sub as $two}{empty name='two.sub'}
<li class="layui-nav-item">
<a data-target-tips="{$two.title}" data-menu-node="m-{$one.id}-{$two.id}" data-open="{$two.url}">
<span class='nav-icon {$two.icon|default="layui-icon layui-icon-senior"}'></span>
<span class="nav-text">{$two.title|default=''}</span>
</a>
</li>
{else}
<li class="layui-nav-item" data-submenu-layout='m-{$one.id}-{$two.id}'>
<a data-target-tips="{$two.title}">
<span class='nav-icon layui-hide {$two.icon|default="layui-icon layui-icon-triangle-d"}'></span>
<span class="nav-text">{$two.title|default=''}</span>
</a>
<dl class="layui-nav-child">
{foreach $two.sub as $thr}
<dd>
<a data-target-tips="{$thr.title}" data-open="{$thr.url}" data-menu-node="m-{$one.id}-{$two.id}-{$thr.id}">
<span class='nav-icon {$thr.icon|default="layui-icon layui-icon-senior"}'></span>
<span class="nav-text">{$thr.title|default=''}</span>
</a>
</dd>
{/foreach}
</dl>
</li>
{/empty}{/foreach}
</ul>
{/notempty}{/foreach}
</div>
</div>
</div>

View File

@@ -0,0 +1,49 @@
<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">
{if isset($super) and $super}
<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>
{/if}
<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}
{if isset($super) and $super}
<dd lay-unselect><a data-width="520px" data-modal="{:sysuri('admin/index/theme')}"><i class="layui-icon layui-icon-theme"></i> {:lang('配色方案')}</a></dd>
{/if}
<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>

View File

@@ -0,0 +1,62 @@
<!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="apple-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')}">
{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 layui-layout-theme-{$theme|default='default'}">
{block name='body'}
<div class="layui-layout layui-layout-admin layui-layout-left-hide">
<!-- 左则菜单 开始 -->
{include file="index/index-left"}
<!-- 左则菜单 结束 -->
<!-- 顶部菜单 开始 -->
{include file='index/index-top'}
<!-- 顶部菜单 结束 -->
<!-- 主体内容 开始 -->
<div class="layui-body">
<div class="think-page-body">
{block name='content'}{/block}
</div>
<!-- 页面加载动画 -->
<div class="think-page-loader layui-hide">
<div class="loader"></div>
</div>
</div>
<!-- 主体内容 结束 -->
</div>
<!-- 加载动画 开始 -->
<div class="think-page-loader">
<div class="loader"></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>

View File

@@ -0,0 +1,36 @@
<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>

View File

@@ -0,0 +1,57 @@
{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}

23
app/admin/view/main.html Normal file
View File

@@ -0,0 +1,23 @@
<div class="layui-card">
{block name='style'}{/block}
{block name='header'}
{notempty name='title'}
<div class="layui-card-header">
<span class="layui-icon font-s10 color-desc margin-right-5">&#xe65b;</span>{$title|lang}
<div class="pull-right">{block name='button'}{/block}</div>
</div>
{/notempty}
{/block}
<div class="layui-card-line"></div>
<div class="layui-card-body">
<div class="layui-card-html">
{notempty name='showErrorMessage'}
<div class="think-box-notify" type="error">
<b>{:lang('系统提示:')}</b><span>{$showErrorMessage|raw}</span>
</div>
{/notempty}
{block name='content'}{/block}
</div>
</div>
{block name='script'}{/block}
</div>

View File

@@ -0,0 +1,97 @@
<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>必选</b>,请选择上级菜单或顶级菜单 ( 目前最多支持三级菜单 )</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="菜单名称" placeholder="请输入菜单名称" class="layui-input">
<p class="help-block"><b>必选</b>,请填写菜单名称 ( 如:系统管理 ),建议字符不要太长,一般 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="菜单链接" placeholder="请输入菜单链接" value="{$vo.url|default='#'}" class="layui-input">
<p class="help-block">
<b>必选</b>,请填写链接地址或选择系统节点 ( https://domain.com/admin/user/index.html admin/user/index )
<br>当填写链接地址时,以下面的 “权限节点” 来判断菜单自动隐藏或显示,注意未填写 “权限节点” 时将不会隐藏该菜单哦
</p>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">{:lang('链接参数')}</label>
<div class="layui-input-block">
<input name="params" placeholder="请输入链接参数" value="{$vo.params|default=''}" class="layui-input">
<p class="help-block"><b>可选</b>,设置菜单链接的 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="请输入权限节点" value="{$vo.node|default=''}" class="layui-input">
<p class="help-block"><b>可选</b>,请填写系统权限节点 ( 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="请输入或选择图标" 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>可选</b>,设置菜单选项前置图标,目前支持 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>

View File

@@ -0,0 +1,114 @@
{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}
<li class="layui-this" data-open="{:url('index')}?type={$k}">{$v}</li>
{else}
<li data-open="{:url('index')}?type={$k}">{$v}</li>
{/if}{/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"}'},
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="确定要删除菜单吗?" data-action="{:url('remove')}" data-value="id#{{d.sps}}"> </a>
{{# }else{ }}
<a class="layui-btn layui-btn-disabled layui-btn-sm"> </a>
{{# } }}
{/if}
<!-- {/if} -->
</script>
{/block}

View File

@@ -0,0 +1,48 @@
{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: 'request_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}

View File

@@ -0,0 +1,87 @@
<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">&#xe615;</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>

View File

@@ -0,0 +1,131 @@
{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=''}">&#xe633;</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}

View File

@@ -0,0 +1,45 @@
<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">&#xe615;</i> {:lang('搜 索')}</button>
</div>
</form>
</fieldset>

23
app/admin/view/table.html Normal file
View File

@@ -0,0 +1,23 @@
<div class="layui-card">
{block name='style'}{/block}
{block name='header'}
{notempty name='title'}
<div class="layui-card-header">
<span class="layui-icon font-s10 color-desc margin-right-5">&#xe65b;</span>{$title|lang}
<div class="pull-right">{block name='button'}{/block}</div>
</div>
{/notempty}
{/block}
<div class="layui-card-line"></div>
<div class="layui-card-body">
<div class="layui-card-table">
{notempty name='showErrorMessage'}
<div class="think-box-notify" type="error">
<b>{:lang('系统提示:')}</b><span>{$showErrorMessage|raw}</span>
</div>
{/notempty}
{block name='content'}{/block}
</div>
</div>
{block name='script'}{/block}
</div>

View File

@@ -0,0 +1,114 @@
<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">用户账号</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>登录账号</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="登录账号" placeholder="请输入登录账号" class="layui-input">
{/if}
<span class="help-block">登录账号不能少于4位字符创建后不能再次修改.</span>
</label>
</div>
<div class="layui-col-xs5">
<label class="block relative">
<span class="help-label"><b>用户名称</b>Nick Name</span>
<input name="nickname" value='{$vo.nickname|default=""}' required vali-name="用户名称" placeholder="请输入用户名称" class="layui-input">
<span class="help-block">用于区分用户数据的用户名称,请尽量不要重复.</span>
</label>
</div>
</div>
</fieldset>
{if !empty($bases) || !empty($auths)}
<fieldset class="layui-bg-gray">
<legend><b class="layui-badge think-bg-violet">用户权限</b></legend>
{if !empty($bases)}
<div class="layui-form-item">
<div class="help-label"><b>角色身份</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>访问权限</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">超级用户拥所有访问权限,不需要配置权限。</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">用户资料</b></legend>
<div class="layui-row layui-col-space15">
<div class="layui-col-xs4">
<label class="relative block">
<span class="help-label"><b>联系邮箱</b>Contact Email</span>
<input name="contact_mail" value='{$vo.contact_mail|default=""}' pattern="email" vali-name="联系邮箱" placeholder="请输入联系电子邮箱" class="layui-input">
<span class="color-desc">可选,请填写用户常用的电子邮箱</span>
</label>
</div>
<div class="layui-col-xs4">
<label class="relative block">
<span class="help-label"><b>联系手机</b>Contact Mobile</span>
<input type="tel" maxlength="11" name="contact_phone" value='{$vo.contact_phone|default=""}' pattern="phone" vali-name="联系手机" placeholder="请输入用户联系手机" class="layui-input">
<span class="color-desc">可选,请填写用户常用的联系手机号</span>
</label>
</div>
<div class="layui-col-xs4">
<label class="relative block">
<span class="help-label"><b>联系QQ</b>Contact QQ</span>
<input name="contact_qq" maxlength="11" value='{$vo.contact_qq|default=""}' pattern="qq" vali-name="联系QQ" placeholder="请输入常用的联系QQ" class="layui-input">
<span class="color-desc">可选,请填写用户常用的联系QQ号</span>
</label>
</div>
</div>
<label class="layui-form-item block relative margin-top-10">
<span class="help-label"><b>用户描述</b>User Remark</span>
<textarea placeholder="请输入用户描述" 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'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
</div>
</form>

View File

@@ -0,0 +1,116 @@
{extend name='table'}
{block name="button"}
{if isset($type) and $type eq 'index'}
<!--{if auth("add")}-->
<button data-modal='{:url("add")}' data-title="{:lang('添加用户')}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('添加用户')}</button>
<!--{/if}-->
<!--{if auth("state")}-->
<a data-confirm="确定要禁用这些用户吗?" data-table-id="UserTable" data-action="{:url('state')}" data-rule="id#{id};status#0" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量禁用')}</a>
<!--{/if}-->
{else}
<!--{if auth("state")}-->
<a data-confirm="确定要恢复这些账号吗?" data-table-id="UserTable" data-action="{:url('state')}" data-rule="id#{id};status#1" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量恢复')}</a>
<!--{/if}-->
<!--{if auth("remove")}-->
<a data-confirm="确定永久删除这些账号吗?" data-table-id="UserTable" data-action='{:url("remove")}' data-rule="id#{id}" class='layui-btn layui-btn-sm layui-btn-primary'>{:lang('批量删除')}</a>
<!--{/if}-->
{/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}
<li data-open="{:url('index')}?type={$k}" class="layui-this">{$v}</li>
{else}
<li data-open="{:url('index')}?type={$k}">{$v}</li>
{/if}{/foreach}
</ul>
<div class="layui-tab-content">
{include file='user/index_search'}
<table id="UserTable" data-url="{:sysuri('index')}" data-target-search="form.form-search"></table>
</div>
</div>
<script>
$(function () {
$('#UserTable').layTable({
even: true, height: 'full',
sort: {field: 'sort desc,id', type: 'desc'},
where: {type: '{$type|default="index"}'},
cols: [[
{checkbox: true, fixed: true},
{field: 'sort', title: '{:lang("排序权重")}', width: 100, align: 'center', sort: true, templet: '#SortInputTpl'},
{
field: 'headimg', title: '{:lang("头像")}', width: 60, align: 'center', templet: function (d) {
if (!d.headimg) return '-';
return layui.laytpl('<div class="headimg headimg-ss shadow-inset margin-0" data-tips-image data-tips-hover data-lazy-src="{{d.headimg}}"></div>').render(d);
}
},
{field: 'username', title: '{:lang("登录账号")}', minWidth: 100, align: 'center', templet: '<div>{{d.username||"-"}}</div>'},
{field: 'nickname', title: '{:lang("用户名称")}', minWidth: 100, align: 'center', templet: '<div>{{d.nickname||"-"}}</div>'},
/* {notempty name='bases'} */
{
field: 'usertype', title: '{:lang("角色身份")}', minWidth: 100, align: 'center', templet: function (d) {
d.userinfo = d.userinfo || {};
return d.userinfo.code ? (d.userinfo.name + ' ( ' + d.userinfo.code + ' ) ') : '-';
}
},
/* {/notempty} */
// {field: 'contact_mail', title: '联系邮箱', minWidth: 80, templet: '<div>{{d.contact_mail||"-"}}</div>'},
// {field: 'contact_phone', title: '联系电话', minWidth: 80, templet: '<div>{{d.contact_phone||"-"}}</div>'},
{field: 'status', title: '{:lang("使用状态")}', align: 'center', minWidth: 110, templet: '#StatusSwitchTpl'},
{field: 'login_num', title: '{:lang("登录次数")}', align: 'center', minWidth: 100, sort: true},
{field: 'login_at', title: '{:lang("最后登录")}', align: 'center', minWidth: 170, sort: true},
{field: 'create_at', title: '{:lang("创建时间")}', align: 'center', minWidth: 170, sort: true},
{toolbar: '#toolbar', title: '{:lang("操作面板")}', align: 'center', minWidth: 180, 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 () {
$('#UserTable').trigger('reload');
}); else {
$('#UserTable').trigger('reload')
}
return false;
}, false);
});
});
</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="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("edit")}-->
<a class="layui-btn layui-btn-sm" data-event-dbclick data-title="{:lang('编辑用户')}" data-modal='{:url("edit")}?id={{d.id}}'>{:lang('编 辑')}</a>
<!--{/if}-->
<!--{if auth("pass")}-->
<a class="layui-btn layui-btn-sm layui-btn-normal" data-title="{:lang('设置密码')}" data-modal='{:url("pass")}?id={{d.id}}'>{:lang('密 码')}</a>
<!--{/if}-->
{else}
<!--{if auth("edit")}-->
<a class="layui-btn 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-sm layui-btn-danger" data-confirm="{:lang('确定要永久删除吗?')}" data-action="{:url('remove')}" data-value="id#{{d.id}}">{:lang('删 除')}</a>
<!--{/if}-->
{/if}
</script>
{/block}

View File

@@ -0,0 +1,44 @@
<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">&#xe615;</i> {:lang('搜 索')}</button>
</div>
</form>

View File

@@ -0,0 +1,40 @@
<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>登录用户账号</b>Username</span>
<input disabled value='{$vo.username|default=""}' class="layui-input think-bg-gray">
<span class="help-block">登录用户账号创建后,不允许再次修改。</span>
</label>
<!--{if $verify}-->
<label class="layui-form-item relative block">
<span class="help-label"><b>旧的登录密码</b>Old Password</span>
<input type="password" autofocus name="oldpassword" value='' pattern="^\S{1,}$" required vali-name="验证密码" placeholder="请输入旧的登录密码" class="layui-input">
<span class="color-desc">请输入旧密码来验证修改权限,旧密码不限制格式。</span>
</label>
<!--{/if}-->
<label class="layui-form-item relative block">
<span class="help-label"><b>新的登录密码</b>New Password</span>
<input type="password" name="password" maxlength="32" pattern="^(?![\d]+$)(?![a-zA-Z]+$)(?![^\da-zA-Z]+$).{6,32}$" required vali-name="登录密码" placeholder="请输入新的登录密码" class="layui-input">
<span class="color-desc">密码必须包含大小写字母、数字、符号的任意两者组合。</span>
</label>
<label class="layui-form-item relative block">
<span class="help-label"><b>重复登录密码</b>Repeat Password</span>
<input type="password" name="repassword" maxlength="32" pattern="^(?![\d]+$)(?![a-zA-Z]+$)(?![^\da-zA-Z]+$).{6,32}$" required vali-name="重复密码" placeholder="请重复输入登录密码" class="layui-input">
<span class="color-desc">密码必须包含大小写字母、数字、符号的任意两者组合。</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'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
</div>
</form>