Pre Merge pull request !135 from sinfree/master

pull/135/MERGE
Gitee 2019-07-11 13:30:01 +08:00
commit b4aab35616
447 changed files with 42888 additions and 53501 deletions

View File

@ -1,3 +1,9 @@
{ {
"directory" : "public/assets/libs" "directory" : "public/assets/libs",
"ignoredDependencies": [
"file-saver",
"html2canvas",
"jspdf",
"jspdf-autotable"
]
} }

11
.env.sample 100644
View File

@ -0,0 +1,11 @@
[app]
debug = false
trace = false
[database]
hostname = 127.0.0.1
database = fastadmin
username = root
password = root
hostport = 3306
prefix = fa_

18
.gitignore vendored 100755 → 100644
View File

@ -1,11 +1,17 @@
/nbproject/ /nbproject/
/thinkphp/
/vendor/
/runtime/*
/addons/*
/application/admin/command/Install/*.lock
/public/assets/libs/
/public/assets/addons/*
/public/uploads/*
.idea .idea
composer.lock composer.lock
*.log *.log
*.css.map *.css.map
thinkphp !.gitkeep
vendor .env
runtime .svn
public/assets/libs/ .vscode
/application/admin/command/Install/*.lock
/public/uploads

View File

@ -1,42 +0,0 @@
sudo: false
language: php
branches:
only:
- stable
cache:
directories:
- $HOME/.composer/cache
before_install:
- composer self-update
install:
- composer install --no-dev --no-interaction --ignore-platform-reqs
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
- composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
- composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
script:
- php think unit
deploy:
provider: releases
api_key:
secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
file:
- ThinkPHP_Core.zip
- ThinkPHP_Full.zip
skip_cleanup: true
on:
tags: true

View File

@ -1,5 +1,4 @@
FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。 FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
===============
## **主要特性** ## **主要特性**
@ -7,52 +6,61 @@ FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
* 基于`Auth`验证的权限管理系统 * 基于`Auth`验证的权限管理系统
* 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置 * 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
* 支持单管理员多角色 * 支持单管理员多角色
* 支持目录和控制器结构一键生成权限节点 * 支持管理子级数据或个人数据
* 强大的一键生成功能
* 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单、回收站等
* 一键压缩打包JS和CSS文件一键CDN静态资源部署
* 一键生成控制器菜单和规则
* 一键生成API接口文档
* 完善的前端功能组件开发 * 完善的前端功能组件开发
* 基于`AdminLTE`二次开发 * 基于`AdminLTE`二次开发
* 基于`Bootstrap`开发自适应手机、平板、PC * 基于`Bootstrap`开发自适应手机、平板、PC
* 基于`RequireJS`进行JS模块管理按需加载 * 基于`RequireJS`进行JS模块管理按需加载
* 基于`Less`进行样式开发 * 基于`Less`进行样式开发
* 基于`Bower`进行前端组件包管理 * 基于`Bower`进行前端组件包管理
* 数据库表一键生成`CRUD`,包括控制器、模型、视图、JS、语言包 * 强大的插件扩展功能,在线安装卸载升级插件
* 一键压缩打包JS和CSS文件 * 通用的会员模块和API模块
* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
* 二级域名部署支持,同时域名支持绑定到插件
* 多语言支持,服务端及客户端支持 * 多语言支持,服务端及客户端支持
* 无缝整合又拍云上传功能 * 强大的第三方模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[博客](https://www.fastadmin.net/store/blog.html)、[知识付费问答](https://www.fastadmin.net/store/ask.html))
* 整合第三方短信接口(阿里云、腾讯云短信)
* 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能
* 第三方富文本编辑器支持(Summernote、Kindeditor、百度编辑器)
* 第三方登录(QQ、微信、微博)整合 * 第三方登录(QQ、微信、微博)整合
* Ucenter整合 * 第三方支付(微信、支付宝)无缝整合微信支持PC端扫码支付
* 丰富的插件应用市场
## **安装使用** ## **安装使用**
http://doc.fastadmin.net https://doc.fastadmin.net
## **在线演示** ## **在线演示**
http://demo.fastadmin.net https://demo.fastadmin.net
用户名admin 用户名admin
 123456  123456
提 示:演示站数据无法进行删除和修改,只能新增,完整体验请下载源码安装体验 提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
## **界面截图** ## **界面截图**
![控制台](https://git.oschina.net/uploads/images/2017/0411/113717_e99ff3e7_10933.png "控制台") ![控制台](https://gitee.com/uploads/images/2017/0411/113717_e99ff3e7_10933.png "控制台")
## **问题反馈** ## **问题反馈**
在使用中有任何问题,请使用以下联系方式联系我们 在使用中有任何问题,请使用以下联系方式联系我们
交流社区: http://forum.fastadmin.net 交流社区: https://forum.fastadmin.net
QQ群: [636393962](https://jq.qq.com/?_wv=1027&k=487PNBb) QQ群: [636393962](https://jq.qq.com/?_wv=1027&k=487PNBb)(满) [708784003](https://jq.qq.com/?_wv=1027&k=5ObjtwM)(满) [964776039](https://jq.qq.com/?_wv=1027&k=59qjU2P)(3群)
Email: (karsonzhang#163.com, 把#换成@) Email: (karsonzhang#163.com, 把#换成@)
weibo: [@karsonzhang](https://weibo.com/karsonzhang)
Github: https://github.com/karsonzhang/fastadmin Github: https://github.com/karsonzhang/fastadmin
Git@OSC: https://git.oschina.net/karson/fastadmin Gitee: https://gitee.com/karson/fastadmin
## **特别鸣谢** ## **特别鸣谢**
@ -60,19 +68,25 @@ Git@OSC: https://git.oschina.net/karson/fastadmin
ThinkPHPhttp://www.thinkphp.cn ThinkPHPhttp://www.thinkphp.cn
AdminLTEhttps://almsaeedstudio.com AdminLTEhttps://adminlte.io
Bootstraphttp://getbootstrap.com Bootstraphttp://getbootstrap.com
jQueryhttp://jquery.com jQueryhttp://jquery.com
Bootstrap-tablehttps://github.com/wenzhixin/bootstrap-table
## 版权信息 Nice-validator: https://validator.niceue.com
SelectPage: https://github.com/TerryZ/SelectPage
## **版权信息**
FastAdmin遵循Apache2开源协议发布并提供免费使用。 FastAdmin遵循Apache2开源协议发布并提供免费使用。
本项目包含的第三方源码和二进制文件之版权信息另行标注。 本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有Copyright © 2017-2018 by FastAdmin (http://www.fastadmin.net) 版权所有Copyright © 2017-2019 by FastAdmin (https://www.fastadmin.net)
All rights reserved。 All rights reserved。

1
addons/.gitkeep 100644
View File

@ -0,0 +1 @@

View File

@ -2,17 +2,12 @@
namespace app\admin\behavior; namespace app\admin\behavior;
use think\Config;
class AdminLog class AdminLog
{ {
public function run(&$params) public function run(&$params)
{ {
if (request()->isPost()) if (request()->isPost()) {
{
\app\admin\model\AdminLog::record(); \app\admin\model\AdminLog::record();
} }
} }
} }

View File

@ -0,0 +1,322 @@
<?php
namespace app\admin\command;
use think\addons\AddonException;
use think\addons\Service;
use think\Config;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\Db;
use think\Exception;
use think\exception\PDOException;
class Addon extends Command
{
protected function configure()
{
$this
->setName('addon')
->addOption('name', 'a', Option::VALUE_REQUIRED, 'addon name', null)
->addOption('action', 'c', Option::VALUE_REQUIRED, 'action(create/enable/disable/install/uninstall/refresh/upgrade/package)', 'create')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', null)
->addOption('release', 'r', Option::VALUE_OPTIONAL, 'addon release version', null)
->addOption('uid', 'u', Option::VALUE_OPTIONAL, 'fastadmin uid', null)
->addOption('token', 't', Option::VALUE_OPTIONAL, 'fastadmin token', null)
->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local package', null)
->setDescription('Addon manager');
}
protected function execute(Input $input, Output $output)
{
$name = $input->getOption('name') ?: '';
$action = $input->getOption('action') ?: '';
if (stripos($name, 'addons/') !== false) {
$name = explode('/', $name)[1];
}
//强制覆盖
$force = $input->getOption('force');
//版本
$release = $input->getOption('release') ?: '';
//uid
$uid = $input->getOption('uid') ?: '';
//token
$token = $input->getOption('token') ?: '';
include dirname(__DIR__) . DS . 'common.php';
if (!$name) {
throw new Exception('Addon name could not be empty');
}
if (!$action || !in_array($action, ['create', 'disable', 'enable', 'install', 'uninstall', 'refresh', 'upgrade', 'package'])) {
throw new Exception('Please input correct action name');
}
// 查询一次SQL,判断连接是否正常
Db::execute("SELECT 1");
$addonDir = ADDON_PATH . $name . DS;
switch ($action) {
case 'create':
//非覆盖模式时如果存在则报错
if (is_dir($addonDir) && !$force) {
throw new Exception("addon already exists!\nIf you need to create again, use the parameter --force=true ");
}
//如果存在先移除
if (is_dir($addonDir)) {
rmdirs($addonDir);
}
mkdir($addonDir, 0755, true);
mkdir($addonDir . DS . 'controller', 0755, true);
$menuList = \app\common\library\Menu::export($name);
$createMenu = $this->getCreateMenu($menuList);
$prefix = Config::get('database.prefix');
$createTableSql = '';
try {
$result = Db::query("SHOW CREATE TABLE `" . $prefix . $name . "`;");
if (isset($result[0]) && isset($result[0]['Create Table'])) {
$createTableSql = $result[0]['Create Table'];
}
} catch (PDOException $e) {
}
$data = [
'name' => $name,
'addon' => $name,
'addonClassName' => ucfirst($name),
'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu, "\t") . ";\n\tMenu::create(\$menu);" : '',
'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '',
'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '',
'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '',
];
$this->writeToFile("addon", $data, $addonDir . ucfirst($name) . '.php');
$this->writeToFile("config", $data, $addonDir . 'config.php');
$this->writeToFile("info", $data, $addonDir . 'info.ini');
$this->writeToFile("controller", $data, $addonDir . 'controller' . DS . 'Index.php');
if ($createTableSql) {
$createTableSql = str_replace("`" . $prefix, '`__PREFIX__', $createTableSql);
file_put_contents($addonDir . 'install.sql', $createTableSql);
}
$output->info("Create Successed!");
break;
case 'disable':
case 'enable':
try {
//调用启用、禁用的方法
Service::$action($name, 0);
} catch (AddonException $e) {
if ($e->getCode() != -3) {
throw new Exception($e->getMessage());
}
if (!$force) {
//如果有冲突文件则提醒
$data = $e->getData();
foreach ($data['conflictlist'] as $k => $v) {
$output->warning($v);
}
$output->info("Are you sure you want to " . ($action == 'enable' ? 'override' : 'delete') . " all those files? Type 'yes' to continue: ");
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
if (trim($line) != 'yes') {
throw new Exception("Operation is aborted!");
}
}
//调用启用、禁用的方法
Service::$action($name, 1);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$output->info(ucfirst($action) . " Successed!");
break;
case 'install':
//非覆盖模式时如果存在则报错
if (is_dir($addonDir) && !$force) {
throw new Exception("addon already exists!\nIf you need to install again, use the parameter --force=true ");
}
//如果存在先移除
if (is_dir($addonDir)) {
rmdirs($addonDir);
}
// 获取本地路径
$local = $input->getOption('local');
try {
Service::install($name, 0, ['version' => $release], $local);
} catch (AddonException $e) {
if ($e->getCode() != -3) {
throw new Exception($e->getMessage());
}
if (!$force) {
//如果有冲突文件则提醒
$data = $e->getData();
foreach ($data['conflictlist'] as $k => $v) {
$output->warning($v);
}
$output->info("Are you sure you want to override all those files? Type 'yes' to continue: ");
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
if (trim($line) != 'yes') {
throw new Exception("Operation is aborted!");
}
}
Service::install($name, 1, ['version' => $release, 'uid' => $uid, 'token' => $token], $local);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$output->info("Install Successed!");
break;
case 'uninstall':
//非覆盖模式时如果存在则报错
if (!$force) {
throw new Exception("If you need to uninstall addon, use the parameter --force=true ");
}
try {
Service::uninstall($name, 0);
} catch (AddonException $e) {
if ($e->getCode() != -3) {
throw new Exception($e->getMessage());
}
if (!$force) {
//如果有冲突文件则提醒
$data = $e->getData();
foreach ($data['conflictlist'] as $k => $v) {
$output->warning($v);
}
$output->info("Are you sure you want to delete all those files? Type 'yes' to continue: ");
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
if (trim($line) != 'yes') {
throw new Exception("Operation is aborted!");
}
}
Service::uninstall($name, 1);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$output->info("Uninstall Successed!");
break;
case 'refresh':
Service::refresh();
$output->info("Refresh Successed!");
break;
case 'upgrade':
Service::upgrade($name, ['version' => $release, 'uid' => $uid, 'token' => $token]);
$output->info("Upgrade Successed!");
break;
case 'package':
$infoFile = $addonDir . 'info.ini';
if (!is_file($infoFile)) {
throw new Exception(__('Addon info file was not found'));
}
$info = get_addon_info($name);
if (!$info) {
throw new Exception(__('Addon info file data incorrect'));
}
$infoname = isset($info['name']) ? $info['name'] : '';
if (!$infoname || !preg_match("/^[a-z]+$/i", $infoname) || $infoname != $name) {
throw new Exception(__('Addon info name incorrect'));
}
$infoversion = isset($info['version']) ? $info['version'] : '';
if (!$infoversion || !preg_match("/^\d+\.\d+\.\d+$/i", $infoversion)) {
throw new Exception(__('Addon info version incorrect'));
}
$addonTmpDir = RUNTIME_PATH . 'addons' . DS;
if (!is_dir($addonTmpDir)) {
@mkdir($addonTmpDir, 0755, true);
}
$addonFile = $addonTmpDir . $infoname . '-' . $infoversion . '.zip';
if (!class_exists('ZipArchive')) {
throw new Exception(__('ZinArchive not install'));
}
$zip = new \ZipArchive;
$zip->open($addonFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($addonDir), \RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = str_replace(DS, '/', substr($filePath, strlen($addonDir)));
if (!in_array($file->getFilename(), ['.git', '.DS_Store', 'Thumbs.db'])) {
$zip->addFile($filePath, $relativePath);
}
}
}
$zip->close();
$output->info("Package Successed!");
break;
default :
break;
}
}
/**
* 获取创建菜单的数组
* @param array $menu
* @return array
*/
protected function getCreateMenu($menu)
{
$result = [];
foreach ($menu as $k => & $v) {
$arr = [
'name' => $v['name'],
'title' => $v['title'],
];
if ($v['icon'] != 'fa fa-circle-o') {
$arr['icon'] = $v['icon'];
}
if ($v['ismenu']) {
$arr['ismenu'] = $v['ismenu'];
}
if (isset($v['childlist']) && $v['childlist']) {
$arr['sublist'] = $this->getCreateMenu($v['childlist']);
}
$result[] = $arr;
}
return $result;
}
/**
* 写入到文件
* @param string $name
* @param array $data
* @param string $pathname
* @return mixed
*/
protected function writeToFile($name, $data, $pathname)
{
$search = $replace = [];
foreach ($data as $k => $v) {
$search[] = "{%{$k}%}";
$replace[] = $v;
}
$stub = file_get_contents($this->getStub($name));
$content = str_replace($search, $replace, $stub);
if (!is_dir(dirname($pathname))) {
mkdir(strtolower(dirname($pathname)), 0755, true);
}
return file_put_contents($pathname, $content);
}
/**
* 获取基础模板
* @param string $name
* @return string
*/
protected function getStub($name)
{
return __DIR__ . '/Addon/stubs/' . $name . '.stub';
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace addons\{%name%};
use app\common\library\Menu;
use think\Addons;
/**
* 插件
*/
class {%addonClassName%} extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
{%addonInstallMenu%}
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
{%addonUninstallMenu%}
return true;
}
/**
* 插件启用方法
* @return bool
*/
public function enable()
{
{%addonEnableMenu%}
return true;
}
/**
* 插件禁用方法
* @return bool
*/
public function disable()
{
{%addonDisableMenu%}
return true;
}
/**
* 实现钩子方法
* @return mixed
*/
public function testhook($param)
{
// 调用钩子时候的参数信息
print_r($param);
// 当前插件的配置信息配置信息存在当前目录的config.php文件中见下方
print_r($this->getConfig());
// 可以返回模板,模板文件默认读取的为插件目录中的文件。模板名不能为空!
//return $this->fetch('view/info');
}
}

View File

@ -0,0 +1,40 @@
<?php
return [
[
//配置唯一标识
'name' => 'usernmae',
//显示的标题
'title' => '用户名',
//类型
'type' => 'string',
//数据字典
'content' => [
],
//值
'value' => '',
//验证规则
'rule' => 'required',
//错误消息
'msg' => '',
//提示消息
'tip' => '',
//成功消息
'ok' => '',
//扩展信息
'extend' => ''
],
[
'name' => 'password',
'title' => '密码',
'type' => 'string',
'content' => [
],
'value' => '',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => ''
],
];

View File

@ -0,0 +1,15 @@
<?php
namespace addons\{%addon%}\controller;
use think\addons\Controller;
class Index extends Controller
{
public function index()
{
$this->error("当前插件暂无前台页面");
}
}

View File

@ -0,0 +1,7 @@
name = {%name%}
title = 插件名称{%name%}
intro = FastAdmin插件
author = yourname
website = https://www.fastadmin.net
version = 1.0.0
state = 1

View File

@ -0,0 +1,178 @@
<?php
namespace app\admin\command;
use app\admin\command\Api\library\Builder;
use think\Config;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\Exception;
class Api extends Command
{
protected function configure()
{
$site = Config::get('site');
$this
->setName('api')
->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'])
->addOption('author', 'a', Option::VALUE_OPTIONAL, 'document author', $site['name'])
->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
->setDescription('Build Api document from controller');
}
protected function execute(Input $input, Output $output)
{
$apiDir = __DIR__ . DS . 'Api' . DS;
$force = $input->getOption('force');
$url = $input->getOption('url');
$language = $input->getOption('language');
$language = $language ? $language : 'zh-cn';
$langFile = $apiDir . 'lang' . DS . $language . '.php';
if (!is_file($langFile)) {
throw new Exception('language file not found');
}
$lang = include_once $langFile;
// 目标目录
$output_dir = ROOT_PATH . 'public' . DS;
$output_file = $output_dir . $input->getOption('output');
if (is_file($output_file) && !$force) {
throw new Exception("api index file already exists!\nIf you need to rebuild again, use the parameter --force=true ");
}
// 模板文件
$template_dir = $apiDir . 'template' . DS;
$template_file = $template_dir . $input->getOption('template');
if (!is_file($template_file)) {
throw new Exception('template file not found');
}
// 额外的类
$classes = $input->getOption('class');
// 标题
$title = $input->getOption('title');
// 作者
$author = $input->getOption('author');
// 模块
$module = $input->getOption('module');
$moduleDir = APP_PATH . $module . DS;
if (!is_dir($moduleDir)) {
throw new Exception('module not found');
}
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
if (extension_loaded('Zend OPcache')) {
$configuration = opcache_get_configuration();
$directives = $configuration['directives'];
$configName = request()->isCli() ? 'opcache.enable_cli' : 'opcache.enable';
if (!$directives[$configName]) {
throw new Exception("Please make sure {$configName} is turned on, Get help:https://forum.fastadmin.net/d/1321");
}
} else {
throw new Exception("Please make sure opcache already enabled, Get help:https://forum.fastadmin.net/d/1321");
}
}
$controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($controllerDir),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file) {
if (!$file->isDir() && $file->getExtension() == 'php') {
$filePath = $file->getRealPath();
$classes[] = $this->get_class_from_file($filePath);
}
}
$classes = array_unique(array_filter($classes));
$config = [
'title' => $title,
'author' => $author,
'description' => '',
'apiurl' => $url,
'language' => $language,
];
$builder = new Builder($classes);
$content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
if (!file_put_contents($output_file, $content)) {
throw new Exception('Cannot save the content to ' . $output_file);
}
$output->info("Build Successed!");
}
/**
* get full qualified class name
*
* @param string $path_to_file
* @author JBYRNE http://jarretbyrne.com/2015/06/197/
* @return string
*/
protected function get_class_from_file($path_to_file)
{
//Grab the contents of the file
$contents = file_get_contents($path_to_file);
//Start with a blank namespace and class
$namespace = $class = "";
//Set helper values to know that we have found the namespace/class token and need to collect the string values after them
$getting_namespace = $getting_class = false;
//Go through each token and evaluate it as necessary
foreach (token_get_all($contents) as $token) {
//If this token is the namespace declaring, then flag that the next tokens will be the namespace name
if (is_array($token) && $token[0] == T_NAMESPACE) {
$getting_namespace = true;
}
//If this token is the class declaring, then flag that the next tokens will be the class name
if (is_array($token) && $token[0] == T_CLASS) {
$getting_class = true;
}
//While we're grabbing the namespace name...
if ($getting_namespace === true) {
//If the token is a string or the namespace separator...
if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {
//Append the token's value to the name of the namespace
$namespace .= $token[1];
} elseif ($token === ';') {
//If the token is the semicolon, then we're done with the namespace declaration
$getting_namespace = false;
}
}
//While we're grabbing the class name...
if ($getting_class === true) {
//If the token is a string, it's the name of the class
if (is_array($token) && $token[0] == T_STRING) {
//Store the token's value as the class name
$class = $token[1];
//Got what we need, stope here
break;
}
}
}
//Build the fully-qualified class name and return it
return $namespace ? $namespace . '\\' . $class : $class;
}
}

View File

@ -0,0 +1,22 @@
<?php
return [
'Info' => '基础信息',
'Sandbox' => '在线测试',
'Sampleoutput' => '返回示例',
'Headers' => 'Headers',
'Parameters' => '参数',
'Body' => '正文',
'Name' => '名称',
'Type' => '类型',
'Required' => '必选',
'Description' => '描述',
'Send' => '提交',
'Reset' => '重置',
'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中',
'Apiurltips' => 'API接口URL',
'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中',
'ReturnHeaders' => '响应头',
'ReturnParameters' => '返回参数',
'Response' => '响应输出',
];

View File

@ -0,0 +1,244 @@
<?php
namespace app\admin\command\Api\library;
use think\Config;
/**
* @website https://github.com/calinrada/php-apidoc
* @author Calin Rada <rada.calin@gmail.com>
* @author Karson <karsonzhang@163.com>
*/
class Builder
{
/**
*
* @var \think\View
*/
public $view = null;
/**
* parse classes
* @var array
*/
protected $classes = [];
/**
*
* @param array $classes
*/
public function __construct($classes = [])
{
$this->classes = array_merge($this->classes, $classes);
$this->view = new \think\View(Config::get('template'), Config::get('view_replace_str'));
}
protected function extractAnnotations()
{
foreach ($this->classes as $class) {
$classAnnotation = Extractor::getClassAnnotations($class);
// 如果忽略
if (isset($classAnnotation['ApiInternal'])) {
continue;
}
Extractor::getClassMethodAnnotations($class);
}
$allClassAnnotation = Extractor::getAllClassAnnotations();
$allClassMethodAnnotation = Extractor::getAllClassMethodAnnotations();
// foreach ($allClassMethodAnnotation as $className => &$methods) {
// foreach ($methods as &$method) {
// //权重判断
// if ($method && !isset($method['ApiWeigh']) && isset($allClassAnnotation[$className]['ApiWeigh'])) {
// $method['ApiWeigh'] = $allClassAnnotation[$className]['ApiWeigh'];
// }
// }
// }
// unset($methods);
return [$allClassAnnotation, $allClassMethodAnnotation];
}
protected function generateHeadersTemplate($docs)
{
if (!isset($docs['ApiHeaders'])) {
return [];
}
$headerslist = array();
foreach ($docs['ApiHeaders'] as $params) {
$tr = array(
'name' => $params['name'],
'type' => $params['type'],
'sample' => isset($params['sample']) ? $params['sample'] : '',
'required' => isset($params['required']) ? $params['required'] : false,
'description' => isset($params['description']) ? $params['description'] : '',
);
$headerslist[] = $tr;
}
return $headerslist;
}
protected function generateParamsTemplate($docs)
{
if (!isset($docs['ApiParams'])) {
return [];
}
$paramslist = array();
foreach ($docs['ApiParams'] as $params) {
$tr = array(
'name' => $params['name'],
'type' => isset($params['type']) ? $params['type'] : 'string',
'sample' => isset($params['sample']) ? $params['sample'] : '',
'required' => isset($params['required']) ? $params['required'] : true,
'description' => isset($params['description']) ? $params['description'] : '',
);
$paramslist[] = $tr;
}
return $paramslist;
}
protected function generateReturnHeadersTemplate($docs)
{
if (!isset($docs['ApiReturnHeaders'])) {
return [];
}
$headerslist = array();
foreach ($docs['ApiReturnHeaders'] as $params) {
$tr = array(
'name' => $params['name'],
'type' => 'string',
'sample' => isset($params['sample']) ? $params['sample'] : '',
'required' => isset($params['required']) && $params['required'] ? 'Yes' : 'No',
'description' => isset($params['description']) ? $params['description'] : '',
);
$headerslist[] = $tr;
}
return $headerslist;
}
protected function generateReturnParamsTemplate($st_params)
{
if (!isset($st_params['ApiReturnParams'])) {
return [];
}
$paramslist = array();
foreach ($st_params['ApiReturnParams'] as $params) {
$tr = array(
'name' => $params['name'],
'type' => isset($params['type']) ? $params['type'] : 'string',
'sample' => isset($params['sample']) ? $params['sample'] : '',
'description' => isset($params['description']) ? $params['description'] : '',
);
$paramslist[] = $tr;
}
return $paramslist;
}
protected function generateBadgeForMethod($data)
{
$method = strtoupper(is_array($data['ApiMethod'][0]) ? $data['ApiMethod'][0]['data'] : $data['ApiMethod'][0]);
$labes = array(
'POST' => 'label-primary',
'GET' => 'label-success',
'PUT' => 'label-warning',
'DELETE' => 'label-danger',
'PATCH' => 'label-default',
'OPTIONS' => 'label-info'
);
return isset($labes[$method]) ? $labes[$method] : $labes['GET'];
}
public function parse()
{
list($allClassAnnotations, $allClassMethodAnnotations) = $this->extractAnnotations();
$sectorArr = [];
foreach ($allClassAnnotations as $index => $allClassAnnotation) {
$sector = isset($allClassAnnotation['ApiSector']) ? $allClassAnnotation['ApiSector'][0] : $allClassAnnotation['ApiTitle'][0];
$sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0;
}
arsort($sectorArr);
$routes = include_once CONF_PATH . 'route.php';
$subdomain = false;
if (config('url_domain_deploy') && isset($routes['__domain__']) && isset($routes['__domain__']['api']) && $routes['__domain__']['api']) {
$subdomain = true;
}
$counter = 0;
$section = null;
$weigh = 0;
$docslist = [];
foreach ($allClassMethodAnnotations as $class => $methods) {
foreach ($methods as $name => $docs) {
if (isset($docs['ApiSector'][0])) {
$section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0];
} else {
$section = $class;
}
if (0 === count($docs)) {
continue;
}
$route = is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0];
if ($subdomain) {
$route = substr($route, 4);
}
$docslist[$section][$name] = [
'id' => $counter,
'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
'method_label' => $this->generateBadgeForMethod($docs),
'section' => $section,
'route' => $route,
'title' => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
'body' => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
'headerslist' => $this->generateHeadersTemplate($docs),
'paramslist' => $this->generateParamsTemplate($docs),
'returnheaderslist' => $this->generateReturnHeadersTemplate($docs),
'returnparamslist' => $this->generateReturnParamsTemplate($docs),
'weigh' => is_array($docs['ApiWeigh'][0]) ? $docs['ApiWeigh'][0]['data'] : $docs['ApiWeigh'][0],
'return' => isset($docs['ApiReturn']) ? is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0] : '',
];
$counter++;
}
}
//重建排序
foreach ($docslist as $index => &$methods) {
$methodSectorArr = [];
foreach ($methods as $name => $method) {
$methodSectorArr[$name] = isset($method['weigh']) ? $method['weigh'] : 0;
}
arsort($methodSectorArr);
$methods = array_merge(array_flip(array_keys($methodSectorArr)), $methods);
}
$docslist = array_merge(array_flip(array_keys($sectorArr)), $docslist);
return $docslist;
}
public function getView()
{
return $this->view;
}
/**
* 渲染
* @param string $template
* @param array $vars
* @return string
*/
public function render($template, $vars = [])
{
$docslist = $this->parse();
return $this->view->display(file_get_contents($template), array_merge($vars, ['docslist' => $docslist]));
}
}

View File

