代码功能更新

This commit is contained in:
root
2025-05-18 07:42:27 +00:00
parent 7fc1d422a1
commit 4a90c41da2
2083 changed files with 695218 additions and 3 deletions
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace plugin\apidoc\controller;
use laytp\controller\Backend;
use laytp\library\CommonFun;
use laytp\library\UploadDomain;
class Index extends Backend
{
/**
* apidoc模型对象
* @var \plugin\devtool\model\Apidoc
*/
protected $model;
public $hasSoftDel = 1;//是否拥有软删除功能
protected $noNeedLogin = ['getMenu'];
public function initialize()
{
parent::initialize();
$this->model = new \plugin\apidoc\model\Apidoc();
}
//添加
public function add()
{
$post = CommonFun::filterPostData($this->request->post());
$post['des'] = UploadDomain::delUploadDomain($post['des']);
if ($this->model->create($post)) {
return $this->success('添加成功', $post);
} else {
return $this->error('操作失败');
}
}
//编辑
public function edit()
{
$id = $this->request->param('id');
$info = $this->model->find($id);
$post = CommonFun::filterPostData($this->request->post());
$post['des'] = UploadDomain::delUploadDomain($post['des']);
foreach ($post as $k => $v) {
$info->$k = $v;
}
try {
$updateRes = $info->save();
if ($updateRes) {
return $this->success('编辑成功');
} else if ($updateRes === 0) {
return $this->success('未做修改');
} else if ($updateRes === null) {
return $this->error('操作失败');
}
} catch (\Exception $e) {
return $this->error('数据库异常,操作失败');
}
}
//生成常规CURD
public function create()
{
$api = new \plugin\apidoc\library\Apidoc();
if ($api->execute('api文档')) {
return $this->success('生成成功');
} else {
return $this->error($api->getError());
}
}
// 获取文档菜单接口
public function getMenu()
{
$menuOutputFile = app()->getRootPath() . 'public' . DS . 'static' . DS . 'admin' . DS . 'data' . DS . 'apidocMenu.json';
$menuJson = file_get_contents($menuOutputFile);
$menu = json_decode($menuJson, true);
return $this->success('生成成功', $menu);
}
}
@@ -0,0 +1,164 @@
<?php
use think\migration\Migrator;
class PluginApiDoc extends Migrator
{
public function change()
{
$table = $this->table('plugin_apidoc', [
'engine' => 'InnoDB',
'comment' => 'Api文档',
'collation' => 'utf8mb4_general_ci',
]);
//删除表
if ($table->exists()) {
$table->drop();
}
$table
->addColumn('title', 'string', ['limit' => 255, 'default' => '', 'comment' => '标题'])
->addColumn('des', 'text', ['comment' => '描述'])
->addColumn('create_time', 'datetime', ['null' => 1, 'comment' => '创建时间'])
->addColumn('update_time', 'datetime', ['null' => 1, 'comment' => '更新时间'])
->addColumn('delete_time', 'datetime', ['null' => 1, 'comment' => '删除时间'])
;
$data = [
[
'title' => '文档更新',
'des' => '本文档分为两个部分,`全局说明文档`和`其他文档`。
# 全局说明文档
全局说明下的文档在后台生成Api文档菜单中进行添加,添加完成后点击生成Api文档按钮即可在本文档中看见
# 其他文档
其他文档使用PHP程序,通过PHP的类反射机制,获取到`app/controller/api/`目录下所有文件的注解来进行生成。
注解规则请查阅`laytp.com`官网手册或者框架`app/controller/api/Demo.php`文件。
如果后端程序员更新了`api`接口程序,并且使用了相应的注解规则,在后台点击`生成Api文档`按钮即可在本文档看到最新的Api文档
# 注意点
由于文档使用的是静态html展示,而浏览器对html页面是有缓存的。所以如果文档进行了更新。可能需要强制刷新页面才能看到最新的文档。',
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s'),
],
[
'title' => '在线测试功能使用',
'des' => '本文档使用`Laytp极速后台开发框架 - 生成Api文档插件`进行生成。
如果需要使用本文档的在线测试功能,需要先进行Api文档配置。
### Api文档配置
点击本文档右上角配置图标会弹出配置层。
配置项包括:Api请求域名、签名开关、Header参数。
#### Api请求域名
后台使用单域名部署模式,则无需修改,使用默认值即可。
后台使用多域名部署模式,则后端程序员需要提供请求Api的域名地址。使用者不要以/结尾,将请求Api的域名地址填入此处
#### 签名开关
请求Api有签名中间件,签名中间件是否启用要根据后台[系统配置 - 基础配置 - Api签名开关]是否开启的配置来决定。如果后台配置[Api签名开关]开启了,此处也要开启。此处开启后,使用本文档的在线测试功能时,在ajax请求头部会自动添加request-time和sign两个参数。至于签名具体如何生成,请查阅[签名相关]章节说明
#### Header参数
此处填入后端程序员自定义的请求头部参数。框架自定义的请求头部参数有用户登录凭证token,请求时间戳request-time,签名sign三个。一个复杂的系统,一般还会在Header头部定义一些公用参数。比如平台标识,代理标识等等。这些公用参数根据需求不同由后端程序员定义,在使用本文档时,自行在此处进行添加
### 配置保存
点击保存按钮,或者点击页面阴影部分遮罩层,配置都会进行保存
### 配置持久化
Api文档配置使用的是localStorage存储在浏览器端进行持久化的。无需担心页面关闭后,配置不存在的问题',
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s'),
],
[
'title' => '统一说明',
'des' => '# 请求方式
默认使用POST方式进行请求
# 请求头Content-Type
请求头的Content-Type定义请求参数的数据类型。ThinkPHP6兼容多种常用请求头方式。比如`application/json`、`application/form-data`和 `application/x-www-form-urlencoded`。
一般的接口,客户端可以使用`application/json`,自行将参数定义成`json`格式进行参数传递。
文件上传不支持`json`方式上传文件`Base64`内容,需要使用提交表单方式上传文件。
# 接口域名
- 正式环境
由后端程序员提供
- 测试环境
由后端程序员提供
# 后台地址
- 正式环境
- 访问地址
由后端程序员提供
- 账号
admin
- 密码
123456
- 测试环境
- 访问地址
由后端程序员提供
- 账号
admin
- 密码
123456',
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s'),
],
[
'title' => '签名相关',
'des' => '客户端请求Api接口程序,后端有签名中间件对请求进行拦截。
签名中间件是否启用要根据后台[系统配置 - 基础配置 - Api签名开关]是否开启的配置来决定。
如果后台配置[Api签名开关]开启了,那么客户端在请求Api接口时,需要在Header部分传递两个参数`request-time`和`sign`。
`request-time`的值由客户端自行定义为当前Unix时间戳。
`sign`的值由签名生成规则计算生成。
# 签名生成规则
- 客户端自行定义request-time的值为当前Unix时间戳,并经过`md5`运算得到`stringA`
- 后台系统配置 - 基础配置 - Api签名Key的值为生成签名的Key,并经过`md5`运算得到`stringB`
- 连接`stringA`和`stringB`后经过`md5`运算得到`stringC`
- 最后将`stringC`全部转成大写即是客户端需要在请求头部分传递的`sign`值',
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s'),
],
[
'title' => '接口返回',
'des' => '接口统一返回`json`格式的数据。
# 返回说明
|参数名|必然存在|类型|说明|
|:---- |:---|:----- |----- |
|code |是 |integer |接口返回码.0=常规正确码,表示常规操作成功;1=常规错误码,客户端仅需提示message;其他返回码与具体业务相关。框架实现了的唯一其他返回码:10401,前端需要跳转至登录界面。在一个复杂的交互过程中,你可能需要自行定义其他返回码|
|msg |是 |string | 接口返回文字描述 |
|time |是 |integer | 接口返回时间戳,单位秒 |
|data |是 |object/array | 附加数据。单条数据是对象,多条数据是数组。当为空时,会返回一个空对象{}|
# 返回示例
```
{
"code":1,
"msg":"签名错误",
"time":1613628412,
"data":{}
}
```',
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s'),
],
];
$table->setData($data)->create();
}
}
+8
View File
@@ -0,0 +1,8 @@
name = apidoc
title = 生成Api文档
description = 根据Api接口注释,生成Api文档。支持签名验证,在线测试。
version = 1.0.3
author = Laytp官方
lt_version = 3.0.0
parent_menu = first
menu_ids = 102,103,104,105,106,107,108,109,110,111
+187
View File
@@ -0,0 +1,187 @@
<?php
namespace plugin\apidoc\library;
use app\service\ConfServiceFacade;
use laytp\traits\Error;
use plugin\apidoc\library\apidoc\library\Builder;
use plugin\apidoc\library\apidoc\library\Extractor;
use laytp\library\DirFile;
use think\facade\Env;
use think\facade\View;
class Apidoc
{
use Error;
protected $template = 'index.html';
protected $output = 'api.html';
protected $force = true;
/**
* 执行生成程序
* @param $title
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function execute($title)
{
// $addon = $input->getOption('addon');
// if($addon){
// $addon_service = new Addons();
// $addon_info = $addon_service->_info->getPluginInfo($addon);
// $controllerDir = Env::get('root_path') . DS . 'addons' . DS . $addon . DS . $addon_info['api_module'] . DS;
// }else{
$controllerDir = app()->getRootPath() . DS . 'app' . DS . 'controller' . DS . 'api' . DS;
// }
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
);
$weighs = [];
$k = 0;
foreach ($files as $name => $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$className = $this->getClassFromFile($filePath);
$classAnnotations = Extractor::getClassAnnotations($className);
if (isset($classAnnotations['ApiInternal'])) {
continue;
}
$weigh = isset($classAnnotations['ApiWeigh']) ? intval($classAnnotations['ApiWeigh'][0]) : $k;
$weighs[$this->getClassFromFile($filePath)] = $weigh;
$k++;
}
}
uasort($weighs, function ($a, $b) {
if ($a == $b) return 0;
return ($a > $b) ? -1 : 1;
});
$classes = array_flip($weighs);
$builder = new Builder($classes);
$apiDir = __DIR__ . DS . 'apidoc' . DS;
$templateDir = $apiDir . 'template' . DS;
$templateFile = $templateDir . $this->template;
$var['plugin'] = '';
$var['title'] = $title;
$var['apiDomain'] = Env::get('domain.api');
$var['createSignKey'] = ConfServiceFacade::get('system.basic.signKey');
$var['apidocList'] = \plugin\apidoc\model\Apidoc::select()->toArray();
// $content = $builder->render($templateFile, $var);
$docslist = $builder->render($templateFile, $var);
$menu = [];
$menu[] = [
'id' => 0,
'title' => '全局说明',
'docType' => 'meditor'
];
$key = 0;
foreach($var['apidocList'] as $k=>$v){
$key++;
$temp = [];
$temp['id'] = $key;
$temp['title'] = $v['title'];
$temp['docType'] = 'meditor';
$menu[0]['children'][] = $temp;
}
foreach($docslist as $k=>$v){
$key++;
$temp = [];
$temp['id'] = $key;
$temp['title'] = $k;
$temp['docType'] = 'api';
foreach($v as $api){
$key++;
$children = [];
$children['id'] = $key;
$children['title'] = $api['title'];
$children['docType'] = 'api';
$temp['children'][] = $children;
}
$menu[] = $temp;
}
$menuOutputFile = app()->getRootPath() . 'public' . DS . 'static' . DS . 'admin' . DS . 'data' . DS . 'apidocMenu.json';
file_put_contents($menuOutputFile, json_encode($menu, JSON_UNESCAPED_UNICODE));
View::engine()->layout(false);
$content = View::display(file_get_contents($templateFile), array_merge($var, ['docslist' => $docslist]));
$outputDir = app()->getRootPath() . DS . 'public' . DS;
$outputFile = $outputDir . $this->output;
DirFile::createDir(dirname($outputFile));
if (!file_put_contents($outputFile, $content)) {
$this->setError('Cannot save the content to ' . $outputFile);
return false;
}
return true;
}
/**
* get full qualified class name
*
* @param string $path_to_file
* @return string
* @author JBYRNE http://jarretbyrne.com/2015/06/197/
*/
protected function getClassFromFile($path_to_file)
{
//Grab the contents of the file
$contents = file_get_contents($path_to_file);
//Start with a blank namespace and class
$namespace = $class = "";
//Set helper values to know that we have found the namespace/class token and need to collect the string values after them
$getting_namespace = $getting_class = false;
//Go through each token and evaluate it as necessary
foreach (token_get_all($contents) as $token) {
//If this token is the namespace declaring, then flag that the next tokens will be the namespace name
if (is_array($token) && $token[0] == T_NAMESPACE) {
$getting_namespace = true;
}
//If this token is the class declaring, then flag that the next tokens will be the class name
if (is_array($token) && $token[0] == T_CLASS) {
$getting_class = true;
}
//While we're grabbing the namespace name...
if ($getting_namespace === true) {
//If the token is a string or the namespace separator...
if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {
//Append the token's value to the name of the namespace
$namespace .= $token[1];
} else if ($token === ';') {
//If the token is the semicolon, then we're done with the namespace declaration
$getting_namespace = false;
}
}
//While we're grabbing the class name...
if ($getting_class === true) {
//If the token is a string, it's the name of the class
if (is_array($token) && $token[0] == T_STRING) {
//Store the token's value as the class name
$class = $token[1];
//Got what we need, stope here
break;
}
}
}
//Build the fully-qualified class name and return it
return $namespace ? $namespace . '\\' . $class : $class;
}
}
@@ -0,0 +1,209 @@
<?php
namespace plugin\apidoc\library\apidoc\library;
use think\facade\View;
/**
* @website https://github.com/calinrada/php-apidoc
* @author Calin Rada <rada.calin@gmail.com>
* @author Karson <karsonzhang@163.com>
*/
class Builder
{
/**
*
* @var \think\View
*/
public $view = null;
/**
* parse classes
* @var array
*/
protected $classes = [];
/**
*
* @param array $classes
*/
public function __construct($classes = [])
{
$this->classes = array_merge($this->classes, $classes);
}
protected function extractAnnotations()
{
$st_output = [];
foreach ($this->classes as $class) {
$st_output[] = Extractor::getAllClassAnnotations($class);
}
return end($st_output);
}
protected function generateHeadersTemplate($docs)
{
if (!isset($docs['ApiHeaders'])) {
return [];
}
$headerslist = [];
foreach ($docs['ApiHeaders'] as $params) {
$tr = [
'name' => $params['name'],
'type' => $params['type'],
'sample' => isset($params['sample']) ? $params['sample'] : '',
'required' => isset($params['required']) ? $params['required'] : false,
'description' => isset($params['description']) ? $params['description'] : '',
];
$headerslist[] = $tr;
}
return $headerslist;
}
protected function generateParamsTemplate($docs)
{
if (!isset($docs['ApiParams'])) {
return [];
}
$paramslist = [];
foreach ($docs['ApiParams'] as $params) {
$tr = [
'name' => $params['name'],
'type' => isset($params['type']) ? $params['type'] : 'string',
'sample' => isset($params['sample']) ? $params['sample'] : '',
'required' => isset($params['required']) ? $params['required'] : true,
'description' => isset($params['description']) ? $params['description'] : '',
];
$paramslist[] = $tr;
}
return $paramslist;
}
protected function generateReturnHeadersTemplate($docs)
{
if (!isset($docs['ApiReturnHeaders'])) {
return [];
}
$headerslist = [];
foreach ($docs['ApiReturnHeaders'] as $params) {
$tr = [
'name' => $params['name'],
'type' => 'string',
'sample' => isset($params['sample']) ? $params['sample'] : '',
'required' => isset($params['required']) && $params['required'] ? 'Yes' : 'No',
'description' => isset($params['description']) ? $params['description'] : '',
];
$headerslist[] = $tr;
}
return $headerslist;
}
protected function generateReturnParamsTemplate($st_params)
{
if (!isset($st_params['ApiReturnParams'])) {
return [];
}
$paramslist = [];
foreach ($st_params['ApiReturnParams'] as $params) {
$tr = [
'name' => $params['name'],
'type' => isset($params['type']) ? $params['type'] : 'string',
'sample' => isset($params['sample']) ? $params['sample'] : '',
'description' => isset($params['description']) ? $params['description'] : '',
];
$paramslist[] = $tr;
}
return $paramslist;
}
protected function generateBadgeForMethod($data)
{
$method = strtoupper(is_array($data['ApiMethod'][0]) ? $data['ApiMethod'][0]['data'] : $data['ApiMethod'][0]);
$labes = [
'POST' => 'label-primary',
'GET' => 'label-success',
'PUT' => 'label-warning',
'DELETE' => 'label-danger',
'PATCH' => 'label-default',
'OPTIONS' => 'label-info',
];
return isset($labes[$method]) ? $labes[$method] : $labes['GET'];
}
public function parse()
{
$annotations = $this->extractAnnotations();
$counter = 0;
$section = null;
$docslist = [];
foreach ($annotations as $class => $methods) {
foreach ($methods as $name => $docs) {
if (isset($docs['ApiSector'][0])) {
$section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0];
} else {
$section = $class;
}
if (0 === count($docs)) {
continue;
}
$docslist[$section][] = [
'id' => $counter,
'title' => $docs['ApiTitle'][0],
'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
'method_label' => $this->generateBadgeForMethod($docs),
'section' => $section,
'route' => is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0],
'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
'body' => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
'headerslist' => $this->generateHeadersTemplate($docs),
'paramslist' => $this->generateParamsTemplate($docs),
'returnheaderslist' => $this->generateReturnHeadersTemplate($docs),
'returnparamslist' => $this->generateReturnParamsTemplate($docs),
'return' => isset($docs['ApiReturn']) ? is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0] : '',
];
$counter++;
}
}
return $docslist;
}
public function getView()
{
return $this->view;
}
/**
* 渲染
* @param $template
* @param array $vars
* @return array|string
*/
public function render($template, $vars = [])
{
$docslist = $this->parse();
$ids = [];
foreach ($docslist as $v) {
foreach ($v as $api) {
$ids[] = $api['id'];
}
}
return $docslist;
return View::display(file_get_contents($template), array_merge($vars, ['docslist' => $docslist, 'ids' => json_encode($ids)]));
}
}
@@ -0,0 +1,510 @@
<?php
namespace plugin\apidoc\library\apidoc\library;
use Exception;
/**
* Class imported from https://github.com/eriknyk/Annotations
* @author Erik Amaru Ortiz https://github.com/eriknyk
*
* @license http://opensource.org/licenses/bsd-license.php The BSD License
* @author Calin Rada <rada.calin@gmail.com>
*/
class Extractor
{
/**
* Static array to store already parsed annotations
* @var array
*/
private static $annotationCache;
/**
* Indicates that annotations should has strict behavior, 'false' by default
* @var boolean
*/
private $strict = false;
/**
* Stores the default namespace for Objects instance, usually used on methods like getMethodAnnotationsObjects()
* @var string
*/
public $defaultNamespace = '';
/**
* Sets strict variable to true/false
* @param bool $value boolean value to indicate that annotations to has strict behavior
*/
public function setStrict($value)
{
$this->strict = (bool)$value;
}
/**
* Sets default namespace to use in object instantiation
* @param string $namespace default namespace
*/
public function setDefaultNamespace($namespace)
{
$this->defaultNamespace = $namespace;
}
/**
* Gets default namespace used in object instantiation
* @return string $namespace default namespace
*/
public function getDefaultAnnotationNamespace()
{
return $this->defaultNamespace;
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a given class
*
* @param string $className class name to get annotations
* @return array self::$annotationCache all annotated elements
*/
public static function getClassAnnotations($className)
{
if (!isset(self::$annotationCache[$className])) {
$class = new \ReflectionClass($className);
self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment());
}
return self::$annotationCache[$className];
}
public static function getAllClassAnnotations($className)
{
$class = new \ReflectionClass($className);
foreach ($class->getMethods() as $object) {
self::$annotationCache['annotations'][$className][$object->name] = self::getMethodAnnotations($className, $object->name);
}
return self::$annotationCache['annotations'];
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
*
* @param string $className class name
* @param string $methodName method name to get annotations
* @return array self::$annotationCache all annotated elements of a method given
*/
public static function getMethodAnnotations($className, $methodName)
{
if (!isset(self::$annotationCache[$className . '::' . $methodName])) {
try {
$method = new \ReflectionMethod($className, $methodName);
$class = new \ReflectionClass($className);
if ($className == $method->class) {
if (!$method->isPublic() || $method->isConstructor()) {
$annotations = [];
} else {
$annotations = self::consolidateAnnotations($method, $class);
}
} else {
$annotations = [];
}
} catch (\ReflectionException $e) {
$annotations = [];
}
self::$annotationCache[$className . '::' . $methodName] = $annotations;
}
return self::$annotationCache[$className . '::' . $methodName];
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
* and instance its abcAnnotation class
*
* @param string $className class name
* @param string $methodName method name to get annotations
* @return array self::$annotationCache all annotated objects of a method given
*/
public function getMethodAnnotationsObjects($className, $methodName)
{
$annotations = $this->getMethodAnnotations($className, $methodName);
$objects = [];
$i = 0;
foreach ($annotations as $annotationClass => $listParams) {
$annotationClass = ucfirst($annotationClass);
$class = $this->defaultNamespace . $annotationClass . 'Annotation';
// verify is the annotation class exists, depending if Annotations::strict is true
// if not, just skip the annotation instance creation.
if (!class_exists($class)) {
if ($this->strict) {
throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class));
} else {
// silent skip & continue
continue;
}
}
if (empty($objects[$annotationClass])) {
$objects[$annotationClass] = new $class();
}
foreach ($listParams as $params) {
if (is_array($params)) {
foreach ($params as $key => $value) {
$objects[$annotationClass]->set($key, $value);
}
} else {
$objects[$annotationClass]->set($i++, $params);
}
}
}
return $objects;
}
private static function consolidateAnnotations($method, $class)
{
$dockblockClass = $class->getDocComment();
$docblockMethod = $method->getDocComment();
$methodName = $method->getName();
$methodAnnotations = self::parseAnnotations($docblockMethod);
$classAnnotations = self::parseAnnotations($dockblockClass);
if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') {
return [];
}
$properties = $class->getDefaultProperties();
$noNeedLogin = isset($properties['noNeedLogin']) ? is_array($properties['noNeedLogin']) ? $properties['noNeedLogin'] : [$properties['noNeedLogin']] : [];
$noNeedRight = isset($properties['noNeedRight']) ? is_array($properties['noNeedRight']) ? $properties['noNeedRight'] : [$properties['noNeedRight']] : [];
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblockMethod), $methodArr);
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $dockblockClass), $classArr);
$methodTitle = isset($methodArr[1]) && isset($methodArr[1][0]) ? $methodArr[1][0] : '';
$classTitle = isset($classArr[1]) && isset($classArr[1][0]) ? $classArr[1][0] : '';
if (!isset($methodAnnotations['ApiMethod'])) {
$methodAnnotations['ApiMethod'] = ['get'];
}
if (!isset($methodAnnotations['ApiSummary'])) {
$methodAnnotations['ApiSummary'] = [$methodTitle];
}
if ($methodAnnotations) {
foreach ($classAnnotations as $name => $valueClass) {
if (count($valueClass) !== 1) {
continue;
}
if ($name === 'ApiRoute') {
if (isset($methodAnnotations[$name])) {
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . $methodAnnotations[$name][0]];
} else {
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . '/' . $method->getName()];
}
}
if ($name === 'ApiSector') {
$methodAnnotations[$name] = $valueClass;
}
}
}
if (!isset($methodAnnotations['ApiTitle'])) {
$methodAnnotations['ApiTitle'] = [$methodTitle];
}
if (!isset($methodAnnotations['ApiRoute'])) {
$urlArr = [];
$className = $class->getName();
list($prefix, $suffix) = explode('\\controller\\', $className);
$prefixArr = explode('\\', $prefix);
$suffixArr = explode('\\', $suffix);
if ($prefixArr[0] == 'app') {
$prefixArr[0] = '';
} else if ($prefixArr[0] == 'plugin') {
$tempPrefixArr = [];
$tempPrefixArr[0] = '';
$tempPrefixArr[1] = $prefixArr[2];
$prefixArr = $tempPrefixArr;
}
$urlArr = array_merge($urlArr, $prefixArr);
$urlArr[] = implode('.', array_map(function ($item) {
return self::parseName($item);
}, $suffixArr));
$urlArr[] = $method->getName();
$methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
}
if (!isset($methodAnnotations['ApiSector'])) {
$methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : [$classTitle];
}
if (!isset($methodAnnotations['ApiParams'])) {
$params = self::parseCustomAnnotations($docblockMethod, 'param');
foreach ($params as $k => $v) {
$arr = explode(' ', preg_replace("/[\s]+/", " ", $v));
$methodAnnotations['ApiParams'][] = [
'name' => isset($arr[1]) ? str_replace('$', '', $arr[1]) : '',
'nullable' => false,
'type' => isset($arr[0]) ? $arr[0] : 'string',
'description' => isset($arr[2]) ? $arr[2] : '',
];
}
}
$methodAnnotations['ApiPermissionLogin'] = [!in_array('*', $noNeedLogin) && !in_array($methodName, $noNeedLogin)];
$methodAnnotations['ApiPermissionRight'] = [!in_array('*', $noNeedRight) && !in_array($methodName, $noNeedRight)];
return $methodAnnotations;
}
/**
* 字符串命名风格转换
* type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
* @access public
* @param string $name 字符串
* @param integer $type 转换类型
* @param bool $ucfirst 首字母是否大写(驼峰规则)
* @return string
*/
private static function parseName($name, $type = 0, $ucfirst = true)
{
if ($type) {
$name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
return strtoupper($match[1]);
}, $name);
return $ucfirst ? ucfirst($name) : lcfirst($name);
}
return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
}
/**
* Parse annotations
*
* @param string $docblock
* @param string $name
* @return array parsed annotations params
*/
private static function parseCustomAnnotations($docblock, $name = 'param')
{
$annotations = [];
$docblock = substr($docblock, 3, -2);
if (preg_match_all('/@' . $name . '(?:\s*(?:\(\s*)?(.*?)(?:\s*\))?)??\s*(?:\n|\*\/)/', $docblock, $matches)) {
foreach ($matches[1] as $k => $v) {
$annotations[] = $v;
}
}
return $annotations;
}
/**
* Parse annotations
*
* @param string $docblock
* @return array parsed annotations params
*/
private static function parseAnnotations($docblock)
{
$annotations = [];
// Strip away the docblock header and footer to ease parsing of one line annotations
$docblock = substr($docblock, 3, -2);
if (preg_match_all('/@(?<name>[A-Za-z_-]+)[\s\t]*\((?<args>(?:(?!\)).)*)\)\r?/s', $docblock, $matches)) {
$numMatches = count($matches[0]);
for ($i = 0; $i < $numMatches; ++$i) {
// annotations has arguments
if (isset($matches['args'][$i])) {
$argsParts = trim($matches['args'][$i]);
$name = $matches['name'][$i];
if ($name == 'ApiReturn') {
$value = $argsParts;
} else {
$argsParts = preg_replace("/\{(\w+)\}/", '#$1#', $argsParts);
$value = self::parseArgs($argsParts);
if (is_string($value)) {
$value = preg_replace("/\#(\w+)\#/", '{$1}', $argsParts);
}
}
} else {
$value = [];
}
$annotations[$name][] = $value;
}
}
return $annotations;
}
/**
* Parse individual annotation arguments
*
* @param string $content arguments string
* @return array annotated arguments
*/
private static function parseArgs($content)
{
// Replace initial stars
$content = preg_replace('/^\s*\*/m', '', $content);
$data = [];
$len = strlen($content);
$i = 0;
$var = '';
$val = '';
$level = 1;
$prevDelimiter = '';
$nextDelimiter = '';
$nextToken = '';
$composing = false;
$type = 'plain';
$delimiter = null;
$quoted = false;
$tokens = ['"', '"', '{', '}', ',', '='];
while ($i <= $len) {
$prev_c = substr($content, $i - 1, 1);
$c = substr($content, $i++, 1);
if ($c === '"' && $prev_c !== "\\") {
$delimiter = $c;
//open delimiter
if (!$composing && empty($prevDelimiter) && empty($nextDelimiter)) {
$prevDelimiter = $nextDelimiter = $delimiter;
$val = '';
$composing = true;
$quoted = true;
} else {
// close delimiter
if ($c !== $nextDelimiter) {
throw new Exception(sprintf(
"Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
));
}
// validating syntax
if ($i < $len) {
if (',' !== substr($content, $i, 1) && '\\' !== $prev_c) {
throw new Exception(sprintf(
"Parse Error: missing comma separator near: ...%s<--", substr($content, ($i - 10), $i)
));
}
}
$prevDelimiter = $nextDelimiter = '';
$composing = false;
$delimiter = null;
}
} elseif (!$composing && in_array($c, $tokens)) {
switch ($c) {
case '=':
$prevDelimiter = $nextDelimiter = '';
$level = 2;
$composing = false;
$type = 'assoc';
$quoted = false;
break;
case ',':
$level = 3;
// If composing flag is true yet,
// it means that the string was not enclosed, so it is parsing error.
if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) {
throw new Exception(sprintf(
"Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
));
}
$prevDelimiter = $nextDelimiter = '';
break;
case '{':
$subc = '';
$subComposing = true;
while ($i <= $len) {
$c = substr($content, $i++, 1);
if (isset($delimiter) && $c === $delimiter) {
throw new Exception(sprintf(
"Parse Error: Composite variable is not enclosed correctly."
));
}
if ($c === '}') {
$subComposing = false;
break;
}
$subc .= $c;
}
// if the string is composing yet means that the structure of var. never was enclosed with '}'
if ($subComposing) {
throw new Exception(sprintf(
"Parse Error: Composite variable is not enclosed correctly. near: ...%s'", $subc
));
}
$val = self::parseArgs($subc);
break;
}
} else {
if ($level == 1) {
$var .= $c;
} elseif ($level == 2) {
$val .= $c;
}
}
if ($level === 3 || $i === $len) {
if ($type == 'plain' && $i === $len) {
$data = self::castValue($var);
} else {
$data[trim($var)] = self::castValue($val, !$quoted);
}
$level = 1;
$var = $val = '';
$composing = false;
$quoted = false;
}
}
return $data;
}
/**
* Try determinate the original type variable of a string
*
* @param string $val string containing possibles variables that can be cast to bool or int
* @param boolean $trim indicate if the value passed should be trimmed after to try cast
* @return mixed returns the value converted to original type if was possible
*/
private static function castValue($val, $trim = false)
{
if (is_array($val)) {
foreach ($val as $key => $value) {
$val[$key] = self::castValue($value);
}
} elseif (is_string($val)) {
if ($trim) {
$val = trim($val);
}
$val = stripslashes($val);
$tmp = strtolower($val);
if ($tmp === 'false' || $tmp === 'true') {
$val = $tmp === 'true';
} elseif (is_numeric($val)) {
return $val + 0;
}
unset($tmp);
}
return $val;
}
}
@@ -0,0 +1,744 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Cache" content="no-cache">
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Api文档</title>
<!-- 依 赖 样 式 -->
<link rel="stylesheet" href="/static/component/laytp/css/laytp.css" />
<!-- 加 载 样 式-->
<link rel="stylesheet" href="/static/admin/css/load.css" />
<!-- 布 局 样 式 -->
<link rel="stylesheet" href="/static/admin/css/apidoc.css" />
</head>
<style>
.api-page-tab-content {
background-color: #fff;
overflow: auto;
min-height: 100%;
}
.api-url {
/*position: relative;*/
color: rgb(221, 17, 68);
background: #f6f6f6;
border: 1px solid #ddd;
}
.layui-form-pane .layui-form-label {
width: 280px;
/*height: 34px;*/
padding: 6px 15px;
}
.layui-form-pane .layui-input-block {
margin-left: 280px;
}
.editormd-preview-container, .editormd-html-preview {
width: 98% !important;
font-size: 16px;
}
.markdown-body li {
/* 使md编辑器中的li取消样式none */
list-style: unset;
}
.api-tab-title {
position: fixed;
left: 230px;
right: 0;
z-index: 999999;
background-color: #fff;
}
.layui-tab-bar{
display:none;
}
</style>
<!-- 结 构 代 码 -->
<body class="layui-layout-body laytp-admin">
<!-- 布 局 框 架 -->
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<!-- 顶 部 左 侧 功 能 -->
<ul class="layui-nav layui-layout-left">
<li class="collaspe layui-nav-item"><a href="javascript:void(0);" class="layui-icon layui-icon-shrink-right"></a></li>
</ul>
<!-- 顶 部 右 侧 菜 单 -->
<div id="control" class="layui-layout-control"></div>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide-xs"><a href="javascript:void(0);" class="fullScreen layui-icon layui-icon-screen-full"></a></li>
<!-- 主 题 配 置 -->
<li class="layui-nav-item apiSet"><a href="javascript:void(0);" class="layui-icon layui-icon-set"></a></li>
<li class="layui-nav-item setting"><a href="javascript:void(0);" class="layui-icon layui-icon-more-vertical"></a></li>
</ul>
</div>
<!-- 侧 边 区 域 -->
<div class="layui-side layui-bg-black">
<!-- 菜 单 顶 部 -->
<div class="layui-logo">
<!-- 图 标 -->
<img class="logo" />
<!-- 标 题 -->
<span class="title"></span>
</div>
<!-- 菜 单 内 容 -->
<div>
<ul class="layui-nav arrow layui-nav-tree laytp-nav-tree" style="display: block;">
<li class="layui-nav-item">
<div class="search" style="padding: 5px;">
<input type="text" class="layui-input search-menu" placeholder="搜索文档">
</div>
</li>
</ul>
</div>
<div class="layui-side-scroll">
<div id="sideMenu"></div>
</div>
</div>
<!-- 视 图 页 面 -->
<div class="layui-body">
{php}$key = 0;{/php}
{foreach $apidocList as $k=>$v}
{php}$key++;{/php}
<div class="api-page-tab-content" id="meditor_{$key}" style="display:none;">
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title api-tab-title">
<li class="layui-this">
{$v.title}
</li>
</ul>
<div class="layui-tab-content meditorContent api-content" id="meditor_{$v.id}_content">
<textarea style="display:none;">{$v.des}</textarea>
</div>
</div>
</div>
{/foreach}
{foreach $docslist as $k=>$v}
{php}$key++;{/php}
{foreach $v as $ak=>$api}
{php}$key++;{/php}
<div class="api-page-tab-content" id="api_{$key}" style="display:none;">
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title api-tab-title">
<li class="layui-this">接口说明</li>
<li>在线测试</li>
</ul>
<div class="layui-tab-content markdown-body api-content">
<div class="layui-tab-item layui-show editormd-html-preview">
<p><strong>简要说明</strong></p>
<ul>
<li>{$api.summary}</li>
</ul>
<p><strong>请求方式</strong></p>
<ul>
<li><code class="api-url">{$api.method}</code></li>
</ul>
<p><strong>请求地址</strong></p>
<ul>
<li><code class="api-url" route="{if $apiDomain && substr($api['route'],0,5) === '/api.'}/{:substr($api['route'],5)}{else /}{$api.route}{/if}">{if $apiDomain && substr($api['route'],0,5) === '/api.'}/{:substr($api['route'],5)}{else /}{$api.route}{/if}</code></li>
</ul>
<p><strong>Headers参数</strong></p>
{if $api.headerslist}
<div style="width: 100%;overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th style="text-align: left;">名称</th>
<th style="text-align: left;">类型</th>
<th style="text-align: left;">必选</th>
<th style="text-align: left;">描述</th>
</tr>
</thead>
<tbody>
{foreach $api.headerslist as $hk=>$hv}
<tr>
<td style="text-align:left">{$hv.name}</td>
<td style="text-align:left">{$hv.type}</td>
<td style="text-align:left">{if $hv.required}是{else}否{/if}</td>
<td>{$hv.description}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{else}
<ul>
<li></li>
</ul>
{/if}
<p><strong>Body参数</strong></p>
{if $api.paramslist}
<div style="width: 100%;overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th style="text-align: left;">名称</th>
<th style="text-align: left;">类型</th>
<th style="text-align: left;">必选</th>
<th style="text-align: left;">描述</th>
</tr>
</thead>
<tbody>
{foreach $api.paramslist as $pk=>$pv}
<tr>
<td style="text-align:left">{$pv.name}</td>
<td style="text-align:left">{$pv.type}</td>
<td style="text-align:left">{if $pv.required}是{else}否{/if}</td>
<td>{if $pv.sample}{$pv.description} -
例:{$pv.sample}{else}{$pv.description}{/if}
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{else}
<ul>
<li></li>
</ul>
{/if}
<p><strong>返回示例</strong></p>
{if $api.return}
<pre><code class="json">{$api.return}</code></pre>
{else}
<ul>
<li></li>
</ul>
{/if}
<p><strong>返回说明</strong></p>
{if $api.returnparamslist}
<div style="width: 100%;overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th style="text-align: left;">名称</th>
<th style="text-align: left;">类型</th>
<th style="text-align: left;">描述</th>
</tr>
</thead>
<tbody>
{foreach $api.returnparamslist as $pk=>$pv}
<tr>
<td style="text-align:left">{$pv.name}</td>
<td style="text-align:left">{$pv.type}</td>
<td style="text-align:left">{$pv.description}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{else}
<ul>
<li></li>
</ul>
{/if}
</div>
<div class="layui-tab-item editormd-html-preview">
<div class="layui-card">
<div class="layui-card-header" style="background-color: #F2F2F2;">Body传参</div>
<div class="layui-card-body main-container">
<form class="layui-form layui-form-pane" method="{$api.method}"
route="{if $apiDomain && substr($api['route'],0,5) === '/api.'}/{:substr($api['route'],5)}{else /}{$api.route}{/if}"
lay-filter="test_inline_{$key}" id="test_inline_{$key}">
{foreach $api.paramslist as $pk=>$pv}
<div class="layui-form-item">
<label class="layui-form-label">{$pv.name}</label>
<div class="layui-input-block">
{if($pv.type == 'array')}
<table class="layui-table">
<tbody>
<tr>
<td align="right" style="width:400px">
<input type="text" class="layui-input" name="{$pv.name}[]" autocomplete="off" placeholder="{if $pv.sample}{$pv.description} - 例:{$pv.sample}{else}请输入{$pv.description}{/if}" />
</td>
<td>
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-array-param"></a>
</td>
</tr>
<tr>
<td colspan="4">
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-add-1 add-array-param" data-field="{$pv.name}">追加参数</a>
</td>
</tr>
</tbody>
</table>
{else}
<input type="{if ($pv.type == 'file')}file{else}text{/if}"
name="{$pv.name}" autocomplete="off"
placeholder="{if $pv.sample}{$pv.description} - 例:{$pv.sample}{else}请输入{$pv.description}{/if}"
class="layui-input">
{/if}
</div>
</div>
{/foreach}
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="laytp-btn laytp-btn-primary"
href="javascript:void(0);"
onclick="api.testInline({$key})">提 交
</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-card" id="response_card_{$key}">
<div class="layui-card-header" style="background-color: #F2F2F2;">响应输出</div>
<div class="layui-card-body">
<pre><code class="json" id="response_{$key}"></code></pre>
<pre class="layui-code" id="response_headers_{$key}"></pre>
</div>
</div>
</div>
</div>
</div>
</div>
{/foreach}
{/foreach}
</div>
<!-- 遮 盖 层 -->
<div class="laytp-cover"></div>
<!-- 加 载 动 画-->
<div class="loader-main">
<div class="loader"></div>
</div>
</div>
<!-- 移 动 端 便 捷 操 作 -->
<div class="laytp-collasped-pe collaspe">
<a href="javascript:void(0);" class="layui-icon layui-icon-shrink-right"></a>
</div>
<!-- 请求配置弹出层内容开始 -->
<script type="text/html" id="set">
<div class="popup-content">
<div class="api-page-tab-content">
<form class="layui-form" lay-filter="setDataForm" id="setDataForm">
<div class="layui-form-item">
<label class="layui-form-label">Api请求域名</label>
<div class="layui-input-block">
<input type="text" name="apiUrl"
placeholder="完整的请求前缀,以http://或者https://开头,不以/结尾" autocomplete="off" class="layui-input"
value="{{d.apiUrl}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" title="开关">签名验证</label>
<div class="layui-input-block">
<!-- 隐藏域设置开关未选中时需要传递的参数值 -->
<input type="hidden" name="createSign" id="createSign-2" value="2"/>
<input type="checkbox" name="createSign" id="createSign-1" lay-skin="switch" lay-text="验证|不验证"
{{# if(d.createSign== 1){ }}checked="checked" {{# } }} value="1"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" title="Header参数">Header参数</label>
<div class="layui-input-block">
<table class="layui-table">
<thead>
<tr>
<th>参数Key</th>
<th>参数Value</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">
<input type="text" class="layui-input" value="token" readonly="readonly"/>
</td>
<td>
<input type="text" class="layui-input" name="token" value="{{d.headerJson.token}}"
autocomplete="off"/>
</td>
<td></td>
</tr>
{{# let k; }}
{{# for(k in d.headerJson){ }}
{{# if(k !== "token" && k !== "sign" && k !== "request-time"){ }}
<tr>
<td align="right">
<input type="text" class="layui-input" name="header[key][]" value="{{k}}"
autocomplete="off"/>
</td>
<td>
<input type="text" class="layui-input" name="header[value][]"
value="{{d.headerJson[k]}}" autocomplete="off"/>
</td>
<td>
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-header-param"></a>
</td>
</tr>
{{# } }}
{{# } }}
<tr>
<td colspan="4">
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-add-1 add-header-param">追加参数</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</form>
</div>
</div>
</script>
<!-- 请求配置弹出层内容结束 -->
<script type="text/html" id="arrayParam">
<table class="layui-table">
<tbody>
<tr>
<td align="right">
<input type="text" class="layui-input" name="{{d.field}}[]" autocomplete="off"/>
</td>
<td>
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-array-param"></a>
</td>
</tr>
<tr>
<td colspan="4">
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-add-1 add-array-param">追加参数</a>
</td>
</tr>
</tbody>
</table>
</script>
<!-- 依 赖 脚 本 -->
<script type="text/javascript" src="/static/component/jquery_3.3.1.js" charset="utf-8"></script>
<script type="text/javascript" src="/static/component/jquery.serializejson.min.js" charset="utf-8"></script>
<script type="text/javascript" src="/static/component/highlight/highlight.pack.js" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="/static/component/highlight/styles/docco.css"/>
<link rel="stylesheet" href="/static/plugin/meditor/css/editormd.css"/>
<script src="/static/plugin/meditor/lib/marked.min.js"></script>
<script src="/static/plugin/meditor/lib/prettify.min.js"></script>
<script src="/static/plugin/meditor/lib/flowchart.min.js"></script>
<script src="/static/plugin/meditor/lib/raphael.min.js"></script>
<script src="/static/plugin/meditor/lib/underscore.min.js"></script>
<script src="/static/plugin/meditor/lib/sequence-diagram.min.js"></script>
<script src="/static/plugin/meditor/lib/jquery.flowchart.min.js"></script>
<script src="/static/plugin/meditor/editormd.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.18.0/js/md5.js"></script>
<script>
// 初始化静态文件版本号
localStorage.setItem("version","1.0.2.Release");
document.write("<script src='/static/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='/static/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
</script>
<!-- 框 架 初 始 化 -->
<script>
//定义storage
let storage = (function () {
let storage;
try {
storage = window.localStorage;
return storage;
} catch (exception) {
return false;
}
}());
layui.use(['laytp','apidoc','popup'], function() {
let createSignKey = "{$createSignKey}";
let apiDomain = "{$apiDomain}";
let apiUrl = (apiDomain.length > 0) ? apiDomain : window.location.protocol + "//" + window.location.host;
//请求配置
let nowSetJson = storage.getItem("setJson") ? JSON.parse(storage.getItem("setJson")) : {
apiUrl: apiUrl,
createSign: 2,
headerJson: {token: ""}
};
//渲染所有的meditor编辑器内容
layui.each($(".meditorContent"), function (key, item) {
editormd.markdownToHTML($(item).attr('id'), {
htmlDecode: "style,script,iframe", // you can filter tags decode
emoji: true,
taskList: true,
tex: true, // 默认不解析
flowChart: true, // 默认不解析
sequenceDiagram: true, // 默认不解析
});
});
//当表格列数过长时将自动出现滚动条
var themeColorContext = layui.context.get('theme-color-context');
$.each($('table'), function (i, item) {
if($(this).attr('class') !== 'layui-table'){
$('thead > tr', this).attr('style', 'background-color: '+themeColorContext+';color:white;');
$(this).prop('outerHTML', '<div style="width: 95%;overflow-x: auto;">' + $(this).prop('outerHTML') + '</div>');
}
});
//弹窗展示请求配置
$(document).off("click", ".apiSet").on("click", ".apiSet", function () {
let layerDiv = layui.layer.open({
type: 1,
offset: 'r',
area: ['600px', '100%'],
title: "请求配置",
content: layui.laytpl($("#set").html()).render(nowSetJson),
shade: 0.1,
closeBtn: 0,
shadeClose: false,
anim: -1,
skin: 'layer-anim-right',
move: false,
success: function (layero, index) {
$(document).off("click", '#layui-layer-shade' + index).on("click", '#layui-layer-shade' + index, function () {
var $layero = $('#layui-layer' + index);
$layero.animate({
left: $layero.offset().left + $layero.width()
}, 200, function() {
let formJson = $('#setDataForm').serializeJSON();
let headerJson = {
token: formJson.token
};
let keyJson = {};
if (formJson.hasOwnProperty("header") && formJson.header.hasOwnProperty("key")) {
keyJson = formJson.header.key;
}
let valueJson = {};
if (formJson.hasOwnProperty("header") && formJson.header.hasOwnProperty("value")) {
valueJson = formJson.header.value;
}
$.each(keyJson, function (key, item) {
if(keyJson[key]){
headerJson[keyJson[key]] = valueJson[key];
}
});
let setJson = {
apiUrl: formJson.apiUrl,
createSign: formJson.createSign,
headerJson: headerJson
};
nowSetJson = setJson;
storage.setItem("setJson", JSON.stringify(setJson));
facade.success("保存成功");
layer.close(index);
});
});
},
btn: ['保 存', '取 消'],
yes: function (index, layero) {
let formJson = $('#setDataForm').serializeJSON();
let headerJson = {
token: formJson.token
};
let keyJson = {};
if (formJson.hasOwnProperty("header") && formJson.header.hasOwnProperty("key")) {
keyJson = formJson.header.key;
}
let valueJson = {};
if (formJson.hasOwnProperty("header") && formJson.header.hasOwnProperty("value")) {
valueJson = formJson.header.value;
}
$.each(keyJson, function (key, item) {
if(keyJson[key]){
headerJson[keyJson[key]] = valueJson[key];
}
});
let setJson = {
apiUrl: formJson.apiUrl,
createSign: formJson.createSign,
headerJson: headerJson
};
nowSetJson = setJson;
storage.setItem("setJson", JSON.stringify(setJson));
facade.success("保存成功");
layer.close(index);
}
});
layui.laytpForm.render("#layui-layer" + layerDiv);
});
//追加Header参数
$(document).off("click", ".add-header-param").on("click", ".add-header-param", function () {
let clickObj = $(this);
let template = '<tr>' +
'<td align="right">' +
'<input type="text" class="layui-input" name="header[key][]" autocomplete="off" />' +
'</td>' +
'<td>' +
'<input type="text" class="layui-input" name="header[value][]" autocomplete="off" />' +
'</td>' +
'<td>' +
'<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-header-param"></a>' +
'</td>' +
'</tr>';
clickObj.parent().parent().before(template);
});
//追加数组参数
$(document).off("click", ".add-array-param").on("click", ".add-array-param", function () {
let clickObj = $(this);
let field = clickObj.data('field');
let template = '<tr>' +
'<td align="right">' +
'<input type="text" class="layui-input" name="' + field + '[]" autocomplete="off" />' +
'</td>' +
'<td>' +
'<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-array-param"></a>' +
'</td>' +
'</tr>';
clickObj.parent().parent().before(template);
});
//删除header参数
$(document).off("click", ".del-header-param").on("click", ".del-header-param", function () {
let clickObj = $(this);
clickObj.parent().parent().remove();
});
//删除array参数
$(document).off("click", ".del-array-param").on("click", ".del-array-param", function () {
let clickObj = $(this);
clickObj.parent().parent().remove();
});
layui.apidoc.setConfigType("yml");
layui.apidoc.setConfigPath("/static/component/laytp/config/api.config.yml?v=" + localStorage.getItem("version"));
layui.apidoc.render();
// 搜索菜单功能添加在这里,不要添加到js组件里面,因为会在子页面进行调用,这里只会在父页面执行
// 这里被注释的代码,触发搜索菜单要按下回车键
$(".search-menu").focus(
function(){
$(document).on("keydown",
function(event){
if( event.keyCode === 13 ){
window.searchMenuData = [];
var searchKey = $(".search-menu").val();
if(searchKey){
searchMenu(window.menuData, searchKey);
layui.apidoc.setConfigType("yml");
layui.apidoc.setConfigPath("/static/component/laytp/config/api.config.yml?v=" + localStorage.getItem("version"));
var param = layui.apidoc.readConfig();
param.menu.async = false;
param.menu.data = window.searchMenuData;
param.isSearch = true;
layui.apidoc.render(param);
}else{
$("#sideMenu").show();
$("#searchSideMenu").hide();
renderMenu();
}
}
}
);
$(".search-menu").unbind();
}
);
// 递归搜索菜单
window.searchMenu = function(menuData, searchKey){
searchKey = searchKey.toLowerCase();
$.each(menuData, function(i, item) {
var oldTitle = item.title;
item.title = item.title.toLowerCase();
if(item.title.indexOf(searchKey) > -1){
item.title = oldTitle;
window.searchMenuData.push(item);
}else{
item.title = oldTitle;
if(item.children && item.children.length > 0){
searchMenu(item.children, searchKey);
}
}
});
};
// 重新渲染菜单
window.renderMenu = function(){
$(".search-menu").val('');
layui.apidoc.setConfigType("yml");
layui.apidoc.setConfigPath("/static/component/laytp/config/api.config.yml?v=" + localStorage.getItem("version"));
var config = layui.apidoc.readConfig();
layui.apidoc.menuRender(config);
};
window.api = {
testInline: function (id) {
let route = $("#test_inline_" + id).attr('route');
let url = nowSetJson.apiUrl + route;
let method = $("#test_inline_" + id).attr('method');
if (nowSetJson.createSign === "1") {
nowSetJson.headerJson['request-time'] = Date.now();
nowSetJson.headerJson.sign = api.createSign(nowSetJson.headerJson['request-time']);
}
let formData = new FormData();
$('#test_inline_'+id).find('input').each(function (i, input) {
if ($(input).attr('type') == 'file') {
formData.append($(input).attr('name'), $(input)[0].files[0]);
} else {
formData.append($(input).attr('name'), $(input).val())
}
});
$.ajax({
url: url,
headers: nowSetJson.headerJson,
data: $('#test_inline_'+id).prop('method').toLowerCase() == 'get' ? $('#test_inline_'+id).serialize() : formData,
type: method,
dataType: 'json',
contentType: false,
processData: false,
success: function (data, textStatus, xhr) {
if (typeof data === 'object') {
var str = JSON.stringify(data, null, 2);
if(str) {
$('#response_' + id).html(hljs.highlightAuto(str).value);
}
} else {
$('#response_' + id).html(data || '');
}
$('#response_headers_' + id).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
$('#response_card_' + id).show();
facade.success('请求成功');
},
error: function (xhr) {
try {
var str = JSON.stringify($.parseJSON(xhr.responseText), null, 2);
} catch (e) {
var str = xhr.responseText;
}
$('#response_headers_' + id).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
if(str){
$('#response_' + id).html(hljs.highlightAuto(str).value);
}
$('#response_card_' + id).show();
facade.error('请求失败');
}
});
return false;
},
createSign: function (requestTime) {
return md5(md5(requestTime) + md5(createSignKey)).toUpperCase();
},
};
});
</script>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
<?php
return[[
'name'=>'生成Api文档',
'href'=>'/admin/plugin/apidoc/index.html',
'is_menu'=>1,
'icon'=>'layui-icon layui-icon-read',
'children'=>[
['name'=>'查看和搜索列表', 'rule'=>'/plugin/apidoc/index/index', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
['name'=>'查看单条数据详情', 'rule'=>'/plugin/apidoc/index/info', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
['name'=>'添加', 'rule'=>'/plugin/apidoc/index/add', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
['name'=>'编辑', 'rule'=>'/plugin/apidoc/index/edit', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
['name'=>'生成文档', 'rule'=>'/plugin/apidoc/index/create', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
['name'=>'删除', 'rule'=>'/plugin/apidoc/index/del', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
['name'=>'回收站', 'rule'=>'/plugin/apidoc/index/recycle', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
['name'=>'还原', 'rule'=>'/plugin/apidoc/index/restore', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
['name'=>'真实删除', 'rule'=>'/plugin/apidoc/index/trueDel', 'is_menu'=>2, 'icon'=>'layui-icon layui-icon-fire'],
]
]];
+15
View File
@@ -0,0 +1,15 @@
<?php
/**
* 后台系统配置模型
*/
namespace plugin\apidoc\model;
use laytp\BaseModel;
use think\model\concern\SoftDelete;
class Apidoc extends BaseModel
{
use SoftDelete;
protected $name = 'plugin_apidoc';
}
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加Api文档</title>
</head>
<body>
<form class="layui-form" lay-filter="layui-form">
<div class="mainBox">
<div class="main-container">
<div class="main-container">
<div class="layui-form-item">
<label class="layui-form-label" title="标题">标题</label>
<div class="layui-input-block">
<input autocomplete="off" type="text" name="title" id="title" placeholder="请输入标题" class="layui-input"
lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" title="描述">描述</label>
<div class="layui-input-block">
<iframe src="/admin/meditor.html" class="editor" data-type="meditor" data-id="des"
style="width:100%;height:560px;border: 0"></iframe>
</div>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="button" class="laytp-btn laytp-btn-primary laytp-btn-sm" lay-submit="" lay-filter="add">
<i class="layui-icon layui-icon-ok"></i>
提交
</button>
<button type="reset" class="laytp-btn laytp-btn-sm">
<i class="layui-icon layui-icon-refresh"></i>
重置
</button>
</div>
</div>
</form>
<script>
if(localStorage.getItem("staticDomain")){
document.write("<link rel='stylesheet' href='" + localStorage.getItem("staticDomain") + "/component/laytp/css/laytp.css?v=" + localStorage.getItem("version") + "'>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
}else{
document.write("<link rel='stylesheet' href='/static/component/laytp/css/laytp.css?v=" + localStorage.getItem("version") + "'>");
document.write("<script src='/static/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='/static/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
}
</script>
<script>
layui.use(['laytp'],function(){
layui.form.on('submit(add)', function(data){
data = facade.setEditorField(data);
facade.ajax({
route:'/plugin/apidoc/index/add',
data : data.field
}).done(function(res){
if(res.code === 0){
parent.layui.layer.close(parent.layui.layer.getFrameIndex(window.name));//关闭当前页
parent.layui.table.reload("laytp-table");
}
});
return false;
});
})
</script>
</body>
</html>
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>编辑Api文档</title>
</head>
<body>
<script type="text/html" id="form">
<input type="hidden" name="id" value="{{ d.id }}" />
<div class="mainBox">
<div class="main-container">
<div class="main-container">
<div class="layui-form-item">
<label class="layui-form-label" title="标题">标题</label>
<div class="layui-input-block">
<input autocomplete="off" type="text" name="title" id="title" placeholder="请输入标题" class="layui-input"
lay-verify="required" value="{{=d.title}}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" title="描述">描述</label>
<div class="layui-input-block">
<iframe src="/admin/meditor.html?id=des" class="editor" data-type="meditor" data-id="des"
style="width:100%;height:560px;border: 0"></iframe>
<textarea class="editorContent" data-id="des" style="display:none;">{{=d.des}}</textarea>
</div>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="button" class="laytp-btn laytp-btn-primary laytp-btn-sm" lay-submit="" lay-filter="edit">
<i class="layui-icon layui-icon-ok"></i>
提交
</button>
<button type="reset" class="laytp-btn laytp-btn-sm">
<i class="layui-icon layui-icon-refresh"></i>
重置
</button>
</div>
</div>
</script>
<form class="layui-form" lay-filter="layui-form"></form>
<script>
if(localStorage.getItem("staticDomain")){
document.write("<link rel='stylesheet' href='" + localStorage.getItem("staticDomain") + "/component/laytp/css/laytp.css?v=" + localStorage.getItem("version") + "'>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
}else{
document.write("<link rel='stylesheet' href='/static/component/laytp/css/laytp.css?v=" + localStorage.getItem("version") + "'>");
document.write("<script src='/static/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='/static/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
}
</script>
<script>
layui.use(['laytp'],function(){
let form = layui.form;
let $ = layui.jquery;
//获取参数ID
var id = facade.getUrlParam('id');
if(!id){
facade.error('参数ID不能为空');
return false;
}
//获取数据,渲染到对应的节点上
facade.ajax({
route: "/plugin/apidoc/index/info",
data: {id: id},
successAlert: false,
showLoading: true
}).done(function(res){
if(res.code === 0){
layui.laytpl($("#form").html()).render(res.data,function(string){
$("form").html(string);
layui.laytpForm.render();
form.render();
});
}
});
form.on('submit(edit)', function(data){
data = facade.setEditorField(data);
facade.ajax({
route:'/plugin/apidoc/index/edit',
data:data.field
}).done(function(res){
if(res.code === 0){
parent.layui.layer.close(parent.layui.layer.getFrameIndex(window.name));//关闭当前页
parent.layui.table.reload("laytp-table");
}
});
return false;
});
})
</script>
</body>
</html>
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>用户管理</title>
</head>
<body class="laytp-container">
<div id="search-form" style="display:none;">
<div class="layui-layer-title">搜索</div>
<form class="layui-form search-form-body" lay-filter="layui-form">
<div class="layui-form-item layui-inline">
<label class="layui-form-label" title="ID">ID</label>
<div class="layui-input-inline">
<input autocomplete="off" type="text" id="id" name="search_param[id][value]" id="id"
placeholder="请输入ID" class="layui-input">
<input type="hidden" name="search_param[id][condition]" value="=">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label" title="标题">标题</label>
<div class="layui-input-inline">
<input autocomplete="off" type="text" id="title" name="search_param[title][value]"
placeholder="请输入标题" class="layui-input">
<input type="hidden" name="search_param[title][condition]" value="LIKE">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label" title="描述">描述</label>
<div class="layui-input-inline">
<input autocomplete="off" type="text" id="des" name="search_param[des][value]" placeholder="请输入描述"
class="layui-input">
<input type="hidden" name="search_param[des][condition]" value="LIKE">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label" title="创建时间">创建时间</label>
<div class="layui-input-inline">
<input type="text" class="layui-input laydate"
id="create_time" name="search_param[create_time][value]"
data-type="datetime" data-isRange="true" placeholder="请选择创建时间">
<input type="hidden" name="search_param[create_time][condition]" value="BETWEEN">
</div>
</div>
<div class="bottom">
<div class="button-container">
<button class="laytp-btn laytp-btn-primary laytp-btn-sm" lay-submit="" lay-filter="laytp-recycle-search-form">
<i class="layui-icon layui-icon-ok"></i>
提交
</button>
<button type="button" class="laytp-btn laytp-btn-sm laytp-recycle-search-form-reset">
<i class="layui-icon layui-icon-refresh"></i>
重置
</button>
</div>
</div>
</form>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="laytp-table" lay-filter="laytp-table"></table>
</div>
</div>
<script type="text/html" id="default-toolbar">
<div class="dropdown-menu">
<button class="laytp-btn laytp-btn-primary laytp-btn-md">
<i class="layui-icon layui-icon-triangle-d"></i>
批量操作
</button>
<ul class="dropdown-menu-nav layui-anim-upbit dropdown-bottom-left layui-anim">
<div class="dropdown-anchor"></div>
{{# if(facade.hasAuth("/plugin/apidoc/index/edit")){ }}
<li><a lay-event="edit"><i class="layui-icon layui-icon-edit"></i></a></li>
{{# } }}
{{# if(facade.hasAuth("/plugin/apidoc/index/del")){ }}
<li><a lay-event="del"><i class="layui-icon layui-icon-delete"></i></a></li>
{{# } }}
</ul>
</div>
{{# if(facade.hasAuth("/plugin/apidoc/index/add")){ }}
<button class="laytp-btn laytp-btn-danger laytp-btn-md" lay-event="add">
<i class="layui-icon layui-icon-add-1"></i>
新增
</button>
{{# } }}
<button class="laytp-btn laytp-btn-warming laytp-btn-md" lay-event="search">
<i class="layui-icon layui-icon-search"></i>
搜索
</button>
{{# if(facade.hasAuth("/plugin/apidoc/index/recycle")){ }}
<button class="laytp-btn laytp-btn-success laytp-btn-md" lay-event="recycle">
<i class="layui-icon layui-icon-delete"></i>
回收站
</button>
{{# } }}
{{# if(facade.hasAuth("/plugin/apidoc/index/create")){ }}
<button class="laytp-btn laytp-btn-default laytp-btn-md"lay-event="create">
<i class="layui-icon layui-icon-tabs"></i>
生成Api文档
</button>
{{# } }}
{{# if(facade.hasAuth("/plugin/apidoc/index/create")){ }}
<button class="laytp-btn laytp-btn-primary laytp-btn-md fa fa-binoculars"
lay-event="open">&nbsp;查看Api文档
</button>
{{# } }}
</script>
<script type="text/html" id="default-bar">
{{# if(facade.hasAuth("/plugin/apidoc/index/edit")){ }}
<button class="laytp-btn laytp-btn-primary laytp-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i></button>
{{# } }}
{{# if(facade.hasAuth("/plugin/apidoc/index/del")){ }}
<button class="laytp-btn laytp-btn-danger laytp-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i></button>
{{# } }}
</script>
<script>
if(localStorage.getItem("staticDomain")){
document.write("<link rel='stylesheet' href='" + localStorage.getItem("staticDomain") + "/component/laytp/css/laytp.css?v=" + localStorage.getItem("version") + "'>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/plugin/apidoc/js/index.js?v="+localStorage.getItem("version")+"'><\/script>");
}else{
document.write("<link rel='stylesheet' href='/static/component/laytp/css/laytp.css?v=" + localStorage.getItem("version") + "'>");
document.write("<script src='/static/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='/static/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='/static/plugin/apidoc/js/index.js?v="+localStorage.getItem("version")+"'><\/script>");
}
</script>
</body>
</html>
@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Api文档回收站</title>
</head>
<body class="laytp-container-recycle">
<div id="recycle-search-form" style="display:none;">
<div class="layui-layer-title">搜索</div>
<form class="layui-form search-form-body" lay-filter="layui-form">
<div class="layui-form-item layui-inline">
<label class="layui-form-label" title="ID">ID</label>
<div class="layui-input-inline">
<input autocomplete="off" type="text" id="id" name="search_param[id][value]" id="id"
placeholder="请输入ID" class="layui-input">
<input type="hidden" name="search_param[id][condition]" value="=">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label" title="标题">标题</label>
<div class="layui-input-inline">
<input autocomplete="off" type="text" id="title" name="search_param[title][value]"
placeholder="请输入标题" class="layui-input">
<input type="hidden" name="search_param[title][condition]" value="LIKE">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label" title="描述">描述</label>
<div class="layui-input-inline">
<input autocomplete="off" type="text" id="des" name="search_param[des][value]" placeholder="请输入描述"
class="layui-input">
<input type="hidden" name="search_param[des][condition]" value="LIKE">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label" title="创建时间">创建时间</label>
<div class="layui-input-inline">
<input type="text" class="layui-input laydate"
id="create_time" name="search_param[create_time][value]"
data-type="datetime" data-isRange="true" placeholder="请选择创建时间">
<input type="hidden" name="search_param[create_time][condition]" value="BETWEEN">
</div>
</div>
<div class="bottom">
<div class="button-container">
<button class="laytp-btn laytp-btn-primary laytp-btn-sm" lay-submit="" lay-filter="laytp-recycle-search-form">
<i class="layui-icon layui-icon-ok"></i>
提交
</button>
<button type="button" class="laytp-btn laytp-btn-sm laytp-recycle-search-form-reset">
<i class="layui-icon layui-icon-refresh"></i>
重置
</button>
</div>
</div>
</form>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="laytp-recycle-table" lay-filter="laytp-recycle-table"></table>
</div>
</div>
<script type="text/html" id="recycle-default-toolbar">
<button class="laytp-btn laytp-btn-primary laytp-btn-md" lay-event="restore">
<i class="laytp-icon laytp-icon-back"></i>
还原
</button>
<button class="laytp-btn laytp-btn-danger laytp-btn-md" lay-event="true-del">
<i class="layui-icon layui-icon-delete"></i>
删除
</button>
<button class="laytp-btn laytp-btn-warming laytp-btn-md" lay-event="recycle-search">
<i class="layui-icon layui-icon-search"></i>
搜索
</button>
</script>
<script type="text/html" id="recycle-default-bar">
<button class="laytp-btn laytp-btn-primary laytp-btn-xs" lay-event="restore"><i class="laytp-icon laytp-icon-back"></i></button>
<button class="laytp-btn laytp-btn-danger laytp-btn-xs" lay-event="true-del"><i class="layui-icon layui-icon-delete"></i></button>
</script>
<script>
if(localStorage.getItem("staticDomain")){
document.write("<link rel='stylesheet' href='" + localStorage.getItem("staticDomain") + "/component/laytp/css/laytp.css?v=" + localStorage.getItem("version") + "'>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='" + localStorage.getItem("staticDomain") + "/plugin/apidoc/js/recycle.js?v="+localStorage.getItem("version")+"'><\/script>");
}else{
document.write("<link rel='stylesheet' href='/static/component/laytp/css/laytp.css?v=" + localStorage.getItem("version") + "'>");
document.write("<script src='/static/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='/static/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
document.write("<script src='/static/plugin/apidoc/js/recycle.js?v="+localStorage.getItem("version")+"'><\/script>");
}
</script>
</body>
</html>
@@ -0,0 +1,79 @@
---
## 网站配置
logo:
## 网站名称
title: "Api文档"
## 网站图标
image: "/static/admin/images/logo.png"
## 菜单配置
menu:
## 菜单数据来源
data: "/plugin/apidoc/index/getMenu"
## 菜单接口的请求方式 GET / POST
method: "GET"
## 是否同时只打开一个菜单目录
accordion: true
## 是否开启多系统菜单模式
control: true
## 当control为true时,顶部最多显示多少菜单,其他顶部菜单需要点击最后的顶部下拉菜单进行展示
maxTopMenuNum: 8
## 默认选中的菜单项
select: "1"
## 是否开启异步菜单,false 时 data 属性设置为菜单数据,true 时为 json 文件或后端接口
async: true
## 视图内容配置
tab:
## 是否开启多选项卡
muiltTab: true
## 保持视图状态
keepState: false
## 开启选项卡记忆
session: true
## 最大可打开的选项卡数量
tabMax: "30"
## 首页
index:
id: "2" ## 标识 ID , 建议与菜单项中的 ID 一致
href: "/admin/console.html" ## 页面地址
title: "控制面板" ## 标题
## 主题配置
theme:
## 默认主题色,对应 colors 配置中的 ID 标识
defaultColor: "2"
## 默认的菜单主题 dark-theme 黑 / light-theme 白
defaultMenu: "dark-theme"
## 是否允许用户切换主题,false 时关闭自定义主题面板
allowCustom: true
## 主题色配置列表
colors:
- id: "1"
color: "#2d8cf0"
- id: "2"
color: "#36b368"
- id: "3"
color: "#f6ad55"
- id: "4"
color: "#f56c6c"
- id: "5"
color: "#3963bc"
## 主题面板的链接列表
links:
- icon: "layui-icon layui-icon-website"
title: "官方网站"
href: "http://www.laytp.com"
- icon: "layui-icon layui-icon-read"
title: "开发文档"
href: "https://www.laytp.com/doc.html"
- icon: "layui-icon layui-icon-fonts-code"
title: "开源地址"
href: "https://gitee.com/junstar/laytp"
## 其他配置
other:
## 主页动画时长
keepLoad: "500"
## 布局顶部主题
autoHead: false
## 头部配置
header:
## 站内消息,数据来源,通过 false 设置关闭
message: false
@@ -0,0 +1,522 @@
layui.define([
'message',
'table',
'jquery',
'element',
'yaml',
'form',
'laytpTab',
'apimenu',
'frame',
'theme',
'convert'
],
function(exports) {
"use strict";
var $ = layui.jquery,
form = layui.form,
element = layui.element,
yaml = layui.yaml,
laytpTab = layui.laytpTab,
convert = layui.convert,
laytpApiMenu = layui.apimenu,
laytpFrame = layui.frame,
laytpTheme = layui.theme,
message = layui.message;
var bodyFrame;
var sideMenu;
var bodyTab;
var config;
var logout = function() {};
var msgInstance;
var body = layui.$('body');
var laytpApidoc = new function() {
// 默认配置
var configType = 'yml';
var configPath = 'api.config.yml';
this.setConfigPath = function(path) {
configPath = path;
};
this.setConfigType = function(type) {
configType = type;
};
this.setAvatar = function(url, username) {
var image = new Image();
image.src = url || "/static/admin/images/avatar.jpg";
image.onload = function() {
layui.$(".layui-nav-img").attr("src", convert.imageToBase64(image));
};
layui.$(".layui-nav-img").parent().append(username);
};
this.render = function(initConfig) {
if (initConfig !== undefined) {
applyConfig(initConfig);
} else {
applyConfig(laytpApidoc.readConfig());
}
};
this.readConfig = function() {
if (configType === "yml") {
return yaml.load(configPath);
} else {
var data;
$.ajax({
url: configPath,
type: 'get',
dataType: 'json',
async: false,
success: function(result) {
data = result;
}
});
return data;
}
};
this.messageRender = function(option) {
var option = {
elem: '.message',
url: option.header.message,
height: '250px'
};
msgInstance = message.render(option);
};
this.logoRender = function(param) {
layui.$(".layui-logo .logo").attr("src", param.logo.image);
layui.$(".layui-logo .title").html(param.logo.title);
};
this.menuRender = function(param) {
sideMenu = laytpApiMenu.render({
elem: 'sideMenu',
async: param.menu.async !== undefined ? param.menu.async : true,
theme: "dark-theme",
// height: '100%',
method: param.menu.method,
control: param.menu.control ? 'control' : false, // control
defaultMenu: 0,
accordion: param.menu.accordion,
url: param.menu.data,
data: param.menu.data, //async为false时,传入菜单数组
parseData: function(res){
if(param.isSearch){
var result = {
"id" : -1,
"is_menu" : 1,
"is_show" : 1,
"title" : "搜索结果",
"type" : 0,
"children" : res,
"href" : "",
"icon" : ""
};
var resArr = [];
resArr.push(result);
return resArr;
}else{
window.menuData = laytpApidoc.parseData(res.data);
return window.menuData;
}
},
change: function() {
compatible();
},
done: function() {
let firstMenuObj = layui.$("#sideMenu a[menu-id='" + param.menu.select + "']").parent();
let menuType = $(firstMenuObj.html()).attr("doc-type");
let id = $(firstMenuObj.html()).attr("menu-id");
$('.api-page-tab-content').hide();
$('#' + menuType + '_' + id).show();
sideMenu.selectItem(param.menu.select);
}
});
};
this.parseData = function(data){
$.each(data, function(i, item) {
var tempItem = item;
// tempItem.id = item.id;
// tempItem.href = item.href;
// tempItem.icon = item.icon;
tempItem.type = 0;
// tempItem.title = item.name;
if(typeof item.children != null && typeof item.children !== "undefined" && item.children.length > 0){
tempItem.children = laytpApidoc.parseData(item.children);
}else{
tempItem.type = 1;
}
data[i] = tempItem;
});
return data;
};
this.bodyRender = function(param) {
if (param.tab.muiltTab) {
sideMenu.click(function(dom, data) {
let menuType = data.docType;
let id = data.menuId;
$('.api-page-tab-content').hide();
$('#' + menuType + '_' + id).show();
compatible();
});
} else {
sideMenu.click(function(dom, data) {
let menuType = data.docType;
let id = data.menuId;
$('.api-page-tab-content').hide();
$('#' + menuType + '_' + id).show();
compatible();
});
}
};
this.keepLoad = function(param) {
compatible();
setTimeout(function() {
layui.$(".loader-main").fadeOut(200);
}, param.other.keepLoad)
};
this.themeRender = function(option) {
if (option.theme.allowCustom === false) {
layui.$(".setting").remove();
}
var colorId = localStorage.getItem("theme-color");
var currentColor = getColorById(colorId);
localStorage.setItem("theme-color", currentColor.id);
localStorage.setItem("theme-color-context", currentColor.color);
laytpTheme.changeTheme(window, option.other.autoHead);
var menu = localStorage.getItem("theme-menu");
if (menu == null) {
menu = option.theme.defaultMenu;
} else {
if (option.theme.allowCustom === false) {
menu = option.theme.defaultMenu;
}
}
localStorage.setItem("theme-menu", menu);
this.menuSkin(menu);
}
this.menuSkin = function(theme) {
var laytpApidoc = layui.$(".laytp-admin");
laytpApidoc.removeClass("light-theme");
laytpApidoc.removeClass("dark-theme");
laytpApidoc.addClass(theme);
}
this.logout = function(callback) {
logout = callback;
}
this.message = function(callback) {
if (callback != null) {
msgInstance.click(callback);
} else {
msgInstance.click(messageTip);
}
}
this.jump = function(id, title, url) {
if (config.tab.muiltTab) {
bodyTab.addTabOnly({
id: id,
title: title,
url: url,
icon: null,
close: true
}, 300);
} else {
sideMenu.selectItem(id);
bodyFrame.changePage(url, title, true);
}
}
};
var messageTip = function(id, title, context, form) {
layer.open({
type: 1,
title: '消息', //标题
area: ['390px', '330px'], //宽高
shade: 0.4, //遮罩透明度
content: "<div style='background-color:whitesmoke;'><div class='layui-card'><div class='layui-card-body'>来源 : &nbsp; " +
form + "</div><div class='layui-card-header' >标题 : &nbsp; " + title +
"</div><div class='layui-card-body' >内容 : &nbsp; " + context + "</div></div></div>", //支持获取DOM元素
btn: ['确认'], //按钮组
scrollbar: false, //屏蔽浏览器滚动条
yes: function(index) { //layer.msg('yes'); //点击确定回调
layer.close(index);
showToast();
}
});
}
function collaspe() {
sideMenu.collaspe();
var admin = layui.$(".laytp-admin");
var left = layui.$(".layui-icon-spread-left")
var right = layui.$(".layui-icon-shrink-right")
if (admin.is(".laytp-mini")) {
$(".search").show();
left.addClass("layui-icon-shrink-right")
left.removeClass("layui-icon-spread-left")
admin.removeClass("laytp-mini");
} else {
$(".search").hide();
right.addClass("layui-icon-spread-left")
right.removeClass("layui-icon-shrink-right")
admin.addClass("laytp-mini");
}
}
body.on("click", ".logout", function() {
// 回调
var result = logout();
if (result) {
// 清空缓存
bodyTab.clear();
}
})
body.on("click", ".collaspe,.laytp-cover", function() {
collaspe();
});
body.on("click", ".fullScreen", function() {
if (layui.$(this).hasClass("layui-icon-screen-restore")) {
screenFun(2).then(function() {
layui.$(".fullScreen").eq(0).removeClass("layui-icon-screen-restore");
});
} else {
screenFun(1).then(function() {
layui.$(".fullScreen").eq(0).addClass("layui-icon-screen-restore");
});
}
});
body.on("click", '[user-menu-id]', function() {
if (config.tab.muiltTab) {
bodyTab.addTabOnly({
id: layui.$(this).attr("user-menu-id"),
title: layui.$(this).attr("user-menu-title"),
url: layui.$(this).attr("user-menu-url"),
icon: "",
close: true
}, 300);
} else {
bodyFrame.changePage(layui.$(this).attr("user-menu-url"), "", true);
}
});
body.on("click", ".setting", function() {
var bgColorHtml =
'<li class="layui-this" data-select-bgcolor="dark-theme" >' +
'<a href="javascript:;" data-skin="skin-blue" style="" class="clearfix full-opacity-hover">' +
'<div><span style="display:block; width: 20%; float: left; height: 12px; background: #28333E;"></span><span style="display:block; width: 80%; float: left; height: 12px; background: white;"></span></div>' +
'<div><span style="display:block; width: 20%; float: left; height: 40px; background: #28333E;"></span><span style="display:block; width: 80%; float: left; height: 40px; background: #f4f5f7;"></span></div>' +
'</a>' +
'</li>';
bgColorHtml +=
'<li data-select-bgcolor="light-theme" >' +
'<a href="javascript:;" data-skin="skin-blue" style="" class="clearfix full-opacity-hover">' +
'<div><span style="display:block; width: 20%; float: left; height: 12px; background: white;"></span><span style="display:block; width: 80%; float: left; height: 12px; background: white;"></span></div>' +
'<div><span style="display:block; width: 20%; float: left; height: 40px; background: white;"></span><span style="display:block; width: 80%; float: left; height: 40px; background: #f4f5f7;"></span></div>' +
'</a>' +
'</li>';
var html =
'<div class="laytpone-color">\n' +
'<div class="color-title">整体风格</div>\n' +
'<div class="color-content">\n' +
'<ul>\n' + bgColorHtml + '</ul>\n' +
'</div>\n' +
'</div>';
layer.open({
type: 1,
offset: 'r',
area: ['320px', '100%'],
title: false,
shade: 0.1,
closeBtn: 0,
shadeClose: false,
anim: -1,
skin: 'layer-anim-right',
move: false,
content: html + buildColorHtml() + buildLinkHtml() + bottomTool(),
success: function(layero, index) {
var color = localStorage.getItem("theme-color");
var menu = localStorage.getItem("theme-menu");
if (color !== "null") {
layui.$(".select-color-item").removeClass("layui-icon").removeClass("layui-icon-ok");
layui.$("*[color-id='" + color + "']").addClass("layui-icon").addClass("layui-icon-ok");
}
if (menu !== "null") {
layui.$("*[data-select-bgcolor]").removeClass("layui-this");
layui.$("[data-select-bgcolor='" + menu + "']").addClass("layui-this");
}
layui.$('#layui-layer-shade' + index).click(function() {
var $layero = layui.$('#layui-layer' + index);
$layero.animate({
left: $layero.offset().left + $layero.width()
}, 200, function() {
layer.close(index);
});
})
layui.$('#closeTheme').click(function() {
var $layero = layui.$('#layui-layer' + index);
$layero.animate({
left: $layero.offset().left + $layero.width()
}, 200, function() {
layer.close(index);
});
})
}
});
});
function bottomTool() {
return "<button id='closeTheme' style='position: absolute;bottom: 20px;left: 20px;' class='laytp-btn'>关闭</button>"
}
body.on('click', '[data-select-bgcolor]', function() {
var theme = layui.$(this).attr('data-select-bgcolor');
layui.$('[data-select-bgcolor]').removeClass("layui-this");
layui.$(this).addClass("layui-this");
localStorage.setItem("theme-menu", theme);
laytpApidoc.menuSkin(theme);
});
body.on('click', '.select-color-item', function() {
layui.$(".select-color-item").removeClass("layui-icon").removeClass("layui-icon-ok");
layui.$(this).addClass("layui-icon").addClass("layui-icon-ok");
var colorId = layui.$(".select-color-item.layui-icon-ok").attr("color-id");
var currentColor = getColorById(colorId);
localStorage.setItem("theme-color", currentColor.id);
localStorage.setItem("theme-color-context", currentColor.color);
laytpTheme.changeTheme(window, config.other.autoHead);
});
function applyConfig(param) {
config = param;
laytpApidoc.logoRender(param);
laytpApidoc.menuRender(param);
laytpApidoc.bodyRender(param);
laytpApidoc.themeRender(param);
laytpApidoc.keepLoad(param);
if (param.header.message !== false) {
laytpApidoc.messageRender(param);
}
}
function getColorById(id) {
var color;
var flag = false;
$.each(config.colors, function(i, value) {
if (value.id === id) {
color = value;
flag = true;
}
})
if (flag === false || config.theme.allowCustom === false) {
$.each(config.colors, function(i, value) {
if (value.id === config.theme.defaultColor) {
color = value;
}
})
}
return color;
}
function buildLinkHtml() {
var links = "";
$.each(config.links, function(i, value) {
links += '<a class="more-menu-item" href="' + value.href + '">' +
'<i class="' + value.icon + '" style="font-size: 19px;"></i> ' + value.title +
'</a>'
})
return '<div class="more-menu-list">' + links + '</div>';
}
function buildColorHtml() {
return "";
var colors = "";
$.each(config.colors, function(i, value) {
colors += "<span class='select-color-item' color-id='" + value.id + "' style='background-color:" + value.color +
";'></span>";
})
return "<div class='select-color'><div class='select-color-title'>主题配色</div><div class='select-color-content'>" +
colors + "</div></div>"
}
function compatible() {
if (layui.$(window).width() <= 768) {
collaspe()
}
}
function screenFun(num) {
num = num || 1;
num = num * 1;
var docElm = document.documentElement;
switch (num) {
case 1:
if (docElm.requestFullscreen) {
docElm.requestFullscreen();
} else if (docElm.mozRequestFullScreen) {
docElm.mozRequestFullScreen();
} else if (docElm.webkitRequestFullScreen) {
docElm.webkitRequestFullScreen();
} else if (docElm.msRequestFullscreen) {
docElm.msRequestFullscreen();
}
break;
case 2:
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
break;
}
return new Promise(function(res, rej) {
res("返回值");
});
}
function isFullscreen() {
return document.fullscreenElement ||
document.msFullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement || false;
}
window.onresize = function() {
if (!isFullscreen()) {
layui.$(".fullScreen").eq(0).removeClass("layui-icon-screen-restore");
}
}
exports('apidoc', laytpApidoc);
})
@@ -0,0 +1,491 @@
layui.define(['table', 'jquery', 'element'], function(exports) {
"use strict";
var MOD_NAME = 'apimenu',
$ = layui.jquery,
element = layui.element;
var laytpApiMenu = function(opt) {
this.option = opt;
};
// 供外部调用的,渲染菜单方法
laytpApiMenu.prototype.render = function(opt) {
var option = {
elem: opt.elem,
async: opt.async,
parseData: opt.parseData,
url: opt.url,
method: opt.method ? opt.method : "GET",
defaultOpen: opt.defaultOpen,
defaultSelect: opt.defaultSelect,
control: opt.control,
defaultMenu: opt.defaultMenu,
accordion: opt.accordion,
height: opt.height,
theme: opt.theme,
data: opt.data ? opt.data : [],
change: opt.change ? opt.change : function() {},
done: opt.done ? opt.done : function() {}
};
if (option.async) {
if (option.method === "GET") {
getData(option.url).then(function(data) {
option.data = data;
renderMenu(option);
});
} else {
postData(option.url).then(function(data) {
option.data = data;
renderMenu(option);
});
}
} else {
//renderMenu中需要调用done事件,done事件中需要menu对象,但是此时还未返回menu对象,做个延时提前返回对象
window.setTimeout(function() { renderMenu(option);}, 500);
}
// 处理高度
layui.$("#"+opt.elem).height(option.height);
return new laytpApiMenu(opt);
};
// 任意左侧菜单绑定点击事件
laytpApiMenu.prototype.click = function(clickEvent) {
var _this = this;
layui.$(document).off("click", "#" + _this.option.elem + " .site-demo-active").on("click", "#" + _this.option.elem + " .site-demo-active", function() {
// layui.$("body").on("click", "#" + _this.option.elem + " .site-demo-active", function() {
var dom = layui.$(this);
var data = {
menuId: dom.attr("menu-id"),
docType: dom.attr("doc-type"),
menuTitle: dom.attr("menu-title"),
};
var doms = hash(dom);
if (doms != null) {
if (doms.text() != '') {
data['menuPath'] = doms.find("span").text() + " / " + data['menuPath'];
}
}
if (doms != null) {
var domss = hash(doms);
if(domss!=null){
if (domss.text() != '') {
data['menuPath'] = domss.find("span").text() + " / " + data['menuPath'];
}}
}
if (domss != null) {
var domsss = hash(domss);
if(domsss!=null){
if (domsss.text() != '') {
data['menuPath'] = domsss.find("span").text() + " / " + data['menuPath'];
}}
}
if (layui.$("#" + _this.option.elem).is(".laytp-nav-mini")) {
if (_this.option.accordion) {
activeMenus = layui.$(this).parent().parent().parent().children("a");
} else {
activeMenus.push(layui.$(this).parent().parent().parent().children("a"));
}
}
clickEvent(dom, data);
})
};
function hash(dom) {
var d = dom.parent().parent().prev();
if (d.prop("tagName") === "UL") {
return null;
}
return d;
}
// 样式选择
laytpApiMenu.prototype.skin = function(skin) {
var menu = layui.$(".laytp-nav-tree[lay-filter='" + this.option.elem + "']").parent();
menu.removeClass("dark-theme");
menu.removeClass("light-theme");
menu.addClass(skin);
};
// 选择没有子级的菜单节点
laytpApiMenu.prototype.selectItem = function(laytpId) {
if (this.option.control != false) {
layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parents(".layui-side-scroll ").find("ul").css({
display: "none"
});
layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parents(".layui-side-scroll ").find(".layui-this").removeClass(
"layui-this");
layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parents("ul").css({
display: "block"
});
var controlId = layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parents("ul").attr("menu-id");
if (controlId != undefined) {
layui.$("#" + this.option.control).find(".layui-this").removeClass("layui-this");
layui.$("#" + this.option.control).find("[menu-id='" + controlId + "']").addClass("layui-this");
}
}
if (this.option.accordion === true) {
layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parents(".laytp-nav-tree").find(".layui-nav-itemed").removeClass(
"layui-nav-itemed");
}
layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parents(".laytp-nav-tree").find(".layui-this").removeClass(
"layui-this");
if (!layui.$("#" + this.option.elem).is(".laytp-nav-mini")) {
layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parents(".layui-nav-item").addClass("layui-nav-itemed");
layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parents("dd").addClass("layui-nav-itemed");
}
layui.$("#" + this.option.elem + " a[menu-id='" + laytpId + "']").parent().addClass("layui-this");
}
var activeMenus;
// 手机模式下,右下角按钮点击展开收缩左侧菜单事件
laytpApiMenu.prototype.collaspe = function(time) {
var elem = this.option.elem;
var config = this.option;
if (layui.$("#" + this.option.elem).is(".laytp-nav-mini")) {
$.each(activeMenus, function(i, item) {
layui.$("#" + elem + " a[menu-id='" + layui.$(this).attr("menu-id") + "']").parent().addClass("layui-nav-itemed");
});
layui.$("#" + this.option.elem).removeClass("laytp-nav-mini");
layui.$("#" + this.option.elem).animate({
width: "220px"
}, 150);
isHoverMenu(false, config);
} else {
activeMenus = layui.$("#" + this.option.elem).find(".layui-nav-itemed>a");
layui.$("#" + this.option.elem).find(".layui-nav-itemed").removeClass("layui-nav-itemed");
layui.$("#" + this.option.elem).addClass("laytp-nav-mini");
layui.$("#" + this.option.elem).animate({
width: "60px"
}, 400);
isHoverMenu(true, config);
}
};
function getData(url) {
var defer = $.Deferred();
var contact = (url.indexOf('?') > -1) ? "&" : "?";
$.get(url + contact + "fresh=" + Math.random(), function(result) {
defer.resolve(result)
});
return defer.promise();
}
function postData(url) {
var defer = $.Deferred();
$.post(url + "?fresh=" + Math.random(), function(result) {
defer.resolve(result)
});
return defer.promise();
}
// 内部使用,渲染菜单函数
function renderMenu(option) {
if (option.parseData != false) {
option.data = option.parseData(option.data);
}
if (option.data.length > 0) {
if (option.control != false) {
createMenuAndControl(option);
} else {
createMenu(option);
}
}
element.init();
downShow(option);
option.done();
}
// 创建菜单
function createMenu(option) {
var menuHtml = '<div style="height:100%!important;" class="laytp-side-scroll layui-side-scroll ' + option.theme + '"><ul lay-filter="' + option.elem +
'" class="layui-nav arrow laytp-menu layui-nav-tree laytp-nav-tree">';
$.each(option.data, function(i, item) {
var content = '<li class="layui-nav-item">';
if (i === option.defaultOpen) {
content = '<li class="layui-nav-item layui-nav-itemed">';
}
var href = "javascript:void(0);";
var className = "site-demo-active";
if (item.openType === "_blank" && item.type === 1) {
className = "";
}
if (item.type === 0) {
// 创 建 目 录 结 构
content += '<a href="javascript:;" menu-type="' + item.type + '" doc-type="' + item.docType + '" menu-id="' + item.id + '" href="' + href + '">' +
'<span>' + item.title + '</span></a>';
} else if (item.type === 1) {
content += '<a class="' + className + '" menu-type="' + item.type + '" doc-type="' + item.docType + '" menu-id="' +
item.id +
'" menu-title="' + item.title + '" href="' + href + '"><span>' + item.title + '</span></a>';
}
// 调 用 递 归 方 法 加 载 无 限 层 级 的 子 菜 单
content += loadchild(item);
// 结 束 一 个 根 菜 单 项
content += '</li>';
menuHtml += content;
});
// 结 束 菜 单 结 构 的 初 始 化
menuHtml += "</ul></div>";
// 将 菜 单 拼 接 到 初 始 化 容 器 中
layui.$("#" + option.elem).html(menuHtml);
}
// 创建多系统菜单, 包括渲染静态html和绑定顶部菜单点击事件
function createMenuAndControl(option) {
var control = '<ul class="layui-nav laytp-nav-control pc layui-hide-xs">';
var controlPe = '<ul class="layui-nav laytp-nav-control layui-hide-sm">';
// 声 明 头 部
var menu = '<div class="layui-side-scroll ' + option.theme + '">';
// 开 启 同 步 操 作
var index = 0;
var controlItemPe = '<dl class="layui-nav-child">';
var config = layui.apidoc.readConfig();
$.each(option.data, function(i, item) {
var menuItem = '';
var controlItem = '';
if(index < config.menu['maxTopMenuNum']){
if (i === option.defaultMenu) {
controlItem = '<li menu-title="' + item.title + '" doc-type="' + item.docType + '" menu-id="' + item.id +
'" class="layui-this layui-nav-item"><a href="javascript:void(0);">' + item.title + '</a></li>';
menuItem = '<ul menu-id="' + item.id + '" lay-filter="' + option.elem +
'" class="layui-nav arrow laytp-menu layui-nav-tree laytp-nav-tree">';
// 兼容移动端
controlPe += '<li class="layui-nav-item"><a class="pe-title" href="javascript:void(0);" >' + item.title + '</a>';
controlItemPe += '<dd menu-title="' + item.title + '" menu-id="' + item.id +
'"><a href="javascript:void(0);">' + item.title + '</a></dd>';
} else {
controlItem = '<li menu-title="' + item.title + '" doc-type="' + item.docType + '" menu-id="' + item.id +
'" class="layui-nav-item"><a href="javascript:void(0);">' + item.title + '</a></li>';
menuItem = '<ul style="display:none" menu-id="' + item.id + '" lay-filter="' + option.elem +
'" class="layui-nav arrow layui-nav-tree laytp-nav-tree">';
controlItemPe += '<dd menu-title="' + item.title + '" doc-type="' + item.docType + '" menu-id="' + item.id +
'"><a href="javascript:void(0);">' + item.title + '</a></dd>';
}
index++;
}else{
menuItem = '<ul style="display:none" menu-id="' + item.id + '" lay-filter="' + option.elem +
'" class="layui-nav arrow layui-nav-tree laytp-nav-tree">';
controlItemPe += '<dd menu-title="' + item.title + '" doc-type="' + item.docType + '" menu-id="' + item.id +
'"><a href="javascript:void(0);">' + item.title + '</a></dd>';
index++;
}
$.each(item.children, function(i, note) {
// 创 建 每 一 个 菜 单 项
var content = '<li class="layui-nav-item" >';
var href = "javascript:void(0);";
var target = "";
var className = "site-demo-active";
if (note.openType == "_blank" && note.type == 1) {
href = note.href;
target = "target='_blank'";
className = "";
}
// 判 断 菜 单 类 型 0 是 不可跳转的目录 1 是 可 点 击 跳 转 的 菜 单
if (note.type == 0) {
// 创 建 目 录 结 构
content += '<a href="javascript:void(0);" menu-type="' + item.type + '" doc-type="' + note.docType + '" menu-id="' + note.id +
'" ><span>' + note.title +
'</span></a>';
} else if (note.type == 1) {
// 创 建 菜 单 结 构
content += '<a class="' + className + '" menu-type="' + item.type + '" doc-type="' + note.docType + '" menu-id="' + note.id +
'" menu-title="' + note.title + '" href="' + href + '"><span>' + note.title + '</span></a>';
}
content += loadchild(note);
content += '</li>';
menuItem += content;
})
menu += menuItem + '</ul>';
control += controlItem;
})
if(option.data.length > config.menu['maxTopMenuNum']){
control += '<li class="layui-nav-item">\n' +
'<a href="javascript:void(0);" class="laytp-icon laytp-icon-elipsis"></a>' +
'<!-- 功 能 菜 单 -->\n' +
'<dl class="layui-nav-child">';
var moreTopMenuIndex = 0;
$.each(option.data, function(i, item) {
var moreTopMenuItem = '';
if(moreTopMenuIndex >= config.menu['maxTopMenuNum']){
moreTopMenuItem = '<dd><a menu-title="' + item.title + '" menu-id="' + item.id +'">' + item.title + '</a></dd>';
}
moreTopMenuIndex++;
control += moreTopMenuItem;
});
control += '</dl></li></ul>';
}
controlItemPe += "</li></dl></ul>";
controlPe += controlItemPe;
layui.$("#" + option.control).html(control);
layui.$("#" + option.control).append(controlPe);
layui.$("#" + option.elem).html(menu);
// 绑定顶部菜单点击事件
layui.$(document).off("click", "#" + option.control + " .laytp-nav-control [menu-id]").on("click", "#" + option.control + " .laytp-nav-control [menu-id]", function() {
// layui.$("#" + option.control + " .laytp-nav-control").on("click", "[menu-id]", function() {
layui.$("#" + option.elem).find(".laytp-nav-tree").css({
display: 'none'
});
layui.$("#" + option.elem).find(".laytp-nav-tree[menu-id='" + layui.$(this).attr("menu-id") + "']").css({
display: 'block'
});
layui.$("#" + option.control).find(".pe-title").html(layui.$(this).attr("menu-title"));
layui.$("#" + option.control).find("");
option.change(layui.$(this).attr("menu-id"), layui.$(this).attr("menu-title"), layui.$(this).attr("menu-href"));
// 自动点击左侧的第一个菜单
var s = layui.$("a:first",layui.$("#" + option.elem).find("ul[style='display: block;']")).parent();
if(s.hasClass("layui-nav-itemed")){
recursionFindA(s);
}else{
layui.$("a:first",layui.$("#" + option.elem).find("ul[style='display: block;']")).click();
}
});
}
// 点击顶部菜单,自动点击左侧菜单,当菜单已经是展开状态时,要递归找到最低级别的A标签进行点击
function recursionFindA(obj){
var dlObj = layui.$("dl:first", obj);
if(dlObj.length === 0){
layui.$("a:first", obj).click();
}else{
recursionFindA(layui.$("dd:first",dlObj));
}
}
/** 加载子菜单 (递归)*/
function loadchild(obj) {
// 判 单 是 否 是 菜 单, 如 果 是 菜 单 直 接 返 回
if (obj.type == 1) {
return "";
}
// 创 建 子 菜 单 结 构
var content = '<dl class="layui-nav-child">';
// 如 果 嵌 套 不 等 于 空
if (obj.children != null && obj.children.length > 0) {
// 遍 历 子 项 目
$.each(obj.children, function(i, note) {
// 创 建 子 项 结 构
content += '<dd>';
var href = "javascript:;";
var target = "";
var className = "site-demo-active";
if (note.openType == "_blank" && note.type == 1) {
href = note.href;
target = "target='_blank'";
className = "";
}
// 判 断 子 项 类 型
if (note.type == 0) {
// 创 建 目 录 结 构
content += '<a href="' + href + '" doc-type="' + note.docType + '" menu-id="' + note.id +
'"><i class="' + note.icon + '"></i><span>' + note.title + '</span></a>';
} else if (note.type == 1) {
// 创 建 菜 单 结 构
content += '<a class="' + className + '" doc-type="' + note.docType + '" menu-id="' + note.id + '" menu-title="' + note.title + '" href="' + href +
'" ><i class="' + note.icon + '"></i><span>' + note.title + '</span></a>';
}
// 加 载 子 项 目 录
content += loadchild(note);
// 结 束 当 前 子 菜 单
content += '</dd>';
});
// 封 装
} else {
content += '<div class="toast"> 无 内 容 </div>';
}
content += '</dl>';
return content;
}
// 左侧菜单点击事件
function downShow(option) {
layui.$(document).off("click", "#" + option.elem + " a[menu-type='0']").on("click", "#" + option.elem + " a[menu-type='0']", function() {
// layui.$("body #" + option.elem).on("click", "a[menu-type='0']", function() {
if (!layui.$("#" + option.elem).is(".laytp-nav-mini")) {
var superEle = layui.$(this).parent();
var ele = layui.$(this).next('.layui-nav-child');
var heights = ele.children("dd").length * 48;
if (superEle.is(".layui-nav-itemed")) {
if (option.accordion) {
superEle.parent().find(".layui-nav-itemed").removeClass("layui-nav-itemed");
superEle.addClass("layui-nav-itemed");
//自动点击第一个子菜单
layui.$("a:first",ele).click();
}
ele.height(0);
ele.animate({
height: heights + "px"
}, 200, function() {
ele.css({
height: "auto"
});
});
} else {
superEle.addClass("layui-nav-itemed");
ele.animate({
height: "0px"
}, 200, function() {
ele.css({
height: "auto"
});
superEle.removeClass("layui-nav-itemed");
});
}
}
})
}
/** 二 级 悬 浮 菜 单*/
function isHoverMenu(b, option) {
if (b) {
layui.$("#" + option.elem + ".laytp-nav-mini .layui-nav-item,#" + option.elem + ".laytp-nav-mini dd").hover(function(e) {
e.stopPropagation();
var _this = layui.$(this);
_this.siblings().find(".layui-nav-child")
.removeClass("layui-nav-hover").css({
left: 0,
top: 0
});
_this.children(".layui-nav-child").addClass("layui-nav-hover");
_this.closest('.layui-nav-item').data('time') && clearTimeout(_this.closest('.layui-nav-item').data('time'));
var height = layui.$(window).height();
var topLength = _this.offset().top;
var thisHeight = _this.children(".layui-nav-child").height();
if ((thisHeight + topLength) > height) {
topLength = height - thisHeight - 10;
}
var left = _this.offset().left + 60;
if (!_this.hasClass("layui-nav-item")) {
left = _this.offset().left + _this.width();
}
_this.children(".layui-nav-child").offset({
top: topLength,
left: left + 3
});
}, function(e) {
e.stopPropagation();
var _this = layui.$(this);
_this.closest('.layui-nav-item').data('time', setTimeout(function() {
_this.closest('.layui-nav-item')
.find(".layui-nav-child")
.removeClass("layui-nav-hover")
.css({
left: 0,
top: 0
});
}, 50));
})
} else {
layui.$("#" + option.elem + " .layui-nav-item").off('mouseenter').unbind('mouseleave');
layui.$("#" + option.elem + " dd").off('mouseenter').unbind('mouseleave');
}
}
exports(MOD_NAME, new laytpApiMenu());
});
@@ -0,0 +1,89 @@
layui.use(["laytp"], function () {
const funController = {};
//静态页面地址前缀
window.htmlPrefix = facade.compatibleHtmlPath("/plugin/apidoc/");
//后端接口地址前缀
window.apiPrefix = facade.compatibleApiRoute("/plugin/apidoc/index/");
//表格渲染
funController.tableRender = function (where, page) {
layui.table.render({
elem: "#laytp-table"
, id: "laytp-table"
, url: facade.url("/plugin/apidoc/index/index",{paging:1})
, toolbar: "#default-toolbar"
, defaultToolbar: [{
title: '刷新',
layEvent: 'refresh',
icon: 'layui-icon-refresh',
}, 'filter', 'print', 'exports']
, where: where
, method: "GET"
, cellMinWidth: 120
, skin: 'line'
, loading: false
, page: {
curr: page
}
, parseData: function (res) { //res 即为原始返回的数据
return facade.parseTableData(res, true);
}
, done: function(){
layui.laytpTable.done();
}
, cols: [[
{type: 'checkbox', fixed: 'left'}
, {field: 'id', title: 'ID', align: 'center', width: 80, fixed: 'left'}
, {field: 'title', title: '标题', align: 'center'}
, {field: 'des', title: '描述', align: 'center'}
, {field: 'create_time', title: '创建时间', align: 'center'}
, {field:'operation',title:'操作',align:'center',toolbar:'#default-bar',width:150,fixed:'right'}
]]
});
//监听数据表格顶部左侧按钮点击事件
layui.table.on("toolbar(laytp-table)", function (obj) {
//默认按钮点击事件,包括添加按钮和回收站按钮
var defaultTableToolbar = layui.context.get("defaultTableToolbar");
if (defaultTableToolbar.indexOf(obj.event) !== -1) {
laytp.tableToolbar(obj);
//其他自定义按钮点击事件
} else {
//自定义按钮点击事件
switch (obj.event) {
//生成Api文档
case "create":
facade.ajax({
route: "/plugin/apidoc/index/create",
showLoading: true
});
break;
//查看Api文档
case "open":
window.open(facade.getAdminApiDomain() + "/api.html");
break;
}
}
});
//监听数据表格[操作列]按钮点击事件
layui.table.on("tool(laytp-table)", function (obj) {
var defaultTableTool = layui.context.get("defaultTableTool");
if (defaultTableTool.indexOf(obj.event) !== -1) {
laytp.tableTool(obj);
} else {
// //自定义按钮点击事件
// switch(obj.event){
// //自定义按钮点击事件
// case "":
//
// break;
// }
}
});
};
funController.tableRender();
window.funController = funController;
});
@@ -0,0 +1,78 @@
layui.use(['laytp'], function () {
const funRecycleController = {};
//静态页面地址前缀
window.htmlPrefix = facade.compatibleHtmlPath("/plugin/apidoc/");
//后端接口地址前缀
window.apiPrefix = facade.compatibleApiRoute("/plugin/apidoc/index/");
//表格渲染
funRecycleController.tableRender = function (where, page) {
layui.table.render({
elem: "#laytp-recycle-table"
, id: "laytp-recycle-table"
, url: facade.url("/plugin/apidoc/index/recycle")
, toolbar: "#recycle-default-toolbar"
, defaultToolbar: [{
title: '刷新',
layEvent: 'recycle-refresh',
icon: 'layui-icon-refresh',
}, 'filter', 'print', 'exports']
, where: where
, method: "GET"
, cellMinWidth: 80
, skin: 'line'
, loading: false
, page: {
curr: page
}
, parseData: function (res) { //res 即为原始返回的数据
return facade.parseTableData(res, true);
}
, cols: [[ //表头
{type: 'checkbox', fixed: 'left'}
, {field: 'id', title: 'ID', align: 'center', width: 80, fixed: 'left'}
, {field: 'title', title: '标题', align: 'center'}
, {field: 'des', title: '描述', align: 'center'}
, {field: 'create_time', title: '创建时间', align: 'center'}
, {field:'operation',title:'操作',align:'center',toolbar:'#recycle-default-bar',width:150,fixed:'right'}
]]
});
//监听数据表格顶部左侧按钮点击事件
layui.table.on("toolbar(laytp-recycle-table)", function (obj) {
var defaultTableToolbar = layui.context.get("defaultTableToolbar");
if (defaultTableToolbar.indexOf(obj.event) !== -1) {
//默认按钮点击事件
laytp.tableToolbar(obj);
} else {
// //自定义按钮点击事件
// switch(obj.event){
// //自定义按钮点击事件
// case "":
//
// break;
// }
}
});
//监听数据表格[操作列]按钮点击事件
layui.table.on('tool(laytp-recycle-table)', function (obj) {
var defaultTableTool = layui.context.get("defaultTableTool");
if (defaultTableTool.indexOf(obj.event) !== -1) {
laytp.tableTool(obj);
} else {
// //自定义按钮
// switch(obj.event){
// //自定义按钮点击事件
// case '':
//
// break;
// }
}
});
};
funRecycleController.tableRender();
window.funRecycleController = funRecycleController;
});