Merge branch 'develop'

# Conflicts:
#	application/admin/command/Api.php
#	application/admin/command/Install.php
#	application/config.php
pull/273/head
Karson 2021-01-25 16:57:18 +08:00
commit b82dbd7eee
44 changed files with 31609 additions and 366 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ composer.lock
.env .env
.svn .svn
.vscode .vscode
node_modules

View File

@ -23,9 +23,9 @@ class Api extends Command
->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html') ->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false) ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name']) ->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('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn') ->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
->addOption('addon', 'a', Option::VALUE_OPTIONAL, 'addon name', null)
->addOption('controller', 'r', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name', null) ->addOption('controller', 'r', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name', null)
->setDescription('Build Api document from controller'); ->setDescription('Build Api document from controller');
} }
@ -59,12 +59,21 @@ class Api extends Command
$classes = $input->getOption('class'); $classes = $input->getOption('class');
// 标题 // 标题
$title = $input->getOption('title'); $title = $input->getOption('title');
// 作者
$author = $input->getOption('author');
// 模块 // 模块
$module = $input->getOption('module'); $module = $input->getOption('module');
// 插件
$addon = $input->getOption('addon');
$moduleDir = APP_PATH . $module . DS; $moduleDir = $addonDir = '';
if ($addon) {
$addonInfo = get_addon_info($addon);
if (!$addonInfo) {
throw new Exception('addon not found');
}
$moduleDir = ADDON_PATH . $addon . DS;
} else {
$moduleDir = APP_PATH . $module . DS;
}
if (!is_dir($moduleDir)) { if (!is_dir($moduleDir)) {
throw new Exception('module not found'); throw new Exception('module not found');
} }
@ -81,9 +90,10 @@ class Api extends Command
throw new Exception("Please make sure opcache already enabled, Get help:https://forum.fastadmin.net/d/1321"); throw new Exception("Please make sure opcache already enabled, Get help:https://forum.fastadmin.net/d/1321");
} }
} }
//控制器名 //控制器名
$controller = $input->getOption('controller') ?: ''; $controller = $input->getOption('controller') ?: [];
if(!$controller) { if (!$controller) {
$controllerDir = $moduleDir . Config::get('url_controller_layer') . DS; $controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
$files = new \RecursiveIteratorIterator( $files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($controllerDir), new \RecursiveDirectoryIterator($controllerDir),
@ -96,26 +106,29 @@ class Api extends Command
$classes[] = $this->get_class_from_file($filePath); $classes[] = $this->get_class_from_file($filePath);
} }
} }
} } else {
else{
foreach ($controller as $index => $item) { foreach ($controller as $index => $item) {
$filePath=$moduleDir . Config::get('url_controller_layer') . DS .$item.'.php'; $filePath = $moduleDir . Config::get('url_controller_layer') . DS . $item . '.php';
$classes[] = $this->get_class_from_file($filePath); $classes[] = $this->get_class_from_file($filePath);
} }
} }
$classes = array_unique(array_filter($classes)); $classes = array_unique(array_filter($classes));
$config = [ $config = [
'sitename' => config('site.name'), 'sitename' => config('site.name'),
'title' => $title, 'title' => $title,
'author' => $author, 'author' => config('site.name'),
'description' => '', 'description' => '',
'apiurl' => $url, 'apiurl' => $url,
'language' => $language, 'language' => $language,
]; ];
$builder = new Builder($classes); try {
$content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]); $builder = new Builder($classes);
$content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
} catch (\Exception $e) {
print_r($e);
}
if (!file_put_contents($output_file, $content)) { if (!file_put_contents($output_file, $content)) {
throw new Exception('Cannot save the content to ' . $output_file); throw new Exception('Cannot save the content to ' . $output_file);
} }

View File

@ -16,6 +16,9 @@ return [
'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中', 'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中',
'Apiurltips' => 'API接口URL', 'Apiurltips' => 'API接口URL',
'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中', 'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中',
'Authorization' => '权限',
'NeedLogin' => '登录',
'NeedRight' => '鉴权',
'ReturnHeaders' => '响应头', 'ReturnHeaders' => '响应头',
'ReturnParameters' => '返回参数', 'ReturnParameters' => '返回参数',
'Response' => '响应输出', 'Response' => '响应输出',

View File

@ -43,9 +43,11 @@ class Builder
continue; continue;
} }
Extractor::getClassMethodAnnotations($class); Extractor::getClassMethodAnnotations($class);
//Extractor::getClassPropertyValues($class);
} }
$allClassAnnotation = Extractor::getAllClassAnnotations(); $allClassAnnotation = Extractor::getAllClassAnnotations();
$allClassMethodAnnotation = Extractor::getAllClassMethodAnnotations(); $allClassMethodAnnotation = Extractor::getAllClassMethodAnnotations();
//$allClassPropertyValue = Extractor::getAllClassPropertyValues();
// foreach ($allClassMethodAnnotation as $className => &$methods) { // foreach ($allClassMethodAnnotation as $className => &$methods) {
// foreach ($methods as &$method) { // foreach ($methods as &$method) {
@ -162,10 +164,12 @@ class Builder
list($allClassAnnotations, $allClassMethodAnnotations) = $this->extractAnnotations(); list($allClassAnnotations, $allClassMethodAnnotations) = $this->extractAnnotations();
$sectorArr = []; $sectorArr = [];
foreach ($allClassAnnotations as $index => $allClassAnnotation) { foreach ($allClassAnnotations as $index => &$allClassAnnotation) {
$sector = isset($allClassAnnotation['ApiSector']) ? $allClassAnnotation['ApiSector'][0] : $allClassAnnotation['ApiTitle'][0]; $sector = isset($allClassAnnotation['ApiSector']) ? $allClassAnnotation['ApiSector'][0] : $allClassAnnotation['ApiTitle'][0];
$sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0; $sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0;
} }
unset($allClassAnnotation);
arsort($sectorArr); arsort($sectorArr);
$routes = include_once CONF_PATH . 'route.php'; $routes = include_once CONF_PATH . 'route.php';
$subdomain = false; $subdomain = false;
@ -175,7 +179,7 @@ class Builder
$counter = 0; $counter = 0;
$section = null; $section = null;
$weigh = 0; $weigh = 0;
$docslist = []; $docsList = [];
foreach ($allClassMethodAnnotations as $class => $methods) { foreach ($allClassMethodAnnotations as $class => $methods) {
foreach ($methods as $name => $docs) { foreach ($methods as $name => $docs) {
if (isset($docs['ApiSector'][0])) { if (isset($docs['ApiSector'][0])) {
@ -190,28 +194,30 @@ class Builder
if ($subdomain) { if ($subdomain) {
$route = substr($route, 4); $route = substr($route, 4);
} }
$docslist[$section][$class . $name] = [ $docsList[$section][$name] = [
'id' => $counter, 'id' => $counter,
'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0], 'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
'method_label' => $this->generateBadgeForMethod($docs), 'methodLabel' => $this->generateBadgeForMethod($docs),
'section' => $section, 'section' => $section,
'route' => $route, 'route' => $route,
'title' => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0], 'title' => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0], 'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
'body' => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '', 'body' => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
'headerslist' => $this->generateHeadersTemplate($docs), 'headersList' => $this->generateHeadersTemplate($docs),
'paramslist' => $this->generateParamsTemplate($docs), 'paramsList' => $this->generateParamsTemplate($docs),
'returnheaderslist' => $this->generateReturnHeadersTemplate($docs), 'returnHeadersList' => $this->generateReturnHeadersTemplate($docs),
'returnparamslist' => $this->generateReturnParamsTemplate($docs), 'returnParamsList' => $this->generateReturnParamsTemplate($docs),
'weigh' => is_array($docs['ApiWeigh'][0]) ? $docs['ApiWeigh'][0]['data'] : $docs['ApiWeigh'][0], 'weigh' => is_array($docs['ApiWeigh'][0]) ? $docs['ApiWeigh'][0]['data'] : $docs['ApiWeigh'][0],
'return' => isset($docs['ApiReturn']) ? is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0] : '', 'return' => isset($docs['ApiReturn']) ? is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0] : '',
'needLogin' => $docs['ApiPermissionLogin'][0],
'needRight' => $docs['ApiPermissionRight'][0],
]; ];
$counter++; $counter++;
} }
} }
//重建排序 //重建排序
foreach ($docslist as $index => &$methods) { foreach ($docsList as $index => &$methods) {
$methodSectorArr = []; $methodSectorArr = [];
foreach ($methods as $name => $method) { foreach ($methods as $name => $method) {
$methodSectorArr[$name] = isset($method['weigh']) ? $method['weigh'] : 0; $methodSectorArr[$name] = isset($method['weigh']) ? $method['weigh'] : 0;
@ -219,9 +225,8 @@ class Builder
arsort($methodSectorArr); arsort($methodSectorArr);
$methods = array_merge(array_flip(array_keys($methodSectorArr)), $methods); $methods = array_merge(array_flip(array_keys($methodSectorArr)), $methods);
} }
$docslist = array_merge(array_flip(array_keys($sectorArr)), $docslist); $docsList = array_merge(array_flip(array_keys($sectorArr)), $docsList);
$docslist = array_filter($docslist , function($v) {return is_array($v) ; }) ; return $docsList;
return $docslist;
} }
public function getView() public function getView()
@ -237,8 +242,8 @@ class Builder
*/ */
public function render($template, $vars = []) public function render($template, $vars = [])
{ {
$docslist = $this->parse(); $docsList = $this->parse();
return $this->view->display(file_get_contents($template), array_merge($vars, ['docslist' => $docslist])); return $this->view->display(file_get_contents($template), array_merge($vars, ['docsList' => $docsList]));
} }
} }

View File