@ -0,0 +1,511 @@
<?php
namespace app\admin\command\Api\library;
use Exception;
/**
* Class imported from https://github.com/eriknyk/Annotations
* @author Erik Amaru Ortiz https://github.com/eriknyk
*
* @license http://opensource.org/licenses/bsd-license.php The BSD License
* @author Calin Rada <rada.calin@gmail.com>
*/
class Extractor
{
/**
* Static array to store already parsed annotations
* @var array
*/
private static $annotationCache;
private static $classAnnotationCache;
private static $classMethodAnnotationCache;
/**
* Indicates that annotations should has strict behavior, 'false' by default
* @var boolean
*/
private $strict = false;
/**
* Stores the default namespace for Objects instance, usually used on methods like getMethodAnnotationsObjects()
* @var string
*/
public $defaultNamespace = '';
/**
* Sets strict variable to true/false
* @param bool $value boolean value to indicate that annotations to has strict behavior
*/
public function setStrict($value)
{
$this->strict = (bool)$value;
}
/**
* Sets default namespace to use in object instantiation
* @param string $namespace default namespace
*/
public function setDefaultNamespace($namespace)
{
$this->defaultNamespace = $namespace;
}
/**
* Gets default namespace used in object instantiation
* @return string $namespace default namespace
*/
public function getDefaultAnnotationNamespace()
{
return $this->defaultNamespace;
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a given class
*
* @param string $className class name to get annotations
* @return array self::$classAnnotationCache all annotated elements
*/
public static function getClassAnnotations($className)
{
if (!isset(self::$classAnnotationCache[$className])) {
$class = new \ReflectionClass($className);
self::$classAnnotationCache[$className] = self::parseAnnotations($class->getDocComment());
}
return self::$classAnnotationCache[$className];
}
/**
* 获取类所有方法的属性配置
* @param $className
* @return mixed
* @throws \ReflectionException
*/
public static function getClassMethodAnnotations($className)
{
$class = new \ReflectionClass($className);
foreach ($class->getMethods() as $object) {
self::$classMethodAnnotationCache[$className][$object->name] = self::getMethodAnnotations($className, $object->name);
}
return self::$classMethodAnnotationCache[$className];
}
public static function getAllClassAnnotations()
{
return self::$classAnnotationCache;
}
public static function getAllClassMethodAnnotations()
{
return self::$classMethodAnnotationCache;
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
*
* @param string $className class name
* @param string $methodName method name to get annotations
* @return array self::$annotationCache all annotated elements of a method given
*/
public static function getMethodAnnotations($className, $methodName)
{
if (!isset(self::$annotationCache[$className . '::' . $methodName])) {
try {
$method = new \ReflectionMethod($className, $methodName);
$class = new \ReflectionClass($className);
if (!$method->isPublic() || $method->isConstructor()) {
$annotations = array();
} else {
$annotations = self::consolidateAnnotations($method, $class);
}
} catch (\ReflectionException $e) {
$annotations = array();
}
self::$annotationCache[$className . '::' . $methodName] = $annotations;
}
return self::$annotationCache[$className . '::' . $methodName];
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
* and instance its abcAnnotation class
*
* @param string $className class name
* @param string $methodName method name to get annotations
* @return array self::$annotationCache all annotated objects of a method given
*/
public function getMethodAnnotationsObjects($className, $methodName)
{
$annotations = $this->getMethodAnnotations($className, $methodName);
$objects = array();
$i = 0;
foreach ($annotations as $annotationClass => $listParams) {
$annotationClass = ucfirst($annotationClass);
$class = $this->defaultNamespace . $annotationClass . 'Annotation';
// verify is the annotation class exists, depending if Annotations::strict is true
// if not, just skip the annotation instance creation.
if (!class_exists($class)) {
if ($this->strict) {
throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class));
} else {
// silent skip & continue
continue;
}
}
if (empty($objects[$annotationClass])) {
$objects[$annotationClass] = new $class();
}
foreach ($listParams as $params) {
if (is_array($params)) {
foreach ($params as $key => $value) {
$objects[$annotationClass]->set($key, $value);
}
} else {
$objects[$annotationClass]->set($i++, $params);
}
}
}
return $objects;
}
private static function consolidateAnnotations($method, $class)
{
$dockblockClass = $class->getDocComment();
$docblockMethod = $method->getDocComment();
$methodName = $method->getName();
$methodAnnotations = self::parseAnnotations($docblockMethod);
$classAnnotations = self::parseAnnotations($dockblockClass);
if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') {
return [];
}
$properties = $class->getDefaultProperties();
$noNeedLogin = isset($properties['noNeedLogin']) ? is_array($properties['noNeedLogin']) ? $properties['noNeedLogin'] : [$properties['noNeedLogin']] : [];
$noNeedRight = isset($properties['noNeedRight']) ? is_array($properties['noNeedRight']) ? $properties['noNeedRight'] : [$properties['noNeedRight']] : [];
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblockMethod), $methodArr);
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $dockblockClass), $classArr);
if (!isset($methodAnnotations['ApiMethod'])) {
$methodAnnotations['ApiMethod'] = ['get'];
}
if (!isset($methodAnnotations['ApiWeigh'])) {
$methodAnnotations['ApiWeigh'] = [0];
}
if (!isset($methodAnnotations['ApiSummary'])) {
$methodAnnotations['ApiSummary'] = $methodAnnotations['ApiTitle'];
}
if ($methodAnnotations) {
foreach ($classAnnotations as $name => $valueClass) {
if (count($valueClass) !== 1) {
continue;
}
if ($name === 'ApiRoute') {
if (isset($methodAnnotations[$name])) {
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . $methodAnnotations[$name][0]];
} else {
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . '/' . $method->getName()];
}
}
if ($name === 'ApiSector') {
$methodAnnotations[$name] = $valueClass;
}
}
}
if (!isset($methodAnnotations['ApiRoute'])) {
$urlArr = [];
$className = $class->getName();
list($prefix, $suffix) = explode('\\' . \think\Config::get('url_controller_layer') . '\\', $className);
$prefixArr = explode('\\', $prefix);
$suffixArr = explode('\\', $suffix);
if ($prefixArr[0] == \think\Config::get('app_namespace')) {
$prefixArr[0] = '';
}
$urlArr = array_merge($urlArr, $prefixArr);
$urlArr[] = implode('.', array_map(function ($item) {
return \think\Loader::parseName($item);
}, $suffixArr));
$urlArr[] = $method->getName();
$methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
}
if (!isset($methodAnnotations['ApiSector'])) {
$methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : $classAnnotations['ApiTitle'];
}
if (!isset($methodAnnotations['ApiParams'])) {
$params = self::parseCustomAnnotations($docblockMethod, 'param');
foreach ($params as $k => $v) {
$arr = explode(' ', preg_replace("/[\s]+/", " ", $v));
$methodAnnotations['ApiParams'][] = [
'name' => isset($arr[1]) ? str_replace('$', '', $arr[1]) : '',
'nullable' => false,
'type' => isset($arr[0]) ? $arr[0] : 'string',
'description' => isset($arr[2]) ? $arr[2] : ''
];
}
}
$methodAnnotations['ApiPermissionLogin'] = [!in_array('*', $noNeedLogin) && !in_array($methodName, $noNeedLogin)];
$methodAnnotations['ApiPermissionRight'] = [!in_array('*', $noNeedRight) && !in_array($methodName, $noNeedRight)];
return $methodAnnotations;
}
/**
* Parse annotations
*
* @param string $docblock
* @param string $name
* @return array parsed annotations params
*/
private static function parseCustomAnnotations($docblock, $name = 'param')
{
$annotations = array();
$docblock = substr($docblock, 3, -2);
if (preg_match_all('/@' . $name . '(?:\s*(?:\(\s*)?(.*?)(?:\s*\))?)??\s*(?:\n|\*\/)/', $docblock, $matches)) {
foreach ($matches[1] as $k => $v) {
$annotations[] = $v;
}
}
return $annotations;
}
/**
* Parse annotations
*
* @param string $docblock
* @return array parsed annotations params
*/
private static function parseAnnotations($docblock)
{
$annotations = array();
// Strip away the docblock header and footer to ease parsing of one line annotations
$docblock = substr($docblock, 3, -2);
if (preg_match_all('/@(?<name>[A-Za-z_-]+)[\s\t]*\((?<args>(?:(?!\)).)*)\)\r?/s', $docblock, $matches)) {
$numMatches = count($matches[0]);
for ($i = 0; $i < $numMatches; ++$i) {
$name = $matches['name'][$i];
$value = '';
// annotations has arguments
if (isset($matches['args'][$i])) {
$argsParts = trim($matches['args'][$i]);
if ($name == 'ApiReturn') {
$value = $argsParts;
} elseif ($matches['args'][$i] != '') {
$argsParts = preg_replace("/\{(\w+)\}/", '#$1#', $argsParts);
$value = self::parseArgs($argsParts);
if (is_string($value)) {
$value = preg_replace("/\#(\w+)\#/", '{$1}', $argsParts);
}
}
}
$annotations[$name][] = $value;
}
}
if (stripos($docblock, '@ApiInternal') !== false) {
$annotations['ApiInternal'] = [true];
}
if (!isset($annotations['ApiTitle'])) {
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblock), $matchArr);
$title = isset($matchArr[1]) && isset($matchArr[1][0]) ? $matchArr[1][0] : '';
$annotations['ApiTitle'] = [$title];
}
return $annotations;
}
/**
* Parse individual annotation arguments
*
* @param string $content arguments string
* @return array annotated arguments
*/
private static function parseArgs($content)
{
// Replace initial stars
$content = preg_replace('/^\s*\*/m', '', $content);
$data = array();
$len = strlen($content);
$i = 0;
$var = '';
$val = '';
$level = 1;
$prevDelimiter = '';
$nextDelimiter = '';
$nextToken = '';
$composing = false;
$type = 'plain';
$delimiter = null;
$quoted = false;
$tokens = array('"', '"', '{', '}', ',', '=');
while ($i <= $len) {
$prev_c = substr($content, $i - 1, 1);
$c = substr($content, $i++, 1);
if ($c === '"' && $prev_c !== "\\") {
$delimiter = $c;
//open delimiter
if (!$composing && empty($prevDelimiter) && empty($nextDelimiter)) {
$prevDelimiter = $nextDelimiter = $delimiter;
$val = '';
$composing = true;
$quoted = true;
} else {
// close delimiter
if ($c !== $nextDelimiter) {
throw new Exception(sprintf(
"Parse Error: enclosing error -> expected: [%s], given: [%s]",
$nextDelimiter,
$c
));
}
// validating syntax
if ($i < $len) {
if (',' !== substr($content, $i, 1) && '\\' !== $prev_c) {
throw new Exception(sprintf(
"Parse Error: missing comma separator near: ...%s<--",
substr($content, ($i - 10), $i)
));
}
}
$prevDelimiter = $nextDelimiter = '';
$composing = false;
$delimiter = null;
}
} elseif (!$composing && in_array($c, $tokens)) {
switch ($c) {
case '=':
$prevDelimiter = $nextDelimiter = '';
$level = 2;
$composing = false;
$type = 'assoc';
$quoted = false;
break;
case ',':
$level = 3;
// If composing flag is true yet,
// it means that the string was not enclosed, so it is parsing error.
if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) {
throw new Exception(sprintf(
"Parse Error: enclosing error -> expected: [%s], given: [%s]",
$nextDelimiter,
$c
));
}
$prevDelimiter = $nextDelimiter = '';
break;
case '{':
$subc = '';
$subComposing = true;
while ($i <= $len) {
$c = substr($content, $i++, 1);
if (isset($delimiter) && $c === $delimiter) {
throw new Exception(sprintf(
"Parse Error: Composite variable is not enclosed correctly."
));
}
if ($c === '}') {
$subComposing = false;
break;
}
$subc .= $c;
}
// if the string is composing yet means that the structure of var. never was enclosed with '}'
if ($subComposing) {
throw new Exception(sprintf(
"Parse Error: Composite variable is not enclosed correctly. near: ...%s'",
$subc
));
}
$val = self::parseArgs($subc);
break;
}
} else {
if ($level == 1) {
$var .= $c;
} elseif ($level == 2) {
$val .= $c;
}
}
if ($level === 3 || $i === $len) {
if ($type == 'plain' && $i === $len) {
$data = self::castValue($var);
} else {
$data[trim($var)] = self::castValue($val, !$quoted);
}
$level = 1;
$var = $val = '';
$composing = false;
$quoted = false;
}
}
return $data;
}
/**
* Try determinate the original type variable of a string
*
* @param string $val string containing possibles variables that can be cast to bool or int
* @param boolean $trim indicate if the value passed should be trimmed after to try cast
* @return mixed returns the value converted to original type if was possible
*/
private static function castValue($val, $trim = false)
{
if (is_array($val)) {
foreach ($val as $key => $value) {
$val[$key] = self::castValue($value);
}
} elseif (is_string($val)) {
if ($trim) {
$val = trim($val);
}
$val = stripslashes($val);
$tmp = strtolower($val);
if ($tmp === 'false' || $tmp === 'true') {
$val = $tmp === 'true';
} elseif (is_numeric($val)) {
return $val + 0;
}
unset($tmp);
}
return $val;
}
}

View File

