Browse Source

新增CRUD多表关联
新增CRUD筛选字段
新增表格按钮组按钮url支持function功能
新增表格按钮组按钮hidden参数
新增菜单生成支持多控制器

优化错误页面和跳转页面多语言
优化一键生成API文档在PHP5下的提示
优化第三方依赖包配置规则

Karson 7 years ago
parent
commit
f28089b1f0
34 changed files with 4397 additions and 4614 deletions
  1. 1 0
      README.md
  2. 45 45
      application/admin/command/Api.php
  3. 1 1
      application/admin/command/Api/library/Builder.php
  4. 1 1
      application/admin/command/Api/template/index.html
  5. 327 372
      application/admin/command/Crud.php
  6. 9 3
      application/admin/command/Crud/stubs/controllerindex.stub
  7. 1 1
      application/admin/command/Crud/stubs/mixins/modelrelationmethod.stub
  8. 2 2
      application/admin/command/Crud/stubs/model.stub
  9. 2 2
      application/admin/command/Crud/stubs/relationmodel.stub
  10. 2 2
      application/admin/command/Install/fastadmin.sql
  11. 66 93
      application/admin/command/Menu.php
  12. 133 123
      application/admin/common.php
  13. 2 0
      application/admin/lang/zh-cn.php
  14. 1 1
      application/admin/lang/zh-cn/addon.php
  15. 1 0
      application/admin/library/traits/Backend.php
  16. 3 3
      application/admin/view/common/header.html
  17. 2 0
      application/common/lang/zh-cn/addon.php
  18. 10 10
      application/common/view/tpl/dispatch_jump.tpl
  19. 27 7
      application/common/view/tpl/think_exception.tpl
  20. 1 1
      application/config.php
  21. 13 0
      application/index/lang/en/index.php
  22. 4 0
      application/index/lang/zh-cn.php
  23. 9 1
      application/index/lang/zh-cn/index.php
  24. 5 0
      application/index/lang/zh-cn/user.php
  25. 7 7
      application/index/view/index/index.html
  26. 3 3
      application/index/view/user/changepwd.html
  27. 1 1
      application/index/view/user/index.html
  28. 2 2
      application/index/view/user/login.html
  29. 6 6
      application/index/view/user/profile.html
  30. 19 19
      bower.json
  31. 1 1
      public/api.html
  32. 2 3
      public/assets/js/frontend/user.js
  33. 3643 3879
      public/assets/js/require-backend.min.js
  34. 45 25
      public/assets/js/require-table.js

+ 1 - 0
README.md

@@ -8,6 +8,7 @@ FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
     * 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
     * 支持单管理员多角色
     * 支持目录和控制器结构一键生成权限节点
+    * 支持管理子级数据或个人数据
 * 完善的前端功能组件开发
     * 基于`AdminLTE`二次开发
     * 基于`Bootstrap`开发,自适应手机、平板、PC

+ 45 - 45
application/admin/command/Api.php

@@ -17,17 +17,17 @@ class Api extends Command
     {
         $site = Config::get('site');
         $this
-                ->setName('api')
-                ->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
-                ->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
-                ->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
-                ->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
-                ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
-                ->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'])
-                ->addOption('author', 'a', Option::VALUE_OPTIONAL, 'document author', $site['name'])
-                ->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
-                ->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
-                ->setDescription('Compress js and css file');
+            ->setName('api')
+            ->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
+            ->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
+            ->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
+            ->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
+            ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
+            ->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'])
+            ->addOption('author', 'a', Option::VALUE_OPTIONAL, 'document author', $site['name'])
+            ->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
+            ->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
+            ->setDescription('Compress js and css file');
     }
 
     protected function execute(Input $input, Output $output)
@@ -37,24 +37,22 @@ class Api extends Command
         $force = $input->getOption('force');
         $url = $input->getOption('url');
         $language = $input->getOption('language');
+        $language = $language ? $language : 'zh-cn';
         $langFile = $apiDir . 'lang' . DS . $language . '.php';
