commit
47cc8fb54a
@ -0,0 +1,11 @@
|
||||
{
|
||||
"directory": "public/assets/libs",
|
||||
"ignoredDependencies": [
|
||||
"es6-promise",
|
||||
"file-saver",
|
||||
"html2canvas",
|
||||
"jspdf",
|
||||
"jspdf-autotable",
|
||||
"pdfmake"
|
||||
]
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
[app]
|
||||
debug = false
|
||||
trace = false
|
||||
|
||||
[database]
|
||||
hostname = 127.0.0.1
|
||||
database = fastadmin
|
||||
username = root
|
||||
password = root
|
||||
hostport = 3306
|
||||
prefix = fa_
|
@ -0,0 +1,19 @@
|
||||
/nbproject/
|
||||
/thinkphp/
|
||||
/vendor/
|
||||
/runtime/*
|
||||
/addons/*
|
||||
/application/admin/command/Install/*.lock
|
||||
/application/config.php
|
||||
/public/assets/libs/
|
||||
/public/assets/addons/*
|
||||
/public/uploads/*
|
||||
.idea
|
||||
composer.lock
|
||||
*.log
|
||||
*.css.map
|
||||
!.gitkeep
|
||||
.env
|
||||
.svn
|
||||
.vscode
|
||||
node_modules
|
@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "{}" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright 2017 Karson
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1 @@
|
||||
deny from all
|
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\behavior;
|
||||
|
||||
class AdminLog
|
||||
{
|
||||
public function run(&$params)
|
||||
{
|
||||
//只记录POST请求的日志
|
||||
if (request()->isPost() && config('fastadmin.auto_record_log')) {
|
||||
\app\admin\model\AdminLog::record();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\command;
|
||||
|
||||
use think\addons\AddonException;
|
||||
use think\addons\Service;
|
||||
use think\Config;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\exception\PDOException;
|
||||
|
||||
class Addon extends Command
|
||||
{
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('addon')
|
||||
->addOption('name', 'a', Option::VALUE_REQUIRED, 'addon name', null)
|
||||
->addOption('action', 'c', Option::VALUE_REQUIRED, 'action(create/enable/disable/uninstall/refresh/package/move)', 'create')
|
||||
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', null)
|
||||
->addOption('release', 'r', Option::VALUE_OPTIONAL, 'addon release version', null)
|
||||
->addOption('uid', 'u', Option::VALUE_OPTIONAL, 'fastadmin uid', null)
|
||||
->addOption('token', 't', Option::VALUE_OPTIONAL, 'fastadmin token', null)
|
||||
->addOption('domain', 'd', Option::VALUE_OPTIONAL, 'domain', null)
|
||||
->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local package', null)
|
||||
->setDescription('Addon manager');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$name = $input->getOption('name') ?: '';
|
||||
$action = $input->getOption('action') ?: '';
|
||||
if (stripos($name, 'addons' . DS) !== false) {
|
||||
$name = explode(DS, $name)[1];
|
||||
}
|
||||
//强制覆盖
|
||||
$force = $input->getOption('force');
|
||||
//版本
|
||||
$release = $input->getOption('release') ?: '';
|
||||
//uid
|
||||
$uid = $input->getOption('uid') ?: '';
|
||||
//token
|
||||
$token = $input->getOption('token') ?: '';
|
||||
|
||||
include dirname(__DIR__) . DS . 'common.php';
|
||||
|
||||
if (!$name && !in_array($action, ['refresh'])) {
|
||||
throw new Exception('Addon name could not be empty');
|
||||
}
|
||||
if (!$action || !in_array($action, ['create', 'disable', 'enable', 'install', 'uninstall', 'refresh', 'upgrade', 'package', 'move'])) {
|
||||
throw new Exception('Please input correct action name');
|
||||
}
|
||||
|
||||
// 查询一次SQL,判断连接是否正常
|
||||
Db::execute("SELECT 1");
|
||||
|
||||
$addonDir = ADDON_PATH . $name . DS;
|
||||
switch ($action) {
|
||||
case 'create':
|
||||
//非覆盖模式时如果存在则报错
|
||||
if (is_dir($addonDir) && !$force) {
|
||||
throw new Exception("addon already exists!\nIf you need to create again, use the parameter --force=true ");
|
||||
}
|
||||
//如果存在先移除
|
||||
if (is_dir($addonDir)) {
|
||||
rmdirs($addonDir);
|
||||
}
|
||||
mkdir($addonDir, 0755, true);
|
||||
mkdir($addonDir . DS . 'controller', 0755, true);
|
||||
$menuList = \app\common\library\Menu::export($name);
|
||||
$createMenu = $this->getCreateMenu($menuList);
|
||||
$prefix = Config::get('database.prefix');
|
||||
$createTableSql = '';
|
||||
try {
|
||||
$result = Db::query("SHOW CREATE TABLE `" . $prefix . $name . "`;");
|
||||
if (isset($result[0]) && isset($result[0]['Create Table'])) {
|
||||
$createTableSql = $result[0]['Create Table'];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'addon' => $name,
|
||||
'addonClassName' => ucfirst($name),
|
||||
'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu) . ";\n\tMenu::create(\$menu);" : '',
|
||||
'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '',
|
||||
'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '',
|
||||
'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '',
|
||||
];
|
||||
$this->writeToFile("addon", $data, $addonDir . ucfirst($name) . '.php');
|
||||
$this->writeToFile("config", $data, $addonDir . 'config.php');
|
||||
$this->writeToFile("info", $data, $addonDir . 'info.ini');
|
||||
$this->writeToFile("controller", $data, $addonDir . 'controller' . DS . 'Index.php');
|
||||
if ($createTableSql) {
|
||||
$createTableSql = str_replace("`" . $prefix, '`__PREFIX__', $createTableSql);
|
||||
file_put_contents($addonDir . 'install.sql', $createTableSql);
|
||||
}
|
||||
|
||||
$output->info("Create Successed!");
|
||||
break;
|
||||
case 'disable':
|
||||
case 'enable':
|
||||
try {
|
||||
//调用启用、禁用的方法
|
||||
Service::$action($name, 0);
|
||||
} catch (AddonException $e) {
|
||||
if ($e->getCode() != -3) {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
if (!$force) {
|
||||
//如果有冲突文件则提醒
|
||||
$data = $e->getData();
|
||||
foreach ($data['conflictlist'] as $k => $v) {
|
||||
$output->warning($v);
|
||||
}
|
||||
$output->info("Are you sure you want to " . ($action == 'enable' ? 'override' : 'delete') . " all those files? Type 'yes' to continue: ");
|
||||
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
|
||||
if (trim($line) != 'yes') {
|
||||
throw new Exception("Operation is aborted!");
|
||||
}
|
||||
}
|
||||
//调用启用、禁用的方法
|
||||
Service::$action($name, 1);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
$output->info(ucfirst($action) . " Successed!");
|
||||
break;
|
||||
case 'uninstall':
|
||||
//非覆盖模式时如果存在则报错
|
||||
if (!$force) {
|
||||
throw new Exception("If you need to uninstall addon, use the parameter --force=true ");
|
||||
}
|
||||
try {
|
||||
Service::uninstall($name, 0);
|
||||
} catch (AddonException $e) {
|
||||
if ($e->getCode() != -3) {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
if (!$force) {
|
||||
//如果有冲突文件则提醒
|
||||
$data = $e->getData();
|
||||
foreach ($data['conflictlist'] as $k => $v) {
|
||||
$output->warning($v);
|
||||
}
|
||||
$output->info("Are you sure you want to delete all those files? Type 'yes' to continue: ");
|
||||
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
|
||||
if (trim($line) != 'yes') {
|
||||
throw new Exception("Operation is aborted!");
|
||||
}
|
||||
}
|
||||
Service::uninstall($name, 1);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
|
||||
$output->info("Uninstall Successed!");
|
||||
break;
|
||||
case 'refresh':
|
||||
Service::refresh();
|
||||
$output->info("Refresh Successed!");
|
||||
break;
|
||||
case 'package':
|
||||
$infoFile = $addonDir . 'info.ini';
|
||||
if (!is_file($infoFile)) {
|
||||
throw new Exception(__('Addon info file was not found'));
|
||||
}
|
||||
|
||||
$info = get_addon_info($name);
|
||||
if (!$info) {
|
||||
throw new Exception(__('Addon info file data incorrect'));
|
||||
}
|
||||
$infoname = isset($info['name']) ? $info['name'] : '';
|
||||
if (!$infoname || !preg_match("/^[a-z]+$/i", $infoname) || $infoname != $name) {
|
||||
throw new Exception(__('Addon info name incorrect'));
|
||||
}
|
||||
|
||||
$infoversion = isset($info['version']) ? $info['version'] : '';
|
||||
if (!$infoversion || !preg_match("/^\d+\.\d+\.\d+$/i", $infoversion)) {
|
||||
throw new Exception(__('Addon info version incorrect'));
|
||||
}
|
||||
|
||||
$addonTmpDir = RUNTIME_PATH . 'addons' . DS;
|
||||
if (!is_dir($addonTmpDir)) {
|
||||
@mkdir($addonTmpDir, 0755, true);
|
||||
}
|
||||
$addonFile = $addonTmpDir . $infoname . '-' . $infoversion . '.zip';
|
||||
if (!class_exists('ZipArchive')) {
|
||||
throw new Exception(__('ZinArchive not install'));
|
||||
}
|
||||
$zip = new \ZipArchive;
|
||||
$zip->open($addonFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
|
||||
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($addonDir), \RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($files as $name => $file) {
|
||||
if (!$file->isDir()) {
|
||||
$filePath = $file->getRealPath();
|
||||
$relativePath = str_replace(DS, '/', substr($filePath, strlen($addonDir)));
|
||||
if (!in_array($file->getFilename(), ['.git', '.DS_Store', 'Thumbs.db'])) {
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
$output->info("Package Successed!");
|
||||
break;
|
||||
case 'move':
|
||||
$movePath = [
|
||||
'adminOnlySelfDir' => ['admin/behavior', 'admin/controller', 'admin/library', 'admin/model', 'admin/validate', 'admin/view'],
|
||||
'adminAllSubDir' => ['admin/lang'],
|
||||
'publicDir' => ['public/assets/addons', 'public/assets/js/backend']
|
||||
];
|
||||
$paths = [];
|
||||
$appPath = str_replace('/', DS, APP_PATH);
|
||||
$rootPath = str_replace('/', DS, ROOT_PATH);
|
||||
foreach ($movePath as $k => $items) {
|
||||
switch ($k) {
|
||||
case 'adminOnlySelfDir':
|
||||
foreach ($items as $v) {
|
||||
$v = str_replace('/', DS, $v);
|
||||
$oldPath = $appPath . $v . DS . $name;
|
||||
$newPath = $rootPath . "addons" . DS . $name . DS . "application" . DS . $v . DS . $name;
|
||||
$paths[$oldPath] = $newPath;
|
||||
}
|
||||
break;
|
||||
case 'adminAllSubDir':
|
||||
foreach ($items as $v) {
|
||||
$v = str_replace('/', DS, $v);
|
||||
$vPath = $appPath . $v;
|
||||
$list = scandir($vPath);
|
||||
foreach ($list as $_v) {
|
||||
if (!in_array($_v, ['.', '..']) && is_dir($vPath . DS . $_v)) {
|
||||
$oldPath = $appPath . $v . DS . $_v . DS . $name;
|
||||
$newPath = $rootPath . "addons" . DS . $name . DS . "application" . DS . $v . DS . $_v . DS . $name;
|
||||
$paths[$oldPath] = $newPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'publicDir':
|
||||
foreach ($items as $v) {
|
||||
$v = str_replace('/', DS, $v);
|
||||
$oldPath = $rootPath . $v . DS . $name;
|
||||
$newPath = $rootPath . 'addons' . DS . $name . DS . $v . DS . $name;
|
||||
$paths[$oldPath] = $newPath;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach ($paths as $oldPath => $newPath) {
|
||||
if (is_dir($oldPath)) {
|
||||
if ($force) {
|
||||
if (is_dir($newPath)) {
|
||||
$list = scandir($newPath);
|
||||
foreach ($list as $_v) {
|
||||
if (!in_array($_v, ['.', '..'])) {
|
||||
$file = $newPath . DS . $_v;
|
||||
@chmod($file, 0777);
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
@rmdir($newPath);
|
||||
}
|
||||
}
|
||||
copydirs($oldPath, $newPath);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取创建菜单的数组
|
||||
* @param array $menu
|
||||
* @return array
|
||||
*/
|
||||
protected function getCreateMenu($menu)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($menu as $k => & $v) {
|
||||
$arr = [
|
||||
'name' => $v['name'],
|
||||
'title' => $v['title'],
|
||||
];
|
||||
if ($v['icon'] != 'fa fa-circle-o') {
|
||||
$arr['icon'] = $v['icon'];
|
||||
}
|
||||
if ($v['ismenu']) {
|
||||
$arr['ismenu'] = $v['ismenu'];
|
||||
}
|
||||
if (isset($v['childlist']) && $v['childlist']) {
|
||||
$arr['sublist'] = $this->getCreateMenu($v['childlist']);
|
||||
}
|
||||
$result[] = $arr;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入到文件
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @param string $pathname
|
||||
* @return mixed
|
||||
*/
|
||||
protected function writeToFile($name, $data, $pathname)
|
||||
{
|
||||
$search = $replace = [];
|
||||
foreach ($data as $k => $v) {
|
||||
$search[] = "{%{$k}%}";
|
||||
$replace[] = $v;
|
||||
}
|
||||
$stub = file_get_contents($this->getStub($name));
|
||||
$content = str_replace($search, $replace, $stub);
|
||||
|
||||
if (!is_dir(dirname($pathname))) {
|
||||
mkdir(strtolower(dirname($pathname)), 0755, true);
|
||||
}
|
||||
return file_put_contents($pathname, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础模板
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub($name)
|
||||
{
|
||||
return __DIR__ . '/Addon/stubs/' . $name . '.stub';
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace addons\{%name%};
|
||||
|
||||
use app\common\library\Menu;
|
||||
use think\Addons;
|
||||
|
||||
/**
|
||||
* 插件
|
||||
*/
|
||||
class {%addonClassName%} extends Addons
|
||||
{
|
||||
|
||||
/**
|
||||
* 插件安装方法
|
||||
* @return bool
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
{%addonInstallMenu%}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件卸载方法
|
||||
* @return bool
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
{%addonUninstallMenu%}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件启用方法
|
||||
* @return bool
|
||||
*/
|
||||
public function enable()
|
||||
{
|
||||
{%addonEnableMenu%}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件禁用方法
|
||||
* @return bool
|
||||
*/
|
||||
public function disable()
|
||||
{
|
||||
{%addonDisableMenu%}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
//配置唯一标识
|
||||
'name' => 'usernmae',
|
||||
//显示的标题
|
||||
'title' => '用户名',
|
||||
//类型
|
||||
'type' => 'string',
|
||||
//分组
|
||||
'group' => '',
|
||||
//动态显示
|
||||
'visible' => '',
|
||||
//数据字典
|
||||
'content' => [
|
||||
],
|
||||
//值
|
||||
'value' => '',
|
||||
//验证规则
|
||||
'rule' => 'required',
|
||||
//错误消息
|
||||
'msg' => '',
|
||||
//提示消息
|
||||
'tip' => '',
|
||||
//成功消息
|
||||
'ok' => '',
|
||||
//扩展信息
|
||||
'extend' => ''
|
||||
],
|
||||
[
|
||||
'name' => 'password',
|
||||
'title' => '密码',
|
||||
'type' => 'string',
|
||||
'content' => [
|
||||
],
|
||||
'value' => '',
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '',
|
||||
'ok' => '',
|
||||
'extend' => ''
|
||||
],
|
||||
];
|
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace addons\{%addon%}\controller;
|
||||
|
||||
use think\addons\Controller;
|
||||
|
||||
class Index extends Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->error("当前插件暂无前台页面");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
name = {%name%}
|
||||
title = 插件名称{%name%}
|
||||
intro = 插件介绍
|
||||
author = yourname
|
||||
website = https://www.fastadmin.net
|
||||
version = 1.0.0
|
||||
state = 1
|
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\command;
|
||||
|
||||
use app\admin\command\Api\library\Builder;
|
||||
use think\Config;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\Exception;
|
||||
|
||||
class Api extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$site = Config::get('site');
|
||||
$this
|
||||
->setName('api')
|
||||
->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
|
||||
->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
|
||||
->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
|
||||
->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
|
||||
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
|
||||
->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'] ?? '')
|
||||
->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
|
||||
->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
|
||||
->addOption('addon', 'a', Option::VALUE_OPTIONAL, 'addon name', null)
|
||||
->addOption('controller', 'r', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name', null)
|
||||
->setDescription('Build Api document from controller');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$apiDir = __DIR__ . DS . 'Api' . DS;
|
||||
|
||||
$force = $input->getOption('force');
|
||||
$url = $input->getOption('url');
|
||||
$language = $input->getOption('language');
|
||||
$template = $input->getOption('template');
|
||||
if (!preg_match("/^([a-z0-9]+)\.html\$/i", $template)) {
|
||||
throw new Exception('template file not correct');
|
||||
}
|
||||
$language = $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) {
|
||||
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)) {
|
||||
throw new Exception('template file not found');
|
||||
}
|
||||
// 额外的类
|
||||
$classes = $input->getOption('class');
|
||||
// 标题
|
||||
$title = $input->getOption('title');
|
||||
// 模块
|
||||
$module = $input->getOption('module');
|
||||
// 插件
|
||||
$addon = $input->getOption('addon');
|
||||
|
||||
$moduleDir = $addonDir = '';
|
||||
if ($addon) {
|
||||
$addonInfo = get_addon_info($addon);
|
||||
if (!$addonInfo) {
|
||||
throw new Exception('addon not found');
|
||||
}
|
||||
$moduleDir = ADDON_PATH . $addon . DS;
|
||||
} else {
|
||||
$moduleDir = APP_PATH . $module . DS;
|
||||
}
|
||||
if (!is_dir($moduleDir)) {
|
||||
throw new Exception('module not found');
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
|
||||
throw new Exception("Requires PHP version 7.0 or newer");
|
||||
}
|
||||
|
||||
//控制器名
|
||||
$controller = $input->getOption('controller') ?: [];
|
||||
if (!$controller) {
|
||||
$controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($controllerDir),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($files as $name => $file) {
|
||||
if (!$file->isDir() && $file->getExtension() == 'php') {
|
||||
$filePath = $file->getRealPath();
|
||||
$classes[] = $this->get_class_from_file($filePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($controller as $index => $item) {
|
||||
$filePath = $moduleDir . Config::get('url_controller_layer') . DS . $item . '.php';
|
||||
$classes[] = $this->get_class_from_file($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
$classes = array_unique(array_filter($classes));
|
||||
|
||||
$config = [
|
||||
'sitename' => config('site.name'),
|
||||
'title' => $title,
|
||||
'author' => config('site.name'),
|
||||
'description' => '',
|
||||
'apiurl' => $url,
|
||||
'language' => $language,
|
||||
];
|
||||
|
||||
$builder = new Builder($classes);
|
||||
$content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
|
||||
|
||||
if (!file_put_contents($output_file, $content)) {
|
||||
throw new Exception('Cannot save the content to ' . $output_file);
|
||||
}
|
||||
$output->info("Build Successed!");
|
||||
}
|
||||
|
||||
/**
|
||||
* get full qualified class name
|
||||
*
|
||||
* @param string $path_to_file
|
||||
* @return string
|
||||
* @author JBYRNE http://jarretbyrne.com/2015/06/197/
|
||||
*/
|
||||
protected function get_class_from_file($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];
|
||||
} elseif ($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,25 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Info' => '基础信息',
|
||||
'Sandbox' => '在线测试',
|
||||
'Sampleoutput' => '返回示例',
|
||||
'Headers' => 'Headers',
|
||||
'Parameters' => '参数',
|
||||
'Body' => '正文',
|
||||
'Name' => '名称',
|
||||
'Type' => '类型',
|
||||
'Required' => '必选',
|
||||
'Description' => '描述',
|
||||
'Send' => '提交',
|
||||
'Reset' => '重置',
|
||||
'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中',
|
||||
'Apiurltips' => 'API接口URL',
|
||||
'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中',
|
||||
'Authorization' => '权限',
|
||||
'NeedLogin' => '登录',
|
||||
'NeedRight' => '鉴权',
|
||||
'ReturnHeaders' => '响应头',
|
||||
'ReturnParameters' => '返回参数',
|
||||
'Response' => '响应输出',
|
||||
];
|
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\command\Api\library;
|
||||
|
||||
use think\Config;
|
||||
|
||||
/**
|
||||
* @website https://github.com/calinrada/php-apidoc
|
||||
* @author Calin Rada <rada.calin@gmail.com>
|
||||
* @author Karson <karson@fastadmin.net>
|
||||
*/
|
||||
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);
|
||||
$this->view = new \think\View(Config::get('template'), Config::get('view_replace_str'));
|
||||
}
|
||||
|
||||
protected function extractAnnotations()
|
||||
{
|
||||
foreach ($this->classes as $class) {
|
||||
$classAnnotation = Extractor::getClassAnnotations($class);
|
||||
// 如果忽略
|
||||
if (isset($classAnnotation['ApiInternal'])) {
|
||||
continue;
|
||||
}
|
||||
Extractor::getClassMethodAnnotations($class);
|
||||
//Extractor::getClassPropertyValues($class);
|
||||
}
|
||||
$allClassAnnotation = Extractor::getAllClassAnnotations();
|
||||
$allClassMethodAnnotation = Extractor::getAllClassMethodAnnotations();
|
||||
//$allClassPropertyValue = Extractor::getAllClassPropertyValues();
|
||||
|
||||
// foreach ($allClassMethodAnnotation as $className => &$methods) {
|
||||
// foreach ($methods as &$method) {
|
||||
// //权重判断
|
||||
// if ($method && !isset($method['ApiWeigh']) && isset($allClassAnnotation[$className]['ApiWeigh'])) {
|
||||
// $method['ApiWeigh'] = $allClassAnnotation[$className]['ApiWeigh'];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// unset($methods);
|
||||
return [$allClassAnnotation, $allClassMethodAnnotation];
|
||||
}
|
||||
|
||||
protected function generateHeadersTemplate($docs)
|
||||
{
|
||||
if (!isset($docs['ApiHeaders'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$headerslist = array();
|
||||
foreach ($docs['ApiHeaders'] as $params) {
|
||||
$tr = array(
|
||||
'name' => $params['name'] ?? '',
|
||||
'type' => $params['type'] ?? 'string',
|
||||
'sample' => $params['sample'] ?? '',
|
||||
'required' => $params['required'] ?? false,
|
||||
'description' => $params['description'] ?? '',
|
||||
);
|
||||
$headerslist[] = $tr;
|
||||
}
|
||||
|
||||
return $headerslist;
|
||||
}
|
||||
|
||||
protected function generateParamsTemplate($docs)
|
||||
{
|
||||
if (!isset($docs['ApiParams'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$paramslist = array();
|
||||
foreach ($docs['ApiParams'] as $params) {
|
||||
$tr = array(
|
||||
'name' => $params['name'],
|
||||
'type' => $params['type'] ?? 'string',
|
||||
'sample' => $params['sample'] ?? '',
|
||||
'required' => $params['required'] ?? true,
|
||||
'description' => $params['description'] ?? '',
|
||||
);
|
||||
$paramslist[] = $tr;
|
||||
}
|
||||
|
||||
return $paramslist;
|
||||
}
|
||||
|
||||
protected function generateReturnHeadersTemplate($docs)
|
||||
{
|
||||
if (!isset($docs['ApiReturnHeaders'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$headerslist = array();
|
||||
foreach ($docs['ApiReturnHeaders'] as $params) {
|
||||
$tr = array(
|
||||
'name' => $params['name'] ?? '',
|
||||
'type' => 'string',
|
||||
'sample' => $params['sample'] ?? '',
|
||||
'required' => isset($params['required']) && $params['required'] ? 'Yes' : 'No',
|
||||
'description' => $params['description'] ?? '',
|
||||
);
|
||||
$headerslist[] = $tr;
|
||||
}
|
||||
|
||||
return $headerslist;
|
||||
}
|
||||
|
||||
protected function generateReturnParamsTemplate($st_params)
|
||||
{
|
||||
if (!isset($st_params['ApiReturnParams'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$paramslist = array();
|
||||
foreach ($st_params['ApiReturnParams'] as $params) {
|
||||
$tr = array(
|
||||
'name' => $params['name'] ?? '',
|
||||
'type' => $params['type'] ?? 'string',
|
||||
'sample' => $params['sample'] ?? '',
|
||||
'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 = array(
|
||||
'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()
|
||||
{
|
||||
list($allClassAnnotations, $allClassMethodAnnotations) = $this->extractAnnotations();
|
||||
|
||||
$sectorArr = [];
|
||||
foreach ($allClassAnnotations as $index => &$allClassAnnotation) {
|
||||
// 如果设置隐藏,则不显示在文档
|
||||
if (isset($allClassAnnotation['ApiInternal'])) {
|
||||
continue;
|
||||
}
|
||||
$sector = isset($allClassAnnotation['ApiSector']) ? $allClassAnnotation['ApiSector'][0] : $allClassAnnotation['ApiTitle'][0];
|
||||
$sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0;
|
||||
}
|
||||
unset($allClassAnnotation);
|
||||
|
||||
arsort($sectorArr);
|
||||
$routes = include_once CONF_PATH . 'route.php';
|
||||
$subdomain = false;
|
||||
if (config('url_domain_deploy') && isset($routes['__domain__']) && isset($routes['__domain__']['api']) && $routes['__domain__']['api']) {
|
||||
$subdomain = true;
|
||||
}
|
||||
$counter = 0;
|
||||
$section = null;
|
||||
$weigh = 0;
|
||||
$docsList = [];
|
||||
foreach ($allClassMethodAnnotations 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;
|
||||
}
|
||||
$route = is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0];
|
||||
if ($subdomain) {
|
||||
$route = substr($route, 4);
|
||||
}
|
||||
$docsList[$section][$name] = [
|
||||
'id' => $counter,
|
||||
'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
|
||||
'methodLabel' => $this->generateBadgeForMethod($docs),
|
||||
'section' => $section,
|
||||
'route' => $route,
|
||||
'title' => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
|
||||
'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
|
||||
'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),
|
||||
'weigh' => is_array($docs['ApiWeigh'][0]) ? $docs['ApiWeigh'][0]['data'] : $docs['ApiWeigh'][0],
|
||||
'return' => isset($docs['ApiReturn']) ? (is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0]) : '',
|
||||
'needLogin' => $docs['ApiPermissionLogin'][0],
|
||||
'needRight' => $docs['ApiPermissionRight'][0],
|
||||
];
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//重建排序
|
||||
foreach ($docsList as $index => &$methods) {
|
||||
$methodSectorArr = [];
|
||||
foreach ($methods as $name => $method) {
|
||||
$methodSectorArr[$name] = isset($method['weigh']) ? $method['weigh'] : 0;
|
||||
}
|
||||
arsort($methodSectorArr);
|
||||
$methods = array_merge(array_flip(array_keys($methodSectorArr)), $methods);
|
||||
}
|
||||
$docsList = array_merge(array_flip(array_keys($sectorArr)), $docsList);
|
||||
return $docsList;
|
||||
}
|
||||
|
||||
public function getView()
|
||||
{
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @return string
|
||||
*/
|
||||
public function render($template, $vars = [])
|
||||
{
|
||||
$docsList = $this->parse();
|
||||
|
||||
return $this->view->display(file_get_contents($template), array_merge($vars, ['docsList' => $docsList]));
|
||||
}
|
||||
}
|
@ -0,0 +1,654 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<title>{$config.title}</title>
|
||||
|
||||
<!-- Bootstrap Core CSS -->
|
||||
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Plugin CSS -->
|
||||
<link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding-top: 70px; margin-bottom: 15px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: "Roboto", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", BlinkMacSystemFont, -apple-system, "Segoe UI", "Microsoft Yahei", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
h2 { font-size: 1.2em; }
|
||||
hr { margin-top: 10px; }
|
||||
.tab-pane { padding-top: 10px; }
|
||||
.mt0 { margin-top: 0px; }
|
||||
.footer { font-size: 12px; color: #666; }
|
||||
.docs-list .label { display: inline-block; min-width: 65px; padding: 0.3em 0.6em 0.3em; }
|
||||
.string { color: green; }
|
||||
.number { color: darkorange; }
|
||||
.boolean { color: blue; }
|
||||
.null { color: magenta; }
|
||||
.key { color: red; }
|
||||
.popover { max-width: 400px; max-height: 400px; overflow-y: auto;}
|
||||
.list-group.panel > .list-group-item {
|
||||
}
|
||||
.list-group-item:last-child {
|
||||
border-radius:0;
|
||||
}
|
||||
h4.panel-title a {
|
||||
font-weight:normal;
|
||||
font-size:14px;
|
||||
}
|
||||
h4.panel-title a .text-muted {
|
||||
font-size:12px;
|
||||
font-weight:normal;
|
||||
font-family: 'Verdana';
|
||||
}
|
||||
#sidebar {
|
||||
width: 220px;
|
||||
position: fixed;
|
||||
margin-left: -240px;
|
||||
overflow-y:auto;
|
||||
}
|
||||
#sidebar > .list-group {
|
||||
margin-bottom:0;
|
||||
}
|
||||
#sidebar > .list-group > a{
|
||||
text-indent:0;
|
||||
}
|
||||
#sidebar .child > a .tag{
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 11px;
|
||||
}
|
||||
#sidebar .child > a .pull-right{
|
||||
margin-left:3px;
|
||||
}
|
||||
#sidebar .child {
|
||||
border:1px solid #ddd;
|
||||
border-bottom:none;
|
||||
}
|
||||
#sidebar .child:last-child {
|
||||
border-bottom:1px solid #ddd;
|
||||
}
|
||||
#sidebar .child > a {
|
||||
border:0;
|
||||
min-height: 40px;
|
||||
}
|
||||
#sidebar .list-group a.current {
|
||||
background:#f5f5f5;
|
||||
}
|
||||
@media (max-width: 1620px){
|
||||
#sidebar {
|
||||
margin:0;
|
||||
}
|
||||
#accordion {
|
||||
padding-left:235px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px){
|
||||
#sidebar {
|
||||
display: none;
|
||||
}
|
||||
#accordion {
|
||||
padding-left:0px;
|
||||
}
|
||||
}
|
||||
.label-primary {
|
||||
background-color: #248aff;
|
||||
}
|
||||
.docs-list .panel .panel-body .table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Fixed navbar -->
|
||||
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="./" target="_blank">{$config.title}</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<form class="navbar-form navbar-right">
|
||||
<div class="form-group">
|
||||
Token:
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Tokentips}" placeholder="token" id="token" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
Apiurl:
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input id="apiUrl" type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Apiurltips}" placeholder="https://api.mydomain.com" value="{$config.apiurl}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-success btn-sm" data-toggle="tooltip" title="{$lang.Savetips}" id="save_data">
|
||||
<span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<!-- menu -->
|
||||
<div id="sidebar">
|
||||
<div class="list-group panel">
|
||||
{foreach name="docsList" id="docs"}
|
||||
<a href="#{$key}" class="list-group-item" data-toggle="collapse" data-parent="#sidebar">{$key} <i class="fa fa-caret-down"></i></a>
|
||||
<div class="child collapse" id="{$key}">
|
||||
{foreach name="docs" id="api" }
|
||||
<a href="javascript:;" data-id="{$api.id}" class="list-group-item">{$api.title}
|
||||
<span class="tag">
|
||||
{if $api.needRight}
|
||||
<span class="label label-danger pull-right">鉴</span>
|
||||
{/if}
|
||||
{if $api.needLogin}
|
||||
<span class="label label-success pull-right noneedlogin">登</span>
|
||||
{/if}
|
||||
</span>
|
||||
</a>
|
||||
{/foreach}
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-group docs-list" id="accordion">
|
||||
{foreach name="docsList" id="docs"}
|
||||
<h2>{$key}</h2>
|
||||
<hr>
|
||||
{foreach name="docs" id="api" }
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="heading-{$api.id}">
|
||||
<h4 class="panel-title">
|
||||
<span class="label {$api.methodLabel}">{$api.method|strtoupper}</span>
|
||||
<a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.title} <span class="text-muted">{$api.route}</span></a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapseOne{$api.id}" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" id="doctab{$api.id}">
|
||||
<li class="active"><a href="#info{$api.id}" data-toggle="tab">{$lang.Info}</a></li>
|
||||
<li><a href="#sandbox{$api.id}" data-toggle="tab">{$lang.Sandbox}</a></li>
|
||||
<li><a href="#sample{$api.id}" data-toggle="tab">{$lang.Sampleoutput}</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
|
||||
<div class="tab-pane active" id="info{$api.id}">
|
||||
<div class="well">
|
||||
{$api.summary}
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{$lang.Authorization}</strong></div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{$lang.NeedLogin}</td>
|
||||
<td>{$api.needLogin?'是':'否'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{$lang.NeedRight}</td>
|
||||
<td>{$api.needRight?'是':'否'}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{$lang.Headers}</strong></div>
|
||||
<div class="panel-body">
|
||||
{if $api.headersList}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{$lang.Name}</th>
|
||||
<th>{$lang.Type}</th>
|
||||
<th>{$lang.Required}</th>
|
||||
<th>{$lang.Description}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach name="api['headersList']" id="header"}
|
||||
<tr>
|
||||
<td>{$header.name}</td>
|
||||
<td>{$header.type}</td>
|
||||
<td>{$header.required?'是':'否'}</td>
|
||||
<td>{$header.description}</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
{else /}
|
||||
无
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
|
||||
<div class="panel-body">
|
||||
{if $api.paramsList}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{$lang.Name}</th>
|
||||
<th>{$lang.Type}</th>
|
||||
<th>{$lang.Required}</th>
|
||||
<th>{$lang.Description}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach name="api['paramsList']" id="param"}
|
||||
<tr>
|
||||
<td>{$param.name}</td>
|
||||
<td>{$param.type}</td>
|
||||
<td>{:$param.required?'是':'否'}</td>
|
||||
<td>{$param.description}</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
{else /}
|
||||
无
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{$lang.Body}</strong></div>
|
||||
<div class="panel-body">
|
||||
{$api.body|default='无'}
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- #info -->
|
||||
|
||||
<div class="tab-pane" id="sandbox{$api.id}">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{if $api.headersList}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{$lang.Headers}</strong></div>
|
||||
<div class="panel-body">
|
||||
<div class="headers">
|
||||
{foreach name="api['headersList']" id="param"}
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="{$param.name}">{$param.name}</label>
|
||||
<input type="{$param.type}" class="form-control input-sm" id="{$param.name}" {if $param.required}required{/if} placeholder="{$param.description} - Ex: {$param.sample}" name="{$param.name}">
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{$lang.Parameters}</strong>
|
||||
<div class="pull-right">
|
||||
<a href="javascript:" class="btn btn-xs btn-info btn-append">追加</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form enctype="application/x-www-form-urlencoded" role="form" action="{$api.route}" method="{$api.method}" name="form{$api.id}" id="form{$api.id}">
|
||||
{if $api.paramsList}
|
||||
{foreach name="api['paramsList']" id="param"}
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="{$param.name}">{$param.name}</label>
|
||||
<input type="{$param.type}" class="form-control input-sm" id="{$param.name}" {if $param.required}required{/if} placeholder="{$param.description}{if $param.sample} - 例: {$param.sample}{/if}" name="{$param.name}">
|
||||
</div>
|
||||
{/foreach}
|
||||
{else /}
|
||||
<div class="form-group">
|
||||
无
|
||||
</div>
|
||||
{/if}
|
||||
<div class="form-group form-group-submit">
|
||||
<button type="submit" class="btn btn-success send" rel="{$api.id}">{$lang.Send}</button>
|
||||
<button type="reset" class="btn btn-info" rel="{$api.id}">{$lang.Reset}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{$lang.Response}</strong></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="overflow-x:auto">
|
||||
<pre id="response_headers{$api.id}"></pre>
|
||||
<pre id="response{$api.id}"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{$lang.ReturnParameters}</strong></div>
|
||||
<div class="panel-body">
|
||||
{if $api.returnParamsList}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{$lang.Name}</th>
|
||||
<th>{$lang.Type}</th>
|
||||
<th>{$lang.Description}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach name="api['returnParamsList']" id="param"}
|
||||
<tr>
|
||||
<td>{$param.name}</td>
|
||||
<td>{$param.type}</td>
|
||||
<td>{$param.description}</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
{else /}
|
||||
无
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- #sandbox -->
|
||||
|
||||
<div class="tab-pane" id="sample{$api.id}">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<pre id="sample_response{$api.id}">{$api.return|default='无'}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- #sample -->
|
||||
|
||||
</div><!-- .tab-content -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="row mt0 footer">
|
||||
<div class="col-md-6" align="left">
|
||||
|
||||
</div>
|
||||
<div class="col-md-6" align="right">
|
||||
Generated on {:date('Y-m-d H:i:s')} <a href="./" target="_blank">{$config.sitename}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /container -->
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
|
||||
|
||||
<!-- Bootstrap Core JavaScript -->
|
||||
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function syntaxHighlight(json) {
|
||||
if (typeof json != 'string') {
|
||||
json = JSON.stringify(json, undefined, 2);
|
||||
}
|
||||
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
||||
var cls = 'number';
|
||||
if (/^"/.test(match)) {
|
||||
if (/:$/.test(match)) {
|
||||
cls = 'key';
|
||||
} else {
|
||||
cls = 'string';
|
||||
}
|
||||
} else if (/true|false/.test(match)) {
|
||||
cls = 'boolean';
|
||||
} else if (/null/.test(match)) {
|
||||
cls = 'null';
|
||||
}
|
||||
return '<span class="' + cls + '">' + match + '</span>';
|
||||
});
|
||||
}
|
||||
|
||||
function prepareStr(str) {
|
||||
try {
|
||||
return syntaxHighlight(JSON.stringify(JSON.parse(str.replace(/'/g, '"')), null, 2));
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
var storage = (function () {
|
||||
var uid = new Date;
|
||||
var storage;
|
||||
var result;
|
||||
try {
|
||||
(storage = window.localStorage).setItem(uid, uid);
|
||||
result = storage.getItem(uid) == uid;
|
||||
storage.removeItem(uid);
|
||||
return result && storage;
|
||||
} catch (exception) {
|
||||
}
|
||||
}());
|
||||
|
||||
$.fn.serializeObject = function ()
|
||||
{
|
||||
var o = {};
|
||||
var a = this.serializeArray();
|
||||
$.each(a, function () {
|
||||
if (!this.value) {
|
||||
return;
|
||||
}
|
||||
if (o[this.name] !== undefined) {
|
||||
if (!o[this.name].push) {
|
||||
o[this.name] = [o[this.name]];
|
||||
}
|
||||
o[this.name].push(this.value || '');
|
||||
} else {
|
||||
o[this.name] = this.value || '';
|
||||
}
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
if (storage) {
|
||||
storage.getItem('token') && $('#token').val(storage.getItem('token'));
|
||||
storage.getItem('apiUrl') && $('#apiUrl').val(storage.getItem('apiUrl'));
|
||||
}
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
placement: 'bottom'
|
||||
});
|
||||
|
||||
$(window).on("resize", function(){
|
||||
$("#sidebar").css("max-height", $(window).height()-80);
|
||||
});
|
||||
|
||||
$(window).trigger("resize");
|
||||
|
||||
$(document).on("click", "#sidebar .list-group > .list-group-item", function(){
|
||||
$("#sidebar .list-group > .list-group-item").removeClass("current");
|
||||
$(this).addClass("current");
|
||||
});
|
||||
$(document).on("click", "#sidebar .child a", function(){
|
||||
var heading = $("#heading-"+$(this).data("id"));
|
||||
if(!heading.next().hasClass("in")){
|
||||
$("a", heading).trigger("click");
|
||||
}
|
||||
$("html,body").animate({scrollTop:heading.offset().top-70});
|
||||
});
|
||||
|
||||
$('code[id^=response]').hide();
|
||||
|
||||
$.each($('pre[id^=sample_response],pre[id^=sample_post_body]'), function () {
|
||||
if ($(this).html() == 'NA') {
|
||||
return;
|
||||
}
|
||||
var str = prepareStr($(this).html());
|
||||
$(this).html(str);
|
||||
});
|
||||
|
||||
$("[data-toggle=popover]").popover({placement: 'right'});
|
||||
|
||||
$('[data-toggle=popover]').on('shown.bs.popover', function () {
|
||||
var $sample = $(this).parent().find(".popover-content"),
|
||||
str = $(this).data('content');
|
||||
if (typeof str == "undefined" || str === "") {
|
||||
return;
|
||||
}
|
||||
var str = prepareStr(str);
|
||||
$sample.html('<pre>' + str + '</pre>');
|
||||
});
|
||||
|
||||
$(document).on('click', '#save_data', function (e) {
|
||||
if (storage) {
|
||||
storage.setItem('token', $('#token').val());
|
||||
storage.setItem('apiUrl', $('#apiUrl').val());
|
||||
} else {
|
||||
alert('Your browser does not support local storage');
|
||||
}
|
||||
});
|
||||
$(document).on('click', '.btn-append', function (e) {
|
||||
$($("#appendtpl").html()).insertBefore($(this).closest(".panel").find(".form-group-submit"));
|
||||
return false;
|
||||
});
|
||||
$(document).on('click', '.btn-remove', function (e) {
|
||||
$(this).closest(".form-group").remove();
|
||||
return false;
|
||||
});
|
||||
$(document).on('keyup', '.input-custom-name', function (e) {
|
||||
$(this).closest(".row").find(".input-custom-value").attr("name", $(this).val());
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on('click', '.send', function (e) {
|
||||
e.preventDefault();
|
||||
var form = $(this).closest('form');
|
||||
//added /g to get all the matched params instead of only first
|
||||
var matchedParamsInRoute = $(form).attr('action').match(/[^{]+(?=\})/g);
|
||||
var theId = $(this).attr('rel');
|
||||
//keep a copy of action attribute in order to modify the copy
|
||||
//instead of the initial attribute
|
||||
var url = $(form).attr('action');
|
||||
var method = $(form).prop('method').toLowerCase() || 'get';
|
||||
|
||||
var formData = new FormData();
|
||||
|
||||
$(form).find('input').each(function (i, input) {
|
||||
if ($(input).attr('type').toLowerCase() == 'file') {
|
||||
formData.append($(input).attr('name'), $(input)[0].files[0]);
|
||||
method = 'post';
|
||||
} else {
|
||||
formData.append($(input).attr('name'), $(input).val())
|
||||
}
|
||||
});
|
||||
|
||||
var index, key, value;
|
||||
|
||||
if (matchedParamsInRoute) {
|
||||
var params = {};
|
||||
formData.forEach(function(value, key){
|
||||
params[key] = value;
|
||||
});
|
||||
for (index = 0; index < matchedParamsInRoute.length; ++index) {
|
||||
try {
|
||||
key = matchedParamsInRoute[index];
|
||||
value = params[key];
|
||||
if (typeof value == "undefined")
|
||||
value = "";
|
||||
url = url.replace("\{" + key + "\}", value);
|
||||
formData.delete(key);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var headers = {};
|
||||
|
||||
var token = $('#token').val();
|
||||
if (token.length > 0) {
|
||||
headers['token'] = token;
|
||||
}
|
||||
|
||||
$("#sandbox" + theId + " .headers input[type=text]").each(function () {
|
||||
val = $(this).val();
|
||||
if (val.length > 0) {
|
||||
headers[$(this).prop('name')] = val;
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: $('#apiUrl').val() + url,
|
||||
data: method == 'get' ? $(form).serialize() : formData,
|
||||
type: method,
|
||||
dataType: 'json',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
headers: headers,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
success: function (data, textStatus, xhr) {
|
||||
if (typeof data === 'object') {
|
||||
var str = JSON.stringify(data, null, 2);
|
||||
$('#response' + theId).html(syntaxHighlight(str));
|
||||
} else {
|
||||
$('#response' + theId).html(data || '');
|
||||
}
|
||||
$('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
|
||||
$('#response' + theId).show();
|
||||
},
|
||||
error: function (xhr, textStatus, error) {
|
||||
try {
|
||||
var str = JSON.stringify($.parseJSON(xhr.responseText), null, 2);
|
||||
} catch (e) {
|
||||
var str = xhr.responseText;
|
||||
}
|
||||
$('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
|
||||
$('#response' + theId).html(syntaxHighlight(str));
|
||||
$('#response' + theId).show();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script type="text/html" id="appendtpl">
|
||||
<div class="form-group">
|
||||
<label class="control-label">自定义</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<input type="text" class="form-control input-sm input-custom-name" placeholder="名称">
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<input type="text" class="form-control input-sm input-custom-value" placeholder="值">
|
||||
</div>
|
||||
<div class="col-xs-2 text-center">
|
||||
<a href="javascript:" class="btn btn-sm btn-danger btn-remove">删除</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
|
||||
{%addList%}
|
||||
<div class="form-group layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
|
||||
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace {%controllerNamespace%};
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* {%tableComment%}
|
||||
*
|
||||
* @icon {%iconName%}
|
||||
*/
|
||||
class {%controllerName%} extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* {%modelName%}模型对象
|
||||
* @var \{%modelNamespace%}\{%modelName%}
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \{%modelNamespace%}\{%modelName%};
|
||||
{%controllerAssignList%}
|
||||
}
|
||||
|
||||
{%controllerImport%}
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
{%controllerIndex%}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
|
||||
{%editList%}
|
||||
<div class="form-group layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
|
||||
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -0,0 +1,6 @@
|
||||
|
||||
<div class="checkbox">
|
||||
{foreach name="{%fieldList%}" item="vo"}
|
||||
<label for="{%fieldName%}-{$key}"><input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="checkbox" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} /> {$vo}</label>
|
||||
{/foreach}
|
||||
</div>
|
@ -0,0 +1,20 @@
|
||||
|
||||
<table class="table fieldlist" data-name="{%fieldName%}" data-template="{%fieldName%}tpl">
|
||||
<tr>
|
||||
{%theadList%}
|
||||
<td width="90">{:__('Operate')}</td>
|
||||
</tr>
|
||||
<tr><td colspan="{%colspan%}">
|
||||
<a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a>
|
||||
<textarea name="{%fieldName%}" class="form-control hide" cols="30" rows="5">{%fieldValue%}</textarea>
|
||||
</td></tr>
|
||||
</table>
|
||||
<script type="text/html" id="{%fieldName%}tpl">
|
||||
<tr>
|
||||
{%tbodyList%}
|
||||
<td width="90">
|
||||
<span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span>
|
||||
<span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span>
|
||||
</td>
|
||||
</tr>
|
||||
</script>
|
@ -0,0 +1,10 @@
|
||||
|
||||
<dl class="fieldlist" data-name="{%fieldName%}">
|
||||
<dd>
|
||||
<ins>{:__('{%itemKey%}')}</ins>
|
||||
<ins>{:__('{%itemValue%}')}</ins>
|
||||
</dd>
|
||||
<dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
|
||||
<textarea name="{%fieldName%}" class="form-control hide" cols="30" rows="5">{%fieldValue%}</textarea>
|
||||
</dl>
|
||||
|
@ -0,0 +1,10 @@
|
||||
|
||||
<div class="panel-heading">
|
||||
{:build_heading(null,FALSE)}
|
||||
<ul class="nav nav-tabs" data-field="{%field%}">
|
||||
<li class="{:$Think.get.{%field%} === null ? 'active' : ''}"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li>
|
||||
{foreach name="{%fieldName%}List" item="vo"}
|
||||
<li class="{:$Think.get.{%field%} === (string)$key ? 'active' : ''}"><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li>
|
||||
{/foreach}
|
||||
</ul>
|
||||
</div>
|
@ -0,0 +1,6 @@
|
||||
|
||||
<div class="radio">
|
||||
{foreach name="{%fieldList%}" item="vo"}
|
||||
<label for="{%fieldName%}-{$key}"><input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="radio" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} /> {$vo}</label>
|
||||
{/foreach}
|
||||
</div>
|
@ -0,0 +1 @@
|
||||
<a class="btn btn-success btn-recyclebin btn-dialog {:$auth->check('{%controllerUrl%}/recyclebin')?'':'hide'}" href="{%controllerUrl%}/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>
|
@ -0,0 +1,6 @@
|
||||
|
||||
<select {%attrStr%}>
|
||||
{foreach name="{%fieldList%}" item="vo"}
|
||||
<option value="{$key}" {in name="key" value="{%selectedValue%}"}selected{/in}>{$vo}</option>
|
||||
{/foreach}
|
||||
</select>
|
@ -0,0 +1,5 @@
|
||||
|
||||
<input {%attrStr%} name="{%fieldName%}" type="hidden" value="{%fieldValue%}">
|
||||
<a href="javascript:;" data-toggle="switcher" class="btn-switcher" data-input-id="c-{%field%}" data-yes="{%fieldYes%}" data-no="{%fieldNo%}" >
|
||||
<i class="fa fa-toggle-on text-success {%fieldSwitchClass%} fa-2x"></i>
|
||||
</a>
|
@ -0,0 +1,35 @@
|
||||
<div class="panel panel-default panel-intro">
|
||||
{%headingHtml%}
|
||||
|
||||
<div class="panel-body">
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div class="tab-pane fade active in" id="one">
|
||||
<div class="widget-body no-padding">
|
||||
<div id="toolbar" class="toolbar">
|
||||
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
|
||||
<a href="javascript:;" class="btn btn-success btn-add {:$auth->check('{%controllerUrl%}/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
|
||||
<a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('{%controllerUrl%}/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
|
||||
<a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('{%controllerUrl%}/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
|
||||
{%importHtml%}
|
||||
|
||||
<div class="dropdown btn-group {:$auth->check('{%controllerUrl%}/multi')?'':'hide'}">
|
||||
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
|
||||
<ul class="dropdown-menu text-left" role="menu">
|
||||
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
|
||||
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{%recyclebinHtml%}
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
|
||||
data-operate-edit="{:$auth->check('{%controllerUrl%}/edit')}"
|
||||
data-operate-del="{:$auth->check('{%controllerUrl%}/del')}"
|
||||
width="100%">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,48 @@
|
||||
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
|
||||
|
||||
var Controller = {
|
||||
index: function () {
|
||||
// 初始化表格参数配置
|
||||
Table.api.init({
|
||||
extend: {
|
||||
index_url: '{%controllerUrl%}/index' + location.search,
|
||||
add_url: '{%controllerUrl%}/add',
|
||||
edit_url: '{%controllerUrl%}/edit',
|
||||
del_url: '{%controllerUrl%}/del',
|
||||
multi_url: '{%controllerUrl%}/multi',
|
||||
import_url: '{%controllerUrl%}/import',
|
||||
table: '{%table%}',
|
||||
}
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
// 初始化表格
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
pk: '{%pk%}',
|
||||
sortName: '{%order%}',{%fixedColumnsJs%}
|
||||
columns: [
|
||||
[
|
||||
{%javascriptList%}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
// 为表格绑定事件
|
||||
Table.api.bindevent(table);
|
||||
},{%recyclebinJs%}
|
||||
add: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
api: {
|
||||
bindevent: function () {
|
||||
Form.api.bindevent($("form[role=form]"));
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
{%langList%}
|
||||
];
|
@ -0,0 +1,8 @@
|
||||
|
||||
public function {%methodName%}($value, $data)
|
||||
{
|
||||
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
|
||||
$valueArr = explode(',', $value);
|
||||
$list = $this->{%listMethodName%}();
|
||||
return implode(',', array_intersect_key($list, array_flip($valueArr)));
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
|
||||
public function {%methodName%}($value, $data)
|
||||
{
|
||||
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
|
||||
return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
public function import()
|
||||
{
|
||||
parent::import();
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
|
||||
protected static function init()
|
||||
{
|
||||
self::afterInsert(function ($row) {
|
||||
$pk = $row->getPk();
|
||||
$row->getQuery()->where($pk, $row[$pk])->update(['{%order%}' => $row[$pk]]);
|
||||
});
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
|
||||
public function {%relationMethod%}s()
|
||||
{
|
||||
return $this->{%relationMode%}('{%relationClassName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}');
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
|
||||
public function {%relationMethod%}()
|
||||
{
|
||||
return $this->{%relationMode%}('{%relationClassName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}', [], 'LEFT')->setEagerlyType(0);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
|
||||
public function {%methodName%}($value, $data)
|
||||
{
|
||||
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
|
||||
$valueArr = explode(',', $value);
|
||||
$list = $this->{%listMethodName%}();
|
||||
return implode(',', array_intersect_key($list, array_flip($valueArr)));
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
public function {%methodName%}($value, $data)
|
||||
{
|
||||
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
|
||||
$list = $this->{%listMethodName%}();
|
||||
return isset($list[$value]) ? $list[$value] : '';
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
|
||||
recyclebin: function () {
|
||||
// 初始化表格参数配置
|
||||
Table.api.init({
|
||||
extend: {
|
||||
'dragsort_url': ''
|
||||
}
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
// 初始化表格
|
||||
table.bootstrapTable({
|
||||
url: '{%controllerUrl%}/recyclebin' + location.search,
|
||||
pk: 'id',
|
||||
sortName: 'id',
|
||||
columns: [
|
||||
[
|
||||
{checkbox: true},
|
||||
{field: 'id', title: __('Id')},{%recyclebinTitleJs%}
|
||||
{
|
||||
field: '{%deleteTimeField%}',
|
||||
title: __('Deletetime'),
|
||||
operate: 'RANGE',
|
||||
addclass: 'datetimerange',
|
||||
formatter: Table.api.formatter.datetime
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
width: '130px',
|
||||
title: __('Operate'),
|
||||
table: table,
|
||||
events: Table.api.events.operate,
|
||||
buttons: [
|
||||
{
|
||||
name: 'Restore',
|
||||
text: __('Restore'),
|
||||
classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
|
||||
icon: 'fa fa-rotate-left',
|
||||
url: '{%controllerUrl%}/restore',
|
||||
refresh: true
|
||||
},
|
||||
{
|
||||
name: 'Destroy',
|
||||
text: __('Destroy'),
|
||||
classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
|
||||
icon: 'fa fa-times',
|
||||
url: '{%controllerUrl%}/destroy',
|
||||
refresh: true
|
||||
}
|
||||
],
|
||||
formatter: Table.api.formatter.operate
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
// 为表格绑定事件
|
||||
Table.api.bindevent(table);
|
||||
},
|
@ -0,0 +1,7 @@
|
||||
|
||||
public function {%methodName%}($value, $data)
|
||||
{
|
||||
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
|
||||
$list = $this->{%listMethodName%}();
|
||||
return isset($list[$value]) ? $list[$value] : '';
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace {%modelNamespace%};
|
||||
|
||||
use think\Model;
|
||||
{%softDeleteClassPath%}
|
||||
|
||||
class {%modelName%} extends Model
|
||||
{
|
||||
|
||||
{%softDelete%}
|
||||
|
||||
{%modelConnection%}
|
||||
|
||||
// 表名
|
||||
protected ${%modelTableType%} = '{%modelTableTypeName%}';
|
||||
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = {%modelAutoWriteTimestamp%};
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = {%createTime%};
|
||||
protected $updateTime = {%updateTime%};
|
||||
protected $deleteTime = {%deleteTime%};
|
||||
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
{%appendAttrList%}
|
||||
];
|
||||
|
||||
{%modelInit%}
|
||||
|
||||
{%getEnumList%}
|
||||
|
||||
{%getAttrList%}
|
||||
|
||||
{%setAttrList%}
|
||||
|
||||
{%relationMethodList%}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<div class="panel panel-default panel-intro">
|
||||
{:build_heading()}
|
||||
|
||||
<div class="panel-body">
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div class="tab-pane fade active in" id="one">
|
||||
<div class="widget-body no-padding">
|
||||
<div id="toolbar" class="toolbar">
|
||||
{:build_toolbar('refresh')}
|
||||
<a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('{%controllerUrl%}/restore')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
|
||||
<a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('{%controllerUrl%}/destroy')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
|
||||
<a class="btn btn-success btn-restoreall {:$auth->check('{%controllerUrl%}/restore')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
|
||||
<a class="btn btn-danger btn-destroyall {:$auth->check('{%controllerUrl%}/destroy')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover"
|
||||
data-operate-restore="{:$auth->check('{%controllerUrl%}/restore')}"
|
||||
data-operate-destroy="{:$auth->check('{%controllerUrl%}/destroy')}"
|
||||
width="100%">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace {%modelNamespace%};
|
||||
|
||||
use think\Model;
|
||||
|
||||
class {%relationName%} extends Model
|
||||
{
|
||||
// 表名
|
||||
protected ${%relationTableType%} = '{%relationTableTypeName%}';
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace {%validateNamespace%};
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class {%validateName%} extends Validate
|
||||
{
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
protected $rule = [
|
||||
];
|
||||
/**
|
||||
* 提示消息
|
||||
*/
|
||||
protected $message = [
|
||||
];
|
||||
/**
|
||||
* 验证场景
|
||||
*/
|
||||
protected $scene = [
|
||||
'add' => [],
|
||||
'edit' => [],
|
||||
];
|
||||
|
||||
}
|
@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\command;
|
||||
|
||||
use fast\Random;
|
||||
use PDO;
|
||||
use think\Config;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\Lang;
|
||||
use think\Request;
|
||||
use think\View;
|
||||
|
||||
class Install extends Command
|
||||
{
|
||||
protected $model = null;
|
||||
/**
|
||||
* @var \think\View 视图类实例
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var \think\Request Request 实例
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$config = Config::get('database');
|
||||
$this
|
||||
->setName('install')
|
||||
->addOption('hostname', 'a', Option::VALUE_OPTIONAL, 'mysql hostname', $config['hostname'])
|
||||
->addOption('hostport', 'o', Option::VALUE_OPTIONAL, 'mysql hostport', $config['hostport'])
|
||||
->addOption('database', 'd', Option::VALUE_OPTIONAL, 'mysql database', $config['database'])
|
||||
->addOption('prefix', 'r', Option::VALUE_OPTIONAL, 'table prefix', $config['prefix'])
|
||||
->addOption('username', 'u', Option::VALUE_OPTIONAL, 'mysql username', $config['username'])
|
||||
->addOption('password', 'p', Option::VALUE_OPTIONAL, 'mysql password', $config['password'])
|
||||
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', false)
|
||||
->setDescription('New installation of FastAdmin');
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令行安装
|
||||
*/
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
define('INSTALL_PATH', APP_PATH . 'admin' . DS . 'command' . DS . 'Install' . DS);
|
||||
// 覆盖安装
|
||||
$force = $input->getOption('force');
|
||||
$hostname = $input->getOption('hostname');
|
||||
$hostport = $input->getOption('hostport');
|
||||
$database = $input->getOption('database');
|
||||
$prefix = $input->getOption('prefix');
|
||||
$username = $input->getOption('username');
|
||||
$password = $input->getOption('password');
|
||||
|
||||
$installLockFile = INSTALL_PATH . "install.lock";
|
||||
if (is_file($installLockFile) && !$force) {
|
||||
throw new Exception("\nFastAdmin already installed!\nIf you need to reinstall again, use the parameter --force=true ");
|
||||
}
|
||||
|
||||
$adminUsername = 'admin';
|
||||
$adminPassword = Random::alnum(10);
|
||||
$adminEmail = 'admin@admin.com';
|
||||
$siteName = __('My Website');
|
||||
|
||||
$adminName = $this->installation($hostname, $hostport, $database, $username, $password, $prefix, $adminUsername, $adminPassword, $adminEmail, $siteName);
|
||||
if ($adminName) {
|
||||
$output->highlight("Admin url:http://www.yoursite.com/{$adminName}");
|
||||
}
|
||||
|
||||
$output->highlight("Admin username:{$adminUsername}");
|
||||
$output->highlight("Admin password:{$adminPassword}");
|
||||
|
||||
\think\Cache::rm('__menu__');
|
||||
|
||||
$output->info("Install Successed!");
|
||||
}
|
||||
|
||||
/**
|
||||
* PC端安装
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->view = View::instance(Config::get('template'), Config::get('view_replace_str'));
|
||||
$this->request = Request::instance();
|
||||
|
||||
define('INSTALL_PATH', APP_PATH . 'admin' . DS . 'command' . DS . 'Install' . DS);
|
||||
|
||||
$lang = $this->request->langset();
|
||||
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
|
||||
|
||||
if (!$lang || in_array($lang, ['zh-cn', 'zh-hans-cn'])) {
|
||||
Lang::load(INSTALL_PATH . 'zh-cn.php');
|
||||
}
|
||||
|
||||
$installLockFile = INSTALL_PATH . "install.lock";
|
||||
|
||||
if (is_file($installLockFile)) {
|
||||
echo __('The system has been installed. If you need to reinstall, please remove %s first', 'install.lock');
|
||||
exit;
|
||||
}
|
||||
$output = function ($code, $msg, $url = null, $data = null) {
|
||||
return json(['code' => $code, 'msg' => $msg, 'url' => $url, 'data' => $data]);
|
||||
};
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
$mysqlHostname = $this->request->post('mysqlHostname', '127.0.0.1');
|
||||
$mysqlHostport = $this->request->post('mysqlHostport', '3306');
|
||||
$hostArr = explode(':', $mysqlHostname);
|
||||
if (count($hostArr) > 1) {
|
||||
$mysqlHostname = $hostArr[0];
|
||||
$mysqlHostport = $hostArr[1];
|
||||
}
|
||||
$mysqlUsername = $this->request->post('mysqlUsername', 'root');
|
||||
$mysqlPassword = $this->request->post('mysqlPassword', '');
|
||||
$mysqlDatabase = $this->request->post('mysqlDatabase', '');
|
||||
$mysqlPrefix = $this->request->post('mysqlPrefix', 'fa_');
|
||||
$adminUsername = $this->request->post('adminUsername', 'admin');
|
||||
$adminPassword = $this->request->post('adminPassword', '');
|
||||
$adminPasswordConfirmation = $this->request->post('adminPasswordConfirmation', '');
|
||||
$adminEmail = $this->request->post('adminEmail', 'admin@admin.com');
|
||||
$siteName = $this->request->post('siteName', __('My Website'));
|
||||
|
||||
if ($adminPassword !== $adminPasswordConfirmation) {
|
||||
return $output(0, __('The two passwords you entered did not match'));
|
||||
}
|
||||
|
||||
$adminName = '';
|
||||
try {
|
||||
$adminName = $this->installation($mysqlHostname, $mysqlHostport, $mysqlDatabase, $mysqlUsername, $mysqlPassword, $mysqlPrefix, $adminUsername, $adminPassword, $adminEmail, $siteName);
|
||||
} catch (\PDOException $e) {
|
||||
throw new Exception($e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
return $output(0, $e->getMessage());
|
||||
}
|
||||
return $output(1, __('Install Successed'), null, ['adminName' => $adminName]);
|
||||
}
|
||||
$errInfo = '';
|
||||
try {
|
||||
$this->checkenv();
|
||||
} catch (\Exception $e) {
|
||||
$errInfo = $e->getMessage();
|
||||
}
|
||||
return $this->view->fetch(INSTALL_PATH . "install.html", ['errInfo' => $errInfo]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行安装
|
||||
*/
|
||||
protected function installation($mysqlHostname, $mysqlHostport, $mysqlDatabase, $mysqlUsername, $mysqlPassword, $mysqlPrefix, $adminUsername, $adminPassword, $adminEmail = null, $siteName = null)
|
||||
{
|
||||
$this->checkenv();
|
||||
|
||||
if ($mysqlDatabase == '') {
|
||||
throw new Exception(__('Please input correct database'));
|
||||
}
|
||||
if (!preg_match("/^\w{3,12}$/", $adminUsername)) {
|
||||
throw new Exception(__('Please input correct username'));
|
||||
}
|
||||
if (!preg_match("/^[\S]{6,16}$/", $adminPassword)) {
|
||||
throw new Exception(__('Please input correct password'));
|
||||
}
|
||||
$weakPasswordArr = ['123456', '12345678', '123456789', '654321', '111111', '000000', 'password', 'qwerty', 'abc123', '1qaz2wsx'];
|
||||
if (in_array($adminPassword, $weakPasswordArr)) {
|
||||
throw new Exception(__('Password is too weak'));
|
||||
}
|
||||
if ($siteName == '' || preg_match("/fast" . "admin/i", $siteName)) {
|
||||
throw new Exception(__('Please input correct website'));
|
||||
}
|
||||
|
||||
$sql = file_get_contents(INSTALL_PATH . 'fastadmin.sql');
|
||||
|
||||
$sql = str_replace("`fa_", "`{$mysqlPrefix}", $sql);
|
||||
|
||||
// 先尝试能否自动创建数据库
|
||||
$config = Config::get('database');
|
||||
try {
|
||||
$pdo = new PDO("{$config['type']}:host={$mysqlHostname}" . ($mysqlHostport ? ";port={$mysqlHostport}" : ''), $mysqlUsername, $mysqlPassword);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->query("CREATE DATABASE IF NOT EXISTS `{$mysqlDatabase}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;");
|
||||
|
||||
// 连接install命令中指定的数据库
|
||||
$instance = Db::connect([
|
||||
'type' => "{$config['type']}",
|
||||
'hostname' => "{$mysqlHostname}",
|
||||
'hostport' => "{$mysqlHostport}",
|
||||
'database' => "{$mysqlDatabase}",
|
||||
'username' => "{$mysqlUsername}",
|
||||
'password' => "{$mysqlPassword}",
|
||||
'prefix' => "{$mysqlPrefix}",
|
||||
]);
|
||||
|
||||
// 查询一次SQL,判断连接是否正常
|
||||
$instance->execute("SELECT 1");
|
||||
|
||||
// 调用原生PDO对象进行批量查询
|
||||
$instance->getPdo()->exec($sql);
|
||||
} catch (\PDOException $e) {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
// 后台入口文件
|
||||
$adminFile = ROOT_PATH . 'public' . DS . 'admin.php';
|
||||
|
||||
// 数据库配置文件
|
||||
$dbConfigFile = APP_PATH . 'database.php';
|
||||
$dbConfigText = @file_get_contents($dbConfigFile);
|
||||
$callback = function ($matches) use ($mysqlHostname, $mysqlHostport, $mysqlUsername, $mysqlPassword, $mysqlDatabase, $mysqlPrefix) {
|
||||
$field = "mysql" . ucfirst($matches[1]);
|
||||
$replace = $$field;
|
||||
if ($matches[1] == 'hostport' && $mysqlHostport == 3306) {
|
||||
$replace = '';
|
||||
}
|
||||
return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),";
|
||||
};
|
||||
$dbConfigText = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $dbConfigText);
|
||||
|
||||
// 检测能否成功写入数据库配置
|
||||
$result = @file_put_contents($dbConfigFile, $dbConfigText);
|
||||
if (!$result) {
|
||||
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/database.php'));
|
||||
}
|
||||
|
||||
// 设置新的Token随机密钥key
|
||||
$oldTokenKey = config('token.key');
|
||||
$newTokenKey = \fast\Random::alnum(32);
|
||||
$coreConfigFile = CONF_PATH . 'config.php';
|
||||
$coreConfigText = @file_get_contents($coreConfigFile);
|
||||
$coreConfigText = preg_replace("/'key'(\s+)=>(\s+)'{$oldTokenKey}'/", "'key'\$1=>\$2'{$newTokenKey}'", $coreConfigText);
|
||||
|
||||
$result = @file_put_contents($coreConfigFile, $coreConfigText);
|
||||
if (!$result) {
|
||||
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/config.php'));
|
||||
}
|
||||
|
||||
$avatar = request()->domain() . '/assets/img/avatar.png';
|
||||
// 变更默认管理员密码
|
||||
$adminPassword = $adminPassword ? $adminPassword : Random::alnum(8);
|
||||
$adminEmail = $adminEmail ? $adminEmail : "admin@admin.com";
|
||||
$newSalt = substr(md5(uniqid(true)), 0, 6);
|
||||
$newPassword = md5(md5($adminPassword) . $newSalt);
|
||||
$data = ['username' => $adminUsername, 'email' => $adminEmail, 'avatar' => $avatar, 'password' => $newPassword, 'salt' => $newSalt];
|
||||
$instance->name('admin')->where('username', 'admin')->update($data);
|
||||
|
||||
// 变更前台默认用户的密码,随机生成
|
||||
$newSalt = substr(md5(uniqid(true)), 0, 6);
|
||||
$newPassword = md5(md5(Random::alnum(8)) . $newSalt);
|
||||
$instance->name('user')->where('username', 'admin')->update(['avatar' => $avatar, 'password' => $newPassword, 'salt' => $newSalt]);
|
||||
|
||||
// 修改后台入口
|
||||
$adminName = '';
|
||||
if (is_file($adminFile)) {
|
||||
$adminName = Random::alpha(10) . '.php';
|
||||
rename($adminFile, ROOT_PATH . 'public' . DS . $adminName);
|
||||
}
|
||||
|
||||
//修改站点名称
|
||||
if ($siteName != config('site.name')) {
|
||||
$instance->name('config')->where('name', 'name')->update(['value' => $siteName]);
|
||||
$siteConfigFile = CONF_PATH . 'extra' . DS . 'site.php';
|
||||
$siteConfig = include $siteConfigFile;
|
||||
$configList = $instance->name("config")->select();
|
||||
foreach ($configList as $k => $value) {
|
||||
if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'])) {
|
||||
$value['value'] = is_array($value['value']) ? $value['value'] : explode(',', $value['value']);
|
||||
}
|
||||
if ($value['type'] == 'array') {
|
||||
$value['value'] = (array)json_decode($value['value'], true);
|
||||
}
|
||||
$siteConfig[$value['name']] = $value['value'];
|
||||
}
|
||||
$siteConfig['name'] = $siteName;
|
||||
file_put_contents($siteConfigFile, '<?php' . "\n\nreturn " . var_export_short($siteConfig) . ";\n");
|
||||
}
|
||||
|
||||
$installLockFile = INSTALL_PATH . "install.lock";
|
||||
//检测能否成功写入lock文件
|
||||
$result = @file_put_contents($installLockFile, 1);
|
||||
if (!$result) {
|
||||
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/admin/command/Install/install.lock'));
|
||||
}
|
||||
|
||||
try {
|
||||
//删除安装脚本
|
||||
@unlink(ROOT_PATH . 'public' . DS . 'install.php');
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
|
||||
return $adminName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测环境
|
||||
*/
|
||||
protected function checkenv()
|
||||
{
|
||||
// 检测目录是否存在
|
||||
$checkDirs = [
|
||||
'thinkphp',
|
||||
'vendor',
|
||||
'public' . DS . 'assets' . DS . 'libs'
|
||||
];
|
||||
|
||||
//数据库配置文件
|
||||
$dbConfigFile = APP_PATH . 'database.php';
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.1.0', '<')) {
|
||||
throw new Exception(__("The current version %s is too low, please use PHP 7.1 or higher", PHP_VERSION));
|
||||
}
|
||||
if (!extension_loaded("PDO")) {
|
||||
throw new Exception(__("PDO is not currently installed and cannot be installed"));
|
||||
}
|
||||
if (!is_really_writable($dbConfigFile)) {
|
||||
throw new Exception(__('The current permissions are insufficient to write the configuration file application/database.php'));
|
||||
}
|
||||
foreach ($checkDirs as $k => $v) {
|
||||
if (!is_dir(ROOT_PATH . $v)) {
|
||||
throw new Exception(__('Please go to the official website to download the full package or resource package and try to install'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,316 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>{:__('Installing FastAdmin')}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: #f1f6fd;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body, input, button {
|
||||
font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, 'Microsoft Yahei', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #7E96B3;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4e73df;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
font-weight: normal;
|
||||
color: #3C5675;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group .form-field:first-child input {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.form-group .form-field:last-child input {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.form-field input {
|
||||
background: #fff;
|
||||
margin: 0 0 2px;
|
||||
border: 2px solid transparent;
|
||||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||||
width: 100%;
|
||||
padding: 15px 15px 15px 180px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-field input:focus {
|
||||
border-color: #4e73df;
|
||||
background: #fff;
|
||||
color: #444;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
float: left;
|
||||
width: 160px;
|
||||
text-align: right;
|
||||
margin-right: -160px;
|
||||
position: relative;
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
button, .btn {
|
||||
background: #3C5675;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 15px 30px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
height: 52px;
|
||||
line-height: 52px;
|
||||
}
|
||||
|
||||
.form-buttons .btn {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#error, .error, #success, .success, #warmtips, .warmtips {
|
||||
background: #D83E3E;
|
||||
color: #fff;
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#success {
|
||||
background: #3C5675;
|
||||
}
|
||||
|
||||
#error a, .error a {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#warmtips {
|
||||
background: #ffcdcd;
|
||||
font-size: 14px;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
#warmtips a {
|
||||
background: #ffffff7a;
|
||||
display: block;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin-top: 10px;
|
||||
color: #e21a1a;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>
|
||||
<svg width="80px" height="96px" viewBox="0 0 768 830" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M64.433651,605.899968 C20.067302,536.265612 0,469.698785 0,389.731348 C0,174.488668 171.922656,0 384,0 C596.077344,0 768,174.488668 768,389.731348 C768,469.698785 747.932698,536.265612 703.566349,605.899968 C614.4,753.480595 441.6,870.4 384,870.4 C326.4,870.4 153.6,753.480595 64.433651,605.899968 L64.433651,605.899968 Z"
|
||||
id="body" fill="#4e73df"></path>
|
||||
<path d="M429.648991,190.816 L430.160991,190.816 L429.648991,190.816 L429.648991,190.816 Z M429.648991,156 L427.088991,156 C419.408991,157.024 411.728991,160.608 404.560991,168.8 L403.024991,170.848 L206.928991,429.92 C198.736991,441.184 197.712991,453.984 204.368991,466.784 C210.512991,478.048 222.288991,485.728 235.600991,485.728 L336.464991,486.24 L304.208991,673.632 C301.648991,689.504 310.352991,705.376 325.200991,712.032 C329.808991,714.08 334.416991,714.592 339.536991,714.592 C349.776991,714.592 358.992991,709.472 366.160991,700.256 L561.744991,419.168 C569.936991,407.904 570.960991,395.104 564.304991,382.304 C557.648991,369.504 547.408991,363.36 533.072991,363.36 L432.208991,363.36 L463.952991,199.008 C464.464991,196.448 464.976991,193.376 464.976991,190.816 C464.976991,171.872 449.104991,156 431.184991,156 L429.648991,156 L429.648991,156 Z"
|
||||
id="flash" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</h1>
|
||||
<h2>{:__('Installing FastAdmin')}</h2>
|
||||
<div>
|
||||
|
||||
<form method="post">
|
||||
{if $errInfo}
|
||||
<div class="error">
|
||||
{$errInfo}
|
||||
</div>
|
||||
{/if}
|
||||
<div id="error" style="display:none"></div>
|
||||
<div id="success" style="display:none"></div>
|
||||
<div id="warmtips" style="display:none"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-field">
|
||||
<label>{:__('Mysql Hostname')}</label>
|
||||
<input type="text" name="mysqlHostname" value="127.0.0.1" required="">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>{:__('Mysql Database')}</label>
|
||||
<input type="text" name="mysqlDatabase" value="" required="">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>{:__('Mysql Username')}</label>
|
||||
<input type="text" name="mysqlUsername" value="root" required="">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>{:__('Mysql Password')}</label>
|
||||
<input type="password" name="mysqlPassword">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>{:__('Mysql Prefix')}</label>
|
||||
<input type="text" name="mysqlPrefix" value="fa_">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>{:__('Mysql Hostport')}</label>
|
||||
<input type="number" name="mysqlHostport" value="3306">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-field">
|
||||
<label>{:__('Admin Username')}</label>
|
||||
<input name="adminUsername" value="admin" required=""/>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>{:__('Admin Email')}</label>
|
||||
<input name="adminEmail" value="admin@admin.com" required="">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>{:__('Admin Password')}</label>
|
||||
<input type="password" name="adminPassword" required="">
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>{:__('Repeat Password')}</label>
|
||||
<input type="password" name="adminPasswordConfirmation" required="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-field">
|
||||
<label>{:__('Website')}</label>
|
||||
<input type="text" name="siteName" value="{:__('My Website')}" required=""/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<!--@formatter:off-->
|
||||
<button type="submit" {:$errInfo?'disabled':''}>{:__('Install now')}</button>
|
||||
<!--@formatter:on-->
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$('form :input:first').select();
|
||||
|
||||
$('form').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var form = this;
|
||||
var $error = $("#error");
|
||||
var $success = $("#success");
|
||||
var $button = $(this).find('button')
|
||||
.text("{:__('Installing')}")
|
||||
.prop('disabled', true);
|
||||
$.ajax({
|
||||
url: "",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: $(this).serialize(),
|
||||
success: function (ret) {
|
||||
if (ret.code == 1) {
|
||||
var data = ret.data;
|
||||
$error.hide();
|
||||
$(".form-group", form).remove();
|
||||
$button.remove();
|
||||
$("#success").text(ret.msg).show();
|
||||
|
||||
$buttons = $(".form-buttons", form);
|
||||
$("<a class='btn' href='./'>{:__('Home')}</a>").appendTo($buttons);
|
||||
|
||||
if (typeof data.adminName !== 'undefined') {
|
||||
var url = location.href.replace(/install\.php/, data.adminName);
|
||||
$("#warmtips").html("{:__('Security tips')}" + '<a href="' + url + '">' + url + '</a>').show();
|
||||
$('<a class="btn" href="' + url + '" id="btn-admin" style="background:#4e73df">' + "{:__('Dashboard')}" + '</a>').appendTo($buttons);
|
||||
}
|
||||
localStorage.setItem("fastep", "installed");
|
||||
} else {
|
||||
$error.show().text(ret.msg);
|
||||
$button.prop('disabled', false).text("{:__('Install now')}");
|
||||
$("html,body").animate({
|
||||
scrollTop: 0
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
error: function (xhr) {
|
||||
$error.show().text(xhr.responseText);
|
||||
$button.prop('disabled', false).text("{:__('Install now')}");
|
||||
$("html,body").animate({
|
||||
scrollTop: 0
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,6 @@
|
||||
({
|
||||
cssIn: "{%cssBasePath%}{%cssBaseName%}.css",
|
||||
out: "{%cssBasePath%}{%cssBaseName%}.min.css",
|
||||
optimizeCss: "default",
|
||||
optimize: "{%optimize%}"
|
||||
})
|
@ -0,0 +1,11 @@
|
||||
({
|
||||
{%config%}
|
||||
,
|
||||
optimizeCss: "standard",
|
||||
optimize: "{%optimize%}", //可使用uglify|closure|none
|
||||
preserveLicenseComments: false,
|
||||
removeCombined: false,
|
||||
baseUrl: "{%jsBasePath%}", //JS文件所在的基础目录
|
||||
name: "{%jsBaseName%}", //来源文件,不包含后缀
|
||||
out: "{%jsBasePath%}{%jsBaseName%}.min.js" //目标文件
|
||||
});
|
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
use app\common\model\Category;
|
||||
use fast\Form;
|
||||
use fast\Tree;
|
||||
use think\Db;
|
||||
use think\Loader;
|
||||
|
||||
if (!function_exists('build_select')) {
|
||||
|
||||
/**
|
||||
* 生成下拉列表
|
||||
* @param string $name
|
||||
* @param mixed $options
|
||||
* @param mixed $selected
|
||||
* @param mixed $attr
|
||||
* @return string
|
||||
*/
|
||||
function build_select($name, $options, $selected = [], $attr = [])
|
||||
{
|
||||
$options = is_array($options) ? $options : explode(',', $options);
|
||||
$selected = is_array($selected) ? $selected : explode(',', $selected);
|
||||
return Form::select($name, $options, $selected, $attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('build_radios')) {
|
||||
|
||||
/**
|
||||
* 生成单选按钮组
|
||||
* @param string $name
|
||||
* @param array $list
|
||||
* @param mixed $selected
|
||||
* @return string
|
||||
*/
|
||||
function build_radios($name, $list = [], $selected = null)
|
||||
{
|
||||
$html = [];
|
||||
$selected = is_null($selected) ? key($list) : $selected;
|
||||
$selected = is_array($selected) ? $selected : explode(',', $selected);
|
||||
foreach ($list as $k => $v) {
|
||||
$html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::radio($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
|
||||
}
|
||||
return '<div class="radio">' . implode(' ', $html) . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('build_checkboxs')) {
|
||||
|
||||
/**
|
||||
* 生成复选按钮组
|
||||
* @param string $name
|
||||
* @param array $list
|
||||
* @param mixed $selected
|
||||
* @return string
|
||||
*/
|
||||
function build_checkboxs($name, $list = [], $selected = null)
|
||||
{
|
||||
$html = [];
|
||||
$selected = is_null($selected) ? [] : $selected;
|
||||
$selected = is_array($selected) ? $selected : explode(',', $selected);
|
||||
foreach ($list as $k => $v) {
|
||||
$html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::checkbox($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
|
||||
}
|
||||
return '<div class="checkbox">' . implode(' ', $html) . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('build_category_select')) {
|
||||
|
||||
/**
|
||||
* 生成分类下拉列表框
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param mixed $selected
|
||||
* @param array $attr
|
||||
* @param array $header
|
||||
* @return string
|
||||
*/
|
||||
function build_category_select($name, $type, $selected = null, $attr = [], $header = [])
|
||||
{
|
||||
$tree = Tree::instance();
|
||||
$tree->init(Category::getCategoryArray($type), 'pid');
|
||||
$categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
|
||||
$categorydata = $header ? $header : [];
|
||||
foreach ($categorylist as $k => $v) {
|
||||
$categorydata[$v['id']] = $v['name'];
|
||||
}
|
||||
$attr = array_merge(['id' => "c-{$name}", 'class' => 'form-control selectpicker'], $attr);
|
||||
return build_select($name, $categorydata, $selected, $attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('build_toolbar')) {
|
||||
|
||||
/**
|
||||
* 生成表格操作按钮栏
|
||||
* @param array $btns 按钮组
|
||||
* @param array $attr 按钮属性值
|
||||
* @return string
|
||||
*/
|
||||
function build_toolbar($btns = null, $attr = [])
|
||||
{
|
||||
$auth = \app\admin\library\Auth::instance();
|
||||
$controller = str_replace('.', '/', Loader::parseName(request()->controller()));
|
||||
$btns = $btns ? $btns : ['refresh', 'add', 'edit', 'del', 'import'];
|
||||
$btns = is_array($btns) ? $btns : explode(',', $btns);
|
||||
$index = array_search('delete', $btns);
|
||||
if ($index !== false) {
|
||||
$btns[$index] = 'del';
|
||||
}
|
||||
$btnAttr = [
|
||||
'refresh' => ['javascript:;', 'btn btn-primary btn-refresh', 'fa fa-refresh', '', __('Refresh')],
|
||||
'add' => ['javascript:;', 'btn btn-success btn-add', 'fa fa-plus', __('Add'), __('Add')],
|
||||
'edit' => ['javascript:;', 'btn btn-success btn-edit btn-disabled disabled', 'fa fa-pencil', __('Edit'), __('Edit')],
|
||||
'del' => ['javascript:;', 'btn btn-danger btn-del btn-disabled disabled', 'fa fa-trash', __('Delete'), __('Delete')],
|
||||
'import' => ['javascript:;', 'btn btn-info btn-import', 'fa fa-upload', __('Import'), __('Import')],
|
||||
];
|
||||
$btnAttr = array_merge($btnAttr, $attr);
|
||||
$html = [];
|
||||
foreach ($btns as $k => $v) {
|
||||
//如果未定义或没有权限
|
||||
if (!isset($btnAttr[$v]) || ($v !== 'refresh' && !$auth->check("{$controller}/{$v}"))) {
|
||||
continue;
|
||||
}
|
||||
list($href, $class, $icon, $text, $title) = $btnAttr[$v];
|
||||
//$extend = $v == 'import' ? 'id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"' : '';
|
||||
//$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '" ' . $extend . '><i class="' . $icon . '"></i> ' . $text . '</a>';
|
||||
if ($v == 'import') {
|
||||
$template = str_replace('/', '_', $controller);
|
||||
$download = '';
|
||||
if (file_exists("./template/{$template}.xlsx")) {
|
||||
$download .= "<li><a href=\"/template/{$template}.xlsx\" target=\"_blank\">XLSX模版</a></li>";
|
||||
}
|
||||
if (file_exists("./template/{$template}.xls")) {
|
||||
$download .= "<li><a href=\"/template/{$template}.xls\" target=\"_blank\">XLS模版</a></li>";
|
||||
}
|
||||
if (file_exists("./template/{$template}.csv")) {
|
||||
$download .= empty($download) ? '' : "<li class=\"divider\"></li>";
|
||||
$download .= "<li><a href=\"/template/{$template}.csv\" target=\"_blank\">CSV模版</a></li>";
|
||||
}
|
||||
$download .= empty($download) ? '' : "\n ";
|
||||
if (!empty($download)) {
|
||||
$html[] = <<<EOT
|
||||
<div class="btn-group">
|
||||
<button type="button" href="{$href}" class="btn btn-info btn-import" title="{$title}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="{$icon}"></i> {$text}</button>
|
||||
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" title="下载批量导入模版">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">{$download}</ul>
|
||||
</div>
|
||||
EOT;
|
||||
} else {
|
||||
$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="' . $icon . '"></i> ' . $text . '</a>';
|
||||
}
|
||||
} else {
|
||||
$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '"><i class="' . $icon . '"></i> ' . $text . '</a>';
|
||||
}
|
||||
}
|
||||
return implode(' ', $html);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('build_heading')) {
|
||||
|
||||
/**
|
||||
* 生成页面Heading
|
||||
*
|
||||
* @param string $path 指定的path
|
||||
* @return string
|
||||
*/
|
||||
function build_heading($path = null, $container = true)
|
||||
{
|
||||
$title = $content = '';
|
||||
if (is_null($path)) {
|
||||
$action = request()->action();
|
||||
$controller = str_replace('.', '/', Loader::parseName(request()->controller()));
|
||||
$path = strtolower($controller . ($action && $action != 'index' ? '/' . $action : ''));
|
||||
}
|
||||
// 根据当前的URI自动匹配父节点的标题和备注
|
||||
$data = Db::name('auth_rule')->where('name', $path)->field('title,remark')->find();
|
||||
if ($data) {
|
||||
$title = __($data['title']);
|
||||
$content = __($data['remark']);
|
||||
}
|
||||
if (!$content) {
|
||||
return '';
|
||||
}
|
||||
$result = '<div class="panel-lead"><em>' . $title . '</em>' . $content . '</div>';
|
||||
if ($container) {
|
||||
$result = '<div class="panel-heading">' . $result . '</div>';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
//配置文件
|
||||
return [
|
||||
'url_common_param' => true,
|
||||
'url_html_suffix' => '',
|
||||
'controller_auto_search' => true,
|
||||
];
|
@ -0,0 +1,453 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use fast\Http;
|
||||
use think\addons\AddonException;
|
||||
use think\addons\Service;
|
||||
use think\Cache;
|
||||
use think\Config;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 插件管理
|
||||
*
|
||||
* @icon fa fa-cube
|
||||
* @remark 可在线安装、卸载、禁用、启用、配置、升级插件,插件升级前请做好备份。
|
||||
*/
|
||||
class Addon extends Backend
|
||||
{
|
||||
protected $model = null;
|
||||
protected $noNeedRight = ['get_table_list'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
if (!$this->auth->isSuperAdmin() && in_array($this->request->action(), ['install', 'uninstall', 'local', 'upgrade', 'authorization', 'testdata'])) {
|
||||
$this->error(__('Access is allowed only to the super management group'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件列表
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$addons = get_addon_list();
|
||||
foreach ($addons as $k => &$v) {
|
||||
$config = get_addon_config($v['name']);
|
||||
$v['config'] = $config ? 1 : 0;
|
||||
$v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
|
||||
}
|
||||
$this->assignconfig(['addons' => $addons, 'api_url' => config('fastadmin.api_url'), 'faversion' => config('fastadmin.version'), 'domain' => request()->host(true)]);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置
|
||||
*/
|
||||
public function config($name = null)
|
||||
{
|
||||
$name = $name ? $name : $this->request->get("name");
|
||||
if (!$name) {
|
||||
$this->error(__('Parameter %s can not be empty', 'name'));
|
||||
}
|
||||
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
|
||||
$this->error(__('Addon name incorrect'));
|
||||
}
|
||||
$info = get_addon_info($name);
|
||||
$config = get_addon_fullconfig($name);
|
||||
if (!$info) {
|
||||
$this->error(__('Addon not exists'));
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$params = $this->request->post("row/a", [], 'trim');
|
||||
if ($params) {
|
||||
foreach ($config as $k => &$v) {
|
||||
if (isset($params[$v['name']])) {
|
||||
if ($v['type'] == 'array') {
|
||||
$params[$v['name']] = is_array($params[$v['name']]) ? $params[$v['name']] : (array)json_decode($params[$v['name']], true);
|
||||
$value = $params[$v['name']];
|
||||
} else {
|
||||
$value = is_array($params[$v['name']]) ? implode(',', $params[$v['name']]) : $params[$v['name']];
|
||||
}
|
||||
$v['value'] = $value;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$addon = get_addon_instance($name);
|
||||
//插件自定义配置实现逻辑
|
||||
if (method_exists($addon, 'config')) {
|
||||
$addon->config($name, $config);
|
||||
} else {
|
||||
//更新配置文件
|
||||
set_addon_fullconfig($name, $config);
|
||||
Service::refresh();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
$tips = [];
|
||||
$groupList = [];
|
||||
foreach ($config as $index => &$item) {
|
||||
//如果有设置分组
|
||||
if (isset($item['group']) && $item['group']) {
|
||||
if (!in_array($item['group'], $groupList)) {
|
||||
$groupList["custom" . (count($groupList) + 1)] = $item['group'];
|
||||
}
|
||||
}
|
||||
if ($item['name'] == '__tips__') {
|
||||
$tips = $item;
|
||||
unset($config[$index]);
|
||||
}
|
||||
}
|
||||
$groupList['other'] = '其它';
|
||||
$this->view->assign("groupList", $groupList);
|
||||
$this->view->assign("addon", ['info' => $info, 'config' => $config, 'tips' => $tips]);
|
||||
$configFile = ADDON_PATH . $name . DS . 'config.html';
|
||||
$viewFile = is_file($configFile) ? $configFile : '';
|
||||
return $this->view->fetch($viewFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
$name = $this->request->post("name");
|
||||
$force = (int)$this->request->post("force");
|
||||
if (!$name) {
|
||||
$this->error(__('Parameter %s can not be empty', 'name'));
|
||||
}
|
||||
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
|
||||
$this->error(__('Addon name incorrect'));
|
||||
}
|
||||
|
||||
$info = [];
|
||||
try {
|
||||
$uid = $this->request->post("uid");
|
||||
$token = $this->request->post("token");
|
||||
$version = $this->request->post("version");
|
||||
$faversion = $this->request->post("faversion");
|
||||
$extend = [
|
||||
'uid' => $uid,
|
||||
'token' => $token,
|
||||
'version' => $version,
|
||||
'faversion' => $faversion
|
||||
];
|
||||
$info = Service::install($name, $force, $extend);
|
||||
} catch (AddonException $e) {
|
||||
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()), $e->getCode());
|
||||
}
|
||||
$this->success(__('Install successful'), '', ['addon' => $info]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
$name = $this->request->post("name");
|
||||
$force = (int)$this->request->post("force");
|
||||
$droptables = (int)$this->request->post("droptables");
|
||||
if (!$name) {
|
||||
$this->error(__('Parameter %s can not be empty', 'name'));
|
||||
}
|
||||
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
|
||||
$this->error(__('Addon name incorrect'));
|
||||
}
|
||||
//只有开启调试且为超级管理员才允许删除相关数据库
|
||||
$tables = [];
|
||||
if ($droptables && Config::get("app_debug") && $this->auth->isSuperAdmin()) {
|
||||
$tables = get_addon_tables($name);
|
||||
}
|
||||
try {
|
||||
Service::uninstall($name, $force);
|
||||
if ($tables) {
|
||||
$prefix = Config::get('database.prefix');
|
||||
//删除插件关联表
|
||||
foreach ($tables as $index => $table) {
|
||||
//忽略非插件标识的表名
|
||||
if (!preg_match("/^{$prefix}{$name}/", $table)) {
|
||||
continue;
|
||||
}
|
||||
Db::execute("DROP TABLE IF EXISTS `{$table}`");
|
||||
}
|
||||
}
|
||||
} catch (AddonException $e) {
|
||||
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success(__('Uninstall successful'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用启用
|
||||
*/
|
||||
public function state()
|
||||
{
|
||||
$name = $this->request->post("name");
|
||||
$action = $this->request->post("action");
|
||||
$force = (int)$this->request->post("force");
|
||||
if (!$name) {
|
||||
$this->error(__('Parameter %s can not be empty', 'name'));
|
||||
}
|
||||
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
|
||||
$this->error(__('Addon name incorrect'));
|
||||
}
|
||||
try {
|
||||
$action = $action == 'enable' ? $action : 'disable';
|
||||
//调用启用、禁用的方法
|
||||
Service::$action($name, $force);
|
||||
Cache::rm('__menu__');
|
||||
} catch (AddonException $e) {
|
||||
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success(__('Operate successful'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地上传
|
||||
*/
|
||||
public function local()
|
||||
{
|
||||
Config::set('default_return_type', 'json');
|
||||
|
||||
$info = [];
|
||||
$file = $this->request->file('file');
|
||||
try {
|
||||
$uid = $this->request->post("uid");
|
||||
$token = $this->request->post("token");
|
||||
$faversion = $this->request->post("faversion");
|
||||
if (!$uid || !$token) {
|
||||
throw new Exception(__('Please login and try to install'));
|
||||
}
|
||||
$extend = [
|
||||
'uid' => $uid,
|
||||
'token' => $token,
|
||||
'faversion' => $faversion
|
||||
];
|
||||
$info = Service::local($file, $extend);
|
||||
} catch (AddonException $e) {
|
||||
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success(__('Offline installed tips'), '', ['addon' => $info]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新插件
|
||||
*/
|
||||
public function upgrade()
|
||||
{
|
||||
$name = $this->request->post("name");
|
||||
$addonTmpDir = RUNTIME_PATH . 'addons' . DS;
|
||||
if (!$name) {
|
||||
$this->error(__('Parameter %s can not be empty', 'name'));
|
||||
}
|
||||
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
|
||||
$this->error(__('Addon name incorrect'));
|
||||
}
|
||||
if (!is_dir($addonTmpDir)) {
|
||||
@mkdir($addonTmpDir, 0755, true);
|
||||
}
|
||||
|
||||
$info = [];
|
||||
try {
|
||||
$uid = $this->request->post("uid");
|
||||
$token = $this->request->post("token");
|
||||
$version = $this->request->post("version");
|
||||
$faversion = $this->request->post("faversion");
|
||||
$extend = [
|
||||
'uid' => $uid,
|
||||
'token' => $token,
|
||||
'version' => $version,
|
||||
'faversion' => $faversion
|
||||
];
|
||||
//调用更新的方法
|
||||
$info = Service::upgrade($name, $extend);
|
||||
Cache::rm('__menu__');
|
||||
} catch (AddonException $e) {
|
||||
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success(__('Operate successful'), '', ['addon' => $info]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试数据
|
||||
*/
|
||||
public function testdata()
|
||||
{
|
||||
$name = $this->request->post("name");
|
||||
if (!$name) {
|
||||
$this->error(__('Parameter %s can not be empty', 'name'));
|
||||
}
|
||||
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
|
||||
$this->error(__('Addon name incorrect'));
|
||||
}
|
||||
|
||||
try {
|
||||
Service::importsql($name, 'testdata.sql');
|
||||
} catch (AddonException $e) {
|
||||
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()), $e->getCode());
|
||||
}
|
||||
$this->success(__('Import successful'), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 已装插件
|
||||
*/
|
||||
public function downloaded()
|
||||
{
|
||||
$offset = (int)$this->request->get("offset");
|
||||
$limit = (int)$this->request->get("limit");
|
||||
$filter = $this->request->get("filter");
|
||||
$search = $this->request->get("search");
|
||||
$search = htmlspecialchars(strip_tags($search));
|
||||
$onlineaddons = $this->getAddonList();
|
||||
$filter = (array)json_decode($filter, true);
|
||||
$addons = get_addon_list();
|
||||
$list = [];
|
||||
foreach ($addons as $k => $v) {
|
||||
if ($search && stripos($v['name'], $search) === false && stripos($v['title'], $search) === false && stripos($v['intro'], $search) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($onlineaddons[$v['name']])) {
|
||||
$v = array_merge($v, $onlineaddons[$v['name']]);
|
||||
$v['price'] = '-';
|
||||
} else {
|
||||
$v['category_id'] = 0;
|
||||
$v['flag'] = '';
|
||||
$v['banner'] = '';
|
||||
$v['image'] = '';
|
||||
$v['donateimage'] = '';
|
||||
$v['demourl'] = '';
|
||||
$v['price'] = __('None');
|
||||
$v['screenshots'] = [];
|
||||
$v['releaselist'] = [];
|
||||
$v['url'] = addon_url($v['name']);
|
||||
$v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
|
||||
}
|
||||
$v['createtime'] = filemtime(ADDON_PATH . $v['name']);
|
||||
if ($filter && isset($filter['category_id']) && is_numeric($filter['category_id']) && $filter['category_id'] != $v['category_id']) {
|
||||
continue;
|
||||
}
|
||||
$list[] = $v;
|
||||
}
|
||||
$total = count($list);
|
||||
if ($limit) {
|
||||
$list = array_slice($list, $offset, $limit);
|
||||
}
|
||||
$result = array("total" => $total, "rows" => $list);
|
||||
|
||||
$callback = $this->request->get('callback') ? "jsonp" : "json";
|
||||
return $callback($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测
|
||||
*/
|
||||
public function isbuy()
|
||||
{
|
||||
$name = $this->request->post("name");
|
||||
$uid = $this->request->post("uid");
|
||||
$token = $this->request->post("token");
|
||||
$version = $this->request->post("version");
|
||||
$faversion = $this->request->post("faversion");
|
||||
$extend = [
|
||||
'uid' => $uid,
|
||||
'token' => $token,
|
||||
'version' => $version,
|
||||
'faversion' => $faversion
|
||||
];
|
||||
try {
|
||||
$result = Service::isBuy($name, $extend);
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
return json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新授权
|
||||
*/
|
||||
public function authorization()
|
||||
{
|
||||
$params = [
|
||||
'uid' => $this->request->post('uid'),
|
||||
'token' => $this->request->post('token'),
|
||||
'faversion' => $this->request->post('faversion'),
|
||||
];
|
||||
try {
|
||||
Service::authorization($params);
|
||||
} catch (Exception $e) {
|
||||
$this->error(__($e->getMessage()));
|
||||
}
|
||||
$this->success(__('Operate successful'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件相关表
|
||||
*/
|
||||
public function get_table_list()
|
||||
{
|
||||
$name = $this->request->post("name");
|
||||
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
|
||||
$this->error(__('Addon name incorrect'));
|
||||
}
|
||||
$tables = get_addon_tables($name);
|
||||
$prefix = Config::get('database.prefix');
|
||||
foreach ($tables as $index => $table) {
|
||||
//忽略非插件标识的表名
|
||||
if (!preg_match("/^{$prefix}{$name}/", $table)) {
|
||||
unset($tables[$index]);
|
||||
}
|
||||
}
|
||||
$tables = array_values($tables);
|
||||
$this->success('', null, ['tables' => $tables]);
|
||||
}
|
||||
|
||||
protected function getAddonList()
|
||||
{
|
||||
$onlineaddons = Cache::get("onlineaddons");
|
||||
if (!is_array($onlineaddons) && config('fastadmin.api_url')) {
|
||||
$onlineaddons = [];
|
||||
$params = [
|
||||
'uid' => $this->request->post('uid'),
|
||||
'token' => $this->request->post('token'),
|
||||
'version' => config('fastadmin.version'),
|
||||
'faversion' => config('fastadmin.version'),
|
||||
];
|
||||
$json = [];
|
||||
try {
|
||||
$json = Service::addons($params);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
$rows = isset($json['rows']) ? $json['rows'] : [];
|
||||
foreach ($rows as $index => $row) {
|
||||
$onlineaddons[$row['name']] = $row;
|
||||
}
|
||||
Cache::set("onlineaddons", $onlineaddons, 600);
|
||||
}
|
||||
return $onlineaddons;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\exception\UploadException;
|
||||
use app\common\library\Upload;
|
||||
use fast\Random;
|
||||
use think\addons\Service;
|
||||
use think\Cache;
|
||||
use think\Config;
|
||||
use think\Db;
|
||||
use think\Lang;
|
||||
use think\Response;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* Ajax异步请求接口
|
||||
* @internal
|
||||
*/
|
||||
class Ajax extends Backend
|
||||
{
|
||||
|
||||
protected $noNeedLogin = ['lang'];
|
||||
protected $noNeedRight = ['*'];
|
||||
protected $layout = '';
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
|
||||
//设置过滤方法
|
||||
$this->request->filter(['trim', 'strip_tags', 'htmlspecialchars']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载语言包
|
||||
*/
|
||||
public function lang()
|
||||
{
|
||||
|
||||
$header = ['Content-Type' => 'application/javascript'];
|
||||
if (!config('app_debug')) {
|
||||
$offset = 30 * 60 * 60 * 24; // 缓存一个月
|
||||
$header['Cache-Control'] = 'public';
|
||||
$header['Pragma'] = 'cache';
|
||||
$header['Expires'] = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
|
||||
}
|
||||
|
||||
$controllername = input("controllername");
|
||||
//默认只加载了控制器对应的语言名,你还根据控制器名来加载额外的语言包
|
||||
$this->loadlang($controllername);
|
||||
return jsonp(Lang::get(), 200, $header, ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
public function upload()
|
||||
{
|
||||
Config::set('default_return_type', 'json');
|
||||
//必须设定cdnurl为空,否则cdnurl函数计算错误
|
||||
Config::set('upload.cdnurl', '');
|
||||
$chunkid = $this->request->post("chunkid");
|
||||
if ($chunkid) {
|
||||
if (!Config::get('upload.chunking')) {
|
||||
$this->error(__('Chunk file disabled'));
|
||||
}
|
||||
$action = $this->request->post("action");
|
||||
$chunkindex = $this->request->post("chunkindex/d");
|
||||
$chunkcount = $this->request->post("chunkcount/d");
|
||||
$filename = $this->request->post("filename");
|
||||
$method = $this->request->method(true);
|
||||
if ($action == 'merge') {
|
||||
$attachment = null;
|
||||
//合并分片文件
|
||||
try {
|
||||
$upload = new Upload();
|
||||
$attachment = $upload->merge($chunkid, $chunkcount, $filename);
|
||||
} catch (UploadException $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
|
||||
} elseif ($method == 'clean') {
|
||||
//删除冗余的分片文件
|
||||
try {
|
||||
$upload = new Upload();
|
||||
$upload->clean($chunkid);
|
||||
} catch (UploadException $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success();
|
||||
} else {
|
||||
//上传分片文件
|
||||
//默认普通上传文件
|
||||
$file = $this->request->file('file');
|
||||
try {
|
||||
$upload = new Upload($file);
|
||||
$upload->chunk($chunkid, $chunkindex, $chunkcount);
|
||||
} catch (UploadException $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
} else {
|
||||
$attachment = null;
|
||||
//默认普通上传文件
|
||||
$file = $this->request->file('file');
|
||||
try {
|
||||
$upload = new Upload($file);
|
||||
$attachment = $upload->upload();
|
||||
} catch (UploadException $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用排序
|
||||
*/
|
||||
public function weigh()
|
||||
{
|
||||
//排序的数组
|
||||
$ids = $this->request->post("ids");
|
||||
//拖动的记录ID
|
||||
$changeid = $this->request->post("changeid");
|
||||
//操作字段
|
||||
$field = $this->request->post("field");
|
||||
//操作的数据表
|
||||
$table = $this->request->post("table");
|
||||
if (!Validate::is($table, "alphaDash")) {
|
||||
$this->error();
|
||||
}
|
||||
//主键
|
||||
$pk = $this->request->post("pk");
|
||||
//排序的方式
|
||||
$orderway = strtolower($this->request->post("orderway", ""));
|
||||
$orderway = $orderway == 'asc' ? 'ASC' : 'DESC';
|
||||
$sour = $weighdata = [];
|
||||
$ids = explode(',', $ids);
|
||||
$prikey = $pk && preg_match("/^[a-z0-9\-_]+$/i", $pk) ? $pk : (Db::name($table)->getPk() ?: 'id');
|
||||
$pid = $this->request->post("pid", "");
|
||||
//限制更新的字段
|
||||
$field = in_array($field, ['weigh']) ? $field : 'weigh';
|
||||
|
||||
// 如果设定了pid的值,此时只匹配满足条件的ID,其它忽略
|
||||
if ($pid !== '') {
|
||||
$hasids = [];
|
||||
$list = Db::name($table)->where($prikey, 'in', $ids)->where('pid', 'in', $pid)->field("{$prikey},pid")->select();
|
||||
foreach ($list as $k => $v) {
|
||||
$hasids[] = $v[$prikey];
|
||||
}
|
||||
$ids = array_values(array_intersect($ids, $hasids));
|
||||
}
|
||||
|
||||
$list = Db::name($table)->field("$prikey,$field")->where($prikey, 'in', $ids)->order($field, $orderway)->select();
|
||||
foreach ($list as $k => $v) {
|
||||
$sour[] = $v[$prikey];
|
||||
$weighdata[$v[$prikey]] = $v[$field];
|
||||
}
|
||||
$position = array_search($changeid, $ids);
|
||||
$desc_id = isset($sour[$position]) ? $sour[$position] : end($sour); //移动到目标的ID值,取出所处改变前位置的值
|
||||
$sour_id = $changeid;
|
||||
$weighids = array();
|
||||
$temp = array_values(array_diff_assoc($ids, $sour));
|
||||
foreach ($temp as $m => $n) {
|
||||
if ($n == $sour_id) {
|
||||
$offset = $desc_id;
|
||||
} else {
|
||||
if ($sour_id == $temp[0]) {
|
||||
$offset = isset($temp[$m + 1]) ? $temp[$m + 1] : $sour_id;
|
||||
} else {
|
||||
$offset = isset($temp[$m - 1]) ? $temp[$m - 1] : $sour_id;
|
||||
}
|
||||
}
|
||||
if (!isset($weighdata[$offset])) {
|
||||
continue;
|
||||
}
|
||||
$weighids[$n] = $weighdata[$offset];
|
||||
Db::name($table)->where($prikey, $n)->update([$field => $weighdata[$offset]]);
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空系统缓存
|
||||
*/
|
||||
public function wipecache()
|
||||
{
|
||||
try {
|
||||
$type = $this->request->request("type");
|
||||
switch ($type) {
|
||||
case 'all':
|
||||
// no break
|
||||
case 'content':
|
||||
//内容缓存
|
||||
rmdirs(CACHE_PATH, false);
|
||||
Cache::clear();
|
||||
if ($type == 'content') {
|
||||
break;
|
||||
}
|
||||
case 'template':
|
||||
// 模板缓存
|
||||
rmdirs(TEMP_PATH, false);
|
||||
if ($type == 'template') {
|
||||
break;
|
||||
}
|
||||
case 'addons':
|
||||
// 插件缓存
|
||||
Service::refresh();
|
||||
if ($type == 'addons') {
|
||||
break;
|
||||
}
|
||||
case 'browser':
|
||||
// 浏览器缓存
|
||||
// 只有生产环境下才修改
|
||||
if (!config('app_debug')) {
|
||||
$version = config('site.version');
|
||||
$newversion = preg_replace_callback("/(.*)\.([0-9]+)\$/", function ($match) {
|
||||
return $match[1] . '.' . ($match[2] + 1);
|
||||
}, $version);
|
||||
if ($newversion && $newversion != $version) {
|
||||
Db::startTrans();
|
||||
try {
|
||||
\app\common\model\Config::where('name', 'version')->update(['value' => $newversion]);
|
||||
\app\common\model\Config::refreshFile();
|
||||
Db::commit();
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($type == 'browser') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
\think\Hook::listen("wipecache_after");
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取分类数据,联动列表
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
$type = $this->request->get('type', '');
|
||||
$pid = $this->request->get('pid', '');
|
||||
$where = ['status' => 'normal'];
|
||||
$categorylist = null;
|
||||
if ($pid || $pid === '0') {
|
||||
$where['pid'] = $pid;
|
||||
}
|
||||
if ($type) {
|
||||
$where['type'] = $type;
|
||||
}
|
||||
|
||||
$categorylist = Db::name('category')->where($where)->field('id as value,name')->order('weigh desc,id desc')->select();
|
||||
|
||||
$this->success('', '', $categorylist);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取省市区数据,联动列表
|
||||
*/
|
||||
public function area()
|
||||
{
|
||||
$params = $this->request->get("row/a");
|
||||
if (!empty($params)) {
|
||||
$province = isset($params['province']) ? $params['province'] : '';
|
||||
$city = isset($params['city']) ? $params['city'] : '';
|
||||
} else {
|
||||
$province = $this->request->get('province', '');
|
||||
$city = $this->request->get('city', '');
|
||||
}
|
||||
$where = ['pid' => 0, 'level' => 1];
|
||||
$provincelist = null;
|
||||
if ($province !== '') {
|
||||
$where['pid'] = $province;
|
||||
$where['level'] = 2;
|
||||
if ($city !== '') {
|
||||
$where['pid'] = $city;
|
||||
$where['level'] = 3;
|
||||
}
|
||||
}
|
||||
$provincelist = Db::name('area')->where($where)->field('id as value,name')->select();
|
||||
$this->success('', '', $provincelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成后缀图标
|
||||
*/
|
||||
public function icon()
|
||||
{
|
||||
$suffix = $this->request->request("suffix");
|
||||
$suffix = $suffix ? $suffix : "FILE";
|
||||
$data = build_suffix_image($suffix);
|
||||
$header = ['Content-Type' => 'image/svg+xml'];
|
||||
$offset = 30 * 60 * 60 * 24; // 缓存一个月
|
||||
$header['Cache-Control'] = 'public';
|
||||
$header['Pragma'] = 'cache';
|
||||
$header['Expires'] = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
|
||||
$response = Response::create($data, '', 200, $header);
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\model\Category as CategoryModel;
|
||||
use fast\Tree;
|
||||
|
||||
/**
|
||||
* 分类管理
|
||||
*
|
||||
* @icon fa fa-list
|
||||
* @remark 用于管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加
|
||||
*/
|
||||
class Category extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \app\common\model\Category
|
||||
*/
|
||||
protected $model = null;
|
||||
protected $categorylist = [];
|
||||
protected $noNeedRight = ['selectpage'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('app\common\model\Category');
|
||||
|
||||
$tree = Tree::instance();
|
||||
$tree->init(collection($this->model->order('weigh desc,id desc')->select())->toArray(), 'pid');
|
||||
$this->categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
|
||||
$categorydata = [0 => ['type' => 'all', 'name' => __('None')]];
|
||||
foreach ($this->categorylist as $k => $v) {
|
||||
$categorydata[$v['id']] = $v;
|
||||
}
|
||||
$typeList = CategoryModel::getTypeList();
|
||||
$this->view->assign("flagList", $this->model->getFlagList());
|
||||
$this->view->assign("typeList", $typeList);
|
||||
$this->view->assign("parentList", $categorydata);
|
||||
$this->assignconfig('typeList', $typeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//设置过滤方法
|
||||
$this->request->filter(['strip_tags']);
|
||||
if ($this->request->isAjax()) {
|
||||
$search = $this->request->request("search");
|
||||
$type = $this->request->request("type");
|
||||
|
||||
//构造父类select列表选项数据
|
||||
$list = [];
|
||||
|
||||
foreach ($this->categorylist as $k => $v) {
|
||||
if ($search) {
|
||||
if ($v['type'] == $type && stripos($v['name'], $search) !== false || stripos($v['nickname'], $search) !== false) {
|
||||
if ($type == "all" || $type == null) {
|
||||
$list = $this->categorylist;
|
||||
} else {
|
||||
$list[] = $v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($type == "all" || $type == null) {
|
||||
$list = $this->categorylist;
|
||||
} elseif ($v['type'] == $type) {
|
||||
$list[] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$total = count($list);
|
||||
$result = array("total" => $total, "rows" => $list);
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
}
|
||||
return parent::add();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
$row = $this->model->get($ids);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (is_array($adminIds)) {
|
||||
if (!in_array($row[$this->dataLimitField], $adminIds)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
$params = $this->request->post("row/a");
|
||||
if ($params) {
|
||||
$params = $this->preExcludeFields($params);
|
||||
|
||||
if ($params['pid'] != $row['pid']) {
|
||||
$childrenIds = Tree::instance()->init(collection(\app\common\model\Category::select())->toArray())->getChildrenIds($row['id'], true);
|
||||
if (in_array($params['pid'], $childrenIds)) {
|
||||
$this->error(__('Can not change the parent to child or itself'));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
//是否采用模型验证
|
||||
if ($this->modelValidate) {
|
||||
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
|
||||
$row->validate($validate);
|
||||
}
|
||||
$result = $row->allowField(true)->save($params);
|
||||
if ($result !== false) {
|
||||
$this->success();
|
||||
} else {
|
||||
$this->error($row->getError());
|
||||
}
|
||||
} catch (\think\exception\PDOException $e) {
|
||||
$this->error($e->getMessage());
|
||||
} catch (\think\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
$this->view->assign("row", $row);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Selectpage搜索
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function selectpage()
|
||||
{
|
||||
return parent::selectpage();
|
||||
}
|
||||
}
|
@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use think\Config;
|
||||
use think\console\Input;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 在线命令管理
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Command extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Command模型对象
|
||||
*/
|
||||
protected $model = null;
|
||||
protected $noNeedRight = ['get_controller_list', 'get_field_list'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('Command');
|
||||
$this->view->assign("statusList", $this->model->getStatusList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
|
||||
$tableList = [];
|
||||
$list = \think\Db::query("SHOW TABLES");
|
||||
foreach ($list as $key => $row) {
|
||||
$tableList[reset($row)] = reset($row);
|
||||
}
|
||||
|
||||
$this->view->assign("tableList", $tableList);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段列表
|
||||
* @internal
|
||||
*/
|
||||
public function get_field_list()
|
||||
{
|
||||
$dbname = Config::get('database.database');
|
||||
$prefix = Config::get('database.prefix');
|
||||
$table = $this->request->request('table');
|
||||
//从数据库中获取表字段信息
|
||||
$sql = "SELECT * FROM `information_schema`.`columns` "
|
||||
. "WHERE TABLE_SCHEMA = ? AND table_name = ? "
|
||||
. "ORDER BY ORDINAL_POSITION";
|
||||
//加载主表的列
|
||||
$columnList = Db::query($sql, [$dbname, $table]);
|
||||
$fieldlist = [];
|
||||
foreach ($columnList as $index => $item) {
|
||||
$fieldlist[] = $item['COLUMN_NAME'];
|
||||
}
|
||||
$this->success("", null, ['fieldlist' => $fieldlist]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取控制器列表
|
||||
* @internal
|
||||
*/
|
||||
public function get_controller_list()
|
||||
{
|
||||
//搜索关键词,客户端输入以空格分开,这里接收为数组
|
||||
$word = (array)$this->request->request("q_word/a");
|
||||
$word = implode('', $word);
|
||||
|
||||
$adminPath = dirname(__DIR__) . DS;
|
||||
$controllerDir = $adminPath . 'controller' . DS;
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
$list = [];
|
||||
foreach ($files as $name => $file) {
|
||||
if (!$file->isDir()) {
|
||||
$filePath = $file->getRealPath();
|
||||
$name = str_replace($controllerDir, '', $filePath);
|
||||
$name = str_replace(DS, "/", $name);
|
||||
if (!preg_match("/(.*)\.php\$/", $name)) {
|
||||
continue;
|
||||
}
|
||||
if (!$word || stripos($name, $word) !== false) {
|
||||
$list[] = ['id' => $name, 'name' => $name];
|
||||
}
|
||||
}
|
||||
}
|
||||
$pageNumber = $this->request->request("pageNumber");
|
||||
$pageSize = $this->request->request("pageSize");
|
||||
return json(['list' => array_slice($list, ($pageNumber - 1) * $pageSize, $pageSize), 'total' => count($list)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
public function detail($ids)
|
||||
{
|
||||
$row = $this->model->get($ids);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
$this->view->assign("row", $row);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行
|
||||
*/
|
||||
public function execute($ids)
|
||||
{
|
||||
$row = $this->model->get($ids);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
$result = $this->doexecute($row['type'], json_decode($row['params'], true));
|
||||
$this->success("", null, ['result' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*/
|
||||
public function command($action = '')
|
||||
{
|
||||
$commandtype = $this->request->request("commandtype");
|
||||
$params = $this->request->request();
|
||||
$allowfields = [
|
||||
'crud' => 'table,controller,model,fields,force,local,delete,menu',
|
||||
'menu' => 'controller,delete',
|
||||
'min' => 'module,resource,optimize',
|
||||
'api' => 'url,module,output,template,force,title,author,class,language,addon',
|
||||
];
|
||||
$argv = [];
|
||||
$allowfields = isset($allowfields[$commandtype]) ? explode(',', $allowfields[$commandtype]) : [];
|
||||
$allowfields = array_filter(array_intersect_key($params, array_flip($allowfields)));
|
||||
if (isset($params['local']) && !$params['local']) {
|
||||
$allowfields['local'] = $params['local'];
|
||||
} else {
|
||||
unset($allowfields['local']);
|
||||
}
|
||||
foreach ($allowfields as $key => $param) {
|
||||
$argv[] = "--{$key}=" . (is_array($param) ? implode(',', $param) : $param);
|
||||
}
|
||||
if ($commandtype == 'crud') {
|
||||
$extend = 'setcheckboxsuffix,enumradiosuffix,imagefield,filefield,intdatesuffix,switchsuffix,citysuffix,selectpagesuffix,selectpagessuffix,ignorefields,sortfield,editorsuffix,headingfilterfield,tagsuffix,jsonsuffix,fixedcolumns';
|
||||
$extendArr = explode(',', $extend);
|
||||
foreach ($params as $index => $item) {
|
||||
if (in_array($index, $extendArr)) {
|
||||
foreach (explode(',', $item) as $key => $value) {
|
||||
if ($value) {
|
||||
$argv[] = "--{$index}={$value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$isrelation = (int)$this->request->request('isrelation');
|
||||
if ($isrelation && isset($params['relation'])) {
|
||||
foreach ($params['relation'] as $index => $relation) {
|
||||
foreach ($relation as $key => $value) {
|
||||
$argv[] = "--{$key}=" . (is_array($value) ? implode(',', $value) : $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($commandtype == 'menu') {
|
||||
if (isset($params['allcontroller']) && $params['allcontroller']) {
|
||||
$argv[] = "--controller=all-controller";
|
||||
} else {
|
||||
foreach (explode(',', $params['controllerfile']) as $index => $param) {
|
||||
if ($param) {
|
||||
$argv[] = "--controller=" . substr($param, 0, -4);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($commandtype == 'min') {
|
||||
|
||||
} else {
|
||||
if ($commandtype == 'api') {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($action == 'execute') {
|
||||
if (stripos(implode(' ', $argv), '--controller=all-controller') !== false) {
|
||||
$this->error("只允许在命令行执行该命令,执行前请做好菜单规则备份!!!");
|
||||
}
|
||||
if (config('app_debug')) {
|
||||
$result = $this->doexecute($commandtype, $argv);
|
||||
$this->success("", null, ['result' => $result]);
|
||||
} else {
|
||||
$this->error("只允许在开发环境下执行命令");
|
||||
}
|
||||
} else {
|
||||
$this->success("", null, ['command' => "php think {$commandtype} " . implode(' ', $argv)]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
protected function doexecute($commandtype, $argv)
|
||||
{
|
||||
$commandName = "\\app\\admin\\command\\" . ucfirst($commandtype);
|
||||
$input = new Input($argv);
|
||||
$output = new \addons\command\library\Output();
|
||||
$command = new $commandName($commandtype);
|
||||
$data = [
|
||||
'type' => $commandtype,
|
||||
'params' => json_encode($argv),
|
||||
'command' => "php think {$commandtype} " . implode(' ', $argv),
|
||||
'executetime' => time(),
|
||||
];
|
||||
$this->model->save($data);
|
||||
try {
|
||||
$command->run($input, $output);
|
||||
$result = implode("\n", $output->getMessage());
|
||||
$this->model->status = 'successed';
|
||||
} catch (Exception $e) {
|
||||
$result = implode("\n", $output->getMessage()) . "\n";
|
||||
$result .= $e->getMessage();
|
||||
$this->model->status = 'failured';
|
||||
}
|
||||
$result = trim($result);
|
||||
$this->model->content = $result;
|
||||
$this->model->save();
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\Admin;
|
||||
use app\admin\model\User;
|
||||
use app\common\controller\Backend;
|
||||
use app\common\model\Attachment;
|
||||
use fast\Date;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 控制台
|
||||
*
|
||||
* @icon fa fa-dashboard
|
||||
* @remark 用于展示当前系统中的统计数据、统计报表及重要实时数据
|
||||
*/
|
||||
class Dashboard extends Backend
|
||||
{
|
||||
public function index(){
|
||||
$exp = new \think\db\Expression("logintime BETWEEN unix_timestamp(now())-300 and unix_timestamp(now())");
|
||||
$online = Db("visit_log")->where($exp)->count();
|
||||
$this->assign('online', $online);
|
||||
|
||||
$exp = new \think\db\Expression("to_days(from_unixtime(jointime))=to_days(now())");
|
||||
$today = Db("visit_log")->where($exp)->count();
|
||||
$this->assign('today', $today);
|
||||
|
||||
$exp = new \think\db\Expression("date_sub(curdate(),interval 7 day) < date(from_unixtime(jointime))");
|
||||
$days_7 = Db("visit_log")->where($exp)->count();
|
||||
$this->assign('days_7', $days_7);
|
||||
|
||||
$exp = new \think\db\Expression("date_sub(curdate(),interval 30 day) < date(from_unixtime(jointime))");
|
||||
$days_30 = Db("visit_log")->where($exp)->count();
|
||||
$this->assign('days_30', $days_30);
|
||||
|
||||
$exp = new \think\db\Expression("date_sub(curdate(),interval 90 day) < date(from_unixtime(jointime))");
|
||||
$days_90 = Db("visit_log")->where($exp)->count();
|
||||
$this->assign('days_90', $days_90);
|
||||
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function indexs()
|
||||
{
|
||||
try {
|
||||
\think\Db::execute("SET @@sql_mode='';");
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
$column = [];
|
||||
$starttime = Date::unixtime('day', -6);
|
||||
$endtime = Date::unixtime('day', 0, 'end');
|
||||
$joinlist = Db("user")->where('jointime', 'between time', [$starttime, $endtime])
|
||||
->field('jointime, status, COUNT(*) AS nums, DATE_FORMAT(FROM_UNIXTIME(jointime), "%Y-%m-%d") AS join_date')
|
||||
->group('join_date')
|
||||
->select();
|
||||
for ($time = $starttime; $time <= $endtime;) {
|
||||
$column[] = date("Y-m-d", $time);
|
||||
$time += 86400;
|
||||
}
|
||||
$userlist = array_fill_keys($column, 0);
|
||||
foreach ($joinlist as $k => $v) {
|
||||
$userlist[$v['join_date']] = $v['nums'];
|
||||
}
|
||||
|
||||
$dbTableList = Db::query("SHOW TABLE STATUS");
|
||||
$addonList = get_addon_list();
|
||||
$totalworkingaddon = 0;
|
||||
$totaladdon = count($addonList);
|
||||
foreach ($addonList as $index => $item) {
|
||||
if ($item['state']) {
|
||||
$totalworkingaddon += 1;
|
||||
}
|
||||
}
|
||||
$this->view->assign([
|
||||
'totaluser' => User::count(),
|
||||
'totaladdon' => $totaladdon,
|
||||
'totaladmin' => Admin::count(),
|
||||
'totalcategory' => \app\common\model\Category::count(),
|
||||
'todayusersignup' => User::whereTime('jointime', 'today')->count(),
|
||||
'todayuserlogin' => User::whereTime('logintime', 'today')->count(),
|
||||
'sevendau' => User::whereTime('jointime|logintime|prevtime', '-7 days')->count(),
|
||||
'thirtydau' => User::whereTime('jointime|logintime|prevtime', '-30 days')->count(),
|
||||
'threednu' => User::whereTime('jointime', '-3 days')->count(),
|
||||
'sevendnu' => User::whereTime('jointime', '-7 days')->count(),
|
||||
'dbtablenums' => count($dbTableList),
|
||||
'dbsize' => array_sum(array_map(function ($item) {
|
||||
return $item['Data_length'] + $item['Index_length'];
|
||||
}, $dbTableList)),
|
||||
'totalworkingaddon' => $totalworkingaddon,
|
||||
'attachmentnums' => Attachment::count(),
|
||||
'attachmentsize' => Attachment::sum('filesize'),
|
||||
'picturenums' => Attachment::where('mimetype', 'like', 'image/%')->count(),
|
||||
'picturesize' => Attachment::where('mimetype', 'like', 'image/%')->sum('filesize'),
|
||||
]);
|
||||
|
||||
$this->assignconfig('column', array_keys($userlist));
|
||||
$this->assignconfig('userdata', array_values($userlist));
|
||||
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\library\Auth;
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Csv;
|
||||
use think\Db;
|
||||
use think\db\exception\BindParamException;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\exception\DbException;
|
||||
use think\exception\PDOException;
|
||||
use think\exception\ValidateException;
|
||||
use think\response\Json;
|
||||
|
||||
/**
|
||||
* hackathon
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Hackathon extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Hackathon模型对象
|
||||
* @var \app\admin\model\Hackathon
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\Hackathon;
|
||||
$this->view->assign("statusList", $this->model->getStatusList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @return string
|
||||
* @throws \think\Exception
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if (false === $this->request->isPost()) {
|
||||
return $this->view->fetch();
|
||||
}
|
||||
$params = $this->request->post('row/a');
|
||||
if (empty($params)) {
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
$params = $this->preExcludeFields($params);
|
||||
|
||||
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
|
||||
$params[$this->dataLimitField] = $this->auth->id;
|
||||
}
|
||||
$result = false;
|
||||
Db::startTrans();
|
||||
try {
|
||||
//是否采用模型验证
|
||||
if ($this->modelValidate) {
|
||||
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
|
||||
$this->model->validateFailException()->validate($validate);
|
||||
}
|
||||
|
||||
$params['starttime'] = strtotime($params['starttime']);
|
||||
$params['endtime'] = strtotime($params['endtime']);
|
||||
if(!empty($params['starttime']) && !empty($params['endtime'])){
|
||||
if($params['endtime'] < time()){
|
||||
$params['status'] = 'Past';
|
||||
}else if($params['starttime'] > time()){
|
||||
$params['status'] = 'Upcoming';
|
||||
}else{
|
||||
$params['status'] = 'Ongoing';
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->model->allowField(true)->save($params);
|
||||
Db::commit();
|
||||
} catch (ValidateException|PDOException|Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result === false) {
|
||||
$this->error(__('No rows were inserted'));
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param $ids
|
||||
* @return string
|
||||
* @throws DbException
|
||||
* @throws \think\Exception
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
$row = $this->model->get($ids);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
if (false === $this->request->isPost()) {
|
||||
$this->view->assign('row', $row);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
$params = $this->request->post('row/a');
|
||||
if (empty($params)) {
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
$params = $this->preExcludeFields($params);
|
||||
$result = false;
|
||||
Db::startTrans();
|
||||
try {
|
||||
//是否采用模型验证
|
||||
if ($this->modelValidate) {
|
||||
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
|
||||
$row->validateFailException()->validate($validate);
|
||||
}
|
||||
|
||||
$params['starttime'] = strtotime($params['starttime']);
|
||||
$params['endtime'] = strtotime($params['endtime']);
|
||||
if(!empty($params['starttime']) && !empty($params['endtime'])){
|
||||
if($params['endtime'] < time()){
|
||||
$params['status'] = 'Past';
|
||||
}else if($params['starttime'] > time()){
|
||||
$params['status'] = 'Upcoming';
|
||||
}else{
|
||||
$params['status'] = 'Ongoing';
|
||||
}
|
||||
}
|
||||
|
||||
$result = $row->allowField(true)->save($params);
|
||||
Db::commit();
|
||||
} catch (ValidateException|PDOException|Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if (false === $result) {
|
||||
$this->error(__('No rows were updated'));
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\AdminLog;
|
||||
use app\common\controller\Backend;
|
||||
use think\Config;
|
||||
use think\Hook;
|
||||
use think\Session;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 后台首页
|
||||
* @internal
|
||||
*/
|
||||
class Index extends Backend
|
||||
{
|
||||
|
||||
protected $noNeedLogin = ['login'];
|
||||
protected $noNeedRight = ['index', 'logout'];
|
||||
protected $layout = '';
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
//移除HTML标签
|
||||
$this->request->filter('trim,strip_tags,htmlspecialchars');
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台首页
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$cookieArr = ['adminskin' => "/^skin\-([a-z\-]+)\$/i", 'multiplenav' => "/^(0|1)\$/", 'multipletab' => "/^(0|1)\$/", 'show_submenu' => "/^(0|1)\$/"];
|
||||
foreach ($cookieArr as $key => $regex) {
|
||||
$cookieValue = $this->request->cookie($key);
|
||||
if (!is_null($cookieValue) && preg_match($regex, $cookieValue)) {
|
||||
config('fastadmin.' . $key, $cookieValue);
|
||||
}
|
||||
}
|
||||
//左侧菜单
|
||||
list($menulist, $navlist, $fixedmenu, $referermenu) = $this->auth->getSidebar([
|
||||
'dashboard' => 'hot',
|
||||
'addon' => ['new', 'red', 'badge'],
|
||||
'auth/rule' => __('Menu'),
|
||||
'general' => ['new', 'purple'],
|
||||
], $this->view->site['fixedpage']);
|
||||
$action = $this->request->request('action');
|
||||
if ($this->request->isPost()) {
|
||||
if ($action == 'refreshmenu') {
|
||||
$this->success('', null, ['menulist' => $menulist, 'navlist' => $navlist]);
|
||||
}
|
||||
}
|
||||
$this->assignconfig('cookie', ['prefix' => config('cookie.prefix')]);
|
||||
$this->view->assign('menulist', $menulist);
|
||||
$this->view->assign('navlist', $navlist);
|
||||
$this->view->assign('fixedmenu', $fixedmenu);
|
||||
$this->view->assign('referermenu', $referermenu);
|
||||
$this->view->assign('title', __('Home'));
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
$url = $this->request->get('url', 'index/index');
|
||||
if ($this->auth->isLogin()) {
|
||||
$this->success(__("You've logged in, do not login again"), $url);
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$username = $this->request->post('username');
|
||||
$password = $this->request->post('password');
|
||||
$keeplogin = $this->request->post('keeplogin');
|
||||
$token = $this->request->post('__token__');
|
||||
$rule = [
|
||||
'username' => 'require|length:3,30',
|
||||
'password' => 'require|length:3,30',
|
||||
'__token__' => 'require|token',
|
||||
];
|
||||
$data = [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'__token__' => $token,
|
||||
];
|
||||
if (Config::get('fastadmin.login_captcha')) {
|
||||
$rule['captcha'] = 'require|captcha';
|
||||
$data['captcha'] = $this->request->post('captcha');
|
||||
}
|
||||
$validate = new Validate($rule, [], ['username' => __('Username'), 'password' => __('Password'), 'captcha' => __('Captcha')]);
|
||||
$result = $validate->check($data);
|
||||
if (!$result) {
|
||||
$this->error($validate->getError(), $url, ['token' => $this->request->token()]);
|
||||
}
|
||||
AdminLog::setTitle(__('Login'));
|
||||
$result = $this->auth->login($username, $password, $keeplogin ? 86400 : 0);
|
||||
if ($result === true) {
|
||||
Hook::listen("admin_login_after", $this->request);
|
||||
$this->success(__('Login successful'), $url, ['url' => $url, 'id' => $this->auth->id, 'username' => $username, 'avatar' => $this->auth->avatar]);
|
||||
} else {
|
||||
$msg = $this->auth->getError();
|
||||
$msg = $msg ? $msg : __('Username or password is incorrect');
|
||||
$this->error($msg, $url, ['token' => $this->request->token()]);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据客户端的cookie,判断是否可以自动登录
|
||||
if ($this->auth->autologin()) {
|
||||
Session::delete("referer");
|
||||
$this->redirect($url);
|
||||
}
|
||||
$background = Config::get('fastadmin.login_background');
|
||||
$background = $background ? (stripos($background, 'http') === 0 ? $background : config('site.cdnurl') . $background) : '';
|
||||
$this->view->assign('background', $background);
|
||||
$this->view->assign('title', __('Login'));
|
||||
Hook::listen("admin_login_init", $this->request);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->auth->logout();
|
||||
Hook::listen("admin_logout_after", $this->request);
|
||||
$this->success(__('Logout successful'), 'index/login');
|
||||
}
|
||||
$html = "<form id='logout_submit' name='logout_submit' action='' method='post'>" . token() . "<input type='submit' value='ok' style='display:none;'></form>";
|
||||
$html .= "<script>document.forms['logout_submit'].submit();</script>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* media
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Media extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Media模型对象
|
||||
* @var \app\admin\model\Media
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\Media;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use fast\Tree;
|
||||
|
||||
/**
|
||||
* navigation
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Navigation extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Navigation模型对象
|
||||
* @var \app\admin\model\Navigation
|
||||
*/
|
||||
protected $model = null;
|
||||
protected $navlist = [];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\Navigation;
|
||||
|
||||
$tree = Tree::instance();
|
||||
$tree->init(collection($this->model->order('id asc')->select())->toArray(), 'pid');
|
||||
$this->navlist = $tree->getTreeList($tree->getTreeArray(0), 'title');
|
||||
$navlistdata = [0 => ['type' => 'all', 'title' => __('None')]];
|
||||
foreach ($this->navlist as $k => $v) {
|
||||
$navlistdata[$v['id']] = $v;
|
||||
}
|
||||
$this->view->assign("parentList", $navlistdata);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//设置过滤方法
|
||||
$this->request->filter(['strip_tags']);
|
||||
if ($this->request->isAjax()) {
|
||||
$search = $this->request->request("search");
|
||||
$type = $this->request->request("type");
|
||||
|
||||
//构造父类select列表选项数据
|
||||
$list = [];
|
||||
|
||||
foreach ($this->navlist as $k => $v) {
|
||||
if ($search) {
|
||||
if ($v['type'] == $type && stripos($v['name'], $search) !== false || stripos($v['nickname'], $search) !== false) {
|
||||
if ($type == "all" || $type == null) {
|
||||
$list = $this->navlist;
|
||||
} else {
|
||||
$list[] = $v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($type == "all" || $type == null) {
|
||||
$list = $this->navlist;
|
||||
} elseif ($v['type'] == $type) {
|
||||
$list[] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$total = count($list);
|
||||
$result = array("total" => $total, "rows" => $list);
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\cms\Channel as ChannelModel;
|
||||
use app\common\controller\Backend;
|
||||
use app\admin\library\Auth;
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Csv;
|
||||
use think\Db;
|
||||
use think\db\exception\BindParamException;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\exception\DbException;
|
||||
use think\exception\PDOException;
|
||||
use think\exception\ValidateException;
|
||||
use think\response\Json;
|
||||
use fast\Http;
|
||||
use think\Cache;
|
||||
|
||||
/**
|
||||
* news
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class News extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* News模型对象
|
||||
* @var \app\admin\model\News
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\News;
|
||||
$this->view->assign("statusList", $this->model->getStatusList());
|
||||
$this->view->assign("typeList", $this->model->getTypeList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @return string
|
||||
* @throws \think\Exception
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if (false === $this->request->isPost()) {
|
||||
return $this->view->fetch();
|
||||
}
|
||||
$params = $this->request->post('row/a');
|
||||
if (empty($params)) {
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
|
||||
if(count(explode(',',$params['tag'])) > 3){
|
||||
$this->error(__("Set up to three tag",''));
|
||||
}
|
||||
|
||||
$params = $this->preExcludeFields($params);
|
||||
|
||||
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
|
||||
$params[$this->dataLimitField] = $this->auth->id;
|
||||
}
|
||||
$result = false;
|
||||
Db::startTrans();
|
||||
try {
|
||||
//是否采用模型验证
|
||||
if ($this->modelValidate) {
|
||||
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
|
||||
$this->model->validateFailException()->validate($validate);
|
||||
}
|
||||
|
||||
if(!empty($params['status'])){
|
||||
$params['publishedtime'] = strtotime($params['publishedtime']);
|
||||
}
|
||||
|
||||
if(!empty($params['status'] && $params['status'] == 'Scheduled')){
|
||||
$params['publishedtime'] = '';
|
||||
}
|
||||
|
||||
$result = $this->model->allowField(true)->save($params);
|
||||
Db::commit();
|
||||
} catch (ValidateException|PDOException|Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result === false) {
|
||||
$this->error(__('No rows were inserted'));
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param $ids
|
||||
* @return string
|
||||
* @throws DbException
|
||||
* @throws \think\Exception
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
$row = $this->model->get($ids);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
if (false === $this->request->isPost()) {
|
||||
$this->view->assign('row', $row);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
$params = $this->request->post('row/a');
|
||||
if (empty($params)) {
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
|
||||
if(count(explode(',',$params['tag'])) > 3){
|
||||
$this->error(__("Set up to three tag",''));
|
||||
}
|
||||
|
||||
$params = $this->preExcludeFields($params);
|
||||
$result = false;
|
||||
Db::startTrans();
|
||||
try {
|
||||
//是否采用模型验证
|
||||
if ($this->modelValidate) {
|
||||
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
|
||||
$row->validateFailException()->validate($validate);
|
||||
}
|
||||
|
||||
if(!empty($params['status'])){
|
||||
$params['publishedtime'] = strtotime($params['publishedtime']);
|
||||
}
|
||||
|
||||
if(!empty($params['status'] && $params['status'] == 'Scheduled')){
|
||||
$params['publishedtime'] = '';
|
||||
}
|
||||
|
||||
$result = $row->allowField(true)->save($params);
|
||||
Db::commit();
|
||||
} catch (ValidateException|PDOException|Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if (false === $result) {
|
||||
$this->error(__('No rows were updated'));
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param $ids
|
||||
* @return void
|
||||
* @throws DbException
|
||||
* @throws DataNotFoundException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function del($ids = null)
|
||||
{
|
||||
if (false === $this->request->isPost()) {
|
||||
$this->error(__("Invalid parameters"));
|
||||
}
|
||||
$ids = $ids ?: $this->request->post("ids");
|
||||
if (empty($ids)) {
|
||||
$this->error(__('Parameter %s can not be empty', 'ids'));
|
||||
}
|
||||
$pk = $this->model->getPk();
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (is_array($adminIds)) {
|
||||
$this->model->where($this->dataLimitField, 'in', $adminIds);
|
||||
}
|
||||
$list = $this->model->where($pk, 'in', $ids)->select();
|
||||
|
||||
$count = 0;
|
||||
Db::startTrans();
|
||||
try {
|
||||
foreach ($list as $item) {
|
||||
$count += $item->save(['status'=>'Delisted']);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (PDOException|Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($count) {
|
||||
$this->success();
|
||||
}
|
||||
$this->error(__('No rows were deleted'));
|
||||
}
|
||||
|
||||
//Sync Medium
|
||||
public function syncMedium($ids){
|
||||
|
||||
//get research info
|
||||
$info = $this->model->where(['id'=>$ids])->find();
|
||||
|
||||
//get Medium Tokens
|
||||
$tokens = config('site.medium_tokens');
|
||||
|
||||
//get user info
|
||||
$id = Cache::get('medium_userid');
|
||||
if(!$id){
|
||||
$res = $this->getUser($tokens);
|
||||
if(!isset($res['data'])){
|
||||
$this->error(__('Sync failed One'));
|
||||
}
|
||||
$id = $res['data']['id'];
|
||||
Cache::set('medium_userid',$res['data']['id']);
|
||||
}
|
||||
|
||||
//create posts
|
||||
$header = [
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization:Bearer '.$tokens,
|
||||
'Content-Type:application/json',
|
||||
]
|
||||
];
|
||||
$data = [
|
||||
'title' =>$info['title'],
|
||||
'contentFormat' =>'html',
|
||||
'content' =>$info['content'],
|
||||
'publishStatus' =>'public',
|
||||
];
|
||||
|
||||
if(!empty($info['tag'])){
|
||||
$data['tags'] = explode(',',$info['tag']);
|
||||
}
|
||||
|
||||
$api = 'https://api.medium.com/v1/users/'.$id.'/posts';
|
||||
$res = Http::post($api,json_encode($data),$header);
|
||||
$res_arr = json_decode($res,true);
|
||||
|
||||
if(!isset($res['data'])){
|
||||
$this->success(__('Sync succeeded'));
|
||||
}else{
|
||||
$this->error(__('Sync failed Two'));
|
||||
}
|
||||
}
|
||||
|
||||
//get user info
|
||||
public function getUser($tokens){
|
||||
$api = 'https://api.medium.com/v1/me';
|
||||
$header = [CURLOPT_HTTPHEADER => ['Authorization:Bearer '.$tokens]];
|
||||
$res = Http::get($api,[],$header);
|
||||
$res_arr = json_decode($res,true);
|
||||
return $res_arr;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* newsletter
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Newsletter extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Newsletter模型对象
|
||||
* @var \app\admin\model\Newsletter
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\Newsletter;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* page
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Page extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Page模型对象
|
||||
* @var \app\admin\model\Page
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\Page;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* share
|
||||
*
|
||||
* @icon fa fa-share
|
||||
*/
|
||||
class Share extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Share模型对象
|
||||
* @var \app\admin\model\Share
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\Share;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* team
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Team extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Team模型对象
|
||||
* @var \app\admin\model\Team
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\Team;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use app\admin\model\AuthGroup;
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* 管理员日志
|
||||
*
|
||||
* @icon fa fa-users
|
||||
* @remark 管理员可以查看自己所拥有的权限的管理员日志
|
||||
*/
|
||||
class Adminlog extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \app\admin\model\AdminLog
|
||||
*/
|
||||
protected $model = null;
|
||||
protected $childrenGroupIds = [];
|
||||
protected $childrenAdminIds = [];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('AdminLog');
|
||||
|
||||
$this->childrenAdminIds = $this->auth->getChildrenAdminIds(true);
|
||||
$this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
|
||||
|
||||
$groupName = AuthGroup::where('id', 'in', $this->childrenGroupIds)
|
||||
->column('id,name');
|
||||
|
||||
$this->view->assign('groupdata', $groupName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//设置过滤方法
|
||||
$this->request->filter(['strip_tags', 'trim']);
|
||||
if ($this->request->isAjax()) {
|
||||
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
|
||||
$list = $this->model
|
||||
->where($where)
|
||||
->where('admin_id', 'in', $this->childrenAdminIds)
|
||||
->order($sort, $order)
|
||||
->paginate($limit);
|
||||
|
||||
$result = array("total" => $list->total(), "rows" => $list->items());
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
public function detail($ids)
|
||||
{
|
||||
$row = $this->model->get(['id' => $ids]);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
if (!$row['admin_id'] || !in_array($row['admin_id'], $this->childrenAdminIds)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
$this->view->assign("row", $row->toArray());
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @internal
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$this->error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @internal
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
$this->error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
public function del($ids = "")
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error(__("Invalid parameters"));
|
||||
}
|
||||
$ids = $ids ? $ids : $this->request->post("ids");
|
||||
if ($ids) {
|
||||
$adminList = $this->model->where('id', 'in', $ids)->where('admin_id', 'in', $this->childrenAdminIds)->select();
|
||||
if ($adminList) {
|
||||
$deleteIds = [];
|
||||
foreach ($adminList as $k => $v) {
|
||||
$deleteIds[] = $v->id;
|
||||
}
|
||||
if ($deleteIds) {
|
||||
$this->model->destroy($deleteIds);
|
||||
$this->success();
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新
|
||||
* @internal
|
||||
*/
|
||||
public function multi($ids = "")
|
||||
{
|
||||
// 管理员禁止批量操作
|
||||
$this->error();
|
||||
}
|
||||
|
||||
public function selectpage()
|
||||
{
|
||||
return parent::selectpage();
|
||||
}
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use app\admin\model\AuthGroup;
|
||||
use app\common\controller\Backend;
|
||||
use fast\Tree;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 角色组
|
||||
*
|
||||
* @icon fa fa-group
|
||||
* @remark 角色组可以有多个,角色有上下级层级关系,如果子角色有角色组和管理员的权限则可以派生属于自己组别下级的角色组或管理员
|
||||
*/
|
||||
class Group extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \app\admin\model\AuthGroup
|
||||
*/
|
||||
protected $model = null;
|
||||
//当前登录管理员所有子组别
|
||||
protected $childrenGroupIds = [];
|
||||
//当前组别列表数据
|
||||
protected $grouplist = [];
|
||||
protected $groupdata = [];
|
||||
//无需要权限判断的方法
|
||||
protected $noNeedRight = ['roletree'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('AuthGroup');
|
||||
|
||||
$this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
|
||||
|
||||
$groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
|
||||
|
||||
Tree::instance()->init($groupList);
|
||||
$groupList = [];
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
$groupList = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
|
||||
} else {
|
||||
$groups = $this->auth->getGroups();
|
||||
$groupIds = [];
|
||||
foreach ($groups as $m => $n) {
|
||||
if (in_array($n['id'], $groupIds) || in_array($n['pid'], $groupIds)) {
|
||||
continue;
|
||||
}
|
||||
$groupList = array_merge($groupList, Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['pid'])));
|
||||
foreach ($groupList as $index => $item) {
|
||||
$groupIds[] = $item['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$groupName = [];
|
||||
foreach ($groupList as $k => $v) {
|
||||
$groupName[$v['id']] = $v['name'];
|
||||
}
|
||||
|
||||
$this->grouplist = $groupList;
|
||||
$this->groupdata = $groupName;
|
||||
$this->assignconfig("admin", ['id' => $this->auth->id, 'group_ids' => $this->auth->getGroupIds()]);
|
||||
|
||||
$this->view->assign('groupdata', $this->groupdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$list = $this->grouplist;
|
||||
$total = count($list);
|
||||
$result = array("total" => $total, "rows" => $list);
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
$params = $this->request->post("row/a", [], 'strip_tags');
|
||||
$params['rules'] = explode(',', $params['rules']);
|
||||
if (!in_array($params['pid'], $this->childrenGroupIds)) {
|
||||
$this->error(__('The parent group exceeds permission limit'));
|
||||
}
|
||||
$parentmodel = model("AuthGroup")->get($params['pid']);
|
||||
if (!$parentmodel) {
|
||||
$this->error(__('The parent group can not found'));
|
||||
}
|
||||
// 父级别的规则节点
|
||||
$parentrules = explode(',', $parentmodel->rules);
|
||||
// 当前组别的规则节点
|
||||
$currentrules = $this->auth->getRuleIds();
|
||||
$rules = $params['rules'];
|
||||
// 如果父组不是超级管理员则需要过滤规则节点,不能超过父组别的权限
|
||||
$rules = in_array('*', $parentrules) ? $rules : array_intersect($parentrules, $rules);
|
||||
// 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
|
||||
$rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
|
||||
$params['rules'] = implode(',', $rules);
|
||||
if ($params) {
|
||||
$this->model->create($params);
|
||||
$this->success();
|
||||
}
|
||||
$this->error();
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
if (!in_array($ids, $this->childrenGroupIds)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
$row = $this->model->get(['id' => $ids]);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
$params = $this->request->post("row/a", [], 'strip_tags');
|
||||
//父节点不能是非权限内节点
|
||||
if (!in_array($params['pid'], $this->childrenGroupIds)) {
|
||||
$this->error(__('The parent group exceeds permission limit'));
|
||||
}
|
||||
// 父节点不能是它自身的子节点或自己本身
|
||||
if (in_array($params['pid'], Tree::instance()->getChildrenIds($row->id, true))) {
|
||||
$this->error(__('The parent group can not be its own child or itself'));
|
||||
}
|
||||
$params['rules'] = explode(',', $params['rules']);
|
||||
|
||||
$parentmodel = model("AuthGroup")->get($params['pid']);
|
||||
if (!$parentmodel) {
|
||||
$this->error(__('The parent group can not found'));
|
||||
}
|
||||
// 父级别的规则节点
|
||||
$parentrules = explode(',', $parentmodel->rules);
|
||||
// 当前组别的规则节点
|
||||
$currentrules = $this->auth->getRuleIds();
|
||||
$rules = $params['rules'];
|
||||
// 如果父组不是超级管理员则需要过滤规则节点,不能超过父组别的权限
|
||||
$rules = in_array('*', $parentrules) ? $rules : array_intersect($parentrules, $rules);
|
||||
// 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
|
||||
$rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
|
||||
$params['rules'] = implode(',', $rules);
|
||||
if ($params) {
|
||||
Db::startTrans();
|
||||
try {
|
||||
$row->save($params);
|
||||
$children_auth_groups = model("AuthGroup")->all(['id' => ['in', implode(',', (Tree::instance()->getChildrenIds($row->id)))]]);
|
||||
$childparams = [];
|
||||
foreach ($children_auth_groups as $key => $children_auth_group) {
|
||||
$childparams[$key]['id'] = $children_auth_group->id;
|
||||
$childparams[$key]['rules'] = implode(',', array_intersect(explode(',', $children_auth_group->rules), $rules));
|
||||
}
|
||||
model("AuthGroup")->saveAll($childparams);
|
||||
Db::commit();
|
||||
$this->success();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
$this->error();
|
||||
return;
|
||||
}
|
||||
$this->view->assign("row", $row);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
public function del($ids = "")
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error(__("Invalid parameters"));
|
||||
}
|
||||
$ids = $ids ? $ids : $this->request->post("ids");
|
||||
if ($ids) {
|
||||
$ids = explode(',', $ids);
|
||||
$grouplist = $this->auth->getGroups();
|
||||
$group_ids = array_map(function ($group) {
|
||||
return $group['id'];
|
||||
}, $grouplist);
|
||||
// 移除掉当前管理员所在组别
|
||||
$ids = array_diff($ids, $group_ids);
|
||||
|
||||
// 循环判断每一个组别是否可删除
|
||||
$grouplist = $this->model->where('id', 'in', $ids)->select();
|
||||
$groupaccessmodel = model('AuthGroupAccess');
|
||||
foreach ($grouplist as $k => $v) {
|
||||
// 当前组别下有管理员
|
||||
$groupone = $groupaccessmodel->get(['group_id' => $v['id']]);
|
||||
if ($groupone) {
|
||||
$ids = array_diff($ids, [$v['id']]);
|
||||
continue;
|
||||
}
|
||||
// 当前组别下有子组别
|
||||
$groupone = $this->model->get(['pid' => $v['id']]);
|
||||
if ($groupone) {
|
||||
$ids = array_diff($ids, [$v['id']]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!$ids) {
|
||||
$this->error(__('You can not delete group that contain child group and administrators'));
|
||||
}
|
||||
$count = $this->model->where('id', 'in', $ids)->delete();
|
||||
if ($count) {
|
||||
$this->success();
|
||||
}
|
||||
}
|
||||
$this->error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新
|
||||
* @internal
|
||||
*/
|
||||
public function multi($ids = "")
|
||||
{
|
||||
// 组别禁止批量操作
|
||||
$this->error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取角色权限树
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function roletree()
|
||||
{
|
||||
$this->loadlang('auth/group');
|
||||
|
||||
$model = model('AuthGroup');
|
||||
$id = $this->request->post("id");
|
||||
$pid = $this->request->post("pid");
|
||||
$parentGroupModel = $model->get($pid);
|
||||
$currentGroupModel = null;
|
||||
if ($id) {
|
||||
$currentGroupModel = $model->get($id);
|
||||
}
|
||||
if (($pid || $parentGroupModel) && (!$id || $currentGroupModel)) {
|
||||
$id = $id ? $id : null;
|
||||
$ruleList = collection(model('AuthRule')->order('weigh', 'desc')->order('id', 'asc')->select())->toArray();
|
||||
//读取父类角色所有节点列表
|
||||
$parentRuleList = [];
|
||||
if (in_array('*', explode(',', $parentGroupModel->rules))) {
|
||||
$parentRuleList = $ruleList;
|
||||
} else {
|
||||
$parentRuleIds = explode(',', $parentGroupModel->rules);
|
||||
foreach ($ruleList as $k => $v) {
|
||||
if (in_array($v['id'], $parentRuleIds)) {
|
||||
$parentRuleList[] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ruleTree = new Tree();
|
||||
$groupTree = new Tree();
|
||||
//当前所有正常规则列表
|
||||
$ruleTree->init($parentRuleList);
|
||||
//角色组列表
|
||||
$groupTree->init(collection(model('AuthGroup')->where('id', 'in', $this->childrenGroupIds)->select())->toArray());
|
||||
|
||||
//读取当前角色下规则ID集合
|
||||
$adminRuleIds = $this->auth->getRuleIds();
|
||||
//是否是超级管理员
|
||||
$superadmin = $this->auth->isSuperAdmin();
|
||||
//当前拥有的规则ID集合
|
||||
$currentRuleIds = $id ? explode(',', $currentGroupModel->rules) : [];
|
||||
|
||||
if (!$id || !in_array($pid, $this->childrenGroupIds) || !in_array($pid, $groupTree->getChildrenIds($id, true))) {
|
||||
$parentRuleList = $ruleTree->getTreeList($ruleTree->getTreeArray(0), 'name');
|
||||
$hasChildrens = [];
|
||||
foreach ($parentRuleList as $k => $v) {
|
||||
if ($v['haschild']) {
|
||||
$hasChildrens[] = $v['id'];
|
||||
}
|
||||
}
|
||||
$parentRuleIds = array_map(function ($item) {
|
||||
return $item['id'];
|
||||
}, $parentRuleList);
|
||||
$nodeList = [];
|
||||
foreach ($parentRuleList as $k => $v) {
|
||||
if (!$superadmin && !in_array($v['id'], $adminRuleIds)) {
|
||||
continue;
|
||||
}
|
||||
if ($v['pid'] && !in_array($v['pid'], $parentRuleIds)) {
|
||||
continue;
|
||||
}
|
||||
$state = array('selected' => in_array($v['id'], $currentRuleIds) && !in_array($v['id'], $hasChildrens));
|
||||
$nodeList[] = array('id' => $v['id'], 'parent' => $v['pid'] ? $v['pid'] : '#', 'text' => __($v['title']), 'type' => 'menu', 'state' => $state);
|
||||
}
|
||||
$this->success('', null, $nodeList);
|
||||
} else {
|
||||
$this->error(__('Can not change the parent to child'));
|
||||
}
|
||||
} else {
|
||||
$this->error(__('Group not found'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\auth;
|
||||
|
||||
use app\admin\model\AuthRule;
|
||||
use app\common\controller\Backend;
|
||||
use fast\Tree;
|
||||
use think\Cache;
|
||||
|
||||
/**
|
||||
* 规则管理
|
||||
*
|
||||
* @icon fa fa-list
|
||||
* @remark 规则通常对应一个控制器的方法,同时左侧的菜单栏数据也从规则中体现,通常建议通过控制台进行生成规则节点
|
||||
*/
|
||||
class Rule extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \app\admin\model\AuthRule
|
||||
*/
|
||||
protected $model = null;
|
||||
protected $rulelist = [];
|
||||
protected $multiFields = 'ismenu,status';
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
$this->error(__('Access is allowed only to the super management group'));
|
||||
}
|
||||
$this->model = model('AuthRule');
|
||||
// 必须将结果集转换为数组
|
||||
$ruleList = \think\Db::name("auth_rule")->field('type,condition,remark,createtime,updatetime', true)->order('weigh DESC,id ASC')->select();
|
||||
foreach ($ruleList as $k => &$v) {
|
||||
$v['title'] = __($v['title']);
|
||||
}
|
||||
unset($v);
|
||||
Tree::instance()->init($ruleList);
|
||||
$this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
|
||||
$ruledata = [0 => __('None')];
|
||||
foreach ($this->rulelist as $k => &$v) {
|
||||
if (!$v['ismenu']) {
|
||||
continue;
|
||||
}
|
||||
$ruledata[$v['id']] = $v['title'];
|
||||
unset($v['spacer']);
|
||||
}
|
||||
unset($v);
|
||||
$this->view->assign('ruledata', $ruledata);
|
||||
$this->view->assign("menutypeList", $this->model->getMenutypeList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$list = $this->rulelist;
|
||||
$total = count($this->rulelist);
|
||||
$result = array("total" => $total, "rows" => $list);
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
$params = $this->request->post("row/a", [], 'strip_tags');
|
||||
if ($params) {
|
||||
if (!$params['ismenu'] && !$params['pid']) {
|
||||
$this->error(__('The non-menu rule must have parent'));
|
||||
}
|
||||
$result = $this->model->validate()->save($params);
|
||||
if ($result === false) {
|
||||
$this->error($this->model->getError());
|
||||
}
|
||||
Cache::rm('__menu__');
|
||||
$this->success();
|
||||
}
|
||||
$this->error();
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
$row = $this->model->get(['id' => $ids]);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
$params = $this->request->post("row/a", [], 'strip_tags');
|
||||
if ($params) {
|
||||
if (!$params['ismenu'] && !$params['pid']) {
|
||||
$this->error(__('The non-menu rule must have parent'));
|
||||
}
|
||||
if ($params['pid'] == $row['id']) {
|
||||
$this->error(__('Can not change the parent to self'));
|
||||
}
|
||||
if ($params['pid'] != $row['pid']) {
|
||||
$childrenIds = Tree::instance()->init(collection(AuthRule::select())->toArray())->getChildrenIds($row['id']);
|
||||
if (in_array($params['pid'], $childrenIds)) {
|
||||
$this->error(__('Can not change the parent to child'));
|
||||
}
|
||||
}
|
||||
//这里需要针对name做唯一验证
|
||||
$ruleValidate = \think\Loader::validate('AuthRule');
|
||||
$ruleValidate->rule([
|
||||
'name' => 'require|unique:AuthRule,name,' . $row->id,
|
||||
]);
|
||||
$result = $row->validate()->save($params);
|
||||
if ($result === false) {
|
||||
$this->error($row->getError());
|
||||
}
|
||||
Cache::rm('__menu__');
|
||||
$this->success();
|
||||
}
|
||||
$this->error();
|
||||
}
|
||||
$this->view->assign("row", $row);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
public function del($ids = "")
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error(__("Invalid parameters"));
|
||||
}
|
||||
$ids = $ids ? $ids : $this->request->post("ids");
|
||||
if ($ids) {
|
||||
$delIds = [];
|
||||
foreach (explode(',', $ids) as $k => $v) {
|
||||
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
|
||||
}
|
||||
$delIds = array_unique($delIds);
|
||||
$count = $this->model->where('id', 'in', $delIds)->delete();
|
||||
if ($count) {
|
||||
Cache::rm('__menu__');
|
||||
$this->success();
|
||||
}
|
||||
}
|
||||
$this->error();
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\general;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* 附件管理
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
* @remark 主要用于管理上传到服务器或第三方存储的数据
|
||||
*/
|
||||
class Attachment extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \app\common\model\Attachment
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
protected $searchFields = 'id,filename,url';
|
||||
protected $noNeedRight = ['classify'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('Attachment');
|
||||
$this->view->assign("mimetypeList", \app\common\model\Attachment::getMimetypeList());
|
||||
$this->view->assign("categoryList", \app\common\model\Attachment::getCategoryList());
|
||||
$this->assignconfig("categoryList", \app\common\model\Attachment::getCategoryList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//设置过滤方法
|
||||
$this->request->filter(['strip_tags', 'trim']);
|
||||
if ($this->request->isAjax()) {
|
||||
$mimetypeQuery = [];
|
||||
$filter = $this->request->request('filter');
|
||||
$filterArr = (array)json_decode($filter, true);
|
||||
if (isset($filterArr['category']) && $filterArr['category'] == 'unclassed') {
|
||||
$filterArr['category'] = ',unclassed';
|
||||
$this->request->get(['filter' => json_encode(array_diff_key($filterArr, ['category' => '']))]);
|
||||
}
|
||||
if (isset($filterArr['mimetype']) && preg_match("/(\/|\,|\*)/", $filterArr['mimetype'])) {
|
||||
$mimetype = $filterArr['mimetype'];
|
||||
$filterArr = array_diff_key($filterArr, ['mimetype' => '']);
|
||||
$mimetypeQuery = function ($query) use ($mimetype) {
|
||||
$mimetypeArr = array_filter(explode(',', $mimetype));
|
||||
foreach ($mimetypeArr as $index => $item) {
|
||||
$query->whereOr('mimetype', 'like', '%' . str_replace("/*", "/", $item) . '%');
|
||||
}
|
||||
};
|
||||
}
|
||||
$this->request->get(['filter' => json_encode($filterArr)]);
|
||||
|
||||
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
|
||||
|
||||
$list = $this->model
|
||||
->where($mimetypeQuery)
|
||||
->where($where)
|
||||
->order($sort, $order)
|
||||
->paginate($limit);
|
||||
|
||||
$cdnurl = preg_replace("/\/(\w+)\.php$/i", '', $this->request->root());
|
||||
foreach ($list as $k => &$v) {
|
||||
$v['fullurl'] = ($v['storage'] == 'local' ? $cdnurl : $this->view->config['upload']['cdnurl']) . $v['url'];
|
||||
}
|
||||
unset($v);
|
||||
$result = array("total" => $list->total(), "rows" => $list->items());
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择附件
|
||||
*/
|
||||
public function select()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
return $this->index();
|
||||
}
|
||||
$mimetype = $this->request->get('mimetype', '');
|
||||
$mimetype = substr($mimetype, -1) === '/' ? $mimetype . '*' : $mimetype;
|
||||
$this->view->assign('mimetype', $mimetype);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$this->error();
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除附件
|
||||
* @param array $ids
|
||||
*/
|
||||
public function del($ids = "")
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error(__("Invalid parameters"));
|
||||
}
|
||||
$ids = $ids ? $ids : $this->request->post("ids");
|
||||
if ($ids) {
|
||||
\think\Hook::add('upload_delete', function ($params) {
|
||||
if ($params['storage'] == 'local') {
|
||||
$attachmentFile = ROOT_PATH . '/public' . $params['url'];
|
||||
if (is_file($attachmentFile)) {
|
||||
@unlink($attachmentFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
$attachmentlist = $this->model->where('id', 'in', $ids)->select();
|
||||
foreach ($attachmentlist as $attachment) {
|
||||
\think\Hook::listen("upload_delete", $attachment);
|
||||
$attachment->delete();
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
$this->error(__('Parameter %s can not be empty', 'ids'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 归类
|
||||
*/
|
||||
public function classify()
|
||||
{
|
||||
if (!$this->auth->check('general/attachment/edit')) {
|
||||
\think\Hook::listen('admin_nopermission', $this);
|
||||
$this->error(__('You have no permission'), '');
|
||||
}
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error(__("Invalid parameters"));
|
||||
}
|
||||
$category = $this->request->post('category', '');
|
||||
$ids = $this->request->post('ids');
|
||||
if (!$ids) {
|
||||
$this->error(__('Parameter %s can not be empty', 'ids'));
|
||||
}
|
||||
$categoryList = \app\common\model\Attachment::getCategoryList();
|
||||
if ($category && !isset($categoryList[$category])) {
|
||||
$this->error(__('Category not found'));
|
||||
}
|
||||
$category = $category == 'unclassed' ? '' : $category;
|
||||
\app\common\model\Attachment::where('id', 'in', $ids)->update(['category' => $category]);
|
||||
$this->success();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\general;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\library\Email;
|
||||
use app\common\model\Config as ConfigModel;
|
||||
use think\Cache;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 系统配置
|
||||
*
|
||||
* @icon fa fa-cogs
|
||||
* @remark 可以在此增改系统的变量和分组,也可以自定义分组和变量,如果需要删除请从数据库中删除
|
||||
*/
|
||||
class Config extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \app\common\model\Config
|
||||
*/
|
||||
protected $model = null;
|
||||
protected $noNeedRight = ['check', 'rulelist', 'selectpage', 'get_fields_list'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
// $this->model = model('Config');
|
||||
$this->model = new ConfigModel;
|
||||
ConfigModel::event('before_write', function ($row) {
|
||||
if (isset($row['name']) && $row['name'] == 'name' && preg_match("/fast" . "admin/i", $row['value'])) {
|
||||
throw new Exception(__("Site name incorrect"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$siteList = [];
|
||||
$groupList = ConfigModel::getGroupList();
|
||||
foreach ($groupList as $k => $v) {
|
||||
$siteList[$k]['name'] = $k;
|
||||
$siteList[$k]['title'] = $v;
|
||||
$siteList[$k]['list'] = [];
|
||||
}
|
||||
|
||||
foreach ($this->model->all() as $k => $v) {
|
||||
if (!isset($siteList[$v['group']])) {
|
||||
continue;
|
||||
}
|
||||
$value = $v->toArray();
|
||||
$value['title'] = __($value['title']);
|
||||
if (in_array($value['type'], ['select', 'selects', 'checkbox', 'radio'])) {
|
||||
$value['value'] = explode(',', $value['value']);
|
||||
}
|
||||
$value['content'] = json_decode($value['content'], true);
|
||||
if (in_array($value['name'], ['categorytype', 'configgroup', 'attachmentcategory'])) {
|
||||
$dictValue = (array)json_decode($value['value'], true);
|
||||
foreach ($dictValue as $index => &$item) {
|
||||
$item = __($item);
|
||||
}
|
||||
unset($item);
|
||||
$value['value'] = json_encode($dictValue, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
$value['tip'] = htmlspecialchars($value['tip']);
|
||||
if ($value['name'] == 'cdnurl') {
|
||||
//cdnurl不支持在线修改
|
||||
continue;
|
||||
}
|
||||
$siteList[$v['group']]['list'][] = $value;
|
||||
}
|
||||
$index = 0;
|
||||
foreach ($siteList as $k => &$v) {
|
||||
$v['active'] = !$index ? true : false;
|
||||
$index++;
|
||||
}
|
||||
$this->view->assign('siteList', $siteList);
|
||||
$this->view->assign('typeList', ConfigModel::getTypeList());
|
||||
$this->view->assign('ruleList', ConfigModel::getRegexList());
|
||||
$this->view->assign('groupList', ConfigModel::getGroupList());
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if (!config('app_debug')) {
|
||||
$this->error(__('Only work at development environment'));
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
$params = $this->request->post("row/a", [], 'trim');
|
||||
if ($params) {
|
||||
foreach ($params as $k => &$v) {
|
||||
$v = is_array($v) && $k !== 'setting' ? implode(',', $v) : $v;
|
||||
}
|
||||
if (in_array($params['type'], ['select', 'selects', 'checkbox', 'radio', 'array'])) {
|
||||
$params['content'] = json_encode(ConfigModel::decode($params['content']), JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$params['content'] = '';
|
||||
}
|
||||
try {
|
||||
$result = $this->model->create($params);
|
||||
} catch (Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result !== false) {
|
||||
try {
|
||||
ConfigModel::refreshFile();
|
||||
} catch (Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success();
|
||||
} else {
|
||||
$this->error($this->model->getError());
|
||||
}
|
||||
}
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @param null $ids
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
$row = $this->request->post("row/a", [], 'trim');
|
||||
if ($row) {
|
||||
$configList = [];
|
||||
foreach ($this->model->all() as $v) {
|
||||
if (isset($row[$v['name']])) {
|
||||
$value = $row[$v['name']];
|
||||
if (is_array($value) && isset($value['field'])) {
|
||||
$value = json_encode(ConfigModel::getArrayData($value), JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$value = is_array($value) ? implode(',', $value) : $value;
|
||||
}
|
||||
$v['value'] = $value;
|
||||
$configList[] = $v->toArray();
|
||||
}
|
||||
}
|
||||
try {
|
||||
$this->model->allowField(true)->saveAll($configList);
|
||||
} catch (Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
try {
|
||||
ConfigModel::refreshFile();
|
||||
} catch (Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param string $ids
|
||||
*/
|
||||
public function del($ids = "")
|
||||
{
|
||||
if (!config('app_debug')) {
|
||||
$this->error(__('Only work at development environment'));
|
||||
}
|
||||
$name = $this->request->post('name');
|
||||
$config = ConfigModel::getByName($name);
|
||||
if ($name && $config) {
|
||||
try {
|
||||
$config->delete();
|
||||
ConfigModel::refreshFile();
|
||||
} catch (Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success();
|
||||
} else {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测配置项是否存在
|
||||
* @internal
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$params = $this->request->post("row/a");
|
||||
if ($params) {
|
||||
$config = $this->model->get($params);
|
||||
if (!$config) {
|
||||
$this->success();
|
||||
} else {
|
||||
$this->error(__('Name already exist'));
|
||||
}
|
||||
} else {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则列表
|
||||
* @internal
|
||||
*/
|
||||
public function rulelist()
|
||||
{
|
||||
//主键
|
||||
$primarykey = $this->request->request("keyField");
|
||||
//主键值
|
||||
$keyValue = $this->request->request("keyValue", "");
|
||||
|
||||
$keyValueArr = array_filter(explode(',', $keyValue));
|
||||
$regexList = \app\common\model\Config::getRegexList();
|
||||
$list = [];
|
||||
foreach ($regexList as $k => $v) {
|
||||
if ($keyValueArr) {
|
||||
if (in_array($k, $keyValueArr)) {
|
||||
$list[] = ['id' => $k, 'name' => $v];
|
||||
}
|
||||
} else {
|
||||
$list[] = ['id' => $k, 'name' => $v];
|
||||
}
|
||||
}
|
||||
return json(['list' => $list]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送测试邮件
|
||||
* @internal
|
||||
*/
|
||||
public function emailtest()
|
||||
{
|
||||
$row = $this->request->post('row/a');
|
||||
$receiver = $this->request->post("receiver");
|
||||
if ($receiver) {
|
||||
if (!Validate::is($receiver, "email")) {
|
||||
$this->error(__('Please input correct email'));
|
||||
}
|
||||
\think\Config::set('site', array_merge(\think\Config::get('site'), $row));
|
||||
$email = new Email;
|
||||
$result = $email
|
||||
->to($receiver)
|
||||
->subject(__("This is a test mail", config('site.name')))
|
||||
->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content', config('site.name')) . '</div>')
|
||||
->send();
|
||||
if ($result) {
|
||||
$this->success();
|
||||
} else {
|
||||
$this->error($email->getError());
|
||||
}
|
||||
} else {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
}
|
||||
|
||||
public function selectpage()
|
||||
{
|
||||
$id = $this->request->get("id/d");
|
||||
$config = \app\common\model\Config::get($id);
|
||||
if (!$config) {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
$setting = $config['setting'];
|
||||
//自定义条件
|
||||
$custom = isset($setting['conditions']) ? (array)json_decode($setting['conditions'], true) : [];
|
||||
$custom = array_filter($custom);
|
||||
|
||||
$this->request->request(['showField' => $setting['field'], 'keyField' => $setting['primarykey'], 'custom' => $custom, 'searchField' => [$setting['field'], $setting['primarykey']]]);
|
||||
$this->model = \think\Db::connect()->setTable($setting['table']);
|
||||
return parent::selectpage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表列表
|
||||
* @internal
|
||||
*/
|
||||
public function get_table_list()
|
||||
{
|
||||
$tableList = [];
|
||||
$dbname = \think\Config::get('database.database');
|
||||
$tableList = \think\Db::query("SELECT `TABLE_NAME` AS `name`,`TABLE_COMMENT` AS `title` FROM `information_schema`.`TABLES` where `TABLE_SCHEMA` = '{$dbname}';");
|
||||
$this->success('', null, ['tableList' => $tableList]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表字段列表
|
||||
* @internal
|
||||
*/
|
||||
public function get_fields_list()
|
||||
{
|
||||
$table = $this->request->request('table');
|
||||
$dbname = \think\Config::get('database.database');
|
||||
//从数据库中获取表字段信息
|
||||
$sql = "SELECT `COLUMN_NAME` AS `name`,`COLUMN_COMMENT` AS `title`,`DATA_TYPE` AS `type` FROM `information_schema`.`columns` WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION";
|
||||
//加载主表的列
|
||||
$fieldList = Db::query($sql, [$dbname, $table]);
|
||||
$this->success("", null, ['fieldList' => $fieldList]);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\portfolio;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* portfolio indsutry
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Indsutry extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Indsutry模型对象
|
||||
* @var \app\admin\model\portfolio\Indsutry
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\portfolio\Indsutry;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\research;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* author
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Author extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Author模型对象
|
||||
* @var \app\admin\model\research\Author
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\research\Author;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\research;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* research topic
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Topic extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Topic模型对象
|
||||
* @var \app\admin\model\research\Topic
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\research\Topic;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* 会员组管理
|
||||
*
|
||||
* @icon fa fa-users
|
||||
*/
|
||||
class Group extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \app\admin\model\UserGroup
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('UserGroup');
|
||||
$this->view->assign("statusList", $this->model->getStatusList());
|
||||
}
|
||||
|
||||
public function add()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
}
|
||||
$nodeList = \app\admin\model\UserRule::getTreeList();
|
||||
$this->assign("nodeList", $nodeList);
|
||||
return parent::add();
|
||||
}
|
||||
|
||||
public function edit($ids = null)
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
}
|
||||
$row = $this->model->get($ids);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
$rules = explode(',', $row['rules']);
|
||||
$nodeList = \app\admin\model\UserRule::getTreeList($rules);
|
||||
$this->assign("nodeList", $nodeList);
|
||||
return parent::edit($ids);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use fast\Tree;
|
||||
|
||||
/**
|
||||
* 会员规则管理
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Rule extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \app\admin\model\UserRule
|
||||
*/
|
||||
protected $model = null;
|
||||
protected $rulelist = [];
|
||||
protected $multiFields = 'ismenu,status';
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('UserRule');
|
||||
$this->view->assign("statusList", $this->model->getStatusList());
|
||||
// 必须将结果集转换为数组
|
||||
$ruleList = collection($this->model->order('weigh', 'desc')->select())->toArray();
|
||||
foreach ($ruleList as $k => &$v) {
|
||||
$v['title'] = __($v['title']);
|
||||
$v['remark'] = __($v['remark']);
|
||||
}
|
||||
unset($v);
|
||||
Tree::instance()->init($ruleList);
|
||||
$this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
|
||||
$ruledata = [0 => __('None')];
|
||||
foreach ($this->rulelist as $k => &$v) {
|
||||
if (!$v['ismenu']) {
|
||||
continue;
|
||||
}
|
||||
$ruledata[$v['id']] = $v['title'];
|
||||
}
|
||||
$this->view->assign('ruledata', $ruledata);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$list = $this->rulelist;
|
||||
$total = count($this->rulelist);
|
||||
|
||||
$result = array("total" => $total, "rows" => $list);
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
}
|
||||
return parent::add();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->token();
|
||||
}
|
||||
return parent::edit($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
public function del($ids = "")
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error(__("Invalid parameters"));
|
||||
}
|
||||
$ids = $ids ? $ids : $this->request->post("ids");
|
||||
if ($ids) {
|
||||
$delIds = [];
|
||||
foreach (explode(',', $ids) as $k => $v) {
|
||||
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
|
||||
}
|
||||
$delIds = array_unique($delIds);
|
||||
$count = $this->model->where('id', 'in', $delIds)->delete();
|
||||
if ($count) {
|
||||
$this->success();
|
||||
}
|
||||
}
|
||||
$this->error();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Id' => 'ID',
|
||||
'Email' => 'Email',
|
||||
'Createtime' => 'Subscribed Date'
|
||||
];
|
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Id' => 'ID',
|
||||
'Avatar' => 'avatar',
|
||||
'Name' => 'name',
|
||||
'Description' => 'description',
|
||||
'Sort' => 'Sort'
|
||||
];
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Id' => 'ID',
|
||||
'Title' => 'Title',
|
||||
'Image' => 'Image',
|
||||
'Link' => 'Link'
|
||||
];
|
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
return [];
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'The parent group can not be its own child' => '父组别不能是自身的子组别',
|
||||
'The parent group can not found' => '父组别未找到',
|
||||
'Group not found' => '组别未找到',
|
||||
'Can not change the parent to child' => '父组别不能是它的子组别',
|
||||
'Can not change the parent to self' => '父组别不能是它自己',
|
||||
'You can not delete group that contain child group and administrators' => '你不能删除含有子组和管理员的组',
|
||||
'The parent group exceeds permission limit' => '父组别超出权限范围',
|
||||
'The parent group can not be its own child or itself' => '父组别不能是它的子组别及本身',
|
||||
];
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Toggle all' => '显示全部',
|
||||
'Condition' => '规则条件',
|
||||
'Remark' => '备注',
|
||||
'Icon' => '图标',
|
||||
'Alert' => '警告',
|
||||
'Name' => '规则',
|
||||
'Controller/Action' => '控制器名/方法名',
|
||||
'Ismenu' => '菜单',
|
||||
'Menutype' => '菜单类型',
|
||||
'Addtabs' => '选项卡(默认)',
|
||||
'Dialog' => '弹窗',
|
||||
'Ajax' => 'Ajax请求',
|
||||
'Blank' => '链接',
|
||||
'Extend' => '扩展属性',
|
||||
'Search icon' => '搜索图标',
|
||||
'Toggle menu visible' => '点击切换菜单显示',
|
||||
'Toggle sub menu' => '点击切换子菜单',
|
||||
'Menu tips' => '父级菜单无需匹配控制器和方法,子级菜单请使用控制器名',
|
||||
'Node tips' => '控制器/方法名,如果有目录请使用 目录名/控制器名/方法名',
|
||||
'Url tips' => '一般情况下留空即可,如果是外部链接或相对链接请输入',
|
||||
'The non-menu rule must have parent' => '非菜单规则节点必须有父级',
|
||||
'Can not change the parent to child' => '父级不能是它的子级',
|
||||
'Can not change the parent to self' => '父级不能是它自己',
|
||||
'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
|
||||
];
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Id' => 'ID',
|
||||
'Pid' => '父ID',
|
||||
'Type' => '类型',
|
||||
'All' => '全部',
|
||||
'Image' => '图片',
|
||||
'Keywords' => '关键字',
|
||||
'Description' => '描述',
|
||||
'Diyname' => '自定义名称',
|
||||
'Createtime' => '创建时间',
|
||||
'Updatetime' => '更新时间',
|
||||
'Weigh' => '权重',
|
||||
'Category warmtips' => '温馨提示:栏目类型请前往<b>常规管理</b>-><b>系统配置</b>-><b>字典配置</b>中进行管理',
|
||||
'Can not change the parent to child or itself' => '父组别不能是它的子组别或它自己',
|
||||
'Status' => '状态'
|
||||
];
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Id' => 'ID',
|
||||
'Type' => '类型',
|
||||
'Params' => '参数',
|
||||
'Command' => '命令',
|
||||
'Content' => '返回结果',
|
||||
'Executetime' => '执行时间',
|
||||
'Createtime' => '创建时间',
|
||||
'Updatetime' => '更新时间',
|
||||
'Execute again' => '再次执行',
|
||||
'Successed' => '成功',
|
||||
'Failured' => '失败',
|
||||
'Status' => '状态'
|
||||
];
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue