新增CRUD一键生成筛选选项卡功能

新增一键生成API文档导航列表功能
优化后台默认加载页的响应速度
优化二级栏目挺拽排序功能
修复后台部分列表会被截断的BUG
修复一键生成API文档@ApiInternal不生效的BUG
修复一键生成API文档空类的BUG
修复一键生成CRUD模型不正在字段的BUG
pull/69/head
Karson 2018-08-05 21:59:13 +08:00
parent 3683728023
commit 9753d94bc0
46 changed files with 1526 additions and 1024 deletions

View File

@ -93,6 +93,7 @@ class Api extends Command
$classes[] = $this->get_class_from_file($filePath); $classes[] = $this->get_class_from_file($filePath);
} }
} }
$classes = array_unique(array_filter($classes));
$config = [ $config = [
'title' => $title, 'title' => $title,

View File

@ -12,6 +12,7 @@ return [
'Required' => '必选', 'Required' => '必选',
'Description' => '描述', 'Description' => '描述',
'Send' => '提交', 'Send' => '提交',
'Reset' => '重置',
'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中', 'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中',
'Apiurltips' => 'API接口URL', 'Apiurltips' => 'API接口URL',
'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中', 'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中',

View File

@ -37,8 +37,12 @@ class Builder
protected function extractAnnotations() protected function extractAnnotations()
{ {
$st_output = []; $st_output = [];
foreach ($this->classes as $class) foreach ($this->classes as $class) {
{ $classAnnotation = Extractor::getClassAnnotations($class);
// 如果忽略
if (isset($classAnnotation['ApiInternal'])) {
continue;
}
$st_output[] = Extractor::getAllClassAnnotations($class); $st_output[] = Extractor::getAllClassAnnotations($class);
} }
return end($st_output); return end($st_output);
@ -46,14 +50,12 @@ class Builder
protected function generateHeadersTemplate($docs) protected function generateHeadersTemplate($docs)
{ {
if (!isset($docs['ApiHeaders'])) if (!isset($docs['ApiHeaders'])) {
{
return []; return [];
} }
$headerslist = array(); $headerslist = array();
foreach ($docs['ApiHeaders'] as $params) foreach ($docs['ApiHeaders'] as $params) {
{
$tr = array( $tr = array(
'name' => $params['name'], 'name' => $params['name'],
'type' => $params['type'], 'type' => $params['type'],
@ -69,14 +71,12 @@ class Builder
protected function generateParamsTemplate($docs) protected function generateParamsTemplate($docs)
{ {
if (!isset($docs['ApiParams'])) if (!isset($docs['ApiParams'])) {
{
return []; return [];
} }
$paramslist = array(); $paramslist = array();
foreach ($docs['ApiParams'] as $params) foreach ($docs['ApiParams'] as $params) {
{
$tr = array( $tr = array(
'name' => $params['name'], 'name' => $params['name'],
'type' => isset($params['type']) ? $params['type'] : 'string', 'type' => isset($params['type']) ? $params['type'] : 'string',
@ -92,14 +92,12 @@ class Builder
protected function generateReturnHeadersTemplate($docs) protected function generateReturnHeadersTemplate($docs)
{ {
if (!isset($docs['ApiReturnHeaders'])) if (!isset($docs['ApiReturnHeaders'])) {
{
return []; return [];
} }
$headerslist = array(); $headerslist = array();
foreach ($docs['ApiReturnHeaders'] as $params) foreach ($docs['ApiReturnHeaders'] as $params) {
{
$tr = array( $tr = array(
'name' => $params['name'], 'name' => $params['name'],
'type' => 'string', 'type' => 'string',
@ -115,14 +113,12 @@ class Builder
protected function generateReturnParamsTemplate($st_params) protected function generateReturnParamsTemplate($st_params)
{ {
if (!isset($st_params['ApiReturnParams'])) if (!isset($st_params['ApiReturnParams'])) {
{
return []; return [];
} }
$paramslist = array(); $paramslist = array();
foreach ($st_params['ApiReturnParams'] as $params) foreach ($st_params['ApiReturnParams'] as $params) {
{
$tr = array( $tr = array(
'name' => $params['name'], 'name' => $params['name'],
'type' => isset($params['type']) ? $params['type'] : 'string', 'type' => isset($params['type']) ? $params['type'] : 'string',
@ -157,20 +153,14 @@ class Builder
$counter = 0; $counter = 0;
$section = null; $section = null;
$docslist = []; $docslist = [];
foreach ($annotations as $class => $methods) foreach ($annotations as $class => $methods) {
{ foreach ($methods as $name => $docs) {
foreach ($methods as $name => $docs) if (isset($docs['ApiSector'][0])) {
{
if (isset($docs['ApiSector'][0]))
{
$section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0]; $section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0];
} } else {
else
{
$section = $class; $section = $class;
} }
if (0 === count($docs)) if (0 === count($docs)) {
{
continue; continue;
} }
@ -180,6 +170,7 @@ class Builder
'method_label' => $this->generateBadgeForMethod($docs), 'method_label' => $this->generateBadgeForMethod($docs),
'section' => $section, 'section' => $section,
'route' => is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0], 'route' => is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][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),

View File

@ -38,7 +38,7 @@ class Extractor
*/ */
public function setStrict($value) public function setStrict($value)
{ {
$this->strict = (bool) $value; $this->strict = (bool)$value;
} }
/** /**
@ -67,8 +67,7 @@ class Extractor
*/ */
public static function getClassAnnotations($className) public static function getClassAnnotations($className)
{ {
if (!isset(self::$annotationCache[$className])) if (!isset(self::$annotationCache[$className])) {
{
$class = new \ReflectionClass($className); $class = new \ReflectionClass($className);
self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment()); self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment());
} }
@ -80,8 +79,7 @@ class Extractor
{ {
$class = new \ReflectionClass($className); $class = new \ReflectionClass($className);
foreach ($class->getMethods() as $object) foreach ($class->getMethods() as $object) {
{
self::$annotationCache['annotations'][$className][$object->name] = self::getMethodAnnotations($className, $object->name); self::$annotationCache['annotations'][$className][$object->name] = self::getMethodAnnotations($className, $object->name);
} }
@ -97,23 +95,16 @@ class Extractor
*/ */
public static function getMethodAnnotations($className, $methodName) public static function getMethodAnnotations($className, $methodName)
{ {
if (!isset(self::$annotationCache[$className . '::' . $methodName])) if (!isset(self::$annotationCache[$className . '::' . $methodName])) {
{ try {
try
{
$method = new \ReflectionMethod($className, $methodName); $method = new \ReflectionMethod($className, $methodName);
$class = new \ReflectionClass($className); $class = new \ReflectionClass($className);
if (!$method->isPublic() || $method->isConstructor()) if (!$method->isPublic() || $method->isConstructor()) {
{
$annotations = array(); $annotations = array();
} } else {
else
{
$annotations = self::consolidateAnnotations($method, $class); $annotations = self::consolidateAnnotations($method, $class);
} }
} } catch (\ReflectionException $e) {
catch (\ReflectionException $e)
{
$annotations = array(); $annotations = array();
} }
@ -138,42 +129,31 @@ class Extractor
$i = 0; $i = 0;
foreach ($annotations as $annotationClass => $listParams) foreach ($annotations as $annotationClass => $listParams) {
{
$annotationClass = ucfirst($annotationClass); $annotationClass = ucfirst($annotationClass);
$class = $this->defaultNamespace . $annotationClass . 'Annotation'; $class = $this->defaultNamespace . $annotationClass . 'Annotation';
// verify is the annotation class exists, depending if Annotations::strict is true // verify is the annotation class exists, depending if Annotations::strict is true
// if not, just skip the annotation instance creation. // if not, just skip the annotation instance creation.
if (!class_exists($class)) if (!class_exists($class)) {
{ if ($this->strict) {
if ($this->strict)
{
throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class)); throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class));
} } else {
else
{
// silent skip & continue // silent skip & continue
continue; continue;
} }
} }
if (empty($objects[$annotationClass])) if (empty($objects[$annotationClass])) {
{
$objects[$annotationClass] = new $class(); $objects[$annotationClass] = new $class();
} }
foreach ($listParams as $params) foreach ($listParams as $params) {
{ if (is_array($params)) {
if (is_array($params)) foreach ($params as $key => $value) {
{
foreach ($params as $key => $value)
{
$objects[$annotationClass]->set($key, $value); $objects[$annotationClass]->set($key, $value);
} }
} } else {
else
{
$objects[$annotationClass]->set($i++, $params); $objects[$annotationClass]->set($i++, $params);
} }
} }
@ -190,8 +170,7 @@ class Extractor
$methodAnnotations = self::parseAnnotations($docblockMethod); $methodAnnotations = self::parseAnnotations($docblockMethod);
$classAnnotations = self::parseAnnotations($dockblockClass); $classAnnotations = self::parseAnnotations($dockblockClass);
if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') {
{
return []; return [];
} }
@ -205,74 +184,58 @@ class Extractor
$methodTitle = isset($methodArr[1]) && isset($methodArr[1][0]) ? $methodArr[1][0] : ''; $methodTitle = isset($methodArr[1]) && isset($methodArr[1][0]) ? $methodArr[1][0] : '';
$classTitle = isset($classArr[1]) && isset($classArr[1][0]) ? $classArr[1][0] : ''; $classTitle = isset($classArr[1]) && isset($classArr[1][0]) ? $classArr[1][0] : '';
if (!isset($methodAnnotations['ApiMethod'])) if (!isset($methodAnnotations['ApiMethod'])) {
{
$methodAnnotations['ApiMethod'] = ['get']; $methodAnnotations['ApiMethod'] = ['get'];
} }
if (!isset($methodAnnotations['ApiSummary'])) if (!isset($methodAnnotations['ApiSummary'])) {
{
$methodAnnotations['ApiSummary'] = [$methodTitle]; $methodAnnotations['ApiSummary'] = [$methodTitle];
} }
if ($methodAnnotations) if ($methodAnnotations) {
{ foreach ($classAnnotations as $name => $valueClass) {
foreach ($classAnnotations as $name => $valueClass) if (count($valueClass) !== 1) {
{
if (count($valueClass) !== 1)
{
continue; continue;
} }
if ($name === 'ApiRoute') if ($name === 'ApiRoute') {
{ if (isset($methodAnnotations[$name])) {
if (isset($methodAnnotations[$name]))
{
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . $methodAnnotations[$name][0]]; $methodAnnotations[$name] = [rtrim($valueClass[0], '/') . $methodAnnotations[$name][0]];
} } else {
else
{
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . '/' . $method->getName()]; $methodAnnotations[$name] = [rtrim($valueClass[0], '/') . '/' . $method->getName()];
} }
} }
if ($name === 'ApiSector') if ($name === 'ApiSector') {
{
$methodAnnotations[$name] = $valueClass; $methodAnnotations[$name] = $valueClass;
} }
} }
} }
if (!isset($methodAnnotations['ApiTitle'])) if (!isset($methodAnnotations['ApiTitle'])) {
{
$methodAnnotations['ApiTitle'] = [$methodTitle]; $methodAnnotations['ApiTitle'] = [$methodTitle];
} }
if (!isset($methodAnnotations['ApiRoute'])) if (!isset($methodAnnotations['ApiRoute'])) {
{
$urlArr = []; $urlArr = [];
$className = $class->getName(); $className = $class->getName();
list($prefix, $suffix) = explode('\\' . \think\Config::get('url_controller_layer') . '\\', $className); list($prefix, $suffix) = explode('\\' . \think\Config::get('url_controller_layer') . '\\', $className);
$prefixArr = explode('\\', $prefix); $prefixArr = explode('\\', $prefix);
$suffixArr = explode('\\', $suffix); $suffixArr = explode('\\', $suffix);
if ($prefixArr[0] == \think\Config::get('app_namespace')) if ($prefixArr[0] == \think\Config::get('app_namespace')) {
{
$prefixArr[0] = ''; $prefixArr[0] = '';
} }
$urlArr = array_merge($urlArr, $prefixArr); $urlArr = array_merge($urlArr, $prefixArr);
$urlArr[] = implode('.', array_map(function($item) { $urlArr[] = implode('.', array_map(function ($item) {
return \think\Loader::parseName($item); return \think\Loader::parseName($item);
}, $suffixArr)); }, $suffixArr));
$urlArr[] = $method->getName(); $urlArr[] = $method->getName();
$methodAnnotations['ApiRoute'] = [implode('/', $urlArr)]; $methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
} }
if (!isset($methodAnnotations['ApiSector'])) if (!isset($methodAnnotations['ApiSector'])) {
{
$methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : [$classTitle]; $methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : [$classTitle];
} }
if (!isset($methodAnnotations['ApiParams'])) if (!isset($methodAnnotations['ApiParams'])) {
{
$params = self::parseCustomAnnotations($docblockMethod, 'param'); $params = self::parseCustomAnnotations($docblockMethod, 'param');
foreach ($params as $k => $v) foreach ($params as $k => $v) {
{
$arr = explode(' ', preg_replace("/[\s]+/", " ", $v)); $arr = explode(' ', preg_replace("/[\s]+/", " ", $v));
$methodAnnotations['ApiParams'][] = [ $methodAnnotations['ApiParams'][] = [
'name' => isset($arr[1]) ? str_replace('$', '', $arr[1]) : '', 'name' => isset($arr[1]) ? str_replace('$', '', $arr[1]) : '',
@ -299,10 +262,8 @@ class Extractor
$annotations = array(); $annotations = array();
$docblock = substr($docblock, 3, -2); $docblock = substr($docblock, 3, -2);
if (preg_match_all('/@' . $name . '(?:\s*(?:\(\s*)?(.*?)(?:\s*\))?)??\s*(?:\n|\*\/)/', $docblock, $matches)) if (preg_match_all('/@' . $name . '(?:\s*(?:\(\s*)?(.*?)(?:\s*\))?)??\s*(?:\n|\*\/)/', $docblock, $matches)) {
{ foreach ($matches[1] as $k => $v) {
foreach ($matches[1] as $k => $v)
{
$annotations[] = $v; $annotations[] = $v;
} }
} }
@ -321,36 +282,31 @@ class Extractor
// Strip away the docblock header and footer to ease parsing of one line annotations // Strip away the docblock header and footer to ease parsing of one line annotations
$docblock = substr($docblock, 3, -2); $docblock = substr($docblock, 3, -2);
if (preg_match_all('/@(?<name>[A-Za-z_-]+)[\s\t]*\((?<args>(?:(?!\)).)*)\)\r?/s', $docblock, $matches)) if (preg_match_all('/@(?<name>[A-Za-z_-]+)[\s\t]*\((?<args>(?:(?!\)).)*)\)\r?/s', $docblock, $matches)) {
{
$numMatches = count($matches[0]); $numMatches = count($matches[0]);
for ($i = 0; $i < $numMatches; ++$i) for ($i = 0; $i < $numMatches; ++$i) {
{
// annotations has arguments
if (isset($matches['args'][$i]))
{
$argsParts = trim($matches['args'][$i]);
$name = $matches['name'][$i]; $name = $matches['name'][$i];
if($name == 'ApiReturn') $value = '';
{ // annotations has arguments
if (isset($matches['args'][$i])) {
$argsParts = trim($matches['args'][$i]);
if ($name == 'ApiReturn') {
$value = $argsParts; $value = $argsParts;
} else { } else if ($matches['args'][$i] != '') {
$argsParts = preg_replace("/\{(\w+)\}/", '#$1#', $argsParts); $argsParts = preg_replace("/\{(\w+)\}/", '#$1#', $argsParts);
$value = self::parseArgs($argsParts); $value = self::parseArgs($argsParts);
if(is_string($value)) if (is_string($value)) {
{
$value = preg_replace("/\#(\w+)\#/", '{$1}', $argsParts); $value = preg_replace("/\#(\w+)\#/", '{$1}', $argsParts);
} }
} }
} }
else
{
$value = array();
}
$annotations[$name][] = $value; $annotations[$name][] = $value;
} }
} }
if (stripos($docblock, '@ApiInternal') !== false) {
$annotations['ApiInternal'] = [true];
}
return $annotations; return $annotations;
} }
@ -382,37 +338,29 @@ class Extractor
$quoted = false; $quoted = false;
$tokens = array('"', '"', '{', '}', ',', '='); $tokens = array('"', '"', '{', '}', ',', '=');
while ($i <= $len) while ($i <= $len) {
{
$prev_c = substr($content, $i - 1, 1); $prev_c = substr($content, $i - 1, 1);
$c = substr($content, $i++, 1); $c = substr($content, $i++, 1);
if ($c === '"' && $prev_c !== "\\") if ($c === '"' && $prev_c !== "\\") {
{
$delimiter = $c; $delimiter = $c;
//open delimiter //open delimiter
if (!$composing && empty($prevDelimiter) && empty($nextDelimiter)) if (!$composing && empty($prevDelimiter) && empty($nextDelimiter)) {
{
$prevDelimiter = $nextDelimiter = $delimiter; $prevDelimiter = $nextDelimiter = $delimiter;
$val = ''; $val = '';
$composing = true; $composing = true;
$quoted = true; $quoted = true;
} } else {
else
{
// close delimiter // close delimiter
if ($c !== $nextDelimiter) if ($c !== $nextDelimiter) {
{
throw new Exception(sprintf( throw new Exception(sprintf(
"Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c "Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
)); ));
} }
// validating syntax // validating syntax
if ($i < $len) if ($i < $len) {
{ if (',' !== substr($content, $i, 1) && '\\' !== $prev_c) {
if (',' !== substr($content, $i, 1) && '\\' !== $prev_c)
{
throw new Exception(sprintf( throw new Exception(sprintf(
"Parse Error: missing comma separator near: ...%s<--", substr($content, ($i - 10), $i) "Parse Error: missing comma separator near: ...%s<--", substr($content, ($i - 10), $i)
)); ));
@ -423,11 +371,8 @@ class Extractor
$composing = false; $composing = false;
$delimiter = null; $delimiter = null;
} }
} } elseif (!$composing && in_array($c, $tokens)) {
elseif (!$composing && in_array($c, $tokens)) switch ($c) {
{
switch ($c)
{
case '=': case '=':
$prevDelimiter = $nextDelimiter = ''; $prevDelimiter = $nextDelimiter = '';
$level = 2; $level = 2;
@ -440,8 +385,7 @@ class Extractor
// If composing flag is true yet, // If composing flag is true yet,
// it means that the string was not enclosed, so it is parsing error. // it means that the string was not enclosed, so it is parsing error.
if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) {
{
throw new Exception(sprintf( throw new Exception(sprintf(
"Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c "Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
)); ));
@ -453,19 +397,16 @@ class Extractor
$subc = ''; $subc = '';
$subComposing = true; $subComposing = true;
while ($i <= $len) while ($i <= $len) {
{
$c = substr($content, $i++, 1); $c = substr($content, $i++, 1);
if (isset($delimiter) && $c === $delimiter) if (isset($delimiter) && $c === $delimiter) {
{
throw new Exception(sprintf( throw new Exception(sprintf(
"Parse Error: Composite variable is not enclosed correctly." "Parse Error: Composite variable is not enclosed correctly."
)); ));
} }
if ($c === '}') if ($c === '}') {
{
$subComposing = false; $subComposing = false;
break; break;
} }
@ -473,8 +414,7 @@ class Extractor
} }
// if the string is composing yet means that the structure of var. never was enclosed with '}' // if the string is composing yet means that the structure of var. never was enclosed with '}'
if ($subComposing) if ($subComposing) {
{
throw new Exception(sprintf( throw new Exception(sprintf(
"Parse Error: Composite variable is not enclosed correctly. near: ...%s'", $subc "Parse Error: Composite variable is not enclosed correctly. near: ...%s'", $subc
)); ));
@ -483,27 +423,18 @@ class Extractor
$val = self::parseArgs($subc); $val = self::parseArgs($subc);
break; break;
} }
} } else {
else if ($level == 1) {
{
if ($level == 1)
{
$var .= $c; $var .= $c;
} } elseif ($level == 2) {
elseif ($level == 2)
{
$val .= $c; $val .= $c;
} }
} }
if ($level === 3 || $i === $len) if ($level === 3 || $i === $len) {
{ if ($type == 'plain' && $i === $len) {
if ($type == 'plain' && $i === $len)
{
$data = self::castValue($var); $data = self::castValue($var);
} } else {
else
{
$data[trim($var)] = self::castValue($val, !$quoted); $data[trim($var)] = self::castValue($val, !$quoted);
} }
@ -526,28 +457,20 @@ class Extractor
*/ */
private static function castValue($val, $trim = false) private static function castValue($val, $trim = false)
{ {
if (is_array($val)) if (is_array($val)) {
{ foreach ($val as $key => $value) {
foreach ($val as $key => $value)
{
$val[$key] = self::castValue($value); $val[$key] = self::castValue($value);
} }
} } elseif (is_string($val)) {
elseif (is_string($val)) if ($trim) {
{
if ($trim)
{
$val = trim($val); $val = trim($val);
} }
$val = stripslashes($val); $val = stripslashes($val);
$tmp = strtolower($val); $tmp = strtolower($val);
if ($tmp === 'false' || $tmp === 'true') if ($tmp === 'false' || $tmp === 'true') {
{
$val = $tmp === 'true'; $val = $tmp === 'true';
} } elseif (is_numeric($val)) {
elseif (is_numeric($val))
{
return $val + 0; return $val + 0;
} }

View File

@ -8,6 +8,7 @@
<meta name="author" content="{$config.author}"> <meta name="author" content="{$config.author}">
<title>{$config.title}</title> <title>{$config.title}</title>
<link href="https://cdn.bootcss.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.bootcss.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/font-awesome/4.6.2/css/font-awesome.min.css" rel="stylesheet">
<style type="text/css"> <style type="text/css">
body { body {
padding-top: 70px; margin-bottom: 15px; padding-top: 70px; margin-bottom: 15px;
@ -28,6 +29,59 @@
.null { color: magenta; } .null { color: magenta; }
.key { color: red; } .key { color: red; }
.popover { max-width: 400px; max-height: 400px; overflow-y: auto;} .popover { max-width: 400px; max-height: 400px; overflow-y: auto;}
.list-group.panel > .list-group-item {
}
.list-group-item:last-child {
border-radius:0;
}
h4.panel-title a {
font-weight:normal;
font-size:14px;
}
h4.panel-title a .text-muted {
font-size:12px;
font-weight:normal;
font-family: 'Verdana';
}
#sidebar {
width: 220px;
position: fixed;
margin-left: -240px;
overflow-y:auto;
}
#sidebar > .list-group {
margin-bottom:0;
}
#sidebar > .list-group > a{
text-indent:0;
}
#sidebar .child {
border:1px solid #ddd;
border-bottom:none;
}
#sidebar .child > a {
border:0;
}
#sidebar .list-group a.current {
background:#f5f5f5;
}
@media (max-width: 1620px){
#sidebar {
margin:0;
}
#accordion {
padding-left:235px;
}
}
@media (max-width: 768px){
#sidebar {
display: none;
}
#accordion {
padding-left:0px;
}
}
</style> </style>
</head> </head>
<body> <body>
@ -68,15 +122,29 @@
</div> </div>
<div class="container"> <div class="container">
<!-- menu -->
<div id="sidebar">
<div class="list-group panel">
{foreach name="docslist" id="docs"}
<a href="#{$key}" class="list-group-item" data-toggle="collapse" data-parent="#sidebar">{$key} <i class="fa fa-caret-down"></i></a>
<div class="child collapse" id="{$key}">
{foreach name="docs" id="api" }
<a href="javascript:;" data-id="{$api.id}" class="list-group-item">{$api.title}</a>
{/foreach}
</div>
{/foreach}
</div>
</div>
<div class="panel-group" id="accordion"> <div class="panel-group" 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"> <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> <a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.route}</a> <span class="label {$api.method_label}">{$api.method|strtoupper}</span>
<a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.title} <span class="text-muted">{$api.route}</span></a>
</h4> </h4>
</div> </div>
<div id="collapseOne{$api.id}" class="panel-collapse collapse"> <div id="collapseOne{$api.id}" class="panel-collapse collapse">
@ -198,6 +266,7 @@
{/if} {/if}
<div class="form-group"> <div class="form-group">
<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>
</div> </div>
</form> </form>
</div> </div>
@ -349,6 +418,24 @@
placement: 'bottom' placement: 'bottom'
}); });
$(window).on("resize", function(){
$("#sidebar").css("max-height", $(window).height()-80);
});
$(window).trigger("resize");
$(document).on("click", "#sidebar .list-group > .list-group-item", function(){
$("#sidebar .list-group > .list-group-item").removeClass("current");
$(this).addClass("current");
});
$(document).on("click", "#sidebar .child a", function(){
var heading = $("#heading-"+$(this).data("id"));
if(!heading.next().hasClass("in")){
$("a", heading).trigger("click");
}
$("html,body").animate({scrollTop:heading.offset().top-70});
});
$('code[id^=response]').hide(); $('code[id^=response]').hide();
$.each($('pre[id^=sample_response],pre[id^=sample_post_body]'), function () { $.each($('pre[id^=sample_response],pre[id^=sample_post_body]'), function () {

View File

@ -104,6 +104,12 @@ class Crud extends Command
*/ */
protected $sortField = 'weigh'; protected $sortField = 'weigh';
/**
* 筛选字段
* @var string
*/
protected $headingFilterField = 'status';
/** /**
* 编辑器的Class * 编辑器的Class
*/ */
@ -138,6 +144,7 @@ class Crud extends Command
->addOption('selectpagessuffix', null, Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'automatically generate multiple selectpage component with suffix', null) ->addOption('selectpagessuffix', null, Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'automatically generate multiple selectpage component with suffix', null)
->addOption('ignorefields', null, Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'ignore fields', null) ->addOption('ignorefields', null, Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'ignore fields', null)
->addOption('sortfield', null, Option::VALUE_OPTIONAL, 'sort field', null) ->addOption('sortfield', null, Option::VALUE_OPTIONAL, 'sort field', null)
->addOption('headingfilterfield', null, Option::VALUE_OPTIONAL, 'heading filter field', null)
->addOption('editorclass', null, Option::VALUE_OPTIONAL, 'automatically generate editor class', null) ->addOption('editorclass', null, Option::VALUE_OPTIONAL, 'automatically generate editor class', null)
->setDescription('Build CRUD controller and model from table'); ->setDescription('Build CRUD controller and model from table');
} }
@ -198,6 +205,8 @@ class Crud extends Command
$ignoreFields = $input->getOption('ignorefields'); $ignoreFields = $input->getOption('ignorefields');
//排序字段 //排序字段
$sortfield = $input->getOption('sortfield'); $sortfield = $input->getOption('sortfield');
//顶部筛选过滤字段
$headingfilterfield = $input->getOption('headingfilterfield');
//编辑器Class //编辑器Class
$editorclass = $input->getOption('editorclass'); $editorclass = $input->getOption('editorclass');
if ($setcheckboxsuffix) if ($setcheckboxsuffix)
@ -224,6 +233,8 @@ class Crud extends Command
$this->editorClass = $editorclass; $this->editorClass = $editorclass;
if ($sortfield) if ($sortfield)
$this->sortField = $sortfield; $this->sortField = $sortfield;
if ($headingfilterfield)
$this->headingFilterField = $headingfilterfield;
$dbname = Config::get('database.database'); $dbname = Config::get('database.database');
$prefix = Config::get('database.prefix'); $prefix = Config::get('database.prefix');
@ -466,6 +477,8 @@ class Crud extends Command
$getEnumArr = []; $getEnumArr = [];
$appendAttrList = []; $appendAttrList = [];
$controllerAssignList = []; $controllerAssignList = [];
$headingHtml = '{:build_heading()}';
$headingJs = '';
//循环所有字段,开始构造视图的HTML和JS信息 //循环所有字段,开始构造视图的HTML和JS信息
foreach ($columnList as $k => $v) { foreach ($columnList as $k => $v) {
@ -681,6 +694,10 @@ class Crud extends Command
//构造JS列信息 //构造JS列信息
$javascriptList[] = $this->getJsColumn($field, $v['DATA_TYPE'], $inputType && in_array($inputType, ['select', 'checkbox', 'radio']) ? '_text' : '', $itemArr); $javascriptList[] = $this->getJsColumn($field, $v['DATA_TYPE'], $inputType && in_array($inputType, ['select', 'checkbox', 'radio']) ? '_text' : '', $itemArr);
} }
if ($this->headingFilterField && $this->headingFilterField == $field && $itemArr) {
$headingHtml = $this->getReplacedStub('html/heading-html', ['field' => $field]);
$headingJs = $this->getReplacedStub('html/heading-js', ['field' => $field]);
}
//排序方式,如果有指定排序字段,否则按主键排序 //排序方式,如果有指定排序字段,否则按主键排序
$order = $field == $this->sortField ? $this->sortField : $order; $order = $field == $this->sortField ? $this->sortField : $order;
} }
@ -724,6 +741,7 @@ class Crud extends Command
$modelInit = $this->getReplacedStub('mixins' . DS . 'modelinit', ['order' => $order]); $modelInit = $this->getReplacedStub('mixins' . DS . 'modelinit', ['order' => $order]);
} }
$data = [ $data = [
'controllerNamespace' => $controllerNamespace, 'controllerNamespace' => $controllerNamespace,
'modelNamespace' => $modelNamespace, 'modelNamespace' => $modelNamespace,
@ -753,6 +771,8 @@ class Crud extends Command
'relationWithList' => '', 'relationWithList' => '',
'relationMethodList' => '', 'relationMethodList' => '',
'controllerIndex' => '', 'controllerIndex' => '',
'headingHtml' => $headingHtml,
'headingJs' => $headingJs,
'visibleFieldList' => $fields ? "\$row->visible(['" . implode("','", array_filter(explode(',', $fields))) . "']);" : '', 'visibleFieldList' => $fields ? "\$row->visible(['" . implode("','", array_filter(explode(',', $fields))) . "']);" : '',
'appendAttrList' => implode(",\n", $appendAttrList), 'appendAttrList' => implode(",\n", $appendAttrList),
'getEnumList' => implode("\n\n", $getEnumArr), 'getEnumList' => implode("\n\n", $getEnumArr),

View File

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

View File

@ -0,0 +1,18 @@
// 绑定TAB事件
$('.panel-heading a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var field = $(this).closest("ul").data("field");
var value = $(this).data("value");
var options = table.bootstrapTable('getOptions');
options.pageNumber = 1;
options.queryParams = function (params) {
var filter = {};
if (value !== '') {
filter[field] = value;
}
params.filter = JSON.stringify(filter);
return params;
};
table.bootstrapTable('refresh', {});
return false;
});

View File

@ -1,5 +1,5 @@
<div class="panel panel-default panel-intro"> <div class="panel panel-default panel-intro">
{:build_heading()} {%headingHtml%}
<div class="panel-body"> <div class="panel-body">
<div id="myTabContent" class="tab-content"> <div id="myTabContent" class="tab-content">

View File

@ -28,6 +28,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
] ]
}); });
{%headingJs%}
// 为表格绑定事件 // 为表格绑定事件
Table.api.bindevent(table); Table.api.bindevent(table);
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -165,15 +165,6 @@ class Ajax extends Backend
$ids = array_values(array_intersect($ids, $hasids)); $ids = array_values(array_intersect($ids, $hasids));
} }
//直接修复排序
$one = Db::name($table)->field("{$field},COUNT(*) AS nums")->group($field)->having('nums > 1')->find();
if ($one) {
$list = Db::name($table)->field("$prikey,$field")->order($field, $orderway)->select();
foreach ($list as $k => $v) {
Db::name($table)->where($prikey, $v[$prikey])->update([$field => $k + 1]);
}
$this->success();
} else {
$list = Db::name($table)->field("$prikey,$field")->where($prikey, 'in', $ids)->order($field, $orderway)->select(); $list = Db::name($table)->field("$prikey,$field")->where($prikey, 'in', $ids)->order($field, $orderway)->select();
foreach ($list as $k => $v) { foreach ($list as $k => $v) {
$sour[] = $v[$prikey]; $sour[] = $v[$prikey];
@ -182,10 +173,6 @@ class Ajax extends Backend
$position = array_search($changeid, $ids); $position = array_search($changeid, $ids);
$desc_id = $sour[$position]; //移动到目标的ID值,取出所处改变前位置的值 $desc_id = $sour[$position]; //移动到目标的ID值,取出所处改变前位置的值
$sour_id = $changeid; $sour_id = $changeid;
$desc_value = $weighdata[$desc_id];
$sour_value = $weighdata[$sour_id];
//echo "移动的ID:{$sour_id}\n";
//echo "替换的ID:{$desc_id}\n";
$weighids = array(); $weighids = array();
$temp = array_values(array_diff_assoc($ids, $sour)); $temp = array_values(array_diff_assoc($ids, $sour));
foreach ($temp as $m => $n) { foreach ($temp as $m => $n) {
@ -203,7 +190,6 @@ class Ajax extends Backend
} }
$this->success(); $this->success();
} }
}
/** /**
* 清空系统缓存 * 清空系统缓存

View File

@ -15,6 +15,9 @@ use fast\Tree;
class Category extends Backend class Category extends Backend
{ {
/**
* @var \app\common\model\Category
*/
protected $model = null; protected $model = null;
protected $categorylist = []; protected $categorylist = [];
protected $noNeedRight = ['selectpage']; protected $noNeedRight = ['selectpage'];

View File

@ -30,7 +30,7 @@ class Index extends Backend
public function index() public function index()
{ {
//左侧菜单 //左侧菜单
list($menulist, $navlist) = $this->auth->getSidebar([ list($menulist, $navlist, $fixedmenu, $referermenu) = $this->auth->getSidebar([
'dashboard' => 'hot', 'dashboard' => 'hot',
'addon' => ['new', 'red', 'badge'], 'addon' => ['new', 'red', 'badge'],
'auth/rule' => __('Menu'), 'auth/rule' => __('Menu'),
@ -44,6 +44,8 @@ class Index extends Backend
} }
$this->view->assign('menulist', $menulist); $this->view->assign('menulist', $menulist);
$this->view->assign('navlist', $navlist); $this->view->assign('navlist', $navlist);
$this->view->assign('fixedmenu', $fixedmenu);
$this->view->assign('referermenu', $referermenu);
$this->view->assign('title', __('Home')); $this->view->assign('title', __('Home'));
return $this->view->fetch(); return $this->view->fetch();
} }

View File

@ -17,6 +17,9 @@ use fast\Tree;
class Admin extends Backend class Admin extends Backend
{ {
/**
* @var \app\admin\model\Admin
*/
protected $model = null; protected $model = null;
protected $childrenGroupIds = []; protected $childrenGroupIds = [];
protected $childrenAdminIds = []; protected $childrenAdminIds = [];

View File

@ -14,6 +14,9 @@ use app\common\controller\Backend;
class Adminlog extends Backend class Adminlog extends Backend
{ {
/**
* @var \app\admin\model\AdminLog
*/
protected $model = null; protected $model = null;
protected $childrenGroupIds = []; protected $childrenGroupIds = [];
protected $childrenAdminIds = []; protected $childrenAdminIds = [];

View File

@ -15,6 +15,9 @@ use fast\Tree;
class Group extends Backend class Group extends Backend
{ {
/**
* @var \app\admin\model\AuthGroup
*/
protected $model = null; protected $model = null;
//当前登录管理员所有子组别 //当前登录管理员所有子组别
protected $childrenGroupIds = []; protected $childrenGroupIds = [];

View File

@ -15,6 +15,9 @@ use think\Cache;
class Rule extends Backend class Rule extends Backend
{ {
/**
* @var \app\admin\model\AuthRule
*/
protected $model = null; protected $model = null;
protected $rulelist = []; protected $rulelist = [];
protected $multiFields = 'ismenu,status'; protected $multiFields = 'ismenu,status';

View File

@ -13,6 +13,9 @@ use app\common\controller\Backend;
class Attachment extends Backend class Attachment extends Backend
{ {
/**
* @var \app\common\model\Attachment
*/
protected $model = null; protected $model = null;
public function _initialize() public function _initialize()

View File

@ -16,6 +16,9 @@ use think\Exception;
class Config extends Backend class Config extends Backend
{ {
/**
* @var \app\common\model\Config
*/
protected $model = null; protected $model = null;
protected $noNeedRight = ['check']; protected $noNeedRight = ['check'];

View File

@ -13,7 +13,7 @@ class Group extends Backend
{ {
/** /**
* UserGroup模型对象 * @var \app\admin\model\UserGroup
*/ */
protected $model = null; protected $model = null;

View File

@ -13,8 +13,9 @@ use fast\Tree;
class Rule extends Backend class Rule extends Backend
{ {
/** /**
* UserRule模型对象 * @var \app\admin\model\UserRule
*/ */
protected $model = null; protected $model = null;
protected $rulelist = []; protected $rulelist = [];

View File

@ -14,8 +14,9 @@ class User extends Backend
protected $relationSearch = true; protected $relationSearch = true;
/** /**
* User模型对象 * @var \app\admin\model\User
*/ */
protected $model = null; protected $model = null;

View File

@ -17,6 +17,8 @@ return [
'Del' => '删除', 'Del' => '删除',
'Delete' => '删除', 'Delete' => '删除',
'Import' => '导入', 'Import' => '导入',
'Export' => '导出',
'All' => '全部',
'Detail' => '详情', 'Detail' => '详情',
'Multi' => '批量更新', 'Multi' => '批量更新',
'Setting' => '配置', 'Setting' => '配置',

View File

@ -367,7 +367,8 @@ class Auth extends \fast\Auth
// 读取管理员当前拥有的权限节点 // 读取管理员当前拥有的权限节点
$userRule = $this->getRuleList(); $userRule = $this->getRuleList();
$select_id = 0; $selected = $referer = [];
$refererUrl = Session::get('referer');
$pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader'); $pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
// 必须将结果集转换为数组 // 必须将结果集转换为数组
$ruleList = collection(\app\admin\model\AuthRule::where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray(); $ruleList = collection(\app\admin\model\AuthRule::where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray();
@ -376,15 +377,20 @@ class Auth extends \fast\Auth
unset($ruleList[$k]); unset($ruleList[$k]);
continue; continue;
} }
$select_id = $v['name'] == $fixedPage ? $v['id'] : $select_id;
$v['icon'] = $v['icon'] . ' fa-fw'; $v['icon'] = $v['icon'] . ' fa-fw';
$v['url'] = '/' . $module . '/' . $v['name']; $v['url'] = '/' . $module . '/' . $v['name'];
$v['badge'] = isset($badgeList[$v['name']]) ? $badgeList[$v['name']] : ''; $v['badge'] = isset($badgeList[$v['name']]) ? $badgeList[$v['name']] : '';
$v['py'] = $pinyin->abbr($v['title'], ''); $v['py'] = $pinyin->abbr($v['title'], '');
$v['pinyin'] = $pinyin->permalink($v['title'], ''); $v['pinyin'] = $pinyin->permalink($v['title'], '');
$v['title'] = __($v['title']); $v['title'] = __($v['title']);
$selected = $v['name'] == $fixedPage ? $v : $selected;
$referer = $v['url'] == $refererUrl ? $v : $referer;
}
if ($selected == $referer) {
$referer = [];
} }
$select_id = $selected ? $selected['id'] : 0;
$menu = $nav = ''; $menu = $nav = '';
if (Config::get('fastadmin.multiplenav')) { if (Config::get('fastadmin.multiplenav')) {
$topList = []; $topList = [];
@ -412,10 +418,16 @@ class Auth extends \fast\Auth
// 构造菜单数据 // 构造菜单数据
Tree::instance()->init($ruleList); Tree::instance()->init($ruleList);
$menu = Tree::instance()->getTreeMenu(0, '<li class="@class"><a href="@url@addtabs" addtabs="@id" url="@url" py="@py" pinyin="@pinyin"><i class="@icon"></i> <span>@title</span> <span class="pull-right-container">@caret @badge</span></a> @childlist</li>', $select_id, '', 'ul', 'class="treeview-menu"'); $menu = Tree::instance()->getTreeMenu(0, '<li class="@class"><a href="@url@addtabs" addtabs="@id" url="@url" py="@py" pinyin="@pinyin"><i class="@icon"></i> <span>@title</span> <span class="pull-right-container">@caret @badge</span></a> @childlist</li>', $select_id, '', 'ul', 'class="treeview-menu"');
if ($selected) {
$nav .= '<li role="presentation" id="tab_' . $selected['id'] . '" class="' . ($referer ? '' : 'active') . '"><a href="#con_' . $selected['id'] . '" node-id="' . $selected['id'] . '" aria-controls="' . $selected['id'] . '" role="tab" data-toggle="tab"><i class="' . $selected['icon'] . ' fa-fw"></i> <span>' . $selected['title'] . '</span> </a></li>';
}
if ($referer) {
$nav .= '<li role="presentation" id="tab_' . $referer['id'] . '" class="active"><a href="#con_' . $referer['id'] . '" node-id="' . $referer['id'] . '" aria-controls="' . $referer['id'] . '" role="tab" data-toggle="tab"><i class="' . $referer['icon'] . ' fa-fw"></i> <span>' . $referer['title'] . '</span> </a> <i class="close-tab fa fa-remove"></i></li>';
}
} }
return [$menu, $nav]; return [$menu, $nav, $selected, $referer];
} }
/** /**

View File

@ -92,6 +92,8 @@ trait Backend
} }
} catch (\think\exception\PDOException $e) { } catch (\think\exception\PDOException $e) {
$this->error($e->getMessage()); $this->error($e->getMessage());
} catch (\think\Exception $e) {
$this->error($e->getMessage());
} }
} }
$this->error(__('Parameter %s can not be empty', '')); $this->error(__('Parameter %s can not be empty', ''));
@ -131,6 +133,8 @@ trait Backend
} }
} catch (\think\exception\PDOException $e) { } catch (\think\exception\PDOException $e) {
$this->error($e->getMessage()); $this->error($e->getMessage());
} catch (\think\Exception $e) {
$this->error($e->getMessage());
} }
} }
$this->error(__('Parameter %s can not be empty', '')); $this->error(__('Parameter %s can not be empty', ''));
@ -322,6 +326,8 @@ trait Backend
$this->model->saveAll($insert); $this->model->saveAll($insert);
} catch (\think\exception\PDOException $exception) { } catch (\think\exception\PDOException $exception) {
$this->error($exception->getMessage()); $this->error($exception->getMessage());
} catch (\Exception $e) {
$this->error($e->getMessage());
} }
$this->success(); $this->success();

View File

@ -14,7 +14,7 @@
<div class="tab-pane fade active in" id="one"> <div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding"> <div class="widget-body no-padding">
<div id="toolbar" class="toolbar"> <div id="toolbar" class="toolbar">
{:build_toolbar()} {:build_toolbar('refresh,add,edit,del')}
<div class="dropdown btn-group {:$auth->check('category/multi')?'':'hide'}"> <div class="dropdown btn-group {:$auth->check('category/multi')?'':'hide'}">
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a> <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
<ul class="dropdown-menu text-left" role="menu"> <ul class="dropdown-menu text-left" role="menu">

View File

@ -141,6 +141,12 @@
<!--第二级菜单,只有在multiplenav开启时才显示--> <!--第二级菜单,只有在multiplenav开启时才显示-->
<div id="secondnav"> <div id="secondnav">
<ul class="nav nav-tabs nav-addtabs disable-top-badge" role="tablist"> <ul class="nav nav-tabs nav-addtabs disable-top-badge" role="tablist">
{if $fixedmenu}
<li role="presentation" id="tab_{$fixedmenu.id}" class="{:$referermenu?'':'active'}"><a href="#con_{$fixedmenu.id}" node-id="{$fixedmenu.id}" aria-controls="{$fixedmenu.id}" role="tab" data-toggle="tab"><i class="fa fa-dashboard fa-fw"></i> <span>{$fixedmenu.title}</span> <span class="pull-right-container"> </span></a></li>
{/if}
{if $referermenu}
<li role="presentation" id="tab_{$referermenu.id}" class="active"><a href="#con_{$referermenu.id}" node-id="{$referermenu.id}" aria-controls="{$referermenu.id}" role="tab" data-toggle="tab"><i class="fa fa-list fa-fw"></i> <span>{$referermenu.title}</span> <span class="pull-right-container"> </span></a> <i class="close-tab fa fa-remove"></i></li>
{/if}
</ul> </ul>
</div> </div>
{/if} {/if}

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{$config.language}"> <html lang="{$config.language}">
<head> <head>
<!-- 加载部部样式及META信息 --> <!-- 加载样式及META信息 -->
{include file="common/meta" /} {include file="common/meta" /}
</head> </head>
<body class="hold-transition skin-green sidebar-mini fixed {if $config.fastadmin.multiplenav}multiplenav{/if}" id="tabs"> <body class="hold-transition skin-green sidebar-mini fixed {if $config.fastadmin.multiplenav}multiplenav{/if}" id="tabs">
@ -19,7 +19,16 @@
<!-- 主体内容区域 --> <!-- 主体内容区域 -->
<div class="content-wrapper tab-content tab-addtabs"> <div class="content-wrapper tab-content tab-addtabs">
{if $fixedmenu}
<div role="tabpanel" class="tab-pane {:$referermenu?'':'active'}" id="con_{$fixedmenu.id}">
<iframe src="{$fixedmenu.url}?addtabs=1" width="100%" height="100%" frameborder="no" border="0" marginwidth="0" marginheight="0" scrolling-x="no" scrolling-y="auto" allowtransparency="yes"></iframe>
</div>
{/if}
{if $referermenu}
<div role="tabpanel" class="tab-pane active" id="con_{$referermenu.id}">
<iframe src="{$referermenu.url}?addtabs=1" width="100%" height="100%" frameborder="no" border="0" marginwidth="0" marginheight="0" scrolling-x="no" scrolling-y="auto" allowtransparency="yes"></iframe>
</div>
{/if}
</div> </div>
<!-- 底部链接,默认隐藏 --> <!-- 底部链接,默认隐藏 -->

View File

@ -6,7 +6,7 @@
<div class="tab-pane fade active in" id="one"> <div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding"> <div class="widget-body no-padding">
<div id="toolbar" class="toolbar"> <div id="toolbar" class="toolbar">
{:build_toolbar()} {:build_toolbar('refresh,add,edit,del')}
<div class="dropdown btn-group {:$auth->check('user/group/multi')?'':'hide'}"> <div class="dropdown btn-group {:$auth->check('user/group/multi')?'':'hide'}">
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a> <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
<ul class="dropdown-menu text-left" role="menu"> <ul class="dropdown-menu text-left" role="menu">

View File

@ -15,10 +15,34 @@ class Demo extends Api
//如果接口已经设置无需登录,那也就无需鉴权了 //如果接口已经设置无需登录,那也就无需鉴权了
// //
// 无需登录的接口,*表示全部 // 无需登录的接口,*表示全部
protected $noNeedLogin = ['test1']; protected $noNeedLogin = ['test', 'test1'];
// 无需鉴权的接口,*表示全部 // 无需鉴权的接口,*表示全部
protected $noNeedRight = ['test2']; protected $noNeedRight = ['test2'];
/**
* 测试方法
*
* @ApiTitle (测试名称)
* @ApiSummary (测试描述信息)
* @ApiMethod (POST)
* @ApiRoute (/api/demo/test/id/{id}/name/{name})
* @ApiHeaders (name=token, type=string, required=true, description="请求的Token")
* @ApiParams (name="id", type="integer", required=true, description="会员ID")
* @ApiParams (name="name", type="string", required=true, description="用户名")
* @ApiParams (name="data", type="object", sample="{'user_id':'int','user_name':'string','profile':{'email':'string','age':'integer'}}", description="扩展数据")
* @ApiReturnParams (name="code", type="integer", required=true, sample="0")
* @ApiReturnParams (name="msg", type="string", required=true, sample="返回成功")
* @ApiReturnParams (name="data", type="object", sample="{'user_id':'int','user_name':'string','profile':{'email':'string','age':'integer'}}", description="扩展数据返回")
* @ApiReturn ({
'code':'1',
'msg':'返回成功'
})
*/
public function test()
{
$this->success('返回成功', $this->request->param());
}
/** /**
* 无需登录的接口 * 无需登录的接口
* *

View File

@ -303,7 +303,7 @@ class Backend extends Controller
case 'FINDIN': case 'FINDIN':
case 'FINDINSET': case 'FINDINSET':
case 'FIND_IN_SET': case 'FIND_IN_SET':
$where[] = "FIND_IN_SET('{$v}', " . ($this->relationSearch ? $k : '`' . str_replace('.', '`.`', $k) . '`') . ")"; $where[] = "FIND_IN_SET('{$v}', " . ($relationSearch ? $k : '`' . str_replace('.', '`.`', $k) . '`') . ")";
break; break;
case 'IN': case 'IN':
case 'IN(...)': case 'IN(...)':

View File

@ -31,7 +31,7 @@
"fastadmin-citypicker": "~1.3.0", "fastadmin-citypicker": "~1.3.0",
"fastadmin-cxselect": "~1.4.0", "fastadmin-cxselect": "~1.4.0",
"fastadmin-dragsort": "~1.0.0", "fastadmin-dragsort": "~1.0.0",
"fastadmin-addtabs": "~1.0.0", "fastadmin-addtabs": "~1.0.3",
"fastadmin-selectpage": "~1.0.0", "fastadmin-selectpage": "~1.0.0",
"fastadmin-layer": "~3.1.2" "fastadmin-layer": "~3.1.2"
} }

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,10 @@
@import url("../libs/nice-validator/dist/jquery.validator.css"); @import url("../libs/nice-validator/dist/jquery.validator.css");
@import url("../libs/bootstrap-select/dist/css/bootstrap-select.min.css"); @import url("../libs/bootstrap-select/dist/css/bootstrap-select.min.css");
@import url("../libs/fastadmin-selectpage/selectpage.css"); @import url("../libs/fastadmin-selectpage/selectpage.css");
html,
body {
height: 100%;
}
body { body {
background: #f1f4f6; background: #f1f4f6;
font-size: 13px; font-size: 13px;
@ -44,18 +48,19 @@ html.ios-fix body {
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
.wrapper {
height: 100%;
}
#header { #header {
background: #fff; background: #fff;
} }
.content-wrapper { .content-wrapper {
position: relative; position: relative;
height: 100%;
} }
.control-relative { .control-relative {
position: relative; position: relative;
} }
.tab-addtabs {
overflow: hidden;
}
.tab-addtabs .tab-pane { .tab-addtabs .tab-pane {
height: 100%; height: 100%;
width: 100%; width: 100%;

File diff suppressed because one or more lines are too long

View File

@ -245,11 +245,11 @@ function _init() {
$.AdminLTE.layout = { $.AdminLTE.layout = {
activate: function () { activate: function () {
var _this = this; var _this = this;
_this.fix(); //_this.fix();
_this.fixSidebar(); _this.fixSidebar();
$('body, html, .wrapper').css('height', 'auto'); //$('body, html, .wrapper').css('height', 'auto');
$(window, ".wrapper").resize(function () { $(window, ".wrapper").resize(function () {
_this.fix(); //_this.fix();
_this.fixSidebar(); _this.fixSidebar();
}); });
}, },
@ -782,16 +782,13 @@ function _init() {
}; };
//set/get form element value //set/get form element value
$.fn.field = function (name, value) $.fn.field = function (name, value) {
{
if (typeof name !== "string") if (typeof name !== "string")
return false; return false;
var element = $(this).find("[name='" + name + "']"); var element = $(this).find("[name='" + name + "']");
if (typeof value === "undefined" && element.length >= 1) if (typeof value === "undefined" && element.length >= 1) {
{ switch (element.attr("type")) {
switch (element.attr("type"))
{
case "checkbox": case "checkbox":
var result = new Array(); var result = new Array();
element.each(function (i, val) { element.each(function (i, val) {
@ -814,10 +811,8 @@ function _init() {
return element.val(); return element.val();
break; break;
} }
} else } else {
{ switch (element.attr("type")) {
switch (element.attr("type"))
{
case "checkbox": case "checkbox":
case "radio": case "radio":
value = $.isArray(value) ? value : [value]; value = $.isArray(value) ? value : [value];

View File

@ -10,7 +10,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
edit_url: 'category/edit', edit_url: 'category/edit',
del_url: 'category/del', del_url: 'category/del',
multi_url: 'category/multi', multi_url: 'category/multi',
dragsort_url: '', dragsort_url: 'ajax/weigh',
table: 'category', table: 'category',
} }
}); });

View File

@ -29,9 +29,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
boundaryGap: false, boundaryGap: false,
data: Orderdata.column data: Orderdata.column
}, },
yAxis: { yAxis: {},
},
grid: [{ grid: [{
left: 'left', left: 'left',
top: 'top', top: 'top',
@ -43,8 +41,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
type: 'line', type: 'line',
smooth: true, smooth: true,
areaStyle: { areaStyle: {
normal: { normal: {}
}
}, },
lineStyle: { lineStyle: {
normal: { normal: {
@ -58,8 +55,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
type: 'line', type: 'line',
smooth: true, smooth: true,
areaStyle: { areaStyle: {
normal: { normal: {}
}
}, },
lineStyle: { lineStyle: {
normal: { normal: {
@ -100,12 +96,15 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
data: Orderdata.createdata data: Orderdata.createdata
}] }]
}); });
if ($("#echart").width() != $("#echart canvas").width() && $("#echart canvas").width() < $("#echart").width()) {
myChart.resize();
}
}, 2000); }, 2000);
$(window).resize(function () { $(window).resize(function () {
myChart.resize(); myChart.resize();
}); });
$(document).on("click", ".btn-checkversion", function(){ $(document).on("click", ".btn-checkversion", function () {
top.window.$("[data-toggle=checkupdate]").trigger("click"); top.window.$("[data-toggle=checkupdate]").trigger("click");
}); });

View File

@ -1,11 +1,6 @@
define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], function ($, undefined, Backend, undefined, AdminLTE, Form) { define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], function ($, undefined, Backend, undefined, AdminLTE, Form) {
var Controller = { var Controller = {
index: function () { index: function () {
//窗口大小改变,修正主窗体最小高度
$(window).resize(function () {
$(".tab-addtabs").css("height", $(".content-wrapper").height() + "px");
});
//双击重新加载页面 //双击重新加载页面
$(document).on("dblclick", ".sidebar-menu li > a", function (e) { $(document).on("dblclick", ".sidebar-menu li > a", function (e) {
$("#con_" + $(this).attr("addtabs") + " iframe").attr('src', function (i, val) { $("#con_" + $(this).attr("addtabs") + " iframe").attr('src', function (i, val) {

View File

@ -10517,6 +10517,7 @@ define("drop", function(){});
if (options.close && $("li", navobj).size() > 0) { if (options.close && $("li", navobj).size() > 0) {
tabitem.append(' <i class="close-tab fa fa-remove"></i>'); tabitem.append(' <i class="close-tab fa fa-remove"></i>');
} }
if (conitem.size() === 0) {
//创建新TAB的内容 //创建新TAB的内容
conitem = $('<div role="tabpanel" class="tab-pane" id="' + conid + '"></div>'); conitem = $('<div role="tabpanel" class="tab-pane" id="' + conid + '"></div>');
//是否指定TAB内容 //是否指定TAB内容
@ -10530,13 +10531,14 @@ define("drop", function(){});
conitem.append(data); conitem.append(data);
}); });
} }
tabobj.append(conitem);
}
//加入TABS //加入TABS
if ($('.tabdrop li', navobj).size() > 0) { if ($('.tabdrop li', navobj).size() > 0) {
$('.tabdrop ul', navobj).append(tabitem); $('.tabdrop ul', navobj).append(tabitem);
} else { } else {
navobj.append(tabitem); navobj.append(tabitem);
} }
tabobj.append(conitem);
} else { } else {
//强制刷新iframe //强制刷新iframe
if (options.iframeForceRefresh) { if (options.iframeForceRefresh) {

View File

@ -21,6 +21,11 @@
@panel-intro-bg: darken(@main-bg, 3%); @panel-intro-bg: darken(@main-bg, 3%);
@panel-nav-bg: #fff; @panel-nav-bg: #fff;
html,
body {
height: 100%;
}
body { body {
background: #f1f4f6; background: #f1f4f6;
font-size: 13px; font-size: 13px;
@ -56,7 +61,9 @@ html.ios-fix, html.ios-fix body {
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
.wrapper {
height: 100%;
}
#header { #header {
background: #fff; background: #fff;
//box-shadow: 0 2px 2px rgba(0,0,0,.05),0 1px 0 rgba(0,0,0,.05); //box-shadow: 0 2px 2px rgba(0,0,0,.05),0 1px 0 rgba(0,0,0,.05);
@ -64,6 +71,7 @@ html.ios-fix, html.ios-fix body {
.content-wrapper { .content-wrapper {
position: relative; position: relative;
height:100%;
} }
.control-relative { .control-relative {
@ -71,7 +79,7 @@ html.ios-fix, html.ios-fix body {
} }
.tab-addtabs { .tab-addtabs {
overflow: hidden; //overflow: hidden;
.tab-pane { .tab-pane {
height: 100%; height: 100%;
width: 100%; width: 100%;