作者 Karson

新增命令行一键生成API文档功能

新增插件绑定二级域名功能
新增加载JS公用模块
新增命令行创建插件自动生成菜单功能
新增后台菜单Fast.api.refreshmenu
新增后台菜单在数据变更后自动刷新的功能
新增require.min.js压缩版
新增从Headers中读取授权token的功能
新增Form.events.daterangepicker时间区别事件
新增Form表单提示成功和失败的回调事件
新增Fast.api.getrowbyid和Fast.api.getrowbyindex方法
新增commonsearch的find_in_set类型搜索
新增Menu::export的方法
新增php think api一键生成API文档功能
新增php think min的压缩参数和调试功能

优化API模块生产环境下错误信息的显示
优化移动端显示移除顶部Logo一行
优化bower.json和composer.json的版本依赖
优化插件管理列表显示
优化后台控制区多作的选项卡数据
优化CRUD生成的复选框样式及文字
优化规则管理的列表显示
优化第三方前端资源,移除冗余资源

修复在启用域名部署下的BUG
修复API初始化接口的BUG
修复会员积分日志模型BUG
修复多语言切换不存在的BUG
修复Backend.php中multi操作不触发模型事件的BUG
正在显示 73 个修改的文件 包含 2193 行增加390 行删除

要显示太多修改。

为保证性能只显示 73 of 73+ 个文件。

1 { 1 {
2 - "directory" : "public/assets/libs" 2 + "directory" : "public/assets/libs",
  3 + "ignoredDependencies": [
  4 + "file-saver",
  5 + "html2canvas",
  6 + "jspdf",
  7 + "jspdf-autotable"
  8 + ]
3 } 9 }
@@ -16,10 +16,12 @@ FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。 @@ -16,10 +16,12 @@ FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
16 * 基于`Bower`进行前端组件包管理 16 * 基于`Bower`进行前端组件包管理
17 * 数据库表一键生成`CRUD`,包括控制器、模型、视图、JS、语言包、菜单等 17 * 数据库表一键生成`CRUD`,包括控制器、模型、视图、JS、语言包、菜单等
18 * 一键压缩打包JS和CSS文件,一键CDN静态资源部署 18 * 一键压缩打包JS和CSS文件,一键CDN静态资源部署
  19 +* 一键生成API接口文档
19 * 强大的插件扩展功能,在线安装卸载升级插件 20 * 强大的插件扩展功能,在线安装卸载升级插件
20 * 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证 21 * 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
  22 +* 二级域名部署支持,同时域名支持绑定到插件
21 * 多语言支持,服务端及客户端支持 23 * 多语言支持,服务端及客户端支持
22 -* 强大的第三方插件支持(CMS、博客、文档生成) 24 +* 强大的第三方模块支持(CMS、博客、文档生成)
23 * 整合第三方短信接口(阿里云、创蓝短信) 25 * 整合第三方短信接口(阿里云、创蓝短信)
24 * 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能 26 * 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能
25 * 第三方登录(QQ、微信、微博)整合 27 * 第三方登录(QQ、微信、微博)整合
@@ -2,14 +2,17 @@ @@ -2,14 +2,17 @@
2 2
3 namespace app\admin\command; 3 namespace app\admin\command;
4 4
  5 +use app\common\library\Menu;
5 use think\addons\AddonException; 6 use think\addons\AddonException;
6 use think\addons\Service; 7 use think\addons\Service;
  8 +use think\Config;
7 use think\console\Command; 9 use think\console\Command;
8 use think\console\Input; 10 use think\console\Input;
9 use think\console\input\Option; 11 use think\console\input\Option;
10 use think\console\Output; 12 use think\console\Output;
11 use think\Db; 13 use think\Db;
12 use think\Exception; 14 use think\Exception;
  15 +use think\exception\PDOException;
13 16
14 class Addon extends Command 17 class Addon extends Command
15 { 18 {
@@ -63,14 +66,43 @@ class Addon extends Command @@ -63,14 +66,43 @@ class Addon extends Command
63 rmdirs($addonDir); 66 rmdirs($addonDir);
64 } 67 }
65 mkdir($addonDir); 68 mkdir($addonDir);
  69 + mkdir($addonDir . DS . 'controller');
  70 + $menuList = Menu::export($name);
  71 + $createMenu = $this->getCreateMenu($menuList);
  72 + $prefix = Config::get('database.prefix');
  73 + $createTableSql = '';
  74 + try
  75 + {
  76 + $result = Db::query("SHOW CREATE TABLE `" . $prefix . $name . "`;");
  77 + if (isset($result[0]) && isset($result[0]['Create Table']))
  78 + {
  79 + $createTableSql = $result[0]['Create Table'];
  80 + }
  81 + }
  82 + catch (PDOException $e)
  83 + {
  84 +
  85 + }
  86 +
66 $data = [ 87 $data = [
67 'name' => $name, 88 'name' => $name,
68 'addon' => $name, 89 'addon' => $name,
69 - 'addonClassName' => ucfirst($name) 90 + 'addonClassName' => ucfirst($name),
  91 + 'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu, "\t") . ";\n\tMenu::create(\$menu);" : '',
  92 + 'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '',
  93 + 'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '',
  94 + 'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '',
70 ]; 95 ];
71 $this->writeToFile("addon", $data, $addonDir . ucfirst($name) . '.php'); 96 $this->writeToFile("addon", $data, $addonDir . ucfirst($name) . '.php');
72 $this->writeToFile("config", $data, $addonDir . 'config.php'); 97 $this->writeToFile("config", $data, $addonDir . 'config.php');
73 $this->writeToFile("info", $data, $addonDir . 'info.ini'); 98 $this->writeToFile("info", $data, $addonDir . 'info.ini');
  99 + $this->writeToFile("controller", $data, $addonDir . 'controller' . DS . 'Index.php');
  100 + if ($createTableSql)
  101 + {
  102 + $createTableSql = str_replace("`" . $prefix, '`__PREFIX__', $createTableSql);
  103 + file_put_contents($addonDir . 'install.sql', $createTableSql);
  104 + }
  105 +
74 $output->info("Create Successed!"); 106 $output->info("Create Successed!");
75 break; 107 break;
76 case 'disable': 108 case 'disable':
@@ -257,6 +289,37 @@ class Addon extends Command @@ -257,6 +289,37 @@ class Addon extends Command
257 } 289 }
258 290
259 /** 291 /**
  292 + * 获取创建菜单的数组
  293 + * @param array $menu
  294 + * @return array
  295 + */
  296 + protected function getCreateMenu($menu)
  297 + {
  298 + $result = [];
  299 + foreach ($menu as $k => & $v)
  300 + {
  301 + $arr = [
  302 + 'name' => $v['name'],
  303 + 'title' => $v['title'],
  304 + ];
  305 + if ($v['icon'] != 'fa fa-circle-o')
  306 + {
  307 + $arr['icon'] = $v['icon'];
  308 + }
  309 + if ($v['ismenu'])
  310 + {
  311 + $arr['ismenu'] = $v['ismenu'];
  312 + }
  313 + if (isset($v['childlist']) && $v['childlist'])
  314 + {
  315 + $arr['sublist'] = $this->getCreateMenu($v['childlist']);
  316 + }
  317 + $result[] = $arr;
  318 + }
  319 + return $result;
  320 + }
  321 +
  322 + /**
260 * 写入到文件 323 * 写入到文件
261 * @param string $name 324 * @param string $name
262 * @param array $data 325 * @param array $data
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 namespace addons\{%name%}; 3 namespace addons\{%name%};
4 4
  5 +use app\common\library\Menu;
5 use think\Addons; 6 use think\Addons;
6 7
7 /** 8 /**
@@ -16,6 +17,7 @@ class {%addonClassName%} extends Addons @@ -16,6 +17,7 @@ class {%addonClassName%} extends Addons
16 */ 17 */
17 public function install() 18 public function install()
18 { 19 {
  20 + {%addonInstallMenu%}
19 return true; 21 return true;
20 } 22 }
21 23
@@ -25,6 +27,7 @@ class {%addonClassName%} extends Addons @@ -25,6 +27,7 @@ class {%addonClassName%} extends Addons
25 */ 27 */
26 public function uninstall() 28 public function uninstall()
27 { 29 {
  30 + {%addonUninstallMenu%}
28 return true; 31 return true;
29 } 32 }
30 33
@@ -34,6 +37,7 @@ class {%addonClassName%} extends Addons @@ -34,6 +37,7 @@ class {%addonClassName%} extends Addons
34 */ 37 */
35 public function enable() 38 public function enable()
36 { 39 {
  40 + {%addonEnableMenu%}
37 return true; 41 return true;
38 } 42 }
39 43
@@ -43,6 +47,7 @@ class {%addonClassName%} extends Addons @@ -43,6 +47,7 @@ class {%addonClassName%} extends Addons
43 */ 47 */
44 public function disable() 48 public function disable()
45 { 49 {
  50 + {%addonDisableMenu%}
46 return true; 51 return true;
47 } 52 }
48 53
  1 +<?php
  2 +
  3 +namespace addons\{%addon%}\controller;
  4 +
  5 +use think\addons\Controller;
  6 +
  7 +class Index extends Controller
  8 +{
  9 +
  10 + public function index()
  11 + {
  12 + $this->error("当前插件暂无前台页面");
  13 + }
  14 +
  15 +}