@ -0,0 +1,582 @@
<!DOCTYPE html>
<html lang="{$config.language}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="{$config.author}">
<title>{$config.title}</title>
<!-- Bootstrap Core CSS -->
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- Plugin CSS -->
<link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<style type="text/css">
body {
padding-top: 70px; margin-bottom: 15px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: "Roboto", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", BlinkMacSystemFont, -apple-system, "Segoe UI", "Microsoft Yahei", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
font-weight: 400;
}
h2 { font-size: 1.6em; }
hr { margin-top: 10px; }
.tab-pane { padding-top: 10px; }
.mt0 { margin-top: 0px; }
.footer { font-size: 12px; color: #666; }
.label { display: inline-block; min-width: 65px; padding: 0.3em 0.6em 0.3em; }
.string { color: green; }
.number { color: darkorange; }
.boolean { color: blue; }
.null { color: magenta; }
.key { color: red; }
.popover { max-width: 400px; max-height: 400px; overflow-y: auto;}
.list-group.panel > .list-group-item {
}
.list-group-item:last-child {
border-radius:0;
}
h4.panel-title a {
font-weight:normal;
font-size:14px;
}
h4.panel-title a .text-muted {
font-size:12px;
font-weight:normal;
font-family: 'Verdana';
}
#sidebar {
width: 220px;
position: fixed;
margin-left: -240px;
overflow-y:auto;
}
#sidebar > .list-group {
margin-bottom:0;
}
#sidebar > .list-group > a{
text-indent:0;
}
#sidebar .child {
border:1px solid #ddd;
border-bottom:none;
}
#sidebar .child > a {
border:0;
}
#sidebar .list-group a.current {
background:#f5f5f5;
}
@media (max-width: 1620px){
#sidebar {
margin:0;
}
#accordion {
padding-left:235px;
}
}
@media (max-width: 768px){
#sidebar {
display: none;
}
#accordion {
padding-left:0px;
}
}
.label-primary {
background-color: #248aff;
}
</style>
</head>
<body>
<!-- Fixed navbar -->
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="https://www.fastadmin.net" target="_blank">{$config.title}</a>
</div>
<div class="navbar-collapse collapse">
<form class="navbar-form navbar-right">
<div class="form-group">
Token:
</div>
<div class="form-group">
<input type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Tokentips}" placeholder="token" id="token" />
</div>
<div class="form-group">
Apiurl:
</div>
<div class="form-group">
<input id="apiUrl" type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Apiurltips}" placeholder="https://api.mydomain.com" value="{$config.apiurl}" />
</div>
<div class="form-group">
<button type="button" class="btn btn-success btn-sm" data-toggle="tooltip" title="{$lang.Savetips}" id="save_data">
<span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
</button>
</div>
</form>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container">
<!-- menu -->
<div id="sidebar">
<div class="list-group panel">
{foreach name="docslist" id="docs"}
<a href="#{$key}" class="list-group-item" data-toggle="collapse" data-parent="#sidebar">{$key} <i class="fa fa-caret-down"></i></a>
<div class="child collapse" id="{$key}">
{foreach name="docs" id="api" }
<a href="javascript:;" data-id="{$api.id}" class="list-group-item">{$api.title}</a>
{/foreach}
</div>
{/foreach}
</div>
</div>
<div class="panel-group" id="accordion">
{foreach name="docslist" id="docs"}
<h2>{$key}</h2>
<hr>
{foreach name="docs" id="api" }
<div class="panel panel-default">
<div class="panel-heading" id="heading-{$api.id}">
<h4 class="panel-title">
<span class="label {$api.method_label}">{$api.method|strtoupper}</span>
<a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.title} <span class="text-muted">{$api.route}</span></a>
</h4>
</div>
<div id="collapseOne{$api.id}" class="panel-collapse collapse">
<div class="panel-body">
<!-- Nav tabs -->
<ul class="nav nav-tabs" id="doctab{$api.id}">
<li class="active"><a href="#info{$api.id}" data-toggle="tab">{$lang.Info}</a></li>
<li><a href="#sandbox{$api.id}" data-toggle="tab">{$lang.Sandbox}</a></li>
<li><a href="#sample{$api.id}" data-toggle="tab">{$lang.Sampleoutput}</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane active" id="info{$api.id}">
<div class="well">
{$api.summary}
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Headers}</strong></div>
<div class="panel-body">
{if $api.headerslist}
<table class="table table-hover">
<thead>
<tr>
<th>{$lang.Name}</th>
<th>{$lang.Type}</th>
<th>{$lang.Required}</th>
<th>{$lang.Description}</th>
</tr>
</thead>
<tbody>
{foreach name="api['headerslist']" id="header"}
<tr>
<td>{$header.name}</td>
<td>{$header.type}</td>
<td>{$header.required?'是':'否'}</td>
<td>{$header.description}</td>
</tr>
{/foreach}
</tbody>
</table>
{else /}
{/if}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
<div class="panel-body">
{if $api.paramslist}
<table class="table table-hover">
<thead>
<tr>
<th>{$lang.Name}</th>
<th>{$lang.Type}</th>
<th>{$lang.Required}</th>
<th>{$lang.Description}</th>
</tr>
</thead>
<tbody>
{foreach name="api['paramslist']" id="param"}
<tr>
<td>{$param.name}</td>
<td>{$param.type}</td>
<td>{:$param.required?'是':'否'}</td>
<td>{$param.description}</td>
</tr>
{/foreach}
</tbody>
</table>
{else /}
{/if}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Body}</strong></div>
<div class="panel-body">
{$api.body|default='无'}
</div>
</div>
</div><!-- #info -->
<div class="tab-pane" id="sandbox{$api.id}">
<div class="row">
<div class="col-md-12">
{if $api.headerslist}
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Headers}</strong></div>
<div class="panel-body">
<div class="headers">
{foreach name="api['headerslist']" id="param"}
<div class="form-group">
<label class="control-label" for="{$param.name}">{$param.name}</label>
<input type="{$param.type}" class="form-control input-sm" id="{$param.name}" {if $param.required}required{/if} placeholder="{$param.description} - Ex: {$param.sample}" name="{$param.name}">
</div>
{/foreach}
</div>
</div>
</div>
{/if}
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
<div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="{$api.route}" method="{$api.method}" name="form{$api.id}" id="form{$api.id}">
{if $api.paramslist}
{foreach name="api['paramslist']" id="param"}
<div class="form-group">
<label class="control-label" for="{$param.name}">{$param.name}</label>
<input type="{$param.type}" class="form-control input-sm" id="{$param.name}" {if $param.required}required{/if} placeholder="{$param.description}{if $param.sample} - 例: {$param.sample}{/if}" name="{$param.name}">
</div>
{/foreach}
{else /}
<div class="form-group">
</div>
{/if}
<div class="form-group">
<button type="submit" class="btn btn-success send" rel="{$api.id}">{$lang.Send}</button>
<button type="reset" class="btn btn-info" rel="{$api.id}">{$lang.Reset}</button>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Response}</strong></div>
<div class="panel-body">
<div class="row">
<div class="col-md-12" style="overflow-x:auto">
<pre id="response_headers{$api.id}"></pre>
<pre id="response{$api.id}"></pre>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.ReturnParameters}</strong></div>
<div class="panel-body">
{if $api.returnparamslist}
<table class="table table-hover">
<thead>
<tr>
<th>{$lang.Name}</th>
<th>{$lang.Type}</th>
<th>{$lang.Description}</th>
</tr>
</thead>
<tbody>
{foreach name="api['returnparamslist']" id="param"}
<tr>
<td>{$param.name}</td>
<td>{$param.type}</td>
<td>{$param.description}</td>
</tr>
{/foreach}
</tbody>
</table>
{else /}
{/if}
</div>
</div>
</div>
</div>
</div><!-- #sandbox -->
<div class="tab-pane" id="sample{$api.id}">
<div class="row">
<div class="col-md-12">
<pre id="sample_response{$api.id}">{$api.return|default='无'}</pre>
</div>
</div>
</div><!-- #sample -->
</div><!-- .tab-content -->
</div>
</div>
</div>
{/foreach}
{/foreach}
</div>
<hr>
<div class="row mt0 footer">
<div class="col-md-6" align="left">
Generated on {:date('Y-m-d H:i:s')}
</div>
<div class="col-md-6" align="right">
<a href="https://www.fastadmin.net" target="_blank">FastAdmin</a>
</div>
</div>
</div> <!-- /container -->
<!-- jQuery -->
<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
function syntaxHighlight(json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2);
}
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
function prepareStr(str) {
try {
return syntaxHighlight(JSON.stringify(JSON.parse(str.replace(/'/g, '"')), null, 2));
} catch (e) {
return str;
}
}
var storage = (function () {
var uid = new Date;
var storage;
var result;
try {
(storage = window.localStorage).setItem(uid, uid);
result = storage.getItem(uid) == uid;
storage.removeItem(uid);
return result && storage;
} catch (exception) {
}
}());
$.fn.serializeObject = function ()
{
var o = {};
var a = this.serializeArray();
$.each(a, function () {
if (!this.value) {
return;
}
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
$(document).ready(function () {
if (storage) {
storage.getItem('token') && $('#token').val(storage.getItem('token'));
storage.getItem('apiUrl') && $('#apiUrl').val(storage.getItem('apiUrl'));
}
$('[data-toggle="tooltip"]').tooltip({
placement: 'bottom'
});
$(window).on("resize", function(){
$("#sidebar").css("max-height", $(window).height()-80);
});
$(window).trigger("resize");
$(document).on("click", "#sidebar .list-group > .list-group-item", function(){
$("#sidebar .list-group > .list-group-item").removeClass("current");
$(this).addClass("current");
});
$(document).on("click", "#sidebar .child a", function(){
var heading = $("#heading-"+$(this).data("id"));
if(!heading.next().hasClass("in")){
$("a", heading).trigger("click");
}
$("html,body").animate({scrollTop:heading.offset().top-70});
});
$('code[id^=response]').hide();
$.each($('pre[id^=sample_response],pre[id^=sample_post_body]'), function () {
if ($(this).html() == 'NA') {
return;
}
var str = prepareStr($(this).html());
$(this).html(str);
});
$("[data-toggle=popover]").popover({placement: 'right'});
$('[data-toggle=popover]').on('shown.bs.popover', function () {
var $sample = $(this).parent().find(".popover-content"),
str = $(this).data('content');
if (typeof str == "undefined" || str === "") {
return;
}
var str = prepareStr(str);
$sample.html('<pre>' + str + '</pre>');
});
$('body').on('click', '#save_data', function (e) {
if (storage) {
storage.setItem('token', $('#token').val());
storage.setItem('apiUrl', $('#apiUrl').val());
} else {
alert('Your browser does not support local storage');
}
});
$('body').on('click', '.send', function (e) {
e.preventDefault();
var form = $(this).closest('form');
//added /g to get all the matched params instead of only first
var matchedParamsInRoute = $(form).attr('action').match(/[^{]+(?=\})/g);
var theId = $(this).attr('rel');
//keep a copy of action attribute in order to modify the copy
//instead of the initial attribute
var url = $(form).attr('action');
var method = $(form).prop('method').toLowerCase() || 'get';
var formData = new FormData();
$(form).find('input').each(function (i, input) {
if ($(input).attr('type').toLowerCase() == 'file') {
formData.append($(input).attr('name'), $(input)[0].files[0]);
method = 'post';
} else {
formData.append($(input).attr('name'), $(input).val())
}
});
var index, key, value;
if (matchedParamsInRoute) {
var params = {};
formData.forEach(function(value, key){
params[key] = value;
});
for (index = 0; index < matchedParamsInRoute.length; ++index) {
try {
key = matchedParamsInRoute[index];
value = params[key];
if (typeof value == "undefined")
value = "";
url = url.replace("\{" + key + "\}", value);
formData.delete(key);
} catch (err) {
console.log(err);
}
}
}
var headers = {};
var token = $('#token').val();
if (token.length > 0) {
headers['token'] = token;
}
$("#sandbox" + theId + " .headers input[type=text]").each(function () {
val = $(this).val();
if (val.length > 0) {
headers[$(this).prop('name')] = val;
}
});
$.ajax({
url: $('#apiUrl').val() + url,
data: method == 'get' ? $(form).serialize() : formData,
type: method,
dataType: 'json',
contentType: false,
processData: false,
headers: headers,
xhrFields: {
withCredentials: true
},
success: function (data, textStatus, xhr) {
if (typeof data === 'object') {
var str = JSON.stringify(data, null, 2);
$('#response' + theId).html(syntaxHighlight(str));
} else {
$('#response' + theId).html(data || '');
}
$('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
$('#response' + theId).show();
},
error: function (xhr, textStatus, error) {
try {
var str = JSON.stringify($.parseJSON(xhr.responseText), null, 2);
} catch (e) {
var str = xhr.responseText;
}
$('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
$('#response' + theId).html(syntaxHighlight(str));
$('#response' + theId).show();
}
});
return false;
});
});
</script>
</body>
</html>

1148
application/admin/command/Crud.php 100644 → 100755

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,6 @@ namespace {%controllerNamespace%};
use app\common\controller\Backend; use app\common\controller\Backend;
use think\Controller;
use think\Request;
/** /**
* {%tableComment%} * {%tableComment%}
* *
@ -17,19 +14,20 @@ class {%controllerName%} extends Backend
/** /**
* {%modelName%}模型对象 * {%modelName%}模型对象
* @var \{%modelNamespace%}\{%modelName%}
*/ */
protected $model = null; protected $model = null;
public function _initialize() public function _initialize()
{ {
parent::_initialize(); parent::_initialize();
$this->model = model('{%modelName%}'); $this->model = new \{%modelNamespace%}\{%modelName%};
{%controllerAssignList%} {%controllerAssignList%}
} }
/** /**
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个方法 * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
* 因此在当前控制器中可不用编写增删改查的代码,如果需要自己控制这部分逻辑 * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
*/ */

View File

@ -7,22 +7,33 @@
//当前是否为关联查询 //当前是否为关联查询
$this->relationSearch = {%relationSearch%}; $this->relationSearch = {%relationSearch%};
//设置过滤方法 //设置过滤方法
$this->request->filter(['strip_tags', 'htmlspecialchars']); $this->request->filter(['strip_tags']);
if ($this->request->isAjax()) if ($this->request->isAjax())
{ {
//如果发送的来源是Selectpage则转发到Selectpage
if ($this->request->request('keyField'))
{
return $this->selectpage();
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams(); list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model $total = $this->model
{%relationWith%} {%relationWithList%}
->where($where) ->where($where)
->order($sort, $order) ->order($sort, $order)
->count(); ->count();
$list = $this->model $list = $this->model
{%relationWith%} {%relationWithList%}
->where($where) ->where($where)
->order($sort, $order) ->order($sort, $order)
->limit($offset, $limit) ->limit($offset, $limit)
->select(); ->select();
foreach ($list as $row) {
{%visibleFieldList%}
{%relationVisibleFieldList%}
}
$list = collection($list)->toArray();
$result = array("total" => $total, "rows" => $list); $result = array("total" => $total, "rows" => $list);
return json($result); return json($result);

View File

@ -1,4 +1,6 @@
<div class="checkbox">
{foreach name="{%fieldList%}" item="vo"} {foreach name="{%fieldList%}" item="vo"}
<label for="{%fieldName%}-{$key}"><input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="checkbox" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} /> {$vo}</label> <label for="{%fieldName%}-{$key}"><input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="checkbox" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} /> {$vo}</label>
{/foreach} {/foreach}
</div>

View File

@ -0,0 +1,10 @@
<dl class="fieldlist" data-name="{%fieldName%}">
<dd>
<ins>{:__('{%itemKey%}')}</ins>
<ins>{:__('{%itemValue%}')}</ins>
</dd>
<dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
<textarea name="{%fieldName%}" class="form-control hide" cols="30" rows="5">{%fieldValue%}</textarea>
</dl>

View File

@ -0,0 +1,10 @@
<div class="panel-heading">
{:build_heading(null,FALSE)}
<ul class="nav nav-tabs" data-field="{%field%}">
<li class="active"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li>
{foreach name="{%fieldName%}List" item="vo"}
<li><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li>
{/foreach}
</ul>
</div>

View File

@ -1,4 +1,6 @@
<div class="radio">
{foreach name="{%fieldList%}" item="vo"} {foreach name="{%fieldList%}" item="vo"}
<label for="{%fieldName%}-{$key}"><input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="radio" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} /> {$vo}</label> <label for="{%fieldName%}-{$key}"><input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="radio" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} /> {$vo}</label>
{/foreach} {/foreach}
</div>

View File

@ -0,0 +1 @@
<a class="btn btn-success btn-recyclebin btn-dialog {:$auth->check('{%controllerUrl%}/recyclebin')?'':'hide'}" href="{%controllerUrl%}/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>

View File

@ -0,0 +1,5 @@
<input {%attrStr%} name="{%fieldName%}" type="hidden" value="{%fieldValue%}">
<a href="javascript:;" data-toggle="switcher" class="btn-switcher" data-input-id="c-{%field%}" data-yes="{%fieldYes%}" data-no="{%fieldNo%}" >
<i class="fa fa-toggle-on text-success {%fieldSwitchClass%} fa-2x"></i>
</a>

View File

@ -1,21 +1,31 @@
<div class="panel panel-default panel-intro"> <div class="panel panel-default panel-intro">
{:build_heading()} {%headingHtml%}
<div class="panel-body"> <div class="panel-body">
<div id="myTabContent" class="tab-content"> <div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="one"> <div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding"> <div class="widget-body no-padding">
<div id="toolbar" class="toolbar"> <div id="toolbar" class="toolbar">
{:build_toolbar()} <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
<div class="dropdown btn-group"> <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('{%controllerUrl%}/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
<a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('{%controllerUrl%}/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
<a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('{%controllerUrl%}/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
<a href="javascript:;" class="btn btn-danger btn-import {:$auth->check('{%controllerUrl%}/import')?'':'hide'}" title="{:__('Import')}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="fa fa-upload"></i> {:__('Import')}</a>
<div class="dropdown btn-group {:$auth->check('{%controllerUrl%}/multi')?'':'hide'}">
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a> <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
<ul class="dropdown-menu text-left" role="menu"> <ul class="dropdown-menu text-left" role="menu">
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li> <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li> <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
</ul> </ul>
</div> </div>
{%recyclebinHtml%}
</div> </div>
<table id="table" class="table table-striped table-bordered table-hover" width="100%"> <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
data-operate-edit="{:$auth->check('{%controllerUrl%}/edit')}"
data-operate-del="{:$auth->check('{%controllerUrl%}/del')}"
width="100%">
</table> </table>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
// 初始化表格参数配置 // 初始化表格参数配置
Table.api.init({ Table.api.init({
extend: { extend: {
index_url: '{%controllerUrl%}/index', index_url: '{%controllerUrl%}/index' + location.search,
add_url: '{%controllerUrl%}/add', add_url: '{%controllerUrl%}/add',
edit_url: '{%controllerUrl%}/edit', edit_url: '{%controllerUrl%}/edit',
del_url: '{%controllerUrl%}/del', del_url: '{%controllerUrl%}/del',
@ -30,7 +30,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
// 为表格绑定事件 // 为表格绑定事件
Table.api.bindevent(table); Table.api.bindevent(table);
}, },{%recyclebinJs%}
add: function () { add: function () {
Controller.api.bindevent(); Controller.api.bindevent();
}, },

View File

@ -1,7 +1,7 @@
public function {%methodName%}($value, $data) public function {%methodName%}($value, $data)
{ {
$value = $value ? $value : $data['{%field%}']; $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
$valueArr = explode(',', $value); $valueArr = explode(',', $value);
$list = $this->{%listMethodName%}(); $list = $this->{%listMethodName%}();
return implode(',', array_intersect_key($list, array_flip($valueArr))); return implode(',', array_intersect_key($list, array_flip($valueArr)));

View File

@ -1,6 +1,6 @@
public function {%methodName%}($value, $data) public function {%methodName%}($value, $data)
{ {
$value = $value ? $value : $data['{%field%}']; $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
} }

View File

@ -2,6 +2,7 @@
protected static function init() protected static function init()
{ {
self::afterInsert(function ($row) { self::afterInsert(function ($row) {
$row->save(['{%order%}' => $row['id']]); $pk = $row->getPk();
$row->getQuery()->where($pk, $row[$pk])->update(['{%order%}' => $row[$pk]]);
}); });
} }

View File

@ -1,5 +1,5 @@
public function {%relationMethod%}() public function {%relationMethod%}()
{ {
return $this->{%relationMode%}('{%relationModelName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}')->setEagerlyType(0); return $this->{%relationMode%}('{%relationClassName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}', [], 'LEFT')->setEagerlyType(0);
} }

View File

@ -1,7 +1,7 @@
public function {%methodName%}($value, $data) public function {%methodName%}($value, $data)
{ {
$value = $value ? $value : $data['{%field%}']; $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
$valueArr = explode(',', $value); $valueArr = explode(',', $value);
$list = $this->{%listMethodName%}(); $list = $this->{%listMethodName%}();
return implode(',', array_intersect_key($list, array_flip($valueArr))); return implode(',', array_intersect_key($list, array_flip($valueArr)));

View File

@ -1,7 +1,7 @@
public function {%methodName%}($value, $data) public function {%methodName%}($value, $data)
{ {
$value = $value ? $value : $data['{%field%}']; $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
$list = $this->{%listMethodName%}(); $list = $this->{%listMethodName%}();
return isset($list[$value]) ? $list[$value] : ''; return isset($list[$value]) ? $list[$value] : '';
} }

View File

@ -0,0 +1,60 @@
recyclebin: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
'dragsort_url': ''
}
});
var table = $("#table");
// 初始化表格
table.bootstrapTable({
url: '{%controllerUrl%}/recyclebin' + location.search,
pk: 'id',
sortName: 'id',
columns: [
[
{checkbox: true},
{field: 'id', title: __('Id')},{%recyclebinTitleJs%}
{
field: 'deletetime',
title: __('Deletetime'),
operate: 'RANGE',
addclass: 'datetimerange',
formatter: Table.api.formatter.datetime
},
{
field: 'operate',
width: '130px',
title: __('Operate'),
table: table,
events: Table.api.events.operate,
buttons: [
{
name: 'Restore',
text: __('Restore'),
classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
icon: 'fa fa-rotate-left',
url: '{%controllerUrl%}/restore',
refresh: true
},
{
name: 'Destroy',
text: __('Destroy'),
classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
icon: 'fa fa-times',
url: '{%controllerUrl%}/destroy',
refresh: true
}
],
formatter: Table.api.formatter.operate
}
]
]
});
// 为表格绑定事件
Table.api.bindevent(table);
},

View File

@ -1,7 +1,7 @@
public function {%methodName%}($value, $data) public function {%methodName%}($value, $data)
{ {
$value = $value ? $value : $data['{%field%}']; $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
$list = $this->{%listMethodName%}(); $list = $this->{%listMethodName%}();
return isset($list[$value]) ? $list[$value] : ''; return isset($list[$value]) ? $list[$value] : '';
} }

View File

@ -3,11 +3,17 @@
namespace {%modelNamespace%}; namespace {%modelNamespace%};
use think\Model; use think\Model;
{%sofeDeleteClassPath%}
class {%modelName%} extends Model class {%modelName%} extends Model
{ {
{%softDelete%}
{%modelConnection%}
// 表名 // 表名
protected ${%modelTableType%} = '{%modelTableName%}'; protected ${%modelTableType%} = '{%modelTableTypeName%}';
// 自动写入时间戳字段 // 自动写入时间戳字段
protected $autoWriteTimestamp = {%modelAutoWriteTimestamp%}; protected $autoWriteTimestamp = {%modelAutoWriteTimestamp%};
@ -15,6 +21,7 @@ class {%modelName%} extends Model
// 定义时间戳字段名 // 定义时间戳字段名
protected $createTime = {%createTime%}; protected $createTime = {%createTime%};
protected $updateTime = {%updateTime%}; protected $updateTime = {%updateTime%};
protected $deleteTime = {%deleteTime%};
// 追加属性 // 追加属性
protected $append = [ protected $append = [
@ -29,5 +36,5 @@ class {%modelName%} extends Model
{%setAttrList%} {%setAttrList%}
{%modelRelationMethod%} {%relationMethodList%}
} }

View File

@ -0,0 +1,25 @@
<div class="panel panel-default panel-intro">
{:build_heading()}
<div class="panel-body">
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
{:build_toolbar('refresh')}
<a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('{%controllerUrl%}/restore')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
<a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('{%controllerUrl%}/destroy')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
<a class="btn btn-success btn-restoreall {:$auth->check('{%controllerUrl%}/restore')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
<a class="btn btn-danger btn-destroyall {:$auth->check('{%controllerUrl%}/destroy')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
</div>
<table id="table" class="table table-striped table-bordered table-hover"
data-operate-restore="{:$auth->check('{%controllerUrl%}/restore')}"
data-operate-destroy="{:$auth->check('{%controllerUrl%}/destroy')}"
width="100%">
</table>
</div>
</div>
</div>
</div>
</div>

View File

@ -4,9 +4,9 @@ namespace {%modelNamespace%};
use think\Model; use think\Model;
class {%relationModelName%} extends Model class {%relationName%} extends Model
{ {
// 表名 // 表名
protected ${%relationModelTableType%} = '{%relationModelTableName%}'; protected ${%relationTableType%} = '{%relationTableTypeName%}';
} }

View File

@ -13,45 +13,83 @@ use think\Exception;
class Install extends Command class Install extends Command
{ {
protected $model = null; protected $model = null;
protected function configure() protected function configure()
{ {
$config = Config::get('database');
$this $this
->setName('install') ->setName('install')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', FALSE) ->addOption('hostname', 'a', Option::VALUE_OPTIONAL, 'mysql hostname', $config['hostname'])
->addOption('hostport', 'o', Option::VALUE_OPTIONAL, 'mysql hostport', $config['hostport'])
->addOption('database', 'd', Option::VALUE_OPTIONAL, 'mysql database', $config['database'])
->addOption('prefix', 'r', Option::VALUE_OPTIONAL, 'table prefix', $config['prefix'])
->addOption('username', 'u', Option::VALUE_OPTIONAL, 'mysql username', $config['username'])
->addOption('password', 'p', Option::VALUE_OPTIONAL, 'mysql password', $config['password'])
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', false)
->setDescription('New installation of FastAdmin'); ->setDescription('New installation of FastAdmin');
} }
protected function execute(Input $input, Output $output) protected function execute(Input $input, Output $output)
{ {
// 覆盖安装
//覆盖安装
$force = $input->getOption('force'); $force = $input->getOption('force');
$hostname = $input->getOption('hostname');
$hostport = $input->getOption('hostport');
$database = $input->getOption('database');
$prefix = $input->getOption('prefix');
$username = $input->getOption('username');
$password = $input->getOption('password');
$installLockFile = __DIR__ . "/Install/install.lock"; $installLockFile = __DIR__ . "/Install/install.lock";
if (is_file($installLockFile) && !$force) if (is_file($installLockFile) && !$force) {
{
throw new Exception("\nFastAdmin already installed!\nIf you need to reinstall again, use the parameter --force=true "); throw new Exception("\nFastAdmin already installed!\nIf you need to reinstall again, use the parameter --force=true ");
} }
$sql = file_get_contents(__DIR__ . '/Install/fastadmin.sql'); $sql = file_get_contents(__DIR__ . '/Install/fastadmin.sql');
$sql = str_replace("`fa_", "`{$prefix}", $sql);
// 先尝试能否自动创建数据库 // 先尝试能否自动创建数据库
$config = Config::get('database'); $config = Config::get('database');
$pdo = new PDO("{$config['type']}:host={$config['hostname']}" . ($config['hostport'] ? ";port={$config['hostport']}" : ''), $config['username'], $config['password']); $pdo = new PDO("{$config['type']}:host={$hostname}" . ($hostport ? ";port={$hostport}" : ''), $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->query("CREATE DATABASE IF NOT EXISTS `{$config['database']}` CHARACTER SET utf8 COLLATE utf8_general_ci;"); $pdo->query("CREATE DATABASE IF NOT EXISTS `{$database}` CHARACTER SET utf8 COLLATE utf8_general_ci;");
// 连接install命令中指定的数据库
$instance = Db::connect([
'type' => "{$config['type']}",
'hostname' => "{$hostname}",
'hostport' => "{$hostport}",
'database' => "{$database}",
'username' => "{$username}",
'password' => "{$password}",
]);
// 查询一次SQL,判断连接是否正常 // 查询一次SQL,判断连接是否正常
Db::execute("SELECT 1"); $instance->execute("SELECT 1");
// 调用原生PDO对象进行批量查询 // 调用原生PDO对象进行批量查询
Db::getPdo()->exec($sql); $instance->getPdo()->exec($sql);
file_put_contents($installLockFile, 1); file_put_contents($installLockFile, 1);
$dbConfigFile = APP_PATH . 'database.php';
$config = @file_get_contents($dbConfigFile);
$callback = function ($matches) use ($hostname, $hostport, $username, $password, $database, $prefix) {
$field = $matches[1];
$replace = $$field;
if ($matches[1] == 'hostport' && $hostport == 3306) {
$replace = '';
}
return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),";
};
$config = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $config);
// 写入数据库配置
file_put_contents($dbConfigFile, $config);
\think\Cache::rm('__menu__');
$output->info("Install Successed!"); $output->info("Install Successed!");
} }
} }

File diff suppressed because one or more lines are too long

213
application/admin/command/Menu.php 100644 → 100755
View File

@ -12,58 +12,80 @@ use think\console\Input;
use think\console\input\Option; use think\console\input\Option;
use think\console\Output; use think\console\Output;
use think\Exception; use think\Exception;
use think\Loader;
class Menu extends Command class Menu extends Command
{ {
protected $model = null; protected $model = null;
protected function configure() protected function configure()
{ {
$this $this
->setName('menu') ->setName('menu')
->addOption('controller', 'c', Option::VALUE_REQUIRED, 'controller name,use \'all-controller\' when build all menu', null) ->addOption('controller', 'c', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name,use \'all-controller\' when build all menu', null)
->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete the specified menu', '') ->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete the specified menu', '')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force delete menu,without tips', null)
->addOption('equal', 'e', Option::VALUE_OPTIONAL, 'the controller must be equal', null)
->setDescription('Build auth menu from controller'); ->setDescription('Build auth menu from controller');
//要执行的controller必须一样不适用模糊查询
} }
protected function execute(Input $input, Output $output) protected function execute(Input $input, Output $output)
{ {
$this->model = new AuthRule(); $this->model = new AuthRule();
$adminPath = dirname(__DIR__) . DS; $adminPath = dirname(__DIR__) . DS;
$moduleName = 'admin';
//控制器名 //控制器名
$controller = $input->getOption('controller') ?: ''; $controller = $input->getOption('controller') ?: '';
if (!$controller) if (!$controller) {
{
throw new Exception("please input controller name"); throw new Exception("please input controller name");
} }
$force = $input->getOption('force');
//是否为删除模式 //是否为删除模式
$delete = $input->getOption('delete'); $delete = $input->getOption('delete');
if ($delete) //是否控制器完全匹配
{ $equal = $input->getOption('equal');
if ($controller == 'all-controller')
{
if ($delete) {
if (in_array('all-controller', $controller)) {
throw new Exception("could not delete all menu"); throw new Exception("could not delete all menu");
} }
$ids = []; $ids = [];
$list = $this->model->where('name', 'like', "/{$moduleName}/" . strtolower($controller) . "%")->select(); $list = $this->model->where(function ($query) use ($controller, $equal) {
foreach ($list as $k => $v) foreach ($controller as $index => $item) {
{ if (stripos($item, '_') !== false) {
$item = Loader::parseName($item, 1);
}
if (stripos($item, '/') !== false) {
$controllerArr = explode('/', $item);
end($controllerArr);
$key = key($controllerArr);
$controllerArr[$key] = Loader::parseName($controllerArr[$key]);
} else {
$controllerArr = [Loader::parseName($item)];
}
$item = str_replace('_', '\_', implode('/', $controllerArr));
if ($equal) {
$query->whereOr('name', 'eq', $item);
} else {
$query->whereOr('name', 'like', strtolower($item) . "%");
}
}
})->select();
foreach ($list as $k => $v) {
$output->warning($v->name); $output->warning($v->name);
$ids[] = $v->id; $ids[] = $v->id;
} }
if (!$ids) if (!$ids) {
{
throw new Exception("There is no menu to delete"); throw new Exception("There is no menu to delete");
} }
$readyMenu = []; if (!$force) {
$output->info("Are you sure you want to delete all those menu? Type 'yes' to continue: "); $output->info("Are you sure you want to delete all those menu? Type 'yes' to continue: ");
$line = fgets(STDIN); $line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
if (trim($line) != 'yes') if (trim($line) != 'yes') {
{
throw new Exception("Operation is aborted!"); throw new Exception("Operation is aborted!");
} }
}
AuthRule::destroy($ids); AuthRule::destroy($ids);
Cache::rm("__menu__"); Cache::rm("__menu__");
@ -71,23 +93,32 @@ class Menu extends Command
return; return;
} }
if ($controller != 'all-controller') if (!in_array('all-controller', $controller)) {
{ foreach ($controller as $index => $item) {
$controllerArr = explode('/', $controller); if (stripos($item, '_') !== false) {
$item = Loader::parseName($item, 1);
}
if (stripos($item, '/') !== false) {
$controllerArr = explode('/', $item);
end($controllerArr); end($controllerArr);
$key = key($controllerArr); $key = key($controllerArr);
$controllerArr[$key] = ucfirst($controllerArr[$key]); $controllerArr[$key] = ucfirst($controllerArr[$key]);
} else {
$controllerArr = [ucfirst($item)];
}
$adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php'; $adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php';
if (!is_file($adminPath)) if (!is_file($adminPath)) {
{
$output->error("controller not found"); $output->error("controller not found");
return; return;
} }
$this->importRule($controller); $this->importRule($item);
} }
else } else {
{ $authRuleList = AuthRule::select();
$this->model->destroy([]); //生成权限规则备份文件
file_put_contents(RUNTIME_PATH . 'authrule.json', json_encode(collection($authRuleList)->toArray()));
$this->model->where('id', '>', 0)->delete();
$controllerDir = $adminPath . 'controller' . DS; $controllerDir = $adminPath . 'controller' . DS;
// 扫描新的节点信息并导入 // 扫描新的节点信息并导入
$treelist = $this->import($this->scandir($controllerDir)); $treelist = $this->import($this->scandir($controllerDir));
@ -105,16 +136,11 @@ class Menu extends Command
{ {
$result = []; $result = [];
$cdir = scandir($dir); $cdir = scandir($dir);
foreach ($cdir as $value) foreach ($cdir as $value) {
{ if (!in_array($value, array(".", ".."))) {
if (!in_array($value, array(".", ".."))) if (is_dir($dir . DS . $value)) {
{
if (is_dir($dir . DS . $value))
{
$result[$value] = $this->scandir($dir . DS . $value); $result[$value] = $this->scandir($dir . DS . $value);
} } else {
else
{
$result[] = $value; $result[] = $value;
} }
} }
@ -131,19 +157,14 @@ class Menu extends Command
public function import($dirarr, $parentdir = []) public function import($dirarr, $parentdir = [])
{ {
$menuarr = []; $menuarr = [];
foreach ($dirarr as $k => $v) foreach ($dirarr as $k => $v) {
{ if (is_array($v)) {
if (is_array($v))
{
//当前是文件夹 //当前是文件夹
$nowparentdir = array_merge($parentdir, [$k]); $nowparentdir = array_merge($parentdir, [$k]);
$this->import($v, $nowparentdir); $this->import($v, $nowparentdir);
} } else {
else
{
//只匹配PHP文件 //只匹配PHP文件
if (!preg_match('/^(\w+)\.php$/', $v, $matchone)) if (!preg_match('/^(\w+)\.php$/', $v, $matchone)) {
{
continue; continue;
} }
//导入文件 //导入文件
@ -157,10 +178,16 @@ class Menu extends Command
protected function importRule($controller) protected function importRule($controller)
{ {
$controller = str_replace('\\', '/', $controller);
if (stripos($controller, '/') !== false) {
$controllerArr = explode('/', $controller); $controllerArr = explode('/', $controller);
end($controllerArr); end($controllerArr);
$key = key($controllerArr); $key = key($controllerArr);
$controllerArr[$key] = ucfirst($controllerArr[$key]); $controllerArr[$key] = ucfirst($controllerArr[$key]);
} else {
$key = 0;
$controllerArr = [ucfirst($controller)];
}
$classSuffix = Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; $classSuffix = Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
$className = "\\app\\admin\\controller\\" . implode("\\", $controllerArr) . $classSuffix; $className = "\\app\\admin\\controller\\" . implode("\\", $controllerArr) . $classSuffix;
@ -179,8 +206,7 @@ class Menu extends Command
//反射机制调用类的注释和方法名 //反射机制调用类的注释和方法名
$reflector = new ReflectionClass($className); $reflector = new ReflectionClass($className);
if (isset($tempClassFile)) if (isset($tempClassFile)) {
{
//删除临时文件 //删除临时文件
@unlink($tempClassFile); @unlink($tempClassFile);
} }
@ -188,25 +214,33 @@ class Menu extends Command
//只匹配公共的方法 //只匹配公共的方法
$methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC); $methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
$classComment = $reflector->getDocComment(); $classComment = $reflector->getDocComment();
//判断是否有启用软删除
$softDeleteMethods = ['destroy', 'restore', 'recyclebin'];
$withSofeDelete = false;
$modelRegexArr = ["/\\\$this\->model\s*=\s*model\(['|\"](\w+)['|\"]\);/", "/\\\$this\->model\s*=\s*new\s+([a-zA-Z\\\]+);/"];
$modelRegex = preg_match($modelRegexArr[0], $classContent) ? $modelRegexArr[0] : $modelRegexArr[1];
preg_match_all($modelRegex, $classContent, $matches);
if (isset($matches[1]) && isset($matches[1][0]) && $matches[1][0]) {
\think\Request::instance()->module('admin');
$model = model($matches[1][0]);
if (in_array('trashed', get_class_methods($model))) {
$withSofeDelete = true;
}
}
//忽略的类 //忽略的类
if (stripos($classComment, "@internal") !== FALSE) if (stripos($classComment, "@internal") !== false) {
{
return; return;
} }
preg_match_all('#(@.*?)\n#s', $classComment, $annotations); preg_match_all('#(@.*?)\n#s', $classComment, $annotations);
$controllerIcon = 'fa fa-circle-o'; $controllerIcon = 'fa fa-circle-o';
$controllerRemark = ''; $controllerRemark = '';
//判断注释中是否设置了icon值 //判断注释中是否设置了icon值
if (isset($annotations[1])) if (isset($annotations[1])) {
{ foreach ($annotations[1] as $tag) {
foreach ($annotations[1] as $tag) if (stripos($tag, '@icon') !== false) {
{
if (stripos($tag, '@icon') !== FALSE)
{
$controllerIcon = substr($tag, stripos($tag, ' ') + 1); $controllerIcon = substr($tag, stripos($tag, ' ') + 1);
} }
if (stripos($tag, '@remark') !== FALSE) if (stripos($tag, '@remark') !== false) {
{
$controllerRemark = substr($tag, stripos($tag, ' ') + 1); $controllerRemark = substr($tag, stripos($tag, ' ') + 1);
} }
} }
@ -217,56 +251,73 @@ class Menu extends Command
//导入中文语言包 //导入中文语言包
\think\Lang::load(dirname(__DIR__) . DS . 'lang/zh-cn.php'); \think\Lang::load(dirname(__DIR__) . DS . 'lang/zh-cn.php');
//先入菜单的数据 //先入菜单的数据
$pid = 0; $pid = 0;
$name = "/admin"; foreach ($controllerArr as $k => $v) {
foreach (explode('/', $controller) as $k => $v) $key = $k + 1;
{ //驼峰转下划线
$name .= '/' . strtolower($v); $controllerNameArr = array_slice($controllerArr, 0, $key);
$title = (!isset($controllerArr[$k + 1]) ? $controllerTitle : ''); foreach ($controllerNameArr as &$val) {
$icon = (!isset($controllerArr[$k + 1]) ? $controllerIcon : 'fa fa-list'); $val = strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $val), "_"));
$remark = (!isset($controllerArr[$k + 1]) ? $controllerRemark : ''); }
$title = $title ? $title : __(ucfirst($v) . ' manager'); unset($val);
$name = implode('/', $controllerNameArr);
$title = (!isset($controllerArr[$key]) ? $controllerTitle : '');
$icon = (!isset($controllerArr[$key]) ? $controllerIcon : 'fa fa-list');
$remark = (!isset($controllerArr[$key]) ? $controllerRemark : '');
$title = $title ? $title : $v;
$rulemodel = $this->model->get(['name' => $name]); $rulemodel = $this->model->get(['name' => $name]);
if (!$rulemodel) if (!$rulemodel) {
{
$this->model $this->model
->data(['pid' => $pid, 'name' => $name, 'title' => $title, 'icon' => $icon, 'remark' => $remark, 'ismenu' => 1, 'status' => 'normal']) ->data(['pid' => $pid, 'name' => $name, 'title' => $title, 'icon' => $icon, 'remark' => $remark, 'ismenu' => 1, 'status' => 'normal'])
->isUpdate(false) ->isUpdate(false)
->save(); ->save();
$pid = $this->model->id; $pid = $this->model->id;
} } else {
else
{
$pid = $rulemodel->id; $pid = $rulemodel->id;
} }
} }
$ruleArr = []; $ruleArr = [];
foreach ($methods as $m => $n) foreach ($methods as $m => $n) {
{
//过滤特殊的类 //过滤特殊的类
if (substr($n->name, 0, 2) == '__' || $n->name == '_initialize') if (substr($n->name, 0, 2) == '__' || $n->name == '_initialize') {
{ continue;
}
//未启用软删除时过滤相关方法
if (!$withSofeDelete && in_array($n->name, $softDeleteMethods)) {
continue; continue;
} }
//只匹配符合的方法 //只匹配符合的方法
if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo)) if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo)) {
{
unset($methods[$m]); unset($methods[$m]);
continue; continue;
} }
$comment = $reflector->getMethod($n->name)->getDocComment(); $comment = $reflector->getMethod($n->name)->getDocComment();
//忽略的方法 //忽略的方法
if (stripos($comment, "@internal") !== FALSE) if (stripos($comment, "@internal") !== false) {
{
continue; continue;
} }
//过滤掉其它字符 //过滤掉其它字符
$comment = preg_replace(array('/^\/\*\*(.*)[\n\r\t]/u', '/[\s]+\*\//u', '/\*\s@(.*)/u', '/[\s|\*]+/u'), '', $comment); $comment = preg_replace(array('/^\/\*\*(.*)[\n\r\t]/u', '/[\s]+\*\//u', '/\*\s@(.*)/u', '/[\s|\*]+/u'), '', $comment);
$ruleArr[] = array('pid' => $pid, 'name' => $name . "/" . strtolower($n->name), 'icon' => 'fa fa-circle-o', 'title' => $comment ? $comment : $n->name, 'ismenu' => 0, 'status' => 'normal'); $title = $comment ? $comment : ucfirst($n->name);
//获取主键作为AuthRule更新依据
$id = $this->getAuthRulePK($name . "/" . strtolower($n->name));
$ruleArr[] = array('id' => $id, 'pid' => $pid, 'name' => $name . "/" . strtolower($n->name), 'icon' => 'fa fa-circle-o', 'title' => $title, 'ismenu' => 0, 'status' => 'normal');
} }
$this->model->saveAll($ruleArr); $this->model->isUpdate(false)->saveAll($ruleArr);
} }
//获取主键
protected function getAuthRulePK($name)
{
if (!empty($name)) {
$id = $this->model
->where('name', $name)
->value('id');
return $id ? $id : null;
}
}
} }

View File

@ -27,20 +27,20 @@ class Min extends Command
->setName('min') ->setName('min')
->addOption('module', 'm', Option::VALUE_REQUIRED, 'module name(frontend or backend),use \'all\' when build all modules', null) ->addOption('module', 'm', Option::VALUE_REQUIRED, 'module name(frontend or backend),use \'all\' when build all modules', null)
->addOption('resource', 'r', Option::VALUE_REQUIRED, 'resource name(js or css),use \'all\' when build all resources', null) ->addOption('resource', 'r', Option::VALUE_REQUIRED, 'resource name(js or css),use \'all\' when build all resources', null)
->addOption('optimize', 'o', Option::VALUE_OPTIONAL, 'optimize type(uglify|closure|none)', 'none')
->setDescription('Compress js and css file'); ->setDescription('Compress js and css file');
} }
protected function execute(Input $input, Output $output) protected function execute(Input $input, Output $output)
{ {
$module = $input->getOption('module') ? : ''; $module = $input->getOption('module') ?: '';
$resource = $input->getOption('resource') ? : ''; $resource = $input->getOption('resource') ?: '';
$optimize = $input->getOption('optimize') ?: 'none';
if (!$module || !in_array($module, ['frontend', 'backend', 'all'])) if (!$module || !in_array($module, ['frontend', 'backend', 'all'])) {
{
throw new Exception('Please input correct module name'); throw new Exception('Please input correct module name');
} }
if (!$resource || !in_array($resource, ['js', 'css', 'all'])) if (!$resource || !in_array($resource, ['js', 'css', 'all'])) {
{
throw new Exception('Please input correct resource name'); throw new Exception('Please input correct resource name');
} }
@ -51,58 +51,55 @@ class Min extends Command
$publicPath = ROOT_PATH . 'public' . DS; $publicPath = ROOT_PATH . 'public' . DS;
$tempFile = $minPath . 'temp.js'; $tempFile = $minPath . 'temp.js';
// Winsows下请手动配置配置该值 $nodeExec = '';
$nodeExec = "";
if (!$nodeExec) if (!$nodeExec) {
{ if (IS_WIN) {
if (IS_WIN) // Winsows下请手动配置配置该值,一般将该值配置为 '"C:\Program Files\nodejs\node.exe"'除非你的Node安装路径有变更
{ $nodeExec = 'C:\Program Files\nodejs\node.exe';
throw new Exception("node environment not found!please check http://doc.fastadmin.net/faq.html !"); if (file_exists($nodeExec)) {
$nodeExec = '"' . $nodeExec . '"';
} else {
// 如果 '"C:\Program Files\nodejs\node.exe"' 不存在可能是node安装路径有变更
// 但安装node会自动配置环境变量直接执行 '"node.exe"' 提高第一次使用压缩打包的成功率
$nodeExec = '"node.exe"';
} }
} else {
try try {
{
$nodeExec = exec("which node"); $nodeExec = exec("which node");
if (!$nodeExec) if (!$nodeExec) {
{
throw new Exception("node environment not found!please install node first!"); throw new Exception("node environment not found!please install node first!");
} }
} } catch (Exception $e) {
catch (Exception $e)
{
throw new Exception($e->getMessage()); throw new Exception($e->getMessage());
} }
} }
}
foreach ($moduleArr as $mod) foreach ($moduleArr as $mod) {
{ foreach ($resourceArr as $res) {
foreach ($resourceArr as $res)
{
$data = [ $data = [
'publicPath' => $publicPath, 'publicPath' => $publicPath,
'jsBaseName' => str_replace('{module}', $mod, $this->options['jsBaseName']), 'jsBaseName' => str_replace('{module}', $mod, $this->options['jsBaseName']),
'jsBaseUrl' => $this->options['jsBaseUrl'], 'jsBaseUrl' => $this->options['jsBaseUrl'],
'cssBaseName' => str_replace('{module}', $mod, $this->options['cssBaseName']), 'cssBaseName' => str_replace('{module}', $mod, $this->options['cssBaseName']),
'cssBaseUrl' => $this->options['cssBaseUrl'], 'cssBaseUrl' => $this->options['cssBaseUrl'],
'jsBasePath' => ROOT_PATH . $this->options['jsBaseUrl'], 'jsBasePath' => str_replace(DS, '/', ROOT_PATH . $this->options['jsBaseUrl']),
'cssBasePath' => ROOT_PATH . $this->options['cssBaseUrl'], 'cssBasePath' => str_replace(DS, '/', ROOT_PATH . $this->options['cssBaseUrl']),
'optimize' => $optimize,
'ds' => DS, 'ds' => DS,
]; ];
//源文件 //源文件
$from = $data["{$res}BasePath"] . $data["{$res}BaseName"] . '.' . $res; $from = $data["{$res}BasePath"] . $data["{$res}BaseName"] . '.' . $res;
if (!is_file($from)) if (!is_file($from)) {
{
$output->error("{$res} source file not found!file:{$from}"); $output->error("{$res} source file not found!file:{$from}");
continue; continue;
} }
if ($res == "js") if ($res == "js") {
{
$content = file_get_contents($from); $content = file_get_contents($from);
preg_match("/require\.config\(\{[\n]+(.*?)\n\}\);/is", $content, $matches); preg_match("/require\.config\(\{[\r\n]?[\n]?+(.*?)[\r\n]?[\n]?}\);/is", $content, $matches);
if (!isset($matches[1])) if (!isset($matches[1])) {
{
$output->error("js config not found!"); $output->error("js config not found!");
continue; continue;
} }
@ -115,11 +112,17 @@ class Min extends Command
$output->info("Compress " . $data["{$res}BaseName"] . ".{$res}"); $output->info("Compress " . $data["{$res}BaseName"] . ".{$res}");
// 执行压缩 // 执行压缩
echo exec("{$nodeExec} {$minPath}r.js -o {$tempFile} >> {$minPath}node.log"); $command = "{$nodeExec} \"{$minPath}r.js\" -o \"{$tempFile}\" >> \"{$minPath}node.log\"";
if ($output->isDebug()) {
$output->warning($command);
}
echo exec($command);
} }
} }
if (!$output->isDebug()) {
@unlink($tempFile); @unlink($tempFile);
}
$output->info("Build Successed!"); $output->info("Build Successed!");
} }
@ -134,16 +137,14 @@ class Min extends Command
protected function writeToFile($name, $data, $pathname) protected function writeToFile($name, $data, $pathname)
{ {
$search = $replace = []; $search = $replace = [];
foreach ($data as $k => $v) foreach ($data as $k => $v) {
{
$search[] = "{%{$k}%}"; $search[] = "{%{$k}%}";
$replace[] = $v; $replace[] = $v;
} }
$stub = file_get_contents($this->getStub($name)); $stub = file_get_contents($this->getStub($name));
$content = str_replace($search, $replace, $stub); $content = str_replace($search, $replace, $stub);
if (!is_dir(dirname($pathname))) if (!is_dir(dirname($pathname))) {
{
mkdir(strtolower(dirname($pathname)), 0755, true); mkdir(strtolower(dirname($pathname)), 0755, true);
} }
return file_put_contents($pathname, $content); return file_put_contents($pathname, $content);
@ -156,7 +157,6 @@ class Min extends Command
*/ */
protected function getStub($name) protected function getStub($name)
{ {
return __DIR__ . '/Min/stubs/' . $name . '.stub'; return __DIR__ . DS . 'Min' . DS . 'stubs' . DS . $name . '.stub';
} }
} }

View File

@ -1,5 +1,6 @@
({ ({
cssIn: "{%cssBasePath%}{%cssBaseName%}.css", cssIn: "{%cssBasePath%}{%cssBaseName%}.css",
out: "{%cssBasePath%}{%cssBaseName%}.min.css", out: "{%cssBasePath%}{%cssBaseName%}.min.css",
optimizeCss: "default" optimizeCss: "default",
optimize: "{%optimize%}"
}) })

View File

@ -2,7 +2,8 @@
{%config%} {%config%}
, ,
optimizeCss: "standard", optimizeCss: "standard",
optimize: "none", //可使用uglify|closure|none optimize: "{%optimize%}", //可使用uglify|closure|none
preserveLicenseComments: false,
removeCombined: false, removeCombined: false,
baseUrl: "{%jsBasePath%}", //JS文件所在的基础目录 baseUrl: "{%jsBasePath%}", //JS文件所在的基础目录
name: "{%jsBaseName%}", //来源文件,不包含后缀 name: "{%jsBaseName%}", //来源文件,不包含后缀

157
application/admin/common.php 100644 → 100755
View File

@ -5,7 +5,9 @@ use fast\Form;
use fast\Tree; use fast\Tree;
use think\Db; use think\Db;
/** if (!function_exists('build_select')) {
/**
* 生成下拉列表 * 生成下拉列表
* @param string $name * @param string $name
* @param mixed $options * @param mixed $options
@ -13,125 +15,182 @@ use think\Db;
* @param mixed $attr * @param mixed $attr
* @return string * @return string
*/ */
function build_select($name, $options, $selected = [], $attr = []) function build_select($name, $options, $selected = [], $attr = [])
{ {
$options = is_array($options) ? $options : explode(',', $options); $options = is_array($options) ? $options : explode(',', $options);
$selected = is_array($selected) ? $selected : explode(',', $selected); $selected = is_array($selected) ? $selected : explode(',', $selected);
return Form::select($name, $options, $selected, $attr); return Form::select($name, $options, $selected, $attr);
}
} }
/** if (!function_exists('build_radios')) {
/**
* 生成单选按钮组 * 生成单选按钮组
* @param string $name * @param string $name
* @param array $list * @param array $list
* @param mixed $selected * @param mixed $selected
* @return string * @return string
*/ */
function build_radios($name, $list = [], $selected = null) function build_radios($name, $list = [], $selected = null)
{ {
$html = []; $html = [];
$selected = is_null($selected) ? key($list) : $selected; $selected = is_null($selected) ? key($list) : $selected;
$selected = is_array($selected) ? $selected : explode(',', $selected); $selected = is_array($selected) ? $selected : explode(',', $selected);
foreach ($list as $k => $v) foreach ($list as $k => $v) {
{
$html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::radio($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"])); $html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::radio($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
} }
return implode(' ', $html); return '<div class="radio">' . implode(' ', $html) . '</div>';
}
} }
/** if (!function_exists('build_checkboxs')) {
/**
* 生成复选按钮组 * 生成复选按钮组
* @param string $name * @param string $name
* @param array $list * @param array $list
* @param mixed $selected * @param mixed $selected
* @return string * @return string
*/ */
function build_checkboxs($name, $list = [], $selected = null) function build_checkboxs($name, $list = [], $selected = null)
{ {
$html = []; $html = [];
$selected = is_null($selected) ? [] : $selected; $selected = is_null($selected) ? [] : $selected;
$selected = is_array($selected) ? $selected : explode(',', $selected); $selected = is_array($selected) ? $selected : explode(',', $selected);
foreach ($list as $k => $v) foreach ($list as $k => $v) {
{
$html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::checkbox($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"])); $html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::checkbox($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
} }
return implode(' ', $html); return '<div class="checkbox">' . implode(' ', $html) . '</div>';
}
} }
/**
if (!function_exists('build_category_select')) {
/**
* 生成分类下拉列表框 * 生成分类下拉列表框
* @param string $name * @param string $name
* @param string $type * @param string $type
* @param mixed $selected * @param mixed $selected
* @param array $attr * @param array $attr
* @param array $header
* @return string * @return string
*/ */
function build_category_select($name, $type, $selected = null, $attr = [], $header = []) function build_category_select($name, $type, $selected = null, $attr = [], $header = [])
{ {
$tree = Tree::instance(); $tree = Tree::instance();
$tree->init(Category::getCategoryArray($type), 'pid'); $tree->init(Category::getCategoryArray($type), 'pid');
$categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name'); $categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
$categorydata = $header ? $header : []; $categorydata = $header ? $header : [];
foreach ($categorylist as $k => $v) foreach ($categorylist as $k => $v) {
{
$categorydata[$v['id']] = $v['name']; $categorydata[$v['id']] = $v['name'];
} }
$attr = array_merge(['id' => "c-{$name}", 'class' => 'form-control selectpicker'], $attr); $attr = array_merge(['id' => "c-{$name}", 'class' => 'form-control selectpicker'], $attr);
return build_select($name, $categorydata, $selected, $attr); return build_select($name, $categorydata, $selected, $attr);
}
} }
/** if (!function_exists('build_toolbar')) {
/**
* 生成表格操作按钮栏 * 生成表格操作按钮栏
* @param array $btns 按钮组 * @param array $btns 按钮组
* @param array $attr 按钮属性值 * @param array $attr 按钮属性值
* @return string * @return string
*/ */
function build_toolbar($btns = NULL, $attr = []) function build_toolbar($btns = null, $attr = [])
{ {
$btns = $btns ? $btns : ['refresh', 'add', 'edit', 'delete']; $auth = \app\admin\library\Auth::instance();
$controller = str_replace('.', '/', strtolower(think\Request::instance()->controller()));
$btns = $btns ? $btns : ['refresh', 'add', 'edit', 'del', 'import'];
$btns = is_array($btns) ? $btns : explode(',', $btns); $btns = is_array($btns) ? $btns : explode(',', $btns);
$index = array_search('delete', $btns);
if ($index !== false) {
$btns[$index] = 'del';
}
$btnAttr = [ $btnAttr = [
'refresh' => ['javascript:;', 'btn btn-primary btn-refresh', 'fa fa-refresh', ''], 'refresh' => ['javascript:;', 'btn btn-primary btn-refresh', 'fa fa-refresh', '', __('Refresh')],
'add' => ['javascript:;', 'btn btn-success btn-add', 'fa fa-plus', __('Add')], 'add' => ['javascript:;', 'btn btn-success btn-add', 'fa fa-plus', __('Add'), __('Add')],
'edit' => ['javascript:;', 'btn btn-success btn-edit btn-disabled disabled', 'fa fa-pencil', __('Edit')], 'edit' => ['javascript:;', 'btn btn-success btn-edit btn-disabled disabled', 'fa fa-pencil', __('Edit'), __('Edit')],
'delete' => ['javascript:;', 'btn btn-danger btn-del btn-disabled disabled', 'fa fa-trash', __('Delete')], 'del' => ['javascript:;', 'btn btn-danger btn-del btn-disabled disabled', 'fa fa-trash', __('Delete'), __('Delete')],
'import' => ['javascript:;', 'btn btn-info btn-import', 'fa fa-upload', __('Import'), __('Import')],
]; ];
$btnAttr = array_merge($btnAttr, $attr); $btnAttr = array_merge($btnAttr, $attr);
$html = []; $html = [];
foreach ($btns as $k => $v) foreach ($btns as $k => $v) {
{ //如果未定义或没有权限
if (!isset($btnAttr[$v])) if (!isset($btnAttr[$v]) || ($v !== 'refresh' && !$auth->check("{$controller}/{$v}"))) {
{
continue; continue;
} }
list($href, $class, $icon, $text) = $btnAttr[$v]; list($href, $class, $icon, $text, $title) = $btnAttr[$v];
$html[] = '<a href="' . $href . '" class="' . $class . '" ><i class="' . $icon . '"></i> ' . $text . '</a>'; //$extend = $v == 'import' ? 'id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"' : '';
//$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '" ' . $extend . '><i class="' . $icon . '"></i> ' . $text . '</a>';
if ($v == 'import') {
$template = str_replace('/', '_', $controller);
$download = '';
if (file_exists("./template/{$template}.xlsx")) {
$download .= "<li><a href=\"/template/{$template}.xlsx\" target=\"_blank\">XLSX模版</a></li>";
}
if (file_exists("./template/{$template}.xls")) {
$download .= "<li><a href=\"/template/{$template}.xls\" target=\"_blank\">XLS模版</a></li>";
}
if (file_exists("./template/{$template}.csv")) {
$download .= empty($download) ? '' : "<li class=\"divider\"></li>";
$download .= "<li><a href=\"/template/{$template}.csv\" target=\"_blank\">CSV模版</a></li>";
}
$download .= empty($download) ? '' : "\n ";
if (!empty($download)) {
$html[] = <<<EOT
<div class="btn-group">
<button type="button" href="{$href}" class="btn btn-info btn-import" title="{$title}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="{$icon}"></i> {$text}</button>
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" title="下载批量导入模版">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">{$download}</ul>
</div>
EOT;
} else {
$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="' . $icon . '"></i> ' . $text . '</a>';
}
} else {
$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '"><i class="' . $icon . '"></i> ' . $text . '</a>';
}
} }
return implode(' ', $html); return implode(' ', $html);
}
} }
/** if (!function_exists('build_heading')) {
/**
* 生成页面Heading * 生成页面Heading
* *
* @param string $title * @param string $path 指定的path
* @param string $content
* @return string * @return string
*/ */
function build_heading($title = NULL, $content = NULL) function build_heading($path = null, $container = true)
{
if (is_null($title) && is_null($content))
{ {
$path = request()->pathinfo(); $title = $content = '';
$path = $path[0] == '/' ? $path : '/' . $path; if (is_null($path)) {
$action = request()->action();
$controller = str_replace('.', '/', request()->controller());
$path = strtolower($controller . ($action && $action != 'index' ? '/' . $action : ''));
}
// 根据当前的URI自动匹配父节点的标题和备注 // 根据当前的URI自动匹配父节点的标题和备注
$data = Db::name('auth_rule')->where('name', $path)->field('title,remark')->find(); $data = Db::name('auth_rule')->where('name', $path)->field('title,remark')->find();
if ($data) if ($data) {
{ $title = __($data['title']);
$title = $data['title']; $content = __($data['remark']);
$content = $data['remark'];
} }
} if (!$content) {
if (!$content)
return ''; return '';
return '<div class="panel-heading"><div class="panel-lead"><em>' . $title . '</em>' . $content . '</div></div>'; }
$result = '<div class="panel-lead"><em>' . $title . '</em>' . $content . '</div>';
if ($container) {
$result = '<div class="panel-heading">' . $result . '</div>';
}
return $result;
}
} }

View File

@ -0,0 +1,360 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use fast\Http;
use think\addons\AddonException;
use think\addons\Service;
use think\Cache;
use think\Config;
use think\Exception;
/**
* 插件管理
*
* @icon fa fa-cube
* @remark 可在线安装、卸载、禁用、启用插件同时支持添加本地插件。FastAdmin已上线插件商店 ,你可以发布你的免费或付费插件:<a href="https://www.fastadmin.net/store.html" target="_blank">https://www.fastadmin.net/store.html</a>
*/
class Addon extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
}
/**
* 查看
*/
public function index()
{
$addons = get_addon_list();
foreach ($addons as $k => &$v) {
$config = get_addon_config($v['name']);
$v['config'] = $config ? 1 : 0;
$v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
}
$this->assignconfig(['addons' => $addons]);
return $this->view->fetch();
}
/**
* 配置
*/
public function config($ids = null)
{
$name = $this->request->get("name");
if (!$name) {
$this->error(__('Parameter %s can not be empty', $ids ? 'id' : 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
if (!is_dir(ADDON_PATH . $name)) {
$this->error(__('Directory not found'));
}
$info = get_addon_info($name);
$config = get_addon_fullconfig($name);
if (!$info) {
$this->error(__('No Results were found'));
}
if ($this->request->isPost()) {
$params = $this->request->post("row/a");
if ($params) {
foreach ($config as $k => &$v) {
if (isset($params[$v['name']])) {
if ($v['type'] == 'array') {
$params[$v['name']] = is_array($params[$v['name']]) ? $params[$v['name']] : (array)json_decode($params[$v['name']], true);
$value = $params[$v['name']];
} else {
$value = is_array($params[$v['name']]) ? implode(',', $params[$v['name']]) : $params[$v['name']];
}
$v['value'] = $value;
}
}
try {
//更新配置文件
set_addon_fullconfig($name, $config);
Service::refresh();
$this->success();
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
}
$this->error(__('Parameter %s can not be empty', ''));
}
$tips = [];
foreach ($config as $index => &$item) {
if ($item['name'] == '__tips__') {
$tips = $item;
unset($config[$index]);
}
}
$this->view->assign("addon", ['info' => $info, 'config' => $config, 'tips' => $tips]);
$configFile = ADDON_PATH . $name . DS . 'config.html';
$viewFile = is_file($configFile) ? $configFile : '';
return $this->view->fetch($viewFile);
}
/**
* 安装
*/
public function install()
{
$name = $this->request->post("name");
$force = (int)$this->request->post("force");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
try {
$uid = $this->request->post("uid");
$token = $this->request->post("token");
$version = $this->request->post("version");
$faversion = $this->request->post("faversion");
$extend = [
'uid' => $uid,
'token' => $token,
'version' => $version,
'faversion' => $faversion
];
Service::install($name, $force, $extend);
$info = get_addon_info($name);
$info['config'] = get_addon_config($name) ? 1 : 0;
$info['state'] = 1;
$this->success(__('Install successful'), null, ['addon' => $info]);
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()), $e->getCode());
}
}
/**
* 卸载
*/
public function uninstall()
{
$name = $this->request->post("name");
$force = (int)$this->request->post("force");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
try {
Service::uninstall($name, $force);
$this->success(__('Uninstall successful'));
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
}
/**
* 禁用启用
*/
public function state()
{
$name = $this->request->post("name");
$action = $this->request->post("action");
$force = (int)$this->request->post("force");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
try {
$action = $action == 'enable' ? $action : 'disable';
//调用启用、禁用的方法
Service::$action($name, $force);
Cache::rm('__menu__');
$this->success(__('Operate successful'));
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
}
/**
* 本地上传
*/
public function local()
{
Config::set('default_return_type', 'json');
$file = $this->request->file('file');
$addonTmpDir = RUNTIME_PATH . 'addons' . DS;
if (!is_dir($addonTmpDir)) {
@mkdir($addonTmpDir, 0755, true);
}
$info = $file->rule('uniqid')->validate(['size' => 10240000, 'ext' => 'zip'])->move($addonTmpDir);
if ($info) {
$tmpName = substr($info->getFilename(), 0, stripos($info->getFilename(), '.'));
$tmpAddonDir = ADDON_PATH . $tmpName . DS;
$tmpFile = $addonTmpDir . $info->getSaveName();
try {
Service::unzip($tmpName);
unset($info);
@unlink($tmpFile);
$infoFile = $tmpAddonDir . 'info.ini';
if (!is_file($infoFile)) {
throw new Exception(__('Addon info file was not found'));
}
$config = Config::parse($infoFile, '', $tmpName);
$name = isset($config['name']) ? $config['name'] : '';
if (!$name) {
throw new Exception(__('Addon info file data incorrect'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
throw new Exception(__('Addon name incorrect'));
}
$newAddonDir = ADDON_PATH . $name . DS;
if (is_dir($newAddonDir)) {
throw new Exception(__('Addon already exists'));
}
//重命名插件文件夹
rename($tmpAddonDir, $newAddonDir);
try {
//默认禁用该插件
$info = get_addon_info($name);
if ($info['state']) {
$info['state'] = 0;
set_addon_info($name, $info);
}
//执行插件的安装方法
$class = get_addon_class($name);
if (class_exists($class)) {
$addon = new $class();
$addon->install();
}
//导入SQL
Service::importsql($name);
$info['config'] = get_addon_config($name) ? 1 : 0;
$this->success(__('Offline installed tips'), null, ['addon' => $info]);
} catch (Exception $e) {
@rmdirs($newAddonDir);
throw new Exception(__($e->getMessage()));
}
} catch (Exception $e) {
unset($info);
@unlink($tmpFile);
@rmdirs($tmpAddonDir);
$this->error(__($e->getMessage()));
}
} else {
// 上传失败获取错误信息
$this->error(__($file->getError()));
}
}
/**
* 更新插件
*/
public function upgrade()
{
$name = $this->request->post("name");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
try {
$uid = $this->request->post("uid");
$token = $this->request->post("token");
$version = $this->request->post("version");
$faversion = $this->request->post("faversion");
$extend = [
'uid' => $uid,
'token' => $token,
'version' => $version,
'faversion' => $faversion
];
//调用更新的方法
Service::upgrade($name, $extend);
Cache::rm('__menu__');
$this->success(__('Operate successful'));
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
}
/**
* 已装插件
*/
public function downloaded()
{
$offset = (int)$this->request->get("offset");
$limit = (int)$this->request->get("limit");
$filter = $this->request->get("filter");
$search = $this->request->get("search");
$search = htmlspecialchars(strip_tags($search));
$onlineaddons = Cache::get("onlineaddons");
if (!is_array($onlineaddons)) {
$onlineaddons = [];
$result = Http::sendRequest(config('fastadmin.api_url') . '/addon/index');
if ($result['ret']) {
$json = (array)json_decode($result['msg'], true);
$rows = isset($json['rows']) ? $json['rows'] : [];
foreach ($rows as $index => $row) {
$onlineaddons[$row['name']] = $row;
}
}
Cache::set("onlineaddons", $onlineaddons, 600);
}
$filter = (array)json_decode($filter, true);
$addons = get_addon_list();
$list = [];
foreach ($addons as $k => $v) {
if ($search && stripos($v['name'], $search) === false && stripos($v['intro'], $search) === false) {
continue;
}
if (isset($onlineaddons[$v['name']])) {
$v = array_merge($v, $onlineaddons[$v['name']]);
} else {
$v['category_id'] = 0;
$v['flag'] = '';
$v['banner'] = '';
$v['image'] = '';
$v['donateimage'] = '';
$v['demourl'] = '';
$v['price'] = __('None');
$v['screenshots'] = [];
$v['releaselist'] = [];
}
$v['url'] = addon_url($v['name']);
$v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
$v['createtime'] = filemtime(ADDON_PATH . $v['name']);
if ($filter && isset($filter['category_id']) && is_numeric($filter['category_id']) && $filter['category_id'] != $v['category_id']) {
continue;
}
$list[] = $v;
}
$total = count($list);
if ($limit) {
$list = array_slice($list, $offset, $limit);
}
$result = array("total" => $total, "rows" => $list);
$callback = $this->request->get('callback') ? "jsonp" : "json";
return $callback($result);
}
}

View File

@ -4,8 +4,7 @@ namespace app\admin\controller;
use app\common\controller\Backend; use app\common\controller\Backend;
use fast\Random; use fast\Random;
use RecursiveDirectoryIterator; use think\addons\Service;
use RecursiveIteratorIterator;
use think\Cache; use think\Cache;
use think\Config; use think\Config;
use think\Db; use think\Db;
@ -36,12 +35,10 @@ class Ajax extends Backend
public function lang() public function lang()
{ {
header('Content-Type: application/javascript'); header('Content-Type: application/javascript');
$callback = $this->request->get('callback');
$controllername = input("controllername"); $controllername = input("controllername");
//默认只加载了控制器对应的语言名,你还根据控制器名来加载额外的语言包
$this->loadlang($controllername); $this->loadlang($controllername);
//强制输出JSON Object return jsonp(Lang::get(), 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
$result = 'define(' . json_encode(Lang::get(), JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE) . ');';
return $result;
} }
/** /**
@ -49,35 +46,52 @@ class Ajax extends Backend
*/ */
public function upload() public function upload()
{ {
$this->code = -1; Config::set('default_return_type', 'json');
$file = $this->request->file('file'); $file = $this->request->file('file');
if (empty($file)) if (empty($file)) {
{ $this->error(__('No file upload or server upload limit exceeded'));
$this->msg = "未上传文件或超出服务器上传限制";
return;
} }
//判断是否已经存在附件 //判断是否已经存在附件
$sha1 = $file->hash(); $sha1 = $file->hash();
$uploaded = model("attachment")->where('sha1', $sha1)->find(); $extparam = $this->request->post();
if ($uploaded)
{
$this->code = 1;
$this->data = [
'url' => $uploaded['url']
];
return;
}
$upload = Config::get('upload'); $upload = Config::get('upload');
preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches); preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches);
$type = strtolower($matches[2]); $type = strtolower($matches[2]);
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3]; $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
$size = (int) $upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0); $size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
$fileInfo = $file->getInfo(); $fileInfo = $file->getInfo();
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION)); $suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
$suffix = $suffix ? $suffix : 'file'; $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
$mimetypeArr = explode(',', strtolower($upload['mimetype']));
$typeArr = explode('/', $fileInfo['type']);
//禁止上传PHP和HTML文件
if (in_array($fileInfo['type'], ['text/x-php', 'text/html']) || in_array($suffix, ['php', 'html', 'htm'])) {
$this->error(__('Uploaded file format is limited'));
}
//验证文件后缀
if ($upload['mimetype'] !== '*' &&
(
!in_array($suffix, $mimetypeArr)
|| (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr)))
)
) {
$this->error(__('Uploaded file format is limited'));
}
//验证是否为图片文件
$imagewidth = $imageheight = 0;
if (in_array($fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) {
$imgInfo = getimagesize($fileInfo['tmp_name']);
if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
$this->error(__('Uploaded file is not a valid image'));
}
$imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
$imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
}
$replaceArr = [ $replaceArr = [
'{year}' => date("Y"), '{year}' => date("Y"),
'{mon}' => date("m"), '{mon}' => date("m"),
@ -99,16 +113,10 @@ class Ajax extends Backend
$fileName = substr($savekey, strripos($savekey, '/') + 1); $fileName = substr($savekey, strripos($savekey, '/') + 1);
// //
$splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName); $splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName);
if ($splInfo) if ($splInfo) {
{
$imagewidth = $imageheight = 0;
if (in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']))
{
$imgInfo = getimagesize($splInfo->getPathname());
$imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
$imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
}
$params = array( $params = array(
'admin_id' => (int)$this->auth->id,
'user_id' => 0,
'filesize' => $fileInfo['size'], 'filesize' => $fileInfo['size'],
'imagewidth' => $imagewidth, 'imagewidth' => $imagewidth,
'imageheight' => $imageheight, 'imageheight' => $imageheight,
@ -119,17 +127,18 @@ class Ajax extends Backend
'uploadtime' => time(), 'uploadtime' => time(),
'storage' => 'local', 'storage' => 'local',
'sha1' => $sha1, 'sha1' => $sha1,
'extparam' => json_encode($extparam),
); );
model("attachment")->create(array_filter($params)); $attachment = model("attachment");
$this->code = 1; $attachment->data(array_filter($params));
$this->data = [ $attachment->save();
\think\Hook::listen("upload_after", $attachment);
$this->success(__('Upload successful'), null, [
'url' => $uploadDir . $splInfo->getSaveName() 'url' => $uploadDir . $splInfo->getSaveName()
]; ]);
} } else {
else
{
// 上传失败获取错误信息 // 上传失败获取错误信息
$this->data = $file->getError(); $this->error($file->getError());
} }
} }
@ -146,78 +155,52 @@ class Ajax extends Backend
$field = $this->request->post("field"); $field = $this->request->post("field");
//操作的数据表 //操作的数据表
$table = $this->request->post("table"); $table = $this->request->post("table");
//主键
$pk = $this->request->post("pk");
//排序的方式 //排序的方式
$orderway = $this->request->post("orderway", 'strtolower'); $orderway = $this->request->post("orderway", "", 'strtolower');
$orderway = $orderway == 'asc' ? 'ASC' : 'DESC'; $orderway = $orderway == 'asc' ? 'ASC' : 'DESC';
$sour = $weighdata = []; $sour = $weighdata = [];
$ids = explode(',', $ids); $ids = explode(',', $ids);
$prikey = 'id'; $prikey = $pk ? $pk : (Db::name($table)->getPk() ?: 'id');
$pid = $this->request->post("pid"); $pid = $this->request->post("pid");
//限制更新的字段 //限制更新的字段
$field = in_array($field, ['weigh']) ? $field : 'weigh'; $field = in_array($field, ['weigh']) ? $field : 'weigh';
// 如果设定了pid的值,此时只匹配满足条件的ID,其它忽略 // 如果设定了pid的值,此时只匹配满足条件的ID,其它忽略
if ($pid !== '') if ($pid !== '') {
{
$hasids = []; $hasids = [];
$list = Db::name($table)->where($prikey, 'in', $ids)->where('pid', 'in', $pid)->field('id,pid')->select(); $list = Db::name($table)->where($prikey, 'in', $ids)->where('pid', 'in', $pid)->field("{$prikey},pid")->select();
foreach ($list as $k => $v) foreach ($list as $k => $v) {
{ $hasids[] = $v[$prikey];
$hasids[] = $v['id'];
} }
$ids = array_values(array_intersect($ids, $hasids)); $ids = array_values(array_intersect($ids, $hasids));
} }
//直接修复排序
$one = Db::name($table)->field("{$field},COUNT(*) AS nums")->group($field)->having('nums > 1')->find();
if ($one)
{
$list = Db::name($table)->field("$prikey,$field")->order($field, $orderway)->select();
foreach ($list as $k => $v)
{
Db::name($table)->where($prikey, $v[$prikey])->update([$field => $k + 1]);
}
$this->code = 1;
}
else
{
$list = Db::name($table)->field("$prikey,$field")->where($prikey, 'in', $ids)->order($field, $orderway)->select(); $list = Db::name($table)->field("$prikey,$field")->where($prikey, 'in', $ids)->order($field, $orderway)->select();
foreach ($list as $k => $v) foreach ($list as $k => $v) {
{
$sour[] = $v[$prikey]; $sour[] = $v[$prikey];
$weighdata[$v[$prikey]] = $v[$field]; $weighdata[$v[$prikey]] = $v[$field];
} }
$position = array_search($changeid, $ids); $position = array_search($changeid, $ids);
$desc_id = $sour[$position]; //移动到目标的ID值,取出所处改变前位置的值 $desc_id = $sour[$position]; //移动到目标的ID值,取出所处改变前位置的值
$sour_id = $changeid; $sour_id = $changeid;
$desc_value = $weighdata[$desc_id];
$sour_value = $weighdata[$sour_id];
//echo "移动的ID:{$sour_id}\n";
//echo "替换的ID:{$desc_id}\n";
$weighids = array(); $weighids = array();
$temp = array_values(array_diff_assoc($ids, $sour)); $temp = array_values(array_diff_assoc($ids, $sour));
foreach ($temp as $m => $n) foreach ($temp as $m => $n) {
{ if ($n == $sour_id) {
if ($n == $sour_id)
{
$offset = $desc_id; $offset = $desc_id;
} } else {
else if ($sour_id == $temp[0]) {
{
if ($sour_id == $temp[0])
{
$offset = isset($temp[$m + 1]) ? $temp[$m + 1] : $sour_id; $offset = isset($temp[$m + 1]) ? $temp[$m + 1] : $sour_id;
} } else {
else
{
$offset = isset($temp[$m - 1]) ? $temp[$m - 1] : $sour_id; $offset = isset($temp[$m - 1]) ? $temp[$m - 1] : $sour_id;
} }
} }
$weighids[$n] = $weighdata[$offset]; $weighids[$n] = $weighdata[$offset];
Db::name($table)->where($prikey, $n)->update([$field => $weighdata[$offset]]); Db::name($table)->where($prikey, $n)->update([$field => $weighdata[$offset]]);
} }
$this->code = 1; $this->success();
}
} }
/** /**
@ -225,26 +208,26 @@ class Ajax extends Backend
*/ */
public function wipecache() public function wipecache()
{ {
$wipe_cache_type = ['TEMP_PATH', 'LOG_PATH', 'CACHE_PATH']; $type = $this->request->request("type");
foreach ($wipe_cache_type as $item) switch ($type) {
{ case 'all':
$dir = constant($item); case 'content':
if (!is_dir($dir)) rmdirs(CACHE_PATH, false);
continue;
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo)
{
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
$todo($fileinfo->getRealPath());
}
//rmdir($dir);
}
Cache::clear(); Cache::clear();
$this->code = 1; if ($type == 'content')
break;
case 'template':
rmdirs(TEMP_PATH, false);
if ($type == 'template')
break;
case 'addons':
Service::refresh();
if ($type == 'addons')
break;
}
\think\Hook::listen("wipecache_after");
$this->success();
} }
/** /**
@ -256,22 +239,17 @@ class Ajax extends Backend
$pid = $this->request->get('pid'); $pid = $this->request->get('pid');
$where = ['status' => 'normal']; $where = ['status' => 'normal'];
$categorylist = null; $categorylist = null;
if ($pid !== '') if ($pid !== '') {
{ if ($type) {
if ($type)
{
$where['type'] = $type; $where['type'] = $type;
} }
if ($pid) if ($pid) {
{
$where['pid'] = $pid; $where['pid'] = $pid;
} }
$categorylist = Db::name('category')->where($where)->field('id as value,name')->order('weigh desc,id desc')->select(); $categorylist = Db::name('category')->where($where)->field('id as value,name')->order('weigh desc,id desc')->select();
} }
$this->code = 1; $this->success('', null, $categorylist);
$this->data = $categorylist;
return;
} }
/** /**
@ -279,30 +257,30 @@ class Ajax extends Backend
*/ */
public function area() public function area()
{ {
$params = $this->request->get("row/a");
if (!empty($params)) {
$province = isset($params['province']) ? $params['province'] : '';
$city = isset($params['city']) ? $params['city'] : null;
} else {
$province = $this->request->get('province'); $province = $this->request->get('province');
$city = $this->request->get('city'); $city = $this->request->get('city');
}
$where = ['pid' => 0, 'level' => 1]; $where = ['pid' => 0, 'level' => 1];
$provincelist = null; $provincelist = null;
if ($province !== '') if ($province !== '') {
{ if ($province) {
if ($province)
{
$where['pid'] = $province; $where['pid'] = $province;
$where['level'] = 2; $where['level'] = 2;
} }
if ($city !== '') if ($city !== '') {
{ if ($city) {
if ($city)
{
$where['pid'] = $city; $where['pid'] = $city;
$where['level'] = 3; $where['level'] = 3;
} }
$provincelist = Db::name('area')->where($where)->field('id as value,name')->select(); $provincelist = Db::name('area')->where($where)->field('id as value,name')->select();
} }
} }
$this->code = 1; $this->success('', null, $provincelist);
$this->data = $provincelist;
return;
} }
} }

View File

@ -10,11 +10,14 @@ use fast\Tree;
* 分类管理 * 分类管理
* *
* @icon fa fa-list * @icon fa fa-list
* @remark 用于统一管理网站的所有分类,分类可进行无限级分类 * @remark 用于统一管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加
*/ */
class Category extends Backend class Category extends Backend
{ {
/**
* @var \app\common\model\Category
*/
protected $model = null; protected $model = null;
protected $categorylist = []; protected $categorylist = [];
protected $noNeedRight = ['selectpage']; protected $noNeedRight = ['selectpage'];
@ -22,20 +25,20 @@ class Category extends Backend
public function _initialize() public function _initialize()
{ {
parent::_initialize(); parent::_initialize();
$this->request->filter(['strip_tags']); $this->model = model('app\common\model\Category');
$this->model = model('Category');
$tree = Tree::instance(); $tree = Tree::instance();
$tree->init($this->model->order('weigh desc,id desc')->select(), 'pid'); $tree->init(collection($this->model->order('weigh desc,id desc')->select())->toArray(), 'pid');
$this->categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name'); $this->categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
$categorydata = [0 => __('None')]; $categorydata = [0 => ['type' => 'all', 'name' => __('None')]];
foreach ($this->categorylist as $k => $v) foreach ($this->categorylist as $k => $v) {
{ $categorydata[$v['id']] = $v;
$categorydata[$v['id']] = $v['name'];
} }
$typeList = CategoryModel::getTypeList();
$this->view->assign("flagList", $this->model->getFlagList()); $this->view->assign("flagList", $this->model->getFlagList());
$this->view->assign("typeList", CategoryModel::getTypeList()); $this->view->assign("typeList", $typeList);
$this->view->assign("parentList", $categorydata); $this->view->assign("parentList", $categorydata);
$this->assignconfig('typeList', $typeList);
} }
/** /**
@ -43,19 +46,94 @@ class Category extends Backend
*/ */
public function index() public function index()
{ {
if ($this->request->isAjax()) //设置过滤方法
{ $this->request->filter(['strip_tags']);
if ($this->request->isAjax()) {
$search = $this->request->request("search");
$type = $this->request->request("type");
//构造父类select列表选项数据 //构造父类select列表选项数据
$list = [];
foreach ($this->categorylist as $k => $v) {
if ($search) {
if ($v['type'] == $type && stripos($v['name'], $search) !== false || stripos($v['nickname'], $search) !== false) {
if ($type == "all" || $type == null) {
$list = $this->categorylist; $list = $this->categorylist;
} else {
$list[] = $v;
}
}
} else {
if ($type == "all" || $type == null) {
$list = $this->categorylist;
} elseif ($v['type'] == $type) {
$list[] = $v;
}
}
}
$total = count($list); $total = count($list);
$result = array("total" => 1, "rows" => $list); $result = array("total" => $total, "rows" => $list);
return json($result); return json($result);
} }
return $this->view->fetch(); return $this->view->fetch();
} }
/**
* 编辑
*/
public function edit($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
if (!in_array($row[$this->dataLimitField], $adminIds)) {
$this->error(__('You have no permission'));
}
}
if ($this->request->isPost()) {
$params = $this->request->post("row/a");
if ($params) {
$params = $this->preExcludeFields($params);
if ($params['pid'] != $row['pid']) {
$childrenIds = Tree::instance()->init(collection(\app\common\model\Category::select())->toArray())->getChildrenIds($row['id']);
if (in_array($params['pid'], $childrenIds)) {
$this->error(__('Can not change the parent to child'));
}
}
try {
//是否采用模型验证
if ($this->modelValidate) {
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
$row->validate($validate);
}
$result = $row->allowField(true)->save($params);
if ($result !== false) {
$this->success();
} else {
$this->error($row->getError());
}
} catch (\think\exception\PDOException $e) {
$this->error($e->getMessage());
} catch (\think\Exception $e) {
$this->error($e->getMessage());
}
}
$this->error(__('Parameter %s can not be empty', ''));
}
$this->view->assign("row", $row);
return $this->view->fetch();
}
/** /**
* Selectpage搜索 * Selectpage搜索
* *
@ -65,5 +143,4 @@ class Category extends Backend
{ {
return parent::selectpage(); return parent::selectpage();
} }
} }

View File

@ -3,6 +3,7 @@
namespace app\admin\controller; namespace app\admin\controller;
use app\common\controller\Backend; use app\common\controller\Backend;
use think\Config;
/** /**
* 控制台 * 控制台
@ -26,6 +27,12 @@ class Dashboard extends Backend
$createlist[$day] = mt_rand(20, 200); $createlist[$day] = mt_rand(20, 200);
$paylist[$day] = mt_rand(1, mt_rand(1, $createlist[$day])); $paylist[$day] = mt_rand(1, mt_rand(1, $createlist[$day]));
} }
$hooks = config('addons.hooks');
$uploadmode = isset($hooks['upload_config_init']) && $hooks['upload_config_init'] ? implode(',', $hooks['upload_config_init']) : 'local';
$addonComposerCfg = ROOT_PATH . '/vendor/karsonzhang/fastadmin-addons/composer.json';
Config::parse($addonComposerCfg, "json", "composer");
$config = Config::get("composer");
$addonVersion = isset($config['version']) ? $config['version'] : __('Unknown');
$this->view->assign([ $this->view->assign([
'totaluser' => 35200, 'totaluser' => 35200,
'totalviews' => 219390, 'totalviews' => 219390,
@ -34,14 +41,15 @@ class Dashboard extends Backend
'todayuserlogin' => 321, 'todayuserlogin' => 321,
'todayusersignup' => 430, 'todayusersignup' => 430,
'todayorder' => 2324, 'todayorder' => 2324,
'todayunsettleorder' => 132, 'unsettleorder' => 132,
'sevendnu' => '80%', 'sevendnu' => '80%',
'sevendau' => '32%', 'sevendau' => '32%',
'paylist' => $paylist, 'paylist' => $paylist,
'createlist' => $createlist, 'createlist' => $createlist,
'addonversion' => $addonVersion,
'uploadmode' => $uploadmode
]); ]);
return $this->view->fetch(); return $this->view->fetch();
} }

View File

@ -2,7 +2,10 @@
namespace app\admin\controller; namespace app\admin\controller;
use app\admin\model\AdminLog;
use app\common\controller\Backend; use app\common\controller\Backend;
use think\Config;
use think\Hook;
use think\Validate; use think\Validate;
/** /**
@ -26,15 +29,23 @@ class Index extends Backend
*/ */
public function index() public function index()
{ {
// //左侧菜单
$menulist = $this->auth->getSidebar([ list($menulist, $navlist, $fixedmenu, $referermenu) = $this->auth->getSidebar([
'dashboard' => 'hot', 'dashboard' => 'hot',
'auth' => ['new', 'red', 'badge'], 'addon' => ['new', 'red', 'badge'],
'auth/admin' => 12, 'auth/rule' => __('Menu'),
'auth/rule' => 4, 'general' => ['new', 'purple'],
'general' => ['18', 'purple'],
], $this->view->site['fixedpage']); ], $this->view->site['fixedpage']);
$action = $this->request->request('action');
if ($this->request->isPost()) {
if ($action == 'refreshmenu') {
$this->success('', null, ['menulist' => $menulist, 'navlist' => $navlist]);
}
}
$this->view->assign('menulist', $menulist); $this->view->assign('menulist', $menulist);
$this->view->assign('navlist', $navlist);
$this->view->assign('fixedmenu', $fixedmenu);
$this->view->assign('referermenu', $referermenu);
$this->view->assign('title', __('Home')); $this->view->assign('title', __('Home'));
return $this->view->fetch(); return $this->view->fetch();
} }
@ -45,13 +56,10 @@ class Index extends Backend
public function login() public function login()
{ {
$url = $this->request->get('url', 'index/index'); $url = $this->request->get('url', 'index/index');
if ($this->auth->isLogin()) if ($this->auth->isLogin()) {
{ $this->success(__("You've logged in, do not login again"), $url);
$this->error(__("You've logged in, do not login again"), $url);
return;
} }
if ($this->request->isPost()) if ($this->request->isPost()) {
{
$username = $this->request->post('username'); $username = $this->request->post('username');
$password = $this->request->post('password'); $password = $this->request->post('password');
$keeplogin = $this->request->post('keeplogin'); $keeplogin = $this->request->post('keeplogin');
@ -59,40 +67,43 @@ class Index extends Backend
$rule = [ $rule = [
'username' => 'require|length:3,30', 'username' => 'require|length:3,30',
'password' => 'require|length:3,30', 'password' => 'require|length:3,30',
'__token__' => 'token', '__token__' => 'require|token',
]; ];
$data = [ $data = [
'username' => $username, 'username' => $username,
'password' => $password, 'password' => $password,
'__token__' => $token, '__token__' => $token,
]; ];
$validate = new Validate($rule); if (Config::get('fastadmin.login_captcha')) {
$rule['captcha'] = 'require|captcha';
$data['captcha'] = $this->request->post('captcha');
}
$validate = new Validate($rule, [], ['username' => __('Username'), 'password' => __('Password'), 'captcha' => __('Captcha')]);
$result = $validate->check($data); $result = $validate->check($data);
if (!$result) if (!$result) {
{
$this->error($validate->getError(), $url, ['token' => $this->request->token()]); $this->error($validate->getError(), $url, ['token' => $this->request->token()]);
return;
} }
\app\admin\model\AdminLog::setTitle(__('Login')); AdminLog::setTitle(__('Login'));
$result = $this->auth->login($username, $password, $keeplogin ? 86400 : 0); $result = $this->auth->login($username, $password, $keeplogin ? 86400 : 0);
if ($result === true) if ($result === true) {
{ Hook::listen("admin_login_after", $this->request);
$this->success(__('Login successful'), $url, ['url' => $url, 'id' => $this->auth->id, 'username' => $username, 'avatar' => $this->auth->avatar]); $this->success(__('Login successful'), $url, ['url' => $url, 'id' => $this->auth->id, 'username' => $username, 'avatar' => $this->auth->avatar]);
return; } else {
$msg = $this->auth->getError();
$msg = $msg ? $msg : __('Username or password is incorrect');
$this->error($msg, $url, ['token' => $this->request->token()]);
} }
else
{
$this->error(__('Username or password is incorrect'), $url, ['token' => $this->request->token()]);
}
return;
} }
// 根据客户端的cookie,判断是否可以自动登录 // 根据客户端的cookie,判断是否可以自动登录
if ($this->auth->autologin()) if ($this->auth->autologin()) {
{
$this->redirect($url); $this->redirect($url);
} }
$background = Config::get('fastadmin.login_background');
$background = stripos($background, 'http') === 0 ? $background : config('site.cdnurl') . $background;
$this->view->assign('background', $background);
$this->view->assign('title', __('Login')); $this->view->assign('title', __('Login'));
Hook::listen("admin_login_init", $this->request);
return $this->view->fetch(); return $this->view->fetch();
} }
@ -102,8 +113,8 @@ class Index extends Backend
public function logout() public function logout()
{ {
$this->auth->logout(); $this->auth->logout();
Hook::listen("admin_logout_after", $this->request);
$this->success(__('Logout successful'), 'index/login'); $this->success(__('Logout successful'), 'index/login');
return;
} }
} }

View File

@ -1,51 +0,0 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
/**
* 单页管理
*
* @icon fa fa-circle-o
* @remark 用于管理普通的单页面,通常用于关于我们、联系我们、商务合作等单一页面
*/
class Page extends Backend
{
protected $model = null;
protected $relationSearch = true;
public function _initialize()
{
parent::_initialize();
$this->model = model('Page');
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->with("category")
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->with("category")
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
/**
* 会员管理
*
* @icon fa fa-circle-o
* @internal
*/
class User extends Backend
{
/**
* User模型对象
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('User');
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use think\Controller;
use think\Request;
/**
* 版本管理
*
* @icon fa fa-circle-o
*/
class Version extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('Version');
}
}

View File

@ -2,6 +2,8 @@
namespace app\admin\controller\auth; namespace app\admin\controller\auth;
use app\admin\model\AuthGroup;
use app\admin\model\AuthGroupAccess;
use app\common\controller\Backend; use app\common\controller\Backend;
use fast\Random; use fast\Random;
use fast\Tree; use fast\Tree;
@ -15,34 +17,52 @@ use fast\Tree;
class Admin extends Backend class Admin extends Backend
{ {
/**
* @var \app\admin\model\Admin
*/
protected $model = null; protected $model = null;
//当前登录管理员所有子节点组别 protected $childrenGroupIds = [];
protected $childrenIds = []; protected $childrenAdminIds = [];
public function _initialize() public function _initialize()
{ {
parent::_initialize(); parent::_initialize();
$this->model = model('Admin'); $this->model = model('Admin');
$groups = $this->auth->getGroups(); $this->childrenAdminIds = $this->auth->getChildrenAdminIds(true);
$this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
// 取出所有分组 $groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
$grouplist = model('AuthGroup')->all(['status' => 'normal']);
$objlist = []; Tree::instance()->init($groupList);
foreach ($groups as $K => $v)
{
// 取出包含自己的所有子节点
$childrenlist = Tree::instance()->init($grouplist)->getChildren($v['id'], TRUE);
$obj = Tree::instance()->init($childrenlist)->getTreeArray($v['pid']);
$objlist = array_merge($objlist, Tree::instance()->getTreeList($obj));
}
$groupdata = []; $groupdata = [];
foreach ($objlist as $k => $v) if ($this->auth->isSuperAdmin())
{
$result = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
foreach ($result as $k => $v)
{ {
$groupdata[$v['id']] = $v['name']; $groupdata[$v['id']] = $v['name'];
} }
$this->childrenIds = array_keys($groupdata); }
else
{
$result = [];
$groups = $this->auth->getGroups();
foreach ($groups as $m => $n)
{
$childlist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['id']));
$temp = [];
foreach ($childlist as $k => $v)
{
$temp[$v['id']] = $v['name'];
}
$result[__($n['name'])] = $temp;
}
$groupdata = $result;
}
$this->view->assign('groupdata', $groupdata); $this->view->assign('groupdata', $groupdata);
$this->assignconfig("admin", ['id' => $this->auth->id]);
} }
/** /**
@ -52,31 +72,39 @@ class Admin extends Backend
{ {
if ($this->request->isAjax()) if ($this->request->isAjax())
{ {
$groupData = model('AuthGroup')->where('status', 'normal')->column('id,name'); //如果发送的来源是Selectpage则转发到Selectpage
if ($this->request->request('keyField'))
$childrenAdminIds = []; {
$authGroupList = model('AuthGroupAccess') return $this->selectpage();
}
$childrenGroupIds = $this->childrenGroupIds;
$groupName = AuthGroup::where('id', 'in', $childrenGroupIds)
->column('id,name');
$authGroupList = AuthGroupAccess::where('group_id', 'in', $childrenGroupIds)
->field('uid,group_id') ->field('uid,group_id')
->where('group_id', 'in', $this->childrenIds)
->select(); ->select();
$adminGroupName = []; $adminGroupName = [];
foreach ($authGroupList as $k => $v) foreach ($authGroupList as $k => $v)
{ {
$childrenAdminIds[] = $v['uid']; if (isset($groupName[$v['group_id']]))
if (isset($groupData[$v['group_id']])) $adminGroupName[$v['uid']][$v['group_id']] = $groupName[$v['group_id']];
$adminGroupName[$v['uid']][$v['group_id']] = $groupData[$v['group_id']]; }
$groups = $this->auth->getGroups();
foreach ($groups as $m => $n)
{
$adminGroupName[$this->auth->id][$n['id']] = $n['name'];
} }
list($where, $sort, $order, $offset, $limit) = $this->buildparams(); list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model $total = $this->model
->where($where) ->where($where)
->where('id', 'in', $childrenAdminIds) ->where('id', 'in', $this->childrenAdminIds)
->order($sort, $order) ->order($sort, $order)
->count(); ->count();
$list = $this->model $list = $this->model
->where($where) ->where($where)
->where('id', 'in', $childrenAdminIds) ->where('id', 'in', $this->childrenAdminIds)
->field(['password', 'salt', 'token'], true) ->field(['password', 'salt', 'token'], true)
->order($sort, $order) ->order($sort, $order)
->limit($offset, $limit) ->limit($offset, $limit)
@ -87,6 +115,7 @@ class Admin extends Backend
$v['groups'] = implode(',', array_keys($groups)); $v['groups'] = implode(',', array_keys($groups));
$v['groups_text'] = implode(',', array_values($groups)); $v['groups_text'] = implode(',', array_values($groups));
} }
unset($v);
$result = array("total" => $total, "rows" => $list); $result = array("total" => $total, "rows" => $list);
return json($result); return json($result);
@ -101,29 +130,30 @@ class Admin extends Backend
{ {
if ($this->request->isPost()) if ($this->request->isPost())
{ {
$this->code = -1;
$params = $this->request->post("row/a"); $params = $this->request->post("row/a");
if ($params) if ($params)
{ {
$params['salt'] = Random::alnum(); $params['salt'] = Random::alnum();
$params['password'] = md5(md5($params['password']) . $params['salt']); $params['password'] = md5(md5($params['password']) . $params['salt']);
$params['avatar'] = '/assets/img/avatar.png'; //设置新管理员默认头像。 $params['avatar'] = '/assets/img/avatar.png'; //设置新管理员默认头像。
$result = $this->model->validate('Admin.add')->save($params);
$admin = $this->model->create($params); if ($result === false)
{
$this->error($this->model->getError());
}
$group = $this->request->post("group/a"); $group = $this->request->post("group/a");
//过滤不允许的组别,避免越权 //过滤不允许的组别,避免越权
$group = array_intersect($this->childrenIds, $group); $group = array_intersect($this->childrenGroupIds, $group);
$dataset = []; $dataset = [];
foreach ($group as $value) foreach ($group as $value)
{ {
$dataset[] = ['uid' => $admin->id, 'group_id' => $value]; $dataset[] = ['uid' => $this->model->id, 'group_id' => $value];
} }
model('AuthGroupAccess')->saveAll($dataset); model('AuthGroupAccess')->saveAll($dataset);
$this->code = 1; $this->success();
} }
$this->error();
return;
} }
return $this->view->fetch(); return $this->view->fetch();
} }
@ -138,7 +168,6 @@ class Admin extends Backend
$this->error(__('No Results were found')); $this->error(__('No Results were found'));
if ($this->request->isPost()) if ($this->request->isPost())
{ {
$this->code = -1;
$params = $this->request->post("row/a"); $params = $this->request->post("row/a");
if ($params) if ($params)
{ {
@ -151,7 +180,17 @@ class Admin extends Backend
{ {
unset($params['password'], $params['salt']); unset($params['password'], $params['salt']);
} }
$row->save($params); //这里需要针对username和email做唯一验证
$adminValidate = \think\Loader::validate('Admin');
$adminValidate->rule([
'username' => 'require|max:50|unique:admin,username,' . $row->id,
'email' => 'require|email|unique:admin,email,' . $row->id
]);
$result = $row->validate('Admin.edit')->save($params);
if ($result === false)
{
$this->error($row->getError());
}
// 先移除所有权限 // 先移除所有权限
model('AuthGroupAccess')->where('uid', $row->id)->delete(); model('AuthGroupAccess')->where('uid', $row->id)->delete();
@ -159,7 +198,7 @@ class Admin extends Backend
$group = $this->request->post("group/a"); $group = $this->request->post("group/a");
// 过滤不允许的组别,避免越权 // 过滤不允许的组别,避免越权
$group = array_intersect($this->childrenIds, $group); $group = array_intersect($this->childrenGroupIds, $group);
$dataset = []; $dataset = [];
foreach ($group as $value) foreach ($group as $value)
@ -167,10 +206,9 @@ class Admin extends Backend
$dataset[] = ['uid' => $row->id, 'group_id' => $value]; $dataset[] = ['uid' => $row->id, 'group_id' => $value];
} }
model('AuthGroupAccess')->saveAll($dataset); model('AuthGroupAccess')->saveAll($dataset);
$this->code = 1; $this->success();
} }
$this->error();
return;
} }
$grouplist = $this->auth->getGroups($row['id']); $grouplist = $this->auth->getGroups($row['id']);
$groupids = []; $groupids = [];
@ -188,11 +226,10 @@ class Admin extends Backend
*/ */
public function del($ids = "") public function del($ids = "")
{ {
$this->code = -1;
if ($ids) if ($ids)
{ {
// 避免越权删除管理员 // 避免越权删除管理员
$childrenGroupIds = $this->childrenIds; $childrenGroupIds = $this->childrenGroupIds;
$adminList = $this->model->where('id', 'in', $ids)->where('id', 'in', function($query) use($childrenGroupIds) { $adminList = $this->model->where('id', 'in', $ids)->where('id', 'in', function($query) use($childrenGroupIds) {
$query->name('auth_group_access')->where('group_id', 'in', $childrenGroupIds)->field('uid'); $query->name('auth_group_access')->where('group_id', 'in', $childrenGroupIds)->field('uid');
})->select(); })->select();
@ -208,12 +245,11 @@ class Admin extends Backend
{ {
$this->model->destroy($deleteIds); $this->model->destroy($deleteIds);
model('AuthGroupAccess')->where('uid', 'in', $deleteIds)->delete(); model('AuthGroupAccess')->where('uid', 'in', $deleteIds)->delete();
$this->code = 1; $this->success();
} }
} }
} }
$this->error();
return;
} }
/** /**
@ -223,7 +259,17 @@ class Admin extends Backend
public function multi($ids = "") public function multi($ids = "")
{ {
// 管理员禁止批量操作 // 管理员禁止批量操作
$this->code = -1; $this->error();
}
/**
* 下拉搜索
*/
public function selectpage()
{
$this->dataLimit = 'auth';
$this->dataLimitField = 'id';
return parent::selectpage();
} }
} }

View File

@ -2,8 +2,8 @@
namespace app\admin\controller\auth; namespace app\admin\controller\auth;
use app\admin\model\AuthGroup;
use app\common\controller\Backend; use app\common\controller\Backend;
use fast\Tree;
/** /**
* 管理员日志 * 管理员日志
@ -14,34 +14,25 @@ use fast\Tree;
class Adminlog extends Backend class Adminlog extends Backend
{ {
/**
* @var \app\admin\model\AdminLog
*/
protected $model = null; protected $model = null;
//当前登录管理员所有子节点组别 protected $childrenGroupIds = [];
protected $childrenIds = []; protected $childrenAdminIds = [];
public function _initialize() public function _initialize()
{ {
parent::_initialize(); parent::_initialize();
$this->model = model('AdminLog'); $this->model = model('AdminLog');
$groups = $this->auth->getGroups(); $this->childrenAdminIds = $this->auth->getChildrenAdminIds(true);
$this->childrenGroupIds = $this->auth->getChildrenGroupIds($this->auth->isSuperAdmin() ? true : false);
// 取出所有分组 $groupName = AuthGroup::where('id', 'in', $this->childrenGroupIds)
$grouplist = model('AuthGroup')->all(['status' => 'normal']); ->column('id,name');
$objlist = [];
foreach ($groups as $K => $v) $this->view->assign('groupdata', $groupName);
{
// 取出包含自己的所有子节点
$childrenlist = Tree::instance()->init($grouplist)->getChildren($v['id'], TRUE);
$obj = Tree::instance()->init($childrenlist)->getTreeArray($v['pid']);
$objlist = array_merge($objlist, Tree::instance()->getTreeList($obj));
}
$groupdata = [];
foreach ($objlist as $k => $v)
{
$groupdata[$v['id']] = $v['name'];
}
$this->childrenIds = array_keys($groupdata);
$this->view->assign('groupdata', $groupdata);
} }
/** /**
@ -51,20 +42,16 @@ class Adminlog extends Backend
{ {
if ($this->request->isAjax()) if ($this->request->isAjax())
{ {
$childrenAdminIds = model('AuthGroupAccess')
->field('uid')
->where('group_id', 'in', $this->childrenIds)
->column('uid');
list($where, $sort, $order, $offset, $limit) = $this->buildparams(); list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model $total = $this->model
->where($where) ->where($where)
->where('admin_id', 'in', $childrenAdminIds) ->where('admin_id', 'in', $this->childrenAdminIds)
->order($sort, $order) ->order($sort, $order)
->count(); ->count();
$list = $this->model $list = $this->model
->where($where) ->where($where)
->where('admin_id', 'in', $childrenAdminIds) ->where('admin_id', 'in', $this->childrenAdminIds)
->order($sort, $order) ->order($sort, $order)
->limit($offset, $limit) ->limit($offset, $limit)
->select(); ->select();
@ -93,7 +80,7 @@ class Adminlog extends Backend
*/ */
public function add() public function add()
{ {
$this->code = -1; $this->error();
} }
/** /**
@ -102,7 +89,7 @@ class Adminlog extends Backend
*/ */
public function edit($ids = NULL) public function edit($ids = NULL)
{ {
$this->code = -1; $this->error();
} }
/** /**
@ -110,10 +97,9 @@ class Adminlog extends Backend
*/ */
public function del($ids = "") public function del($ids = "")
{ {
$this->code = -1;
if ($ids) if ($ids)
{ {
$childrenGroupIds = $this->childrenIds; $childrenGroupIds = $this->childrenGroupIds;
$adminList = $this->model->where('id', 'in', $ids)->where('admin_id', 'in', function($query) use($childrenGroupIds) { $adminList = $this->model->where('id', 'in', $ids)->where('admin_id', 'in', function($query) use($childrenGroupIds) {
$query->name('auth_group_access')->field('uid'); $query->name('auth_group_access')->field('uid');
})->select(); })->select();
@ -127,12 +113,11 @@ class Adminlog extends Backend
if ($deleteIds) if ($deleteIds)
{ {
$this->model->destroy($deleteIds); $this->model->destroy($deleteIds);
$this->code = 1; $this->success();
} }
} }
} }
$this->error();
return;
} }
/** /**
@ -142,7 +127,12 @@ class Adminlog extends Backend
public function multi($ids = "") public function multi($ids = "")
{ {
// 管理员禁止批量操作 // 管理员禁止批量操作
$this->code = -1; $this->error();
}
public function selectpage()
{
return parent::selectpage();
} }
} }

View File

@ -2,6 +2,7 @@
namespace app\admin\controller\auth; namespace app\admin\controller\auth;
use app\admin\model\AuthGroup;
use app\common\controller\Backend; use app\common\controller\Backend;
use fast\Tree; use fast\Tree;
@ -14,9 +15,12 @@ use fast\Tree;
class Group extends Backend class Group extends Backend
{ {
/**
* @var \app\admin\model\AuthGroup
*/
protected $model = null; protected $model = null;
//当前登录管理员所有子节点组别 //当前登录管理员所有子组别
protected $childrenIds = []; protected $childrenGroupIds = [];
//当前组别列表数据 //当前组别列表数据
protected $groupdata = []; protected $groupdata = [];
//无需要权限判断的方法 //无需要权限判断的方法
@ -27,27 +31,29 @@ class Group extends Backend
parent::_initialize(); parent::_initialize();
$this->model = model('AuthGroup'); $this->model = model('AuthGroup');
$this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
$groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
Tree::instance()->init($groupList);
$result = [];
if ($this->auth->isSuperAdmin()) {
$result = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
} else {
$groups = $this->auth->getGroups(); $groups = $this->auth->getGroups();
foreach ($groups as $m => $n) {
// 取出所有分组 $result = array_merge($result, Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['pid'])));
$grouplist = model('AuthGroup')->all(['status' => 'normal']); }
$objlist = []; }
foreach ($groups as $K => $v) $groupName = [];
{ foreach ($result as $k => $v) {
// 取出包含自己的所有子节点 $groupName[$v['id']] = $v['name'];
$childrenlist = Tree::instance()->init($grouplist)->getChildren($v['id'], TRUE);
$obj = Tree::instance()->init($childrenlist)->getTreeArray($v['pid']);
$objlist = array_merge($objlist, Tree::instance()->getTreeList($obj));
} }
$groupdata = []; $this->groupdata = $groupName;
foreach ($objlist as $k => $v) $this->assignconfig("admin", ['id' => $this->auth->id, 'group_ids' => $this->auth->getGroupIds()]);
{
$groupdata[$v['id']] = $v['name']; $this->view->assign('groupdata', $this->groupdata);
}
$this->groupdata = $groupdata;
$this->childrenIds = array_keys($groupdata);
$this->view->assign('groupdata', $groupdata);
} }
/** /**
@ -55,14 +61,19 @@ class Group extends Backend
*/ */
public function index() public function index()
{ {
if ($this->request->isAjax()) if ($this->request->isAjax()) {
{ $list = AuthGroup::all(array_keys($this->groupdata));
$list = collection($list)->toArray();
$groupList = [];
foreach ($list as $k => $v) {
$groupList[$v['id']] = $v;
}
$list = []; $list = [];
foreach ($this->groupdata as $k => $v) foreach ($this->groupdata as $k => $v) {
{ if (isset($groupList[$k])) {
$data = $this->model->get($k); $groupList[$k]['name'] = $v;
$data->name = $v; $list[] = $groupList[$k];
$list[] = $data; }
} }
$total = count($list); $total = count($list);
$result = array("total" => $total, "rows" => $list); $result = array("total" => $total, "rows" => $list);
@ -77,22 +88,15 @@ class Group extends Backend
*/ */
public function add() public function add()
{ {
if ($this->request->isPost()) if ($this->request->isPost()) {
{
$this->code = -1;
$params = $this->request->post("row/a", [], 'strip_tags'); $params = $this->request->post("row/a", [], 'strip_tags');
$params['rules'] = explode(',', $params['rules']); $params['rules'] = explode(',', $params['rules']);
if (!in_array($params['pid'], $this->childrenIds)) if (!in_array($params['pid'], $this->childrenGroupIds)) {
{ $this->error(__('The parent group can not be its own child'));
$this->code = -1;
$this->msg = __('');
return;
} }
$parentmodel = model("AuthGroup")->get($params['pid']); $parentmodel = model("AuthGroup")->get($params['pid']);
if (!$parentmodel) if (!$parentmodel) {
{ $this->error(__('The parent group can not found'));
$this->msg = __('The parent group can not found');
return;
} }
// 父级别的规则节点 // 父级别的规则节点
$parentrules = explode(',', $parentmodel->rules); $parentrules = explode(',', $parentmodel->rules);
@ -104,13 +108,11 @@ class Group extends Backend
// 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限 // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
$rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules); $rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
$params['rules'] = implode(',', $rules); $params['rules'] = implode(',', $rules);
if ($params) if ($params) {
{
$this->model->create($params); $this->model->create($params);
$this->code = 1; $this->success();
} }
$this->error();
return;
} }
return $this->view->fetch(); return $this->view->fetch();
} }
@ -118,28 +120,23 @@ class Group extends Backend
/** /**
* 编辑 * 编辑
*/ */
public function edit($ids = NULL) public function edit($ids = null)
{ {
$row = $this->model->get(['id' => $ids]); $row = $this->model->get(['id' => $ids]);
if (!$row) if (!$row) {
$this->error(__('No Results were found')); $this->error(__('No Results were found'));
if ($this->request->isPost()) }
{ if ($this->request->isPost()) {
$this->code = -1;
$params = $this->request->post("row/a", [], 'strip_tags'); $params = $this->request->post("row/a", [], 'strip_tags');
// 父节点不能是它自身的子节点 // 父节点不能是它自身的子节点
if (!in_array($params['pid'], $this->childrenIds)) if (!in_array($params['pid'], $this->childrenGroupIds)) {
{ $this->error(__('The parent group can not be its own child'));
$this->msg = __('The parent group can not be its own child');
return;
} }
$params['rules'] = explode(',', $params['rules']); $params['rules'] = explode(',', $params['rules']);
$parentmodel = model("AuthGroup")->get($params['pid']); $parentmodel = model("AuthGroup")->get($params['pid']);
if (!$parentmodel) if (!$parentmodel) {
{ $this->error(__('The parent group can not found'));
$this->msg = __('The parent group can not found');
return;
} }
// 父级别的规则节点 // 父级别的规则节点
$parentrules = explode(',', $parentmodel->rules); $parentrules = explode(',', $parentmodel->rules);
@ -151,12 +148,11 @@ class Group extends Backend
// 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限 // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
$rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules); $rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
$params['rules'] = implode(',', $rules); $params['rules'] = implode(',', $rules);
if ($params) if ($params) {
{
$row->save($params); $row->save($params);
$this->code = 1; $this->success();
} }
$this->error();
return; return;
} }
$this->view->assign("row", $row); $this->view->assign("row", $row);
@ -168,12 +164,10 @@ class Group extends Backend
*/ */
public function del($ids = "") public function del($ids = "")
{ {
$this->code = -1; if ($ids) {
if ($ids)
{
$ids = explode(',', $ids); $ids = explode(',', $ids);
$grouplist = $this->auth->getGroups(); $grouplist = $this->auth->getGroups();
$group_ids = array_map(function($group) { $group_ids = array_map(function ($group) {
return $group['id']; return $group['id'];
}, $grouplist); }, $grouplist);
// 移除掉当前管理员所在组别 // 移除掉当前管理员所在组别
@ -182,35 +176,29 @@ class Group extends Backend
// 循环判断每一个组别是否可删除 // 循环判断每一个组别是否可删除
$grouplist = $this->model->where('id', 'in', $ids)->select(); $grouplist = $this->model->where('id', 'in', $ids)->select();
$groupaccessmodel = model('AuthGroupAccess'); $groupaccessmodel = model('AuthGroupAccess');
foreach ($grouplist as $k => $v) foreach ($grouplist as $k => $v) {
{
// 当前组别下有管理员 // 当前组别下有管理员
$groupone = $groupaccessmodel->get(['group_id' => $v['id']]); $groupone = $groupaccessmodel->get(['group_id' => $v['id']]);
if ($groupone) if ($groupone) {
{
$ids = array_diff($ids, [$v['id']]); $ids = array_diff($ids, [$v['id']]);
continue; continue;
} }
// 当前组别下有子组别 // 当前组别下有子组别
$groupone = $this->model->get(['pid' => $v['id']]); $groupone = $this->model->get(['pid' => $v['id']]);
if ($groupone) if ($groupone) {
{
$ids = array_diff($ids, [$v['id']]); $ids = array_diff($ids, [$v['id']]);
continue; continue;
} }
} }
if (!$ids) if (!$ids) {
{ $this->error(__('You can not delete group that contain child group and administrators'));
$this->msg = __('You can not delete group that contain child group and administrators');
return;
} }
$count = $this->model->where('id', 'in', $ids)->delete(); $count = $this->model->where('id', 'in', $ids)->delete();
if ($count) if ($count) {
{ $this->success();
$this->code = 1;
} }
} }
return; $this->error();
} }
/** /**
@ -220,8 +208,7 @@ class Group extends Backend
public function multi($ids = "") public function multi($ids = "")
{ {
// 组别禁止批量操作 // 组别禁止批量操作
$this->code = -1; $this->error();
return;
} }
/** /**
@ -236,75 +223,69 @@ class Group extends Backend
$model = model('AuthGroup'); $model = model('AuthGroup');
$id = $this->request->post("id"); $id = $this->request->post("id");
$pid = $this->request->post("pid"); $pid = $this->request->post("pid");
$parentgroupmodel = $model->get($pid); $parentGroupModel = $model->get($pid);
$currentgroupmodel = NULL; $currentGroupModel = null;
if ($id) if ($id) {
{ $currentGroupModel = $model->get($id);
$currentgroupmodel = $model->get($id);
} }
if (($pid || $parentgroupmodel) && (!$id || $currentgroupmodel)) if (($pid || $parentGroupModel) && (!$id || $currentGroupModel)) {
{ $id = $id ? $id : null;
$id = $id ? $id : NULL; $ruleList = collection(model('AuthRule')->order('weigh', 'desc')->order('id', 'asc')->select())->toArray();
$ruleList = collection(model('AuthRule')->order('weigh', 'desc')->select())->toArray();
//读取父类角色所有节点列表 //读取父类角色所有节点列表
$parentRuleList = []; $parentRuleList = [];
if (in_array('*', explode(',', $parentgroupmodel->rules))) if (in_array('*', explode(',', $parentGroupModel->rules))) {
{
$parentRuleList = $ruleList; $parentRuleList = $ruleList;
} } else {
else $parentRuleIds = explode(',', $parentGroupModel->rules);
{ foreach ($ruleList as $k => $v) {
$parent_rule_ids = explode(',', $parentgroupmodel->rules); if (in_array($v['id'], $parentRuleIds)) {
foreach ($ruleList as $k => $v)
{
if (in_array($v['id'], $parent_rule_ids))
{
$parentRuleList[] = $v; $parentRuleList[] = $v;
} }
} }
} }
$ruleTree = new Tree();
$groupTree = new Tree();
//当前所有正常规则列表 //当前所有正常规则列表
Tree::instance()->init($ruleList); $ruleTree->init($parentRuleList);
//角色组列表
$groupTree->init(collection(model('AuthGroup')->where('id', 'in', $this->childrenGroupIds)->select())->toArray());
//读取当前角色下规则ID集合 //读取当前角色下规则ID集合
$admin_rule_ids = $this->auth->getRuleIds(); $adminRuleIds = $this->auth->getRuleIds();
//是否是超级管理员 //是否是超级管理员
$superadmin = $this->auth->isSuperAdmin(); $superadmin = $this->auth->isSuperAdmin();
//当前拥有的规则ID集合 //当前拥有的规则ID集合
$current_rule_ids = $id ? explode(',', $currentgroupmodel->rules) : []; $currentRuleIds = $id ? explode(',', $currentGroupModel->rules) : [];
if (!$id || !in_array($pid, Tree::instance()->getChildrenIds($id, TRUE))) if (!$id || !in_array($pid, $this->childrenGroupIds) || !in_array($pid, $groupTree->getChildrenIds($id, true))) {
{ $parentRuleList = $ruleTree->getTreeList($ruleTree->getTreeArray(0), 'name');
$ruleList = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'name');
$hasChildrens = []; $hasChildrens = [];
foreach ($ruleList as $k => $v) foreach ($parentRuleList as $k => $v) {
{ if ($v['haschild']) {
if ($v['haschild'])
$hasChildrens[] = $v['id']; $hasChildrens[] = $v['id'];
} }
$nodelist = []; }
foreach ($parentRuleList as $k => $v) $parentRuleIds = array_map(function ($item) {
{ return $item['id'];
if (!$superadmin && !in_array($v['id'], $admin_rule_ids)) }, $parentRuleList);
$nodeList = [];
foreach ($parentRuleList as $k => $v) {
if (!$superadmin && !in_array($v['id'], $adminRuleIds)) {
continue; continue;
$state = array('selected' => in_array($v['id'], $current_rule_ids) && !in_array($v['id'], $hasChildrens));
$nodelist[] = array('id' => $v['id'], 'parent' => $v['pid'] ? $v['pid'] : '#', 'text' => $v['title'], 'type' => 'menu', 'state' => $state);
} }
$this->code = 1; if ($v['pid'] && !in_array($v['pid'], $parentRuleIds)) {
$this->data = $nodelist; continue;
} }
else $state = array('selected' => in_array($v['id'], $currentRuleIds) && !in_array($v['id'], $hasChildrens));
{ $nodeList[] = array('id' => $v['id'], 'parent' => $v['pid'] ? $v['pid'] : '#', 'text' => __($v['title']), 'type' => 'menu', 'state' => $state);
$this->code = -1; }
$this->data = __('Can not change the parent to child'); $this->success('', null, $nodeList);
} else {
$this->error(__('Can not change the parent to child'));
}
} else {
$this->error(__('Group not found'));
} }
} }
else
{
$this->code = -1;
$this->data = __('Group not found');
}
}
} }

View File

@ -2,6 +2,7 @@
namespace app\admin\controller\auth; namespace app\admin\controller\auth;
use app\admin\model\AuthRule;
use app\common\controller\Backend; use app\common\controller\Backend;
use fast\Tree; use fast\Tree;
use think\Cache; use think\Cache;
@ -15,6 +16,9 @@ use think\Cache;
class Rule extends Backend class Rule extends Backend
{ {
/**
* @var \app\admin\model\AuthRule
*/
protected $model = null; protected $model = null;
protected $rulelist = []; protected $rulelist = [];
protected $multiFields = 'ismenu,status'; protected $multiFields = 'ismenu,status';
@ -24,15 +28,22 @@ class Rule extends Backend
parent::_initialize(); parent::_initialize();
$this->model = model('AuthRule'); $this->model = model('AuthRule');
// 必须将结果集转换为数组 // 必须将结果集转换为数组
Tree::instance()->init(collection($this->model->order('weigh', 'desc')->select())->toArray()); $ruleList = collection($this->model->order('weigh', 'desc')->order('id', 'asc')->select())->toArray();
foreach ($ruleList as $k => &$v) {
$v['title'] = __($v['title']);
$v['remark'] = __($v['remark']);
}
unset($v);
Tree::instance()->init($ruleList);
$this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title'); $this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
$ruledata = [0 => __('None')]; $ruledata = [0 => __('None')];
foreach ($this->rulelist as $k => $v) foreach ($this->rulelist as $k => &$v) {
{ if (!$v['ismenu']) {
if (!$v['ismenu'])
continue; continue;
}
$ruledata[$v['id']] = $v['title']; $ruledata[$v['id']] = $v['title'];
} }
unset($v);
$this->view->assign('ruledata', $ruledata); $this->view->assign('ruledata', $ruledata);
} }
@ -41,8 +52,7 @@ class Rule extends Backend
*/ */
public function index() public function index()
{ {
if ($this->request->isAjax()) if ($this->request->isAjax()) {
{
$list = $this->rulelist; $list = $this->rulelist;
$total = count($this->rulelist); $total = count($this->rulelist);
@ -58,23 +68,20 @@ class Rule extends Backend
*/ */
public function add() public function add()
{ {
if ($this->request->isPost()) if ($this->request->isPost()) {
{
$this->code = -1;
$params = $this->request->post("row/a", [], 'strip_tags'); $params = $this->request->post("row/a", [], 'strip_tags');
if ($params) if ($params) {
{ if (!$params['ismenu'] && !$params['pid']) {
if (!$params['ismenu'] && !$params['pid']) $this->error(__('The non-menu rule must have parent'));
{ }
$this->msg = __('The non-menu rule must have parent'); $result = $this->model->validate()->save($params);
return; if ($result === false) {
$this->error($this->model->getError());
} }
$this->model->create($params);
Cache::rm('__menu__'); Cache::rm('__menu__');
$this->code = 1; $this->success();
} }
$this->error();
return;
} }
return $this->view->fetch(); return $this->view->fetch();
} }
@ -82,28 +89,37 @@ class Rule extends Backend
/** /**
* 编辑 * 编辑
*/ */
public function edit($ids = NULL) public function edit($ids = null)
{ {
$row = $this->model->get(['id' => $ids]); $row = $this->model->get(['id' => $ids]);
if (!$row) if (!$row) {
$this->error(__('No Results were found')); $this->error(__('No Results were found'));
if ($this->request->isPost()) }
{ if ($this->request->isPost()) {
$this->code = -1;
$params = $this->request->post("row/a", [], 'strip_tags'); $params = $this->request->post("row/a", [], 'strip_tags');
if ($params) if ($params) {
{ if (!$params['ismenu'] && !$params['pid']) {
if (!$params['ismenu'] && !$params['pid']) $this->error(__('The non-menu rule must have parent'));
{ }
$this->msg = __('The non-menu rule must have parent'); if ($params['pid'] != $row['pid']) {
return; $childrenIds = Tree::instance()->init(collection(AuthRule::select())->toArray())->getChildrenIds($row['id']);
if (in_array($params['pid'], $childrenIds)) {
$this->error(__('Can not change the parent to child'));
}
}
//这里需要针对name做唯一验证
$ruleValidate = \think\Loader::validate('AuthRule');
$ruleValidate->rule([
'name' => 'require|format|unique:AuthRule,name,' . $row->id,
]);
$result = $row->validate()->save($params);
if ($result === false) {
$this->error($row->getError());
} }
$row->save($params);
Cache::rm('__menu__'); Cache::rm('__menu__');
$this->code = 1; $this->success();
} }
$this->error();
return;
} }
$this->view->assign("row", $row); $this->view->assign("row", $row);
return $this->view->fetch(); return $this->view->fetch();
@ -114,24 +130,18 @@ class Rule extends Backend
*/ */
public function del($ids = "") public function del($ids = "")
{ {
$this->code = -1; if ($ids) {
if ($ids)
{
$delIds = []; $delIds = [];
foreach (explode(',', $ids) as $k => $v) foreach (explode(',', $ids) as $k => $v) {
{ $delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, TRUE));
} }
$delIds = array_unique($delIds); $delIds = array_unique($delIds);
$count = $this->model->where('id', 'in', $delIds)->delete(); $count = $this->model->where('id', 'in', $delIds)->delete();
if ($count) if ($count) {
{
Cache::rm('__menu__'); Cache::rm('__menu__');
$this->code = 1; $this->success();
} }
} }
$this->error();
return;
} }
} }

View File

@ -1,70 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 表格完整示例
*
* @icon fa fa-table
* @remark 在使用Bootstrap-table中的常用方式,更多使用方式可查看:http://bootstrap-table.wenzhixin.net.cn/zh-cn/
*/
class Bootstraptable extends Backend
{
protected $model = null;
protected $noNeedRight = ['change', 'detail'];
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams(NULL);
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 详情
*/
public function detail($ids)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
$this->error(__('No Results were found'));
$this->view->assign("row", $row->toArray());
return $this->view->fetch();
}
/**
* 变更
* @internal
*/
public function change()
{
$this->code = 1;
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 彩色角标
*
* @icon fa fa-table
* @remark 在JS端控制角标的显示与隐藏,请注意左侧菜单栏角标的数值变化
*/
class Colorbadge extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 控制器间跳转
*
* @icon fa fa-table
* @remark FastAdmin支持在控制器间跳转,点击后将切换到另外一个TAB中,无需刷新当前页面
*/
class Controllerjump extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 多级联动
*
* @icon fa fa-table
* @remark FastAdmin使用了jQuery-cxselect实现多级联动,更多文档请参考https://github.com/karsonzhang/cxSelect
*/
class Cxselect extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
}
}

View File

@ -1,33 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 多表格示例
*
* @icon fa fa-table
* @remark 当一个页面上存在多个Bootstrap-table时该如何控制按钮和表格
*/
class Multitable extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
}
/**
* 查看
*/
public function index()
{
$this->loadlang('general/attachment');
$this->loadlang('general/crontab');
return $this->view->fetch();
}
}

View File

@ -1,52 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 多模型关联
*
* @icon fa fa-table
* @remark 当使用到关联模型时需要重载index方法
*/
class Relationmodel extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
/**
* 查看
*/
public function index()
{
$this->relationSearch = true;
$this->searchFields = "admin.username,id";
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->with("admin")
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->with("admin")
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 表格模板示例
*
* @icon fa fa-table
* @remark 可以通过使用表格模板将表格中的行渲染成一样的展现方式,基于此功能可以任意定制自己想要的展示列表
*/
class Tabletemplate extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams(NULL);
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 详情
*/
public function detail($ids)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
$this->error(__('No Results were found'));
$this->view->assign("row", $row->toArray());
return $this->view->fetch();
}
}

View File

@ -13,6 +13,9 @@ use app\common\controller\Backend;
class Attachment extends Backend class Attachment extends Backend
{ {
/**
* @var \app\common\model\Attachment
*/
protected $model = null; protected $model = null;
public function _initialize() public function _initialize()
@ -26,22 +29,37 @@ class Attachment extends Backend
*/ */
public function index() public function index()
{ {
if ($this->request->isAjax()) //设置过滤方法
{ $this->request->filter(['strip_tags']);
if ($this->request->isAjax()) {
$mimetypeQuery = [];
$filter = $this->request->request('filter');
$filterArr = (array)json_decode($filter, TRUE);
if (isset($filterArr['mimetype']) && stripos($filterArr['mimetype'], ',') !== false) {
$this->request->get(['filter' => json_encode(array_merge($filterArr, ['mimetype' => '']))]);
$mimetypeQuery = function ($query) use ($filterArr) {
$mimetypeArr = explode(',', $filterArr['mimetype']);
foreach ($mimetypeArr as $index => $item) {
$query->whereOr('mimetype', 'like', '%' . $item . '%');
}
};
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams(); list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model $total = $this->model
->where($mimetypeQuery)
->where($where) ->where($where)
->order($sort, $order) ->order($sort, $order)
->count(); ->count();
$list = $this->model $list = $this->model
->where($mimetypeQuery)
->where($where) ->where($where)
->order($sort, $order) ->order($sort, $order)
->limit($offset, $limit) ->limit($offset, $limit)
->select(); ->select();
$cdnurl = preg_replace("/\/(\w+)\.php$/i", '', $this->request->root()); $cdnurl = preg_replace("/\/(\w+)\.php$/i", '', $this->request->root());
foreach ($list as $k => &$v) foreach ($list as $k => &$v) {
{
$v['fullurl'] = ($v['storage'] == 'local' ? $cdnurl : $this->view->config['upload']['cdnurl']) . $v['url']; $v['fullurl'] = ($v['storage'] == 'local' ? $cdnurl : $this->view->config['upload']['cdnurl']) . $v['url'];
} }
unset($v); unset($v);
@ -57,8 +75,7 @@ class Attachment extends Backend
*/ */
public function select() public function select()
{ {
if ($this->request->isAjax()) if ($this->request->isAjax()) {
{
return $this->index(); return $this->index();
} }
return $this->view->fetch(); return $this->view->fetch();
@ -69,11 +86,33 @@ class Attachment extends Backend
*/ */
public function add() public function add()
{ {
if ($this->request->isAjax()) if ($this->request->isAjax()) {
{ $this->error();
$this->code = -1;
} }
return $this->view->fetch(); return $this->view->fetch();
} }
/**
* 删除附件
* @param array $ids
*/
public function del($ids = "")
{
if ($ids) {
\think\Hook::add('upload_delete', function ($params) {
$attachmentFile = ROOT_PATH . '/public' . $params['url'];
if (is_file($attachmentFile)) {
@unlink($attachmentFile);
}
});
$attachmentlist = $this->model->where('id', 'in', $ids)->select();
foreach ($attachmentlist as $attachment) {
\think\Hook::listen("upload_delete", $attachment);
$attachment->delete();
}
$this->success();
}
$this->error(__('Parameter %s can not be empty', 'ids'));
}
} }

View File

@ -10,11 +10,15 @@ use think\Exception;
/** /**
* 系统配置 * 系统配置
* *
* @icon fa fa-circle-o * @icon fa fa-cogs
* @remark 可以在此增改系统的变量和分组,也可以自定义分组和变量,如果需要删除请从数据库中删除
*/ */
class Config extends Backend class Config extends Backend
{ {
/**
* @var \app\common\model\Config
*/
protected $model = null; protected $model = null;
protected $noNeedRight = ['check']; protected $noNeedRight = ['check'];
@ -24,38 +28,34 @@ class Config extends Backend
$this->model = model('Config'); $this->model = model('Config');
} }
/**
* 查看
*/
public function index() public function index()
{ {
$siteList = []; $siteList = [];
$groupList = ConfigModel::getGroupList(); $groupList = ConfigModel::getGroupList();
foreach ($groupList as $k => $v) foreach ($groupList as $k => $v) {
{
$siteList[$k]['name'] = $k; $siteList[$k]['name'] = $k;
$siteList[$k]['title'] = $v; $siteList[$k]['title'] = $v;
$siteList[$k]['list'] = []; $siteList[$k]['list'] = [];
} }
foreach ($this->model->all() as $k => $v) foreach ($this->model->all() as $k => $v) {
{ if (!isset($siteList[$v['group']])) {
if (!isset($siteList[$v['group']]))
{
continue; continue;
} }
$value = $v->toArray(); $value = $v->toArray();
if (in_array($value['type'], ['select', 'selects', 'checkbox', 'radio'])) $value['title'] = __($value['title']);
{ if (in_array($value['type'], ['select', 'selects', 'checkbox', 'radio'])) {
$value['value'] = explode(',', $value['value']); $value['value'] = explode(',', $value['value']);
} }
if ($value['type'] == 'array')
{
$value['value'] = (array) json_decode($value['value'], TRUE);
}
$value['content'] = json_decode($value['content'], TRUE); $value['content'] = json_decode($value['content'], TRUE);
$value['tip'] = htmlspecialchars($value['tip']);
$siteList[$v['group']]['list'][] = $value; $siteList[$v['group']]['list'][] = $value;
} }
$index = 0; $index = 0;
foreach ($siteList as $k => &$v) foreach ($siteList as $k => &$v) {
{
$v['active'] = !$index ? true : false; $v['active'] = !$index ? true : false;
$index++; $index++;
} }
@ -70,141 +70,103 @@ class Config extends Backend
*/ */
public function add() public function add()
{ {
if ($this->request->isPost()) if ($this->request->isPost()) {
{
$this->code = -1;
$params = $this->request->post("row/a"); $params = $this->request->post("row/a");
if ($params) if ($params) {
{ foreach ($params as $k => &$v) {
foreach ($params as $k => &$v)
{
$v = is_array($v) ? implode(',', $v) : $v; $v = is_array($v) ? implode(',', $v) : $v;
} }
try try {
{ if (in_array($params['type'], ['select', 'selects', 'checkbox', 'radio', 'array'])) {
if ($params['content'] && in_array($params['type'], ['select', 'selects', 'checkbox', 'radio'])) $params['content'] = json_encode(ConfigModel::decode($params['content']), JSON_UNESCAPED_UNICODE);
{ } else {
$content = explode("\r\n", $params['content']);
$arr = [];
foreach ($content as $k => &$v)
{
if (stripos($v, "|") !== false)
{
$item = explode('|', $v);
$arr[$item[0]] = $item[1];
}
}
$params['content'] = $arr ? json_encode($arr, JSON_UNESCAPED_UNICODE) : '';
}
else
{
$params['content'] = ''; $params['content'] = '';
} }
$result = $this->model->create($params); $result = $this->model->create($params);
if ($result !== false) if ($result !== false) {
{ try {
try
{
$this->refreshFile(); $this->refreshFile();
$this->code = 1; } catch (Exception $e) {
$this->error($e->getMessage());
} }
catch (Exception $e) $this->success();
{ } else {
$this->msg = $e->getMessage(); $this->error($this->model->getError());
}
} catch (Exception $e) {
$this->error($e->getMessage());
} }
} }
else $this->error(__('Parameter %s can not be empty', ''));
{
$this->msg = $this->model->getError();
}
}
catch (Exception $e)
{
$this->msg = $e->getMessage();
}
}
else
{
$this->msg = __('Parameter %s can not be empty', '');
}
return;
} }
return $this->view->fetch(); return $this->view->fetch();
} }
/**
* 编辑
* @param null $ids
*/
public function edit($ids = NULL) public function edit($ids = NULL)
{ {
$this->code = -1; if ($this->request->isPost()) {
if ($this->request->isPost()) $row = $this->request->post("row/a");
{ if ($row) {
$params = $this->request->post("row/a");
if ($params)
{
$configList = []; $configList = [];
foreach ($this->model->all() as $k => $v) foreach ($this->model->all() as $v) {
{ if (isset($row[$v['name']])) {
if (isset($params[$v['name']])) $value = $row[$v['name']];
{ if (is_array($value) && isset($value['field'])) {
if ($v['type'] == 'array') $value = json_encode(ConfigModel::getArrayData($value), JSON_UNESCAPED_UNICODE);
{ } else {
$fieldarr = $valuearr = []; $value = is_array($value) ? implode(',', $value) : $value;
$field = $params[$v['name']]['field']; }
$value = $params[$v['name']]['value']; $v['value'] = $value;
$configList[] = $v->toArray();
foreach ($field as $m => $n)
{
if ($n != '')
{
$fieldarr[] = $field[$m];
$valuearr[] = $value[$m];
} }
} }
$params[$v['name']] = array_combine($fieldarr, $valuearr); $this->model->allowField(true)->saveAll($configList);
$value = json_encode($params[$v['name']], JSON_UNESCAPED_UNICODE); try {
}
else
{
$value = is_array($params[$v['name']]) ? implode(',', $params[$v['name']]) : $params[$v['name']];
}
$configList[] = ['id' => $v['id'], 'value' => $value];
}
}
$this->model->saveAll($configList);
try
{
$this->refreshFile(); $this->refreshFile();
$this->code = 1; } catch (Exception $e) {
$this->error($e->getMessage());
} }
catch (Exception $e) $this->success();
{
$this->msg = $e->getMessage();
} }
} $this->error(__('Parameter %s can not be empty', ''));
else
{
$this->msg = __('Parameter %s can not be empty', '');
}
return;
} }
} }
public function del($ids = "")
{
$name = $this->request->request('name');
$config = ConfigModel::getByName($name);
if ($config) {
try {
$config->delete();
$this->refreshFile();
} catch (Exception $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
$this->error(__('Invalid parameters'));
}
}
/**
* 刷新配置文件
*/
protected function refreshFile() protected function refreshFile()
{ {
$config = []; $config = [];
foreach ($this->model->all() as $k => $v) foreach ($this->model->all() as $k => $v) {
{
$value = $v->toArray(); $value = $v->toArray();
if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'])) if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'])) {
{
$value['value'] = explode(',', $value['value']); $value['value'] = explode(',', $value['value']);
} }
if ($value['type'] == 'array') if ($value['type'] == 'array') {
{ $value['value'] = (array)json_decode($value['value'], TRUE);
$value['value'] = (array) json_decode($value['value'], TRUE);
} }
$config[$value['name']] = $value['value']; $config[$value['name']] = $value['value'];
} }
@ -212,27 +174,22 @@ class Config extends Backend
} }
/** /**
* 检测配置项是否存在
* @internal * @internal
*/ */
public function check() public function check()
{ {
$params = $this->request->post("row/a"); $params = $this->request->post("row/a");
if ($params) if ($params) {
{
$config = $this->model->get($params); $config = $this->model->get($params);
if (!$config) if (!$config) {
{ return $this->success();
return json(['ok' => '']); } else {
return $this->error(__('Name already exist'));
} }
else } else {
{ return $this->error(__('Invalid parameters'));
return json(['error' => __('Name already exist')]);
}
}
else
{
return json(['error' => __('Invalid parameters')]);
} }
} }
@ -242,6 +199,8 @@ class Config extends Backend
*/ */
public function emailtest() public function emailtest()
{ {
$row = $this->request->post('row/a');
\think\Config::set('site', array_merge(\think\Config::get('site'), $row));
$receiver = $this->request->request("receiver"); $receiver = $this->request->request("receiver");
$email = new Email; $email = new Email;
$result = $email $result = $email
@ -249,14 +208,10 @@ class Config extends Backend
->subject(__("This is a test mail")) ->subject(__("This is a test mail"))
->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content') . '</div>') ->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content') . '</div>')
->send(); ->send();
if ($result) if ($result) {
{ $this->success();
$this->code = 1; } else {
} $this->error($email->getError());
else
{
$this->code = -1;
$this->msg = $email->getError();
} }
} }

View File

@ -1,100 +0,0 @@
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
use Cron\CronExpression;
/**
* 定时任务
*
* @icon fa fa-tasks
* @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前仅支持三种任务:请求URL、执行SQL、执行Shell
*/
class Crontab extends Backend
{
protected $model = null;
protected $noNeedRight = ['check_schedule', 'get_schedule_future'];
public function _initialize()
{
parent::_initialize();
$this->model = model('Crontab');
$this->view->assign('typedata', \app\common\model\Crontab::getTypeList());
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
foreach ($list as $k => &$v)
{
$cron = CronExpression::factory($v['schedule']);
$v['nexttime'] = $cron->getNextRunDate()->getTimestamp();
}
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 判断Crontab格式是否正确
* @internal
*/
public function check_schedule()
{
$row = $this->request->post("row/a");
$schedule = isset($row['schedule']) ? $row['schedule'] : '';
if (CronExpression::isValidExpression($schedule))
{
return json(['ok' => '']);
}
else
{
return json(['error' => __('Crontab format invalid')]);
}
}
/**
* 根据Crontab表达式读取未来七次的时间
* @internal
*/
public function get_schedule_future()
{
$time = [];
$schedule = $this->request->post('schedule');
$days = (int) $this->request->post('days');
try
{
$cron = CronExpression::factory($schedule);
for ($i = 0; $i < $days; $i++)
{
$time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s');
}
}
catch (\Exception $e)
{
}
return json(['futuretime' => $time]);
}
}

View File

@ -1,211 +0,0 @@
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
use think\Db;
use think\Debug;
/**
* 数据库管理
*
* @icon fa fa-database
* @remark 可在线进行一些简单的数据库表优化或修复,查看表结构和数据。也可以进行SQL语句的操作
*/
class Database extends Backend
{
/**
* 查看
*/
function index()
{
$tables_data_length = $tables_index_length = $tables_free_length = $tables_data_count = 0;
$tables = $list = [];
$list = Db::query("SHOW TABLES");
foreach ($list as $key => $row)
{
$tables[] = ['name' => reset($row), 'rows' => 0];
}
$data['tables'] = $tables;
$data['saved_sql'] = [];
$this->view->assign($data);
return $this->view->fetch();
}
/**
* SQL查询
*/
public function query()
{
$do_action = $this->request->post('do_action');
echo '<style type="text/css">
xmp,body{margin:0;padding:0;line-height:18px;font-size:12px;font-family:"Helvetica Neue", Helvetica, Microsoft Yahei, Hiragino Sans GB, WenQuanYi Micro Hei, sans-serif;}
hr{height:1px;margin:5px 1px;background:#e3e3e3;border:none;}
</style>';
if ($do_action == '')
exit(__('Invalid parameters'));
$tablename = $this->request->post("tablename/a");
if (in_array($do_action, array('doquery', 'optimizeall', 'repairall')))
{
$this->$do_action();
}
else if (count($tablename) == 0)
{
exit(__('Invalid parameters'));
}
else
{
foreach ($tablename as $table)
{
$this->$do_action($table);
echo "<br />";
}
}
}
private function viewinfo($name)
{
$row = Db::query("SHOW CREATE TABLE `{$name}`");
$row = array_values($row[0]);
$info = $row[1];
echo "<xmp>{$info};</xmp>";
}
private function viewdata($name = '')
{
$sqlquery = "SELECT * FROM `{$name}`";
$this->doquery($sqlquery);
}
private function optimize($name = '')
{
if (Db::execute("OPTIMIZE TABLE `{$name}`"))
{
echo __('Optimize table %s done', $name);
}
else
{
echo __('Optimize table %s fail', $name);
}
}
private function optimizeall($name = '')
{
$list = Db::query("SHOW TABLES");
foreach ($list as $key => $row)
{
$name = reset($row);
if (Db::execute("OPTIMIZE TABLE {$name}"))
{
echo __('Optimize table %s done', $name);
}
else
{
echo __('Optimize table %s fail', $name);
}
echo "<br />";
}
}
private function repair($name = '')
{
if (Db::execute("REPAIR TABLE `{$name}`"))
{
echo __('Repair table %s done', $name);
}
else
{
echo __('Repair table %s fail', $name);
}
}
private function repairall($name = '')
{
$list = Db::query("SHOW TABLES");
foreach ($list as $key => $row)
{
$name = reset($row);
if (Db::execute("REPAIR TABLE {$name}"))
{
echo __('Repair table %s done', $name);
}
else
{
echo __('Repair table %s fail', $name);
}
echo "<br />";
}
}
private function doquery($sql = null)
{
$sqlquery = $sql ? $sql : $this->request->post('sqlquery');
if ($sqlquery == '')
exit(__('SQL can not be empty'));
$sqlquery = str_replace("\r", "", $sqlquery);
$sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery);
$maxreturn = 100;
$r = '';
foreach ($sqls as $key => $val)
{
if (trim($val) == '')
continue;
$val = rtrim($val, ';');
$r .= "SQL<span style='color:green;'>{$val}</span> ";
if (preg_match("/^(select|explain)(.*)/i ", $val))
{
Debug::remark("begin");
$limit = stripos(strtolower($val), "limit") !== false ? true : false;
$count = Db::execute($val);
if ($count > 0)
{
$resultlist = Db::query($val . (!$limit && $count > $maxreturn ? ' LIMIT ' . $maxreturn : ''));
}
else
{
$resultlist = [];
}
Debug::remark("end");
$time = Debug::getRangeTime('begin', 'end', 4);
$usedseconds = __('Query took %s seconds', $time) . "<br />";
if ($count <= 0)
{
$r .= __('Query returned an empty result');
}
else
{
$r .= (__('Total:%s', $count) . (!$limit && $count > $maxreturn ? ',' . __('Max output:%s', $maxreturn) : ""));
}
$r = $r . ',' . $usedseconds;
$j = 0;
foreach ($resultlist as $m => $n)
{
$j++;
if (!$limit && $j > $maxreturn)
break;
$r .= "<hr/>";
$r .= "<font color='red'>" . __('Row:%s', $j) . "</font><br />";
foreach ($n as $k => $v)
{
$r .= "<font color='blue'>{$k}</font>{$v}<br/>\r\n";
}
}
}
else
{
Debug::remark("begin");
$count = Db::execute($val);
Debug::remark("end");
$time = Debug::getRangeTime('begin', 'end', 4);
$r .= __('Query affected %s rows and took %s seconds', $count, $time) . "<br />";
}
}
echo $r;
}
}

View File

@ -2,10 +2,10 @@
namespace app\admin\controller\general; namespace app\admin\controller\general;
use think\Session; use app\admin\model\Admin;
use app\admin\model\AdminLog;
use app\common\controller\Backend; use app\common\controller\Backend;
use fast\Random; use fast\Random;
use think\Session;
/** /**
* 个人配置 * 个人配置
@ -20,6 +20,8 @@ class Profile extends Backend
*/ */
public function index() public function index()
{ {
//设置过滤方法
$this->request->filter(['strip_tags']);
if ($this->request->isAjax()) if ($this->request->isAjax())
{ {
$model = model('AdminLog'); $model = model('AdminLog');
@ -52,7 +54,6 @@ class Profile extends Backend
{ {
if ($this->request->isPost()) if ($this->request->isPost())
{ {
$this->code = -1;
$params = $this->request->post("row/a"); $params = $this->request->post("row/a");
$params = array_filter(array_intersect_key($params, array_flip(array('email', 'nickname', 'password', 'avatar')))); $params = array_filter(array_intersect_key($params, array_flip(array('email', 'nickname', 'password', 'avatar'))));
unset($v); unset($v);
@ -63,16 +64,13 @@ class Profile extends Backend
} }
if ($params) if ($params)
{ {
model('admin')->where('id', $this->auth->id)->update($params); $admin = Admin::get($this->auth->id);
$admin->save($params);
//因为个人资料面板读取的Session显示修改自己资料后同时更新Session //因为个人资料面板读取的Session显示修改自己资料后同时更新Session
$admin = Session::get('admin'); Session::set("admin", $admin->toArray());
$admin_id = $admin ? $admin->id : 0; $this->success();
if($this->auth->id==$admin_id){
$admin = model('admin')->get(['id' => $admin_id]);
Session::set("admin", $admin);
}
$this->code = 1;
} }
$this->error();
} }
return; return;
} }

View File

@ -0,0 +1,45 @@
<?php
namespace app\admin\controller\user;
use app\common\controller\Backend;
/**
* 会员组管理
*
* @icon fa fa-users
*/
class Group extends Backend
{
/**
* @var \app\admin\model\UserGroup
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('UserGroup');
$this->view->assign("statusList", $this->model->getStatusList());
}
public function add()
{
$nodeList = \app\admin\model\UserRule::getTreeList();
$this->assign("nodeList", $nodeList);
return parent::add();
}
public function edit($ids = NULL)
{
$row = $this->model->get($ids);
if (!$row)
$this->error(__('No Results were found'));
$rules = explode(',', $row['rules']);
$nodeList = \app\admin\model\UserRule::getTreeList($rules);
$this->assign("nodeList", $nodeList);
return parent::edit($ids);
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace app\admin\controller\user;
use app\common\controller\Backend;
use fast\Tree;
/**
* 会员规则管理
*
* @icon fa fa-circle-o
*/
class Rule extends Backend
{
/**
* @var \app\admin\model\UserRule
*/
protected $model = null;
protected $rulelist = [];
protected $multiFields = 'ismenu,status';
public function _initialize()
{
parent::_initialize();
$this->model = model('UserRule');
$this->view->assign("statusList", $this->model->getStatusList());
// 必须将结果集转换为数组
$ruleList = collection($this->model->order('weigh', 'desc')->select())->toArray();
foreach ($ruleList as $k => &$v)
{
$v['title'] = __($v['title']);
$v['remark'] = __($v['remark']);
}
unset($v);
Tree::instance()->init($ruleList);
$this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
$ruledata = [0 => __('None')];
foreach ($this->rulelist as $k => &$v)
{
if (!$v['ismenu'])
continue;
$ruledata[$v['id']] = $v['title'];
}
$this->view->assign('ruledata', $ruledata);
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
$list = $this->rulelist;
$total = count($this->rulelist);
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 删除
*/
public function del($ids = "")
{
if ($ids)
{
$delIds = [];
foreach (explode(',', $ids) as $k => $v)
{
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, TRUE));
}
$delIds = array_unique($delIds);
$count = $this->model->where('id', 'in', $delIds)->delete();
if ($count)
{
$this->success();
}
}
$this->error();
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace app\admin\controller\user;
use app\common\controller\Backend;
/**
* 会员管理
*
* @icon fa fa-user
*/
class User extends Backend
{
protected $relationSearch = true;
/**
* @var \app\admin\model\User
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('User');
}
/**
* 查看
*/
public function index()
{
//设置过滤方法
$this->request->filter(['strip_tags']);
if ($this->request->isAjax()) {
//如果发送的来源是Selectpage则转发到Selectpage
if ($this->request->request('keyField')) {
return $this->selectpage();
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->with('group')
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->with('group')
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
foreach ($list as $k => $v) {
$v->hidden(['password', 'salt']);
}
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 编辑
*/
public function edit($ids = NULL)
{
$row = $this->model->get($ids);
if (!$row)
$this->error(__('No Results were found'));
$this->view->assign('groupList', build_select('row[group_id]', \app\admin\model\UserGroup::column('id,name'), $row['group_id'], ['class' => 'form-control selectpicker']));
return parent::edit($ids);
}
}

View File

@ -1,74 +0,0 @@
<?php
namespace app\admin\controller\wechat;
use app\common\controller\Backend;
use app\common\model\WechatResponse;
/**
* 微信自动回复管理
*
* @icon fa fa-circle-o
*/
class Autoreply extends Backend
{
protected $model = null;
protected $noNeedRight = ['check_text_unique'];
public function _initialize()
{
parent::_initialize();
$this->model = model('WechatAutoreply');
}
/**
* 编辑
*/
public function edit($ids = NULL)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
if ($params)
{
$row->save($params);
$this->code = 1;
}
return;
}
$response = WechatResponse::get(['eventkey' => $row['eventkey']]);
$this->view->assign("response", $response);
$this->view->assign("row", $row);
return $this->view->fetch();
}
/**
* 判断文本是否唯一
* @internal
*/
public function check_text_unique()
{
$row = $this->request->post("row/a");
$except = $this->request->post("except");
$text = isset($row['text']) ? $row['text'] : '';
if ($this->model->where('text', $text)->where(function($query) use($except) {
if ($except)
{
$query->where('text', '<>', $except);
}
})->count() == 0)
{
return json(['ok' => '']);
}
else
{
return json(['error' => __('Text already exists')]);
}
}
}

View File

@ -1,164 +0,0 @@
<?php
namespace app\admin\controller\wechat;
use app\common\controller\Backend;
use think\Controller;
use think\Request;
/**
* 微信配置管理
*
* @icon fa fa-circle-o
*/
class Config extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('WechatConfig');
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
if ($params)
{
foreach ($params as $k => &$v)
{
$v = is_array($v) ? implode(',', $v) : $v;
}
if ($params['mode'] == 'json')
{
//JSON字段
$fieldarr = $valuearr = [];
$field = $this->request->post('field/a');
$value = $this->request->post('value/a');
foreach ($field as $k => $v)
{
if ($v != '')
{
$fieldarr[] = $field[$k];
$valuearr[] = $value[$k];
}
}
$params['value'] = json_encode(array_combine($fieldarr, $valuearr), JSON_UNESCAPED_UNICODE);
}
unset($params['mode']);
try
{
//是否采用模型验证
if ($this->modelValidate)
{
$name = basename(str_replace('\\', '/', get_class($this->model)));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : true) : $this->modelValidate;
$this->model->validate($validate);
}
$result = $this->model->save($params);
if ($result !== false)
{
$this->code = 1;
}
else
{
$this->msg = $this->model->getError();
}
}
catch (\think\exception\PDOException $e)
{
$this->msg = $e->getMessage();
}
}
else
{
$this->msg = __('Parameter %s can not be empty', '');
}
return;
}
return $this->view->fetch();
}
/**
* 编辑
*/
public function edit($ids = NULL)
{
$row = $this->model->get($ids);
if (!$row)
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
if ($params)
{
foreach ($params as $k => &$v)
{
$v = is_array($v) ? implode(',', $v) : $v;
}
if ($params['mode'] == 'json')
{
//JSON字段
$fieldarr = $valuearr = [];
$field = $this->request->post('field/a');
$value = $this->request->post('value/a');
foreach ($field as $k => $v)
{
if ($v != '')
{
$fieldarr[] = $field[$k];
$valuearr[] = $value[$k];
}
}
$params['value'] = json_encode(array_combine($fieldarr, $valuearr), JSON_UNESCAPED_UNICODE);
}
unset($params['mode']);
try
{
//是否采用模型验证
if ($this->modelValidate)
{
$name = basename(str_replace('\\', '/', get_class($this->model)));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : true) : $this->modelValidate;
$row->validate($validate);
}
$result = $row->save($params);
if ($result !== false)
{
$this->code = 1;
}
else
{
$this->msg = $row->getError();
}
}
catch (think\exception\PDOException $e)
{
$this->msg = $e->getMessage();
}
}
else
{
$this->msg = __('Parameter %s can not be empty', '');
}
return;
}
$this->view->assign("row", $row);
$this->view->assign("value", (array) json_decode($row->value, true));
return $this->view->fetch();
}
}

View File

@ -1,111 +0,0 @@
<?php
namespace app\admin\controller\wechat;
use app\common\controller\Backend;
use app\common\model\WechatResponse;
use EasyWeChat\Foundation\Application;
use think\Config;
use think\Exception;
/**
* 菜单管理
*
* @icon fa fa-list-alt
*/
class Menu extends Backend
{
protected $wechatcfg = NULL;
public function _initialize()
{
parent::_initialize();
$this->wechatcfg = \app\common\model\WechatConfig::get(['name' => 'menu']);
}
/**
* 查看
*/
public function index()
{
$responselist = array();
$all = WechatResponse::all();
foreach ($all as $k => $v)
{
$responselist[$v['eventkey']] = $v['title'];
}
$this->view->assign('responselist', $responselist);
$this->view->assign('menu', (array) json_decode($this->wechatcfg->value, TRUE));
return $this->view->fetch();
}
/**
* 修改
*/
public function edit($ids = NULL)
{
$menu = $this->request->post("menu");
$menu = (array) json_decode($menu, TRUE);
$this->wechatcfg->value = json_encode($menu, JSON_UNESCAPED_UNICODE);
$this->wechatcfg->save();
$this->code = 1;
return;
}
/**
* 同步
*/
public function sync($ids = NULL)
{
$this->code = -1;
$app = new Application(Config::get('wechat'));
try
{
$hasError = false;
$menu = json_decode($this->wechatcfg->value, TRUE);
foreach ($menu as $k => $v)
{
if (isset($v['sub_button']))
{
foreach ($v['sub_button'] as $m => $n)
{
if (isset($n['key']) && !$n['key'])
{
$hasError = true;
break 2;
}
}
}
else if (isset($v['key']) && !$v['key'])
{
$hasError = true;
break;
}
}
if (!$hasError)
{
$ret = $app->menu->add($menu);
if ($ret->errcode == 0)
{
$this->code = 1;
}
else
{
$this->msg = $ret->errmsg;
}
}
else
{
$this->msg = __('Invalid parameters');
}
}
catch (Exception $e)
{
$this->msg = $e->getMessage();
}
return;
}
}

View File

@ -1,86 +0,0 @@
<?php
namespace app\admin\controller\wechat;
use app\common\controller\Backend;
use fast\service\Wechat;
/**
* 资源管理
*
* @icon fa fa-list-alt
*/
class Response extends Backend
{
protected $model = null;
protected $searchFields = 'id,title';
public function _initialize()
{
parent::_initialize();
$this->model = model('WechatResponse');
}
/**
* 选择素材
*/
public function select()
{
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
$params['eventkey'] = isset($params['eventkey']) && $params['eventkey'] ? $params['eventkey'] : uniqid();
$params['content'] = json_encode($params['content']);
$params['createtime'] = time();
if ($params)
{
$this->model->save($params);
$this->code = 1;
$this->content = $params;
}
return;
}
$appConfig = Wechat::appConfig();
$this->view->applist = $appConfig;
return $this->view->fetch();
}
/**
* 编辑
*/
public function edit($ids = NULL)
{
$row = $this->model->get($ids);
if (!$row)
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
$params['eventkey'] = isset($params['eventkey']) && $params['eventkey'] ? $params['eventkey'] : uniqid();
$params['content'] = json_encode($params['content']);
if ($params)
{
$row->save($params);
$this->code = 1;
}
return;
}
$this->view->assign("row", $row);
$appConfig = Wechat::appConfig();
$this->view->applist = $appConfig;
return $this->view->fetch();
}
}

93
application/admin/lang/zh-cn.php 100644 → 100755
View File

@ -11,10 +11,17 @@ return [
'Keep login' => '保持会话', 'Keep login' => '保持会话',
'Guest' => '游客', 'Guest' => '游客',
'Welcome' => '%s你好', 'Welcome' => '%s你好',
'View' => '查看',
'Add' => '添加', 'Add' => '添加',
'Edit' => '编辑', 'Edit' => '编辑',
'Del' => '删除',
'Delete' => '删除', 'Delete' => '删除',
'Import' => '导入',
'Export' => '导出',
'All' => '全部',
'Detail' => '详情', 'Detail' => '详情',
'Multi' => '批量更新',
'Setting' => '配置',
'Move' => '移动', 'Move' => '移动',
'Name' => '名称', 'Name' => '名称',
'Status' => '状态', 'Status' => '状态',
@ -25,8 +32,19 @@ return [
'Article' => '文章', 'Article' => '文章',
'Page' => '单页', 'Page' => '单页',
'OK' => '确定', 'OK' => '确定',
'Apply' => '应用',
'Cancel' => '取消', 'Cancel' => '取消',
'Clear' => '清空',
'Custom Range' => '自定义',
'Today' => '今天',
'Yesterday' => '昨天',
'Last 7 days' => '最近7天',
'Last 30 days' => '最近30天',
'Last month' => '上月',
'This month' => '本月',
'Loading' => '加载中', 'Loading' => '加载中',
'Money' => '余额',
'Score' => '积分',
'More' => '更多', 'More' => '更多',
'Yes' => '是', 'Yes' => '是',
'No' => '否', 'No' => '否',
@ -40,6 +58,8 @@ return [
'Choose' => '选择', 'Choose' => '选择',
'Search' => '搜索', 'Search' => '搜索',
'Refresh' => '刷新', 'Refresh' => '刷新',
'Install' => '安装',
'Uninstall' => '卸载',
'First' => '首页', 'First' => '首页',
'Previous' => '上一页', 'Previous' => '上一页',
'Next' => '下一页', 'Next' => '下一页',
@ -53,20 +73,16 @@ return [
'Index' => '首页', 'Index' => '首页',
'Hot' => '热门', 'Hot' => '热门',
'Recommend' => '推荐', 'Recommend' => '推荐',
'Dashboard' => '控制台',
'Upload' => '上传', 'Upload' => '上传',
'Uploading' => '上传中',
'Code' => '编号', 'Code' => '编号',
'Message' => '内容', 'Message' => '内容',
'Line' => '行号', 'Line' => '行号',
'File' => '文件', 'File' => '文件',
'Menu' => '菜单', 'Menu' => '菜单',
'Name' => '名称',
'Weigh' => '权重',
'Type' => '类型', 'Type' => '类型',
'Title' => '标题', 'Title' => '标题',
'Content' => '内容', 'Content' => '内容',
'Status' => '状态',
'Operate' => '操作',
'Append' => '追加', 'Append' => '追加',
'Select' => '选择', 'Select' => '选择',
'Memo' => '备注', 'Memo' => '备注',
@ -80,17 +96,13 @@ return [
'Create time' => '创建时间', 'Create time' => '创建时间',
'Update time' => '更新时间', 'Update time' => '更新时间',
'Flag' => '标志', 'Flag' => '标志',
'Drag to sort' => '拖动进行排序',
'Redirect now' => '立即跳转', 'Redirect now' => '立即跳转',
'Operation completed' => '操作成功!', 'Key' => '键',
'Operation failed' => '操作失败!', 'Value' => '值',
'Unknown data format' => '未知的数据格式!',
'Network error' => '网络错误!',
'Auth manager' => '权限管理',
'General manager' => '常规管理',
'Example manager' => '示例管理',
'Wechat manager' => '微信管理',
'Common search' => '普通搜索', 'Common search' => '普通搜索',
'Search %s' => '搜索 %s', 'Search %s' => '搜索 %s',
'View %s' => '查看 %s',
'%d second%s ago' => '%d秒前', '%d second%s ago' => '%d秒前',
'%d minute%s ago' => '%d分钟前', '%d minute%s ago' => '%d分钟前',
'%d hour%s ago' => '%d小时前', '%d hour%s ago' => '%d小时前',
@ -98,19 +110,72 @@ return [
'%d week%s ago' => '%d周前', '%d week%s ago' => '%d周前',
'%d month%s ago' => '%d月前', '%d month%s ago' => '%d月前',
'%d year%s ago' => '%d年前', '%d year%s ago' => '%d年前',
//
'Set to normal' => '设为正常', 'Set to normal' => '设为正常',
'Set to hidden' => '设为隐藏', 'Set to hidden' => '设为隐藏',
'Recycle bin' => '回收站',
'Restore' => '还原',
'Restore all' => '还原全部',
'Destroy' => '销毁',
'Destroy all' => '清空回收站',
'Nothing need restore' => '没有需要还原的数据',
//提示
'Go back' => '返回首页',
'Jump now' => '立即跳转',
'Click to search %s' => '点击搜索 %s',
'Click to toggle' => '点击切换',
'Operation completed' => '操作成功!',
'Operation failed' => '操作失败!',
'Unknown data format' => '未知的数据格式!',
'Network error' => '网络错误!',
'Invalid parameters' => '未知参数', 'Invalid parameters' => '未知参数',
'No results were found' => '记录未找到', 'No results were found' => '记录未找到',
'No rows were inserted' => '未插入任何行',
'No rows were deleted' => '未删除任何行',
'No rows were updated' => '未更新任何行',
'Parameter %s can not be empty' => '参数%s不能为空', 'Parameter %s can not be empty' => '参数%s不能为空',
'Are you sure you want to delete the %s selected item?' => '确定删除选中的 %s 项?', 'Are you sure you want to delete the %s selected item?' => '确定删除选中的 %s 项?',
'Are you sure you want to delete this item?' => '确定删除此项?', 'Are you sure you want to delete this item?' => '确定删除此项?',
'Are you sure you want to delete or turncate?' => '确定删除或清空?', 'Are you sure you want to delete or turncate?' => '确定删除或清空?',
'Are you sure you want to truncate?' => '确定清空?',
'You have no permission' => '你没有权限访问', 'You have no permission' => '你没有权限访问',
'Please enter your username' => '请输入你的用户名', 'Please enter your username' => '请输入你的用户名',
'Please enter your password' => '请输入你的密码', 'Please enter your password' => '请输入你的密码',
'Please login first' => '请登录后操作', 'Please login first' => '请登录后操作',
'You can upload up to %d file%s' => '你最多还可以上传%d个文件',
'You can choose up to %d file%s' => '你最多还可以选择%d个文件',
'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中', 'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转', 'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
//菜单
'Dashboard' => '控制台',
'General' => '常规管理',
'Category' => '分类管理',
'Addon' => '插件管理',
'Auth' => '权限管理',
'Config' => '系统配置',
'Attachment' => '附件管理',
'Admin' => '管理员管理',
'Admin log' => '管理员日志',
'Group' => '角色组',
'Rule' => '菜单规则',
'User' => '会员管理',
'User group' => '会员分组',
'User rule' => '会员规则',
'Select attachment' => '选择附件',
'Update profile' => '更新个人信息',
'Local install' => '本地安装',
'Update state' => '禁用启用',
'Admin group' => '超级管理组',
'Second group' => '二级管理组',
'Third group' => '三级管理组',
'Second group 2' => '二级管理组2',
'Third group 2' => '三级管理组2',
'Dashboard tips' => '用于展示当前系统中的统计数据、统计报表及重要实时数据',
'Config tips' => '可以在此增改系统的变量和分组,也可以自定义分组和变量,如果需要删除请从数据库中删除',
'Category tips' => '用于统一管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加',
'Attachment tips' => '主要用于管理上传到服务器或第三方存储的数据',
'Addon tips' => '可在线安装、卸载、禁用、启用插件同时支持添加本地插件。FastAdmin已上线插件商店 ,你可以发布你的免费或付费插件:<a href="https://www.fastadmin.net/store.html" target="_blank">https://www.fastadmin.net/store.html</a>',
'Admin tips' => '一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成',
'Admin log tips' => '管理员可以查看自己所拥有的权限的管理员日志',
'Group tips' => '角色组可以有多个,角色有上下级层级关系,如果子角色有角色组和管理员的权限则可以派生属于自己组别的下级角色组或管理员',
'Rule tips' => '规则通常对应一个控制器的方法,同时左侧的菜单栏数据也从规则中体现,通常建议通过命令行进行生成规则节点',
]; ];

View File

@ -0,0 +1,93 @@
<?php
return [
'Id' => 'ID',
'Title' => '插件名称',
'Value' => '配置值',
'Array key' => '键',
'Array value' => '值',
'File' => '文件',
'Donate' => '打赏作者',
'Warmtips' => '温馨提示',
'Pay now' => '立即支付',
'Offline install' => '离线安装',
'Refresh addon cache' => '刷新插件缓存',
'Userinfo' => '会员信息',
'Online store' => '在线商店',
'Local addon' => '本地插件',
'Conflict tips' => '此插件中发现和现有系统中部分文件发现冲突!以下文件将会被影响,请备份好相关文件后再继续操作',
'Login tips' => '此处登录账号为<a href="https://www.fastadmin.net" target="_blank">FastAdmin官网账号</a>',
'Logined tips' => '你好!%s<br />当前你已经登录,将同步保存你的购买记录',
'Pay tips' => '扫码支付后如果仍然无法立即下载,请不要重复支付,请加<a href="https://jq.qq.com/?_wv=1027&k=487PNBb" target="_blank">QQ群636393962</a>向管理员反馈',
'Pay click tips' => '请点击这里在新窗口中进行支付!',
'Pay new window tips' => '请在新弹出的窗口中进行支付,支付完成后再重新点击安装按钮进行安装!',
'Uninstall tips' => '确认卸载<b>[%s]</b><p class="text-danger">卸载将会删除所有插件文件且不可找回!!! 插件如果有创建数据库表请手动删除!!!</p>如有重要数据请备份后再操作!',
'Upgrade tips' => '确认升级<b>[%s]</b><p class="text-danger">如果之前购买插件时未登录,此次升级可能出现购买后才可以下载的提示!!!<br>升级后可能出现部分冗余数据记录,请根据需要移除即可!!!</p>如有重要数据请备份后再操作!',
'Offline installed tips' => '插件安装成功!清除浏览器缓存和框架缓存后生效!',
'Online installed tips' => '插件安装成功!清除浏览器缓存和框架缓存后生效!',
'Not login tips' => '你当前未登录FastAdmin登录后将同步已购买的记录下载时无需二次付费',
'Not installed tips' => '请安装后再访问插件前台页面!',
'Not enabled tips' => '插件已经禁用,请启用后再访问插件前台页面!',
'New version tips' => '发现新版本:%s 点击查看更新日志',
'Store now available tips' => 'FastAdmin插件市场暂不可用是否切换到本地插件',
'Switch to the local' => '切换到本地插件',
'try to reload' => '重新尝试加载',
'Please disable addon first' => '请先禁用插件再进行升级',
'Login now' => '立即登录',
'Continue install' => '不登录,继续安装',
'View addon home page' => '查看插件介绍和帮助',
'View addon index page' => '查看插件前台首页',
'View addon screenshots' => '点击查看插件截图',
'Click to toggle status' => '点击切换插件状态',
'Click to contact developer' => '点击与插件开发者取得联系',
'My addons' => '我购买的插件',
'My posts' => '我发布的插件',
'Index' => '前台',
'All' => '全部',
'Uncategoried' => '未归类',
'Recommend' => '推荐',
'Hot' => '热门',
'New' => '新',
'Paying' => '付费',
'Free' => '免费',
'Sale' => '折扣',
'No image' => '暂无缩略图',
'Price' => '价格',
'Downloads' => '下载',
'Author' => '作者',
'Identify' => '标识',
'Homepage' => '主页',
'Intro' => '介绍',
'Version' => '版本',
'New version' => '新版本',
'Createtime' => '添加时间',
'Releasetime' => '更新时间',
'Detail' => '插件详情',
'Document' => '文档',
'Demo' => '演示',
'Feedback' => '反馈BUG',
'Install' => '安装',
'Uninstall' => '卸载',
'Upgrade' => '升级',
'Setting' => '配置',
'Disable' => '禁用',
'Enable' => '启用',
'Your username or email' => '你的手机号、用户名或邮箱',
'Your password' => '你的密码',
'Login FastAdmin' => '登录FastAdmin',
'Login' => '登录',
'Logout' => '退出登录',
'Register' => '注册账号',
'You\'re not login' => '当前未登录',
'Continue uninstall' => '继续卸载',
'Continue operate' => '继续操作',
'Install successful' => '安装成功',
'Uninstall successful' => '卸载成功',
'Operate successful' => '操作成功',
'Addon name incorrect' => '插件名称不正确',
'Addon info file was not found' => '插件配置文件未找到',
'Addon info file data incorrect' => '插件配置信息不正确',
'Addon already exists' => '上传的插件已经存在',
'Unable to open the zip file' => '无法打开ZIP文件',
'Unable to extract the file' => '无法解压ZIP文件',
];

View File

@ -0,0 +1,8 @@
<?php
return [
'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'Uploaded file format is limited' => '上传文件格式受限制',
'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Upload successful' => '上传成功',
];

View File

@ -2,5 +2,6 @@
return [ return [
'Group' => '所属组别', 'Group' => '所属组别',
'Loginfailure' => '登录失败次数',
'Login time' => '最后登录', 'Login time' => '最后登录',
]; ];

View File

@ -6,8 +6,15 @@ return [
'Remark' => '备注', 'Remark' => '备注',
'Icon' => '图标', 'Icon' => '图标',
'Alert' => '警告', 'Alert' => '警告',
'Name' => '规则URL', 'Name' => '规则',
'Controller/Action' => '控制器名/方法名',
'Ismenu' => '菜单', 'Ismenu' => '菜单',
'Search icon' => '搜索图标',
'Toggle menu visible' => '点击切换菜单显示',
'Toggle sub menu' => '点击切换子菜单',
'Menu tips' => '父级菜单无需匹配控制器和方法,子级菜单请使用控制器名',
'Node tips' => '控制器/方法名,如果有目录请使用 目录名/控制器名/方法名',
'The non-menu rule must have parent' => '非菜单规则节点必须有父级', 'The non-menu rule must have parent' => '非菜单规则节点必须有父级',
'If not necessary, use the command line to build rule' => '非必要情况下请直接使用命令行php think menu来生成', 'Can not change the parent to child' => '父组别不能是它的子组别',
'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
]; ];

View File

@ -3,7 +3,8 @@
return [ return [
'Id' => 'ID', 'Id' => 'ID',
'Pid' => '父ID', 'Pid' => '父ID',
'Type' => '栏目类型', 'Type' => '类型',
'All' => '全部',
'Image' => '图片', 'Image' => '图片',
'Keywords' => '关键字', 'Keywords' => '关键字',
'Description' => '描述', 'Description' => '描述',
@ -11,5 +12,7 @@ return [
'Createtime' => '创建时间', 'Createtime' => '创建时间',
'Updatetime' => '更新时间', 'Updatetime' => '更新时间',
'Weigh' => '权重', 'Weigh' => '权重',
'Category warmtips' => '温馨提示:栏目类型请前往<b>常规管理</b>-><b>系统配置</b>-><b>字典配置</b>中进行管理',
'Can not change the parent to child' => '父组别不能是它的子组别',
'Status' => '状态' 'Status' => '状态'
]; ];

View File

@ -0,0 +1,48 @@
<?php
return [
'Custom' => '自定义',
'Pid' => '父ID',
'Type' => '栏目类型',
'Image' => '图片',
'Total user' => '总会员数',
'Total view' => '总访问数',
'Total order' => '总订单数',
'Total order amount' => '总金额',
'Today user signup' => '今日注册',
'Today user login' => '今日登录',
'Today order' => '今日订单',
'Unsettle order' => '未处理订单',
'Seven dnu' => '七日新增',
'Seven dau' => '七日活跃',
'Custom zone' => '这里是你的自定义数据',
'Sales' => '成交数',
'Orders' => '订单数',
'Real time' => '实时',
'Category count' => '分类统计',
'Category count tips' => '当前分类总记录数',
'Attachment count' => '附件统计',
'Attachment count tips' => '当前上传的附件数量',
'Article count' => '文章统计',
'News count' => '新闻统计',
'Comment count' => '评论次数',
'Like count' => '点赞次数',
'Recent news' => '最新新闻',
'Recent discussion' => '最新发贴',
'Server info' => '服务器信息',
'PHP version' => 'PHP版本',
'Fastadmin version' => '主框架版本',
'Fastadmin addon version' => '插件版本',
'Thinkphp version' => 'ThinkPHP版本',
'Sapi name' => '运行方式',
'Debug mode' => '调试模式',
'Software' => '环境信息',
'Upload mode' => '上传模式',
'Upload url' => '上传URL',
'Upload cdn url' => '上传CDN',
'Cdn url' => '静态资源CDN',
'Timezone' => '时区',
'Language' => '语言',
'View more' => '查看更多',
'Security tips' => '<i class="fa fa-warning"></i> 安全提示:你正在使用默认的后台登录入口,为了你的网站安全,建议你修改后台登录入口,<a href="https://forum.fastadmin.net/thread/7640" target="_blank">点击查看修改方法</a>',
];

View File

@ -1,9 +1,12 @@
<?php <?php
return [ return [
'Id' => 'ID',
'Admin_id' => '管理员ID',
'User_id' => '会员ID',
'Url' => '物理路径', 'Url' => '物理路径',
'Imagewidth' => '宽度', 'Imagewidth' => '宽度',
'Imageheight' => '宽度', 'Imageheight' => '高度',
'Imagetype' => '图片类型', 'Imagetype' => '图片类型',
'Imageframes' => '图片帧数', 'Imageframes' => '图片帧数',
'Preview' => '预览', 'Preview' => '预览',
@ -13,5 +16,7 @@ return [
'Createtime' => '创建日期', 'Createtime' => '创建日期',
'Uploadtime' => '上传时间', 'Uploadtime' => '上传时间',
'Storage' => '存储引擎', 'Storage' => '存储引擎',
'Upload by summernote' => '从编辑器上传' 'Upload to third' => '上传到第三方',
'Upload to local' => '上传到本地',
'Upload from editor' => '从编辑器上传'
]; ];

View File

@ -10,11 +10,13 @@ return [
'Basic' => '基础配置', 'Basic' => '基础配置',
'Email' => '邮件配置', 'Email' => '邮件配置',
'Attachment' => '附件配置', 'Attachment' => '附件配置',
'Dictionary' => '字典配置',
'User' => '会员配置', 'User' => '会员配置',
'Example' => '示例分组', 'Example' => '示例分组',
'Extend' => '扩展属性', 'Extend' => '扩展属性',
'String' => '字符', 'String' => '字符',
'Text' => '文本', 'Text' => '文本',
'Editor' => '编辑器',
'Number' => '数字', 'Number' => '数字',
'Date' => '日期', 'Date' => '日期',
'Time' => '时间', 'Time' => '时间',
@ -25,6 +27,7 @@ return [
'Files' => '文件(多)', 'Files' => '文件(多)',
'Select' => '列表', 'Select' => '列表',
'Selects' => '列表(多选)', 'Selects' => '列表(多选)',
'Switch' => '开关',
'Checkbox' => '复选', 'Checkbox' => '复选',
'Radio' => '单选', 'Radio' => '单选',
'Array' => '数组', 'Array' => '数组',
@ -32,8 +35,26 @@ return [
'Array value' => '键值', 'Array value' => '键值',
'Content' => '数据列表', 'Content' => '数据列表',
'Rule' => '校验规则', 'Rule' => '校验规则',
'Site name' => '站点名称',
'Beian' => '备案号',
'Cdn url' => 'CDN地址',
'Version' => '版本号',
'Timezone' => '时区',
'Forbidden ip' => '禁止IP',
'Languages' => '语言',
'Fixed page' => '后台固定页',
'Category type' => '分类类型',
'Config group' => '配置分组',
'Mail type' => '邮件发送方式',
'Mail smtp host' => 'SMTP服务器',
'Mail smtp port' => 'SMTP端口',
'Mail smtp user' => 'SMTP用户名',
'Mail smtp password' => 'SMTP密码',
'Mail vertify type' => 'SMTP验证方式',
'Mail from' => '发件人邮箱',
'Name already exist' => '变量名称已经存在', 'Name already exist' => '变量名称已经存在',
'Send a test message' => '发送测试邮件', 'Send a test message' => '发送测试邮件',
'This is a test mail content' => '这是一封测试邮件,用于测试邮件配置是否正常!', 'This is a test mail content' => '这是一封来自FastAdmin校验邮件,用于校验邮件配置是否正常!',
'This is a test mail' => '这是一封测试邮件', 'This is a test mail' => '这是一封来自FastAdmin的邮件',
'Please input your email' => '请输入测试接收者邮箱',
]; ];

View File

@ -1,16 +0,0 @@
<?php
return [
'Title' => '任务标题',
'Maximums' => '最多执行',
'Sleep' => '延迟秒数',
'Schedule' => '执行周期',
'Executes' => '执行次数',
'Execute time' => '最后执行时间',
'Request Url' => '请求URL',
'Execute Sql Script' => '执行SQL',
'Execute Shell' => '执行Shell',
'Crontab format invalid' => 'Crontab格式错误',
'Next execute time' => '下次预计时间',
'The next %s times the execution time' => '接下来 %s 次的执行时间',
];

View File

@ -1,30 +0,0 @@
<?php
return [
'SQL Result' => '查询结果',
'Basic query' => '基础查询',
'View structure' => '查看表结构',
'View data' => '查看表数据',
'Optimize' => '优化表',
'Repair' => '修复表',
'Optimize all' => '优化全部表',
'Repair all' => '修复全部表',
'Table:%s' => '总计:%s个表',
'Record:%s' => '记录:%s条',
'Data:%s' => '占用:%s',
'Index:%s' => '索引:%s',
'SQL Result:' => '查询结果:',
'SQL can not be empty' => 'SQL语句不能为空',
'Max output:%s' => '最大返回%s条',
'Total:%s' => '共有%s条记录! ',
'Row:%s' => '记录:%s',
'Executes one or multiple queries which are concatenated by a semicolon' => '请输入SQL语句支持批量查询多条SQL以分号(;)分格',
'Query affected %s rows and took %s seconds' => '共影响%s条记录! 耗时:%s秒!',
'Query returned an empty result' => '返回结果为空!',
'Query took %s seconds' => '耗时%s秒!',
'Optimize table %s done' => '优化表[%s]成功',
'Repair table %s done' => '修复表[%s]成功',
'Optimize table %s fail' => '优化表[%s]失败',
'Repair table %s fail' => '修复表[%s]失败'
];

View File

@ -4,6 +4,7 @@ return [
'Url' => '链接', 'Url' => '链接',
'Userame' => '用户名', 'Userame' => '用户名',
'Createtime' => '操作时间', 'Createtime' => '操作时间',
'Click to edit' => '点击编辑',
'Admin log' => '操作日志', 'Admin log' => '操作日志',
'Leave password blank if dont want to change' => '不修改密码请留空', 'Leave password blank if dont want to change' => '不修改密码请留空',
]; ];

View File

@ -26,9 +26,32 @@ return [
'Username or password is incorrect' => '用户名或密码不正确', 'Username or password is incorrect' => '用户名或密码不正确',
'Username is incorrect' => '用户名不正确', 'Username is incorrect' => '用户名不正确',
'Password is incorrect' => '密码不正确', 'Password is incorrect' => '密码不正确',
'Admin is forbidden' => '管理员已经被禁止登录',
'Please try again after 1 day' => '请于1天后再尝试登录',
'Login successful' => '登录成功!', 'Login successful' => '登录成功!',
'Logout successful' => '退出成功!', 'Logout successful' => '退出成功!',
'Verification code is incorrect' => '验证码不正确', 'Verification code is incorrect' => '验证码不正确',
'Wipe cache completed' => '清除缓存成功', 'Wipe cache completed' => '清除缓存成功',
'Wipe cache failed' => '清除缓存失败', 'Wipe cache failed' => '清除缓存失败',
'Wipe cache' => '清空缓存',
'Wipe all cache' => '一键清除缓存',
'Wipe content cache' => '清空内容缓存',
'Wipe template cache' => '清除模板缓存',
'Wipe addons cache' => '清除插件缓存',
'Check for updates' => '检测更新',
'Discover new version' => '发现新版本',
'Go to download' => '去下载更新',
'Currently is the latest version' => '当前已经是最新版本',
'Ignore this version' => '忽略此次更新',
'Do not remind again' => '不再提示',
'Your current version' => '你的版本是',
'New version' => '新版本',
'Release notes' => '更新说明',
'Latest news' => '最新消息',
'View more' => '查看更多',
'Links' => '相关链接',
'Docs' => '官方文档',
'Forum' => '交流社区',
'QQ qun' => 'QQ交流群',
'Captcha' => '验证码',
]; ];

View File

@ -1,17 +0,0 @@
<?php
return [
'id' => 'ID',
'category_id' => '分类ID',
'category' => '分类',
'title' => '标题',
'keywords' => '关键字',
'flag' => '标志',
'image' => '头像',
'content' => '内容',
'icon' => '图标',
'views' => '点击',
'comments' => '评论',
'weigh' => '权重',
'status' => '状态'
];

View File

@ -0,0 +1,9 @@
<?php
return [
'Name' => '组名',
'Rules' => '权限节点',
'Createtime' => '添加时间',
'Updatetime' => '更新时间',
'Status' => '状态'
];

View File

@ -0,0 +1,15 @@
<?php
return [
'Pid' => '父ID',
'Name' => '规则',
'Title' => '标题',
'Remark' => '备注',
'Ismenu' => '是否菜单',
'Createtime' => '创建时间',
'Updatetime' => '更新时间',
'Menu tips' => '规则任意,请不可重复,仅做层级显示,无需匹配控制器和方法',
'Node tips' => '模块/控制器/方法名',
'Weigh' => '权重',
'Status' => '状态'
];

View File

@ -0,0 +1,33 @@
<?php
return [
'Id' => 'ID',
'Group_id' => '组别ID',
'Username' => '用户名',
'Nickname' => '昵称',
'Password' => '密码',
'Salt' => '密码盐',
'Email' => '电子邮箱',
'Mobile' => '手机号',
'Avatar' => '头像',
'Level' => '等级',
'Gender' => '性别',
'Male' => '男',
'FeMale' => '女',
'Birthday' => '生日',
'Bio' => '格言',
'Score' => '积分',
'Successions' => '连续登录天数',
'Maxsuccessions' => '最大连续登录天数',
'Prevtime' => '上次登录时间',
'Logintime' => '登录时间',
'Loginip' => '登录IP',
'Loginfailure' => '失败次数',
'Joinip' => '加入IP',
'Jointime' => '加入时间',
'Createtime' => '创建时间',
'Updatetime' => '更新时间',
'Token' => 'Token',
'Status' => '状态',
'Leave password blank if dont want to change' => '不修改密码请留空',
];

View File

@ -1,15 +0,0 @@
<?php
return [
'id' => 'ID',
'oldversion' => '旧版本号',
'newversion' => '新版本号',
'packagesize' => '包大小',
'content' => '升级内容',
'downloadurl' => '下载地址',
'enforce' => '强制更新',
'createtime' => '创建时间',
'updatetime' => '更新时间',
'weigh' => '权重',
'status' => '状态'
];

View File

@ -1,8 +0,0 @@
<?php
return [
'Text' => '文本',
'Event key' => '响应标识',
'Remark' => '备注',
'Text already exists' => '文本已经存在',
];

View File

@ -1,10 +0,0 @@
<?php
return [
'name' => '配置名称',
'value' => '配置值',
'Json key' => '键',
'Json value' => '值',
'createtime' => '创建时间',
'updatetime' => '更新时间'
];

View File

@ -1,8 +0,0 @@
<?php
return [
'Resource title' => '资源标题',
'Event key' => '事件标识',
'Text' => '文本',
'App' => '应用',
];

View File

@ -5,15 +5,17 @@ namespace app\admin\library;
use app\admin\model\Admin; use app\admin\model\Admin;
use fast\Random; use fast\Random;
use fast\Tree; use fast\Tree;
use think\Config;
use think\Cookie; use think\Cookie;
use think\Request; use think\Request;
use think\Session; use think\Session;
class Auth extends \fast\Auth class Auth extends \fast\Auth
{ {
protected $_error = '';
protected $requestUri = ''; protected $requestUri = '';
protected $breadcrumb = []; protected $breadcrumb = [];
protected $logined = false; //登录状态
public function __construct() public function __construct()
{ {
@ -25,24 +27,40 @@ class Auth extends \fast\Auth
return Session::get('admin.' . $name); return Session::get('admin.' . $name);
} }
/**
* 管理员登录
*
* @param string $username 用户名
* @param string $password 密码
* @param int $keeptime 有效时长
* @return boolean
*/
public function login($username, $password, $keeptime = 0) public function login($username, $password, $keeptime = 0)
{ {
$admin = Admin::get(['username' => $username]); $admin = Admin::get(['username' => $username]);
if (!$admin) if (!$admin) {
{ $this->setError('Username is incorrect');
return false; return false;
} }
if ($admin->password != md5(md5($password) . $admin->salt)) if ($admin['status'] == 'hidden') {
{ $this->setError('Admin is forbidden');
return false;
}
if (Config::get('fastadmin.login_failure_retry') && $admin->loginfailure >= 10 && time() - $admin->updatetime < 86400) {
$this->setError('Please try again after 1 day');
return false;
}
if ($admin->password != md5(md5($password) . $admin->salt)) {
$admin->loginfailure++; $admin->loginfailure++;
$admin->save(); $admin->save();
$this->setError('Password is incorrect');
return false; return false;
} }
$admin->loginfailure = 0; $admin->loginfailure = 0;
$admin->logintime = time(); $admin->logintime = time();
$admin->token = Random::uuid(); $admin->token = Random::uuid();
$admin->save(); $admin->save();
Session::set("admin", $admin); Session::set("admin", $admin->toArray());
$this->keeplogin($keeptime); $this->keeplogin($keeptime);
return true; return true;
} }
@ -53,12 +71,11 @@ class Auth extends \fast\Auth
public function logout() public function logout()
{ {
$admin = Admin::get(intval($this->id)); $admin = Admin::get(intval($this->id));
if (!$admin) if (!$admin) {
{
return true;
}
$admin->token = ''; $admin->token = '';
$admin->save(); $admin->save();
}
$this->logined = false; //重置登录状态
Session::delete("admin"); Session::delete("admin");
Cookie::delete("keeplogin"); Cookie::delete("keeplogin");
return true; return true;
@ -71,47 +88,41 @@ class Auth extends \fast\Auth
public function autologin() public function autologin()
{ {
$keeplogin = Cookie::get('keeplogin'); $keeplogin = Cookie::get('keeplogin');
if (!$keeplogin) if (!$keeplogin) {
{
return false; return false;
} }
list($id, $keeptime, $expiretime, $key) = explode('|', $keeplogin); list($id, $keeptime, $expiretime, $key) = explode('|', $keeplogin);
if ($id && $keeptime && $expiretime && $key && $expiretime > time()) if ($id && $keeptime && $expiretime && $key && $expiretime > time()) {
{
$admin = Admin::get($id); $admin = Admin::get($id);
if (!$admin || !$admin->token) if (!$admin || !$admin->token) {
{
return false; return false;
} }
//token有变更 //token有变更
if ($key != md5(md5($id) . md5($keeptime) . md5($expiretime) . $admin->token)) if ($key != md5(md5($id) . md5($keeptime) . md5($expiretime) . $admin->token)) {
{
return false; return false;
} }
Session::set("admin", $admin); Session::set("admin", $admin->toArray());
//刷新自动登录的时效 //刷新自动登录的时效
$this->keeplogin($keeptime); $this->keeplogin($keeptime);
return true; return true;
} } else {
else
{
return false; return false;
} }
} }
/** /**
* 刷新保持登录的Cookie * 刷新保持登录的Cookie
*
* @param int $keeptime * @param int $keeptime
* @return boolean * @return boolean
*/ */
protected function keeplogin($keeptime = 0) protected function keeplogin($keeptime = 0)
{ {
if ($keeptime) if ($keeptime) {
{
$expiretime = time() + $keeptime; $expiretime = time() + $keeptime;
$key = md5(md5($this->id) . md5($keeptime) . md5($expiretime) . $this->token); $key = md5(md5($this->id) . md5($keeptime) . md5($expiretime) . $this->token);
$data = [$this->id, $keeptime, $expiretime, $key]; $data = [$this->id, $keeptime, $expiretime, $key];
Cookie::set('keeplogin', implode('|', $data)); Cookie::set('keeplogin', implode('|', $data), 86400 * 30);
return true; return true;
} }
return false; return false;
@ -126,24 +137,24 @@ class Auth extends \fast\Auth
* 检测当前控制器和方法是否匹配传递的数组 * 检测当前控制器和方法是否匹配传递的数组
* *
* @param array $arr 需要验证权限的数组 * @param array $arr 需要验证权限的数组
* @return bool
*/ */
public function match($arr = []) public function match($arr = [])
{ {
$request = Request::instance(); $request = Request::instance();
$arr = is_array($arr) ? $arr : explode(',', $arr); $arr = is_array($arr) ? $arr : explode(',', $arr);
if (!$arr) if (!$arr) {
{ return false;
return FALSE;
} }
$arr = array_map('strtolower', $arr);
// 是否存在 // 是否存在
if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr)) if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr)) {
{ return true;
return TRUE;
} }
// 没找到匹配 // 没找到匹配
return FALSE; return false;
} }
/** /**
@ -153,7 +164,22 @@ class Auth extends \fast\Auth
*/ */
public function isLogin() public function isLogin()
{ {
return Session::get('admin') ? true : false; if ($this->logined) {
return true;
}
$admin = Session::get('admin');
if (!$admin) {
return false;
}
//判断是否同一时间同一账号只能在一个地方登录
if (Config::get('fastadmin.login_unique')) {
$my = Admin::get($admin['id']);
if (!$my || $my['token'] != $admin['token']) {
return false;
}
}
$this->logined = true;
return true;
} }
/** /**
@ -201,7 +227,90 @@ class Auth extends \fast\Auth
public function isSuperAdmin() public function isSuperAdmin()
{ {
return in_array('*', $this->getRuleIds()) ? TRUE : FALSE; return in_array('*', $this->getRuleIds()) ? true : false;
}
/**
* 获取管理员所属于的分组ID
* @param int $uid
* @return array
*/
public function getGroupIds($uid = null)
{
$groups = $this->getGroups($uid);
$groupIds = [];
foreach ($groups as $K => $v) {
$groupIds[] = (int)$v['group_id'];
}
return $groupIds;
}
/**
* 取出当前管理员所拥有权限的分组
* @param boolean $withself 是否包含当前所在的分组
* @return array
*/
public function getChildrenGroupIds($withself = false)
{
//取出当前管理员所有的分组
$groups = $this->getGroups();
$groupIds = [];
foreach ($groups as $k => $v) {
$groupIds[] = $v['id'];
}
// 取出所有分组
$groupList = \app\admin\model\AuthGroup::where(['status' => 'normal'])->select();
$objList = [];
foreach ($groups as $K => $v) {
if ($v['rules'] === '*') {
$objList = $groupList;
break;
}
// 取出包含自己的所有子节点
$childrenList = Tree::instance()->init($groupList)->getChildren($v['id'], true);
$obj = Tree::instance()->init($childrenList)->getTreeArray($v['pid']);
$objList = array_merge($objList, Tree::instance()->getTreeList($obj));
}
$childrenGroupIds = [];
foreach ($objList as $k => $v) {
$childrenGroupIds[] = $v['id'];
}
if (!$withself) {
$childrenGroupIds = array_diff($childrenGroupIds, $groupIds);
}
return $childrenGroupIds;
}
/**
* 取出当前管理员所拥有权限的管理员
* @param boolean $withself 是否包含自身
* @return array
*/
public function getChildrenAdminIds($withself = false)
{
$childrenAdminIds = [];
if (!$this->isSuperAdmin()) {
$groupIds = $this->getChildrenGroupIds(false);
$authGroupList = \app\admin\model\AuthGroupAccess::
field('uid,group_id')
->where('group_id', 'in', $groupIds)
->select();
foreach ($authGroupList as $k => $v) {
$childrenAdminIds[] = $v['uid'];
}
} else {
//超级管理员拥有所有人的权限
$childrenAdminIds = Admin::column('id');
}
if ($withself) {
if (!in_array($this->id, $childrenAdminIds)) {
$childrenAdminIds[] = $this->id;
}
} else {
$childrenAdminIds = array_diff($childrenAdminIds, [$this->id]);
}
return $childrenAdminIds;
} }
/** /**
@ -211,29 +320,29 @@ class Auth extends \fast\Auth
*/ */
public function getBreadCrumb($path = '') public function getBreadCrumb($path = '')
{ {
if ($this->breadcrumb || !$path) if ($this->breadcrumb || !$path) {
return $this->breadcrumb; return $this->breadcrumb;
}
$path_rule_id = 0; $path_rule_id = 0;
foreach ($this->rules as $rule) foreach ($this->rules as $rule) {
{
$path_rule_id = $rule['name'] == $path ? $rule['id'] : $path_rule_id; $path_rule_id = $rule['name'] == $path ? $rule['id'] : $path_rule_id;
} }
if ($path_rule_id) if ($path_rule_id) {
{
$this->breadcrumb = Tree::instance()->init($this->rules)->getParents($path_rule_id, true); $this->breadcrumb = Tree::instance()->init($this->rules)->getParents($path_rule_id, true);
foreach ($this->breadcrumb as $k => &$v) foreach ($this->breadcrumb as $k => &$v) {
{
$v['url'] = url($v['name']); $v['url'] = url($v['name']);
$v['title'] = __($v['title']);
} }
} }
return $this->breadcrumb; return $this->breadcrumb;
} }
/** /**
* 获取左侧菜单栏 * 获取左侧和顶部菜单栏
* *
* @param array $params URL对应的badge数据 * @param array $params URL对应的badge数据
* @return string * @param string $fixedPage 默认页
* @return array
*/ */
public function getSidebar($params = [], $fixedPage = 'dashboard') public function getSidebar($params = [], $fixedPage = 'dashboard')
{ {
@ -242,59 +351,131 @@ class Auth extends \fast\Auth
$badgeList = []; $badgeList = [];
$module = request()->module(); $module = request()->module();
// 生成菜单的badge // 生成菜单的badge
foreach ($params as $k => $v) foreach ($params as $k => $v) {
{ $url = $k;
if (stripos($k, '/') === false) if (is_array($v)) {
{
$url = '/' . $module . '/' . $k;
}
else
{
$url = url($k);
}
if (is_array($v))
{
$nums = isset($v[0]) ? $v[0] : 0; $nums = isset($v[0]) ? $v[0] : 0;
$color = isset($v[1]) ? $v[1] : $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums]; $color = isset($v[1]) ? $v[1] : $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums];
$class = isset($v[2]) ? $v[2] : 'label'; $class = isset($v[2]) ? $v[2] : 'label';
} } else {
else
{
$nums = $v; $nums = $v;
$color = $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums]; $color = $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums];
$class = 'label'; $class = 'label';
} }
//必须nums大于0才显示 //必须nums大于0才显示
if ($nums) if ($nums) {
{
$badgeList[$url] = '<small class="' . $class . ' pull-right bg-' . $color . '">' . $nums . '</small>'; $badgeList[$url] = '<small class="' . $class . ' pull-right bg-' . $color . '">' . $nums . '</small>';
} }
} }
// 读取管理员当前拥有的权限节点 // 读取管理员当前拥有的权限节点
$userRule = $this->getRuleList(); $userRule = $this->getRuleList();
$select_id = 0; $selected = $referer = [];
$activeUrl = '/' . $module . '/' . $fixedPage; $refererUrl = Session::get('referer');
$pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
// 必须将结果集转换为数组 // 必须将结果集转换为数组
$ruleList = collection(model('AuthRule')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray(); $ruleList = collection(\app\admin\model\AuthRule::where('status', 'normal')
foreach ($ruleList as $k => &$v) ->where('ismenu', 1)
{ ->order('weigh', 'desc')
if (!in_array($v['name'], $userRule)) ->cache("__menu__")
{ ->select())->toArray();
$indexRuleList = \app\admin\model\AuthRule::where('status', 'normal')
->where('ismenu', 0)
->where('name', 'like', '%/index')
->column('name,pid');
$pidArr = array_filter(array_unique(array_map(function ($item) {
return $item['pid'];
}, $ruleList)));
foreach ($ruleList as $k => &$v) {
if (!in_array($v['name'], $userRule)) {
unset($ruleList[$k]); unset($ruleList[$k]);
continue; continue;
} }
$select_id = $v['name'] == $activeUrl ? $v['id'] : $select_id; $indexRuleName = $v['name'] . '/index';
$v['url'] = $v['name']; if (isset($indexRuleList[$indexRuleName]) && !in_array($indexRuleName, $userRule)) {
$v['badge'] = isset($badgeList[$v['name']]) ? $badgeList[$v['name']] : ''; unset($ruleList[$k]);
$v['py'] = \fast\Pinyin::get($v['title'], true); continue;
$v['pinyin'] = \fast\Pinyin::get($v['title']);
} }
$v['icon'] = $v['icon'] . ' fa-fw';
$v['url'] = '/' . $module . '/' . $v['name'];
$v['badge'] = isset($badgeList[$v['name']]) ? $badgeList[$v['name']] : '';
$v['py'] = $pinyin->abbr($v['title'], '');
$v['pinyin'] = $pinyin->permalink($v['title'], '');
$v['title'] = __($v['title']);
$selected = $v['name'] == $fixedPage ? $v : $selected;
$referer = url($v['url']) == $refererUrl ? $v : $referer;
}
$lastArr = array_diff($pidArr, array_filter(array_unique(array_map(function ($item) {
return $item['pid'];
}, $ruleList))));
foreach ($ruleList as $index => $item) {
if (in_array($item['id'], $lastArr)) {
unset($ruleList[$index]);
}
}
if ($selected == $referer) {
$referer = [];
}
$selected && $selected['url'] = url($selected['url']);
$referer && $referer['url'] = url($referer['url']);
$select_id = $selected ? $selected['id'] : 0;
$menu = $nav = '';
if (Config::get('fastadmin.multiplenav')) {
$topList = [];
foreach ($ruleList as $index => $item) {
if (!$item['pid']) {
$topList[] = $item;
}
}
$selectParentIds = [];
$tree = Tree::instance();
$tree->init($ruleList);
if ($select_id) {
$selectParentIds = $tree->getParentsIds($select_id, true);
}
foreach ($topList as $index => $item) {
$childList = Tree::instance()->getTreeMenu($item['id'], '<li class="@class" pid="@pid"><a href="@url@addtabs" addtabs="@id" url="@url" py="@py" pinyin="@pinyin"><i class="@icon"></i> <span>@title</span> <span class="pull-right-container">@caret @badge</span></a> @childlist</li>', $select_id, '', 'ul', 'class="treeview-menu"');
$current = in_array($item['id'], $selectParentIds);
$url = $childList ? 'javascript:;' : url($item['url']);
$addtabs = $childList || !$url ? "" : (stripos($url, "?") !== false ? "&" : "?") . "ref=addtabs";
$childList = str_replace('" pid="' . $item['id'] . '"', ' treeview ' . ($current ? '' : 'hidden') . '" pid="' . $item['id'] . '"', $childList);
$nav .= '<li class="' . ($current ? 'active' : '') . '"><a href="' . $url . $addtabs . '" addtabs="' . $item['id'] . '" url="' . $url . '"><i class="' . $item['icon'] . '"></i> <span>' . $item['title'] . '</span> <span class="pull-right-container"> </span></a> </li>';
$menu .= $childList;
}
} else {
// 构造菜单数据 // 构造菜单数据
Tree::instance()->init($ruleList); Tree::instance()->init($ruleList);
$menu = Tree::instance()->getTreeMenu(0, '<li class="@class"><a href="@url" addtabs="@id" url="@url" py="@py" pinyin="@pinyin"><i class="@icon"></i> <span>@title</span> <span class="pull-right-container">@caret @badge</span></a> @childlist</li>', $select_id, '', 'ul', 'class="treeview-menu"'); $menu = Tree::instance()->getTreeMenu(0, '<li class="@class"><a href="@url@addtabs" addtabs="@id" url="@url" py="@py" pinyin="@pinyin"><i class="@icon"></i> <span>@title</span> <span class="pull-right-container">@caret @badge</span></a> @childlist</li>', $select_id, '', 'ul', 'class="treeview-menu"');
return $menu; if ($selected) {
$nav .= '<li role="presentation" id="tab_' . $selected['id'] . '" class="' . ($referer ? '' : 'active') . '"><a href="#con_' . $selected['id'] . '" node-id="' . $selected['id'] . '" aria-controls="' . $selected['id'] . '" role="tab" data-toggle="tab"><i class="' . $selected['icon'] . ' fa-fw"></i> <span>' . $selected['title'] . '</span> </a></li>';
}
if ($referer) {
$nav .= '<li role="presentation" id="tab_' . $referer['id'] . '" class="active"><a href="#con_' . $referer['id'] . '" node-id="' . $referer['id'] . '" aria-controls="' . $referer['id'] . '" role="tab" data-toggle="tab"><i class="' . $referer['icon'] . ' fa-fw"></i> <span>' . $referer['title'] . '</span> </a> <i class="close-tab fa fa-remove"></i></li>';
}
} }
return [$menu, $nav, $selected, $referer];
}
/**
* 设置错误信息
*
* @param string $error 错误信息
* @return Auth
*/
public function setError($error)
{
$this->_error = $error;
return $this;
}
/**
* 获取错误信息
* @return string
*/
public function getError()
{
return $this->_error ? __($this->_error) : '';
}
} }

View File

@ -2,9 +2,41 @@
namespace app\admin\library\traits; namespace app\admin\library\traits;
use app\admin\library\Auth;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Reader\Csv;
use think\Db;
use think\Exception;
use think\exception\PDOException;
use think\exception\ValidateException;
trait Backend trait Backend
{ {
/**
* 排除前台提交过来的字段
* @param $params
* @return array
*/
protected function preExcludeFields($params)
{
if (is_array($this->excludeFields)) {
foreach ($this->excludeFields as $field) {
if (key_exists($field, $params)) {
unset($params[$field]);
}
}
} else {
if (key_exists($this->excludeFields, $params)) {
unset($params[$this->excludeFields]);
}
}
return $params;
}
/** /**
* 查看 * 查看
*/ */
@ -12,11 +44,9 @@ trait Backend
{ {
//设置过滤方法 //设置过滤方法
$this->request->filter(['strip_tags']); $this->request->filter(['strip_tags']);
if ($this->request->isAjax()) if ($this->request->isAjax()) {
{
//如果发送的来源是Selectpage则转发到Selectpage //如果发送的来源是Selectpage则转发到Selectpage
if ($this->request->request('pkey_name')) if ($this->request->request('keyField')) {
{
return $this->selectpage(); return $this->selectpage();
} }
list($where, $sort, $order, $offset, $limit) = $this->buildparams(); list($where, $sort, $order, $offset, $limit) = $this->buildparams();
@ -31,6 +61,36 @@ trait Backend
->limit($offset, $limit) ->limit($offset, $limit)
->select(); ->select();
$list = collection($list)->toArray();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 回收站
*/
public function recyclebin()
{
//设置过滤方法
$this->request->filter(['strip_tags']);
if ($this->request->isAjax()) {
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->onlyTrashed()
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->onlyTrashed()
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list); $result = array("total" => $total, "rows" => $list);
return json($result); return json($result);
@ -43,37 +103,39 @@ trait Backend
*/ */
public function add() public function add()
{ {
if ($this->request->isPost()) if ($this->request->isPost()) {
{
$params = $this->request->post("row/a"); $params = $this->request->post("row/a");
if ($params) if ($params) {
{ $params = $this->preExcludeFields($params);
foreach ($params as $k => &$v)
{ if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
$v = is_array($v) ? implode(',', $v) : $v; $params[$this->dataLimitField] = $this->auth->id;
} }
try $result = false;
{ Db::startTrans();
try {
//是否采用模型验证 //是否采用模型验证
if ($this->modelValidate) if ($this->modelValidate) {
{ $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
$name = basename(str_replace('\\', '/', get_class($this->model))); $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : true) : $this->modelValidate; $this->model->validateFailException(true)->validate($validate);
$this->model->validate($validate);
} }
$result = $this->model->save($params); $result = $this->model->allowField(true)->save($params);
if ($result !== false) Db::commit();
{ } catch (ValidateException $e) {
$this->success(); Db::rollback();
}
else
{
$this->error($this->model->getError());
}
}
catch (\think\exception\PDOException $e)
{
$this->error($e->getMessage()); $this->error($e->getMessage());
} catch (PDOException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($result !== false) {
$this->success();
} else {
$this->error(__('No rows were inserted'));
} }
} }
$this->error(__('Parameter %s can not be empty', '')); $this->error(__('Parameter %s can not be empty', ''));
@ -84,42 +146,47 @@ trait Backend
/** /**
* 编辑 * 编辑
*/ */
public function edit($ids = NULL) public function edit($ids = null)
{ {
$row = $this->model->get($ids); $row = $this->model->get($ids);
if (!$row) if (!$row) {
$this->error(__('No Results were found')); $this->error(__('No Results were found'));
if ($this->request->isPost()) }
{ $adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
if (!in_array($row[$this->dataLimitField], $adminIds)) {
$this->error(__('You have no permission'));
}
}
if ($this->request->isPost()) {
$params = $this->request->post("row/a"); $params = $this->request->post("row/a");
if ($params) if ($params) {
{ $params = $this->preExcludeFields($params);
foreach ($params as $k => &$v) $result = false;
{ Db::startTrans();
$v = is_array($v) ? implode(',', $v) : $v; try {
}
try
{
//是否采用模型验证 //是否采用模型验证
if ($this->modelValidate) if ($this->modelValidate) {
{ $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
$name = basename(str_replace('\\', '/', get_class($this->model))); $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : true) : $this->modelValidate; $row->validateFailException(true)->validate($validate);
$row->validate($validate);
} }
$result = $row->save($params); $result = $row->allowField(true)->save($params);
if ($result !== false) Db::commit();
{ } catch (ValidateException $e) {
$this->success(); Db::rollback();
}
else
{
$this->error($row->getError());
}
}
catch (think\exception\PDOException $e)
{
$this->error($e->getMessage()); $this->error($e->getMessage());
} catch (PDOException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($result !== false) {
$this->success();
} else {
$this->error(__('No rows were updated'));
} }
} }
$this->error(__('Parameter %s can not be empty', '')); $this->error(__('Parameter %s can not be empty', ''));
@ -133,39 +200,143 @@ trait Backend
*/ */
public function del($ids = "") public function del($ids = "")
{ {
if ($ids) if ($ids) {
{ $pk = $this->model->getPk();
$count = $this->model->destroy($ids); $adminIds = $this->getDataLimitAdminIds();
if ($count) if (is_array($adminIds)) {
{ $this->model->where($this->dataLimitField, 'in', $adminIds);
}
$list = $this->model->where($pk, 'in', $ids)->select();
$count = 0;
Db::startTrans();
try {
foreach ($list as $k => $v) {
$count += $v->delete();
}
Db::commit();
} catch (PDOException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($count) {
$this->success(); $this->success();
} else {
$this->error(__('No rows were deleted'));
} }
} }
$this->error(__('Parameter %s can not be empty', 'ids')); $this->error(__('Parameter %s can not be empty', 'ids'));
} }
/**
* 真实删除
*/
public function destroy($ids = "")
{
$pk = $this->model->getPk();
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$this->model->where($this->dataLimitField, 'in', $adminIds);
}
if ($ids) {
$this->model->where($pk, 'in', $ids);
}
$count = 0;
Db::startTrans();
try {
$list = $this->model->onlyTrashed()->select();
foreach ($list as $k => $v) {
$count += $v->delete(true);
}
Db::commit();
} catch (PDOException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($count) {
$this->success();
} else {
$this->error(__('No rows were deleted'));
}
$this->error(__('Parameter %s can not be empty', 'ids'));
}
/**
* 还原
*/
public function restore($ids = "")
{
$pk = $this->model->getPk();
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$this->model->where($this->dataLimitField, 'in', $adminIds);
}
if ($ids) {
$this->model->where($pk, 'in', $ids);
}
$count = 0;
Db::startTrans();
try {
$list = $this->model->onlyTrashed()->select();
foreach ($list as $index => $item) {
$count += $item->restore();
}
Db::commit();
} catch (PDOException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($count) {
$this->success();
}
$this->error(__('No rows were updated'));
}
/** /**
* 批量更新 * 批量更新
*/ */
public function multi($ids = "") public function multi($ids = "")
{ {
$ids = $ids ? $ids : $this->request->param("ids"); $ids = $ids ? $ids : $this->request->param("ids");
if ($ids) if ($ids) {
{ if ($this->request->has('params')) {
if ($this->request->has('params'))
{
parse_str($this->request->post("params"), $values); parse_str($this->request->post("params"), $values);
$values = array_intersect_key($values, array_flip(is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields))); $values = array_intersect_key($values, array_flip(is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields)));
if ($values) if ($values || $this->auth->isSuperAdmin()) {
{ $adminIds = $this->getDataLimitAdminIds();
$count = $this->model->where($this->model->getPk(), 'in', $ids)->update($values); if (is_array($adminIds)) {
if ($count) $this->model->where($this->dataLimitField, 'in', $adminIds);
{ }
$count = 0;
Db::startTrans();
try {
$list = $this->model->where($this->model->getPk(), 'in', $ids)->select();
foreach ($list as $index => $item) {
$count += $item->allowField(true)->isUpdate(true)->save($values);
}
Db::commit();
} catch (PDOException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($count) {
$this->success(); $this->success();
} else {
$this->error(__('No rows were updated'));
} }
} } else {
else
{
$this->error(__('You have no permission')); $this->error(__('You have no permission'));
} }
} }
@ -173,4 +344,136 @@ trait Backend
$this->error(__('Parameter %s can not be empty', 'ids')); $this->error(__('Parameter %s can not be empty', 'ids'));
} }
/**
* 导入
*/
protected function import()
{
$file = $this->request->request('file');
if (!$file) {
$this->error(__('Parameter %s can not be empty', 'file'));
}
$filePath = ROOT_PATH . DS . 'public' . DS . $file;
if (!is_file($filePath)) {
$this->error(__('No results were found'));
}
//实例化reader
$ext = pathinfo($filePath, PATHINFO_EXTENSION);
if (!in_array($ext, ['csv', 'xls', 'xlsx'])) {
$this->error(__('Unknown data format'));
}
if ($ext === 'csv') {
$file = fopen($filePath, 'r');
$filePath = tempnam(sys_get_temp_dir(), 'import_csv');
$fp = fopen($filePath, "w");
$n = 0;
while ($line = fgets($file)) {
$line = rtrim($line, "\n\r\0");
$encoding = mb_detect_encoding($line, ['utf-8', 'gbk', 'latin1', 'big5']);
if ($encoding != 'utf-8') {
$line = mb_convert_encoding($line, 'utf-8', $encoding);
}
if ($n == 0 || preg_match('/^".*"$/', $line)) {
fwrite($fp, $line . "\n");
} else {
fwrite($fp, '"' . str_replace(['"', ','], ['""', '","'], $line) . "\"\n");
}
$n++;
}
fclose($file) || fclose($fp);
$reader = new Csv();
} elseif ($ext === 'xls') {
$reader = new Xls();
} else {
$reader = new Xlsx();
}
//导入文件首行类型,默认是注释,如果需要使用字段名称请使用name
$importHeadType = isset($this->importHeadType) ? $this->importHeadType : 'comment';
$table = $this->model->getQuery()->getTable();
$database = \think\Config::get('database.database');
$fieldArr = [];
$list = db()->query("SELECT COLUMN_NAME,COLUMN_COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?", [$table, $database]);
foreach ($list as $k => $v) {
if ($importHeadType == 'comment') {
$fieldArr[$v['COLUMN_COMMENT']] = $v['COLUMN_NAME'];
} else {
$fieldArr[$v['COLUMN_NAME']] = $v['COLUMN_NAME'];
}
}
//加载文件
$insert = [];
try {
if (!$PHPExcel = $reader->load($filePath)) {
$this->error(__('Unknown data format'));
}
$currentSheet = $PHPExcel->getSheet(0); //读取文件中的第一个工作表
$allColumn = $currentSheet->getHighestDataColumn(); //取得最大的列号
$allRow = $currentSheet->getHighestRow(); //取得一共有多少行
$maxColumnNumber = Coordinate::columnIndexFromString($allColumn);
$fields = [];
for ($currentRow = 1; $currentRow <= 1; $currentRow++) {
for ($currentColumn = 1; $currentColumn <= $maxColumnNumber; $currentColumn++) {
$val = $currentSheet->getCellByColumnAndRow($currentColumn, $currentRow)->getValue();
$fields[] = $val;
}
}
for ($currentRow = 2; $currentRow <= $allRow; $currentRow++) {
$values = [];
for ($currentColumn = 1; $currentColumn <= $maxColumnNumber; $currentColumn++) {
$val = $currentSheet->getCellByColumnAndRow($currentColumn, $currentRow)->getValue();
$values[] = is_null($val) ? '' : $val;
}
$row = [];
$temp = array_combine($fields, $values);
foreach ($temp as $k => $v) {
if (isset($fieldArr[$k]) && $k !== '') {
$row[$fieldArr[$k]] = $v;
}
}
if ($row) {
$insert[] = $row;
}
}
} catch (Exception $exception) {
$this->error($exception->getMessage());
}
if (!$insert) {
$this->error(__('No rows were updated'));
}
try {
//是否包含admin_id字段
$has_admin_id = false;
foreach ($fieldArr as $name => $key) {
if ($key == 'admin_id') {
$has_admin_id = true;
break;
}
}
if ($has_admin_id) {
$auth = Auth::instance();
foreach ($insert as &$val) {
if (!isset($val['admin_id']) || empty($val['admin_id'])) {
$val['admin_id'] = $auth->isLogin() ? $auth->id : 0;
}
}
}
$this->model->saveAll($insert);
} catch (PDOException $exception) {
$msg = $exception->getMessage();
if (preg_match("/.+Integrity constraint violation: 1062 Duplicate entry '(.+)' for key '(.+)'/is", $msg, $matches)) {
$msg = "导入失败,包含【{$matches[1]}】的记录已存在";
};
$this->error($msg);
} catch (\Exception $e) {
$this->error($e->getMessage());
}
$this->success();
}
} }

Some files were not shown because too many files have changed in this diff Show More