@ -24,6 +24,8 @@ class Extractor
private static $classMethodAnnotationCache; private static $classMethodAnnotationCache;
private static $classPropertyValueCache;
/** /**
* Indicates that annotations should has strict behavior, 'false' by default * Indicates that annotations should has strict behavior, 'false' by default
* @var boolean * @var boolean
@ -66,14 +68,16 @@ class Extractor
/** /**
* Gets all anotations with pattern @SomeAnnotation() from a given class * Gets all anotations with pattern @SomeAnnotation() from a given class
* *
* @param string $className class name to get annotations * @param string $className class name to get annotations
* @return array self::$classAnnotationCache all annotated elements * @return array self::$classAnnotationCache all annotated elements
*/ */
public static function getClassAnnotations($className) public static function getClassAnnotations($className)
{ {
if (!isset(self::$classAnnotationCache[$className])) { if (!isset(self::$classAnnotationCache[$className])) {
$class = new \ReflectionClass($className); $class = new \ReflectionClass($className);
self::$classAnnotationCache[$className] = self::parseAnnotations($class->getDocComment()); $annotationArr = self::parseAnnotations($class->getDocComment());
$annotationArr['ApiTitle'] = !isset($annotationArr['ApiTitle'][0]) || !trim($annotationArr['ApiTitle'][0]) ? [$class->getShortName()] : $annotationArr['ApiTitle'];
self::$classAnnotationCache[$className] = $annotationArr;
} }
return self::$classAnnotationCache[$className]; return self::$classAnnotationCache[$className];
@ -96,6 +100,17 @@ class Extractor
return self::$classMethodAnnotationCache[$className]; return self::$classMethodAnnotationCache[$className];
} }
public static function getClassPropertyValues($className)
{
$class = new \ReflectionClass($className);
foreach ($class->getProperties() as $object) {
self::$classPropertyValueCache[$className][$object->name] = self::getClassPropertyValue($className, $object->name);
}
return self::$classMethodAnnotationCache[$className];
}
public static function getAllClassAnnotations() public static function getAllClassAnnotations()
{ {
return self::$classAnnotationCache; return self::$classAnnotationCache;
@ -106,11 +121,25 @@ class Extractor
return self::$classMethodAnnotationCache; return self::$classMethodAnnotationCache;
} }
public static function getAllClassPropertyValues()
{
return self::$classPropertyValueCache;
}
public static function getClassPropertyValue($className, $property)
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$reflectionClass = new \ReflectionClass($className);
$reflectionProperty = $reflectionClass->getProperty($property);
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($reflectionClass->newInstanceWithoutConstructor());
}
/** /**
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class * Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
* *
* @param string $className class name * @param string $className class name
* @param string $methodName method name to get annotations * @param string $methodName method name to get annotations
* @return array self::$annotationCache all annotated elements of a method given * @return array self::$annotationCache all annotated elements of a method given
*/ */
public static function getMethodAnnotations($className, $methodName) public static function getMethodAnnotations($className, $methodName)
@ -138,8 +167,8 @@ class Extractor
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class * Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
* and instance its abcAnnotation class * and instance its abcAnnotation class
* *
* @param string $className class name * @param string $className class name
* @param string $methodName method name to get annotations * @param string $methodName method name to get annotations
* @return array self::$annotationCache all annotated objects of a method given * @return array self::$annotationCache all annotated objects of a method given
*/ */
public function getMethodAnnotationsObjects($className, $methodName) public function getMethodAnnotationsObjects($className, $methodName)
@ -189,7 +218,11 @@ class Extractor
$methodName = $method->getName(); $methodName = $method->getName();
$methodAnnotations = self::parseAnnotations($docblockMethod); $methodAnnotations = self::parseAnnotations($docblockMethod);
$methodAnnotations['ApiTitle'] = !isset($methodAnnotations['ApiTitle'][0]) || !trim($methodAnnotations['ApiTitle'][0]) ? [$method->getName()] : $methodAnnotations['ApiTitle'];
$classAnnotations = self::parseAnnotations($dockblockClass); $classAnnotations = self::parseAnnotations($dockblockClass);
$classAnnotations['ApiTitle'] = !isset($classAnnotations['ApiTitle'][0]) || !trim($classAnnotations['ApiTitle'][0]) ? [$class->getShortName()] : $classAnnotations['ApiTitle'];
if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') { if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') {
return []; return [];
} }
@ -264,15 +297,15 @@ class Extractor
} }
} }
$methodAnnotations['ApiPermissionLogin'] = [!in_array('*', $noNeedLogin) && !in_array($methodName, $noNeedLogin)]; $methodAnnotations['ApiPermissionLogin'] = [!in_array('*', $noNeedLogin) && !in_array($methodName, $noNeedLogin)];
$methodAnnotations['ApiPermissionRight'] = [!in_array('*', $noNeedRight) && !in_array($methodName, $noNeedRight)]; $methodAnnotations['ApiPermissionRight'] = !$methodAnnotations['ApiPermissionLogin'][0] ? false : [!in_array('*', $noNeedRight) && !in_array($methodName, $noNeedRight)];
return $methodAnnotations; return $methodAnnotations;
} }
/** /**
* Parse annotations * Parse annotations
* *
* @param string $docblock * @param string $docblock
* @param string $name * @param string $name
* @return array parsed annotations params * @return array parsed annotations params
*/ */
private static function parseCustomAnnotations($docblock, $name = 'param') private static function parseCustomAnnotations($docblock, $name = 'param')
@ -291,7 +324,7 @@ class Extractor
/** /**
* Parse annotations * Parse annotations
* *
* @param string $docblock * @param string $docblock
* @return array parsed annotations params * @return array parsed annotations params
*/ */
private static function parseAnnotations($docblock) private static function parseAnnotations($docblock)
@ -337,7 +370,7 @@ class Extractor
/** /**
* Parse individual annotation arguments * Parse individual annotation arguments
* *
* @param string $content arguments string * @param string $content arguments string
* @return array annotated arguments * @return array annotated arguments
*/ */
private static function parseArgs($content) private static function parseArgs($content)
@ -480,8 +513,8 @@ class Extractor
/** /**
* Try determinate the original type variable of a string * Try determinate the original type variable of a string
* *
* @param string $val string containing possibles variables that can be cast to bool or int * @param string $val string containing possibles variables that can be cast to bool or int
* @param boolean $trim indicate if the value passed should be trimmed after to try cast * @param boolean $trim indicate if the value passed should be trimmed after to try cast
* @return mixed returns the value converted to original type if was possible * @return mixed returns the value converted to original type if was possible
*/ */
private static function castValue($val, $trim = false) private static function castValue($val, $trim = false)

View File

@ -27,12 +27,12 @@
font-family: "Roboto", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", BlinkMacSystemFont, -apple-system, "Segoe UI", "Microsoft Yahei", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif; font-family: "Roboto", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", BlinkMacSystemFont, -apple-system, "Segoe UI", "Microsoft Yahei", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
font-weight: 400; font-weight: 400;
} }
h2 { font-size: 1.6em; } h2 { font-size: 1.2em; }
hr { margin-top: 10px; } hr { margin-top: 10px; }
.tab-pane { padding-top: 10px; } .tab-pane { padding-top: 10px; }
.mt0 { margin-top: 0px; } .mt0 { margin-top: 0px; }
.footer { font-size: 12px; color: #666; } .footer { font-size: 12px; color: #666; }
.label { display: inline-block; min-width: 65px; padding: 0.3em 0.6em 0.3em; } .docs-list .label { display: inline-block; min-width: 65px; padding: 0.3em 0.6em 0.3em; }
.string { color: green; } .string { color: green; }
.number { color: darkorange; } .number { color: darkorange; }
.boolean { color: blue; } .boolean { color: blue; }
@ -65,12 +65,24 @@
#sidebar > .list-group > a{ #sidebar > .list-group > a{
text-indent:0; text-indent:0;
} }
#sidebar .child > a .tag{
position: absolute;
right: 10px;
top: 11px;
}
#sidebar .child > a .pull-right{
margin-left:3px;
}
#sidebar .child { #sidebar .child {
border:1px solid #ddd; border:1px solid #ddd;
border-bottom:none; border-bottom:none;
} }
#sidebar .child:last-child {
border-bottom:1px solid #ddd;
}
#sidebar .child > a { #sidebar .child > a {
border:0; border:0;
min-height: 40px;
} }
#sidebar .list-group a.current { #sidebar .list-group a.current {
background:#f5f5f5; background:#f5f5f5;
@ -94,6 +106,9 @@
.label-primary { .label-primary {
background-color: #248aff; background-color: #248aff;
} }
.docs-list .panel .panel-body .table {
margin-bottom: 0;
}
</style> </style>
</head> </head>
@ -138,25 +153,34 @@
<!-- menu --> <!-- menu -->
<div id="sidebar"> <div id="sidebar">
<div class="list-group panel"> <div class="list-group panel">
{foreach name="docslist" id="docs"} {foreach name="docsList" id="docs"}
<a href="#{$key}" class="list-group-item" data-toggle="collapse" data-parent="#sidebar">{$key} <i class="fa fa-caret-down"></i></a> <a href="#{$key}" class="list-group-item" data-toggle="collapse" data-parent="#sidebar">{$key} <i class="fa fa-caret-down"></i></a>
<div class="child collapse" id="{$key}"> <div class="child collapse" id="{$key}">
{foreach name="docs" id="api" } {foreach name="docs" id="api" }
<a href="javascript:;" data-id="{$api.id}" class="list-group-item">{$api.title}</a> <a href="javascript:;" data-id="{$api.id}" class="list-group-item">{$api.title}
<span class="tag">
{if $api.needRight}
<span class="label label-danger pull-right"></span>
{/if}
{if $api.needLogin}
<span class="label label-success pull-right noneedlogin"></span>
{/if}
</span>
</a>
{/foreach} {/foreach}
</div> </div>
{/foreach} {/foreach}
</div> </div>
</div> </div>
<div class="panel-group" id="accordion"> <div class="panel-group docs-list" id="accordion">
{foreach name="docslist" id="docs"} {foreach name="docsList" id="docs"}
<h2>{$key}</h2> <h2>{$key}</h2>
<hr> <hr>
{foreach name="docs" id="api" } {foreach name="docs" id="api" }
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading" id="heading-{$api.id}"> <div class="panel-heading" id="heading-{$api.id}">
<h4 class="panel-title"> <h4 class="panel-title">
<span class="label {$api.method_label}">{$api.method|strtoupper}</span> <span class="label {$api.methodLabel}">{$api.method|strtoupper}</span>
<a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.title} <span class="text-muted">{$api.route}</span></a> <a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.title} <span class="text-muted">{$api.route}</span></a>
</h4> </h4>
</div> </div>
@ -177,10 +201,27 @@
<div class="well"> <div class="well">
{$api.summary} {$api.summary}
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Authorization}</strong></div>
<div class="panel-body">
<table class="table table-hover">
<tbody>
<tr>
<td>{$lang.NeedLogin}</td>
<td>{$api.needLogin?'是':'否'}</td>
</tr>
<tr>
<td>{$lang.NeedRight}</td>
<td>{$api.needRight?'是':'否'}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Headers}</strong></div> <div class="panel-heading"><strong>{$lang.Headers}</strong></div>
<div class="panel-body"> <div class="panel-body">
{if $api.headerslist} {if $api.headersList}
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
@ -191,7 +232,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{foreach name="api['headerslist']" id="header"} {foreach name="api['headersList']" id="header"}
<tr> <tr>
<td>{$header.name}</td> <td>{$header.name}</td>
<td>{$header.type}</td> <td>{$header.type}</td>
@ -209,7 +250,7 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Parameters}</strong></div> <div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
<div class="panel-body"> <div class="panel-body">
{if $api.paramslist} {if $api.paramsList}
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
@ -220,7 +261,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{foreach name="api['paramslist']" id="param"} {foreach name="api['paramsList']" id="param"}
<tr> <tr>
<td>{$param.name}</td> <td>{$param.name}</td>
<td>{$param.type}</td> <td>{$param.type}</td>
@ -246,12 +287,12 @@
<div class="tab-pane" id="sandbox{$api.id}"> <div class="tab-pane" id="sandbox{$api.id}">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{if $api.headerslist} {if $api.headersList}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Headers}</strong></div> <div class="panel-heading"><strong>{$lang.Headers}</strong></div>
<div class="panel-body"> <div class="panel-body">
<div class="headers"> <div class="headers">
{foreach name="api['headerslist']" id="param"} {foreach name="api['headersList']" id="param"}
<div class="form-group"> <div class="form-group">
<label class="control-label" for="{$param.name}">{$param.name}</label> <label class="control-label" for="{$param.name}">{$param.name}</label>
<input type="{$param.type}" class="form-control input-sm" id="{$param.name}" {if $param.required}required{/if} placeholder="{$param.description} - Ex: {$param.sample}" name="{$param.name}"> <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}">
@ -262,11 +303,15 @@
</div> </div>
{/if} {/if}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Parameters}</strong></div> <div class="panel-heading"><strong>{$lang.Parameters}</strong>
<div class="pull-right">
<a href="javascript:" class="btn btn-xs btn-info btn-append">追加</a>
</div>
</div>
<div class="panel-body"> <div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="{$api.route}" method="{$api.method}" name="form{$api.id}" id="form{$api.id}"> <form enctype="application/x-www-form-urlencoded" role="form" action="{$api.route}" method="{$api.method}" name="form{$api.id}" id="form{$api.id}">
{if $api.paramslist} {if $api.paramsList}
{foreach name="api['paramslist']" id="param"} {foreach name="api['paramsList']" id="param"}
<div class="form-group"> <div class="form-group">
<label class="control-label" for="{$param.name}">{$param.name}</label> <label class="control-label" for="{$param.name}">{$param.name}</label>
<input type="{$param.type}" class="form-control input-sm" id="{$param.name}" {if $param.required}required{/if} placeholder="{$param.description}{if $param.sample} - 例: {$param.sample}{/if}" name="{$param.name}"> <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}">
@ -277,7 +322,7 @@
</div> </div>
{/if} {/if}
<div class="form-group"> <div class="form-group form-group-submit">
<button type="submit" class="btn btn-success send" rel="{$api.id}">{$lang.Send}</button> <button type="submit" class="btn btn-success send" rel="{$api.id}">{$lang.Send}</button>
<button type="reset" class="btn btn-info" rel="{$api.id}">{$lang.Reset}</button> <button type="reset" class="btn btn-info" rel="{$api.id}">{$lang.Reset}</button>
</div> </div>
@ -298,7 +343,7 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.ReturnParameters}</strong></div> <div class="panel-heading"><strong>{$lang.ReturnParameters}</strong></div>
<div class="panel-body"> <div class="panel-body">
{if $api.returnparamslist} {if $api.returnParamsList}
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
@ -308,7 +353,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{foreach name="api['returnparamslist']" id="param"} {foreach name="api['returnParamsList']" id="param"}
<tr> <tr>
<td>{$param.name}</td> <td>{$param.name}</td>
<td>{$param.type}</td> <td>{$param.type}</td>
@ -346,10 +391,10 @@
<div class="row mt0 footer"> <div class="row mt0 footer">
<div class="col-md-6" align="left"> <div class="col-md-6" align="left">
Generated on {:date('Y-m-d H:i:s')}
</div> </div>
<div class="col-md-6" align="right"> <div class="col-md-6" align="right">
<a href="./" target="_blank">{$config.sitename}</a> Generated on {:date('Y-m-d H:i:s')} <a href="./" target="_blank">{$config.sitename}</a>
</div> </div>
</div> </div>
@ -475,7 +520,7 @@
$sample.html('<pre>' + str + '</pre>'); $sample.html('<pre>' + str + '</pre>');
}); });
$('body').on('click', '#save_data', function (e) { $(document).on('click', '#save_data', function (e) {
if (storage) { if (storage) {
storage.setItem('token', $('#token').val()); storage.setItem('token', $('#token').val());
storage.setItem('apiUrl', $('#apiUrl').val()); storage.setItem('apiUrl', $('#apiUrl').val());
@ -483,8 +528,20 @@
alert('Your browser does not support local storage'); alert('Your browser does not support local storage');
} }
}); });
$(document).on('click', '.btn-append', function (e) {
$($("#appendtpl").html()).insertBefore($(this).closest(".panel").find(".form-group-submit"));
return false;
});
$(document).on('click', '.btn-remove', function (e) {
$(this).closest(".form-group").remove();
return false;
});
$(document).on('keyup', '.input-custom-name', function (e) {
$(this).closest(".row").find(".input-custom-value").attr("name", $(this).val());
return false;
});
$('body').on('click', '.send', function (e) { $(document).on('click', '.send', function (e) {
e.preventDefault(); e.preventDefault();
var form = $(this).closest('form'); var form = $(this).closest('form');
//added /g to get all the matched params instead of only first //added /g to get all the matched params instead of only first
@ -577,5 +634,21 @@
}); });
}); });
</script> </script>
<script type="text/html" id="appendtpl">
<div class="form-group">
<label class="control-label">自定义</label>
<div class="row">
<div class="col-xs-4">
<input type="text" class="form-control input-sm input-custom-name" placeholder="名称">
</div>
<div class="col-xs-6">
<input type="text" class="form-control input-sm input-custom-value" placeholder="值">
</div>
<div class="col-xs-2 text-center">
<a href="javascript:" class="btn btn-sm btn-danger btn-remove">删除</a>
</div>
</div>
</div>
</script>
</body> </body>
</html> </html>