1 name = {%name%} 1 name = {%name%}
2 -title = 插件名称 2 +title = 插件名称({%name%})
3 intro = FastAdmin插件 3 intro = FastAdmin插件
4 author = yourname 4 author = yourname
5 website = http://www.fastadmin.net 5 website = http://www.fastadmin.net
  1 +<?php
  2 +
  3 +namespace app\admin\command;
  4 +
  5 +use app\admin\command\Api\library\Builder;
  6 +use think\Config;
  7 +use think\console\Command;
  8 +use think\console\Input;
  9 +use think\console\input\Option;
  10 +use think\console\Output;
  11 +use think\Exception;
  12 +
  13 +class Api extends Command
  14 +{
  15 +
  16 + protected function configure()
  17 + {
  18 + $site = Config::get('site');
  19 + $this
  20 + ->setName('api')
  21 + ->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
  22 + ->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
  23 + ->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
  24 + ->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
  25 + ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
  26 + ->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'])
  27 + ->addOption('author', 'a', Option::VALUE_OPTIONAL, 'document author', $site['name'])
  28 + ->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
  29 + ->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
  30 + ->setDescription('Compress js and css file');
  31 + }
  32 +
  33 + protected function execute(Input $input, Output $output)
  34 + {
  35 + $apiDir = __DIR__ . DS . 'Api' . DS;
  36 +
  37 + $force = $input->getOption('force');
  38 + $url = $input->getOption('url');
  39 + $language = $input->getOption('language');
  40 + $langFile = $apiDir . 'lang' . DS . $language . '.php';
  41 + if (!is_file($langFile))
  42 + {
  43 + throw new Exception('language file not found');
  44 + }
  45 + $lang = include $langFile;
  46 + // 目标目录
  47 + $output_dir = ROOT_PATH . 'public' . DS;
  48 + $output_file = $output_dir . $input->getOption('output');
  49 + if (is_file($output_file) && !$force)
  50 + {
  51 + throw new Exception("api index file already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  52 + }
  53 + // 模板文件
  54 + $template_dir = $apiDir . 'template' . DS;
  55 + $template_file = $template_dir . $input->getOption('template');
  56 + if (!is_file($template_file))
  57 + {
  58 + throw new Exception('template file not found');
  59 + }
  60 + // 额外的类
  61 + $classes = $input->getOption('class');
  62 + // 标题
  63 + $title = $input->getOption('title');
  64 + // 作者
  65 + $author = $input->getOption('author');
  66 + // 模块
  67 + $module = $input->getOption('module');
  68 +
  69 + $moduleDir = APP_PATH . $module . DS;
  70 + if (!is_dir($moduleDir))
  71 + {
  72 + throw new Exception('module not found');
  73 + }
  74 + $controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
  75 + $files = new \RecursiveIteratorIterator(
  76 + new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
  77 + );
  78 +
  79 + foreach ($files as $name => $file)
  80 + {
  81 + if (!$file->isDir())
  82 + {
  83 + $filePath = $file->getRealPath();
  84 + $classes[] = $this->get_class_from_file($filePath);
  85 + }
  86 + }
  87 +
  88 + $config = [
  89 + 'title' => $title,
  90 + 'author' => $author,
  91 + 'description' => '',
  92 + 'apiurl' => $url,
  93 + ];
  94 + $builder = new Builder($classes);
  95 + $content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
  96 +
  97 + if (!file_put_contents($output_file, $content))
  98 + {
  99 + throw new Exception('Cannot save the content to ' . $output_file);
  100 + }
  101 + $output->info("Build Successed!");
  102 + }
  103 +
  104 + /**
  105 + * get full qualified class name
  106 + *
  107 + * @param string $path_to_file
  108 + * @author JBYRNE http://jarretbyrne.com/2015/06/197/
  109 + * @return string
  110 + */
  111 + protected function get_class_from_file($path_to_file)
  112 + {
  113 + //Grab the contents of the file
  114 + $contents = file_get_contents($path_to_file);
  115 +
  116 + //Start with a blank namespace and class
  117 + $namespace = $class = "";
  118 +
  119 + //Set helper values to know that we have found the namespace/class token and need to collect the string values after them
  120 + $getting_namespace = $getting_class = false;
  121 +
  122 + //Go through each token and evaluate it as necessary
  123 + foreach (token_get_all($contents) as $token)
  124 + {
  125 +
  126 + //If this token is the namespace declaring, then flag that the next tokens will be the namespace name
  127 + if (is_array($token) && $token[0] == T_NAMESPACE)
  128 + {
  129 + $getting_namespace = true;
  130 + }
  131 +
  132 + //If this token is the class declaring, then flag that the next tokens will be the class name
  133 + if (is_array($token) && $token[0] == T_CLASS)
  134 + {
  135 + $getting_class = true;
  136 + }
  137 +
  138 + //While we're grabbing the namespace name...
  139 + if ($getting_namespace === true)
  140 + {
  141 +
  142 + //If the token is a string or the namespace separator...
  143 + if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR]))
  144 + {
  145 +
  146 + //Append the token's value to the name of the namespace
  147 + $namespace .= $token[1];
  148 + }
  149 + else if ($token === ';')
  150 + {
  151 +
  152 + //If the token is the semicolon, then we're done with the namespace declaration
  153 + $getting_namespace = false;
  154 + }
  155 + }
  156 +
  157 + //While we're grabbing the class name...
  158 + if ($getting_class === true)
  159 + {
  160 +
  161 + //If the token is a string, it's the name of the class
  162 + if (is_array($token) && $token[0] == T_STRING)
  163 + {
  164 +
  165 + //Store the token's value as the class name
  166 + $class = $token[1];
  167 +
  168 + //Got what we need, stope here
  169 + break;
  170 + }
  171 + }
  172 + }
  173 +
  174 + //Build the fully-qualified class name and return it
  175 + return $namespace ? $namespace . '\\' . $class : $class;
  176 + }
  177 +
  178 +}
  1 +<?php
  2 +
  3 +return [
  4 + 'Info' => '基础信息',
  5 + 'Sandbox' => '在线测试',
  6 + 'Sampleoutput' => '返回示例',
  7 + 'Headers' => 'Headers',
  8 + 'Parameters' => '参数',
  9 + 'Body' => '正文',
  10 + 'Name' => '名称',
  11 + 'Type' => '类型',
  12 + 'Required' => '必选',
  13 + 'Description' => '描述',
  14 + 'Send' => '提交',
  15 + 'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中',
  16 + 'Apiurltips' => 'API接口URL',
  17 + 'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中',
  18 + 'ReturnHeaders' => '响应头',
  19 + 'ReturnParameters' => '返回参数',
  20 + 'Response' => '响应输出',
  21 +];
  1 +<?php
  2 +
  3 +namespace app\admin\command\Api\library;
  4 +
  5 +use think\Config;
  6 +
  7 +/**
  8 + * @website https://github.com/calinrada/php-apidoc
  9 + * @author Calin Rada <rada.calin@gmail.com>
  10 + * @author Karson <karsonzhang@163.com>
  11 + */
  12 +class Builder
  13 +{
  14 +
  15 + /**
  16 + *
  17 + * @var \think\View
  18 + */
  19 + public $view = null;
  20 +
  21 + /**
  22 + * parse classes
  23 + * @var array
  24 + */
  25 + protected $classes = [];
  26 +
  27 + /**
  28 + *
  29 + * @param array $classes
  30 + */
  31 + public function __construct($classes = [])
  32 + {
  33 + $this->classes = array_merge($this->classes, $classes);
  34 + $this->view = \think\View::instance(Config::get('template'), Config::get('view_replace_str'));
  35 + }
  36 +
  37 + protected function extractAnnotations()
  38 + {
  39 + $st_output = [];
  40 + foreach ($this->classes as $class)
  41 + {
  42 + $st_output[] = Extractor::getAllClassAnnotations($class);
  43 + }
  44 + return end($st_output);
  45 + }
  46 +
  47 + protected function generateHeadersTemplate($docs)
  48 + {
  49 + if (!isset($docs['ApiHeaders']))
  50 + {
  51 + return [];
  52 + }
  53 +
  54 + $headerslist = array();
  55 + foreach ($docs['ApiHeaders'] as $params)
  56 + {
  57 + $tr = array(
  58 + 'name' => $params['name'],
  59 + 'type' => $params['type'],
  60 + 'sample' => isset($params['sample']) ? $params['sample'] : '',
  61 + 'required' => isset($params['required']) ? $params['required'] : false,
  62 + 'description' => isset($params['description']) ? $params['description'] : '',
  63 + );
  64 + $headerslist[] = $tr;
  65 + }
  66 +
  67 + return $headerslist;
  68 + }
  69 +
  70 + protected function generateParamsTemplate($docs)
  71 + {
  72 + if (!isset($docs['ApiParams']))
  73 + {
  74 + return [];
  75 + }
  76 +
  77 + $paramslist = array();
  78 + foreach ($docs['ApiParams'] as $params)
  79 + {
  80 + $tr = array(
  81 + 'name' => $params['name'],
  82 + 'type' => isset($params['type']) ? $params['type'] : 'string',
  83 + 'sample' => isset($params['sample']) ? $params['sample'] : '',
  84 + 'required' => isset($params['required']) ? $params['required'] : true,
  85 + 'description' => isset($params['description']) ? $params['description'] : '',
  86 + );
  87 + $paramslist[] = $tr;
  88 + }
  89 +
  90 + return $paramslist;
  91 + }
  92 +
  93 + protected function generateReturnHeadersTemplate($docs)
  94 + {
  95 + if (!isset($docs['ApiReturnHeaders']))
  96 + {
  97 + return [];
  98 + }
  99 +
  100 + $headerslist = array();
  101 + foreach ($docs['ApiReturnHeaders'] as $params)
  102 + {
  103 + $tr = array(
  104 + 'name' => $params['name'],
  105 + 'type' => 'string',
  106 + 'sample' => isset($params['sample']) ? $params['sample'] : '',
  107 + 'required' => isset($params['required']) && $params['required'] ? 'Yes' : 'No',
  108 + 'description' => isset($params['description']) ? $params['description'] : '',
  109 + );
  110 + $headerslist[] = $tr;
  111 + }
  112 +
  113 + return $headerslist;
  114 + }
  115 +
  116 + protected function generateReturnParamsTemplate($st_params)
  117 + {
  118 + if (!isset($st_params['ApiReturnParams']))
  119 + {
  120 + return [];
  121 + }
  122 +
  123 + $paramslist = array();
  124 + foreach ($st_params['ApiReturnParams'] as $params)
  125 + {
  126 + $tr = array(
  127 + 'name' => $params['name'],
  128 + 'type' => isset($params['type']) ? $params['type'] : 'string',
  129 + 'sample' => isset($params['sample']) ? $params['sample'] : '',
  130 + 'description' => isset($params['description']) ? $params['description'] : '',
  131 + );
  132 + $paramslist[] = $tr;
  133 + }
  134 +
  135 + return $paramslist;
  136 + }
  137 +
  138 + protected function generateBadgeForMethod($data)
  139 + {
  140 + $method = strtoupper(is_array($data['ApiMethod'][0]) ? $data['ApiMethod'][0]['data'] : $data['ApiMethod'][0]);
  141 + $labes = array(
  142 + 'POST' => 'label-primary',
  143 + 'GET' => 'label-success',
  144 + 'PUT' => 'label-warning',
  145 + 'DELETE' => 'label-danger',
  146 + 'PATCH' => 'label-default',
  147 + 'OPTIONS' => 'label-info'
  148 + );
  149 +
  150 + return isset($labes[$method]) ? $labes[$method] : $labes['GET'];
  151 + }
  152 +
  153 + public function parse()
  154 + {
  155 + $annotations = $this->extractAnnotations();
  156 +
  157 + $counter = 0;
  158 + $section = null;
  159 + $docslist = [];
  160 + foreach ($annotations as $class => $methods)
  161 + {
  162 + foreach ($methods as $name => $docs)
  163 + {
  164 + if (isset($docs['ApiSector'][0]))
  165 + {
  166 + $section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0];
  167 + }
  168 + else
  169 + {
  170 + $section = $class;
  171 + }
  172 + if (0 === count($docs))
  173 + {
  174 + continue;
  175 + }
  176 +
  177 + $docslist[$section][] = [
  178 + 'id' => $counter,
  179 + 'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
  180 + 'method_label' => $this->generateBadgeForMethod($docs),
  181 + 'section' => $section,
  182 + 'route' => is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0],
  183 + 'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
  184 + 'body' => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
  185 + 'headerslist' => $this->generateHeadersTemplate($docs),
  186 + 'paramslist' => $this->generateParamsTemplate($docs),
  187 + 'returnheaderslist' => $this->generateReturnHeadersTemplate($docs),
  188 + 'returnparamslist' => $this->generateReturnParamsTemplate($docs),
  189 + 'return' => isset($docs['ApiReturn']) ? is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0] : '',
  190 + ];
  191 + $counter++;
  192 + }
  193 + }
  194 +
  195 + return $docslist;
  196 + }
  197 +
  198 + public function getView()
  199 + {
  200 + return $this->view;
  201 + }
  202 +
  203 + /**
  204 + * 渲染
  205 + * @param string $template
  206 + * @param array $vars
  207 + * @return string
  208 + */
  209 + public function render($template, $vars = [])
  210 + {
  211 + $docslist = $this->parse();
  212 +
  213 + return $this->view->display(file_get_contents($template), array_merge($vars, ['docslist' => $docslist]));
  214 + }
  215 +
  216 +}
  1 +<?php
  2 +
  3 +namespace app\admin\command\Api\library;
  4 +
  5 +/**
  6 + * Class imported from https://github.com/eriknyk/Annotations
  7 + * @author Erik Amaru Ortiz https://github.com/eriknyk‎
  8 + *
  9 + * @license http://opensource.org/licenses/bsd-license.php The BSD License
  10 + * @author Calin Rada <rada.calin@gmail.com>
  11 + */
  12 +class Extractor
  13 +{
  14 +
  15 + /**
  16 + * Static array to store already parsed annotations
  17 + * @var array
  18 + */
  19 + private static $annotationCache;
  20 +
  21 + /**
  22 + * Indicates that annotations should has strict behavior, 'false' by default
  23 + * @var boolean
  24 + */
  25 + private $strict = false;
  26 +
  27 + /**
  28 + * Stores the default namespace for Objects instance, usually used on methods like getMethodAnnotationsObjects()
  29 + * @var string
  30 + */
  31 + public $defaultNamespace = '';
  32 +
  33 + /**
  34 + * Sets strict variable to true/false
  35 + * @param bool $value boolean value to indicate that annotations to has strict behavior
  36 + */
  37 + public function setStrict($value)
  38 + {
  39 + $this->strict = (bool) $value;
  40 + }
  41 +
  42 + /**
  43 + * Sets default namespace to use in object instantiation
  44 + * @param string $namespace default namespace
  45 + */
  46 + public function setDefaultNamespace($namespace)
  47 + {
  48 + $this->defaultNamespace = $namespace;
  49 + }
  50 +
  51 + /**
  52 + * Gets default namespace used in object instantiation
  53 + * @return string $namespace default namespace
  54 + */
  55 + public function getDefaultAnnotationNamespace()
  56 + {
  57 + return $this->defaultNamespace;
  58 + }
  59 +
  60 + /**
  61 + * Gets all anotations with pattern @SomeAnnotation() from a given class
  62 + *
  63 + * @param string $className class name to get annotations
  64 + * @return array self::$annotationCache all annotated elements
  65 + */
  66 + public static function getClassAnnotations($className)
  67 + {
  68 + if (!isset(self::$annotationCache[$className]))
  69 + {
  70 + $class = new \ReflectionClass($className);
  71 + self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment());
  72 + }
  73 +
  74 + return self::$annotationCache[$className];
  75 + }
  76 +
  77 + public static function getAllClassAnnotations($className)
  78 + {
  79 + $class = new \ReflectionClass($className);
  80 +
  81 + foreach ($class->getMethods() as $object)
  82 + {
  83 + self::$annotationCache['annotations'][$className][$object->name] = self::getMethodAnnotations($className, $object->name);
  84 + }
  85 +
  86 + return self::$annotationCache['annotations'];
  87 + }
  88 +
  89 + /**
  90 + * Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
  91 + *
  92 + * @param string $className class name
  93 + * @param string $methodName method name to get annotations
  94 + * @return array self::$annotationCache all annotated elements of a method given
  95 + */
  96 + public static function getMethodAnnotations($className, $methodName)
  97 + {
  98 + if (!isset(self::$annotationCache[$className . '::' . $methodName]))
  99 + {
  100 + try
  101 + {
  102 + $method = new \ReflectionMethod($className, $methodName);
  103 + $class = new \ReflectionClass($className);
  104 + if (!$method->isPublic() || $method->isConstructor())
  105 + {
  106 + $annotations = array();
  107 + }
  108 + else
  109 + {
  110 + $annotations = self::consolidateAnnotations($method, $class);
  111 + }
  112 + }
  113 + catch (\ReflectionException $e)
  114 + {
  115 + $annotations = array();
  116 + }
  117 +
  118 + self::$annotationCache[$className . '::' . $methodName] = $annotations;
  119 + }
  120 +
  121 + return self::$annotationCache[$className . '::' . $methodName];
  122 + }
  123 +
  124 + /**
  125 + * Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
  126 + * and instance its abcAnnotation class
  127 + *
  128 + * @param string $className class name
  129 + * @param string $methodName method name to get annotations
  130 + * @return array self::$annotationCache all annotated objects of a method given
  131 + */
  132 + public function getMethodAnnotationsObjects($className, $methodName)
  133 + {
  134 + $annotations = $this->getMethodAnnotations($className, $methodName);
  135 + $objects = array();
  136 +
  137 + $i = 0;
  138 +
  139 + foreach ($annotations as $annotationClass => $listParams)
  140 + {
  141 + $annotationClass = ucfirst($annotationClass);
  142 + $class = $this->defaultNamespace . $annotationClass . 'Annotation';
  143 +
  144 + // verify is the annotation class exists, depending if Annotations::strict is true
  145 + // if not, just skip the annotation instance creation.
  146 + if (!class_exists($class))
  147 + {
  148 + if ($this->strict)
  149 + {
  150 + throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class));
  151 + }
  152 + else
  153 + {
  154 + // silent skip & continue
  155 + continue;
  156 + }
  157 + }
  158 +
  159 + if (empty($objects[$annotationClass]))
  160 + {
  161 + $objects[$annotationClass] = new $class();
  162 + }
  163 +
  164 + foreach ($listParams as $params)
  165 + {
  166 + if (is_array($params))
  167 + {
  168 + foreach ($params as $key => $value)
  169 + {
  170 + $objects[$annotationClass]->set($key, $value);
  171 + }
  172 + }
  173 + else
  174 + {
  175 + $objects[$annotationClass]->set($i++, $params);
  176 + }
  177 + }
  178 + }
  179 +
  180 + return $objects;
  181 + }
  182 +
  183 + private static function consolidateAnnotations($method, $class)
  184 + {
  185 + $dockblockClass = $class->getDocComment();
  186 + $docblockMethod = $method->getDocComment();
  187 + $methodName = $method->getName();
  188 +
  189 + $methodAnnotations = self::parseAnnotations($docblockMethod);
  190 + $classAnnotations = self::parseAnnotations($dockblockClass);
  191 + if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty')
  192 + {
  193 + return [];
  194 + }
  195 +
  196 + $properties = $class->getDefaultProperties();
  197 + $noNeedLogin = isset($properties['noNeedLogin']) ? is_array($properties['noNeedLogin']) ? $properties['noNeedLogin'] : [$properties['noNeedLogin']] : [];
  198 + $noNeedRight = isset($properties['noNeedRight']) ? is_array($properties['noNeedRight']) ? $properties['noNeedRight'] : [$properties['noNeedRight']] : [];
  199 +
  200 + preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblockMethod), $methodArr);
  201 + preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $dockblockClass), $classArr);
  202 +
  203 + $methodTitle = isset($methodArr[1]) && isset($methodArr[1][0]) ? $methodArr[1][0] : '';
  204 + $classTitle = isset($classArr[1]) && isset($classArr[1][0]) ? $classArr[1][0] : '';
  205 +
  206 + if (!isset($methodAnnotations['ApiMethod']))
  207 + {
  208 + $methodAnnotations['ApiMethod'] = ['get'];
  209 + }
  210 + if (!isset($methodAnnotations['ApiSummary']))
  211 + {
  212 + $methodAnnotations['ApiSummary'] = [$methodTitle];
  213 + }
  214 +
  215 + if ($methodAnnotations)
  216 + {
  217 + foreach ($classAnnotations as $name => $valueClass)
  218 + {
  219 + if (count($valueClass) !== 1)
  220 + {
  221 + continue;
  222 + }
  223 +
  224 + if ($name === 'ApiRoute')
  225 + {
  226 + if (isset($methodAnnotations[$name]))
  227 + {
  228 + $methodAnnotations[$name] = [rtrim($valueClass[0], '/') . $methodAnnotations[$name][0]];
  229 + }
  230 + else
  231 + {
  232 + $methodAnnotations[$name] = [rtrim($valueClass[0], '/') . '/' . $method->getName()];
  233 + }
  234 + }
  235 +
  236 + if ($name === 'ApiSector')
  237 + {
  238 + $methodAnnotations[$name] = $valueClass;
  239 + }
  240 + }
  241 + }
  242 + if (!isset($methodAnnotations['ApiTitle']))
  243 + {
  244 + $methodAnnotations['ApiTitle'] = [$methodTitle];
  245 + }
  246 + if (!isset($methodAnnotations['ApiRoute']))
  247 + {
  248 + $urlArr = [];
  249 + $className = $class->getName();
  250 +
  251 + list($prefix, $suffix) = explode('\\' . \think\Config::get('url_controller_layer') . '\\', $className);
  252 + $prefixArr = explode('\\', $prefix);
  253 + $suffixArr = explode('\\', $suffix);
  254 + if ($prefixArr[0] == \think\Config::get('app_namespace'))
  255 + {
  256 + $prefixArr[0] = '';
  257 + }
  258 + $urlArr = array_merge($urlArr, $prefixArr);
  259 + $urlArr[] = implode('.', array_map(function($item) {
  260 + return \think\Loader::parseName($item);
  261 + }, $suffixArr));
  262 + $urlArr[] = $method->getName();
  263 + $methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
  264 + }
  265 + if (!isset($methodAnnotations['ApiSector']))
  266 + {
  267 + $methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : [$classTitle];
  268 + }
  269 + if (!isset($methodAnnotations['ApiParams']))
  270 + {
  271 + $params = self::parseCustomAnnotations($docblockMethod, 'param');
  272 + foreach ($params as $k => $v)
  273 + {
  274 + $arr = explode(' ', preg_replace("/[\s]+/", " ", $v));
  275 + $methodAnnotations['ApiParams'][] = [
  276 + 'name' => isset($arr[1]) ? str_replace('$', '', $arr[1]) : '',
  277 + 'nullable' => false,
  278 + 'type' => isset($arr[0]) ? $arr[0] : 'string',
  279 + 'description' => isset($arr[2]) ? $arr[2] : ''
  280 + ];
  281 + }
  282 + }
  283 + $methodAnnotations['ApiPermissionLogin'] = [!in_array('*', $noNeedLogin) && !in_array($methodName, $noNeedLogin)];
  284 + $methodAnnotations['ApiPermissionRight'] = [!in_array('*', $noNeedRight) && !in_array($methodName, $noNeedRight)];
  285 + return $methodAnnotations;
  286 + }
  287 +
  288 + /**
  289 + * Parse annotations
  290 + *
  291 + * @param string $docblock
  292 + * @param string $name
  293 + * @return array parsed annotations params
  294 + */
  295 + private static function parseCustomAnnotations($docblock, $name = 'param')
  296 + {
  297 + $annotations = array();
  298 +
  299 + $docblock = substr($docblock, 3, -2);
  300 + if (preg_match_all('/@' . $name . '(?:\s*(?:\(\s*)?(.*?)(?:\s*\))?)??\s*(?:\n|\*\/)/', $docblock, $matches))
  301 + {
  302 + foreach ($matches[1] as $k => $v)
  303 + {
  304 + $annotations[] = $v;
  305 + }
  306 + }
  307 + return $annotations;
  308 + }
  309 +
  310 + /**
  311 + * Parse annotations
  312 + *
  313 + * @param string $docblock
  314 + * @return array parsed annotations params
  315 + */
  316 + private static function parseAnnotations($docblock)
  317 + {
  318 + $annotations = array();
  319 +
  320 + // Strip away the docblock header and footer to ease parsing of one line annotations
  321 + $docblock = substr($docblock, 3, -2);
  322 + if (preg_match_all('/@(?<name>[A-Za-z_-]+)[\s\t]*\((?<args>(?:(?!\)).)*)\)\r?/s', $docblock, $matches))
  323 + {
  324 + $numMatches = count($matches[0]);
  325 +
  326 + for ($i = 0; $i < $numMatches; ++$i)
  327 + {
  328 + // annotations has arguments
  329 + if (isset($matches['args'][$i]))
  330 + {
  331 + $argsParts = trim($matches['args'][$i]);
  332 + $name = $matches['name'][$i];
  333 + $value = self::parseArgs($argsParts);
  334 + }
  335 + else
  336 + {
  337 + $value = array();
  338 + }
  339 +
  340 + $annotations[$name][] = $value;
  341 + }
  342 + }
  343 +
  344 + return $annotations;
  345 + }
  346 +
  347 + /**
  348 + * Parse individual annotation arguments
  349 + *
  350 + * @param string $content arguments string
  351 + * @return array annotated arguments
  352 + */
  353 + private static function parseArgs($content)
  354 + {
  355 + // Replace initial stars
  356 + $content = preg_replace('/^\s*\*/m', '', $content);
  357 +
  358 + $data = array();
  359 + $len = strlen($content);
  360 + $i = 0;
  361 + $var = '';
  362 + $val = '';
  363 + $level = 1;
  364 +
  365 + $prevDelimiter = '';
  366 + $nextDelimiter = '';
  367 + $nextToken = '';
  368 + $composing = false;
  369 + $type = 'plain';
  370 + $delimiter = null;
  371 + $quoted = false;
  372 + $tokens = array('"', '"', '{', '}', ',', '=');
  373 +
  374 + while ($i <= $len)
  375 + {
  376 + $prev_c = substr($content, $i - 1, 1);
  377 + $c = substr($content, $i++, 1);
  378 +
  379 + if ($c === '"' && $prev_c !== "\\")
  380 + {
  381 + $delimiter = $c;
  382 + //open delimiter
  383 + if (!$composing && empty($prevDelimiter) && empty($nextDelimiter))
  384 + {
  385 + $prevDelimiter = $nextDelimiter = $delimiter;
  386 + $val = '';
  387 + $composing = true;
  388 + $quoted = true;
  389 + }
  390 + else
  391 + {
  392 + // close delimiter
  393 + if ($c !== $nextDelimiter)
  394 + {
  395 + throw new Exception(sprintf(
  396 + "Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
  397 + ));
  398 + }
  399 +
  400 + // validating syntax
  401 + if ($i < $len)
  402 + {
  403 + if (',' !== substr($content, $i, 1) && '\\' !== $prev_c)
  404 + {
  405 + throw new Exception(sprintf(
  406 + "Parse Error: missing comma separator near: ...%s<--", substr($content, ($i - 10), $i)
  407 + ));
  408 + }
  409 + }
  410 +
  411 + $prevDelimiter = $nextDelimiter = '';
  412 + $composing = false;
  413 + $delimiter = null;
  414 + }
  415 + }
  416 + elseif (!$composing && in_array($c, $tokens))
  417 + {
  418 + switch ($c)
  419 + {
  420 + case '=':
  421 + $prevDelimiter = $nextDelimiter = '';
  422 + $level = 2;
  423 + $composing = false;
  424 + $type = 'assoc';
  425 + $quoted = false;
  426 + break;
  427 + case ',':
  428 + $level = 3;
  429 +
  430 + // If composing flag is true yet,
  431 + // it means that the string was not enclosed, so it is parsing error.
  432 + if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter))
  433 + {
  434 + throw new Exception(sprintf(
  435 + "Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
  436 + ));
  437 + }
  438 +
  439 + $prevDelimiter = $nextDelimiter = '';
  440 + break;
  441 + case '{':
  442 + $subc = '';
  443 + $subComposing = true;
  444 +
  445 + while ($i <= $len)
  446 + {
  447 + $c = substr($content, $i++, 1);
  448 +
  449 + if (isset($delimiter) && $c === $delimiter)
  450 + {
  451 + throw new Exception(sprintf(
  452 + "Parse Error: Composite variable is not enclosed correctly."
  453 + ));
  454 + }
  455 +
  456 + if ($c === '}')
  457 + {
  458 + $subComposing = false;
  459 + break;
  460 + }
  461 + $subc .= $c;
  462 + }
  463 +
  464 + // if the string is composing yet means that the structure of var. never was enclosed with '}'
  465 + if ($subComposing)
  466 + {
  467 + throw new Exception(sprintf(
  468 + "Parse Error: Composite variable is not enclosed correctly. near: ...%s'", $subc
  469 + ));
  470 + }
  471 +
  472 + $val = self::parseArgs($subc);
  473 + break;
  474 + }
  475 + }
  476 + else
  477 + {
  478 + if ($level == 1)
  479 + {
  480 + $var .= $c;
  481 + }
  482 + elseif ($level == 2)
  483 + {
  484 + $val .= $c;
  485 + }
  486 + }
  487 +
  488 + if ($level === 3 || $i === $len)
  489 + {
  490 + if ($type == 'plain' && $i === $len)
  491 + {
  492 + $data = self::castValue($var);
  493 + }
  494 + else
  495 + {
  496 + $data[trim($var)] = self::castValue($val, !$quoted);
  497 + }
  498 +
  499 + $level = 1;
  500 + $var = $val = '';
  501 + $composing = false;
  502 + $quoted = false;
  503 + }
  504 + }
  505 +
  506 + return $data;
  507 + }
  508 +
  509 + /**
  510 + * Try determinate the original type variable of a string
  511 + *
  512 + * @param string $val string containing possibles variables that can be cast to bool or int
  513 + * @param boolean $trim indicate if the value passed should be trimmed after to try cast
  514 + * @return mixed returns the value converted to original type if was possible
  515 + */
  516 + private static function castValue($val, $trim = false)
  517 + {
  518 + if (is_array($val))
  519 + {
  520 + foreach ($val as $key => $value)
  521 + {
  522 + $val[$key] = self::castValue($value);
  523 + }
  524 + }
  525 + elseif (is_string($val))
  526 + {
  527 + if ($trim)
  528 + {
  529 + $val = trim($val);
  530 + }
  531 + $val = stripslashes($val);
  532 + $tmp = strtolower($val);
  533 +
  534 + if ($tmp === 'false' || $tmp === 'true')
  535 + {
  536 + $val = $tmp === 'true';
  537 + }
  538 + elseif (is_numeric($val))
  539 + {
  540 + return $val + 0;
  541 + }
  542 +
  543 + unset($tmp);
  544 + }
  545 +
  546 + return $val;
  547 + }
  548 +
  549 +}
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 + <head>
  4 + <meta charset="utf-8">
  5 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7 + <meta name="description" content="">
  8 + <meta name="author" content="{$config.author}">
  9 + <title>{$config.title}</title>
  10 + <link href="https://cdn.bootcss.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
  11 + <style type="text/css">
  12 + body { padding-top: 70px; margin-bottom: 15px; }
  13 + .tab-pane { padding-top: 10px; }
  14 + .mt0 { margin-top: 0px; }
  15 + .footer { font-size: 12px; color: #666; }
  16 + .label { display: inline-block; min-width: 65px; padding: 0.3em 0.6em 0.3em; }
  17 + .string { color: green; }
  18 + .number { color: darkorange; }
  19 + .boolean { color: blue; }
  20 + .null { color: magenta; }
  21 + .key { color: red; }
  22 + .popover { max-width: 400px; max-height: 400px; overflow-y: auto;}
  23 + </style>
  24 + </head>
  25 + <body>
  26 + <!-- Fixed navbar -->
  27 + <div class="navbar navbar-default navbar-fixed-top" role="navigation">
  28 + <div class="container">
  29 + <div class="navbar-header">
  30 + <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
  31 + <span class="sr-only">Toggle navigation</span>
  32 + <span class="icon-bar"></span>
  33 + <span class="icon-bar"></span>
  34 + <span class="icon-bar"></span>
  35 + </button>
  36 + <a class="navbar-brand" href="http://www.fastadmin.net" target="_blank">{$config.title}</a>
  37 + </div>
  38 + <div class="navbar-collapse collapse">
  39 + <form class="navbar-form navbar-right">
  40 + <div class="form-group">
  41 + Token:
  42 + </div>
  43 + <div class="form-group">
  44 + <input type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Tokentips}" placeholder="token" id="token" />
  45 + </div>
  46 + <div class="form-group">
  47 + Apiurl:
  48 + </div>
  49 + <div class="form-group">
  50 + <input id="apiUrl" type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Apiurltips}" placeholder="https://api.mydomain.com" value="{$config.apiurl}" />
  51 + </div>
  52 + <div class="form-group">
  53 + <button type="button" class="btn btn-success btn-sm" data-toggle="tooltip" title="{$lang.Savetips}" id="save_data">
  54 + <span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
  55 + </button>
  56 + </div>
  57 + </form>
  58 + </div><!--/.nav-collapse -->
  59 + </div>
  60 + </div>
  61 +
  62 + <div class="container">
  63 + <div class="panel-group" id="accordion">
  64 + {foreach name="docslist" id="docs"}
  65 + <h2>{$key}</h2>
  66 + <hr>
  67 + {foreach name="docs" id="api" }
  68 + <div class="panel panel-default">
  69 + <div class="panel-heading">
  70 + <h4 class="panel-title">
  71 + <span class="label {$api.method_label}">{$api.method|strtoupper}</span> <a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.route}</a>
  72 + </h4>
  73 + </div>
  74 + <div id="collapseOne{$api.id}" class="panel-collapse collapse">
  75 + <div class="panel-body">
  76 +
  77 + <!-- Nav tabs -->
  78 + <ul class="nav nav-tabs" id="doctab{$api.id}">
  79 + <li class="active"><a href="#info{$api.id}" data-toggle="tab">{$lang.Info}</a></li>
  80 + <li><a href="#sandbox{$api.id}" data-toggle="tab">{$lang.Sandbox}</a></li>
  81 + <li><a href="#sample{$api.id}" data-toggle="tab">{$lang.Sampleoutput}</a></li>
  82 + </ul>
  83 +
  84 + <!-- Tab panes -->
  85 + <div class="tab-content">
  86 +
  87 + <div class="tab-pane active" id="info{$api.id}">
  88 + <div class="well">
  89 + {$api.summary}
  90 + </div>
  91 + <div class="panel panel-default">
  92 + <div class="panel-heading"><strong>{$lang.Headers}</strong></div>
  93 + <div class="panel-body">
  94 + {if $api.headerslist}
  95 + <table class="table table-hover">
  96 + <thead>
  97 + <tr>
  98 + <th>{$lang.Name}</th>
  99 + <th>{$lang.Type}</th>
  100 + <th>{$lang.Required}</th>
  101 + <th>{$lang.Description}</th>
  102 + </tr>
  103 + </thead>
  104 + <tbody>
  105 + {foreach name="api['headerslist']" id="header"}
  106 + <tr>
  107 + <td>{$header.name}</td>
  108 + <td>{$header.type}</td>
  109 + <td>{$header.required?'是':'否'}</td>
  110 + <td>{$header.description}</td>
  111 + </tr>
  112 + {/foreach}
  113 + </tbody>
  114 + </table>
  115 + {else /}
  116 +
  117 + {/if}
  118 + </div>
  119 + </div>
  120 + <div class="panel panel-default">
  121 + <div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
  122 + <div class="panel-body">
  123 + {if $api.paramslist}
  124 + <table class="table table-hover">
  125 + <thead>
  126 + <tr>
  127 + <th>{$lang.Name}</th>
  128 + <th>{$lang.Type}</th>
  129 + <th>{$lang.Required}</th>
  130 + <th>{$lang.Description}</th>
  131 + </tr>
  132 + </thead>
  133 + <tbody>
  134 + {foreach name="api['paramslist']" id="param"}
  135 + <tr>
  136 + <td>{$param.name}</td>
  137 + <td>{$param.type}</td>
  138 + <td>{:$param.required?'是':'否'}</td>
  139 + <td>{$param.description}</td>
  140 + </tr>
  141 + {/foreach}
  142 + </tbody>
  143 + </table>
  144 + {else /}
  145 +
  146 + {/if}
  147 + </div>
  148 + </div>
  149 + <div class="panel panel-default">
  150 + <div class="panel-heading"><strong>{$lang.Body}</strong></div>
  151 + <div class="panel-body">
  152 + {$api.body|default='无'}
  153 + </div>
  154 + </div>
  155 + </div><!-- #info -->
  156 +
  157 + <div class="tab-pane" id="sandbox{$api.id}">
  158 + <div class="row">
  159 + <div class="col-md-12">
  160 + {if $api.headerslist}
  161 + <div class="panel panel-default">
  162 + <div class="panel-heading"><strong>{$lang.Headers}</strong></div>
  163 + <div class="panel-body">
  164 + <div class="headers">
  165 + {foreach name="api['headerslist']" id="param"}
  166 + <div class="form-group">
  167 + <label class="control-label" for="{$param.name}">{$param.name}</label>
  168 + <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}">
  169 + </div>
  170 + {/foreach}
  171 + </div>
  172 + </div>
  173 + </div>
  174 + {/if}
  175 + <div class="panel panel-default">
  176 + <div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
  177 + <div class="panel-body">
  178 + <form enctype="application/x-www-form-urlencoded" role="form" action="{$api.route}" method="{$api.method}" name="form{$api.id}" id="form{$api.id}">
  179 + {if $api.paramslist}
  180 + {foreach name="api['paramslist']" id="param"}
  181 + <div class="form-group">
  182 + <label class="control-label" for="{$param.name}">{$param.name}</label>
  183 + <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}">
  184 + </div>
  185 + {/foreach}
  186 + {else /}
  187 + <div class="form-group">
  188 +
  189 + </div>
  190 + {/if}
  191 + <div class="form-group">
  192 + <button type="submit" class="btn btn-success send" rel="{$api.id}">{$lang.Send}</button>
  193 + </div>
  194 + </form>
  195 + </div>
  196 + </div>
  197 + <div class="panel panel-default">
  198 + <div class="panel-heading"><strong>{$lang.Response}</strong></div>
  199 + <div class="panel-body">
  200 + <div class="row">
  201 + <div class="col-md-12" style="overflow-x:auto">
  202 + <pre id="response_headers{$api.id}"></pre>
  203 + <pre id="response{$api.id}"></pre>
  204 + </div>
  205 + </div>
  206 + </div>
  207 + </div>
  208 + <div class="panel panel-default">
  209 + <div class="panel-heading"><strong>{$lang.ReturnParameters}</strong></div>
  210 + <div class="panel-body">
  211 + {if $api.returnparamslist}
  212 + <table class="table table-hover">
  213 + <thead>
  214 + <tr>
  215 + <th>{$lang.Name}</th>
  216 + <th>{$lang.Type}</th>
  217 + <th>{$lang.Description}</th>
  218 + </tr>
  219 + </thead>
  220 + <tbody>
  221 + {foreach name="api['returnparamslist']" id="param"}
  222 + <tr>
  223 + <td>{$param.name}</td>
  224 + <td>{$param.type}</td>
  225 + <td>{$param.description}</td>
  226 + </tr>
  227 + {/foreach}
  228 + </tbody>
  229 + </table>
  230 + {else /}
  231 +
  232 + {/if}
  233 + </div>
  234 + </div>
  235 + </div>
  236 + </div>
  237 + </div><!-- #sandbox -->
  238 +
  239 + <div class="tab-pane" id="sample{$api.id}">
  240 + <div class="row">
  241 + <div class="col-md-12">
  242 + <pre id="sample_response{$api.id}">{$api.return|default='无'}</pre>
  243 + </div>
  244 + </div>
  245 + </div><!-- #sample -->
  246 +
  247 + </div><!-- .tab-content -->
  248 + </div>
  249 + </div>
  250 + </div>
  251 + {/foreach}
  252 + {/foreach}
  253 + </div>
  254 +
  255 + <hr>
  256 +
  257 + <div class="row mt0 footer">
  258 + <div class="col-md-6" align="left">
  259 + Generated on {:date('Y-m-d H:i:s')}
  260 + </div>
  261 + <div class="col-md-6" align="right">
  262 + <a href="http://www.fastadmin.net" target="_blank">FastAdmin</a>
  263 + </div>
  264 + </div>
  265 +
  266 + </div> <!-- /container -->
  267 +
  268 + <script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
  269 + <script src="https://cdn.bootcss.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
  270 + <script type="text/javascript">
  271 + function syntaxHighlight(json) {
  272 + if (typeof json != 'string') {
  273 + json = JSON.stringify(json, undefined, 2);
  274 + }
  275 + json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  276 + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
  277 + var cls = 'number';
  278 + if (/^"/.test(match)) {
  279 + if (/:$/.test(match)) {
  280 + cls = 'key';
  281 + } else {
  282 + cls = 'string';
  283 + }
  284 + } else if (/true|false/.test(match)) {
  285 + cls = 'boolean';
  286 + } else if (/null/.test(match)) {
  287 + cls = 'null';
  288 + }
  289 + return '<span class="' + cls + '">' + match + '</span>';
  290 + });
  291 + }
  292 +
  293 + function prepareStr(str) {
  294 + try {
  295 + return syntaxHighlight(JSON.stringify(JSON.parse(str.replace(/'/g, '"')), null, 2));
  296 + } catch (e) {
  297 + return str;
  298 + }
  299 + }
  300 + var storage = (function () {
  301 + var uid = new Date;
  302 + var storage;
  303 + var result;
  304 + try {
  305 + (storage = window.localStorage).setItem(uid, uid);
  306 + result = storage.getItem(uid) == uid;
  307 + storage.removeItem(uid);
  308 + return result && storage;
  309 + } catch (exception) {
  310 + }
  311 + }());
  312 +
  313 + $.fn.serializeObject = function ()
  314 + {
  315 + var o = {};
  316 + var a = this.serializeArray();
  317 + $.each(a, function () {
  318 + if (!this.value) {
  319 + return;
  320 + }
  321 + if (o[this.name] !== undefined) {
  322 + if (!o[this.name].push) {
  323 + o[this.name] = [o[this.name]];
  324 + }
  325 + o[this.name].push(this.value || '');
  326 + } else {
  327 + o[this.name] = this.value || '';
  328 + }
  329 + });
  330 + return o;
  331 + };
  332 +
  333 + $(document).ready(function () {
  334 +
  335 + if (storage) {
  336 + $('#token').val(storage.getItem('token'));
  337 + $('#apiUrl').val(storage.getItem('apiUrl'));
  338 + }
  339 +
  340 + $('[data-toggle="tooltip"]').tooltip({
  341 + placement: 'bottom'
  342 + });
  343 +
  344 + $('code[id^=response]').hide();
  345 +
  346 + $.each($('pre[id^=sample_response],pre[id^=sample_post_body]'), function () {
  347 + if ($(this).html() == 'NA') {
  348 + return;
  349 + }
  350 + var str = prepareStr($(this).html());
  351 + $(this).html(str);
  352 + });
  353 +
  354 + $("[data-toggle=popover]").popover({placement: 'right'});
  355 +
  356 + $('[data-toggle=popover]').on('shown.bs.popover', function () {
  357 + var $sample = $(this).parent().find(".popover-content"),
  358 + str = $(this).data('content');
  359 + if (typeof str == "undefined" || str === "") {
  360 + return;
  361 + }
  362 + var str = prepareStr(str);
  363 + $sample.html('<pre>' + str + '</pre>');
  364 + });
  365 +
  366 + $('body').on('click', '#save_data', function (e) {
  367 + if (storage) {
  368 + storage.setItem('token', $('#token').val());
  369 + storage.setItem('apiUrl', $('#apiUrl').val());
  370 + } else {
  371 + alert('Your browser does not support local storage');
  372 + }
  373 + });
  374 +
  375 + $('body').on('click', '.send', function (e) {
  376 + e.preventDefault();
  377 + var form = $(this).closest('form');
  378 + //added /g to get all the matched params instead of only first
  379 + var matchedParamsInRoute = $(form).attr('action').match(/[^{]+(?=\})/g);
  380 + var theId = $(this).attr('rel');
  381 + //keep a copy of action attribute in order to modify the copy
  382 + //instead of the initial attribute
  383 + var url = $(form).attr('action');
  384 +
  385 + var serializedData = new FormData();
  386 +
  387 + $(form).find('input').each(function (i, input) {
  388 + if ($(input).attr('type') == 'file') {
  389 + serializedData.append($(input).attr('name'), $(input)[0].files[0]);
  390 + } else {
  391 + serializedData.append($(input).attr('name'), $(input).val())
  392 + }
  393 + });
  394 +
  395 + var index, key, value;
  396 +
  397 + if (matchedParamsInRoute) {
  398 + for (index = 0; index < matchedParamsInRoute.length; ++index) {
  399 + try {
  400 + key = matchedParamsInRoute[index];
  401 + value = serializedData[key];
  402 + if (typeof value == "undefined")
  403 + value = "";
  404 + url = url.replace("{" + key + "}", value);
  405 + delete serializedData[key];
  406 + } catch (err) {
  407 + console.log(err);
  408 + }
  409 + }
  410 + }
  411 +
  412 + var headers = {};
  413 +
  414 + var token = $('#token').val();
  415 + if (token.length > 0) {
  416 + headers[token] = token;
  417 + }
  418 +
  419 + $("#sandbox" + theId + " .headers input[type=text]").each(function () {
  420 + val = $(this).val();
  421 + if (val.length > 0) {
  422 + headers[$(this).prop('name')] = val;
  423 + }
  424 + });
  425 +
  426 + $.ajax({
  427 + url: $('#apiUrl').val() + url,
  428 + data: $(form).attr('method') == 'get' ? $(form).serialize() : serializedData,
  429 + type: $(form).attr('method') + '',
  430 + dataType: 'json',
  431 + contentType: false,
  432 + processData: false,
  433 + headers: headers,
  434 + success: function (data, textStatus, xhr) {
  435 + if (typeof data === 'object') {
  436 + var str = JSON.stringify(data, null, 2);
  437 + $('#response' + theId).html(syntaxHighlight(str));
  438 + } else {
  439 + $('#response' + theId).html(data || '');
  440 + }
  441 + $('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
  442 + $('#response' + theId).show();
  443 + },
  444 + error: function (xhr, textStatus, error) {
  445 + try {
  446 + var str = JSON.stringify($.parseJSON(xhr.responseText), null, 2);
  447 + } catch (e) {
  448 + var str = xhr.responseText;
  449 + }
  450 + $('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
  451 + $('#response' + theId).html(syntaxHighlight(str));
  452 + $('#response' + theId).show();
  453 + }
  454 + });
  455 + return false;
  456 + });
  457 + });
  458 + </script>
  459 + </body>
  460 +</html>
@@ -580,8 +580,8 @@ class Crud extends Command @@ -580,8 +580,8 @@ class Crud extends Command
580 } 580 }
581 $formAddElement = $formEditElement = Form::hidden($fieldName, $no, array_merge(['checked' => ''], $attrArr)); 581 $formAddElement = $formEditElement = Form::hidden($fieldName, $no, array_merge(['checked' => ''], $attrArr));
582 $attrArr['id'] = $fieldName . "-switch"; 582 $attrArr['id'] = $fieldName . "-switch";
583 - $formAddElement .= sprintf(Form::label("{$attrArr['id']}", "%s abcdefg"), Form::checkbox($fieldName, $yes, $defaultValue === $yes, $attrArr));  
584 - $formEditElement .= sprintf(Form::label("{$attrArr['id']}", "%s abcdefg"), Form::checkbox($fieldName, $yes, 0, $attrArr)); 583 + $formAddElement .= sprintf(Form::label("{$attrArr['id']}", "%s {:__('Yes')}", ['class'=>'control-label']), Form::checkbox($fieldName, $yes, $defaultValue === $yes, $attrArr));
  584 + $formEditElement .= sprintf(Form::label("{$attrArr['id']}", "%s {:__('Yes')}", ['class'=>'control-label']), Form::checkbox($fieldName, $yes, 0, $attrArr));
585 $formEditElement = str_replace('type="checkbox"', 'type="checkbox" {in name="' . "\$row.{$field}" . '" value="' . $yes . '"}checked{/in}', $formEditElement); 585 $formEditElement = str_replace('type="checkbox"', 'type="checkbox" {in name="' . "\$row.{$field}" . '" value="' . $yes . '"}checked{/in}', $formEditElement);
586 } 586 }
587 else if ($inputType == 'citypicker') 587 else if ($inputType == 'citypicker')
@@ -963,6 +963,7 @@ EOD; @@ -963,6 +963,7 @@ EOD;
963 if ($content || !Lang::has($field)) 963 if ($content || !Lang::has($field))
964 { 964 {
965 $itemArr = []; 965 $itemArr = [];
  966 + $content = str_replace(',', ',', $content);
966 if (stripos($content, ':') !== false && stripos($content, ',') && stripos($content, '=') !== false) 967 if (stripos($content, ':') !== false && stripos($content, ',') && stripos($content, '=') !== false)
967 { 968 {
968 list($fieldLang, $item) = explode(':', $content); 969 list($fieldLang, $item) = explode(':', $content);
@@ -997,6 +998,7 @@ EOD; @@ -997,6 +998,7 @@ EOD;
997 /** 998 /**
998 * 读取数据和语言数组列表 999 * 读取数据和语言数组列表
999 * @param array $arr 1000 * @param array $arr
  1001 + * @param boolean $withTpl
1000 * @return array 1002 * @return array
1001 */ 1003 */
1002 protected function getLangArray($arr, $withTpl = TRUE) 1004 protected function getLangArray($arr, $withTpl = TRUE)
@@ -1035,6 +1037,7 @@ EOD; @@ -1035,6 +1037,7 @@ EOD;
1035 protected function getItemArray($item, $field, $comment) 1037 protected function getItemArray($item, $field, $comment)
1036 { 1038 {
1037 $itemArr = []; 1039 $itemArr = [];
  1040 + $comment = str_replace('', ',', $comment);
1038 if (stripos($comment, ':') !== false && stripos($comment, ',') && stripos($comment, '=') !== false) 1041 if (stripos($comment, ':') !== false && stripos($comment, ',') && stripos($comment, '=') !== false)
1039 { 1042 {
1040 list($fieldLang, $item) = explode(':', $comment); 1043 list($fieldLang, $item) = explode(':', $comment);
@@ -1255,7 +1258,7 @@ EOD; @@ -1255,7 +1258,7 @@ EOD;
1255 { 1258 {
1256 $html .= ", operate:'RANGE', addclass:'datetimerange'"; 1259 $html .= ", operate:'RANGE', addclass:'datetimerange'";
1257 } 1260 }
1258 - else if (in_array($datatype,['float', 'double', 'decimal'])) 1261 + else if (in_array($datatype, ['float', 'double', 'decimal']))
1259 { 1262 {
1260 $html .= ", operate:'BETWEEN'"; 1263 $html .= ", operate:'BETWEEN'";
1261 } 1264 }
@@ -81,6 +81,8 @@ class Install extends Command @@ -81,6 +81,8 @@ class Install extends Command
81 // 写入数据库配置 81 // 写入数据库配置
82 file_put_contents($dbConfigFile, $config); 82 file_put_contents($dbConfigFile, $config);
83 83
  84 + \think\Cache::rm('__menu__');
  85 +
84 $output->info("Install Successed!"); 86 $output->info("Install Successed!");
85 } 87 }
86 88
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 官网: http://www.fastadmin.net 4 官网: http://www.fastadmin.net
5 演示: http://demo.fastadmin.net 5 演示: http://demo.fastadmin.net
6 6
7 - Date: 2017年09月15 7 + Date: 2018年03月07
8 */ 8 */
9 9
10 SET FOREIGN_KEY_CHECKS = 0; 10 SET FOREIGN_KEY_CHECKS = 0;
@@ -396,6 +396,9 @@ BEGIN; @@ -396,6 +396,9 @@ BEGIN;
396 INSERT INTO `fa_test` VALUES (1, 0, 12, '12,13', 'monday', 'hot,index', 'male', 'music,reading', '我是一篇测试文章', '<p>我是测试内容</p>', '/assets/img/avatar.png', '/assets/img/avatar.png,/assets/img/qrcode.png', '/assets/img/avatar.png', '关键字', '描述', '广西壮族自治区/百色市/平果县', 0.00, 0, '2017-07-10', '2017-07-10 18:24:45', 2017, '18:24:45', 1499682285, 1499682526, 1499682526, 0, 1, 'normal', '1'); 396 INSERT INTO `fa_test` VALUES (1, 0, 12, '12,13', 'monday', 'hot,index', 'male', 'music,reading', '我是一篇测试文章', '<p>我是测试内容</p>', '/assets/img/avatar.png', '/assets/img/avatar.png,/assets/img/qrcode.png', '/assets/img/avatar.png', '关键字', '描述', '广西壮族自治区/百色市/平果县', 0.00, 0, '2017-07-10', '2017-07-10 18:24:45', 2017, '18:24:45', 1499682285, 1499682526, 1499682526, 0, 1, 'normal', '1');
397 COMMIT; 397 COMMIT;
398 398
  399 +-- ----------------------------
  400 +-- Table structure for fa_user
  401 +-- ----------------------------
399 DROP TABLE IF EXISTS `fa_user`; 402 DROP TABLE IF EXISTS `fa_user`;
400 CREATE TABLE `fa_user` ( 403 CREATE TABLE `fa_user` (
401 `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', 404 `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
@@ -431,10 +434,16 @@ CREATE TABLE `fa_user` ( @@ -431,10 +434,16 @@ CREATE TABLE `fa_user` (
431 KEY `mobile` (`mobile`) 434 KEY `mobile` (`mobile`)
432 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='会员表'; 435 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='会员表';
433 436
  437 +-- ----------------------------
  438 +-- Records of fa_user
  439 +-- ----------------------------
434 BEGIN; 440 BEGIN;
435 INSERT INTO `fa_user` VALUES (1, 1, 'admin', 'admin', 'c13f62012fd6a8fdf06b3452a94430e5', 'rpR6Bv', 'admin@163.com', '13888888888', '/assets/img/avatar.png', 0, 0, '2017-04-15', '', 0, 1, 1, 1516170492, 1516171614, '127.0.0.1', 0, '127.0.0.1', 1491461418, 0, 1516171614, '', 'normal',''); 441 INSERT INTO `fa_user` VALUES (1, 1, 'admin', 'admin', 'c13f62012fd6a8fdf06b3452a94430e5', 'rpR6Bv', 'admin@163.com', '13888888888', '/assets/img/avatar.png', 0, 0, '2017-04-15', '', 0, 1, 1, 1516170492, 1516171614, '127.0.0.1', 0, '127.0.0.1', 1491461418, 0, 1516171614, '', 'normal','');
436 COMMIT; 442 COMMIT;
437 443
  444 +-- ----------------------------
  445 +-- Table structure for fa_user_group
  446 +-- ----------------------------
438 DROP TABLE IF EXISTS `fa_user_group`; 447 DROP TABLE IF EXISTS `fa_user_group`;
439 CREATE TABLE `fa_user_group` ( 448 CREATE TABLE `fa_user_group` (
440 `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 449 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@@ -446,10 +455,16 @@ CREATE TABLE `fa_user_group` ( @@ -446,10 +455,16 @@ CREATE TABLE `fa_user_group` (
446 PRIMARY KEY (`id`) 455 PRIMARY KEY (`id`)
447 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='会员组表'; 456 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='会员组表';
448 457
  458 +-- ----------------------------
  459 +-- Records of fa_user_group
  460 +-- ----------------------------
449 BEGIN; 461 BEGIN;
450 INSERT INTO `fa_user_group` VALUES (1, '默认组', '1,2,3,4,5,6,7,8,9,10,11,12', 1515386468, 1516168298, 'normal'); 462 INSERT INTO `fa_user_group` VALUES (1, '默认组', '1,2,3,4,5,6,7,8,9,10,11,12', 1515386468, 1516168298, 'normal');
451 COMMIT; 463 COMMIT;
452 464
  465 +-- ----------------------------
  466 +-- Table structure for fa_user_rule
  467 +-- ----------------------------
453 DROP TABLE IF EXISTS `fa_user_rule`; 468 DROP TABLE IF EXISTS `fa_user_rule`;
454 CREATE TABLE `fa_user_rule` ( 469 CREATE TABLE `fa_user_rule` (
455 `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 470 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@@ -465,6 +480,9 @@ CREATE TABLE `fa_user_rule` ( @@ -465,6 +480,9 @@ CREATE TABLE `fa_user_rule` (
465 PRIMARY KEY (`id`) 480 PRIMARY KEY (`id`)
466 ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='会员规则表'; 481 ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='会员规则表';
467 482
  483 +-- ----------------------------
  484 +-- Records of fa_user_rule
  485 +-- ----------------------------
468 BEGIN; 486 BEGIN;
469 INSERT INTO `fa_user_rule` VALUES (1, 0, 'index', '前台', '', 1, 1516168079, 1516168079, 1, 'normal'); 487 INSERT INTO `fa_user_rule` VALUES (1, 0, 'index', '前台', '', 1, 1516168079, 1516168079, 1, 'normal');
470 INSERT INTO `fa_user_rule` VALUES (2, 0, 'api', 'API接口', '', 1, 1516168062, 1516168062, 2, 'normal'); 488 INSERT INTO `fa_user_rule` VALUES (2, 0, 'api', 'API接口', '', 1, 1516168062, 1516168062, 2, 'normal');
@@ -480,6 +498,9 @@ INSERT INTO `fa_user_rule` VALUES (11, 4, 'api/user/index', '会员中心', '', @@ -480,6 +498,9 @@ INSERT INTO `fa_user_rule` VALUES (11, 4, 'api/user/index', '会员中心', '',
480 INSERT INTO `fa_user_rule` VALUES (12, 4, 'api/user/profile', '个人资料', '', 0, 1516015012, 1516015012, 3, 'normal'); 498 INSERT INTO `fa_user_rule` VALUES (12, 4, 'api/user/profile', '个人资料', '', 0, 1516015012, 1516015012, 3, 'normal');
481 COMMIT; 499 COMMIT;
482 500
  501 +-- ----------------------------
  502 +-- Table structure for fa_user_score_log
  503 +-- ----------------------------
483 DROP TABLE IF EXISTS `fa_user_score_log`; 504 DROP TABLE IF EXISTS `fa_user_score_log`;
484 CREATE TABLE `fa_user_score_log` ( 505 CREATE TABLE `fa_user_score_log` (
485 `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 506 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@@ -492,6 +513,9 @@ CREATE TABLE `fa_user_score_log` ( @@ -492,6 +513,9 @@ CREATE TABLE `fa_user_score_log` (
492 PRIMARY KEY (`id`) 513 PRIMARY KEY (`id`)
493 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='会员积分变动表'; 514 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='会员积分变动表';
494 515
  516 +-- ----------------------------
  517 +-- Table structure for fa_user_token
  518 +-- ----------------------------
495 DROP TABLE IF EXISTS `fa_user_token`; 519 DROP TABLE IF EXISTS `fa_user_token`;
496 CREATE TABLE `fa_user_token` ( 520 CREATE TABLE `fa_user_token` (
497 `token` varchar(50) NOT NULL COMMENT 'Token', 521 `token` varchar(50) NOT NULL COMMENT 'Token',
@@ -501,4 +525,30 @@ CREATE TABLE `fa_user_token` ( @@ -501,4 +525,30 @@ CREATE TABLE `fa_user_token` (
501 PRIMARY KEY (`token`) 525 PRIMARY KEY (`token`)
502 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='会员Token表'; 526 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='会员Token表';
503 527
  528 +-- ----------------------------
  529 +-- Table structure for fa_version
  530 +-- ----------------------------
  531 +DROP TABLE IF EXISTS `fa_version`;
  532 +CREATE TABLE `fa_version` (
  533 + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  534 + `oldversion` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '旧版本号',
  535 + `newversion` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '新版本号',
  536 + `packagesize` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '包大小',
  537 + `content` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '升级内容',
  538 + `downloadurl` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '下载地址',
  539 + `enforce` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '强制更新',
  540 + `createtime` int(10) NOT NULL DEFAULT 0 COMMENT '创建时间',
  541 + `updatetime` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间',
  542 + `weigh` int(10) NOT NULL DEFAULT 0 COMMENT '权重',
  543 + `status` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '状态',
  544 + PRIMARY KEY (`id`) USING BTREE
  545 +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '版本表' ROW_FORMAT = Compact;
  546 +
  547 +-- ----------------------------
  548 +-- Table structure for fa_version
  549 +-- ----------------------------
  550 +BEGIN;
  551 +INSERT INTO `fa_version` (`id`, `oldversion`, `newversion`, `packagesize`, `content`, `downloadurl`, `enforce`, `createtime`, `updatetime`, `weigh`, `status`) VALUES
  552 +(1, '1.1.1,2', '1.2.1', '20M', '更新内容', 'http://www.fastadmin.net/download.html', 1, 1520425318, 0, 0, 'normal');
  553 +COMMIT;
504 SET FOREIGN_KEY_CHECKS = 1; 554 SET FOREIGN_KEY_CHECKS = 1;
@@ -56,7 +56,6 @@ class Menu extends Command @@ -56,7 +56,6 @@ class Menu extends Command
56 { 56 {
57 throw new Exception("There is no menu to delete"); 57 throw new Exception("There is no menu to delete");
58 } 58 }
59 - $readyMenu = [];  
60 $output->info("Are you sure you want to delete all those menu? Type 'yes' to continue: "); 59 $output->info("Are you sure you want to delete all those menu? Type 'yes' to continue: ");
61 $line = fgets(STDIN); 60 $line = fgets(STDIN);
62 if (trim($line) != 'yes') 61 if (trim($line) != 'yes')
@@ -27,6 +27,7 @@ class Min extends Command @@ -27,6 +27,7 @@ class Min extends Command
27 ->setName('min') 27 ->setName('min')
28 ->addOption('module', 'm', Option::VALUE_REQUIRED, 'module name(frontend or backend),use \'all\' when build all modules', null) 28 ->addOption('module', 'm', Option::VALUE_REQUIRED, 'module name(frontend or backend),use \'all\' when build all modules', null)
29 ->addOption('resource', 'r', Option::VALUE_REQUIRED, 'resource name(js or css),use \'all\' when build all resources', null) 29 ->addOption('resource', 'r', Option::VALUE_REQUIRED, 'resource name(js or css),use \'all\' when build all resources', null)
  30 + ->addOption('optimize', 'o', Option::VALUE_OPTIONAL, 'optimize type(uglify|closure|none)', 'none')
30 ->setDescription('Compress js and css file'); 31 ->setDescription('Compress js and css file');
31 } 32 }
32 33
@@ -34,6 +35,7 @@ class Min extends Command @@ -34,6 +35,7 @@ class Min extends Command
34 { 35 {
35 $module = $input->getOption('module') ?: ''; 36 $module = $input->getOption('module') ?: '';
36 $resource = $input->getOption('resource') ?: ''; 37 $resource = $input->getOption('resource') ?: '';
  38 + $optimize = $input->getOption('optimize') ?: 'none';
37 39
38 if (!$module || !in_array($module, ['frontend', 'backend', 'all'])) 40 if (!$module || !in_array($module, ['frontend', 'backend', 'all']))
39 { 41 {
@@ -89,6 +91,7 @@ class Min extends Command @@ -89,6 +91,7 @@ class Min extends Command
89 'cssBaseUrl' => $this->options['cssBaseUrl'], 91 'cssBaseUrl' => $this->options['cssBaseUrl'],
90 'jsBasePath' => str_replace(DS, '/', ROOT_PATH . $this->options['jsBaseUrl']), 92 'jsBasePath' => str_replace(DS, '/', ROOT_PATH . $this->options['jsBaseUrl']),
91 'cssBasePath' => str_replace(DS, '/', ROOT_PATH . $this->options['cssBaseUrl']), 93 'cssBasePath' => str_replace(DS, '/', ROOT_PATH . $this->options['cssBaseUrl']),
  94 + 'optimize' => $optimize,
92 'ds' => DS, 95 'ds' => DS,
93 ]; 96 ];
94 97
@@ -117,11 +120,19 @@ class Min extends Command @@ -117,11 +120,19 @@ class Min extends Command
117 $output->info("Compress " . $data["{$res}BaseName"] . ".{$res}"); 120 $output->info("Compress " . $data["{$res}BaseName"] . ".{$res}");
118 121
119 // 执行压缩 122 // 执行压缩
120 - echo exec("{$nodeExec} \"{$minPath}r.js\" -o \"{$tempFile}\" >> \"{$minPath}node.log\""); 123 + $command = "{$nodeExec} \"{$minPath}r.js\" -o \"{$tempFile}\" >> \"{$minPath}node.log\"";
  124 + if ($output->isDebug())
  125 + {
  126 + $output->warning($command);
  127 + }
  128 + echo exec($command);
121 } 129 }
122 } 130 }
123 131
  132 + if (!$output->isDebug())
  133 + {
124 @unlink($tempFile); 134 @unlink($tempFile);
  135 + }
125 136
126 $output->info("Build Successed!"); 137 $output->info("Build Successed!");
127 } 138 }
1 ({ 1 ({
2 cssIn: "{%cssBasePath%}{%cssBaseName%}.css", 2 cssIn: "{%cssBasePath%}{%cssBaseName%}.css",
3 out: "{%cssBasePath%}{%cssBaseName%}.min.css", 3 out: "{%cssBasePath%}{%cssBaseName%}.min.css",
4 - optimizeCss: "default" 4 + optimizeCss: "default",
  5 + optimize: "{%optimize%}"
5 }) 6 })
@@ -2,7 +2,8 @@ @@ -2,7 +2,8 @@
2 {%config%} 2 {%config%}
3 , 3 ,
4 optimizeCss: "standard", 4 optimizeCss: "standard",
5 - optimize: "none", //可使用uglify|closure|none 5 + optimize: "{%optimize%}", //可使用uglify|closure|none
  6 + preserveLicenseComments: false,
6 removeCombined: false, 7 removeCombined: false,
7 baseUrl: "{%jsBasePath%}", //JS文件所在的基础目录 8 baseUrl: "{%jsBasePath%}", //JS文件所在的基础目录
8 name: "{%jsBaseName%}", //来源文件,不包含后缀 9 name: "{%jsBaseName%}", //来源文件,不包含后缀
@@ -5,6 +5,7 @@ namespace app\admin\controller; @@ -5,6 +5,7 @@ namespace app\admin\controller;
5 use app\common\controller\Backend; 5 use app\common\controller\Backend;
6 use think\addons\AddonException; 6 use think\addons\AddonException;
7 use think\addons\Service; 7 use think\addons\Service;
  8 +use think\Cache;
8 use think\Config; 9 use think\Config;
9 use think\Exception; 10 use think\Exception;
10 11
@@ -190,6 +191,7 @@ class Addon extends Backend @@ -190,6 +191,7 @@ class Addon extends Backend
190 $action = $action == 'enable' ? $action : 'disable'; 191 $action = $action == 'enable' ? $action : 'disable';
191 //调用启用、禁用的方法 192 //调用启用、禁用的方法
192 Service::$action($name, $force); 193 Service::$action($name, $force);
  194 + Cache::rm('__menu__');
193 $this->success(__('Operate successful')); 195 $this->success(__('Operate successful'));
194 } 196 }
195 catch (AddonException $e) 197 catch (AddonException $e)
@@ -314,6 +316,7 @@ class Addon extends Backend @@ -314,6 +316,7 @@ class Addon extends Backend
314 ]; 316 ];
315 //调用更新的方法 317 //调用更新的方法
316 Service::upgrade($name, $extend); 318 Service::upgrade($name, $extend);
  319 + Cache::rm('__menu__');
317 $this->success(__('Operate successful')); 320 $this->success(__('Operate successful'));
318 } 321 }
319 catch (AddonException $e) 322 catch (AddonException $e)
@@ -370,7 +373,10 @@ class Addon extends Backend @@ -370,7 +373,10 @@ class Addon extends Backend
370 $list[] = $v; 373 $list[] = $v;
371 } 374 }
372 $total = count($list); 375 $total = count($list);
  376 + if ($limit)
  377 + {
373 $list = array_slice($list, $offset, $limit); 378 $list = array_slice($list, $offset, $limit);
  379 + }
374 $result = array("total" => $total, "rows" => $list); 380 $result = array("total" => $total, "rows" => $list);
375 381
376 $callback = $this->request->get('callback') ? "jsonp" : "json"; 382 $callback = $this->request->get('callback') ? "jsonp" : "json";
@@ -29,13 +29,21 @@ class Index extends Backend @@ -29,13 +29,21 @@ class Index extends Backend
29 */ 29 */
30 public function index() 30 public function index()
31 { 31 {
32 - // 32 + //左侧菜单
33 $menulist = $this->auth->getSidebar([ 33 $menulist = $this->auth->getSidebar([
34 'dashboard' => 'hot', 34 'dashboard' => 'hot',
35 'addon' => ['new', 'red', 'badge'], 35 'addon' => ['new', 'red', 'badge'],
36 - 'auth/rule' => 'side', 36 + 'auth/rule' => __('Menu'),
37 'general' => ['new', 'purple'], 37 'general' => ['new', 'purple'],
38 ], $this->view->site['fixedpage']); 38 ], $this->view->site['fixedpage']);
  39 + $action = $this->request->request('action');
  40 + if ($this->request->isPost())
  41 + {
  42 + if ($action == 'refreshmenu')
  43 + {
  44 + $this->success('', null, ['menulist' => $menulist]);
  45 + }
  46 + }
39 $this->view->assign('menulist', $menulist); 47 $this->view->assign('menulist', $menulist);
40 $this->view->assign('title', __('Home')); 48 $this->view->assign('title', __('Home'));
41 return $this->view->fetch(); 49 return $this->view->fetch();
@@ -13,6 +13,6 @@ return [ @@ -13,6 +13,6 @@ return [
13 'Menu tips' => '规则任意,不可重复,仅做层级显示,无需匹配控制器和方法', 13 'Menu tips' => '规则任意,不可重复,仅做层级显示,无需匹配控制器和方法',
14 'Node tips' => '控制器/方法名', 14 'Node tips' => '控制器/方法名',
15 'The non-menu rule must have parent' => '非菜单规则节点必须有父级', 15 'The non-menu rule must have parent' => '非菜单规则节点必须有父级',
16 - 'If not necessary, use the command line to build rule' => '非必要情况下请直接使用命令行php think menu来生成', 16 + 'If not necessary, use the command line to build rule' => '非必要情况下请直接使用命令行<a href="http://doc.fastadmin.net/docs/command.html#一键生成菜单" target="_blank">php think menu</a>来生成',
17 'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成', 17 'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
18 ]; 18 ];
@@ -12,6 +12,8 @@ return [ @@ -12,6 +12,8 @@ return [
12 'Avatar' => '头像', 12 'Avatar' => '头像',
13 'Level' => '等级', 13 'Level' => '等级',
14 'Gender' => '性别', 14 'Gender' => '性别',
  15 + 'Male' => '男',
  16 + 'FeMale' => '女',
15 'Birthday' => '生日', 17 'Birthday' => '生日',
16 'Bio' => '格言', 18 'Bio' => '格言',
17 'Score' => '积分', 19 'Score' => '积分',
@@ -273,7 +273,7 @@ class Auth extends \fast\Auth @@ -273,7 +273,7 @@ class Auth extends \fast\Auth
273 $groupIds[] = $v['id']; 273 $groupIds[] = $v['id'];
274 } 274 }
275 // 取出所有分组 275 // 取出所有分组
276 - $groupList = model('AuthGroup')->all(['status' => 'normal']); 276 + $groupList = \app\admin\model\AuthGroup::where(['status' => 'normal'])->select();
277 $objList = []; 277 $objList = [];
278 foreach ($groups as $K => $v) 278 foreach ($groups as $K => $v)
279 { 279 {
@@ -310,8 +310,8 @@ class Auth extends \fast\Auth @@ -310,8 +310,8 @@ class Auth extends \fast\Auth
310 if (!$this->isSuperAdmin()) 310 if (!$this->isSuperAdmin())
311 { 311 {
312 $groupIds = $this->getChildrenGroupIds(false); 312 $groupIds = $this->getChildrenGroupIds(false);
313 - $authGroupList = model('AuthGroupAccess')  
314 - ->field('uid,group_id') 313 + $authGroupList = \app\admin\model\AuthGroupAccess::
  314 + field('uid,group_id')
315 ->where('group_id', 'in', $groupIds) 315 ->where('group_id', 'in', $groupIds)
316 ->select(); 316 ->select();
317 317
@@ -407,7 +407,7 @@ class Auth extends \fast\Auth @@ -407,7 +407,7 @@ class Auth extends \fast\Auth
407 $select_id = 0; 407 $select_id = 0;
408 $pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader'); 408 $pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
409 // 必须将结果集转换为数组 409 // 必须将结果集转换为数组
410 - $ruleList = collection(model('AuthRule')->where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray(); 410 + $ruleList = collection(\app\admin\model\AuthRule::where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray();
411 foreach ($ruleList as $k => &$v) 411 foreach ($ruleList as $k => &$v)
412 { 412 {
413 if (!in_array($v['name'], $userRule)) 413 if (!in_array($v['name'], $userRule))
@@ -267,7 +267,8 @@ trait Backend @@ -267,7 +267,8 @@ trait Backend
267 { 267 {
268 $this->model->where($this->dataLimitField, 'in', $adminIds); 268 $this->model->where($this->dataLimitField, 'in', $adminIds);
269 } 269 }
270 - $count = $this->model->where($this->model->getPk(), 'in', $ids)->update($values); 270 + $this->model->where($this->model->getPk(), 'in', $ids);
  271 + $count = $this->model->allowField(true)->isUpdate(true)->save($values);
271 if ($count) 272 if ($count)
272 { 273 {
273 $this->success(); 274 $this->success();
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 namespace app\admin\model; 3 namespace app\admin\model;
4 4
  5 +use think\Cache;
5 use think\Model; 6 use think\Model;
6 7
7 class AuthRule extends Model 8 class AuthRule extends Model
@@ -13,8 +14,16 @@ class AuthRule extends Model @@ -13,8 +14,16 @@ class AuthRule extends Model
13 protected $createTime = 'createtime'; 14 protected $createTime = 'createtime';
14 protected $updateTime = 'updatetime'; 15 protected $updateTime = 'updatetime';
15 16
  17 + protected static function init()
  18 + {
  19 + self::afterWrite(function ($row) {
  20 + Cache::rm('__menu__');
  21 + });
  22 + }
  23 +
16 public function getTitleAttr($value, $data) 24 public function getTitleAttr($value, $data)
17 { 25 {
18 return __($value); 26 return __($value);
19 } 27 }
  28 +
20 } 29 }
@@ -10,6 +10,12 @@ @@ -10,6 +10,12 @@
10 .payimg .alipaycode {position:absolute;left:265px;top:442px;} 10 .payimg .alipaycode {position:absolute;left:265px;top:442px;}
11 .payimg .wechatcode {position:absolute;left:660px;top:442px;} 11 .payimg .wechatcode {position:absolute;left:660px;top:442px;}
12 .thumbnail img{width:100%;} 12 .thumbnail img{width:100%;}
  13 + .fixed-table-toolbar .pull-right.search {
  14 + min-width: 300px;
  15 + }
  16 + .status-disabled .noimage {
  17 + background:#d2d6de;
  18 + }
13 </style> 19 </style>
14 <div id="warmtips" class="alert alert-dismissable alert-danger hide"> 20 <div id="warmtips" class="alert alert-dismissable alert-danger hide">
15 <button type="button" class="close" data-dismiss="alert">×</button> 21 <button type="button" class="close" data-dismiss="alert">×</button>
@@ -158,10 +164,10 @@ @@ -158,10 +164,10 @@
158 </table> 164 </table>
159 </script> 165 </script>
160 <script id="itemtpl" type="text/html"> 166 <script id="itemtpl" type="text/html">
161 - <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 mt-4">  
162 <% var labelarr = ['primary', 'success', 'info', 'danger', 'warning']; %> 167 <% var labelarr = ['primary', 'success', 'info', 'danger', 'warning']; %>
163 <% var label = labelarr[item.id % 5]; %> 168 <% var label = labelarr[item.id % 5]; %>
164 <% var addon = typeof addons[item.name]!= 'undefined' ? addons[item.name] : null; %> 169 <% var addon = typeof addons[item.name]!= 'undefined' ? addons[item.name] : null; %>
  170 + <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 mt-4 status-<%=addon ? (addon.state==1?'enabled':'disabled') : 'uninstalled'%>">
165 <div class="thumbnail addon"> 171 <div class="thumbnail addon">
166 <%if(addon){%> 172 <%if(addon){%>
167 <span> 173 <span>
@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 <label for="icon" class="control-label col-xs-12 col-sm-2">{:__('Icon')}:</label> 31 <label for="icon" class="control-label col-xs-12 col-sm-2">{:__('Icon')}:</label>
32 <div class="col-xs-12 col-sm-8"> 32 <div class="col-xs-12 col-sm-8">
33 <div class="input-group input-groupp-md"> 33 <div class="input-group input-groupp-md">
34 - <input type="text" class="form-control" id="icon" name="row[icon]" value="fa fa-dot" /> 34 + <input type="text" class="form-control" id="icon" name="row[icon]" value="fa fa-circle-o" />
35 <a href="javascript:;" class="btn-search-icon input-group-addon">{:__('Search icon')}</a> 35 <a href="javascript:;" class="btn-search-icon input-group-addon">{:__('Search icon')}</a>
36 </div> 36 </div>
37 </div> 37 </div>
  1 +<style>
  2 + .bootstrap-table tr td .text-muted {color:#888;}
  3 +</style>
1 <div class="panel panel-default panel-intro"> 4 <div class="panel panel-default panel-intro">
2 {:build_heading()} 5 {:build_heading()}
3 6
@@ -6,7 +9,17 @@ @@ -6,7 +9,17 @@
6 <div class="tab-pane fade active in" id="one"> 9 <div class="tab-pane fade active in" id="one">
7 <div class="widget-body no-padding"> 10 <div class="widget-body no-padding">
8 <div id="toolbar" class="toolbar"> 11 <div id="toolbar" class="toolbar">
9 - {:build_toolbar()} 12 + <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
  13 + <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('auth/rule/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
  14 + <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('auth/rule/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
  15 + <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('auth/rule/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
  16 + <div class="dropdown btn-group {:$auth->check('auth/rule/multi')?'':'hide'}">
  17 + <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
  18 + <ul class="dropdown-menu text-left" role="menu">
  19 + <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>
  20 + <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>
  21 + </ul>
  22 + </div>
10 <a href="javascript:;" class="btn btn-danger btn-toggle-all"><i class="fa fa-plus"></i> {:__('Toggle all')}</a> 23 <a href="javascript:;" class="btn btn-danger btn-toggle-all"><i class="fa fa-plus"></i> {:__('Toggle all')}</a>
11 </div> 24 </div>
12 <table id="table" class="table table-bordered table-hover" 25 <table id="table" class="table table-bordered table-hover"
1 <div class="panel panel-default panel-intro"> 1 <div class="panel panel-default panel-intro">
2 <div class="panel-heading"> 2 <div class="panel-heading">
3 - {:build_heading()} 3 + {:build_heading(null,FALSE)}
4 <ul class="nav nav-tabs"> 4 <ul class="nav nav-tabs">
5 <li class="active"><a href="#all" data-toggle="tab">{:__('All')}</a></li> 5 <li class="active"><a href="#all" data-toggle="tab">{:__('All')}</a></li>
6 {foreach name="typeList" item="vo"} 6 {foreach name="typeList" item="vo"}
@@ -43,147 +43,15 @@ @@ -43,147 +43,15 @@
43 <li><a href="javascript:;" data-skin="skin-yellow-light" class="clearfix full-opacity-hover"><div><span style="display:block; width: 20%; float: left; height: 7px;" class="bg-yellow-active"></span><span class="bg-yellow" style="display:block; width: 80%; float: left; height: 7px;"></span></div><div><span style="display:block; width: 20%; float: left; height: 20px; background: #f9fafc;"></span><span style="display:block; width: 80%; float: left; height: 20px; background: #f4f5f7;"></span></div></a><p class="text-center no-margin" style="font-size: 12px;">Yellow Light</p></li> 43 <li><a href="javascript:;" data-skin="skin-yellow-light" class="clearfix full-opacity-hover"><div><span style="display:block; width: 20%; float: left; height: 7px;" class="bg-yellow-active"></span><span class="bg-yellow" style="display:block; width: 80%; float: left; height: 7px;"></span></div><div><span style="display:block; width: 20%; float: left; height: 20px; background: #f9fafc;"></span><span style="display:block; width: 80%; float: left; height: 20px; background: #f4f5f7;"></span></div></a><p class="text-center no-margin" style="font-size: 12px;">Yellow Light</p></li>
44 </ul> 44 </ul>
45 </div> 45 </div>
  46 + <!-- /.tab-pane -->
  47 + <!-- Home tab content -->
46 <div class="tab-pane" id="control-sidebar-home-tab"> 48 <div class="tab-pane" id="control-sidebar-home-tab">
47 - <h3 class="control-sidebar-heading">{:__('Recent Activity')}</h3>  
48 - <ul class="control-sidebar-menu">  
49 - <li>  
50 - <a href="javascript:void(0)">  
51 - <i class="menu-icon fa fa-birthday-cake bg-red"></i>  
52 -  
53 - <div class="menu-info">  
54 - <h4 class="control-sidebar-subheading">Langdon's Birthday</h4>  
55 -  
56 - <p>Will be 23 on April 24th</p>  
57 - </div>  
58 - </a>  
59 - </li>  
60 - <li>  
61 - <a href="javascript:void(0)">  
62 - <i class="menu-icon fa fa-user bg-yellow"></i>  
63 -  
64 - <div class="menu-info">  
65 - <h4 class="control-sidebar-subheading">Frodo Updated His Profile</h4>  
66 -  
67 - <p>New phone +1(800)555-1234</p>  
68 - </div>  
69 - </a>  
70 - </li>  
71 - <li>  
72 - <a href="javascript:void(0)">  
73 - <i class="menu-icon fa fa-envelope-o bg-light-blue"></i>  
74 -  
75 - <div class="menu-info">  
76 - <h4 class="control-sidebar-subheading">Nora Joined Mailing List</h4>  
77 -  
78 - <p>nora@example.com</p>  
79 - </div>  
80 - </a>  
81 - </li>  
82 - <li>  
83 - <a href="javascript:void(0)">  
84 - <i class="menu-icon fa fa-file-code-o bg-green"></i>  
85 -  
86 - <div class="menu-info">  
87 - <h4 class="control-sidebar-subheading">Cron Job 254 Executed</h4>  
88 -  
89 - <p>Execution time 5 seconds</p>  
90 - </div>  
91 - </a>  
92 - </li>  
93 - </ul>  
94 - <!-- /.control-sidebar-menu -->  
95 -  
96 - <h3 class="control-sidebar-heading">{:__('Tasks Progress')}</h3>  
97 - <ul class="control-sidebar-menu">  
98 - <li>  
99 - <a href="javascript:void(0)">  
100 - <h4 class="control-sidebar-subheading">  
101 - Custom Template Design  
102 - <span class="label label-danger pull-right">70%</span>  
103 - </h4>  
104 -  
105 - <div class="progress progress-xxs">  
106 - <div class="progress-bar progress-bar-danger" style="width: 70%"></div>  
107 - </div>  
108 - </a>  
109 - </li>  
110 - <li>  
111 - <a href="javascript:void(0)">  
112 - <h4 class="control-sidebar-subheading">  
113 - Update Resume  
114 - <span class="label label-success pull-right">95%</span>  
115 - </h4>  
116 -  
117 - <div class="progress progress-xxs">  
118 - <div class="progress-bar progress-bar-success" style="width: 95%"></div>  
119 - </div>  
120 - </a>  
121 - </li>  
122 - <li>  
123 - <a href="javascript:void(0)">  
124 - <h4 class="control-sidebar-subheading">  
125 - Laravel Integration  
126 - <span class="label label-warning pull-right">50%</span>  
127 - </h4>  
128 -  
129 - <div class="progress progress-xxs">  
130 - <div class="progress-bar progress-bar-warning" style="width: 50%"></div>  
131 - </div>  
132 - </a>  
133 - </li>  
134 - <li>  
135 - <a href="javascript:void(0)">  
136 - <h4 class="control-sidebar-subheading">  
137 - Back End Framework  
138 - <span class="label label-primary pull-right">68%</span>  
139 - </h4>  
140 -  
141 - <div class="progress progress-xxs">  
142 - <div class="progress-bar progress-bar-primary" style="width: 68%"></div>  
143 - </div>  
144 - </a>  
145 - </li>  
146 - </ul>  
147 - <!-- /.control-sidebar-menu -->  
148 - 49 + <h4 class="control-sidebar-heading">{:__('Home')}</h4>
149 </div> 50 </div>
150 <!-- /.tab-pane --> 51 <!-- /.tab-pane -->
151 - <!-- Stats tab content -->  
152 - <div class="tab-pane" id="control-sidebar-stats-tab">Stats Tab Content</div>  
153 - <!-- /.tab-pane -->  
154 <!-- Settings tab content --> 52 <!-- Settings tab content -->
155 <div class="tab-pane" id="control-sidebar-settings-tab"> 53 <div class="tab-pane" id="control-sidebar-settings-tab">
156 - <form method="post">  
157 - <h3 class="control-sidebar-heading">General Settings</h3>  
158 -  
159 - <!-- /.form-group -->  
160 -  
161 - <div class="form-group">  
162 - <label class="control-sidebar-subheading">  
163 - Allow mail redirect  
164 - <input type="checkbox" class="pull-right" checked>  
165 - </label>  
166 -  
167 - <p>  
168 - Other sets of options are available  
169 - </p>  
170 - </div>  
171 - <!-- /.form-group -->  
172 -  
173 - <div class="form-group">  
174 - <label class="control-sidebar-subheading">  
175 - Expose author name in posts  
176 - <input type="checkbox" class="pull-right" checked>  
177 - </label>  
178 -  
179 - <p>  
180 - Allow the user to show his name in blog posts  
181 - </p>  
182 - </div>  
183 - <!-- /.form-group -->  
184 -  
185 - <!-- /.form-group -->  
186 - </form> 54 + <h4 class="control-sidebar-heading">{:__('Setting')}</h4>
187 </div> 55 </div>
188 <!-- /.tab-pane --> 56 <!-- /.tab-pane -->
189 </div> 57 </div>
1 <!-- Logo --> 1 <!-- Logo -->
2 -<a href="javascript:;" class="logo"> 2 +<a href="javascript:;" class="logo hidden-xs">
3 <!-- 迷你模式下Logo的大小为50X50 --> 3 <!-- 迷你模式下Logo的大小为50X50 -->
4 <span class="logo-mini">{$site.name|mb_substr=0,4,'utf-8'|mb_strtoupper='utf-8'}</span> 4 <span class="logo-mini">{$site.name|mb_substr=0,4,'utf-8'|mb_strtoupper='utf-8'}</span>
5 <!-- 普通模式下Logo --> 5 <!-- 普通模式下Logo -->
@@ -29,10 +29,10 @@ @@ -29,10 +29,10 @@
29 <!--如果想始终显示子菜单,则给ul加上show-submenu类即可--> 29 <!--如果想始终显示子菜单,则给ul加上show-submenu类即可-->
30 <ul class="sidebar-menu"> 30 <ul class="sidebar-menu">
31 {$menulist} 31 {$menulist}
32 - <li class="header">{:__('Links')}</li>  
33 - <li><a href="http://doc.fastadmin.net" target="_blank"><i class="fa fa-list text-red"></i> <span>{:__('Docs')}</span></a></li>  
34 - <li><a href="http://forum.fastadmin.net" target="_blank"><i class="fa fa-comment text-yellow"></i> <span>{:__('Forum')}</span></a></li>  
35 - <li><a href="https://jq.qq.com/?_wv=1027&k=487PNBb" target="_blank"><i class="fa fa-qq text-aqua"></i> <span>{:__('QQ qun')}</span></a></li> 32 + <li class="header" data-rel="external">{:__('Links')}</li>
  33 + <li data-rel="external"><a href="http://doc.fastadmin.net" target="_blank"><i class="fa fa-list text-red"></i> <span>{:__('Docs')}</span></a></li>
  34 + <li data-rel="external"><a href="http://forum.fastadmin.net" target="_blank"><i class="fa fa-comment text-yellow"></i> <span>{:__('Forum')}</span></a></li>
  35 + <li data-rel="external"><a href="https://jq.qq.com/?_wv=1027&k=487PNBb" target="_blank"><i class="fa fa-qq text-aqua"></i> <span>{:__('QQ qun')}</span></a></li>
36 </ul> 36 </ul>
37 </section> 37 </section>
38 <!-- /.sidebar --> 38 <!-- /.sidebar -->
1 -<script src="__CDN__/assets/js/require.js" data-main="__CDN__/assets/js/require-backend{$Think.config.app_debug?'':'.min'}.js?v={$site.version}"></script>  
  1 +<script src="__CDN__/assets/js/require{$Think.config.app_debug?'':'.min'}.js" data-main="__CDN__/assets/js/require-backend{$Think.config.app_debug?'':'.min'}.js?v={$site.version}"></script>
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 <div class="tab-pane fade active in" id="one"> 6 <div class="tab-pane fade active in" id="one">
7 <div class="widget-body no-padding"> 7 <div class="widget-body no-padding">
8 <div id="toolbar" class="toolbar"> 8 <div id="toolbar" class="toolbar">
9 - {:build_toolbar()} 9 + {:build_toolbar('refresh,add,edit,del')}
10 <div class="dropdown btn-group {:$auth->check('user/rule/multi')?'':'hide'}"> 10 <div class="dropdown btn-group {:$auth->check('user/rule/multi')?'':'hide'}">
11 <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a> 11 <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
12 <ul class="dropdown-menu text-left" role="menu"> 12 <ul class="dropdown-menu text-left" role="menu">
1 <?php 1 <?php
  2 +
2 //配置文件 3 //配置文件
3 return [ 4 return [
4 - 5 + 'exception_handle' => '\\app\\api\\library\\ExceptionHandle',
5 ]; 6 ];
@@ -2,9 +2,9 @@ @@ -2,9 +2,9 @@
2 2
3 namespace app\api\controller; 3 namespace app\api\controller;
4 4
5 -use app\api\model\Area;  
6 use app\common\controller\Api; 5 use app\common\controller\Api;
7 -use fast\Version; 6 +use app\common\model\Area;
  7 +use app\common\model\Version;
8 use fast\Random; 8 use fast\Random;
9 use think\Config; 9 use think\Config;
10 10
  1 +<?php
  2 +
  3 +namespace app\api\library;
  4 +
  5 +use Exception;
  6 +use think\exception\Handle;
  7 +
  8 +/**
  9 + * 自定义API模块的错误显示
  10 + */
  11 +class ExceptionHandle extends Handle
  12 +{
  13 +
  14 + public function render(Exception $e)
  15 + {
  16 + // 在生产环境下返回code信息
  17 + if (!\think\Config::get('app_debug'))
  18 + {
  19 + $statuscode = $code = 500;
  20 + $msg = 'An error occurred';
  21 + // 验证异常
  22 + if ($e instanceof \think\exception\ValidateException)
  23 + {
  24 + $code = 0;
  25 + $statuscode = 200;
  26 + $msg = $e->getError();
  27 + }
  28 + // Http异常
  29 + if ($e instanceof \think\exception\HttpException)
  30 + {
  31 + $statuscode = $code = $e->getStatusCode();
  32 + }
  33 + return json(['code' => $code, 'msg' => $msg, 'time' => time(), 'data' => null], $statuscode);
  34 + }
  35 +
  36 + //其它此交由系统处理
  37 + return parent::render($e);
  38 + }
  39 +
  40 +}
@@ -16,4 +16,5 @@ return [ @@ -16,4 +16,5 @@ return [
16 'app\admin\command\Install', 16 'app\admin\command\Install',
17 'app\admin\command\Min', 17 'app\admin\command\Min',
18 'app\admin\command\Addon', 18 'app\admin\command\Addon',
  19 + 'app\admin\command\Api',
19 ]; 20 ];
@@ -22,7 +22,7 @@ if (!function_exists('__')) @@ -22,7 +22,7 @@ if (!function_exists('__'))
22 array_shift($vars); 22 array_shift($vars);
23 $lang = ''; 23 $lang = '';
24 } 24 }
25 - return think\Lang::get($name, $vars, $lang); 25 + return \think\Lang::get($name, $vars, $lang);
26 } 26 }
27 27
28 } 28 }
@@ -89,7 +89,7 @@ if (!function_exists('cdnurl')) @@ -89,7 +89,7 @@ if (!function_exists('cdnurl'))
89 */ 89 */
90 function cdnurl($url) 90 function cdnurl($url)
91 { 91 {
92 - return preg_match("/^https?:\/\/(.*)/i", $url) ? $url : think\Config::get('upload.cdnurl') . $url; 92 + return preg_match("/^https?:\/\/(.*)/i", $url) ? $url : \think\Config::get('upload.cdnurl') . $url;
93 } 93 }
94 94
95 } 95 }
@@ -208,7 +208,6 @@ if (!function_exists('mb_ucfirst')) @@ -208,7 +208,6 @@ if (!function_exists('mb_ucfirst'))
208 208
209 } 209 }
210 210
211 -  
212 if (!function_exists('addtion')) 211 if (!function_exists('addtion'))
213 { 212 {
214 213
@@ -300,3 +299,37 @@ if (!function_exists('addtion')) @@ -300,3 +299,37 @@ if (!function_exists('addtion'))
300 } 299 }
301 300
302 } 301 }
  302 +
  303 +if (!function_exists('var_export_short'))
  304 +{
  305 +
  306 + /**
  307 + * 返回打印数组结构
  308 + * @param string $var 数组
  309 + * @param string $indent 缩进字符
  310 + * @return string
  311 + */
  312 + function var_export_short($var, $indent = "")
  313 + {
  314 + switch (gettype($var))
  315 + {
  316 + case "string":
  317 + return '"' . addcslashes($var, "\\\$\"\r\n\t\v\f") . '"';
  318 + case "array":
  319 + $indexed = array_keys($var) === range(0, count($var) - 1);
  320 + $r = [];
  321 + foreach ($var as $key => $value)
  322 + {
  323 + $r[] = "$indent "
  324 + . ($indexed ? "" : var_export_short($key) . " => ")
  325 + . var_export_short($value, "$indent ");
  326 + }
  327 + return "[\n" . implode(",\n", $r) . "\n" . $indent . "]";
  328 + case "boolean":
  329 + return $var ? "TRUE" : "FALSE";
  330 + default:
  331 + return var_export($var, TRUE);
  332 + }
  333 + }
  334 +
  335 +}
@@ -52,6 +52,11 @@ class Common @@ -52,6 +52,11 @@ class Common
52 { 52 {
53 Config::set('app_trace', false); 53 Config::set('app_trace', false);
54 } 54 }
  55 + // 切换多语言
  56 + if (Config::get('lang_switch_on') && $request->get('lang'))
  57 + {
  58 + \think\Cookie::set('think_var', $request->get('lang'));
  59 + }
55 } 60 }
56 61
57 public function addonBegin(&$request) 62 public function addonBegin(&$request)
@@ -91,7 +91,7 @@ class Api @@ -91,7 +91,7 @@ class Api
91 $actionname = strtolower($this->request->action()); 91 $actionname = strtolower($this->request->action());
92 92
93 // token 93 // token
94 - $token = $this->request->request('token') ?: $this->request->cookie('token'); 94 + $token = $this->request->server('HTTP_TOKEN', $this->request->request('token', \think\Cookie::get('token')));
95 95
96 $path = str_replace('.', '/', $controllername) . '/' . $actionname; 96 $path = str_replace('.', '/', $controllername) . '/' . $actionname;
97 // 设置当前请求的URI 97 // 设置当前请求的URI
@@ -104,7 +104,7 @@ class Api @@ -104,7 +104,7 @@ class Api
104 //检测是否登录 104 //检测是否登录
105 if (!$this->auth->isLogin()) 105 if (!$this->auth->isLogin())
106 { 106 {
107 - $this->error(__('Please login first')); 107 + $this->error(__('Please login first'), null, 401);
108 } 108 }
109 // 判断是否需要验证权限 109 // 判断是否需要验证权限
110 if (!$this->auth->match($this->noNeedRight)) 110 if (!$this->auth->match($this->noNeedRight))
@@ -112,7 +112,7 @@ class Api @@ -112,7 +112,7 @@ class Api
112 // 判断控制器和方法判断是否有对应权限 112 // 判断控制器和方法判断是否有对应权限
113 if (!$this->auth->check($path)) 113 if (!$this->auth->check($path))
114 { 114 {
115 - $this->error(__('You have no permission')); 115 + $this->error(__('You have no permission'), null, 403);
116 } 116 }
117 } 117 }
118 } 118 }
@@ -141,38 +141,40 @@ class Api @@ -141,38 +141,40 @@ class Api
141 * 操作成功返回的数据 141 * 操作成功返回的数据
142 * @param string $msg 提示信息 142 * @param string $msg 提示信息
143 * @param mixed $data 要返回的数据 143 * @param mixed $data 要返回的数据
  144 + * @param int $code 错误码,默认为1
144 * @param string $type 输出类型 145 * @param string $type 输出类型
145 * @param array $header 发送的 Header 信息 146 * @param array $header 发送的 Header 信息
146 */ 147 */
147 - protected function success($msg = '', $data = '', $type = 'json', array $header = []) 148 + protected function success($msg = '', $data = null, $code = 1, $type = 'json', array $header = [])
148 { 149 {
149 - $this->result($data, 1, $msg, $type, $header); 150 + $this->result($msg, $data, $code, $type, $header);
150 } 151 }
151 152
152 /** 153 /**
153 * 操作失败返回的数据 154 * 操作失败返回的数据
154 * @param string $msg 提示信息 155 * @param string $msg 提示信息
155 * @param mixed $data 要返回的数据 156 * @param mixed $data 要返回的数据
  157 + * @param int $code 错误码,默认为0
156 * @param string $type 输出类型 158 * @param string $type 输出类型
157 * @param array $header 发送的 Header 信息 159 * @param array $header 发送的 Header 信息
158 */ 160 */
159 - protected function error($msg = '', $data = '', $type = 'json', array $header = []) 161 + protected function error($msg = '', $data = null, $code = 0, $type = 'json', array $header = [])
160 { 162 {
161 - $this->result($data, 0, $msg, $type, $header); 163 + $this->result($msg, $data, $code, $type, $header);
162 } 164 }
163 165
164 /** 166 /**
165 * 返回封装后的 API 数据到客户端 167 * 返回封装后的 API 数据到客户端
166 * @access protected 168 * @access protected
  169 + * @param mixed $msg 提示信息
167 * @param mixed $data 要返回的数据 170 * @param mixed $data 要返回的数据
168 * @param int $code 返回的 code 171 * @param int $code 返回的 code
169 - * @param mixed $msg 提示信息  
170 * @param string $type 返回数据格式 172 * @param string $type 返回数据格式
171 * @param array $header 发送的 Header 信息 173 * @param array $header 发送的 Header 信息
172 * @return void 174 * @return void
173 * @throws HttpResponseException 175 * @throws HttpResponseException
174 */ 176 */
175 - protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) 177 + protected function result($msg, $data = null, $code = 0, $type = 'json', array $header = [])
176 { 178 {
177 $result = [ 179 $result = [
178 'code' => $code, 180 'code' => $code,
@@ -181,17 +183,18 @@ class Api @@ -181,17 +183,18 @@ class Api
181 'data' => $data, 183 'data' => $data,
182 ]; 184 ];
183 $type = $type ?: $this->getResponseType(); 185 $type = $type ?: $this->getResponseType();
184 - $response = Response::create($result, $type)->header($header);  
185 -  
186 - throw new HttpResponseException($response); 186 + if (isset($header['statuscode']))
  187 + {
  188 + $code = $header['statuscode'];
  189 + unset($header['statuscode']);
187 } 190 }
188 -  
189 - /**  
190 - * 未找到请求的接口  
191 - */  
192 - public function _empty() 191 + else
193 { 192 {
194 - return $this->error('Api not found'); 193 + $code = $code >= 1000 ? 200 : $code;
  194 + }
  195 + $response = Response::create($result, $type, $code)->header($header);
  196 +
  197 + throw new HttpResponseException($response);
195 } 198 }
196 199
197 /** 200 /**
@@ -144,6 +144,7 @@ class Backend extends Controller @@ -144,6 +144,7 @@ class Backend extends Controller
144 $url = preg_replace_callback("/([\?|&]+)ref=addtabs(&?)/i", function($matches) { 144 $url = preg_replace_callback("/([\?|&]+)ref=addtabs(&?)/i", function($matches) {
145 return $matches[2] == '&' ? $matches[1] : ''; 145 return $matches[2] == '&' ? $matches[1] : '';
146 }, $this->request->url()); 146 }, $this->request->url());
  147 + $url = url($url, '', false);
147 $this->redirect('index/index', [], 302, ['referer' => $url]); 148 $this->redirect('index/index', [], 302, ['referer' => $url]);
148 exit; 149 exit;
149 } 150 }
@@ -290,6 +291,10 @@ class Backend extends Controller @@ -290,6 +291,10 @@ class Backend extends Controller
290 case '<=': 291 case '<=':
291 $where[] = [$k, $sym, intval($v)]; 292 $where[] = [$k, $sym, intval($v)];
292 break; 293 break;
  294 + case 'FINDIN':
  295 + case 'FIND_IN_SET':
  296 + $where[] = "FIND_IN_SET('{$v}', `{$k}`)";
  297 + break;
293 case 'IN': 298 case 'IN':
294 case 'IN(...)': 299 case 'IN(...)':
295 case 'NOT IN': 300 case 'NOT IN':
@@ -401,21 +406,21 @@ class Backend extends Controller @@ -401,21 +406,21 @@ class Backend extends Controller
401 //搜索关键词,客户端输入以空格分开,这里接收为数组 406 //搜索关键词,客户端输入以空格分开,这里接收为数组
402 $word = (array) $this->request->request("q_word/a"); 407 $word = (array) $this->request->request("q_word/a");
403 //当前页 408 //当前页
404 - $page = $this->request->request("page"); 409 + $page = $this->request->request("pageNumber");
405 //分页大小 410 //分页大小
406 - $pagesize = $this->request->request("per_page"); 411 + $pagesize = $this->request->request("pageSize");
407 //搜索条件 412 //搜索条件
408 - $andor = $this->request->request("and_or"); 413 + $andor = $this->request->request("andOr");
409 //排序方式 414 //排序方式
410 - $orderby = (array) $this->request->request("order_by/a"); 415 + $orderby = (array) $this->request->request("orderBy/a");
411 //显示的字段 416 //显示的字段
412 - $field = $this->request->request("field"); 417 + $field = $this->request->request("showField");
413 //主键 418 //主键
414 - $primarykey = $this->request->request("pkey_name"); 419 + $primarykey = $this->request->request("keyField");
415 //主键值 420 //主键值
416 - $primaryvalue = $this->request->request("pkey_value"); 421 + $primaryvalue = $this->request->request("keyValue");
417 //搜索字段 422 //搜索字段
418 - $searchfield = (array) $this->request->request("search_field/a"); 423 + $searchfield = (array) $this->request->request("searchField/a");
419 //自定义搜索条件 424 //自定义搜索条件
420 $custom = (array) $this->request->request("custom/a"); 425 $custom = (array) $this->request->request("custom/a");
421 $order = []; 426 $order = [];
@@ -58,8 +58,7 @@ class Frontend extends Controller @@ -58,8 +58,7 @@ class Frontend extends Controller
58 $actionname = strtolower($this->request->action()); 58 $actionname = strtolower($this->request->action());
59 59
60 // token 60 // token
61 - $token = $this->request->request('token');  
62 - $token = $token ? $token : \think\Cookie::get('token'); 61 + $token = $this->request->server('HTTP_TOKEN', $this->request->request('token', \think\Cookie::get('token')));
63 62
64 $path = str_replace('.', '/', $controllername) . '/' . $actionname; 63 $path = str_replace('.', '/', $controllername) . '/' . $actionname;
65 // 设置当前请求的URI 64 // 设置当前请求的URI
@@ -6,4 +6,97 @@ return [ @@ -6,4 +6,97 @@ return [
6 'addon controller %s not found' => '插件控制器未找到', 6 'addon controller %s not found' => '插件控制器未找到',
7 'addon action %s not found' => '插件控制器方法未找到', 7 'addon action %s not found' => '插件控制器方法未找到',
8 'addon can not be empty' => '插件不能为空', 8 'addon can not be empty' => '插件不能为空',
  9 + 'Keep login' => '保持会话',
  10 + 'Forgot password' => '忘记密码?',
  11 + 'Sign in' => '登入',
  12 + 'Username' => '用户名',
  13 + 'User id' => '会员ID',
  14 + 'Username' => '用户名',
  15 + 'Nickname' => '昵称',
  16 + 'Password' => '密码',
  17 + 'Sign up' => '注 册',
  18 + 'Sign in' => '登 录',
  19 + 'Sign out' => '注 销',
  20 + 'Guest' => '游客',
  21 + 'Welcome' => '%s,你好!',
  22 + 'Add' => '添加',
  23 + 'Edit' => '编辑',
  24 + 'Delete' => '删除',
  25 + 'Move' => '移动',
  26 + 'Name' => '名称',
  27 + 'Status' => '状态',
  28 + 'Weigh' => '权重',
  29 + 'Operate' => '操作',
  30 + 'Warning' => '温馨提示',
  31 + 'Default' => '默认',
  32 + 'Article' => '文章',
  33 + 'Page' => '单页',
  34 + 'OK' => '确定',
  35 + 'Cancel' => '取消',
  36 + 'Loading' => '加载中',
  37 + 'More' => '更多',
  38 + 'Normal' => '正常',
  39 + 'Hidden' => '隐藏',
  40 + 'Submit' => '提交',
  41 + 'Reset' => '重置',
  42 + 'Execute' => '执行',
  43 + 'Close' => '关闭',
  44 + 'Search' => '搜索',
  45 + 'Refresh' => '刷新',
  46 + 'First' => '首页',
  47 + 'Previous' => '上一页',
  48 + 'Next' => '下一页',
  49 + 'Last' => '末页',
  50 + 'None' => '无',
  51 + 'Home' => '主页',
  52 + 'Online' => '在线',
  53 + 'Logout' => '注销',
  54 + 'Profile' => '个人资料',
  55 + 'Index' => '首页',
  56 + 'Hot' => '热门',
  57 + 'Recommend' => '推荐',
  58 + 'Dashboard' => '控制台',
  59 + 'Code' => '编号',
  60 + 'Message' => '内容',
  61 + 'Line' => '行号',
  62 + 'File' => '文件',
  63 + 'Menu' => '菜单',
  64 + 'Name' => '名称',
  65 + 'Weigh' => '权重',
  66 + 'Type' => '类型',
  67 + 'Title' => '标题',
  68 + 'Content' => '内容',
  69 + 'Status' => '状态',
  70 + 'Operate' => '操作',
  71 + 'Append' => '追加',
  72 + 'Memo' => '备注',
  73 + 'Parent' => '父级',
  74 + 'Params' => '参数',
  75 + 'Permission' => '权限',
  76 + 'Begin time' => '开始时间',
  77 + 'End time' => '结束时间',
  78 + 'Create time' => '创建时间',
  79 + 'Flag' => '标志',
  80 + 'Home' => '首页',
  81 + 'Store' => '插件市场',
  82 + 'Services' => '服务',
  83 + 'Download' => '下载',
  84 + 'Demo' => '演示',
  85 + 'Donation' => '捐赠',
  86 + 'Forum' => '社区',
  87 + 'Docs' => '文档',
  88 + 'Please login first' => '请登录后再操作',
  89 + 'Send verification code' => '发送验证码',
  90 + 'Redirect now' => '立即跳转',
  91 + 'Operation completed' => '操作成功!',
  92 + 'Operation failed' => '操作失败!',
  93 + 'Unknown data format' => '未知的数据格式!',
  94 + 'Network error' => '网络错误!',
  95 + 'Advanced search' => '高级搜索',
  96 + 'Invalid parameters' => '未知参数',
  97 + 'No results were found' => '记录未找到',
  98 + 'Parameter %s can not be empty' => '参数%s不能为空',
  99 + 'You have no permission' => '你没有权限访问',
  100 + 'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
  101 + 'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
9 ]; 102 ];
@@ -101,6 +101,28 @@ class Menu @@ -101,6 +101,28 @@ class Menu
101 } 101 }
102 102
103 /** 103 /**
  104 + * 导出指定名称的菜单规则
  105 + * @param string $name
  106 + * @return array
  107 + */
  108 + public static function export($name)
  109 + {
  110 + $ids = self::getAuthRuleIdsByName($name);
  111 + if (!$ids)
  112 + {
  113 + return [];
  114 + }
  115 + $menuList = [];
  116 + $menu = AuthRule::getByName($name);
  117 + if ($menu)
  118 + {
  119 + $ruleList = collection(AuthRule::where('id', 'in', $ids)->select())->toArray();
  120 + $menuList = Tree::instance()->init($ruleList)->getTreeArray($menu['id']);
  121 + }
  122 + return $menuList;
  123 + }
  124 +
  125 + /**
104 * 根据名称获取规则IDS 126 * 根据名称获取规则IDS
105 * @param string $name 127 * @param string $name
106 * @return array 128 * @return array
@@ -112,7 +134,7 @@ class Menu @@ -112,7 +134,7 @@ class Menu
112 if ($menu) 134 if ($menu)
113 { 135 {
114 // 必须将结果集转换为数组 136 // 必须将结果集转换为数组
115 - $ruleList = collection(model('AuthRule')->order('weigh', 'desc')->field('id,pid,name')->select())->toArray(); 137 + $ruleList = collection(AuthRule::order('weigh', 'desc')->field('id,pid,name')->select())->toArray();
116 // 构造菜单数据 138 // 构造菜单数据
117 $ids = Tree::instance()->init($ruleList)->getChildrenIds($menu['id'], true); 139 $ids = Tree::instance()->init($ruleList)->getChildrenIds($menu['id'], true);
118 } 140 }
1 <?php 1 <?php
2 2
3 -namespace app\api\model; 3 +namespace app\common\model;
4 4
5 use think\Cache; 5 use think\Cache;
6 use think\Model; 6 use think\Model;
@@ -11,7 +11,7 @@ class ScoreLog Extends Model @@ -11,7 +11,7 @@ class ScoreLog Extends Model
11 { 11 {
12 12
13 // 表名 13 // 表名
14 - protected $name = 'score_log'; 14 + protected $name = 'user_score_log';
15 // 开启自动写入时间戳字段 15 // 开启自动写入时间戳字段
16 protected $autoWriteTimestamp = 'int'; 16 protected $autoWriteTimestamp = 'int';
17 // 定义时间戳字段名 17 // 定义时间戳字段名
  1 +<?php
  2 +
  3 +namespace app\common\model;
  4 +
  5 +use think\Model;
  6 +
  7 +class Version extends Model
  8 +{
  9 +
  10 + // 开启自动写入时间戳字段
  11 + protected $autoWriteTimestamp = 'int';
  12 + // 定义时间戳字段名
  13 + protected $createTime = 'createtime';
  14 + protected $updateTime = 'updatetime';
  15 + // 定义字段类型
  16 + protected $type = [
  17 + ];
  18 +
  19 + /**
  20 + * 检测版本号
  21 + *
  22 + * @param string $version 客户端版本号
  23 + * @return array
  24 + */
  25 + public static function check($version)
  26 + {
  27 + $versionlist = self::where('status', 'normal')->cache('__version__')->order('weigh desc,id desc')->select();
  28 + foreach ($versionlist as $k => $v)
  29 + {
  30 + // 版本正常且新版本号不等于验证的版本号且找到匹配的旧版本
  31 + if ($v['status'] == 'normal' && $v['newversion'] !== $version && \fast\Version::check($version, $v['oldversion']))
  32 + {
  33 + $updateversion = $v;
  34 + break;
  35 + }
  36 + }
  37 + if (isset($updateversion))
  38 + {
  39 + $search = ['{version}', '{newversion}', '{downloadurl}', '{url}', '{packagesize}'];
  40 + $replace = [$version, $updateversion['newversion'], $updateversion['downloadurl'], $updateversion['downloadurl'], $updateversion['packagesize']];
  41 + $upgradetext = str_replace($search, $replace, $updateversion['content']);
  42 + return [
  43 + "enforce" => $updateversion['enforce'],
  44 + "version" => $version,
  45 + "newversion" => $updateversion['newversion'],
  46 + "downloadurl" => $updateversion['downloadurl'],
  47 + "packagesize" => $updateversion['packagesize'],
  48 + "upgradetext" => $upgradetext
  49 + ];
  50 + }
  51 + return NULL;
  52 + }
  53 +
  54 +}
@@ -253,7 +253,7 @@ return [ @@ -253,7 +253,7 @@ return [
253 //自动检测更新 253 //自动检测更新
254 'checkupdate' => false, 254 'checkupdate' => false,
255 //版本号 255 //版本号
256 - 'version' => '1.0.0.20180227_beta', 256 + 'version' => '1.0.0.20180308_beta',
257 'api_url' => 'http://api.fastadmin.net', 257 'api_url' => 'http://api.fastadmin.net',
258 ], 258 ],
259 ]; 259 ];
@@ -97,7 +97,7 @@ return [ @@ -97,7 +97,7 @@ return [
97 'Forum' => '社区', 97 'Forum' => '社区',
98 'Docs' => '文档', 98 'Docs' => '文档',
99 'Please login first' => '请登录后再操作', 99 'Please login first' => '请登录后再操作',
100 - 'Send verification code' => '发验证码', 100 + 'Send verification code' => '发验证码',
101 'Redirect now' => '立即跳转', 101 'Redirect now' => '立即跳转',
102 'Operation completed' => '操作成功!', 102 'Operation completed' => '操作成功!',
103 'Operation failed' => '操作失败!', 103 'Operation failed' => '操作失败!',
1 -<script src="__CDN__/assets/js/require.js" data-main="__CDN__/assets/js/require-frontend{$Think.config.app_debug?'':'.min'}.js?v={$site.version}"></script>  
  1 +<script src="__CDN__/assets/js/require{$Think.config.app_debug?'':'.min'}.js" data-main="__CDN__/assets/js/require-frontend{$Think.config.app_debug?'':'.min'}.js?v={$site.version}"></script>
@@ -174,11 +174,9 @@ @@ -174,11 +174,9 @@
174 $("#mainNav").toggleClass("affix", $(window).height() - $(window).scrollTop() <= 50); 174 $("#mainNav").toggleClass("affix", $(window).height() - $(window).scrollTop() <= 50);
175 }); 175 });
176 176
177 -  
178 //发送版本统计信息 177 //发送版本统计信息
179 try { 178 try {
180 var installed = localStorage.getItem("installed"); 179 var installed = localStorage.getItem("installed");
181 - console.log(installed);  
182 if (!installed) { 180 if (!installed) {
183 $.ajax({ 181 $.ajax({
184 url: "{$Think.config.fastadmin.api_url}/statistics/installed", 182 url: "{$Think.config.fastadmin.api_url}/statistics/installed",
@@ -8,37 +8,31 @@ @@ -8,37 +8,31 @@
8 "dependencies": { 8 "dependencies": {
9 "jquery": "^2.1.4", 9 "jquery": "^2.1.4",
10 "bootstrap": "^3.3.7", 10 "bootstrap": "^3.3.7",
11 - "font-awesome": "fontawesome#^4.6.1", 11 + "font-awesome": "^4.6.1",
12 "bootstrap-table": "^1.11.0", 12 "bootstrap-table": "^1.11.0",
13 - "layer": "*", 13 + "layer": "^3.0",
14 "jstree": "^3.3.2", 14 "jstree": "^3.3.2",
15 - "summernote": "^0.8.2",  
16 - "jquery-pjax": "^1.9.6",  
17 "moment": "^2.15.2", 15 "moment": "^2.15.2",
18 "plupload": "^2.2.0", 16 "plupload": "^2.2.0",
19 "toastr": "^2.1.3", 17 "toastr": "^2.1.3",
20 - "devbridge-autocomplete": "^1.2.26",  
21 - "jcrop": "jcrop#^2.0.4",  
22 - "jquery-qrcode": "*", 18 + "jcrop": "^2.0.4",
23 "eonasdan-bootstrap-datetimepicker": "^4.17.43", 19 "eonasdan-bootstrap-datetimepicker": "^4.17.43",
24 "bootstrap-select": "^1.11.2", 20 "bootstrap-select": "^1.11.2",
25 "require-css": "^0.1.8", 21 "require-css": "^0.1.8",
26 "less": "^2.7.1", 22 "less": "^2.7.1",
27 "tableExport.jquery.plugin": "^1.9.0", 23 "tableExport.jquery.plugin": "^1.9.0",
28 - "jquery-slimscroll": "slimscroll#^1.3.8", 24 + "jquery-slimscroll": "^1.3.8",
29 "jquery.cookie": "^1.4.1", 25 "jquery.cookie": "^1.4.1",
30 "Sortable": "^1.5.0", 26 "Sortable": "^1.5.0",
31 "nice-validator": "^1.1.1", 27 "nice-validator": "^1.1.1",
32 "art-template": "^3.0.1", 28 "art-template": "^3.0.1",
33 "requirejs-plugins": "^1.0.3", 29 "requirejs-plugins": "^1.0.3",
34 "bootstrap-daterangepicker": "^2.1.25", 30 "bootstrap-daterangepicker": "^2.1.25",
35 - "city-picker":"^1.1.0"  
36 - },  
37 - "devDependencies": {  
38 - "dragsort": "https://github.com/karsonzhang/dragsort.git",  
39 - "jquery-addtabs": "https://github.com/karsonzhang/jquery-addtabs.git",  
40 - "jquery-cxselect": "https://github.com/karsonzhang/cxSelect.git",  
41 - "selectpage": "https://github.com/karsonzhang/selectpage.git" 31 + "city-picker": "^1.1.0",
  32 + "fastadmin-cxselect": "~1.4.0",
  33 + "fastadmin-dragsort": "~1.0.0",
  34 + "fastadmin-addtabs": "~1.0.0",
  35 + "fastadmin-selectpage": "~1.0.0"
42 }, 36 },
43 "resolutions": { 37 "resolutions": {
44 "jspdf": "1.1.239 || 1.3.2" 38 "jspdf": "1.1.239 || 1.3.2"
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 "license": "Apache-2.0", 10 "license": "Apache-2.0",
11 "authors": [ 11 "authors": [
12 { 12 {
13 - "name": "karson", 13 + "name": "Karson",
14 "email": "karsonzhang@163.com" 14 "email": "karsonzhang@163.com"
15 } 15 }
16 ], 16 ],
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 "topthink/think-captcha": "^1.0", 22 "topthink/think-captcha": "^1.0",
23 "mtdowling/cron-expression": "^1.2", 23 "mtdowling/cron-expression": "^1.2",
24 "phpmailer/phpmailer": "^5.2", 24 "phpmailer/phpmailer": "^5.2",
25 - "karsonzhang/fastadmin-addons": "dev-master", 25 + "karsonzhang/fastadmin-addons": "~1.1.0",
26 "overtrue/pinyin": "~3.0", 26 "overtrue/pinyin": "~3.0",
27 "phpoffice/phpexcel": "^1.8" 27 "phpoffice/phpexcel": "^1.8"
28 }, 28 },
此 diff 太大无法显示。
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 @import url("../libs/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css"); 9 @import url("../libs/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css");
10 @import url("../libs/bootstrap-daterangepicker/daterangepicker.css"); 10 @import url("../libs/bootstrap-daterangepicker/daterangepicker.css");
11 @import url("../libs/nice-validator/dist/jquery.validator.css"); 11 @import url("../libs/nice-validator/dist/jquery.validator.css");
12 -@import url("../libs/selectpage/selectpage.css"); 12 +@import url("../libs/fastadmin-selectpage/selectpage.css");
13 body { 13 body {
14 background: #f1f4f6; 14 background: #f1f4f6;
15 } 15 }
@@ -44,9 +44,6 @@ body.is-dialog { @@ -44,9 +44,6 @@ body.is-dialog {
44 position: absolute; 44 position: absolute;
45 right: 0; 45 right: 0;
46 } 46 }
47 -.note-dialog .modal {  
48 - z-index: 1060;  
49 -}  
50 .bootstrap-dialog .modal-dialog { 47 .bootstrap-dialog .modal-dialog {
51 /*width: 70%;*/ 48 /*width: 70%;*/
52 max-width: 885px; 49 max-width: 885px;
@@ -645,13 +642,16 @@ form.form-horizontal .control-label { @@ -645,13 +642,16 @@ form.form-horizontal .control-label {
645 overflow: hidden; 642 overflow: hidden;
646 } 643 }
647 .layui-layer-fast .layui-layer-btn a { 644 .layui-layer-fast .layui-layer-btn a {
648 - background-color: #95a5a6!important;  
649 - border-color: #95a5a6!important; 645 + background-color: #95a5a6;
  646 + border-color: #95a5a6;
650 color: #fff!important; 647 color: #fff!important;
  648 + height: 31px;
  649 + margin-top: 0;
  650 + border: 1px solid transparent;
651 } 651 }
652 .layui-layer-fast .layui-layer-btn .layui-layer-btn0 { 652 .layui-layer-fast .layui-layer-btn .layui-layer-btn0 {
653 - background-color: #18bc9c!important;  
654 - border-color: #18bc9c!important; 653 + background-color: #18bc9c;
  654 + border-color: #18bc9c;
655 } 655 }
656 .layui-layer-fast .layui-layer-footer { 656 .layui-layer-fast .layui-layer-footer {
657 padding: 8px 20px; 657 padding: 8px 20px;
@@ -731,6 +731,14 @@ form.form-horizontal .control-label { @@ -731,6 +731,14 @@ form.form-horizontal .control-label {
731 .n-bootstrap .input-group > .n-right { 731 .n-bootstrap .input-group > .n-right {
732 position: absolute; 732 position: absolute;
733 } 733 }
  734 +@media (min-width: 564px) {
  735 + body.is-dialog .daterangepicker {
  736 + min-width: 130px;
  737 + }
  738 + body.is-dialog .daterangepicker .ranges ul {
  739 + width: 130px;
  740 + }
  741 +}
734 /*手机版样式*/ 742 /*手机版样式*/
735 @media (max-width: 480px) { 743 @media (max-width: 480px) {
736 .nav-addtabs { 744 .nav-addtabs {
@@ -739,6 +747,10 @@ form.form-horizontal .control-label { @@ -739,6 +747,10 @@ form.form-horizontal .control-label {
739 .fixed-table-toolbar .columns-right.btn-group { 747 .fixed-table-toolbar .columns-right.btn-group {
740 display: none; 748 display: none;
741 } 749 }
  750 + .fixed .content-wrapper,
  751 + .fixed .right-side {
  752 + padding-top: 50px;
  753 + }
742 } 754 }
743 /*平板样式*/ 755 /*平板样式*/
744 @media (max-width: 768px) { 756 @media (max-width: 768px) {
@@ -41,14 +41,6 @@ body { @@ -41,14 +41,6 @@ body {
41 -moz-box-shadow: none; 41 -moz-box-shadow: none;
42 box-shadow: none; 42 box-shadow: none;
43 } 43 }
44 -.layui-layer-fast {  
45 - -webkit-animation-fill-mode: both;  
46 - animation-fill-mode: both;  
47 - -webkit-animation-duration: .3s;  
48 - animation-duration: .3s;  
49 - -webkit-animation-name: layer-bounceIn;  
50 - animation-name: layer-bounceIn;  
51 -}  
52 /*修复nice-validator和summernote的编辑框冲突*/ 44 /*修复nice-validator和summernote的编辑框冲突*/
53 .nice-validator .note-editor .note-editing-area .note-editable { 45 .nice-validator .note-editor .note-editing-area .note-editable {
54 display: inherit; 46 display: inherit;
@@ -317,6 +309,8 @@ footer.footer { @@ -317,6 +309,8 @@ footer.footer {
317 color: #aaa; 309 color: #aaa;
318 background: #555; 310 background: #555;
319 margin-top: 25px; 311 margin-top: 25px;
  312 + position: fixed;
  313 + bottom: 0;
320 } 314 }
321 footer.footer ul { 315 footer.footer ul {
322 margin: 60px 0 30px 0; 316 margin: 60px 0 30px 0;
  1 +define(['backend'], function (Backend) {
  2 +
  3 +});
@@ -99,6 +99,9 @@ define(['fast', 'moment'], function (Fast, Moment) { @@ -99,6 +99,9 @@ define(['fast', 'moment'], function (Fast, Moment) {
99 url = url.replace(/\{ids\}/g, ids); 99 url = url.replace(/\{ids\}/g, ids);
100 } 100 }
101 return url; 101 return url;
  102 + },
  103 + refreshmenu: function () {
  104 + top.window.$(".sidebar-menu").trigger("refresh");
102 } 105 }
103 }, 106 },
104 init: function () { 107 init: function () {
@@ -56,6 +56,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -56,6 +56,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
56 commonSearch: false, 56 commonSearch: false,
57 searchFormVisible: false, 57 searchFormVisible: false,
58 pageSize: 12, 58 pageSize: 12,
  59 + pagination: false,
59 queryParams: function (params) { 60 queryParams: function (params) {
60 var filter = params.filter ? JSON.parse(params.filter) : {}; 61 var filter = params.filter ? JSON.parse(params.filter) : {};
61 var op = params.op ? JSON.parse(params.op) : {}; 62 var op = params.op ? JSON.parse(params.op) : {};
@@ -111,15 +112,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -111,15 +112,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
111 $(".btn-switch,.btn-userinfo").addClass("disabled"); 112 $(".btn-switch,.btn-userinfo").addClass("disabled");
112 } 113 }
113 114
  115 + // 离线安装
114 require(['upload'], function (Upload) { 116 require(['upload'], function (Upload) {
115 Upload.api.plupload("#plupload-addon", function (data, ret) { 117 Upload.api.plupload("#plupload-addon", function (data, ret) {
116 Config['addons'][data.addon.name] = data.addon; 118 Config['addons'][data.addon.name] = data.addon;
117 - $('.btn-refresh').trigger('click');  
118 Toastr.success(ret.msg); 119 Toastr.success(ret.msg);
  120 + operate(data.addon.name, 'enable', false);
119 }); 121 });
120 }); 122 });
121 123
122 - //查看插件首页 124 + // 查看插件首页
123 $(document).on("click", ".btn-addonindex", function () { 125 $(document).on("click", ".btn-addonindex", function () {
124 if ($(this).attr("href") == 'javascript:;') { 126 if ($(this).attr("href") == 'javascript:;') {
125 Layer.msg(__('Not installed tips'), {icon: 7}); 127 Layer.msg(__('Not installed tips'), {icon: 7});
@@ -128,12 +130,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -128,12 +130,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
128 return false; 130 return false;
129 } 131 }
130 }); 132 });
131 - //切换URL 133 +
  134 + // 切换URL
132 $(document).on("click", ".btn-switch", function () { 135 $(document).on("click", ".btn-switch", function () {
133 $(".btn-switch").removeClass("active"); 136 $(".btn-switch").removeClass("active");
134 $(this).addClass("active"); 137 $(this).addClass("active");
135 table.bootstrapTable('refresh', {url: $(this).data("url"), pageNumber: 1}); 138 table.bootstrapTable('refresh', {url: $(this).data("url"), pageNumber: 1});
136 }); 139 });
  140 +
137 // 会员信息 141 // 会员信息
138 $(document).on("click", ".btn-userinfo", function () { 142 $(document).on("click", ".btn-userinfo", function () {
139 var userinfo = Controller.api.userinfo.get(); 143 var userinfo = Controller.api.userinfo.get();
@@ -195,15 +199,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -195,15 +199,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
195 } 199 }
196 }); 200 });
197 201
198 - // 点击安装  
199 - $(document).on("click", ".btn-install", function () {  
200 - var that = this;  
201 - var name = $(this).closest(".operate").data("name");  
202 - var version = $(this).data("version"); 202 + var install = function (name, version, force) {
203 var userinfo = Controller.api.userinfo.get(); 203 var userinfo = Controller.api.userinfo.get();
204 var uid = userinfo ? userinfo.id : 0; 204 var uid = userinfo ? userinfo.id : 0;
205 var token = userinfo ? userinfo.token : ''; 205 var token = userinfo ? userinfo.token : '';
206 - var install = function (name, force) {  
207 Fast.api.ajax({ 206 Fast.api.ajax({
208 url: 'addon/install', 207 url: 'addon/install',
209 data: {name: name, force: force ? 1 : 0, uid: uid, token: token, version: version, faversion: Config.fastadmin.version} 208 data: {name: name, force: force ? 1 : 0, uid: uid, token: token, version: version, faversion: Config.fastadmin.version}
@@ -229,6 +228,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -229,6 +228,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
229 } 228 }
230 }); 229 });
231 $('.btn-refresh').trigger('click'); 230 $('.btn-refresh').trigger('click');
  231 + Fast.api.refreshmenu();
232 }, function (data, ret) { 232 }, function (data, ret) {
233 //如果是需要购买的插件则弹出二维码提示 233 //如果是需要购买的插件则弹出二维码提示
234 if (ret && ret.code === -1) { 234 if (ret && ret.code === -1) {
@@ -280,26 +280,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -280,26 +280,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
280 return false; 280 return false;
281 }); 281 });
282 }; 282 };
283 - if ($(that).data("type") !== 'free') {  
284 - if (parseInt(uid) === 0) {  
285 - return Layer.alert(__('Not login tips'), {  
286 - title: __('Warning'),  
287 - btn: [__('Login now'), __('Continue install')],  
288 - yes: function (index, layero) {  
289 - $(".btn-userinfo").trigger("click");  
290 - },  
291 - btn2: function () {  
292 - install(name, false);  
293 - }  
294 - });  
295 - }  
296 - }  
297 - install(name, false);  
298 - });  
299 283
300 - //点击卸载  
301 - $(document).on("click", ".btn-uninstall", function () {  
302 - var name = $(this).closest(".operate").data("name");  
303 var uninstall = function (name, force) { 284 var uninstall = function (name, force) {
304 Fast.api.ajax({ 285 Fast.api.ajax({
305 url: 'addon/uninstall', 286 url: 'addon/uninstall',
@@ -308,6 +289,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -308,6 +289,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
308 delete Config['addons'][name]; 289 delete Config['addons'][name];
309 Layer.closeAll(); 290 Layer.closeAll();
310 $('.btn-refresh').trigger('click'); 291 $('.btn-refresh').trigger('click');
  292 + Fast.api.refreshmenu();
311 }, function (data, ret) { 293 }, function (data, ret) {
312 if (ret && ret.code === -3) { 294 if (ret && ret.code === -3) {
313 //插件目录发现影响全局的文件 295 //插件目录发现影响全局的文件
@@ -331,21 +313,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -331,21 +313,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
331 return false; 313 return false;
332 }); 314 });
333 }; 315 };
334 - Layer.confirm(__('Uninstall tips'), function () {  
335 - uninstall(name, false);  
336 - });  
337 - });  
338 -  
339 - //点击配置  
340 - $(document).on("click", ".btn-config", function () {  
341 - var name = $(this).closest(".operate").data("name");  
342 - Fast.api.open("addon/config?name=" + name, __('Setting'));  
343 - });  
344 316
345 - //点击启用/禁用  
346 - $(document).on("click", ".btn-enable,.btn-disable", function () {  
347 - var name = $(this).closest(".operate").data("name");  
348 - var action = $(this).data("action");  
349 var operate = function (name, action, force) { 317 var operate = function (name, action, force) {
350 Fast.api.ajax({ 318 Fast.api.ajax({
351 url: 'addon/state', 319 url: 'addon/state',
@@ -355,6 +323,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -355,6 +323,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
355 addon.state = action === 'enable' ? 1 : 0; 323 addon.state = action === 'enable' ? 1 : 0;
356 Layer.closeAll(); 324 Layer.closeAll();
357 $('.btn-refresh').trigger('click'); 325 $('.btn-refresh').trigger('click');
  326 + Fast.api.refreshmenu();
358 }, function (data, ret) { 327 }, function (data, ret) {
359 if (ret && ret.code === -3) { 328 if (ret && ret.code === -3) {
360 //插件目录发现影响全局的文件 329 //插件目录发现影响全局的文件
@@ -378,21 +347,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -378,21 +347,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
378 return false; 347 return false;
379 }); 348 });
380 }; 349 };
381 - operate(name, action, false);  
382 - });  
383 350
384 - //点击升级  
385 - $(document).on("click", ".btn-upgrade", function () {  
386 - if ($(this).closest(".operate").find("a.btn-disable").size() > 0) {  
387 - Layer.alert(__('Please disable addon first'), {icon: 7});  
388 - return false;  
389 - }  
390 - var name = $(this).closest(".operate").data("name");  
391 - var version = $(this).data("version"); 351 + var upgrade = function (name, version) {
392 var userinfo = Controller.api.userinfo.get(); 352 var userinfo = Controller.api.userinfo.get();
393 var uid = userinfo ? userinfo.id : 0; 353 var uid = userinfo ? userinfo.id : 0;
394 var token = userinfo ? userinfo.token : ''; 354 var token = userinfo ? userinfo.token : '';
395 - var upgrade = function (name) {  
396 Fast.api.ajax({ 355 Fast.api.ajax({
397 url: 'addon/upgrade', 356 url: 'addon/upgrade',
398 data: {name: name, uid: uid, token: token, version: version, faversion: Config.fastadmin.version} 357 data: {name: name, uid: uid, token: token, version: version, faversion: Config.fastadmin.version}
@@ -400,13 +359,71 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -400,13 +359,71 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
400 Config['addons'][name].version = version; 359 Config['addons'][name].version = version;
401 Layer.closeAll(); 360 Layer.closeAll();
402 $('.btn-refresh').trigger('click'); 361 $('.btn-refresh').trigger('click');
  362 + Fast.api.refreshmenu();
403 }, function (data, ret) { 363 }, function (data, ret) {
404 Layer.alert(ret.msg); 364 Layer.alert(ret.msg);
405 return false; 365 return false;
406 }); 366 });
407 }; 367 };
  368 +
  369 + // 点击安装
  370 + $(document).on("click", ".btn-install", function () {
  371 + var that = this;
  372 + var name = $(this).closest(".operate").data("name");
  373 + var version = $(this).data("version");
  374 +
  375 + var userinfo = Controller.api.userinfo.get();
  376 + var uid = userinfo ? userinfo.id : 0;
  377 +
  378 + if ($(that).data("type") !== 'free') {
  379 + if (parseInt(uid) === 0) {
  380 + return Layer.alert(__('Not login tips'), {
  381 + title: __('Warning'),
  382 + btn: [__('Login now'), __('Continue install')],
  383 + yes: function (index, layero) {
  384 + $(".btn-userinfo").trigger("click");
  385 + },
  386 + btn2: function () {
  387 + install(name, version, false);
  388 + }
  389 + });
  390 + }
  391 + }
  392 + install(name, version, false);
  393 + });
  394 +
  395 + // 点击卸载
  396 + $(document).on("click", ".btn-uninstall", function () {
  397 + var name = $(this).closest(".operate").data("name");
  398 + Layer.confirm(__('Uninstall tips'), function () {
  399 + uninstall(name, false);
  400 + });
  401 + });
  402 +
  403 + // 点击配置
  404 + $(document).on("click", ".btn-config", function () {
  405 + var name = $(this).closest(".operate").data("name");
  406 + Fast.api.open("addon/config?name=" + name, __('Setting'));
  407 + });
  408 +
  409 + // 点击启用/禁用
  410 + $(document).on("click", ".btn-enable,.btn-disable", function () {
  411 + var name = $(this).closest(".operate").data("name");
  412 + var action = $(this).data("action");
  413 + operate(name, action, false);
  414 + });
  415 +
  416 + // 点击升级
  417 + $(document).on("click", ".btn-upgrade", function () {
  418 + if ($(this).closest(".operate").find("a.btn-disable").size() > 0) {
  419 + Layer.alert(__('Please disable addon first'), {icon: 7});
  420 + return false;
  421 + }
  422 + var name = $(this).closest(".operate").data("name");
  423 + var version = $(this).data("version");
  424 +
408 Layer.confirm(__('Upgrade tips'), function () { 425 Layer.confirm(__('Upgrade tips'), function () {
409 - upgrade(name); 426 + upgrade(name, version);
410 }); 427 });
411 }); 428 });
412 429
@@ -20,7 +20,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -20,7 +20,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
20 table.bootstrapTable({ 20 table.bootstrapTable({
21 url: $.fn.bootstrapTable.defaults.extend.index_url, 21 url: $.fn.bootstrapTable.defaults.extend.index_url,
22 sortName: 'weigh', 22 sortName: 'weigh',
23 - escape:false, 23 + escape: false,
24 columns: [ 24 columns: [
25 [ 25 [
26 {field: 'state', checkbox: true, }, 26 {field: 'state', checkbox: true, },
@@ -41,10 +41,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -41,10 +41,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
41 }); 41 });
42 42
43 // 为表格绑定事件 43 // 为表格绑定事件
44 - Table.api.bindevent(table);//当内容渲染完成后 44 + Table.api.bindevent(table);
45 45
46 - //默认隐藏所有子节点 46 + //当内容渲染完成后
47 table.on('post-body.bs.table', function (e, settings, json, xhr) { 47 table.on('post-body.bs.table', function (e, settings, json, xhr) {
  48 + //默认隐藏所有子节点
48 //$("a.btn[data-id][data-pid][data-pid!=0]").closest("tr").hide(); 49 //$("a.btn[data-id][data-pid][data-pid!=0]").closest("tr").hide();
49 $(".btn-node-sub.disabled").closest("tr").hide(); 50 $(".btn-node-sub.disabled").closest("tr").hide();
50 51
@@ -57,8 +58,15 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -57,8 +58,15 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
57 $(this).data("shown", !status); 58 $(this).data("shown", !status);
58 return false; 59 return false;
59 }); 60 });
  61 + $(".btn-change[data-id],.btn-delone,.btn-dragsort").data("success", function (data, ret) {
  62 + Fast.api.refreshmenu();
  63 + });
60 64
61 }); 65 });
  66 + //批量删除后的回调
  67 + $(".toolbar > .btn-del,.toolbar .btn-more~ul>li>a").data("success", function (e) {
  68 + Fast.api.refreshmenu();
  69 + });
62 //展开隐藏一级 70 //展开隐藏一级
63 $(document.body).on("click", ".btn-toggle", function (e) { 71 $(document.body).on("click", ".btn-toggle", function (e) {
64 $("a.btn[data-id][data-pid][data-pid!=0].disabled").closest("tr").hide(); 72 $("a.btn[data-id][data-pid][data-pid!=0].disabled").closest("tr").hide();
@@ -88,21 +96,21 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -88,21 +96,21 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
88 api: { 96 api: {
89 formatter: { 97 formatter: {
90 title: function (value, row, index) { 98 title: function (value, row, index) {
91 - return !row.ismenu ? "<span class='text-muted'>" + value + "</span>" : value; 99 + return !row.ismenu || row.status == 'hidden' ? "<span class='text-muted'>" + value + "</span>" : value;
92 }, 100 },
93 name: function (value, row, index) { 101 name: function (value, row, index) {
94 - return !row.ismenu ? "<span class='text-muted'>" + value + "</span>" : value; 102 + return !row.ismenu || row.status == 'hidden' ? "<span class='text-muted'>" + value + "</span>" : value;
95 }, 103 },
96 menu: function (value, row, index) { 104 menu: function (value, row, index) {
97 return "<a href='javascript:;' class='btn btn-" + (value ? "info" : "default") + " btn-xs btn-change' data-id='" 105 return "<a href='javascript:;' class='btn btn-" + (value ? "info" : "default") + " btn-xs btn-change' data-id='"
98 + row.id + "' data-params='ismenu=" + (value ? 0 : 1) + "'>" + (value ? __('Yes') : __('No')) + "</a>"; 106 + row.id + "' data-params='ismenu=" + (value ? 0 : 1) + "'>" + (value ? __('Yes') : __('No')) + "</a>";
99 }, 107 },
100 icon: function (value, row, index) { 108 icon: function (value, row, index) {
101 - return '<i class="' + value + '"></i>'; 109 + return '<span class="' + (!row.ismenu || row.status == 'hidden' ? 'text-muted' : '') + '"><i class="' + value + '"></i></span>';
102 }, 110 },
103 subnode: function (value, row, index) { 111 subnode: function (value, row, index) {
104 - return '<a href="javascript:;" data-id="' + row['id'] + '" data-pid="' + row['pid'] + '" class="btn btn-xs '  
105 - + (row['haschild'] == 1 ? 'btn-success' : 'btn-default disabled') + ' btn-node-sub"><i class="fa fa-sitemap"></i></a>'; 112 + return '<a href="javascript:;" data-id="' + row.id + '" data-pid="' + row.pid + '" class="btn btn-xs '
  113 + + (row.haschild == 1 || row.ismenu == 1 ? 'btn-success' : 'btn-default disabled') + ' btn-node-sub"><i class="fa fa-sitemap"></i></a>';
106 } 114 }
107 }, 115 },
108 bindevent: function () { 116 bindevent: function () {
@@ -113,7 +121,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function @@ -113,7 +121,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
113 $("input[name='row[ismenu]']:checked").trigger("click"); 121 $("input[name='row[ismenu]']:checked").trigger("click");
114 122
115 var iconlist = []; 123 var iconlist = [];
116 - Form.api.bindevent($("form[role=form]")); 124 + Form.api.bindevent($("form[role=form]"), function (data) {
  125 + Fast.api.refreshmenu();
  126 + });
117 $(document).on('click', ".btn-search-icon", function () { 127 $(document).on('click', ".btn-search-icon", function () {
118 if (iconlist.length == 0) { 128 if (iconlist.length == 0) {
119 $.get(Config.site.cdnurl + "/assets/libs/font-awesome/less/variables.less", function (ret) { 129 $.get(Config.site.cdnurl + "/assets/libs/font-awesome/less/variables.less", function (ret) {
@@ -178,6 +178,21 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi @@ -178,6 +178,21 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
178 } 178 }
179 }); 179 });
180 180
  181 + //刷新菜单事件
  182 + $(document).on('refresh', '.sidebar-menu', function () {
  183 + Fast.api.ajax({
  184 + url: 'index/index',
  185 + data: {action: 'refreshmenu'}
  186 + }, function (data) {
  187 + $(".sidebar-menu li:not([data-rel='external'])").remove();
  188 + $(data.menulist).insertBefore($(".sidebar-menu li:first"));
  189 + $("#nav ul li[role='presentation'].active a").trigger('click');
  190 + return false;
  191 + }, function () {
  192 + return false;
  193 + });
  194 + });
  195 +
181 //这一行需要放在点击左侧链接事件之前 196 //这一行需要放在点击左侧链接事件之前
182 var addtabs = Config.referer ? localStorage.getItem("addtabs") : null; 197 var addtabs = Config.referer ? localStorage.getItem("addtabs") : null;
183 198
@@ -188,6 +203,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi @@ -188,6 +203,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
188 } else { 203 } else {
189 $("ul.sidebar-menu li a[url!='javascript:;']:first").trigger("click"); 204 $("ul.sidebar-menu li a[url!='javascript:;']:first").trigger("click");
190 } 205 }
  206 +
191 //如果是刷新操作则直接返回刷新前的页面 207 //如果是刷新操作则直接返回刷新前的页面
192 if (Config.referer) { 208 if (Config.referer) {
193 if (Config.referer === $(addtabs).attr("url")) { 209 if (Config.referer === $(addtabs).attr("url")) {
@@ -203,11 +219,6 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi @@ -203,11 +219,6 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
203 } 219 }
204 } 220 }
205 221
206 - /**  
207 - * List of all the available skins  
208 - *  
209 - * @type Array  
210 - */  
211 var my_skins = [ 222 var my_skins = [
212 "skin-blue", 223 "skin-blue",
213 "skin-white", 224 "skin-white",
@@ -224,19 +235,13 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi @@ -224,19 +235,13 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
224 ]; 235 ];
225 setup(); 236 setup();
226 237
227 - /**  
228 - * Toggles layout classes  
229 - *  
230 - * @param String cls the layout class to toggle  
231 - * @returns void  
232 - */  
233 function change_layout(cls) { 238 function change_layout(cls) {
234 $("body").toggleClass(cls); 239 $("body").toggleClass(cls);
235 AdminLTE.layout.fixSidebar(); 240 AdminLTE.layout.fixSidebar();
236 //Fix the problem with right sidebar and layout boxed 241 //Fix the problem with right sidebar and layout boxed
237 if (cls == "layout-boxed") 242 if (cls == "layout-boxed")
238 AdminLTE.controlSidebar._fix($(".control-sidebar-bg")); 243 AdminLTE.controlSidebar._fix($(".control-sidebar-bg"));
239 - if ($('body').hasClass('fixed') && cls == 'fixed' && false) { 244 + if ($('body').hasClass('fixed') && cls == 'fixed') {
240 AdminLTE.pushMenu.expandOnHover(); 245 AdminLTE.pushMenu.expandOnHover();
241 AdminLTE.layout.activate(); 246 AdminLTE.layout.activate();
242 } 247 }
@@ -244,61 +249,18 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi @@ -244,61 +249,18 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
244 AdminLTE.controlSidebar._fix($(".control-sidebar")); 249 AdminLTE.controlSidebar._fix($(".control-sidebar"));
245 } 250 }
246 251
247 - /**  
248 - * Replaces the old skin with the new skin  
249 - * @param String cls the new skin class  
250 - * @returns Boolean false to prevent link's default action  
251 - */  
252 function change_skin(cls) { 252 function change_skin(cls) {
253 if (!$("body").hasClass(cls)) { 253 if (!$("body").hasClass(cls)) {
254 - $.each(my_skins, function (i) {  
255 - $("body").removeClass(my_skins[i]);  
256 - });  
257 -  
258 - $("body").addClass(cls);  
259 - store('skin', cls); 254 + $("body").removeClass(my_skins.join(' ')).addClass(cls);
  255 + localStorage.setItem('skin', cls);
260 var cssfile = Config.site.cdnurl + "/assets/css/skins/" + cls + ".css"; 256 var cssfile = Config.site.cdnurl + "/assets/css/skins/" + cls + ".css";
261 $('head').append('<link rel="stylesheet" href="' + cssfile + '" type="text/css" />'); 257 $('head').append('<link rel="stylesheet" href="' + cssfile + '" type="text/css" />');
262 } 258 }
263 return false; 259 return false;
264 } 260 }
265 261
266 - /**  
267 - * Store a new settings in the browser  
268 - *  
269 - * @param String name Name of the setting  
270 - * @param String val Value of the setting  
271 - * @returns void  
272 - */  
273 - function store(name, val) {  
274 - if (typeof (Storage) !== "undefined") {  
275 - localStorage.setItem(name, val);  
276 - } else {  
277 - window.alert('Please use a modern browser to properly view this template!');  
278 - }  
279 - }  
280 -  
281 - /**  
282 - * Get a prestored setting  
283 - *  
284 - * @param String name Name of of the setting  
285 - * @returns String The value of the setting | null  
286 - */  
287 - function get(name) {  
288 - if (typeof (Storage) !== "undefined") {  
289 - return localStorage.getItem(name);  
290 - } else {  
291 - window.alert('Please use a modern browser to properly view this template!');  
292 - }  
293 - }  
294 -  
295 - /**  
296 - * Retrieve default settings and apply them to the template  
297 - *  
298 - * @returns void  
299 - */  
300 function setup() { 262 function setup() {
301 - var tmp = get('skin'); 263 + var tmp = localStorage.getItem('skin');
302 if (tmp && $.inArray(tmp, my_skins)) 264 if (tmp && $.inArray(tmp, my_skins))
303 change_skin(tmp); 265 change_skin(tmp);
304 266
@@ -28,7 +28,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin @@ -28,7 +28,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
28 {field: 'id', title: __('Id')}, 28 {field: 'id', title: __('Id')},
29 {field: 'pid', title: __('Pid'), visible: false}, 29 {field: 'pid', title: __('Pid'), visible: false},
30 {field: 'title', title: __('Title'), align: 'left'}, 30 {field: 'title', title: __('Title'), align: 'left'},
31 - {field: 'name', title: __('Name')}, 31 + {field: 'name', title: __('Name'), align: 'left'},
32 {field: 'remark', title: __('Remark')}, 32 {field: 'remark', title: __('Remark')},
33 {field: 'ismenu', title: __('Ismenu'), formatter: Controller.api.formatter.toggle}, 33 {field: 'ismenu', title: __('Ismenu'), formatter: Controller.api.formatter.toggle},
34 {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, visible: false}, 34 {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, visible: false},
@@ -104,7 +104,7 @@ @@ -104,7 +104,7 @@
104 104
105 var createFormCommon = function (pColumns, that) { 105 var createFormCommon = function (pColumns, that) {
106 var htmlForm = []; 106 var htmlForm = [];
107 - var opList = ['=', '>', '>=', '<', '<=', '!=', 'LIKE', 'LIKE %...%', 'NOT LIKE', 'IN', 'NOT IN', 'IN(...)', 'NOT IN(...)', 'BETWEEN', 'NOT BETWEEN', 'RANGE', 'NOT RANGE', 'IS NULL', 'IS NOT NULL']; 107 + var opList = ['=', '>', '>=', '<', '<=', '!=', 'FIND_IN_SET', 'LIKE', 'LIKE %...%', 'NOT LIKE', 'IN', 'NOT IN', 'IN(...)', 'NOT IN(...)', 'BETWEEN', 'NOT BETWEEN', 'RANGE', 'NOT RANGE', 'IS NULL', 'IS NOT NULL'];
108 htmlForm.push(sprintf('<form class="form-horizontal form-commonsearch" action="%s" >', that.options.actionForm)); 108 htmlForm.push(sprintf('<form class="form-horizontal form-commonsearch" action="%s" >', that.options.actionForm));
109 htmlForm.push('<fieldset>'); 109 htmlForm.push('<fieldset>');
110 if (that.options.titleForm.length > 0) 110 if (that.options.titleForm.length > 0)
@@ -33,6 +33,7 @@ @@ -33,6 +33,7 @@
33 if (!that.options.templateView) { 33 if (!that.options.templateView) {
34 return; 34 return;
35 } 35 }
  36 + that.options.cardView = true;
36 37
37 }; 38 };
38 39
@@ -164,8 +164,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine @@ -164,8 +164,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
164 options.area = [top.$(".tab-pane.active").width() + "px", top.$(".tab-pane.active").height() + "px"]; 164 options.area = [top.$(".tab-pane.active").width() + "px", top.$(".tab-pane.active").height() + "px"];
165 options.offset = [top.$(".tab-pane.active").scrollTop() + "px", "0px"]; 165 options.offset = [top.$(".tab-pane.active").scrollTop() + "px", "0px"];
166 } 166 }
167 - Layer.open(options);  
168 - return false; 167 + return Layer.open(options);
169 }, 168 },
170 //关闭窗口并回传数据 169 //关闭窗口并回传数据
171 close: function (data) { 170 close: function (data) {
  1 +define(['frontend'], function (Frontend) {
  2 +
  3 +});
@@ -7,7 +7,7 @@ require.config({ @@ -7,7 +7,7 @@ require.config({
7 } 7 }
8 ], 8 ],
9 //在打包压缩时将会把include中的模块合并到主文件中 9 //在打包压缩时将会把include中的模块合并到主文件中
10 - include: ['css', 'layer', 'toastr', 'fast', 'backend', 'table', 'form', 'dragsort', 'drag', 'drop', 'addtabs', 'selectpage'], 10 + include: ['css', 'layer', 'toastr', 'fast', 'backend', 'backend-init', 'table', 'form', 'dragsort', 'drag', 'drop', 'addtabs', 'selectpage'],
11 paths: { 11 paths: {
12 'lang': "empty:", 12 'lang': "empty:",
13 'form': 'require-form', 13 'form': 'require-form',
@@ -34,22 +34,20 @@ require.config({ @@ -34,22 +34,20 @@ require.config({
34 'bootstrap-table-mobile': '../libs/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile', 34 'bootstrap-table-mobile': '../libs/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile',
35 'bootstrap-table-lang': '../libs/bootstrap-table/dist/locale/bootstrap-table-zh-CN', 35 'bootstrap-table-lang': '../libs/bootstrap-table/dist/locale/bootstrap-table-zh-CN',
36 'tableexport': '../libs/tableExport.jquery.plugin/tableExport.min', 36 'tableexport': '../libs/tableExport.jquery.plugin/tableExport.min',
37 - 'dragsort': '../libs/dragsort/jquery.dragsort',  
38 - 'qrcode': '../libs/jquery-qrcode/jquery.qrcode.min', 37 + 'dragsort': '../libs/fastadmin-dragsort/jquery.dragsort',
39 'sortable': '../libs/Sortable/Sortable.min', 38 'sortable': '../libs/Sortable/Sortable.min',
40 - 'addtabs': '../libs/jquery-addtabs/jquery.addtabs', 39 + 'addtabs': '../libs/fastadmin-addtabs/jquery.addtabs',
41 'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll', 40 'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll',
42 - 'summernote': '../libs/summernote/dist/lang/summernote-zh-CN.min',  
43 'validator-core': '../libs/nice-validator/dist/jquery.validator', 41 'validator-core': '../libs/nice-validator/dist/jquery.validator',
44 'validator-lang': '../libs/nice-validator/dist/local/zh-CN', 42 'validator-lang': '../libs/nice-validator/dist/local/zh-CN',
45 'plupload': '../libs/plupload/js/plupload.min', 43 'plupload': '../libs/plupload/js/plupload.min',
46 'toastr': '../libs/toastr/toastr', 44 'toastr': '../libs/toastr/toastr',
47 'jstree': '../libs/jstree/dist/jstree.min', 45 'jstree': '../libs/jstree/dist/jstree.min',
48 - 'layer': '../libs/layer/src/layer', 46 + 'layer': '../libs/layer/dist/layer',
49 'cookie': '../libs/jquery.cookie/jquery.cookie', 47 'cookie': '../libs/jquery.cookie/jquery.cookie',
50 - 'cxselect': '../libs/jquery-cxselect/js/jquery.cxselect', 48 + 'cxselect': '../libs/fastadmin-cxselect/js/jquery.cxselect',
51 'template': '../libs/art-template/dist/template-native', 49 'template': '../libs/art-template/dist/template-native',
52 - 'selectpage': '../libs/selectpage/selectpage', 50 + 'selectpage': '../libs/fastadmin-selectpage/selectpage',
53 'citypicker': '../libs/city-picker/dist/js/city-picker.min', 51 'citypicker': '../libs/city-picker/dist/js/city-picker.min',
54 'citypicker-data': '../libs/city-picker/dist/js/city-picker.data', 52 'citypicker-data': '../libs/city-picker/dist/js/city-picker.data',
55 }, 53 },
@@ -106,7 +104,6 @@ require.config({ @@ -106,7 +104,6 @@ require.config({
106 ], 104 ],
107 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ], 105 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ],
108 'bootstrap-select-lang': ['bootstrap-select'], 106 'bootstrap-select-lang': ['bootstrap-select'],
109 - 'summernote': ['../libs/summernote/dist/summernote.min', 'css!../libs/summernote/dist/summernote.css'],  
110 // 'toastr': ['css!../libs/toastr/toastr.min.css'], 107 // 'toastr': ['css!../libs/toastr/toastr.min.css'],
111 'jstree': ['css!../libs/jstree/dist/themes/default/style.css', ], 108 'jstree': ['css!../libs/jstree/dist/themes/default/style.css', ],
112 'plupload': { 109 'plupload': {
@@ -116,7 +113,7 @@ require.config({ @@ -116,7 +113,7 @@ require.config({
116 // 'layer': ['css!../libs/layer/dist/theme/default/layer.css'], 113 // 'layer': ['css!../libs/layer/dist/theme/default/layer.css'],
117 // 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'], 114 // 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'],
118 'validator-lang': ['validator-core'], 115 'validator-lang': ['validator-core'],
119 -// 'selectpage': ['css!../libs/selectpage/selectpage.css'], 116 +// 'selectpage': ['css!../libs/fastadmin-selectpage/selectpage.css'],
120 'citypicker': ['citypicker-data', 'css!../libs/city-picker/dist/css/city-picker.css'] 117 'citypicker': ['citypicker-data', 'css!../libs/city-picker/dist/css/city-picker.css']
121 }, 118 },
122 baseUrl: requirejs.s.contexts._.config.config.site.cdnurl + '/assets/js/', //资源基础路径 119 baseUrl: requirejs.s.contexts._.config.config.site.cdnurl + '/assets/js/', //资源基础路径
@@ -144,7 +141,7 @@ require(['jquery', 'bootstrap'], function ($, undefined) { @@ -144,7 +141,7 @@ require(['jquery', 'bootstrap'], function ($, undefined) {
144 // 初始化 141 // 初始化
145 $(function () { 142 $(function () {
146 require(['fast'], function (Fast) { 143 require(['fast'], function (Fast) {
147 - require(['backend', 'addons'], function (Backend, Addons) { 144 + require(['backend', 'backend-init', 'addons'], function (Backend, undefined, Addons) {
148 //加载相应模块 145 //加载相应模块
149 if (Config.jsname) { 146 if (Config.jsname) {
150 require([Config.jsname], function (Controller) { 147 require([Config.jsname], function (Controller) {
@@ -40,6 +40,9 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U @@ -40,6 +40,9 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
40 Form.api.submit($(ret), function (data, ret) { 40 Form.api.submit($(ret), function (data, ret) {
41 that.holdSubmit(false); 41 that.holdSubmit(false);
42 submitBtn.removeClass("disabled"); 42 submitBtn.removeClass("disabled");
  43 + if (false === $(this).triggerHandler("success.form", [data, ret])) {
  44 + return false;
  45 + }
43 if (typeof success === 'function') { 46 if (typeof success === 'function') {
44 if (false === success.call($(this), data, ret)) { 47 if (false === success.call($(this), data, ret)) {
45 return false; 48 return false;
@@ -54,6 +57,9 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U @@ -54,6 +57,9 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
54 return false; 57 return false;
55 }, function (data, ret) { 58 }, function (data, ret) {
56 that.holdSubmit(false); 59 that.holdSubmit(false);
  60 + if (false === $(this).triggerHandler("error.form", [data, ret])) {
  61 + return false;
  62 + }
57 submitBtn.removeClass("disabled"); 63 submitBtn.removeClass("disabled");
58 if (typeof error === 'function') { 64 if (typeof error === 'function') {
59 if (false === error.call($(this), data, ret)) { 65 if (false === error.call($(this), data, ret)) {
@@ -81,13 +87,25 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U @@ -81,13 +87,25 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
81 if ($(".selectpage", form).size() > 0) { 87 if ($(".selectpage", form).size() > 0) {
82 require(['selectpage'], function () { 88 require(['selectpage'], function () {
83 $('.selectpage', form).selectPage({ 89 $('.selectpage', form).selectPage({
84 - source: 'ajax/selectpage', 90 + eAjaxSuccess: function (data) {
  91 + data.list = typeof data.rows !== 'undefined' ? data.rows : (typeof data.list !== 'undefined' ? data.list : []);
  92 + data.totalRow = typeof data.total !== 'undefined' ? data.total : (typeof data.totalRow !== 'undefined' ? data.totalRow : data.list.length);
  93 + return data;
  94 + }
85 }); 95 });
86 }); 96 });
87 //给隐藏的元素添加上validate验证触发事件 97 //给隐藏的元素添加上validate验证触发事件
88 - $(form).on("change", ".selectpage-input-hidden", function () { 98 + $(document).on("change", ".sp_hidden", function () {
89 $(this).trigger("validate"); 99 $(this).trigger("validate");
90 }); 100 });
  101 + $(document).on("change", ".sp_input", function () {
  102 + $(this).closest(".sp_container").find(".sp_hidden").trigger("change");
  103 + });
  104 + $(form).on("reset", function () {
  105 + setTimeout(function () {
  106 + $('.selectpage', form).selectPageClear();
  107 + }, 1);
  108 + });
91 } 109 }
92 }, 110 },
93 cxselect: function (form) { 111 cxselect: function (form) {
@@ -132,6 +150,48 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U @@ -132,6 +150,48 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
132 }); 150 });
133 } 151 }
134 }, 152 },
  153 + daterangepicker: function (form) {
  154 + //绑定日期时间元素事件
  155 + if ($(".datetimerange", form).size() > 0) {
  156 + require(['bootstrap-daterangepicker'], function () {
  157 + var ranges = {};
  158 + ranges[__('Today')] = [Moment().startOf('day'), Moment().endOf('day')];
  159 + ranges[__('Yesterday')] = [Moment().subtract(1, 'days').startOf('day'), Moment().subtract(1, 'days').endOf('day')];
  160 + ranges[__('Last 7 Days')] = [Moment().subtract(6, 'days').startOf('day'), Moment().endOf('day')];
  161 + ranges[__('Last 30 Days')] = [Moment().subtract(29, 'days').startOf('day'), Moment().endOf('day')];
  162 + ranges[__('This Month')] = [Moment().startOf('month'), Moment().endOf('month')];
  163 + ranges[__('Last Month')] = [Moment().subtract(1, 'month').startOf('month'), Moment().subtract(1, 'month').endOf('month')];
  164 + var options = {
  165 + timePicker: false,
  166 + autoUpdateInput: false,
  167 + timePickerSeconds: true,
  168 + timePicker24Hour: true,
  169 + autoApply: true,
  170 + locale: {
  171 + format: 'YYYY-MM-DD HH:mm:ss',
  172 + customRangeLabel: __("Custom Range"),
  173 + applyLabel: __("Apply"),
  174 + cancelLabel: __("Clear"),
  175 + },
  176 + ranges: ranges,
  177 + };
  178 + var origincallback = function (start, end) {
  179 + $(this.element).val(start.format(options.locale.format) + " - " + end.format(options.locale.format));
  180 + $(this.element).trigger('blur');
  181 + };
  182 + $(".datetimerange", form).each(function () {
  183 + var callback = typeof $(this).data('callback') == 'function' ? $(this).data('callback') : origincallback;
  184 + $(this).on('apply.daterangepicker', function (ev, picker) {
  185 + callback.call(picker, picker.startDate, picker.endDate);
  186 + });
  187 + $(this).on('cancel.daterangepicker', function (ev, picker) {
  188 + $(this).val('').trigger('blur');
  189 + });
  190 + $(this).daterangepicker($.extend({}, options, $(this).data()), callback);
  191 + });
  192 + });
  193 + }
  194 + },
135 plupload: function (form) { 195 plupload: function (form) {
136 //绑定plupload上传元素事件 196 //绑定plupload上传元素事件
137 if ($(".plupload", form).size() > 0) { 197 if ($(".plupload", form).size() > 0) {
@@ -287,6 +347,8 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U @@ -287,6 +347,8 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
287 347
288 events.selectpicker(form); 348 events.selectpicker(form);
289 349
  350 + events.daterangepicker(form);
  351 +
290 events.selectpage(form); 352 events.selectpage(form);
291 353
292 events.cxselect(form); 354 events.cxselect(form);
@@ -7,7 +7,7 @@ require.config({ @@ -7,7 +7,7 @@ require.config({
7 } 7 }
8 ], 8 ],
9 //在打包压缩时将会把include中的模块合并到主文件中 9 //在打包压缩时将会把include中的模块合并到主文件中
10 - include: ['css', 'layer', 'toastr', 'fast', 'frontend'], 10 + include: ['css', 'layer', 'toastr', 'fast', 'frontend', 'frontend-init'],
11 paths: { 11 paths: {
12 'lang': "empty:", 12 'lang': "empty:",
13 'form': 'require-form', 13 'form': 'require-form',
@@ -34,22 +34,20 @@ require.config({ @@ -34,22 +34,20 @@ require.config({
34 'bootstrap-table-mobile': '../libs/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile', 34 'bootstrap-table-mobile': '../libs/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile',
35 'bootstrap-table-lang': '../libs/bootstrap-table/dist/locale/bootstrap-table-zh-CN', 35 'bootstrap-table-lang': '../libs/bootstrap-table/dist/locale/bootstrap-table-zh-CN',
36 'tableexport': '../libs/tableExport.jquery.plugin/tableExport.min', 36 'tableexport': '../libs/tableExport.jquery.plugin/tableExport.min',
37 - 'dragsort': '../libs/dragsort/jquery.dragsort',  
38 - 'qrcode': '../libs/jquery-qrcode/jquery.qrcode.min', 37 + 'dragsort': '../libs/fastadmin-dragsort/jquery.dragsort',
39 'sortable': '../libs/Sortable/Sortable.min', 38 'sortable': '../libs/Sortable/Sortable.min',
40 - 'addtabs': '../libs/jquery-addtabs/jquery.addtabs', 39 + 'addtabs': '../libs/fastadmin-addtabs/jquery.addtabs',
41 'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll', 40 'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll',
42 - 'summernote': '../libs/summernote/dist/lang/summernote-zh-CN.min',  
43 'validator-core': '../libs/nice-validator/dist/jquery.validator', 41 'validator-core': '../libs/nice-validator/dist/jquery.validator',
44 'validator-lang': '../libs/nice-validator/dist/local/zh-CN', 42 'validator-lang': '../libs/nice-validator/dist/local/zh-CN',
45 'plupload': '../libs/plupload/js/plupload.min', 43 'plupload': '../libs/plupload/js/plupload.min',
46 'toastr': '../libs/toastr/toastr', 44 'toastr': '../libs/toastr/toastr',
47 'jstree': '../libs/jstree/dist/jstree.min', 45 'jstree': '../libs/jstree/dist/jstree.min',
48 - 'layer': '../libs/layer/src/layer', 46 + 'layer': '../libs/layer/dist/layer',
49 'cookie': '../libs/jquery.cookie/jquery.cookie', 47 'cookie': '../libs/jquery.cookie/jquery.cookie',
50 - 'cxselect': '../libs/jquery-cxselect/js/jquery.cxselect', 48 + 'cxselect': '../libs/fastadmin-cxselect/js/jquery.cxselect',
51 'template': '../libs/art-template/dist/template-native', 49 'template': '../libs/art-template/dist/template-native',
52 - 'selectpage': '../libs/selectpage/selectpage', 50 + 'selectpage': '../libs/fastadmin-selectpage/selectpage',
53 'citypicker': '../libs/city-picker/dist/js/city-picker.min', 51 'citypicker': '../libs/city-picker/dist/js/city-picker.min',
54 'citypicker-data': '../libs/city-picker/dist/js/city-picker.data', 52 'citypicker-data': '../libs/city-picker/dist/js/city-picker.data',
55 }, 53 },
@@ -106,7 +104,6 @@ require.config({ @@ -106,7 +104,6 @@ require.config({
106 ], 104 ],
107 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ], 105 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ],
108 'bootstrap-select-lang': ['bootstrap-select'], 106 'bootstrap-select-lang': ['bootstrap-select'],
109 - 'summernote': ['../libs/summernote/dist/summernote.min', 'css!../libs/summernote/dist/summernote.css'],  
110 // 'toastr': ['css!../libs/toastr/toastr.min.css'], 107 // 'toastr': ['css!../libs/toastr/toastr.min.css'],
111 'jstree': ['css!../libs/jstree/dist/themes/default/style.css', ], 108 'jstree': ['css!../libs/jstree/dist/themes/default/style.css', ],
112 'plupload': { 109 'plupload': {
@@ -116,7 +113,7 @@ require.config({ @@ -116,7 +113,7 @@ require.config({
116 // 'layer': ['css!../libs/layer/dist/theme/default/layer.css'], 113 // 'layer': ['css!../libs/layer/dist/theme/default/layer.css'],
117 // 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'], 114 // 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'],
118 'validator-lang': ['validator-core'], 115 'validator-lang': ['validator-core'],
119 -// 'selectpage': ['css!../libs/selectpage/selectpage.css'], 116 +// 'selectpage': ['css!../libs/fastadmin-selectpage/selectpage.css'],
120 'citypicker': ['citypicker-data', 'css!../libs/city-picker/dist/css/city-picker.css'] 117 'citypicker': ['citypicker-data', 'css!../libs/city-picker/dist/css/city-picker.css']
121 }, 118 },
122 baseUrl: requirejs.s.contexts._.config.config.site.cdnurl + '/assets/js/', //资源基础路径 119 baseUrl: requirejs.s.contexts._.config.config.site.cdnurl + '/assets/js/', //资源基础路径