-        if (!is_file($langFile))
-        {
+        if (!is_file($langFile)) {
             throw new Exception('language file not found');
         }
-        $lang = include $langFile;
+        $lang = include_once $langFile;
         // 目标目录
         $output_dir = ROOT_PATH . 'public' . DS;
         $output_file = $output_dir . $input->getOption('output');
-        if (is_file($output_file) && !$force)
-        {
+        if (is_file($output_file) && !$force) {
             throw new Exception("api index file already exists!\nIf you need to rebuild again, use the parameter --force=true ");
         }
         // 模板文件
         $template_dir = $apiDir . 'template' . DS;
         $template_file = $template_dir . $input->getOption('template');
-        if (!is_file($template_file))
-        {
+        if (!is_file($template_file)) {
             throw new Exception('template file not found');
         }
         // 额外的类
@@ -67,19 +65,30 @@ class Api extends Command
         $module = $input->getOption('module');
 
         $moduleDir = APP_PATH . $module . DS;
-        if (!is_dir($moduleDir))
-        {
+        if (!is_dir($moduleDir)) {
             throw new Exception('module not found');
         }
+
+        if (version_compare(PHP_VERSION, '7.0.0', '<')) {
+            if (extension_loaded('opcache')) {
+                $configuration = opcache_get_configuration();
+                $directives = $configuration['directives'];
+                $configName = request()->isCli() ? 'opcache.enable_cli' : 'opcache.enable';
+                if (!$directives[$configName]) {
+                    throw new Exception("Please make sure {$configName} is turned on, Get help:http://forum.fastadmin.net/d/1321");
+                }
+            } else {
+                throw new Exception("Please make sure opcache already enabled, Get help:http://forum.fastadmin.net/d/1321");
+            }
+        }
+
         $controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
         $files = new \RecursiveIteratorIterator(
-                new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
+            new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
         );
 
-        foreach ($files as $name => $file)
-        {
-            if (!$file->isDir())
-            {
+        foreach ($files as $name => $file) {
+            if (!$file->isDir()) {
                 $filePath = $file->getRealPath();
                 $classes[] = $this->get_class_from_file($filePath);
             }
@@ -90,12 +99,12 @@ class Api extends Command
             'author'      => $author,
             'description' => '',
             'apiurl'      => $url,
+            'language'    => $language,
         ];
         $builder = new Builder($classes);
         $content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
 
-        if (!file_put_contents($output_file, $content))
-        {
+        if (!file_put_contents($output_file, $content)) {
             throw new Exception('Cannot save the content to ' . $output_file);
         }
         $output->info("Build Successed!");
@@ -103,7 +112,7 @@ class Api extends Command
 
     /**
      * get full qualified class name
-     * 
+     *
      * @param string $path_to_file
      * @author JBYRNE http://jarretbyrne.com/2015/06/197/
      * @return string
@@ -120,34 +129,27 @@ class Api extends Command
         $getting_namespace = $getting_class = false;
 
         //Go through each token and evaluate it as necessary
-        foreach (token_get_all($contents) as $token)
-        {
+        foreach (token_get_all($contents) as $token) {
 
             //If this token is the namespace declaring, then flag that the next tokens will be the namespace name
-            if (is_array($token) && $token[0] == T_NAMESPACE)
-            {
+            if (is_array($token) && $token[0] == T_NAMESPACE) {
                 $getting_namespace = true;
             }
 
             //If this token is the class declaring, then flag that the next tokens will be the class name
-            if (is_array($token) && $token[0] == T_CLASS)
-            {
+            if (is_array($token) && $token[0] == T_CLASS) {
                 $getting_class = true;
             }
 
             //While we're grabbing the namespace name...
-            if ($getting_namespace === true)
-            {
+            if ($getting_namespace === true) {
 
                 //If the token is a string or the namespace separator...
-                if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR]))
-                {
+                if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {
 
                     //Append the token's value to the name of the namespace
                     $namespace .= $token[1];
-                }
-                else if ($token === ';')
-                {
+                } else if ($token === ';') {
 
                     //If the token is the semicolon, then we're done with the namespace declaration
                     $getting_namespace = false;
@@ -155,12 +157,10 @@ class Api extends Command
             }
 
             //While we're grabbing the class name...
-            if ($getting_class === true)
-            {
+            if ($getting_class === true) {
 
                 //If the token is a string, it's the name of the class
-                if (is_array($token) && $token[0] == T_STRING)
-                {
+                if (is_array($token) && $token[0] == T_STRING) {
 
                     //Store the token's value as the class name
                     $class = $token[1];

+ 1 - 1
application/admin/command/Api/library/Builder.php

@@ -31,7 +31,7 @@ class Builder
     public function __construct($classes = [])
     {
         $this->classes = array_merge($this->classes, $classes);
-        $this->view = \think\View::instance(Config::get('template'), Config::get('view_replace_str'));
+        $this->view = new \think\View(Config::get('template'), Config::get('view_replace_str'));
     }
 
     protected function extractAnnotations()

+ 1 - 1
application/admin/command/Api/template/index.html

@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="{$config.language}">
     <head>
         <meta charset="utf-8">
         <meta http-equiv="X-UA-Compatible" content="IE=edge">

File diff suppressed because it is too large
+ 327 - 372
application/admin/command/Crud.php


+ 9 - 3
application/admin/command/Crud/stubs/controllerindex.stub

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

+ 1 - 1
application/admin/command/Crud/stubs/mixins/modelrelationmethod.stub

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

+ 2 - 2
application/admin/command/Crud/stubs/model.stub

@@ -7,7 +7,7 @@ use think\Model;
 class {%modelName%} extends Model
 {
     // 表名
-    protected ${%modelTableType%} = '{%modelTableName%}';
+    protected ${%modelTableType%} = '{%modelTableTypeName%}';
     
     // 自动写入时间戳字段
     protected $autoWriteTimestamp = {%modelAutoWriteTimestamp%};
@@ -29,5 +29,5 @@ class {%modelName%} extends Model
 
 {%setAttrList%}
 
-{%modelRelationMethod%}
+{%relationMethodList%}
 }

+ 2 - 2
application/admin/command/Crud/stubs/relationmodel.stub

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

+ 2 - 2
application/admin/command/Install/fastadmin.sql

@@ -50,7 +50,7 @@ CREATE TABLE `fa_admin_log` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
   `admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
   `username` varchar(30) NOT NULL DEFAULT '' COMMENT '管理员名字',
-  `url` varchar(100) NOT NULL DEFAULT '' COMMENT '操作页面',
+  `url` varchar(255) NOT NULL DEFAULT '' COMMENT '操作页面',
   `title` varchar(100) NOT NULL DEFAULT '' COMMENT '日志标题',
   `content` text NOT NULL COMMENT '内容',
   `ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'IP',
@@ -58,7 +58,7 @@ CREATE TABLE `fa_admin_log` (
   `createtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '操作时间',
   PRIMARY KEY (`id`),
   KEY `name` (`username`)
-) ENGINE=InnoDB AUTO_INCREMENT=1317 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='管理员日志表';
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='管理员日志表';
 
 -- ----------------------------
 -- Table structure for fa_attachment

+ 66 - 93
application/admin/command/Menu.php

@@ -21,10 +21,11 @@ class Menu extends Command
     protected function configure()
     {
         $this
-                ->setName('menu')
-                ->addOption('controller', 'c', Option::VALUE_REQUIRED, 'controller name,use \'all-controller\' when build all menu', null)
-                ->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete the specified menu', '')
-                ->setDescription('Build auth menu from controller');
+            ->setName('menu')
+            ->addOption('controller', 'c', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name,use \'all-controller\' when build all menu', null)
+            ->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete the specified menu', '')
+            ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force delete menu,without tips', null)
+            ->setDescription('Build auth menu from controller');
     }
 
     protected function execute(Input $input, Output $output)
@@ -33,34 +34,35 @@ class Menu extends Command
         $adminPath = dirname(__DIR__) . DS;
         //控制器名
         $controller = $input->getOption('controller') ?: '';
-        if (!$controller)
-        {
+        if (!$controller) {
             throw new Exception("please input controller name");
         }
+        $force = $input->getOption('force');
         //是否为删除模式
         $delete = $input->getOption('delete');
-        if ($delete)
-        {
-            if ($controller == 'all-controller')
-            {
+        if ($delete) {
+            if (in_array('all-controller', $controller)) {
                 throw new Exception("could not delete all menu");
             }
             $ids = [];
-            $list = $this->model->where('name', 'like', strtolower($controller) . "%")->select();
-            foreach ($list as $k => $v)
-            {
+            $list = $this->model->where(function ($query) use ($controller) {
+                foreach ($controller as $index => $item) {
+                    $query->whereOr('name', 'like', strtolower($item) . "%");
+                }
+            })->select();
+            foreach ($list as $k => $v) {
                 $output->warning($v->name);
                 $ids[] = $v->id;
             }
-            if (!$ids)
-            {
+            if (!$ids) {
                 throw new Exception("There is no menu to delete");
             }
-            $output->info("Are you sure you want to delete all those menu?  Type 'yes' to continue: ");
-            $line = fgets(STDIN);
-            if (trim($line) != 'yes')
-            {
-                throw new Exception("Operation is aborted!");
+            if (!$force) {
+                $output->info("Are you sure you want to delete all those menu?  Type 'yes' to continue: ");
+                $line = fgets(STDIN);
+                if (trim($line) != 'yes') {
+                    throw new Exception("Operation is aborted!");
+                }
             }
             AuthRule::destroy($ids);
 
@@ -69,22 +71,21 @@ class Menu extends Command
             return;
         }
 
-        if ($controller != 'all-controller')
-        {
-            $controllerArr = explode('/', $controller);
-            end($controllerArr);
-            $key = key($controllerArr);
-            $controllerArr[$key] = ucfirst($controllerArr[$key]);
-            $adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php';
-            if (!is_file($adminPath))
-            {
-                $output->error("controller not found");
-                return;
+        if (!in_array('all-controller', $controller)) {
+            foreach ($controller as $index => $item) {
+                $controllerArr = explode('/', $item);
+                end($controllerArr);
+                $key = key($controllerArr);
+                $controllerArr[$key] = ucfirst($controllerArr[$key]);
+                $adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php';
+                if (!is_file($adminPath)) {
+                    $output->error("controller not found");
+                    return;
+                }
+                $this->importRule($item);
             }
-            $this->importRule($controller);
-        }
-        else
-        {
+
+        } else {
             $this->model->where('id', '>', 0)->delete();
             $controllerDir = $adminPath . 'controller' . DS;
             // 扫描新的节点信息并导入
@@ -103,16 +104,11 @@ class Menu extends Command
     {
         $result = [];
         $cdir = scandir($dir);
-        foreach ($cdir as $value)
-        {
-            if (!in_array($value, array(".", "..")))
-            {
-                if (is_dir($dir . DS . $value))
-                {
+        foreach ($cdir as $value) {
+            if (!in_array($value, array(".", ".."))) {
+                if (is_dir($dir . DS . $value)) {
                     $result[$value] = $this->scandir($dir . DS . $value);
-                }
-                else
-                {
+                } else {
                     $result[] = $value;
                 }
             }
@@ -129,19 +125,14 @@ class Menu extends Command
     public function import($dirarr, $parentdir = [])
     {
         $menuarr = [];
-        foreach ($dirarr as $k => $v)
-        {
-            if (is_array($v))
-            {
+        foreach ($dirarr as $k => $v) {
+            if (is_array($v)) {
                 //当前是文件夹
                 $nowparentdir = array_merge($parentdir, [$k]);
                 $this->import($v, $nowparentdir);
-            }
-            else
-            {
+            } else {
                 //只匹配PHP文件
-                if (!preg_match('/^(\w+)\.php$/', $v, $matchone))
-                {
+                if (!preg_match('/^(\w+)\.php$/', $v, $matchone)) {
                     continue;
                 }
                 //导入文件
@@ -177,8 +168,7 @@ class Menu extends Command
         //反射机制调用类的注释和方法名
         $reflector = new ReflectionClass($className);
 
-        if (isset($tempClassFile))
-        {
+        if (isset($tempClassFile)) {
             //删除临时文件
             @unlink($tempClassFile);
         }
@@ -190,34 +180,27 @@ class Menu extends Command
         $softDeleteMethods = ['destroy', 'restore', 'recyclebin'];
         $withSofeDelete = false;
         preg_match_all("/\\\$this\->model\s*=\s*model\('(\w+)'\);/", $classContent, $matches);
-        if (isset($matches[1]) && isset($matches[1][0]) && $matches[1][0])
-        {
+        if (isset($matches[1]) && isset($matches[1][0]) && $matches[1][0]) {
             \think\Request::instance()->module('admin');
             $model = model($matches[1][0]);
-            if (in_array('trashed', get_class_methods($model)))
-            {
+            if (in_array('trashed', get_class_methods($model))) {
                 $withSofeDelete = true;
             }
         }
         //忽略的类
-        if (stripos($classComment, "@internal") !== FALSE)
-        {
+        if (stripos($classComment, "@internal") !== FALSE) {
             return;
         }
         preg_match_all('#(@.*?)\n#s', $classComment, $annotations);
         $controllerIcon = 'fa fa-circle-o';
         $controllerRemark = '';
         //判断注释中是否设置了icon值
-        if (isset($annotations[1]))
-        {
-            foreach ($annotations[1] as $tag)
-            {
-                if (stripos($tag, '@icon') !== FALSE)
-                {
+        if (isset($annotations[1])) {
+            foreach ($annotations[1] as $tag) {
+                if (stripos($tag, '@icon') !== FALSE) {
                     $controllerIcon = substr($tag, stripos($tag, ' ') + 1);
                 }
-                if (stripos($tag, '@remark') !== FALSE)
-                {
+                if (stripos($tag, '@remark') !== FALSE) {
                     $controllerRemark = substr($tag, stripos($tag, ' ') + 1);
                 }
             }
@@ -230,8 +213,7 @@ class Menu extends Command
 
         //先导入菜单的数据
         $pid = 0;
-        foreach ($controllerArr as $k => $v)
-        {
+        foreach ($controllerArr as $k => $v) {
             $key = $k + 1;
             $name = strtolower(implode('/', array_slice($controllerArr, 0, $key)));
             $title = (!isset($controllerArr[$key]) ? $controllerTitle : '');
@@ -239,42 +221,34 @@ class Menu extends Command
             $remark = (!isset($controllerArr[$key]) ? $controllerRemark : '');
             $title = $title ? $title : $v;
             $rulemodel = $this->model->get(['name' => $name]);
-            if (!$rulemodel)
-            {
+            if (!$rulemodel) {
                 $this->model
-                        ->data(['pid' => $pid, 'name' => $name, 'title' => $title, 'icon' => $icon, 'remark' => $remark, 'ismenu' => 1, 'status' => 'normal'])
-                        ->isUpdate(false)
-                        ->save();
+                    ->data(['pid' => $pid, 'name' => $name, 'title' => $title, 'icon' => $icon, 'remark' => $remark, 'ismenu' => 1, 'status' => 'normal'])
+                    ->isUpdate(false)
+                    ->save();
                 $pid = $this->model->id;
-            }
-            else
-            {
+            } else {
                 $pid = $rulemodel->id;
             }
         }
         $ruleArr = [];
-        foreach ($methods as $m => $n)
-        {
+        foreach ($methods as $m => $n) {
             //过滤特殊的类
-            if (substr($n->name, 0, 2) == '__' || $n->name == '_initialize')
-            {
+            if (substr($n->name, 0, 2) == '__' || $n->name == '_initialize') {
                 continue;
             }
             //未启用软删除时过滤相关方法
-            if (!$withSofeDelete && in_array($n->name, $softDeleteMethods))
-            {
+            if (!$withSofeDelete && in_array($n->name, $softDeleteMethods)) {
                 continue;
             }
             //只匹配符合的方法
-            if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo))
-            {
+            if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo)) {
                 unset($methods[$m]);
                 continue;
             }
             $comment = $reflector->getMethod($n->name)->getDocComment();
             //忽略的方法
-            if (stripos($comment, "@internal") !== FALSE)
-            {
+            if (stripos($comment, "@internal") !== FALSE) {
                 continue;
             }
             //过滤掉其它字符
@@ -293,11 +267,10 @@ class Menu extends Command
     //获取主键
     protected function getAuthRulePK($name)
     {
-        if (!empty($name))
-        {
+        if (!empty($name)) {
             $id = $this->model
-                    ->where('name', $name)
-                    ->value('id');
+                ->where('name', $name)
+                ->value('id');
             return $id ? $id : null;
         }
     }

+ 133 - 123
application/admin/common.php

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

+ 2 - 0
application/admin/lang/zh-cn.php

@@ -113,6 +113,8 @@ return [
     'Set to normal'                                         => '设为正常',
     'Set to hidden'                                         => '设为隐藏',
     //提示
+    'Go back'                                               => '返回首页',
+    'Jump now'                                              => '立即跳转',
     'Operation completed'                                   => '操作成功!',
     'Operation failed'                                      => '操作失败!',
     'Unknown data format'                                   => '未知的数据格式!',

+ 1 - 1
application/admin/lang/zh-cn/addon.php

@@ -23,7 +23,7 @@ return [
     'Pay new window tips'            => '请在新弹出的窗口中进行支付,支付完成后再重新点击安装按钮进行安装!',
     'Uninstall tips'                 => '确认卸载插件?<p class="text-danger">卸载将会删除所有插件文件且不可找回!!! 插件如果有创建数据库表请手动删除!!!</p>如有重要数据请备份后再操作!',
     'Upgrade tips'                   => '确认升级插件?<p class="text-danger">如果之前购买插件时未登录,此次升级可能出现购买后才可以下载的提示!!!<br>升级后可能出现部分冗余数据记录,请根据需要移除即可!!!</p>如有重要数据请备份后再操作!',
-    'Offline installed tips'         => '插件安装成功!你需要手动启用该插件,并清除缓存使之生效',
+    'Offline installed tips'         => '插件安装成功!清除插件缓存和框架缓存后生效!',
     'Online installed tips'          => '插件安装成功!清除插件缓存和框架缓存后生效!',
     'Not login tips'                 => '你当前未登录FastAdmin,登录后将同步已购买的记录,下载时无需二次付费!',
     'Not installed tips'             => '请安装后再访问插件前台页面!',

+ 1 - 0
application/admin/library/traits/Backend.php

@@ -31,6 +31,7 @@ trait Backend
                     ->limit($offset, $limit)
                     ->select();
 
+            $list = collection($list)->toArray();
             $result = array("total" => $total, "rows" => $list);
 
             return json($result);

+ 3 - 3
application/admin/view/common/header.html

@@ -90,13 +90,13 @@
                     <li class="user-body">
                         <div class="row">
                             <div class="col-xs-4 text-center">
-                                <a href="http://www.fastadmin.net" target="_blank">官网</a>
+                                <a href="http://www.fastadmin.net" target="_blank">{:__('FastAdmin')}</a>
                             </div>
                             <div class="col-xs-4 text-center">
-                                <a href="http://forum.fastadmin.net" target="_blank">论坛</a>
+                                <a href="http://forum.fastadmin.net" target="_blank">{:__('Forum')}</a>
                             </div>
                             <div class="col-xs-4 text-center">
-                                <a href="http://doc.fastadmin.net" target="_blank">文档</a>
+                                <a href="http://doc.fastadmin.net" target="_blank">{:__('Docs')}</a>
                             </div>
                         </div>
                     </li>

+ 2 - 0
application/common/lang/zh-cn/addon.php

@@ -85,6 +85,8 @@ return [
     'Donation'                                    => '捐赠',
     'Forum'                                       => '社区',
     'Docs'                                        => '文档',
+    'Go back'                                     => '返回首页',
+    'Jump now'                                    => '立即跳转',
     'Please login first'                          => '请登录后再操作',
     'Send verification code'                      => '发送验证码',
     'Redirect now'                                => '立即跳转',

+ 10 - 10
application/common/view/tpl/dispatch_jump.tpl

@@ -2,7 +2,7 @@
 <html>
     <head>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-        <title>跳转提示</title>
+        <title>{:__('Warning')}</title>
         <meta name="viewport" content="width=device-width, initial-scale=1.0">
         <link rel="shortcut icon" href="__CDN__/assets/img/favicon.ico" />
         <style type="text/css">
@@ -27,17 +27,18 @@
         </style>
     </head>
     <body>
-        <div class="system-message <?php echo $code == 1 ? 'success' : ($code == 0 ? 'error' : 'info');?>">
+        {php}$codeText=$code == 1 ? 'success' : ($code == 0 ? 'error' : 'info');{/php}
+        <div class="system-message {$codeText}">
             <div class="image">
-                <img src="__CDN__/assets/img/<?php echo $code == 1 ? 'success' : ($code == 0 ? 'error' : 'info');?>.svg" alt="" width="150" />
+                <img src="__CDN__/assets/img/{$codeText}.svg" alt="" width="150" />
             </div>
-            <h1><?php echo($msg);?></h1>
+            <h1>{$msg}</h1>
             <p class="jump">
-                页面将在 <span id="wait"><?php echo($wait);?></span> 秒后自动<a id="href" href="<?php echo($url);?>">跳转</a>
+                {:__('This page will be re-directed in %s seconds', '<span id="wait">' . $wait . '</span>')}
             </p>
             <p class="clearfix">
-                <a href="javascript:history.go(-1);" class="btn btn-grey">返回上一步</a>
-                <a href="<?php echo($url);?>" class="btn btn-primary">立即跳转</a>
+                <a href="javascript:history.go(-1);" class="btn btn-grey">{:__('Go back')}</a>
+                <a href="{$url}" class="btn btn-primary">{:__('Jump now')}</a>
             </p>
         </div>
         <div class="copyright">
@@ -45,12 +46,11 @@
         </div>
         <script type="text/javascript">
             (function () {
-                var wait = document.getElementById('wait'),
-                        href = document.getElementById('href').href;
+                var wait = document.getElementById('wait');
                 var interval = setInterval(function () {
                     var time = --wait.innerHTML;
                     if (time <= 0) {
-                        location.href = href;
+                        location.href = "{$url}";
                         clearInterval(interval);
                     }
                 }, 1000);

+ 27 - 7
application/common/view/tpl/think_exception.tpl

@@ -1,12 +1,33 @@
 <?php
 $cdnurl = function_exists('config') ? config('view_replace_str.__CDN__') : '';
 $publicurl = function_exists('config') ? config('view_replace_str.__PUBLIC__') : '/';
+
+$lang = [
+    'An error occurred' => '发生错误',
+    'Home' => '返回主页',
+    'Feedback' => '反馈错误',
+    'The page you are looking for is temporarily unavailable' => '你所浏览的页面暂时无法访问',
+    'You can return to the previous page and try again' => '你可以返回上一页重试,或直接向我们反馈错误报告'
+];
+
+$langSet = '';
+
+if (isset($_GET['lang'])) {
+    $langSet = strtolower($_GET['lang']);
+} elseif (isset($_COOKIE['think_var'])) {
+    $langSet = strtolower($_COOKIE['think_var']);
+} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+    preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
+    $langSet     = strtolower($matches[1]);
+}
+$langSet = $langSet && in_array($langSet, ['zh-cn', 'en']) ? $langSet : 'zh-cn';
+$langSet == 'en' && $lang = array_combine(array_keys($lang), array_keys($lang));
 ?>
 <!DOCTYPE html>
 <html>
     <head>
         <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
-        <title>发生错误</title>
+        <title><?=$lang['An error occurred']?></title>
         <meta name="robots" content="noindex,nofollow" />
         <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
         <link rel="shortcut icon" href="<?php echo $cdnurl;?>/assets/img/favicon.ico" />
@@ -54,25 +75,24 @@ $publicurl = function_exists('config') ? config('view_replace_str.__PUBLIC__') :
         </style>
     </head>
     <body class="error-page-wrapper">
-
         <div class="content-container">
             <div class="head-line">
-                <img src="<?php echo $cdnurl;?>/assets/img/error.svg" alt="" width="120" />
+                <img src="<?=$cdnurl?>/assets/img/error.svg" alt="" width="120"/>
             </div>
             <div class="subheader">
-                <?php echo htmlentities($message); ?>
+                <?=$lang['The page you are looking for is temporarily unavailable']?>
             </div>
             <div class="hr"></div>
             <div class="context">
 
                 <p>
-                    你可以返回上一页重试,或直接向我们反馈错误报告
+                    <?=$lang['You can return to the previous page and try again']?>
                 </p>
 
             </div>
             <div class="buttons-container">
-                <a href="<?php echo $publicurl;?>">返回主页</a>
-                <a href="<?php echo $publicurl;?>">反馈错误</a>
+                <a href="<?=$publicurl?>"><?=$lang['Home']?></a>
+                <a href="<?=$publicurl?>"><?=$lang['Feedback']?></a>
             </div>
         </div>
     </body>

+ 1 - 1
application/config.php

@@ -253,7 +253,7 @@ return [
         //自动检测更新
         'checkupdate'      => false,
         //版本号
-        'version'          => '1.0.0.20180310_beta',
+        'version'          => '1.0.0.20180314_beta',
         'api_url'          => 'http://api.fastadmin.net',
     ],
 ];

+ 13 - 0
application/index/lang/en/index.php

@@ -0,0 +1,13 @@
+<?php
+
+return [
+    'Title'           => 'Title',
+    'Auth tips'       => 'Unlimited parent-child permission grouping, administrator can belong to multiple groups at the same time',
+    'Responsive tips' => 'Based on Bootstrap and AdminLTE, mobile phones, tablets, PCs are automatically adapted',
+    'Languages tips'  => 'Backend and Frontend support, View and JS share the same language package',
+    'Module tips'     => 'RequireJS and Bower for frontend package component management,Composer for backend package component management',
+    'CRUD tips'       => 'One key to generate a controller, model, view and JS file, one key to generate an API document and auth rule',
+    'Extension tips'  => 'Installed and uninstalled directly online plug-in, supporting the command line one key operation',
+    'Do not hesitate' => 'Do not hesitate',
+    'Start to act'    => 'Start Action',
+];

+ 4 - 0
application/index/lang/zh-cn.php

@@ -68,6 +68,8 @@ return [
     'Parent'                                                 => '父级',
     'Params'                                                 => '参数',
     'Permission'                                             => '权限',
+    'Go back'                                                => '返回上一页',
+    'Jump now'                                               => '立即跳转',
     'Advance search'                                         => '高级搜索',
     'Check all'                                              => '选中全部',
     'Expand all'                                             => '展开全部',
@@ -96,6 +98,8 @@ return [
     'Donation'                                               => '捐赠',
     'Forum'                                                  => '社区',
     'Docs'                                                   => '文档',
+    'Go back'                                                => '返回首页',
+    'Jump now'                                               => '立即跳转',
     'Please login first'                                     => '请登录后再操作',
     'Send verification code'                                 => '发送验证码',
     'Redirect now'                                           => '立即跳转',

+ 9 - 1
application/index/lang/zh-cn/index.php

@@ -1,5 +1,13 @@
 <?php
 
 return [
-    'Title' => '标题',
+    'Title'           => '标题',
+    'Auth tips'       => '基于完善的Auth权限控制管理、无限父子级权限分组、可自由分配子级权限、一个管理员可同时属于多个组别',
+    'Responsive tips' => '基于Bootstrap和AdminLTE进行二次开发,手机、平板、PC均自动适配,无需要担心兼容性问题',
+    'Languages tips'  => '不仅仅后台开发支持多语言,同时视图部分和JS部分仍然共享同一个语言包,语法相同且自动加载',
+    'Module tips'     => '控制器、模型、视图、JS一一对应,使用RequireJS进行JS模块化管理,采用Bower进行前端包组件管理',
+    'CRUD tips'       => '控制台进行一键生成控制器、模型、视图和JS文件,一键生成API文档,一键生成后台权限节点和菜单栏',
+    'Extension tips'  => 'FastAdmin提供强大的扩展中心,可直接在线安装和卸载插件,同时支持命令行一键操作',
+    'Do not hesitate' => '不要犹豫',
+    'Start to act'    => '开始行动',
 ];

+ 5 - 0
application/index/lang/zh-cn/user.php

@@ -33,6 +33,9 @@ return [
     'Mobile already exist'                  => '手机号已经存在',
     'Username is incorrect'                 => '用户名不正确',
     'Email is incorrect'                    => '邮箱不正确',
+    'Reset password'                        => '修改密码',
+    'Reset password by email'               => '通过邮箱',
+    'Reset password by mobile'              => '通过手机重置',
     'Account is locked'                     => '账户已经被锁定',
     'Password is incorrect'                 => '密码不正确',
     'Account is incorrect'                  => '账户不正确',
@@ -41,6 +44,7 @@ return [
     'Username or password is incorrect'     => '用户名或密码不正确',
     'You are not logged in'                 => '你当前还未登录',
     'You\'ve logged in, do not login again' => '你已经登录,请不要重复登录',
+    'This guy hasn\'t written anything yet' => '这个人很懒,啥也没写',
     'Profile'                               => '个人资料',
     'Old password'                          => '旧密码',
     'New password'                          => '新密码',
@@ -50,6 +54,7 @@ return [
     'New mobile'                            => '新手机号',
     'Change password successful'            => '修改密码成功',
     'Captcha is incorrect'                  => '验证码不正确',
+    'Upload successful'                     => '上传成功',
     'Sign up successful'                    => '注册成功',
     'Logged in successful'                  => '登录成功',
     'Logout successful'                     => '注销成功',

+ 7 - 7
application/index/view/index/index.html

@@ -87,42 +87,42 @@
                                     <div class="feature-item">
                                         <i class="icon-user text-primary"></i>
                                         <h3>{:__('Auth')}</h3>
-                                        <p class="text-muted">基于完善的Auth权限控制管理、无限父子级权限分组、可自由分配子级权限、一个管理员可同时属于多个组别</p>
+                                        <p class="text-muted">{:__('Auth tips')}</p>
                                     </div>
                                 </div>
                                 <div class="col-md-4">
                                     <div class="feature-item">
                                         <i class="icon-screen-smartphone text-primary"></i>
                                         <h3>{:__('Responsive')}</h3>
-                                        <p class="text-muted">基于Bootstrap和AdminLTE进行二次开发,手机、平板、PC均自动适配,无需要担心兼容性问题</p>
+                                        <p class="text-muted">{:__('Responsive tips')}</p>
                                     </div>
                                 </div>
                                 <div class="col-md-4">
                                     <div class="feature-item">
                                         <i class="icon-present text-primary"></i>
                                         <h3>{:__('Languages')}</h3>
-                                        <p class="text-muted">不仅仅后台开发支持多语言,同时视图部分和JS部分仍然共享同一个语言包,语法相同且自动加载</p>
+                                        <p class="text-muted">{:__('Languages tips')}</p>
                                     </div>
                                 </div>
                                 <div class="col-md-4">
                                     <div class="feature-item">
                                         <i class="icon-layers text-primary"></i>
                                         <h3>{:__('Module')}</h3>
-                                        <p class="text-muted">控制器、模型、视图、JS一一对应,使用RequireJS进行JS模块化管理,采用Bower进行前端包组件管理</p>
+                                        <p class="text-muted">{:__('Module tips')}</p>
                                     </div>
                                 </div>
                                 <div class="col-md-4">
                                     <div class="feature-item">
                                         <i class="icon-docs text-primary"></i>
                                         <h3>{:__('CRUD')}</h3>
-                                        <p class="text-muted">控制台进行一键生成控制器、模型、视图和JS文件,同时可一键生成后台权限节点和菜单栏</p>
+                                        <p class="text-muted">{:__('CRUD tips')}</p>
                                     </div>
                                 </div>
                                 <div class="col-md-4">
                                     <div class="feature-item">
                                         <i class="icon-puzzle text-primary"></i>
                                         <h3>{:__('Extension')}</h3>
-                                        <p class="text-muted">FastAdmin提供强大的扩展中心,可直接在线安装和卸载插件,同时支持命令行一键操作</p>
+                                        <p class="text-muted">{:__('Extension tips')}</p>
                                     </div>
                                 </div>
                             </div>
@@ -135,7 +135,7 @@
         <section class="cta">
             <div class="cta-content">
                 <div class="container">
-                    <h2>不要犹豫<br>开始行动</h2>
+                    <h2>{:__('Do not hesitate')}<br>{:__('Start to act')}</h2>
                     <a href="http://doc.fastadmin.net/docs/contributing.html" class="btn btn-outline btn-xl page-scroll">{:__('Contribution')}</a>
                 </div>
             </div>

+ 3 - 3
application/index/view/user/changepwd.html

@@ -10,19 +10,19 @@
                     <form id="changepwd-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
                         {:token()}
                         <div class="form-group">
-                            <label for="c-name" class="control-label col-xs-12 col-sm-2">旧密码:</label>
+                            <label for="oldpassword" class="control-label col-xs-12 col-sm-2">{:__('Old password')}:</label>
                             <div class="col-xs-12 col-sm-4">
                                 <input type="password" class="form-control" id="oldpassword" name="oldpassword" value="" data-rule="required" placeholder="{:__('Old password')}">
                             </div>
                         </div>
                         <div class="form-group">
-                            <label for="c-name" class="control-label col-xs-12 col-sm-2">{:__('New password')}:</label>
+                            <label for="newpassword" class="control-label col-xs-12 col-sm-2">{:__('New password')}:</label>
                             <div class="col-xs-12 col-sm-4">
                                 <input type="password" class="form-control" id="newpassword" name="newpassword" value="" data-rule="required" placeholder="{:__('New password')}" />
                             </div>
                         </div>
                         <div class="form-group">
-                            <label for="c-name" class="control-label col-xs-12 col-sm-2">{:__('Renew password')}:</label>
+                            <label for="renewpassword" class="control-label col-xs-12 col-sm-2">{:__('Renew password')}:</label>
                             <div class="col-xs-12 col-sm-4">
                                 <input type="password" class="form-control" id="renewpassword" name="renewpassword" value="" data-rule="required" placeholder="{:__('Renew password')}" />
                             </div>

+ 1 - 1
application/index/view/user/index.html

@@ -19,7 +19,7 @@
                                 <!-- Heading -->
                                 <h4><a href="{:url('user/profile')}">{$user.username}</a></h4>
                                 <!-- Paragraph -->
-                                <p><a href="{:url('user/profile')}">{$user.bio|default='这个人很懒,啥也没写'}</a></p>
+                                <p><a href="{:url('user/profile')}">{$user.bio|default=__("This guy hasn't written anything yet")}</a></p>
                                 <!-- Success -->
                                 <div style="margin-top:15px;">
                                     <table class="table">	

+ 2 - 2
application/index/view/user/login.html

@@ -37,8 +37,8 @@
                 <label for="" class="control-label col-xs-12 col-sm-3">{:__('Type')}:</label>
                 <div class="col-xs-12 col-sm-8">
                     <div class="radio">
-                        <label for="type-email"><input id="type-email" checked="checked" name="type" data-send-url="{:url('api/ems/send')}" data-check-url="{:url('api/validate/check_ems_correct')}" type="radio" value="email"> 通过邮箱找回</label> 
-                        <label for="type-mobile"><input id="type-mobile" name="type" type="radio" data-send-url="{:url('api/sms/send')}" data-check-url="{:url('api/validate/check_sms_correct')}" value="mobile"> 通过手机找回</label>
+                        <label for="type-email"><input id="type-email" checked="checked" name="type" data-send-url="{:url('api/ems/send')}" data-check-url="{:url('api/validate/check_ems_correct')}" type="radio" value="email"> {:__('Reset password by email')}</label>
+                        <label for="type-mobile"><input id="type-mobile" name="type" type="radio" data-send-url="{:url('api/sms/send')}" data-check-url="{:url('api/validate/check_sms_correct')}" value="mobile"> {:__('Reset password by mobile')}</label>
                     </div>        
                 </div>
             </div>

+ 6 - 6
application/index/view/user/profile.html

@@ -41,7 +41,7 @@
                         {:token()}
                         <input type="hidden" name="avatar" id="c-avatar" value="{$user.avatar}" />
                         <div class="form-group">
-                            <label for="c-name" class="control-label col-xs-12 col-sm-2"></label>
+                            <label class="control-label col-xs-12 col-sm-2"></label>
                             <div class="col-xs-12 col-sm-4">
                                 <div class="profile-avatar-container">
                                     <img class="profile-user-img img-responsive img-circle plupload" src="{$user.avatar}" alt="">
@@ -51,13 +51,13 @@
                             </div>
                         </div>
                         <div class="form-group">
-                            <label for="c-username" class="control-label col-xs-12 col-sm-2">{:__('Username')}:</label>
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Username')}:</label>
                             <div class="col-xs-12 col-sm-4">
                                 <input type="text" class="form-control" id="username" name="username" value="{$user.username}" data-rule="required;username;remote({:url('api/validate/check_username_available')}, id={$user.id})" placeholder="">
                             </div>
                         </div>
                         <div class="form-group">
-                            <label for="c-nickname" class="control-label col-xs-12 col-sm-2">{:__('Nickname')}:</label>
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Nickname')}:</label>
                             <div class="col-xs-12 col-sm-4">
                                 <input type="text" class="form-control" id="nickname" name="nickname" value="{$user.nickname}" data-rule="required" placeholder="">
                             </div>
@@ -111,14 +111,14 @@
         <div class="form-body">
             <input type="hidden" name="action" value="changeemail" />
             <div class="form-group">
-                <label for="c-email" class="control-label col-xs-12 col-sm-3">{:__('New Email')}:</label>
+                <label class="control-label col-xs-12 col-sm-3">{:__('New Email')}:</label>
                 <div class="col-xs-12 col-sm-8">
                     <input type="text" class="form-control" id="email" name="email" value="" data-rule="required;email;remote({:url('api/validate/check_email_available')}, event=changeemail, id={$user.id})" placeholder="{:__('New email')}">
                     <span class="msg-box"></span>
                 </div>
             </div>
             <div class="form-group">
-                <label for="c-captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
+                <label class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
                 <div class="col-xs-12 col-sm-8">
                     <div class="input-group">
                         <input type="text" name="captcha" id="email-captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_ems_correct')}, event=changeemail, email:#email)" />
@@ -152,7 +152,7 @@
                 </div>
             </div>
             <div class="form-group">
-                <label for="c-captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
+                <label for="mobile-captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
                 <div class="col-xs-12 col-sm-8">
                     <div class="input-group">
                         <input type="text" name="captcha" id="mobile-captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_sms_correct')}, event=changemobile, mobile:#mobile)" />

+ 19 - 19
bower.json

@@ -9,26 +9,26 @@
     "jquery": "^2.1.4",
     "bootstrap": "^3.3.7",
     "font-awesome": "^4.6.1",
-    "bootstrap-table": "^1.11.0",
+    "bootstrap-table": "~1.11.0",
     "layer": "^3.0",
-    "jstree": "^3.3.2",
-    "moment": "^2.15.2",
-    "plupload": "^2.2.0",
-    "toastr": "^2.1.3",
-    "jcrop": "^2.0.4",
-    "eonasdan-bootstrap-datetimepicker": "^4.17.43",
-    "bootstrap-select": "^1.11.2",
-    "require-css": "^0.1.8",
-    "less": "^2.7.1",
-    "tableExport.jquery.plugin": "^1.9.0",
-    "jquery-slimscroll": "^1.3.8",
-    "jquery.cookie": "^1.4.1",
-    "Sortable": "^1.5.0",
-    "nice-validator": "^1.1.1",
-    "art-template": "^3.0.1",
-    "requirejs-plugins": "^1.0.3",
-    "bootstrap-daterangepicker": "^2.1.25",
-    "city-picker": "^1.1.0",
+    "jstree": "~3.3.2",
+    "moment": "~2.15.2",
+    "plupload": "~2.2.0",
+    "toastr": "~2.1.3",
+    "jcrop": "~2.0.4",
+    "eonasdan-bootstrap-datetimepicker": "~4.17.43",
+    "bootstrap-select": "~1.11.2",
+    "require-css": "~0.1.8",
+    "less": "~2.7.1",
+    "tableExport.jquery.plugin": "~1.9.0",
+    "jquery-slimscroll": "~1.3.8",
+    "jquery.cookie": "~1.4.1",
+    "Sortable": "~1.5.0",
+    "nice-validator": "~1.1.1",
+    "art-template": "^3.1.3",
+    "requirejs-plugins": "~1.0.3",
+    "bootstrap-daterangepicker": "~2.1.25",
+    "city-picker": "~1.1.0",
     "fastadmin-cxselect": "~1.4.0",
     "fastadmin-dragsort": "~1.0.0",
     "fastadmin-addtabs": "~1.0.0",

+ 1 - 1
public/api.html

@@ -3351,7 +3351,7 @@
 
             <div class="row mt0 footer">
                 <div class="col-md-6" align="left">
-                    Generated on 2018-03-10 00:52:53                </div>
+                    Generated on 2018-03-13 19:46:39                </div>
                 <div class="col-md-6" align="right">
                     <a href="http://www.fastadmin.net" target="_blank">FastAdmin</a>
                 </div>

+ 2 - 3
public/assets/js/frontend/user.js

@@ -37,7 +37,7 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
                 var content = Template(id, {});
                 Layer.open({
                     type: 1,
-                    title: "修改",
+                    title: __('Reset password'),
                     area: ["450px", "355px"],
                     content: content,
                     success: function (layero) {
@@ -75,7 +75,7 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
             $("#plupload-avatar").data("upload-success", function (data) {
                 var url = Fast.api.cdnurl(data.url);
                 $(".profile-user-img").prop("src", url);
-                Toastr.success("上传成功!");
+                Toastr.success(__('Upload successful'));
             });
             Form.api.bindevent($("#profile-form"));
             $(document).on("click", ".btn-change", function () {
@@ -91,7 +91,6 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
                         var form = $("form", layero);
                         Form.api.bindevent(form, function (data) {
                             Layer.closeAll();
-                            console.log(123);
                         });
                     }
                 });

File diff suppressed because it is too large
+ 3643 - 3879
public/assets/js/require-backend.min.js


+ 45 - 25
public/assets/js/require-table.js

@@ -202,12 +202,12 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     var that = this;
                     var ids = Table.api.selectedids(table);
                     Layer.confirm(
-                            __('Are you sure you want to delete the %s selected item?', ids.length),
-                            {icon: 3, title: __('Warning'), offset: 0, shadeClose: true},
-                            function (index) {
-                                Table.api.multi("del", ids, table, that);
-                                Layer.close(index);
-                            }
+                        __('Are you sure you want to delete the %s selected item?', ids.length),
+                        {icon: 3, title: __('Warning'), offset: 0, shadeClose: true},
+                        function (index) {
+                            Table.api.multi("del", ids, table, that);
+                            Layer.close(index);
+                        }
                     );
                 });
                 // 拖拽排序
@@ -281,12 +281,12 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     var id = $(this).data("id");
                     var that = this;
                     Layer.confirm(
-                            __('Are you sure you want to delete this item?'),
-                            {icon: 3, title: __('Warning'), shadeClose: true},
-                            function (index) {
-                                Table.api.multi("del", id, table, that);
-                                Layer.close(index);
-                            }
+                        __('Are you sure you want to delete this item?'),
+                        {icon: 3, title: __('Warning'), shadeClose: true},
+                        function (index) {
+                            Table.api.multi("del", id, table, that);
+                            Layer.close(index);
+                        }
                     );
                 });
                 var id = table.attr("id");
@@ -345,14 +345,14 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                             top = left = undefined;
                         }
                         Layer.confirm(
-                                __('Are you sure you want to delete this item?'),
-                                {icon: 3, title: __('Warning'), offset: [top, left], shadeClose: true},
-                                function (index) {
-                                    var table = $(that).closest('table');
-                                    var options = table.bootstrapTable('getOptions');
-                                    Table.api.multi("del", row[options.pk], table, that);
-                                    Layer.close(index);
-                                }
+                            __('Are you sure you want to delete this item?'),
+                            {icon: 3, title: __('Warning'), offset: [top, left], shadeClose: true},
+                            function (index) {
+                                var table = $(that).closest('table');
+                                var options = table.bootstrapTable('getOptions');
+                                Table.api.multi("del", row[options.pk], table, that);
+                                Layer.close(index);
+                            }
                         );
                     }
                 }
@@ -449,13 +449,29 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     var buttons = $.extend([], this.buttons || []);
 
                     if (options.extend.dragsort_url !== '') {
-                        buttons.push({name: 'dragsort', icon: 'fa fa-arrows', title: __('Drag to sort'), classname: 'btn btn-xs btn-primary btn-dragsort'});
+                        buttons.push({
+                            name: 'dragsort',
+                            icon: 'fa fa-arrows',
+                            title: __('Drag to sort'),
+                            classname: 'btn btn-xs btn-primary btn-dragsort'
+                        });
                     }
                     if (options.extend.edit_url !== '') {
-                        buttons.push({name: 'edit', icon: 'fa fa-pencil', title: __('Edit'), classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
+                        buttons.push({
+                            name: 'edit',
+                            icon: 'fa fa-pencil',
+                            title: __('Edit'),
+                            classname: 'btn btn-xs btn-success btn-editone',
+                            url: options.extend.edit_url
+                        });
                     }
                     if (options.extend.del_url !== '') {
-                        buttons.push({name: 'del', icon: 'fa fa-trash', title: __('Del'), classname: 'btn btn-xs btn-danger btn-delone'});
+                        buttons.push({
+                            name: 'del',
+                            icon: 'fa fa-trash',
+                            title: __('Del'),
+                            classname: 'btn btn-xs btn-danger btn-delone'
+                        });
                     }
                     return Table.api.buttonlink(this, buttons, value, row, index, 'operate');
                 },
@@ -470,7 +486,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                 type = typeof type === 'undefined' ? 'buttons' : type;
                 var options = table ? table.bootstrapTable('getOptions') : {};
                 var html = [];
-                var url, classname, icon, text, title, extend;
+                var hidden, url, classname, icon, text, title, refresh, confirm, extend;
                 var fieldIndex = column.fieldIndex;
 
                 $.each(buttons, function (i, j) {
@@ -484,8 +500,12 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     }
                     var attr = table.data(type + "-" + j.name);
                     if (typeof attr === 'undefined' || attr) {
+                        hidden = typeof j.hidden === 'function' ? j.hidden.call(table, row, j) : (j.hidden ? j.hidden : false);
+                        if (hidden) {
+                            return true;
+                        }
                         url = j.url ? j.url : '';
-                        url = url ? Fast.api.fixurl(Table.api.replaceurl(url, row, table)) : 'javascript:;';
+                        url = typeof url === 'function' ? url.call(table, row, j) : (url ? Fast.api.fixurl(Table.api.replaceurl(url, row, table)) : 'javascript:;');
                         classname = j.classname ? j.classname : 'btn-primary btn-' + name + 'one';
                         icon = j.icon ? j.icon : '';
                         text = j.text ? j.text : '';