View File

@ -201,7 +201,7 @@ class Install extends Command
// 数据库配置文件 // 数据库配置文件
$dbConfigFile = APP_PATH . 'database.php'; $dbConfigFile = APP_PATH . 'database.php';
$config = @file_get_contents($dbConfigFile); $dbConfigText = @file_get_contents($dbConfigFile);
$callback = function ($matches) use ($mysqlHostname, $mysqlHostport, $mysqlUsername, $mysqlPassword, $mysqlDatabase, $mysqlPrefix) { $callback = function ($matches) use ($mysqlHostname, $mysqlHostport, $mysqlUsername, $mysqlPassword, $mysqlDatabase, $mysqlPrefix) {
$field = "mysql" . ucfirst($matches[1]); $field = "mysql" . ucfirst($matches[1]);
$replace = $$field; $replace = $$field;
@ -210,14 +210,26 @@ class Install extends Command
} }
return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),"; return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),";
}; };
$config = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $config); $dbConfigText = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $config);
// 检测能否成功写入数据库配置 // 检测能否成功写入数据库配置
$result = @file_put_contents($dbConfigFile, $config); $result = @file_put_contents($dbConfigFile, $dbConfigText);
if (!$result) { if (!$result) {
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/database.php')); throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/database.php'));
} }
// 设置新的Token随机密钥key
$oldTokenKey = config('token.key');
$newTokenKey = \fast\Random::alnum(32);
$coreConfigFile = CONF_PATH . 'config.php';
$coreConfigText = @file_get_contents($coreConfigFile);
$coreConfigText = preg_replace("/'key'(\s+)=>(\s+)'{$oldTokenKey}'/", "'key'\$1=>\$2'{$newTokenKey}'", $coreConfigText);
$result = @file_put_contents($coreConfigFile, $coreConfigText);
if (!$result) {
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/config.php'));
}
// 变更默认管理员密码 // 变更默认管理员密码
$adminPassword = $adminPassword ? $adminPassword : Random::alnum(8); $adminPassword = $adminPassword ? $adminPassword : Random::alnum(8);
$adminEmail = $adminEmail ? $adminEmail : "admin@admin.com"; $adminEmail = $adminEmail ? $adminEmail : "admin@admin.com";
@ -241,8 +253,8 @@ class Install extends Command
//修改站点名称 //修改站点名称
if ($siteName != config('site.name')) { if ($siteName != config('site.name')) {
$instance->name('config')->where('name', 'name')->update(['value' => $siteName]); $instance->name('config')->where('name', 'name')->update(['value' => $siteName]);
$configFile = CONF_PATH . 'extra' . DS . 'site.php'; $siteConfigFile = CONF_PATH . 'extra' . DS . 'site.php';
$config = include $configFile; $siteConfig = include $siteConfigFile;
$configList = $instance->name("config")->select(); $configList = $instance->name("config")->select();
foreach ($configList as $k => $value) { foreach ($configList as $k => $value) {
if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'])) { if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'])) {
@ -251,10 +263,10 @@ class Install extends Command
if ($value['type'] == 'array') { if ($value['type'] == 'array') {
$value['value'] = (array)json_decode($value['value'], true); $value['value'] = (array)json_decode($value['value'], true);
} }
$config[$value['name']] = $value['value']; $siteConfig[$value['name']] = $value['value'];
} }
$config['name'] = $siteName; $siteConfig['name'] = $siteName;
file_put_contents($configFile, '<?php' . "\n\nreturn " . var_export_short($config) . ";\n"); file_put_contents($siteConfigFile, '<?php' . "\n\nreturn " . var_export_short($siteConfig) . ";\n");
} }
$installLockFile = INSTALL_PATH . "install.lock"; $installLockFile = INSTALL_PATH . "install.lock";

View File

