From 127919050e5cf700e652ee9d53489007e1be1820 Mon Sep 17 00:00:00 2001 From: Karson Date: Wed, 20 May 2026 17:36:21 +0800 Subject: [PATCH 01/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8DSelectpage=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=AD=9B=E9=80=89=E5=AE=89=E5=85=A8=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/common/controller/Backend.php | 134 +------ application/common/library/SelectPage.php | 433 ++++++++++++++++++++++ 2 files changed, 443 insertions(+), 124 deletions(-) create mode 100644 application/common/library/SelectPage.php diff --git a/application/common/controller/Backend.php b/application/common/controller/Backend.php index 9a22eed6..3a1a13e0 100644 --- a/application/common/controller/Backend.php +++ b/application/common/controller/Backend.php @@ -3,6 +3,7 @@ namespace app\common\controller; use app\admin\library\Auth; +use app\common\library\SelectPage; use think\Config; use think\Controller; use think\Hook; @@ -10,7 +11,6 @@ use think\Lang; use think\Loader; use think\Model; use think\Session; -use fast\Tree; use think\Validate; /** @@ -459,141 +459,27 @@ class Backend extends Controller /** * Selectpage的实现方法 - * - * 当前方法只是一个比较通用的搜索匹配,请按需重载此方法来编写自己的搜索逻辑,$where按自己的需求写即可 - * 这里示例了所有的参数,所以比较复杂,实现上自己实现只需简单的几行即可 - * */ protected function selectpage() { //设置过滤方法 $this->request->filter(['trim', 'strip_tags', 'htmlspecialchars']); - //搜索关键词,客户端输入以空格分开,这里接收为数组 - $word = (array)$this->request->request("q_word/a"); - //当前页 - $page = $this->request->request("pageNumber"); - //分页大小 - $pagesize = $this->request->request("pageSize"); - //搜索条件 - $andor = $this->request->request("andOr", "and", "strtoupper"); - //排序方式 - $orderby = (array)$this->request->request("orderBy/a"); - //显示的字段 - $field = $this->request->request("showField"); - //主键 - $primarykey = $this->request->request("keyField"); - //主键值 - $primaryvalue = $this->request->request("keyValue"); - //搜索字段 - $searchfield = (array)$this->request->request("searchField/a"); - //自定义搜索条件 - $custom = (array)$this->request->request("custom/a"); - //是否返回树形结构 - $istree = $this->request->request("isTree", 0); - $ishtml = $this->request->request("isHtml", 0); - if ($istree) { - $word = []; - $pagesize = 999999; - } - $order = []; - foreach ($orderby as $k => $v) { - $order[$v[0]] = $v[1]; - } - $field = $field ? $field : 'name'; + $selectPage = new SelectPage($this->model, $this->selectpageFields); - //如果有primaryvalue,说明当前是初始化传值 - if ($primaryvalue !== null) { - $where = [$primarykey => ['in', $primaryvalue]]; - $pagesize = 999999; - } else { - $where = function ($query) use ($word, $andor, $field, $searchfield, $custom) { - $logic = $andor == 'AND' ? '&' : '|'; - $searchfield = is_array($searchfield) ? implode($logic, $searchfield) : $searchfield; - $searchfield = str_replace(',', $logic, $searchfield); - $word = array_filter(array_unique($word)); - if (count($word) == 1) { - $query->where($searchfield, "like", "%" . reset($word) . "%"); - } else { - $query->where(function ($query) use ($word, $searchfield) { - foreach ($word as $index => $item) { - $query->whereOr(function ($query) use ($item, $searchfield) { - $query->where($searchfield, "like", "%{$item}%"); - }); - } - }); - } - if ($custom && is_array($custom)) { - foreach ($custom as $k => $v) { - if (is_array($v) && 2 == count($v)) { - $query->where($k, trim($v[0]), $v[1]); - } else { - $query->where($k, '=', $v); - } - } - } - }; - } + // 数据限制 $adminIds = $this->getDataLimitAdminIds(); if (is_array($adminIds)) { - $this->model->where($this->dataLimitField, 'in', $adminIds); + $selectPage->setDataLimit($this->dataLimit, $this->dataLimitField, $adminIds); } - $list = []; - $total = $this->model->where($where)->count(); - if ($total > 0) { - if (is_array($adminIds)) { - $this->model->where($this->dataLimitField, 'in', $adminIds); - } - $fields = is_array($this->selectpageFields) ? $this->selectpageFields : ($this->selectpageFields && $this->selectpageFields != '*' ? explode(',', $this->selectpageFields) : []); - - //如果有primaryvalue,说明当前是初始化传值,按照选择顺序排序 - if ($primaryvalue !== null && preg_match("/^[a-z0-9_\-]+$/i", $primarykey)) { - $primaryvalue = array_unique(is_array($primaryvalue) ? $primaryvalue : explode(',', $primaryvalue)); - //修复自定义data-primary-key为字符串内容时,给排序字段添加上引号 - $primaryvalue = array_map(function ($value) { - return \think\Db::quote($value); - }, $primaryvalue); - - $primaryvalue = implode(',', $primaryvalue); - - $this->model->orderRaw("FIELD(`{$primarykey}`, {$primaryvalue})"); - } else { - $this->model->order($order); - } - - $datalist = $this->model->where($where) - ->page($page, $pagesize) - ->select(); - - foreach ($datalist as $index => $item) { - unset($item['password'], $item['salt']); - if ($this->selectpageFields == '*') { - $result = [ - $primarykey => $item[$primarykey] ?? '', - $field => $item[$field] ?? '', - ]; - } else { - $result = array_intersect_key(($item instanceof Model ? $item->toArray() : (array)$item), array_flip($fields)); - } - $result['pid'] = isset($item['pid']) ? $item['pid'] : (isset($item['parent_id']) ? $item['parent_id'] : 0); - $result = array_map("htmlentities", $result); - $list[] = $result; - } - if ($istree && !$primaryvalue) { - $tree = Tree::instance(); - $tree->init(collection($list)->toArray(), 'pid'); - $list = $tree->getTreeList($tree->getTreeArray(0), $field); - if (!$ishtml) { - foreach ($list as &$item) { - $item = str_replace(' ', ' ', $item); - } - unset($item); - } - } + try { + $result = $selectPage->execute($this->request->request()); + } catch (\think\Exception $e) { + $this->error($e->getMessage()); } - //这里一定要返回有list这个字段,total是可选的,如果total<=list的数量,则会隐藏分页按钮 - return json(['list' => $list, 'total' => $total]); + + return json($result); } /** diff --git a/application/common/library/SelectPage.php b/application/common/library/SelectPage.php new file mode 100644 index 00000000..fbc47066 --- /dev/null +++ b/application/common/library/SelectPage.php @@ -0,0 +1,433 @@ +', '>', '>=', '<', '<=', + 'like', 'not like', 'notlike', + 'in', 'not in', 'notin', + 'between', 'not between', 'notbetween', + 'null', 'not null', 'notnull', + 'exists', 'not exists', 'notexists', + '> time', '< time', '>= time', '<= time', + 'between time', 'not between time', 'notbetween time', + ]; + + /** + * 允许排序的字段 + * @var array + */ + protected $orderFields = []; + + /** + * @param Model $model 模型实例 + * @param string $fields SelectPage可显示的字段 + */ + public function __construct(Model $model, $fields = '*') + { + $this->model = $model; + $this->selectpageFields = $fields; + $this->allowedFields = array_map('strtolower', $model->getTableFields()); + $this->orderFields = $this->allowedFields; + } + + /** + * 设置数据限制 + * @param bool|string $dataLimit auth/personal/false + * @param string $field 限制字段 + * @param array $adminIds 允许的管理员ID列表 + * @return $this + */ + public function setDataLimit($dataLimit, $field = 'admin_id', array $adminIds = []) + { + $this->dataLimit = $dataLimit; + $this->dataLimitField = $field; + + if (is_array($adminIds) && !empty($adminIds)) { + $this->model->where($this->dataLimitField, 'in', $adminIds); + } + + return $this; + } + + /** + * 执行查询 + * @param array $params 请求参数 + * @return array ['list' => [...], 'total' => int] + */ + public function execute(array $params) + { + $keywordWords = $this->getArrayParam($params, 'q_word'); + $page = $params['pageNumber'] ?? 1; + $pageSize = $params['pageSize'] ?? 10; + $andor = strtoupper($params['andOr'] ?? 'AND'); + $orderBy = $this->getArrayParam($params, 'orderBy'); + $showField = $params['showField'] ?? 'name'; + $keyField = $params['keyField'] ?? ''; + $keyValue = $params['keyValue'] ?? null; + $searchField = $this->getArrayParam($params, 'searchField'); + $custom = $this->getArrayParam($params, 'custom'); + $isTree = (bool)($params['isTree'] ?? 0); + $isHtml = (bool)($params['isHtml'] ?? 0); + + // 树形模式强制参数 + if ($isTree) { + $keywordWords = []; + $pageSize = 999999; + } + + // 验证字段 + $showFields = $this->normalizeField($showField); + $keyFields = $keyField ? $this->normalizeField($keyField) : []; + foreach ($showFields as $f) { + $this->validateField($f); + } + foreach ($keyFields as $f) { + $this->validateField($f); + } + + // 验证搜索字段 + foreach ($searchField as $f) { + $this->validateField($f); + } + + // 验证自定义条件的字段和操作符 + $this->validateCustomConditions($custom); + + // 构建排序 + $order = $this->buildOrder($orderBy); + + // 构建查询条件 + $where = $this->buildWhere( + $keywordWords, + $andor, + $showField, + $searchField, + $custom, + $keyField, + $keyValue + ); + + // 执行总数统计 + $total = $this->model->where($where)->count(); + + if ($total <= 0) { + return ['list' => [], 'total' => 0]; + } + + // 排序处理 + if ($keyValue !== null && $keyField) { + $this->applyPrimaryKeyOrder($keyField, $keyValue); + } else { + $this->model->order($order); + } + + // 执行查询 + $dataList = $this->model->where($where) + ->page($page, $pageSize) + ->select(); + + // 构建结果集 + $list = $this->buildResultList($dataList, $showField, $keyField); + + // 树形结构处理 + if ($isTree && !$keyValue) { + $list = $this->buildTreeList($list, $showField, $isHtml); + } + + return ['list' => $list, 'total' => $total]; + } + + /** + * 标准化字段为数组(支持逗号分隔字符串) + */ + protected function normalizeField($field): array + { + if (is_array($field)) { + return $field; + } + if (is_string($field) && strpos($field, ',') !== false) { + return array_map('trim', explode(',', $field)); + } + return $field !== '' ? [$field] : []; + } + + /** + * 获取数组参数 + */ + protected function getArrayParam(array $params, string $key): array + { + $value = $params[$key] ?? []; + if (is_array($value)) { + return $value; + } + if (is_string($value) && strpos($value, ',') !== false) { + return array_map('trim', explode(',', $value)); + } + if ($value === '' || $value === null) { + return []; + } + return [$value]; + } + + /** + * 验证字段名是否在允许列表中 + */ + protected function validateField(string $field) + { + $field = strtolower($field); + if (!in_array($field, $this->allowedFields, true)) { + throw new Exception('Invalid parameters'); + } + } + + /** + * 验证自定义搜索条件 + */ + protected function validateCustomConditions(array $custom) + { + foreach ($custom as $k => $v) { + $field = strtolower($k); + if (!in_array($field, $this->allowedFields, true)) { + throw new Exception('Invalid parameters'); + } + // 如果操作符是数组形式传入,校验操作符合法性 + if (is_array($v) && count($v) >= 2) { + $operator = strtolower(trim($v[0])); + if (!in_array($operator, self::$allowedOperators, true)) { + throw new Exception('Invalid parameters'); + } + } + } + } + + /** + * 构建排序 + */ + protected function buildOrder(array $orderBy): array + { + $order = []; + foreach ($orderBy as $v) { + if (!isset($v[0], $v[1])) { + continue; + } + $field = strtolower($v[0]); + $direction = strtoupper($v[1]) === 'ASC' ? 'ASC' : 'DESC'; + if (in_array($field, $this->orderFields, true)) { + $order[$field] = $direction; + } + } + return $order; + } + + /** + * 构建查询条件 + */ + protected function buildWhere( + array $keywordWords, + string $andor, + string $showField, + array $searchField, + array $custom, + string $keyField, + $keyValue + ) + { + // 如果有 keyValue,按主键值精确查询 + if ($keyValue !== null && $keyField) { + return [$keyField => ['in', is_array($keyValue) ? $keyValue : explode(',', (string)$keyValue)]]; + } + + return function ($query) use ($keywordWords, $andor, $showField, $searchField, $custom) { + // 关键词搜索 + $searchFields = $this->resolveSearchFields($searchField, $showField, $andor); + $words = array_filter(array_unique($keywordWords)); + if (!empty($words)) { + if (count($words) === 1) { + $query->where($searchFields, 'like', '%' . reset($words) . '%'); + } else { + $query->where(function ($query) use ($words, $searchFields) { + foreach ($words as $word) { + $query->whereOr($searchFields, 'like', '%' . $word . '%'); + } + }); + } + } + + // 自定义条件 + foreach ($custom as $k => $v) { + if (is_array($v) && count($v) >= 2) { + $operator = strtolower(trim($v[0])); + $value = $v[1]; + $query->where(strtolower($k), $operator, $value); + } else { + $query->where(strtolower($k), '=', $v); + } + } + }; + } + + /** + * 解析搜索字段 + */ + protected function resolveSearchFields(array $searchField, string $showField, string $andor): string + { + // 过滤掉不在允许列表中的字段 + $validFields = []; + $inputFields = array_filter(array_map('trim', $searchField)); + + foreach ($inputFields as $field) { + $lowerField = strtolower($field); + if (in_array($lowerField, $this->allowedFields, true)) { + $validFields[] = $lowerField; + } + } + + if (empty($validFields)) { + $lowerShow = strtolower($showField); + if (in_array($lowerShow, $this->allowedFields, true)) { + return $lowerShow; + } + return 'id'; + } + + $logic = $andor === 'AND' ? '&' : '|'; + return implode($logic, $validFields); + } + + /** + * 应用主键排序 + */ + protected function applyPrimaryKeyOrder(string $keyField, $keyValue) + { + $values = is_array($keyValue) ? $keyValue : explode(',', (string)$keyValue); + $values = array_unique(array_filter(array_map(function ($v) { + return trim((string)$v); + }, $values))); + + if (empty($values)) { + return; + } + + $quotedValues = implode(',', array_map(function ($v) { + return Db::quote($v); + }, $values)); + + $this->model->orderRaw("FIELD(`{$keyField}`, {$quotedValues})"); + } + + /** + * 构建结果列表 + */ + protected function buildResultList($dataList, string $showField, string $keyField): array + { + $list = []; + $fields = $this->resolveSelectpageFields(); + + foreach ($dataList as $item) { + $row = $item instanceof Model ? $item->toArray() : (array)$item; + + // 移除敏感字段 + unset($row['password'], $row['salt']); + + if ($this->selectpageFields === '*') { + $result = [ + $keyField => $row[$keyField] ?? '', + $showField => $row[$showField] ?? '', + ]; + } else { + $result = array_intersect_key($row, array_flip($fields)); + } + + // 添加父级ID + $result['pid'] = $row['pid'] ?? ($row['parent_id'] ?? 0); + + // HTML 转义 + $result = array_map(function ($value) { + return $value === null ? '' : htmlentities((string)$value, ENT_QUOTES, 'UTF-8'); + }, $result); + + $list[] = $result; + } + + return $list; + } + + /** + * 构建树形列表 + */ + protected function buildTreeList(array $list, string $showField, bool $isHtml): array + { + $tree = Tree::instance(); + $tree->init($list, 'pid'); + $result = $tree->getTreeList($tree->getTreeArray(0), $showField); + + if (!$isHtml) { + foreach ($result as &$item) { + $item = str_replace(' ', ' ', $item); + } + unset($item); + } + + return $result; + } + + /** + * 解析 SelectPage 显示字段 + */ + protected function resolveSelectpageFields(): array + { + if (is_array($this->selectpageFields)) { + return $this->selectpageFields; + } + if ($this->selectpageFields && $this->selectpageFields !== '*') { + return explode(',', $this->selectpageFields); + } + return []; + } +} From 63426c0e72d414eb909d8ebcfda9d7c34019dec7 Mon Sep 17 00:00:00 2001 From: Karson Date: Wed, 20 May 2026 17:44:35 +0800 Subject: [PATCH 02/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/config.php b/application/config.php index 64c33048..aa8525d8 100755 --- a/application/config.php +++ b/application/config.php @@ -302,7 +302,7 @@ return [ //允许跨域的域名,多个以,分隔 'cors_request_domain' => 'localhost,127.0.0.1', //版本号 - 'version' => '1.6.1.20250430', + 'version' => '1.6.3.20260520', //API接口地址 'api_url' => 'https://api.fastadmin.net', ], From 214ff67a55d395e4c23f26aa2faf2df3949a28ce Mon Sep 17 00:00:00 2001 From: Karson Date: Wed, 20 May 2026 17:59:30 +0800 Subject: [PATCH 03/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=97=B6=E7=9A=84=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/common/controller/Backend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/common/controller/Backend.php b/application/common/controller/Backend.php index 3a1a13e0..6dca6453 100644 --- a/application/common/controller/Backend.php +++ b/application/common/controller/Backend.php @@ -476,7 +476,7 @@ class Backend extends Controller try { $result = $selectPage->execute($this->request->request()); } catch (\think\Exception $e) { - $this->error($e->getMessage()); + $this->error(__($e->getMessage())); } return json($result); From 9fe1c1c7a6f2e1cbfc76e17c1134a7842aac5df9 Mon Sep 17 00:00:00 2001 From: Karson Date: Fri, 22 May 2026 11:06:40 +0800 Subject: [PATCH 04/13] =?UTF-8?q?=E4=BF=AE=E5=A4=8DSelectPage=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/common/controller/Backend.php | 6 +-- application/common/library/SelectPage.php | 52 ++++++++++++++--------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/application/common/controller/Backend.php b/application/common/controller/Backend.php index 6dca6453..2522aa73 100644 --- a/application/common/controller/Backend.php +++ b/application/common/controller/Backend.php @@ -468,9 +468,9 @@ class Backend extends Controller $selectPage = new SelectPage($this->model, $this->selectpageFields); // 数据限制 - $adminIds = $this->getDataLimitAdminIds(); - if (is_array($adminIds)) { - $selectPage->setDataLimit($this->dataLimit, $this->dataLimitField, $adminIds); + $dataLimitIds = $this->getDataLimitAdminIds(); + if (is_array($dataLimitIds)) { + $selectPage->setDataLimit($this->dataLimit, $this->dataLimitField, $dataLimitIds); } try { diff --git a/application/common/library/SelectPage.php b/application/common/library/SelectPage.php index fbc47066..17d41a7c 100644 --- a/application/common/library/SelectPage.php +++ b/application/common/library/SelectPage.php @@ -76,22 +76,39 @@ class SelectPage $this->orderFields = $this->allowedFields; } + /** + * 数据限制的ID集合 + * @var array + */ + protected $dataLimitIds = []; + /** * 设置数据限制 - * @param bool|string $dataLimit auth/personal/false - * @param string $field 限制字段 - * @param array $adminIds 允许的管理员ID列表 + * @param bool|string $dataLimit auth/personal/false + * @param string $dataLimitField 限制字段 + * @param array $dataLimitIds 允许的ID列表 * @return $this */ - public function setDataLimit($dataLimit, $field = 'admin_id', array $adminIds = []) + public function setDataLimit($dataLimit, $dataLimitField = 'admin_id', array $dataLimitIds = []) { $this->dataLimit = $dataLimit; - $this->dataLimitField = $field; + $this->dataLimitField = $dataLimitField; + $this->dataLimitIds = $dataLimitIds; - if (is_array($adminIds) && !empty($adminIds)) { - $this->model->where($this->dataLimitField, 'in', $adminIds); + return $this; + } + + /** + * 应用数据限制条件(每次构建新查询链前调用) + * ThinkPHP 的 count()/select() 执行后会清空 model options, + * 所以需要在每次查询前重新注入 dataLimit 条件。 + * @return $this + */ + protected function applyDataLimit() + { + if ($this->dataLimit) { + $this->model->where($this->dataLimitField, 'in', $this->dataLimitIds); } - return $this; } @@ -122,14 +139,8 @@ class SelectPage } // 验证字段 - $showFields = $this->normalizeField($showField); - $keyFields = $keyField ? $this->normalizeField($keyField) : []; - foreach ($showFields as $f) { - $this->validateField($f); - } - foreach ($keyFields as $f) { - $this->validateField($f); - } + $this->validateField($showField); + $this->validateField($keyField); // 验证搜索字段 foreach ($searchField as $f) { @@ -154,7 +165,9 @@ class SelectPage ); // 执行总数统计 - $total = $this->model->where($where)->count(); + $total = $this->applyDataLimit() + ->model->where($where) + ->count(); if ($total <= 0) { return ['list' => [], 'total' => 0]; @@ -167,8 +180,9 @@ class SelectPage $this->model->order($order); } - // 执行查询 - $dataList = $this->model->where($where) + // 执行查询(count()会清空options,需重新应用dataLimit) + $dataList = $this->applyDataLimit() + ->model->where($where) ->page($page, $pageSize) ->select(); From 542771bbf86e0ee328ec9c6cd1cefd2f147c926b Mon Sep 17 00:00:00 2001 From: Karson Date: Fri, 22 May 2026 11:39:35 +0800 Subject: [PATCH 05/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/config.php b/application/config.php index aa8525d8..ac3f9aca 100755 --- a/application/config.php +++ b/application/config.php @@ -302,7 +302,7 @@ return [ //允许跨域的域名,多个以,分隔 'cors_request_domain' => 'localhost,127.0.0.1', //版本号 - 'version' => '1.6.3.20260520', + 'version' => '1.6.4.20260522', //API接口地址 'api_url' => 'https://api.fastadmin.net', ], From c7582f7efe6e1f995a89b28cf156cb3bab9387dd Mon Sep 17 00:00:00 2001 From: Karson Date: Tue, 2 Jun 2026 10:42:14 +0800 Subject: [PATCH 06/13] =?UTF-8?q?=E4=BC=98=E5=8C=96API=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化公共接口输出 优化用户退出逻辑 优化头像保存 优化模板变量输出 --- application/admin/command/Api.php | 43 +- .../admin/command/Api/template/index.html | 1502 ++++++++++------- application/api/controller/Common.php | 25 +- application/api/controller/User.php | 9 +- application/common/view/tpl/dispatch_jump.tpl | 4 +- application/index/controller/User.php | 11 +- application/index/view/user/logout.html | 16 + 7 files changed, 953 insertions(+), 657 deletions(-) create mode 100644 application/index/view/user/logout.html diff --git a/application/admin/command/Api.php b/application/admin/command/Api.php index 759c4ff8..81ee212f 100644 --- a/application/admin/command/Api.php +++ b/application/admin/command/Api.php @@ -19,8 +19,8 @@ class Api extends Command ->setName('api') ->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '') ->addOption('cdnurl', 'd', Option::VALUE_OPTIONAL, 'default cdn url', '') - ->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api') - ->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html') + ->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(index/api)', 'api') + ->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', '') ->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html') ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false) ->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'] ?? '') @@ -43,22 +43,30 @@ class Api extends Command if (!preg_match("/^([a-z0-9]+)\.html\$/i", $template)) { throw new Exception('template file not correct'); } - $language = $language ? $language : 'zh-cn'; + $language = $language ?: 'zh-cn'; $langFile = $apiDir . 'lang' . DS . $language . '.php'; if (!is_file($langFile)) { throw new Exception('language file not found'); } $lang = include_once $langFile; + // 目标目录 - $output_dir = ROOT_PATH . 'public' . DS; - $output_file = $output_dir . $input->getOption('output'); - if (is_file($output_file) && !$force) { + $outputDir = ROOT_PATH . 'runtime' . DS . 'docs' . DS; + if (!is_dir($outputDir)) { + mkdir($outputDir, 0755, true); + } + $outputFilename = $input->getOption('output') ?: 'apidoc_' . date('Ymd_') . strtolower(\fast\Random::alnum(6)) . '.html'; + if ($outputFilename === 'api.html') { + throw new Exception('api.html cannot be used as the output file name'); + } + $outputFile = $outputDir . $outputFilename; + if (is_file($outputFile) && !$force) { throw new Exception("api index file already exists!\nIf you need to rebuild again, use the parameter --force=true "); } // 模板文件 - $template_dir = $apiDir . 'template' . DS; - $template_file = $template_dir . $template; - if (!is_file($template_file)) { + $templateDir = $apiDir . 'template' . DS; + $templateFile = $templateDir . $template; + if (!is_file($templateFile)) { throw new Exception('template file not found'); } // 额外的类 @@ -70,7 +78,6 @@ class Api extends Command // 插件 $addon = $input->getOption('addon'); - $moduleDir = $addonDir = ''; if ($addon) { $addonInfo = get_addon_info($addon); if (!$addonInfo) { @@ -83,9 +90,12 @@ class Api extends Command if (!is_dir($moduleDir)) { throw new Exception('module not found'); } + if (in_array($module, ['admin', 'common'])) { + throw new Exception('module not allowed'); + } - if (version_compare(PHP_VERSION, '7.0.0', '<')) { - throw new Exception("Requires PHP version 7.0 or newer"); + if (version_compare(PHP_VERSION, '7.4.0', '<')) { + throw new Exception("Requires PHP version 7.4 or newer"); } //控制器名 @@ -118,7 +128,7 @@ class Api extends Command $classes = array_unique(array_filter($classes)); - $cdnurl = $cdnurl ? : Config::get('site.cdnurl'); + $cdnurl = $cdnurl ?: Config::get('site.cdnurl'); $config = [ 'sitename' => config('site.name'), @@ -132,12 +142,13 @@ class Api extends Command Config::set('view_replace_str.__CDN__', $cdnurl); $builder = new Builder($classes); - $content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]); + $content = $builder->render($templateFile, ['config' => $config, 'lang' => $lang]); - if (!file_put_contents($output_file, $content)) { - throw new Exception('Cannot save the content to ' . $output_file); + if (!file_put_contents($outputFile, $content)) { + throw new Exception('Cannot save the content to ' . $outputFile); } $output->info("Build Successed!"); + $output->info("Docs Location:" . $outputFile); } /** diff --git a/application/admin/command/Api/template/index.html b/application/admin/command/Api/template/index.html index 10d5a977..e68c0d6a 100755 --- a/application/admin/command/Api/template/index.html +++ b/application/admin/command/Api/template/index.html @@ -1,648 +1,922 @@ - - - - - - {$config.title} + + + + + + {$config.title} + - - - -