@ -340,11 +340,11 @@ INSERT INTO `fa_config` VALUES (7, 'languages', 'basic', 'Languages', '', 'array
INSERT INTO `fa_config` VALUES (8, 'fixedpage', 'basic', 'Fixed page', '请尽量输入左侧菜单栏存在的链接', 'string', 'dashboard', '', 'required', '', ''); INSERT INTO `fa_config` VALUES (8, 'fixedpage', 'basic', 'Fixed page', '请尽量输入左侧菜单栏存在的链接', 'string', 'dashboard', '', 'required', '', '');
INSERT INTO `fa_config` VALUES (9, 'categorytype', 'dictionary', 'Category type', '', 'array', '{\"default\":\"Default\",\"page\":\"Page\",\"article\":\"Article\",\"test\":\"Test\"}', '', '', '', ''); INSERT INTO `fa_config` VALUES (9, 'categorytype', 'dictionary', 'Category type', '', 'array', '{\"default\":\"Default\",\"page\":\"Page\",\"article\":\"Article\",\"test\":\"Test\"}', '', '', '', '');
INSERT INTO `fa_config` VALUES (10, 'configgroup', 'dictionary', 'Config group', '', 'array', '{\"basic\":\"Basic\",\"email\":\"Email\",\"dictionary\":\"Dictionary\",\"user\":\"User\",\"example\":\"Example\"}', '', '', '', ''); INSERT INTO `fa_config` VALUES (10, 'configgroup', 'dictionary', 'Config group', '', 'array', '{\"basic\":\"Basic\",\"email\":\"Email\",\"dictionary\":\"Dictionary\",\"user\":\"User\",\"example\":\"Example\"}', '', '', '', '');
INSERT INTO `fa_config` VALUES (11, 'mail_type', 'email', 'Mail type', '选择邮件发送方式', 'select', '1', '[\"请选择\",\"SMTP\",\"Mail\"]', '', '', ''); INSERT INTO `fa_config` VALUES (11, 'mail_type', 'email', 'Mail type', '选择邮件发送方式', 'select', '1', '[\"请选择\",\"SMTP\"]', '', '', '');
INSERT INTO `fa_config` VALUES (12, 'mail_smtp_host', 'email', 'Mail smtp host', '错误的配置发送邮件会导致服务器超时', 'string', 'smtp.qq.com', '', '', '', ''); INSERT INTO `fa_config` VALUES (12, 'mail_smtp_host', 'email', 'Mail smtp host', '错误的配置发送邮件会导致服务器超时', 'string', 'smtp.qq.com', '', '', '', '');
INSERT INTO `fa_config` VALUES (13, 'mail_smtp_port', 'email', 'Mail smtp port', '(不加密默认25,SSL默认465,TLS默认587)', 'string', '465', '', '', '', ''); INSERT INTO `fa_config` VALUES (13, 'mail_smtp_port', 'email', 'Mail smtp port', '(不加密默认25,SSL默认465,TLS默认587)', 'string', '465', '', '', '', '');
INSERT INTO `fa_config` VALUES (14, 'mail_smtp_user', 'email', 'Mail smtp user', '(填写完整用户名)', 'string', '10000', '', '', '', ''); INSERT INTO `fa_config` VALUES (14, 'mail_smtp_user', 'email', 'Mail smtp user', '(填写完整用户名)', 'string', '10000', '', '', '', '');
INSERT INTO `fa_config` VALUES (15, 'mail_smtp_pass', 'email', 'Mail smtp password', '(填写您的密码', 'string', 'password', '', '', '', ''); INSERT INTO `fa_config` VALUES (15, 'mail_smtp_pass', 'email', 'Mail smtp password', '(填写您的密码或授权码', 'string', 'password', '', '', '', '');
INSERT INTO `fa_config` VALUES (16, 'mail_verify_type', 'email', 'Mail vertify type', 'SMTP验证方式[推荐SSL]', 'select', '2', '[\"无\",\"TLS\",\"SSL\"]', '', '', ''); INSERT INTO `fa_config` VALUES (16, 'mail_verify_type', 'email', 'Mail vertify type', 'SMTP验证方式[推荐SSL]', 'select', '2', '[\"无\",\"TLS\",\"SSL\"]', '', '', '');
INSERT INTO `fa_config` VALUES (17, 'mail_from', 'email', 'Mail from', '', 'string', '10000@qq.com', '', '', '', ''); INSERT INTO `fa_config` VALUES (17, 'mail_from', 'email', 'Mail from', '', 'string', '10000@qq.com', '', '', '', '');
COMMIT; COMMIT;

View File

@ -15,7 +15,7 @@ use think\Exception;
* 插件管理 * 插件管理
* *
* @icon fa fa-cube * @icon fa fa-cube
* @remark 可在线安装、卸载、禁用、启用插件,同时支持添加本地插件。FastAdmin已上线插件商店 ,你可以发布你的免费或付费插件:<a href="https://www.fastadmin.net/store.html" target="_blank">https://www.fastadmin.net/store.html</a> * @remark 可在线安装、卸载、禁用、启用、配置、升级插件,插件升级前请做好备份
*/ */
class Addon extends Backend class Addon extends Backend
{ {

View File

@ -10,7 +10,7 @@ use fast\Tree;
* 分类管理 * 分类管理
* *
* @icon fa fa-list * @icon fa fa-list
* @remark 用于统一管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加 * @remark 用于管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加
*/ */
class Category extends Backend class Category extends Backend
{ {

View File

@ -12,7 +12,7 @@ use think\Validate;
/** /**
* 管理员管理 * 管理员管理
* *
* @icon fa fa-users * @icon fa fa-users
* @remark 一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成 * @remark 一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成
*/ */
class Admin extends Backend class Admin extends Backend
@ -32,8 +32,8 @@ class Admin extends Backend
parent::_initialize(); parent::_initialize();
$this->model = model('Admin'); $this->model = model('Admin');
$this->childrenAdminIds = $this->auth->getChildrenAdminIds(true); $this->childrenAdminIds = $this->auth->getChildrenAdminIds($this->auth->isSuperAdmin());
$this->childrenGroupIds = $this->auth->getChildrenGroupIds(true); $this->childrenGroupIds = $this->auth->getChildrenGroupIds($this->auth->isSuperAdmin());
$groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray(); $groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
@ -136,6 +136,10 @@ class Admin extends Backend
//过滤不允许的组别,避免越权 //过滤不允许的组别,避免越权
$group = array_intersect($this->childrenGroupIds, $group); $group = array_intersect($this->childrenGroupIds, $group);
if (!$group) {
$this->error(__('The parent group exceeds permission limit'));
}
$dataset = []; $dataset = [];
foreach ($group as $value) { foreach ($group as $value) {
$dataset[] = ['uid' => $this->model->id, 'group_id' => $value]; $dataset[] = ['uid' => $this->model->id, 'group_id' => $value];
@ -192,6 +196,9 @@ class Admin extends Backend
// 过滤不允许的组别,避免越权 // 过滤不允许的组别,避免越权
$group = array_intersect($this->childrenGroupIds, $group); $group = array_intersect($this->childrenGroupIds, $group);
if (!$group) {
$this->error(__('The parent group exceeds permission limit'));
}
$dataset = []; $dataset = [];
foreach ($group as $value) { foreach ($group as $value) {

View File

@ -27,7 +27,7 @@ class Adminlog extends Backend
$this->model = model('AdminLog'); $this->model = model('AdminLog');
$this->childrenAdminIds = $this->auth->getChildrenAdminIds(true); $this->childrenAdminIds = $this->auth->getChildrenAdminIds(true);
$this->childrenGroupIds = $this->auth->getChildrenGroupIds($this->auth->isSuperAdmin() ? true : false); $this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
$groupName = AuthGroup::where('id', 'in', $this->childrenGroupIds) $groupName = AuthGroup::where('id', 'in', $this->childrenGroupIds)
->column('id,name'); ->column('id,name');
@ -66,6 +66,9 @@ class Adminlog extends Backend
if (!$row) { if (!$row) {
$this->error(__('No Results were found')); $this->error(__('No Results were found'));
} }
if (!$row['admin_id'] || !in_array($row['admin_id'], $this->childrenAdminIds)) {
$this->error(__('You have no permission'));
}
$this->view->assign("row", $row->toArray()); $this->view->assign("row", $row->toArray());
return $this->view->fetch(); return $this->view->fetch();
} }
@ -98,10 +101,7 @@ class Adminlog extends Backend
} }
$ids = $ids ? $ids : $this->request->post("ids"); $ids = $ids ? $ids : $this->request->post("ids");
if ($ids) { if ($ids) {
$childrenGroupIds = $this->childrenGroupIds; $adminList = $this->model->where('id', 'in', $ids)->where('admin_id', 'in', $this->childrenAdminIds)->select();
$adminList = $this->model->where('id', 'in', $ids)->where('admin_id', 'in', function ($query) use ($childrenGroupIds) {
$query->name('auth_group_access')->field('uid');
})->select();
if ($adminList) { if ($adminList) {
$deleteIds = []; $deleteIds = [];
foreach ($adminList as $k => $v) { foreach ($adminList as $k => $v) {

View File

@ -24,6 +24,7 @@ class Group extends Backend
//当前登录管理员所有子组别 //当前登录管理员所有子组别
protected $childrenGroupIds = []; protected $childrenGroupIds = [];
//当前组别列表数据 //当前组别列表数据
protected $grouplist = [];
protected $groupdata = []; protected $groupdata = [];
//无需要权限判断的方法 //无需要权限判断的方法
protected $noNeedRight = ['roletree']; protected $noNeedRight = ['roletree'];
@ -38,20 +39,28 @@ class Group extends Backend
$groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray(); $groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
Tree::instance()->init($groupList); Tree::instance()->init($groupList);
$result = []; $groupList = [];
if ($this->auth->isSuperAdmin()) { if ($this->auth->isSuperAdmin()) {
$result = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0)); $groupList = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
} else { } else {
$groups = $this->auth->getGroups(); $groups = $this->auth->getGroups();
$groupIds = [];
foreach ($groups as $m => $n) { foreach ($groups as $m => $n) {
$result = array_merge($result, Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['pid']))); if (in_array($n['id'], $groupIds) || in_array($n['pid'], $groupIds)) {
continue;
}
$groupList = array_merge($groupList, Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['pid'])));
foreach ($groupList as $index => $item) {
$groupIds[] = $item['id'];
}
} }
} }
$groupName = []; $groupName = [];
foreach ($result as $k => $v) { foreach ($groupList as $k => $v) {
$groupName[$v['id']] = $v['name']; $groupName[$v['id']] = $v['name'];
} }
$this->grouplist = $groupList;
$this->groupdata = $groupName; $this->groupdata = $groupName;
$this->assignconfig("admin", ['id' => $this->auth->id, 'group_ids' => $this->auth->getGroupIds()]); $this->assignconfig("admin", ['id' => $this->auth->id, 'group_ids' => $this->auth->getGroupIds()]);
@ -64,19 +73,7 @@ class Group extends Backend
public function index() public function index()
{ {
if ($this->request->isAjax()) { if ($this->request->isAjax()) {
$list = AuthGroup::all(array_keys($this->groupdata)); $list = $this->grouplist;
$list = collection($list)->toArray();
$groupList = [];
foreach ($list as $k => $v) {
$groupList[$v['id']] = $v;
}
$list = [];
foreach ($this->groupdata as $k => $v) {
if (isset($groupList[$k])) {
$groupList[$k]['name'] = $v;
$list[] = $groupList[$k];
}
}
$total = count($list); $total = count($list);
$result = array("total" => $total, "rows" => $list); $result = array("total" => $total, "rows" => $list);

View File

@ -105,6 +105,9 @@ class Rule extends Backend
if (!$params['ismenu'] && !$params['pid']) { if (!$params['ismenu'] && !$params['pid']) {
$this->error(__('The non-menu rule must have parent')); $this->error(__('The non-menu rule must have parent'));
} }
if ($params['pid'] == $row['id']) {
$this->error(__('Can not change the parent to self'));
}
if ($params['pid'] != $row['pid']) { if ($params['pid'] != $row['pid']) {
$childrenIds = Tree::instance()->init(collection(AuthRule::select())->toArray())->getChildrenIds($row['id']); $childrenIds = Tree::instance()->init(collection(AuthRule::select())->toArray())->getChildrenIds($row['id']);
if (in_array($params['pid'], $childrenIds)) { if (in_array($params['pid'], $childrenIds)) {

View File

@ -256,8 +256,8 @@ class Config extends Backend
$email = new Email; $email = new Email;
$result = $email $result = $email
->to($receiver) ->to($receiver)
->subject(__("This is a test mail")) ->subject(__("This is a test mail", config('site.name')))
->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content') . '</div>') ->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content', config('site.name')) . '</div>')
->send(); ->send();
if ($result) { if ($result) {
$this->success(); $this->success();

View File

@ -194,9 +194,9 @@ return [
'Third group 2' => '三级管理组2', 'Third group 2' => '三级管理组2',
'Dashboard tips' => '用于展示当前系统中的统计数据、统计报表及重要实时数据', 'Dashboard tips' => '用于展示当前系统中的统计数据、统计报表及重要实时数据',
'Config tips' => '可以在此增改系统的变量和分组,也可以自定义分组和变量', 'Config tips' => '可以在此增改系统的变量和分组,也可以自定义分组和变量',
'Category tips' => '用于统一管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加', 'Category tips' => '用于管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加',
'Attachment tips' => '主要用于管理上传到服务器或第三方存储的数据', 'Attachment tips' => '主要用于管理上传到服务器或第三方存储的数据',
'Addon tips' => '可在线安装、卸载、禁用、启用插件,同时支持添加本地插件。', 'Addon tips' => '可在线安装、卸载、禁用、启用、配置、升级插件,插件升级前请做好备份。',
'Admin tips' => '一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成', 'Admin tips' => '一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成',
'Admin log tips' => '管理员可以查看自己所拥有的权限的管理员日志', 'Admin log tips' => '管理员可以查看自己所拥有的权限的管理员日志',
'Group tips' => '角色组可以有多个,角色有上下级层级关系,如果子角色有角色组和管理员的权限则可以派生属于自己组别的下级角色组或管理员', 'Group tips' => '角色组可以有多个,角色有上下级层级关系,如果子角色有角色组和管理员的权限则可以派生属于自己组别的下级角色组或管理员',

View File

@ -25,7 +25,7 @@ return [
'Offline installed tips' => '安装成功!清除浏览器缓存和框架缓存后生效!', 'Offline installed tips' => '安装成功!清除浏览器缓存和框架缓存后生效!',
'Online installed tips' => '安装成功!清除浏览器缓存和框架缓存后生效!', 'Online installed tips' => '安装成功!清除浏览器缓存和框架缓存后生效!',
'Not login tips' => '你当前未登录FastAdmin登录后将同步已购买的记录下载时无需二次付费', 'Not login tips' => '你当前未登录FastAdmin登录后将同步已购买的记录下载时无需二次付费',
'Please login and try to install' => '请登录后再进行离线安装!', 'Please login and try to install' => '请登录FastAdmin后再进行离线安装!',
'Not installed tips' => '请安装后再访问插件前台页面!', 'Not installed tips' => '请安装后再访问插件前台页面!',
'Not enabled tips' => '插件已经禁用,请启用后再访问插件前台页面!', 'Not enabled tips' => '插件已经禁用,请启用后再访问插件前台页面!',
'New version tips' => '发现新版本:%s 点击查看更新日志', 'New version tips' => '发现新版本:%s 点击查看更新日志',

View File

@ -1,9 +1,10 @@
<?php <?php
return [ return [
'Group' => '所属组别', 'Group' => '所属组别',
'Loginfailure' => '登录失败次数', 'Loginfailure' => '登录失败次数',
'Login time' => '最后登录', 'Login time' => '最后登录',
'Please input correct username' => '用户名只能由3-12位数字、字母、下划线组合', 'The parent group exceeds permission limit' => '父组别超出权限范围',
'Please input correct password' => '密码长度必须在6-16位之间不能包含空格', 'Please input correct username' => '用户名只能由3-12位数字、字母、下划线组合',
'Please input correct password' => '密码长度必须在6-16位之间不能包含空格',
]; ];

View File

@ -5,7 +5,7 @@ return [
'The parent group can not found' => '父组别未找到', 'The parent group can not found' => '父组别未找到',
'Group not found' => '组别未找到', 'Group not found' => '组别未找到',
'Can not change the parent to child' => '父组别不能是它的子组别', 'Can not change the parent to child' => '父组别不能是它的子组别',
'Can not change the parent to self' => '父组别不能是它的子组别', 'Can not change the parent to self' => '父组别不能是它自己',
'You can not delete group that contain child group and administrators' => '你不能删除含有子组和管理员的组', 'You can not delete group that contain child group and administrators' => '你不能删除含有子组和管理员的组',
'The parent group exceeds permission limit' => '父组别超出权限范围', 'The parent group exceeds permission limit' => '父组别超出权限范围',
'The parent group can not be its own child or itself' => '父组别不能是它的子组别及本身', 'The parent group can not be its own child or itself' => '父组别不能是它的子组别及本身',

View File

@ -15,6 +15,7 @@ return [
'Menu tips' => '父级菜单无需匹配控制器和方法,子级菜单请使用控制器名', 'Menu tips' => '父级菜单无需匹配控制器和方法,子级菜单请使用控制器名',
'Node tips' => '控制器/方法名,如果有目录请使用 目录名/控制器名/方法名', 'Node tips' => '控制器/方法名,如果有目录请使用 目录名/控制器名/方法名',
'The non-menu rule must have parent' => '非菜单规则节点必须有父级', 'The non-menu rule must have parent' => '非菜单规则节点必须有父级',
'Can not change the parent to child' => '父组别不能是它的子组别', 'Can not change the parent to child' => '父级不能是它的子级',
'Can not change the parent to self' => '父级不能是它自己',
'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成', 'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
]; ];

View File

@ -70,8 +70,8 @@ return [
'Name already exist' => '变量名称已经存在', 'Name already exist' => '变量名称已经存在',
'Add new config' => '点击添加新的配置', 'Add new config' => '点击添加新的配置',
'Send a test message' => '发送测试邮件', 'Send a test message' => '发送测试邮件',
'This is a test mail content' => '这是一封来自FastAdmin校验邮件,用于校验邮件配置是否正常!', 'This is a test mail content' => '这是一封来自{sitename}校验邮件,用于校验邮件配置是否正常!',
'This is a test mail' => '这是一封来自FastAdmin的邮件', 'This is a test mail' => '这是一封来自{sitename}的邮件',
'Please input your email' => '请输入测试接收者邮箱', 'Please input your email' => '请输入测试接收者邮箱',
'Please input correct email' => '请输入正确的邮箱地址', 'Please input correct email' => '请输入正确的邮箱地址',
]; ];

View File

@ -100,7 +100,7 @@ class Auth extends \fast\Auth
return false; return false;
} }
//token有变更 //token有变更
if ($key != md5(md5($id) . md5($keeptime) . md5($expiretime) . $admin->token)) { if ($key != md5(md5($id) . md5($keeptime) . md5($expiretime) . $admin->token . config('token.key'))) {
return false; return false;
} }
$ip = request()->ip(); $ip = request()->ip();
@ -127,9 +127,9 @@ class Auth extends \fast\Auth
{ {
if ($keeptime) { if ($keeptime) {
$expiretime = time() + $keeptime; $expiretime = time() + $keeptime;
$key = md5(md5($this->id) . md5($keeptime) . md5($expiretime) . $this->token); $key = md5(md5($this->id) . md5($keeptime) . md5($expiretime) . $this->token . config('token.key'));
$data = [$this->id, $keeptime, $expiretime, $key]; $data = [$this->id, $keeptime, $expiretime, $key];
Cookie::set('keeplogin', implode('|', $data), 86400 * 30); Cookie::set('keeplogin', implode('|', $data), 86400 * 7);
return true; return true;
} }
return false; return false;
@ -292,8 +292,8 @@ class Auth extends \fast\Auth
break; break;
} }
// 取出包含自己的所有子节点 // 取出包含自己的所有子节点
$childrenList = Tree::instance()->init($groupList)->getChildren($v['id'], true); $childrenList = Tree::instance()->init($groupList, 'pid')->getChildren($v['id'], true);
$obj = Tree::instance()->init($childrenList)->getTreeArray($v['pid']); $obj = Tree::instance()->init($childrenList, 'pid')->getTreeArray($v['pid']);
$objList = array_merge($objList, Tree::instance()->getTreeList($obj)); $objList = array_merge($objList, Tree::instance()->getTreeList($obj));
} }
$childrenGroupIds = []; $childrenGroupIds = [];

View File

@ -160,7 +160,7 @@
{/case} {/case}
{case value="selectpage" break="0"}{/case} {case value="selectpage" break="0"}{/case}
{case value="selectpages"} {case value="selectpages"}
<input {$item.extend_html} type="text" name="row[{$item.name}]" id="c-{$item.name}" value="{$item.value|htmlentities}" class="form-control selectpage" data-source="{:url('general/config/selectpage')}?id={$item.id}" data-primary-key="{$item.setting.primarykey}" data-field="{$item.setting.field}" data-multiple="{$item.type=='selectpage'?'false':'true'}" data-tip="{$item.tip}" data-rule="{$item.rule}" /> <input {$item.extend_html} type="text" name="row[{$item.name}]" id="c-{$item.name}" value="{$item.value|htmlentities}" class="form-control selectpage" data-source="{:url('general.config/selectpage')}?id={$item.id}" data-primary-key="{$item.setting.primarykey}" data-field="{$item.setting.field}" data-multiple="{$item.type=='selectpage'?'false':'true'}" data-tip="{$item.tip}" data-rule="{$item.rule}" />
{/case} {/case}
{case custom} {case custom}
{$item.extend_html} {$item.extend_html}

View File

@ -85,7 +85,8 @@ if (!function_exists('cdnurl')) {
function cdnurl($url, $domain = false) function cdnurl($url, $domain = false)
{ {
$regex = "/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i"; $regex = "/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i";
$url = preg_match($regex, $url) ? $url : \think\Config::get('upload.cdnurl') . $url; $cdnurl = \think\Config::get('upload.cdnurl');
$url = preg_match($regex, $url) || ($cdnurl && stripos($url, $cdnurl) === 0) ? $url : $cdnurl . $url;
if ($domain && !preg_match($regex, $url)) { if ($domain && !preg_match($regex, $url)) {
$domain = is_bool($domain) ? request()->domain() : $domain; $domain = is_bool($domain) ? request()->domain() : $domain;
$url = $domain . $url; $url = $domain . $url;
@ -246,7 +247,7 @@ if (!function_exists('addtion')) {
$model = $v['name'] ? \think\Db::name($v['name']) : \think\Db::table($v['table']); $model = $v['name'] ? \think\Db::name($v['name']) : \think\Db::table($v['table']);
} }
$primary = $v['primary'] ? $v['primary'] : $model->getPk(); $primary = $v['primary'] ? $v['primary'] : $model->getPk();
$result[$v['field']] = $model->where($primary, 'in', $ids[$v['field']])->column("{$primary},{$v['column']}"); $result[$v['field']] = isset($ids[$v['field']]) ? $model->where($primary, 'in', $ids[$v['field']])->column("{$primary},{$v['column']}") : [];
} }
foreach ($items as $k => &$v) { foreach ($items as $k => &$v) {
@ -265,13 +266,68 @@ if (!function_exists('addtion')) {
if (!function_exists('var_export_short')) { if (!function_exists('var_export_short')) {
/** /**
* 返回打印数组结构 * 使用短标签打印或返回数组结构
* @param string $var 数组 * @param mixed $data
* @param boolean $return 是否返回数据
* @return string * @return string
*/ */
function var_export_short($var) function var_export_short($data, $return = true)
{ {
return VarExporter::export($var); return var_export($data, $return);
$replaced = [];
$count = 0;
//判断是否是对象
if (is_resource($data) || is_object($data)) {
return var_export($data, $return);
}
//判断是否有特殊的键名
$specialKey = false;
array_walk_recursive($data, function (&$value, &$key) use (&$specialKey) {
if (is_string($key) && (stripos($key, "\n") !== false || stripos($key, "array (") !== false)) {
$specialKey = true;
}
});
if ($specialKey) {
return var_export($data, $return);
}
array_walk_recursive($data, function (&$value, &$key) use (&$replaced, &$count, &$stringcheck) {
if (is_object($value) || is_resource($value)) {
$replaced[$count] = var_export($value, true);
$value = "##<{$count}>##";
} else {
if (is_string($value) && (stripos($value, "\n") !== false || stripos($value, "array (") !== false)) {
$index = array_search($value, $replaced);
if ($index === false) {
$replaced[$count] = var_export($value, true);
$value = "##<{$count}>##";
} else {
$value = "##<{$index}>##";
}
}
}
$count++;
});
$dump = var_export($data, true);
$dump = preg_replace('#(?:\A|\n)([ ]*)array \(#i', '[', $dump); // Starts
$dump = preg_replace('#\n([ ]*)\),#', "\n$1],", $dump); // Ends
$dump = preg_replace('#=> \[\n\s+\],\n#', "=> [],\n", $dump); // Empties
$dump = preg_replace('#\)$#', "]", $dump); //End
if ($replaced) {
$dump = preg_replace_callback("/'##<(\d+)>##'/", function ($matches) use ($replaced) {
return isset($replaced[$matches[1]]) ? $replaced[$matches[1]] : "''";
}, $dump);
}
if ($return === true) {
return $dump;
} else {
echo $dump;
}
} }
} }
@ -290,7 +346,7 @@ if (!function_exists('letter_avatar')) {
$bg = "rgb({$r},{$g},{$b})"; $bg = "rgb({$r},{$g},{$b})";
$color = "#ffffff"; $color = "#ffffff";
$first = mb_strtoupper(mb_substr($text, 0, 1)); $first = mb_strtoupper(mb_substr($text, 0, 1));
$src = base64_encode('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="100" width="100"><rect fill="' . $bg . '" x="0" y="0" width="100" height="100"></rect><text x="50" y="50" font-size="50" text-copy="fast" fill="' . $color . '" text-anchor="middle" text-rights="admin" alignment-baseline="central">' . $first . '</text></svg>'); $src = base64_encode('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="100" width="100"><rect fill="' . $bg . '" x="0" y="0" width="100" height="100"></rect><text x="50" y="50" font-size="50" text-copy="fast" fill="' . $color . '" text-anchor="middle" text-rights="admin" dominant-baseline="central">' . $first . '</text></svg>');
$value = 'data:image/svg+xml;base64,' . $src; $value = 'data:image/svg+xml;base64,' . $src;
return $value; return $value;
} }

View File

@ -3,6 +3,9 @@
namespace app\common\library; namespace app\common\library;
use think\Config; use think\Config;
use Tx\Mailer;
use Tx\Mailer\Exceptions\CodeException;
use Tx\Mailer\Exceptions\SendException;
class Email class Email
{ {
@ -20,14 +23,15 @@ class Email
/** /**
* 错误内容 * 错误内容
*/ */
protected $_error = ''; protected $error = '';
/** /**
* 默认配置 * 默认配置
*/ */
public $options = [ public $options = [
'charset' => 'utf-8', //编码格式 'charset' => 'utf-8', //编码格式
'debug' => false, //调式模式 'debug' => false, //调式模式
'mail_type' => 0, //状态
]; ];
/** /**
@ -55,22 +59,12 @@ class Email
$this->options = array_merge($this->options, $config); $this->options = array_merge($this->options, $config);
} }
$this->options = array_merge($this->options, $options); $this->options = array_merge($this->options, $options);
$securArr = [1 => 'tls', 2 => 'ssl']; $secureArr = [0 => '', 1 => 'tls', 2 => 'ssl'];
$secure = isset($secureArr[$this->options['mail_verify_type']]) ? $secureArr[$this->options['mail_verify_type']] : '';
$this->mail = new \PHPMailer\PHPMailer\PHPMailer(true); $this->mail = new Mailer(new Log);
$this->mail->CharSet = $this->options['charset']; $this->mail->setServer($this->options['mail_smtp_host'], $this->options['mail_smtp_port'], $secure);
if ($this->options['mail_type'] == 1) { $this->mail->setAuth($this->options['mail_from'], $this->options['mail_smtp_pass']);
$this->mail->SMTPDebug = $this->options['debug'];
$this->mail->isSMTP();
$this->mail->SMTPAuth = true;
} else {
$this->mail->isMail();
}
$this->mail->Host = $this->options['mail_smtp_host'];
$this->mail->Username = $this->options['mail_from'];
$this->mail->Password = $this->options['mail_smtp_pass'];
$this->mail->SMTPSecure = isset($securArr[$this->options['mail_verify_type']]) ? $securArr[$this->options['mail_verify_type']] : '';
$this->mail->Port = $this->options['mail_smtp_port'];
//设置发件人 //设置发件人
$this->from($this->options['mail_from'], $this->options['mail_smtp_user']); $this->from($this->options['mail_from'], $this->options['mail_smtp_user']);
@ -83,7 +77,7 @@ class Email
*/ */
public function subject($subject) public function subject($subject)
{ {
$this->mail->Subject = $subject; $this->mail->setSubject($subject);
return $this; return $this;
} }
@ -95,7 +89,7 @@ class Email
*/ */
public function from($email, $name = '') public function from($email, $name = '')
{ {
$this->mail->setFrom($email, $name); $this->mail->setFrom($name, $email);
return $this; return $this;
} }
@ -109,7 +103,7 @@ class Email
{ {
$emailArr = $this->buildAddress($email); $emailArr = $this->buildAddress($email);
foreach ($emailArr as $address => $name) { foreach ($emailArr as $address => $name) {
$this->mail->addAddress($address, $name); $this->mail->addTo($name, $address);
} }
return $this; return $this;
@ -124,6 +118,9 @@ class Email
public function cc($email, $name = '') public function cc($email, $name = '')
{ {
$emailArr = $this->buildAddress($email); $emailArr = $this->buildAddress($email);
if (count($emailArr) == 1 && $name) {
$emailArr[key($emailArr)] = $name;
}
foreach ($emailArr as $address => $name) { foreach ($emailArr as $address => $name) {
$this->mail->addCC($address, $name); $this->mail->addCC($address, $name);
} }
@ -139,8 +136,11 @@ class Email
public function bcc($email, $name = '') public function bcc($email, $name = '')
{ {
$emailArr = $this->buildAddress($email); $emailArr = $this->buildAddress($email);
if (count($emailArr) == 1 && $name) {
$emailArr[key($emailArr)] = $name;
}
foreach ($emailArr as $address => $name) { foreach ($emailArr as $address => $name) {
$this->mail->addBCC($address, $name); $this->mail->addBCC($name, $address);
} }
return $this; return $this;
} }
@ -153,11 +153,7 @@ class Email
*/ */
public function message($body, $ishtml = true) public function message($body, $ishtml = true)
{ {
if ($ishtml) { $this->mail->setBody($body);
$this->mail->msgHTML($body);
} else {
$this->mail->Body = $body;
}
return $this; return $this;
} }
@ -169,7 +165,7 @@ class Email
*/ */
public function attachment($path, $name = '') public function attachment($path, $name = '')
{ {
$this->mail->addAttachment($path, $name); $this->mail->addAttachment($name, $path);
return $this; return $this;
} }
@ -180,13 +176,8 @@ class Email
*/ */
protected function buildAddress($emails) protected function buildAddress($emails)
{ {
$emails = is_array($emails) ? $emails : explode(',', str_replace(";", ",", $emails)); $emails = is_array($emails) ? $emails : array_flip(explode(',', str_replace(";", ",", $emails)));
$result = []; return $emails;
foreach ($emails as $key => $value) {
$email = is_numeric($key) ? $value : $key;
$result[$email] = is_numeric($key) ? "" : $value;
}
return $result;
} }
/** /**
@ -195,7 +186,7 @@ class Email
*/ */
public function getError() public function getError()
{ {
return $this->_error; return $this->error;
} }
/** /**
@ -204,7 +195,7 @@ class Email
*/ */
protected function setError($error) protected function setError($error)
{ {
$this->_error = $error; $this->error = $error;
} }
/** /**
@ -217,11 +208,18 @@ class Email
if (in_array($this->options['mail_type'], [1, 2])) { if (in_array($this->options['mail_type'], [1, 2])) {
try { try {
$result = $this->mail->send(); $result = $this->mail->send();
} catch (\PHPMailer\PHPMailer\Exception $e) { } catch (SendException $e) {
$this->setError($e->getCode() . $e->getMessage());
} catch (CodeException $e) {
preg_match_all("/Expected: (\d+)\, Got: (\d+)( \| (.*))?\$/i", $e->getMessage(), $matches);
$code = isset($matches[2][3]) ? $matches[2][3] : 0;
$message = isset($matches[2][0]) ? $matches[4][0] : $e->getMessage();
$this->setError($message);
} catch (\Exception $e) {
$this->setError($e->getMessage()); $this->setError($e->getMessage());
} }
$this->setError($result ? '' : $this->mail->ErrorInfo); $this->setError($result ? '' : $this->getError());
} else { } else {
//邮件功能已关闭 //邮件功能已关闭
$this->setError(__('Mail already closed')); $this->setError(__('Mail already closed'));

View File

@ -183,6 +183,7 @@ class Config extends Model
'mimetype' => $uploadcfg['mimetype'], 'mimetype' => $uploadcfg['mimetype'],
'chunking' => $uploadcfg['chunking'], 'chunking' => $uploadcfg['chunking'],
'chunksize' => $uploadcfg['chunksize'], 'chunksize' => $uploadcfg['chunksize'],
'savekey' => $uploadcfg['savekey'],
'multipart' => [], 'multipart' => [],
'multiple' => $uploadcfg['multiple'], 'multiple' => $uploadcfg['multiple'],
'storage' => 'local' 'storage' => 'local'

View File

@ -51,6 +51,8 @@ return [
'class_suffix' => false, 'class_suffix' => false,
// 控制器类后缀 // 控制器类后缀
'controller_suffix' => false, 'controller_suffix' => false,
// 获取IP的变量
'http_agent_ip' => 'REMOTE_ADDR',
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
// | 模块设置 // | 模块设置
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
@ -290,7 +292,7 @@ return [
//允许跨域的域名,多个以,分隔 //允许跨域的域名,多个以,分隔
'cors_request_domain' => 'localhost,127.0.0.1', 'cors_request_domain' => 'localhost,127.0.0.1',
//版本号 //版本号
'version' => '1.2.0.20201008_beta', 'version' => '1.2.0.20210125_beta',
//API接口地址 //API接口地址
'api_url' => 'https://api.fastadmin.net', 'api_url' => 'https://api.fastadmin.net',
], ],

View File

@ -21,7 +21,7 @@ return [
/** /**
* 可上传的文件类型 * 可上传的文件类型
*/ */
'mimetype' => 'jpg,png,bmp,jpeg,gif,zip,rar,xls,xlsx', 'mimetype' => 'jpg,png,bmp,jpeg,gif,zip,rar,xls,xlsx,wav,mp4,mp3,pdf',
/** /**
* 是否支持批量上传 * 是否支持批量上传
*/ */

View File

@ -65,7 +65,7 @@ class User extends Frontend
*/ */
public function register() public function register()
{ {
$url = $this->request->request('url', ''); $url = $this->request->request('url', '', 'trim');
if ($this->auth->id) { if ($this->auth->id) {
$this->success(__('You\'ve logged in, do not login again'), $url ? $url : url('user/index')); $this->success(__('You\'ve logged in, do not login again'), $url ? $url : url('user/index'));
} }
@ -144,7 +144,7 @@ class User extends Frontend
*/ */
public function login() public function login()
{ {
$url = $this->request->request('url', ''); $url = $this->request->request('url', '', 'trim');
if ($this->auth->id) { if ($this->auth->id) {
$this->success(__('You\'ve logged in, do not login again'), $url ? $url : url('user/index')); $this->success(__('You\'ve logged in, do not login again'), $url ? $url : url('user/index'));
} }
@ -267,6 +267,7 @@ class User extends Frontend
$this->request->filter(['strip_tags']); $this->request->filter(['strip_tags']);
if ($this->request->isAjax()) { if ($this->request->isAjax()) {
$mimetypeQuery = []; $mimetypeQuery = [];
$where = [];
$filter = $this->request->request('filter'); $filter = $this->request->request('filter');
$filterArr = (array)json_decode($filter, true); $filterArr = (array)json_decode($filter, true);
if (isset($filterArr['mimetype']) && preg_match("/[]\,|\*]/", $filterArr['mimetype'])) { if (isset($filterArr['mimetype']) && preg_match("/[]\,|\*]/", $filterArr['mimetype'])) {
@ -281,17 +282,31 @@ class User extends Frontend
} }
} }
}; };
} elseif (isset($filterArr['mimetype'])) {
$where['mimetype'] = ['like', '%' . $filterArr['mimetype'] . '%'];
} }
if (isset($filterArr['filename'])) {
$where['filename'] = ['like', '%' . $filterArr['filename'] . '%'];
}
if (isset($filterArr['createtime'])) {
$timeArr = explode(' - ', $filterArr['createtime']);
$where['createtime'] = ['between', [strtotime($timeArr[0]), strtotime($timeArr[1])]];
}
$model = new Attachment(); $model = new Attachment();
$offset = $this->request->get("offset", 0); $offset = $this->request->get("offset", 0);
$limit = $this->request->get("limit", 0); $limit = $this->request->get("limit", 0);
$total = $model $total = $model
->where($where)
->where($mimetypeQuery) ->where($mimetypeQuery)
->where('user_id', $this->auth->id) ->where('user_id', $this->auth->id)
->order("id", "DESC") ->order("id", "DESC")
->count(); ->count();
$list = $model $list = $model
->where($where)
->where($mimetypeQuery) ->where($mimetypeQuery)
->where('user_id', $this->auth->id) ->where('user_id', $this->auth->id)
->order("id", "DESC") ->order("id", "DESC")

View File

@ -27,7 +27,7 @@
"fastadmin-cxselect": "~1.4.0", "fastadmin-cxselect": "~1.4.0",
"fastadmin-dragsort": "~1.0.0", "fastadmin-dragsort": "~1.0.0",
"fastadmin-addtabs": "~1.0.5", "fastadmin-addtabs": "~1.0.5",
"fastadmin-selectpage": "~1.0.0", "fastadmin-selectpage": "~1.0.6",
"fastadmin-layer": "~3.1.2", "fastadmin-layer": "~3.1.2",
"bootstrap-slider": "*" "bootstrap-slider": "*"
} }

View File

@ -15,19 +15,18 @@
} }
], ],
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.1.0",
"topthink/framework": "~5.0.24", "topthink/framework": "~5.0.24",
"topthink/think-captcha": "^1.0", "topthink/think-captcha": "^1.0",
"phpmailer/phpmailer": "~6.1.6",
"karsonzhang/fastadmin-addons": "~1.2.4", "karsonzhang/fastadmin-addons": "~1.2.4",
"overtrue/pinyin": "~3.0", "overtrue/pinyin": "~3.0",
"phpoffice/phpspreadsheet": "^1.2", "phpoffice/phpspreadsheet": "^1.2",
"overtrue/wechat": "4.2.11", "overtrue/wechat": "4.2.11",
"nelexa/zip": "^3.3", "nelexa/zip": "^3.3",
"symfony/var-exporter": "^4.4.13",
"ext-json": "*", "ext-json": "*",
"ext-curl": "*", "ext-curl": "*",
"ext-pdo": "*" "ext-pdo": "*",
"txthinking/mailer": "^2.0"
}, },
"config": { "config": {
"preferred-install": "dist" "preferred-install": "dist"

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -180,6 +180,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
Toastr.success(ret.msg); Toastr.success(ret.msg);
operate(data.addon.name, 'enable', false); operate(data.addon.name, 'enable', false);
return false; return false;
}, function (data, ret) {
if (ret.msg && ret.msg.match(/(login|登录)/g)) {
return Layer.alert(ret.msg, {
title: __('Warning'),
btn: [__('Login now')],
yes: function (index, layero) {
$(".btn-userinfo").trigger("click");
}
});
}
}); });
}); });
@ -262,7 +272,17 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
return false; return false;
}, },
success: function (layero, index) { success: function (layero, index) {
this.checkEnterKey = function(event){
if(event.keyCode === 13){
$(".layui-layer-btn0").trigger("click");
return false;
}
};
$(document).on('keydown', this.checkEnterKey);
$(".layui-layer-btn1", layero).prop("href", "http://www.fastadmin.net/user/register.html").prop("target", "_blank"); $(".layui-layer-btn1", layero).prop("href", "http://www.fastadmin.net/user/register.html").prop("target", "_blank");
},
end: function(){
$(document).off('keydown', this.checkEnterKey);
} }
}); });
} else { } else {

View File

@ -103,7 +103,8 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
//获取修复后可访问的cdn链接 //获取修复后可访问的cdn链接
cdnurl: function (url, domain) { cdnurl: function (url, domain) {
var rule = new RegExp("^((?:[a-z]+:)?\\/\\/|data:image\\/)", "i"); var rule = new RegExp("^((?:[a-z]+:)?\\/\\/|data:image\\/)", "i");
var url = rule.test(url) ? url : Config.upload.cdnurl + url; var cdnurl = Config.upload.cdnurl;
url = rule.test(url) || (cdnurl && url.indexOf(cdnurl) === 0) ? url : cdnurl + url;
if (domain && !rule.test(url)) { if (domain && !rule.test(url)) {
domain = typeof domain === 'string' ? domain : location.origin; domain = typeof domain === 'string' ? domain : location.origin;
url = domain + url; url = domain + url;

View File

@ -140,25 +140,22 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
showExport: false, showExport: false,
columns: [ columns: [
[ [
{field: 'state', checkbox: multiple, visible: multiple, operate:false}, {field: 'state', checkbox: multiple, visible: multiple, operate: false},
{field: 'id', title: __('Id')}, {field: 'id', title: __('Id'), operate: false},
{field: 'url', title: __('Preview'), formatter: function (value, row, index) { {
field: 'url', title: __('Preview'), formatter: function (value, row, index) {
if (row.mimetype.indexOf("image") > -1) { if (row.mimetype.indexOf("image") > -1) {
var style = row.storage === 'upyun' ? '!/fwfh/120x90' : ''; var style = row.storage === 'upyun' ? '!/fwfh/120x90' : '';
return '<a href="' + row.fullurl + '" target="_blank"><img src="' + row.fullurl + style + '" alt="" style="max-height:90px;max-width:120px"></a>'; return '<a href="' + row.fullurl + '" target="_blank"><img src="' + row.fullurl + style + '" alt="" style="max-height:90px;max-width:120px"></a>';
} else { } else {
return '<a href="' + row.fullurl + '" target="_blank"><img src="' + Fast.api.fixurl("ajax/icon") + "?suffix=" + row.imagetype + '" alt="" style="max-height:90px;max-width:120px"></a>'; return '<a href="' + row.fullurl + '" target="_blank"><img src="' + Fast.api.fixurl("ajax/icon") + "?suffix=" + row.imagetype + '" alt="" style="max-height:90px;max-width:120px"></a>';
} }
}, operate: false}, }, operate: false
},
{field: 'filename', title: __('Filename'), formatter: Table.api.formatter.search, operate: 'like'}, {field: 'filename', title: __('Filename'), formatter: Table.api.formatter.search, operate: 'like'},
{field: 'imagewidth', title: __('Imagewidth'), operate: false}, {field: 'imagewidth', title: __('Imagewidth'), operate: false},
{field: 'imageheight', title: __('Imageheight'), operate: false}, {field: 'imageheight', title: __('Imageheight'), operate: false},
{ {field: 'mimetype', title: __('Mimetype'), formatter: Table.api.formatter.search},
field: 'mimetype', title: __('Mimetype'), operate: 'LIKE %...%',
process: function (value, arg) {
return value.replace(/\*/g, '%');
}
},
{field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true}, {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
{ {
field: 'operate', title: __('Operate'), events: { field: 'operate', title: __('Operate'), events: {

View File

@ -121,7 +121,7 @@ require.config({
'css': '../libs/require-css/css.min' 'css': '../libs/require-css/css.min'
} }
}, },
waitSeconds: 30, waitSeconds: 60,
charset: 'utf-8' // 文件编码 charset: 'utf-8' // 文件编码
}); });

File diff suppressed because one or more lines are too long

View File

@ -335,7 +335,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
row = row ? row : {}; row = row ? row : {};
var vars = {index: index, name: name, data: data, row: row}; var vars = {index: index, name: name, data: data, row: row};
var html = template ? Template(template, vars) : Template.render(Form.config.fieldlisttpl, vars); var html = template ? Template(template, vars) : Template.render(Form.config.fieldlisttpl, vars);
$(html).insertBefore($(tagName + ":last", container)); $(html).attr("fieldlist-item", true).insertBefore($(tagName + ":last", container));
$(this).trigger("fa.event.appendfieldlist", $(this).closest(tagName).prev()); $(this).trigger("fa.event.appendfieldlist", $(this).closest(tagName).prev());
}); });
//移除控制 //移除控制
@ -362,17 +362,20 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
return true; return true;
} }
var template = $(this).data("template"); var template = $(this).data("template");
var json = {}; textarea.on("fa.event.refreshfieldlist", function () {
try { $("[fieldlist-item]", container).remove();
json = JSON.parse(textarea.val()); var json = {};
} catch (e) { try {
} json = JSON.parse($(this).val());
$.each(json, function (i, j) { } catch (e) {
$(".btn-append,.append", container).trigger('click', template ? j : { }
key: i, $.each(json, function (i, j) {
value: j $(".btn-append,.append", container).trigger('click', template ? j : {
key: i, value: j
});
}); });
}); });
textarea.trigger("fa.event.refreshfieldlist");
}); });
}); });
} }

View File

@ -120,7 +120,7 @@ require.config({
'css': '../libs/require-css/css.min' 'css': '../libs/require-css/css.min'
} }
}, },
waitSeconds: 30, waitSeconds: 60,
charset: 'utf-8' // 文件编码 charset: 'utf-8' // 文件编码
}); });

File diff suppressed because one or more lines are too long

View File

@ -48,6 +48,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
iosCardView: true, //ios卡片视图 iosCardView: true, //ios卡片视图
checkOnInit: true, //是否在初始化时判断 checkOnInit: true, //是否在初始化时判断
escape: true, //是否对内容进行转义 escape: true, //是否对内容进行转义
fixDropdownPosition: true, //是否修复下拉的定位
selectedIds: [], selectedIds: [],
selectedData: [], selectedData: [],
extend: { extend: {
@ -253,8 +254,9 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
var exportDataType = options.exportDataType; var exportDataType = options.exportDataType;
// 处理选中筛选框后按钮的状态统一变更 // 处理选中筛选框后按钮的状态统一变更
table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table post-body.bs.table', function (e) { table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table post-body.bs.table', function (e) {
var allIds = table.bootstrapTable("getData").map(function (item) { var allIds = [];
return item[options.pk]; $.each(table.bootstrapTable("getData"), function (i, item) {
allIds.push(typeof item[options.pk] != 'undefined' ? item[options.pk] : '');
}); });
var selectedIds = Table.api.selectedids(table, true), var selectedIds = Table.api.selectedids(table, true),
selectedData = Table.api.selecteddata(table, true); selectedData = Table.api.selecteddata(table, true);
@ -307,7 +309,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
if (url.indexOf("{ids}") !== -1) { if (url.indexOf("{ids}") !== -1) {
url = Table.api.replaceurl(url, {ids: ids.length > 0 ? ids.join(",") : 0}, table); url = Table.api.replaceurl(url, {ids: ids.length > 0 ? ids.join(",") : 0}, table);
} }
Fast.api.open(url, __('Add'), $(this).data() || {}); Fast.api.open(url, $(this).data("original-title") || $(this).attr("title") || __('Add'), $(this).data() || {});
}); });
// 导入按钮事件 // 导入按钮事件
if ($(Table.config.importbtn, toolbar).size() > 0) { if ($(Table.config.importbtn, toolbar).size() > 0) {
@ -330,12 +332,15 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
if (ids.length > 10) { if (ids.length > 10) {
return; return;
} }
var title = $(that).data('title') || $(that).attr("title") || __('Edit');
var data = $(that).data() || {};
delete data.title;
//循环弹出多个编辑框 //循环弹出多个编辑框
$.each(Table.api.selecteddata(table), function (index, row) { $.each(Table.api.selecteddata(table), function (index, row) {
var url = options.extend.edit_url; var url = options.extend.edit_url;
row = $.extend({}, row ? row : {}, {ids: row[options.pk]}); row = $.extend({}, row ? row : {}, {ids: row[options.pk]});
url = Table.api.replaceurl(url, row, table); url = Table.api.replaceurl(url, row, table);
Fast.api.open(url, __('Edit'), $(that).data() || {}); Fast.api.open(url, typeof title === 'function' ? title.call(table, row) : title, data);
}); });
}); });
//清空回收站 //清空回收站
@ -472,7 +477,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
var row = Table.api.getrowbyid(table, ids); var row = Table.api.getrowbyid(table, ids);
row.ids = ids; row.ids = ids;
var url = Table.api.replaceurl(options.extend.edit_url, row, table); var url = Table.api.replaceurl(options.extend.edit_url, row, table);
Fast.api.open(url, __('Edit'), $(this).data() || {}); Fast.api.open(url, $(this).data("original-title") || $(this).attr("title") || __('Edit'), $(this).data() || {});
}); });
table.on("click", "[data-id].btn-del", function (e) { table.on("click", "[data-id].btn-del", function (e) {
e.preventDefault(); e.preventDefault();
@ -487,6 +492,45 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
} }
); );
}); });
//修复dropdown定位溢出的情况
if (options.fixDropdownPosition) {
var tableBody = table.closest(".fixed-table-body");
table.on('show.bs.dropdown fa.event.refreshdropdown', ".btn-group", function (e) {
var dropdownMenu = $(".dropdown-menu", this);
var btnGroup = $(this);
var isPullRight = dropdownMenu.hasClass("pull-right") || dropdownMenu.hasClass("dropdown-menu-right");
var left, top, position;
if (dropdownMenu.outerHeight() + btnGroup.outerHeight() > tableBody.outerHeight() - 41) {
position = 'fixed';
top = btnGroup.offset().top - $(window).scrollTop() + btnGroup.outerHeight();
left = isPullRight ? btnGroup.offset().left + btnGroup.outerWidth() - dropdownMenu.outerWidth() : btnGroup.offset().left;
} else {
if (btnGroup.offset().top + btnGroup.outerHeight() + dropdownMenu.outerHeight() > tableBody.offset().top + tableBody.outerHeight() - 30) {
position = 'absolute';
left = isPullRight ? -(dropdownMenu.outerWidth() - btnGroup.outerWidth()) : 0;
top = -(dropdownMenu.outerHeight() + 3);
}
}
if (left || top) {
dropdownMenu.css({
position: position, left: left, top: top, right: 'inherit'
});
}
});
var checkdropdown = function () {
if ($(".btn-group.open", table).length > 0 && $(".btn-group.open .dropdown-menu", table).css("position") == 'fixed') {
$(".btn-group.open", table).trigger("fa.event.refreshdropdown");
}
};
$(window).on("scroll", function () {
checkdropdown();
});
tableBody.on("scroll", function () {
checkdropdown();
});
}
var id = table.attr("id"); var id = table.attr("id");
Table.list[id] = table; Table.list[id] = table;
return table; return table;
@ -528,7 +572,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
var ids = row[options.pk]; var ids = row[options.pk];
row = $.extend({}, row ? row : {}, {ids: ids}); row = $.extend({}, row ? row : {}, {ids: ids});
var url = options.extend.edit_url; var url = options.extend.edit_url;
Fast.api.open(Table.api.replaceurl(url, row, table), __('Edit'), $(this).data() || {}); Fast.api.open(Table.api.replaceurl(url, row, table), $(this).data("original-title") || $(this).attr("title") || __('Edit'), $(this).data() || {});
}, },
'click .btn-delone': function (e, value, row, index) { 'click .btn-delone': function (e, value, row, index) {
e.stopPropagation(); e.stopPropagation();
@ -557,8 +601,9 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
image: { image: {
'click .img-center': function (e, value, row, index) { 'click .img-center': function (e, value, row, index) {
var data = []; var data = [];
value = value.toString().split(","); value = value === null ? '' : value.toString();
$.each(value, function (index, value) { var arr = value != '' ? split(",") : [];
$.each(arr, function (index, value) {
data.push({ data.push({
src: Fast.api.cdnurl(value), src: Fast.api.cdnurl(value),
}); });
@ -576,22 +621,21 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
// 单元格数据格式化 // 单元格数据格式化
formatter: { formatter: {
icon: function (value, row, index) { icon: function (value, row, index) {
if (!value)
return '';
value = value === null ? '' : value.toString(); value = value === null ? '' : value.toString();
value = value.indexOf(" ") > -1 ? value : "fa fa-" + value; value = value.indexOf(" ") > -1 ? value : "fa fa-" + value;
//渲染fontawesome图标 //渲染fontawesome图标
return '<i class="' + value + '"></i> ' + value; return '<i class="' + value + '"></i> ' + value;
}, },
image: function (value, row, index) { image: function (value, row, index) {
value = value == null || value.length === 0 ? '' : value.toString();
value = value ? value : '/assets/img/blank.gif'; value = value ? value : '/assets/img/blank.gif';
var classname = typeof this.classname !== 'undefined' ? this.classname : 'img-sm img-center'; var classname = typeof this.classname !== 'undefined' ? this.classname : 'img-sm img-center';
return '<a href="javascript:"><img class="' + classname + '" src="' + Fast.api.cdnurl(value) + '" /></a>'; return '<a href="javascript:"><img class="' + classname + '" src="' + Fast.api.cdnurl(value) + '" /></a>';
}, },
images: function (value, row, index) { images: function (value, row, index) {
value = value === null ? '' : value.toString(); value = value == null || value.length === 0 ? '' : value.toString();
var classname = typeof this.classname !== 'undefined' ? this.classname : 'img-sm img-center'; var classname = typeof this.classname !== 'undefined' ? this.classname : 'img-sm img-center';
var arr = value.split(','); var arr = value != '' ? value.split(',') : [];
var html = []; var html = [];
$.each(arr, function (i, value) { $.each(arr, function (i, value) {
value = value ? value : '/assets/img/blank.gif'; value = value ? value : '/assets/img/blank.gif';
@ -618,7 +662,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
if (typeof this.custom !== 'undefined') { if (typeof this.custom !== 'undefined') {
custom = $.extend(custom, this.custom); custom = $.extend(custom, this.custom);
} }
value = value === null ? '' : value.toString(); value = value == null || value.length === 0 ? '' : value.toString();
var keys = typeof this.searchList === 'object' ? Object.keys(this.searchList) : []; var keys = typeof this.searchList === 'object' ? Object.keys(this.searchList) : [];
var index = keys.indexOf(value); var index = keys.indexOf(value);
var color = value && typeof custom[value] !== 'undefined' ? custom[value] : null; var color = value && typeof custom[value] !== 'undefined' ? custom[value] : null;
@ -656,7 +700,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
+ row[pk] + "' " + (url ? "data-url='" + url + "'" : "") + (confirm ? "data-confirm='" + confirm + "'" : "") + " data-params='" + this.field + "=" + (value == yes ? no : yes) + "'><i class='fa fa-toggle-on text-success text-" + color + " " + (value == yes ? '' : 'fa-flip-horizontal text-gray') + " fa-2x'></i></a>"; + row[pk] + "' " + (url ? "data-url='" + url + "'" : "") + (confirm ? "data-confirm='" + confirm + "'" : "") + " data-params='" + this.field + "=" + (value == yes ? no : yes) + "'><i class='fa fa-toggle-on text-success text-" + color + " " + (value == yes ? '' : 'fa-flip-horizontal text-gray') + " fa-2x'></i></a>";
}, },
url: function (value, row, index) { url: function (value, row, index) {
value = value === null ? '' : value.toString(); value = value == null || value.length === 0 ? '' : value.toString();
return '<div class="input-group input-group-sm" style="width:250px;margin:0 auto;"><input type="text" class="form-control input-sm" value="' + value + '"><span class="input-group-btn input-group-sm"><a href="' + value + '" target="_blank" class="btn btn-default btn-sm"><i class="fa fa-link"></i></a></span></div>'; return '<div class="input-group input-group-sm" style="width:250px;margin:0 auto;"><input type="text" class="form-control input-sm" value="' + value + '"><span class="input-group-btn input-group-sm"><a href="' + value + '" target="_blank" class="btn btn-default btn-sm"><i class="fa fa-link"></i></a></span></div>';
}, },
search: function (value, row, index) { search: function (value, row, index) {
@ -668,18 +712,18 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
return '<a href="javascript:;" class="searchit" data-toggle="tooltip" title="' + __('Click to search %s', value) + '" data-field="' + field + '" data-value="' + value + '">' + value + '</a>'; return '<a href="javascript:;" class="searchit" data-toggle="tooltip" title="' + __('Click to search %s', value) + '" data-field="' + field + '" data-value="' + value + '">' + value + '</a>';
}, },
addtabs: function (value, row, index) { addtabs: function (value, row, index) {
var url = Table.api.replaceurl(this.url, row, this.table); var url = Table.api.replaceurl(this.url || '', row, this.table);
var title = this.atitle ? this.atitle : __("Search %s", value); var title = this.atitle ? this.atitle : __("Search %s", value);
return '<a href="' + Fast.api.fixurl(url) + '" class="addtabsit" data-value="' + value + '" title="' + title + '">' + value + '</a>'; return '<a href="' + Fast.api.fixurl(url) + '" class="addtabsit" data-value="' + value + '" title="' + title + '">' + value + '</a>';
}, },
dialog: function (value, row, index) { dialog: function (value, row, index) {
var url = Table.api.replaceurl(this.url, row, this.table); var url = Table.api.replaceurl(this.url || '', row, this.table);
var title = this.atitle ? this.atitle : __("View %s", value); var title = this.atitle ? this.atitle : __("View %s", value);
return '<a href="' + Fast.api.fixurl(url) + '" class="dialogit" data-value="' + value + '" title="' + title + '">' + value + '</a>'; return '<a href="' + Fast.api.fixurl(url) + '" class="dialogit" data-value="' + value + '" title="' + title + '">' + value + '</a>';
}, },
flag: function (value, row, index) { flag: function (value, row, index) {
var that = this; var that = this;
value = value === null ? '' : value.toString(); value = value == null || value.length === 0 ? '' : value.toString();
var colorArr = {index: 'success', hot: 'warning', recommend: 'danger', 'new': 'info'}; var colorArr = {index: 'success', hot: 'warning', recommend: 'danger', 'new': 'info'};
//如果字段列有定义custom //如果字段列有定义custom
if (typeof this.custom !== 'undefined') { if (typeof this.custom !== 'undefined') {
@ -693,10 +737,10 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
//渲染Flag //渲染Flag
var html = []; var html = [];
var arr = value.split(','); var arr = value != '' ? value.split(',') : [];
var color, display, label; var color, display, label;
$.each(arr, function (i, value) { $.each(arr, function (i, value) {
value = value === null ? '' : value.toString(); value = value == null || value.length === 0 ? '' : value.toString();
if (value == '') if (value == '')
return true; return true;
color = value && typeof colorArr[value] !== 'undefined' ? colorArr[value] : 'primary'; color = value && typeof colorArr[value] !== 'undefined' ? colorArr[value] : 'primary';
@ -809,7 +853,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
if (!$.isEmptyObject(dropdowns)) { if (!$.isEmptyObject(dropdowns)) {
var dropdownHtml = []; var dropdownHtml = [];
$.each(dropdowns, function (i, j) { $.each(dropdowns, function (i, j) {
dropdownHtml.push('<div class="btn-group"><button type="button" class="btn btn-primary dropdown-toggle btn-xs" data-toggle="dropdown">' + i + '</button><button type="button" class="btn btn-primary dropdown-toggle btn-xs" data-toggle="dropdown"><span class="caret"></span></button><ul class="dropdown-menu pull-right"><li>' + j.join('</li><li>') + '</li></ul></div>'); dropdownHtml.push('<div class="btn-group"><button type="button" class="btn btn-primary dropdown-toggle btn-xs" data-toggle="dropdown">' + i + '</button><button type="button" class="btn btn-primary dropdown-toggle btn-xs" data-toggle="dropdown"><span class="caret"></span></button><ul class="dropdown-menu dropdown-menu-right"><li>' + j.join('</li><li>') + '</li></ul></div>');
}); });
html.unshift(dropdownHtml); html.unshift(dropdownHtml);
} }
@ -820,6 +864,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
var options = table ? table.bootstrapTable('getOptions') : null; var options = table ? table.bootstrapTable('getOptions') : null;
var ids = options ? row[options.pk] : 0; var ids = options ? row[options.pk] : 0;
row.ids = ids ? ids : (typeof row.ids !== 'undefined' ? row.ids : 0); row.ids = ids ? ids : (typeof row.ids !== 'undefined' ? row.ids : 0);
url = url == null || url.length === 0 ? '' : url.toString();
//自动添加ids参数 //自动添加ids参数
url = !url.match(/\{ids\}/i) ? url + (url.match(/(\?|&)+/) ? "&ids=" : "/ids/") + '{ids}' : url; url = !url.match(/\{ids\}/i) ? url + (url.match(/(\?|&)+/) ? "&ids=" : "/ids/") + '{ids}' : url;
url = url.replace(/\{(.*?)\}/gi, function (matched) { url = url.replace(/\{(.*?)\}/gi, function (matched) {

View File

@ -141,7 +141,7 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
//上传URL //上传URL
url = url ? url : Config.upload.uploadurl; url = url ? url : Config.upload.uploadurl;
url = Fast.api.fixurl(url); url = Fast.api.fixurl(url);
var chunking = false, chunkSize = Config.upload.chunksize || 2097152; var chunking = false, chunkSize = Config.upload.chunksize || 2097152, timeout = Config.upload.timeout || 600000;
//最大可上传文件大小 //最大可上传文件大小
maxsize = typeof maxsize !== "undefined" ? maxsize : Config.upload.maxsize; maxsize = typeof maxsize !== "undefined" ? maxsize : Config.upload.maxsize;
@ -198,6 +198,7 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
maxFilesize: maxFilesize, maxFilesize: maxFilesize,
acceptedFiles: mimetype, acceptedFiles: mimetype,
maxFiles: (maxcount && parseInt(maxcount) > 1 ? maxcount : (multiple ? null : 1)), maxFiles: (maxcount && parseInt(maxcount) > 1 ? maxcount : (multiple ? null : 1)),
timeout: timeout,
previewsContainer: false, previewsContainer: false,
dictDefaultMessage: __("Drop files here to upload"), dictDefaultMessage: __("Drop files here to upload"),
dictFallbackMessage: __("Your browser does not support drag'n'drop file uploads"), dictFallbackMessage: __("Your browser does not support drag'n'drop file uploads"),
@ -243,7 +244,7 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
} }
}, },
error: function (file, response, xhr) { error: function (file, response, xhr) {
var responseObj = $("<div>" + xhr.responseText + "</div>"); var responseObj = $("<div>" + (xhr && typeof xhr.responseText !== 'undefined' ? xhr.responseText : response) + "</div>");
responseObj.find("style, title, script").remove(); responseObj.find("style, title, script").remove();
var ret = {code: 0, data: null, msg: responseObj.text()}; var ret = {code: 0, data: null, msg: responseObj.text()};
Upload.events.onUploadError(this, ret, file); Upload.events.onUploadError(this, ret, file);
@ -343,10 +344,12 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
} }
var suffix = /[\.]?([a-zA-Z0-9]+)$/.exec(j); var suffix = /[\.]?([a-zA-Z0-9]+)$/.exec(j);
suffix = suffix ? suffix[1] : 'file'; suffix = suffix ? suffix[1] : 'file';
var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data(), key: i, index: i, value: (json && typeof json[i] !== 'undefined' ? json[i] : null), suffix: suffix}; var value = (json && typeof json[i] !== 'undefined' ? json[i] : null);
var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data(), key: i, index: i, value: value, row: value, suffix: suffix};
var html = tpl ? Template(tpl, data) : Template.render(Upload.config.previewtpl, data); var html = tpl ? Template(tpl, data) : Template.render(Upload.config.previewtpl, data);
$("#" + preview_id).append(html); $("#" + preview_id).append(html);
}); });
refresh($("#" + preview_id).data("name"));
}); });
$("#" + input_id).trigger("change"); $("#" + input_id).trigger("change");
} }