作者 王晓刚

基本完成

要显示太多修改。

为保证性能只显示 37 of 37+ 个文件。

  1 +{
  2 + "directory" : "public/assets/libs",
  3 + "ignoredDependencies": [
  4 + "file-saver",
  5 + "html2canvas",
  6 + "jspdf",
  7 + "jspdf-autotable"
  8 + ]
  9 +}
  1 +[app]
  2 +debug = false
  3 +trace = false
  4 +
  5 +[database]
  6 +hostname = 127.0.0.1
  7 +database = fastadmin
  8 +username = root
  9 +password = root
  10 +hostport = 3306
  11 +prefix = fa_
  1 +runtime
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<module type="WEB_MODULE" version="4">
  3 + <component name="NewModuleRootManager">
  4 + <content url="file://$MODULE_DIR$">
  5 + <excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
  6 + <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
  7 + <excludeFolder url="file://$MODULE_DIR$/vendor/endroid/qr-code" />
  8 + <excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
  9 + <excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
  10 + <excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
  11 + <excludeFolder url="file://$MODULE_DIR$/vendor/karsonzhang/fastadmin-addons" />
  12 + <excludeFolder url="file://$MODULE_DIR$/vendor/markbaker/complex" />
  13 + <excludeFolder url="file://$MODULE_DIR$/vendor/markbaker/matrix" />
  14 + <excludeFolder url="file://$MODULE_DIR$/vendor/monolog/monolog" />
  15 + <excludeFolder url="file://$MODULE_DIR$/vendor/mtdowling/cron-expression" />
  16 + <excludeFolder url="file://$MODULE_DIR$/vendor/overtrue/pinyin" />
  17 + <excludeFolder url="file://$MODULE_DIR$/vendor/overtrue/socialite" />
  18 + <excludeFolder url="file://$MODULE_DIR$/vendor/overtrue/wechat" />
  19 + <excludeFolder url="file://$MODULE_DIR$/vendor/paragonie/random_compat" />
  20 + <excludeFolder url="file://$MODULE_DIR$/vendor/phpmailer/phpmailer" />
  21 + <excludeFolder url="file://$MODULE_DIR$/vendor/phpoffice/phpspreadsheet" />
  22 + <excludeFolder url="file://$MODULE_DIR$/vendor/pimple/pimple" />
  23 + <excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
  24 + <excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
  25 + <excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
  26 + <excludeFolder url="file://$MODULE_DIR$/vendor/psr/simple-cache" />
  27 + <excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
  28 + <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
  29 + <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
  30 + <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
  31 + <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php70" />
  32 + <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/psr-http-message-bridge" />
  33 + <excludeFolder url="file://$MODULE_DIR$/vendor/topthink/think-captcha" />
  34 + <excludeFolder url="file://$MODULE_DIR$/vendor/topthink/think-helper" />
  35 + <excludeFolder url="file://$MODULE_DIR$/vendor/topthink/think-installer" />
  36 + </content>
  37 + <orderEntry type="inheritedJdk" />
  38 + <orderEntry type="sourceFolder" forTests="false" />
  39 + </component>
  40 +</module>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="JavaScriptSettings">
  4 + <option name="languageLevel" value="ES6" />
  5 + </component>
  6 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="ProjectModuleManager">
  4 + <modules>
  5 + <module fileurl="file://$PROJECT_DIR$/.idea/adverh5.iml" filepath="$PROJECT_DIR$/.idea/adverh5.iml" />
  6 + </modules>
  7 + </component>
  8 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="PhpIncludePathManager">
  4 + <include_path>
  5 + <path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
  6 + <path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
  7 + <path value="$PROJECT_DIR$/vendor/symfony/psr-http-message-bridge" />
  8 + <path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
  9 + <path value="$PROJECT_DIR$/vendor/phpmailer/phpmailer" />
  10 + <path value="$PROJECT_DIR$/vendor/psr/container" />
  11 + <path value="$PROJECT_DIR$/vendor/symfony/polyfill-php70" />
  12 + <path value="$PROJECT_DIR$/vendor/psr/log" />
  13 + <path value="$PROJECT_DIR$/vendor/psr/http-message" />
  14 + <path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
  15 + <path value="$PROJECT_DIR$/vendor/topthink/think-captcha" />
  16 + <path value="$PROJECT_DIR$/vendor/doctrine/cache" />
  17 + <path value="$PROJECT_DIR$/vendor/topthink/think-helper" />
  18 + <path value="$PROJECT_DIR$/vendor/topthink/think-installer" />
  19 + <path value="$PROJECT_DIR$/vendor/overtrue/socialite" />
  20 + <path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
  21 + <path value="$PROJECT_DIR$/vendor/overtrue/wechat" />
  22 + <path value="$PROJECT_DIR$/vendor/overtrue/pinyin" />
  23 + <path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
  24 + <path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
  25 + <path value="$PROJECT_DIR$/vendor/mtdowling/cron-expression" />
  26 + <path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
  27 + <path value="$PROJECT_DIR$/vendor/endroid/qr-code" />
  28 + <path value="$PROJECT_DIR$/vendor/monolog/monolog" />
  29 + <path value="$PROJECT_DIR$/vendor/paragonie/random_compat" />
  30 + <path value="$PROJECT_DIR$/vendor/pimple/pimple" />
  31 + <path value="$PROJECT_DIR$/vendor/composer" />
  32 + <path value="$PROJECT_DIR$/vendor/markbaker/matrix" />
  33 + <path value="$PROJECT_DIR$/vendor/markbaker/complex" />
  34 + <path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
  35 + <path value="$PROJECT_DIR$/vendor/karsonzhang/fastadmin-addons" />
  36 + </include_path>
  37 + </component>
  38 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="VcsDirectoryMappings">
  4 + <mapping directory="$PROJECT_DIR$" vcs="Git" />
  5 + </component>
  6 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="ChangeListManager">
  4 + <list default="true" id="3bd62d6a-e12b-4534-8418-e8a5d7424d73" name="默认的" comment="">
  5 + <change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
  6 + <change afterPath="$PROJECT_DIR$/application/home/controller/Index.php" afterDir="false" />
  7 + <change afterPath="$PROJECT_DIR$/application/home/view/index/index.html" afterDir="false" />
  8 + <change afterPath="$PROJECT_DIR$/application/home/view/index/login.html" afterDir="false" />
  9 + <change afterPath="$PROJECT_DIR$/public/.gitignore" afterDir="false" />
  10 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/css/index.css" afterDir="false" />
  11 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/css/login.css" afterDir="false" />
  12 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/css/public.css" afterDir="false" />
  13 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/1.png" afterDir="false" />
  14 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/10.png" afterDir="false" />
  15 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/2.png" afterDir="false" />
  16 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/3.png" afterDir="false" />
  17 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/4.png" afterDir="false" />
  18 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/5.png" afterDir="false" />
  19 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/6.png" afterDir="false" />
  20 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/7.png" afterDir="false" />
  21 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/8.png" afterDir="false" />
  22 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/9.png" afterDir="false" />
  23 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/bg1.png" afterDir="false" />
  24 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/bg2.png" afterDir="false" />
  25 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/img/image.png" afterDir="false" />
  26 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/js/base.js" afterDir="false" />
  27 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/js/jquery.min.js" afterDir="false" />
  28 + <change afterPath="$PROJECT_DIR$/public/assets/adverh5/js/public.js" afterDir="false" />
  29 + </list>
  30 + <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
  31 + <option name="TRACKING_ENABLED" value="true" />
  32 + <option name="SHOW_DIALOG" value="false" />
  33 + <option name="HIGHLIGHT_CONFLICTS" value="true" />
  34 + <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
  35 + <option name="LAST_RESOLUTION" value="IGNORE" />
  36 + </component>
  37 + <component name="ComposerSettings" synchronizationState="SYNCHRONIZE">
  38 + <pharConfigPath>$PROJECT_DIR$/composer.json</pharConfigPath>
  39 + </component>
  40 + <component name="FileEditorManager">
  41 + <leaf>
  42 + <file leaf-file-name="Index.php" pinned="false" current-in-tab="true">
  43 + <entry file="file://$PROJECT_DIR$/application/home/controller/Index.php">
  44 + <provider selected="true" editor-type-id="text-editor">
  45 + <state relative-caret-position="442">
  46 + <caret line="31" column="5" lean-forward="true" selection-start-line="31" selection-start-column="5" selection-end-line="31" selection-end-column="5" />
  47 + <folding>
  48 + <element signature="e#128#163#0#PHP" expanded="true" />
  49 + </folding>
  50 + </state>
  51 + </provider>
  52 + </entry>
  53 + </file>
  54 + <file leaf-file-name="config.php" pinned="false" current-in-tab="false">
  55 + <entry file="file://$PROJECT_DIR$/application/config.php">
  56 + <provider selected="true" editor-type-id="text-editor">
  57 + <state relative-caret-position="477">
  58 + <caret line="63" column="38" selection-start-line="63" selection-start-column="38" selection-end-line="63" selection-end-column="38" />
  59 + </state>
  60 + </provider>
  61 + </entry>
  62 + </file>
  63 + <file leaf-file-name="login.html" pinned="false" current-in-tab="false">
  64 + <entry file="file://$PROJECT_DIR$/application/home/view/index/login.html">
  65 + <provider selected="true" editor-type-id="text-editor">
  66 + <state relative-caret-position="487">
  67 + <caret line="75" column="32" selection-start-line="75" selection-start-column="32" selection-end-line="75" selection-end-column="32" />
  68 + </state>
  69 + </provider>
  70 + </entry>
  71 + </file>
  72 + <file leaf-file-name="public.js" pinned="false" current-in-tab="false">
  73 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/js/public.js">
  74 + <provider selected="true" editor-type-id="text-editor">
  75 + <state relative-caret-position="102">
  76 + <caret line="6" column="1" selection-start-line="6" selection-start-column="1" selection-end-line="6" selection-end-column="1" />
  77 + </state>
  78 + </provider>
  79 + </entry>
  80 + </file>
  81 + <file leaf-file-name="login.css" pinned="false" current-in-tab="false">
  82 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/css/login.css">
  83 + <provider selected="true" editor-type-id="text-editor">
  84 + <state relative-caret-position="289">
  85 + <caret line="17" column="4" selection-start-line="17" selection-start-column="4" selection-end-line="19" selection-end-column="24" />
  86 + </state>
  87 + </provider>
  88 + </entry>
  89 + </file>
  90 + <file leaf-file-name="index.html" pinned="false" current-in-tab="false">
  91 + <entry file="file://$PROJECT_DIR$/application/home/view/index/index.html">
  92 + <provider selected="true" editor-type-id="text-editor">
  93 + <state relative-caret-position="2210">
  94 + <caret line="130" column="52" selection-start-line="130" selection-start-column="52" selection-end-line="130" selection-end-column="52" />
  95 + <folding>
  96 + <element signature="n#style#0;n#div#5;n#div#2;n#div#0;n#body#0;n#html#0;n#!!top" expanded="true" />
  97 + </folding>
  98 + </state>
  99 + </provider>
  100 + </entry>
  101 + </file>
  102 + <file leaf-file-name="index.css" pinned="false" current-in-tab="false">
  103 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/css/index.css">
  104 + <provider selected="true" editor-type-id="text-editor">
  105 + <state relative-caret-position="679">
  106 + <caret line="116" column="22" selection-start-line="116" selection-start-column="22" selection-end-line="116" selection-end-column="22" />
  107 + </state>
  108 + </provider>
  109 + </entry>
  110 + </file>
  111 + </leaf>
  112 + </component>
  113 + <component name="FileTemplateManagerImpl">
  114 + <option name="RECENT_TEMPLATES">
  115 + <list>
  116 + <option value="HTML File" />
  117 + <option value="CSS File" />
  118 + <option value="JavaScript File" />
  119 + </list>
  120 + </option>
  121 + </component>
  122 + <component name="FindInProjectRecents">
  123 + <findStrings>
  124 + <find>siet</find>
  125 + <find>$site</find>
  126 + <find>site</find>
  127 + <find>box-bottom</find>
  128 + <find>dis</find>
  129 + </findStrings>
  130 + </component>
  131 + <component name="Git.Settings">
  132 + <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
  133 + </component>
  134 + <component name="IdeDocumentHistory">
  135 + <option name="CHANGED_PATHS">
  136 + <list>
  137 + <option value="$PROJECT_DIR$/application/index/view/home/index.html" />
  138 + <option value="$PROJECT_DIR$/public/assets/adverh5/js/base.js" />
  139 + <option value="$PROJECT_DIR$/public/assets/adverh5/css/public.css" />
  140 + <option value="$PROJECT_DIR$/application/admin/view/password/index.html" />
  141 + <option value="$PROJECT_DIR$/public/assets/js/backend/password.js" />
  142 + <option value="$PROJECT_DIR$/public/assets/js/backend/goods.js" />
  143 + <option value="$PROJECT_DIR$/application/admin/view/goods/index.html" />
  144 + <option value="$PROJECT_DIR$/application/admin/view/dashboard/index.html" />
  145 + <option value="$PROJECT_DIR$/application/admin/view/index/login.html" />
  146 + <option value="$PROJECT_DIR$/application/admin/view/index/index.html" />
  147 + <option value="$PROJECT_DIR$/application/admin/view/common/header.html" />
  148 + <option value="$PROJECT_DIR$/application/admin/view/common/menu.html" />
  149 + <option value="$PROJECT_DIR$/application/extra/site.php" />
  150 + <option value="$PROJECT_DIR$/public/assets/adverh5/js/public.js" />
  151 + <option value="$PROJECT_DIR$/public/assets/adverh5/css/login.css" />
  152 + <option value="$PROJECT_DIR$/application/home/view/index/login.html" />
  153 + <option value="$PROJECT_DIR$/application/home/controller/Index.php" />
  154 + <option value="$PROJECT_DIR$/application/home/view/index/index.html" />
  155 + <option value="$PROJECT_DIR$/public/assets/adverh5/css/index.css" />
  156 + <option value="$PROJECT_DIR$/application/config.php" />
  157 + </list>
  158 + </option>
  159 + </component>
  160 + <component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
  161 + <component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
  162 + <component name="JsGulpfileManager">
  163 + <detection-done>true</detection-done>
  164 + <sorting>DEFINITION_ORDER</sorting>
  165 + </component>
  166 + <component name="NodePackageJsonFileManager">
  167 + <packageJsonPaths>
  168 + <path value="$PROJECT_DIR$/public/assets/libs/Sortable/package.json" />
  169 + <path value="$PROJECT_DIR$/public/assets/libs/art-template/loader/package.json" />
  170 + <path value="$PROJECT_DIR$/public/assets/libs/art-template/package.json" />
  171 + <path value="$PROJECT_DIR$/public/assets/libs/bootstrap-daterangepicker/package.json" />
  172 + <path value="$PROJECT_DIR$/public/assets/libs/bootstrap-table/package.json" />
  173 + <path value="$PROJECT_DIR$/public/assets/libs/bootstrap/package.json" />
  174 + <path value="$PROJECT_DIR$/public/assets/libs/eonasdan-bootstrap-datetimepicker/package.json" />
  175 + <path value="$PROJECT_DIR$/public/assets/libs/fastadmin-citypicker/package.json" />
  176 + <path value="$PROJECT_DIR$/public/assets/libs/fastadmin-cxselect/package.json" />
  177 + <path value="$PROJECT_DIR$/public/assets/libs/fastadmin-layer/package.json" />
  178 + <path value="$PROJECT_DIR$/public/assets/libs/jcrop/package.json" />
  179 + <path value="$PROJECT_DIR$/public/assets/libs/jquery-slimscroll/package.json" />
  180 + <path value="$PROJECT_DIR$/public/assets/libs/require-css/package.json" />
  181 + </packageJsonPaths>
  182 + </component>
  183 + <component name="PhpWorkspaceProjectConfiguration">
  184 + <include_path>
  185 + <path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
  186 + <path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
  187 + <path value="$PROJECT_DIR$/vendor/symfony/psr-http-message-bridge" />
  188 + <path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
  189 + <path value="$PROJECT_DIR$/vendor/phpmailer/phpmailer" />
  190 + <path value="$PROJECT_DIR$/vendor/psr/container" />
  191 + <path value="$PROJECT_DIR$/vendor/symfony/polyfill-php70" />
  192 + <path value="$PROJECT_DIR$/vendor/psr/log" />
  193 + <path value="$PROJECT_DIR$/vendor/psr/http-message" />
  194 + <path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
  195 + <path value="$PROJECT_DIR$/vendor/topthink/think-captcha" />
  196 + <path value="$PROJECT_DIR$/vendor/doctrine/cache" />
  197 + <path value="$PROJECT_DIR$/vendor/topthink/think-helper" />
  198 + <path value="$PROJECT_DIR$/vendor/topthink/think-installer" />
  199 + <path value="$PROJECT_DIR$/vendor/overtrue/socialite" />
  200 + <path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
  201 + <path value="$PROJECT_DIR$/vendor/overtrue/wechat" />
  202 + <path value="$PROJECT_DIR$/vendor/overtrue/pinyin" />
  203 + <path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
  204 + <path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
  205 + <path value="$PROJECT_DIR$/vendor/mtdowling/cron-expression" />
  206 + <path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
  207 + <path value="$PROJECT_DIR$/vendor/endroid/qr-code" />
  208 + <path value="$PROJECT_DIR$/vendor/monolog/monolog" />
  209 + <path value="$PROJECT_DIR$/vendor/paragonie/random_compat" />
  210 + <path value="$PROJECT_DIR$/vendor/pimple/pimple" />
  211 + <path value="$PROJECT_DIR$/vendor/composer" />
  212 + <path value="$PROJECT_DIR$/vendor/markbaker/matrix" />
  213 + <path value="$PROJECT_DIR$/vendor/markbaker/complex" />
  214 + <path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
  215 + <path value="$PROJECT_DIR$/vendor/karsonzhang/fastadmin-addons" />
  216 + </include_path>
  217 + </component>
  218 + <component name="ProjectFrameBounds" extendedState="6">
  219 + <option name="x" value="863" />
  220 + <option name="y" value="28" />
  221 + <option name="width" value="960" />
  222 + <option name="height" value="991" />
  223 + </component>
  224 + <component name="ProjectView">
  225 + <navigator proportions="" version="1">
  226 + <foldersAlwaysOnTop value="true" />
  227 + </navigator>
  228 + <panes>
  229 + <pane id="ProjectPane">
  230 + <subPane>
  231 + <expand>
  232 + <path>
  233 + <item name="adverh5" type="b2602c69:ProjectViewProjectNode" />
  234 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  235 + </path>
  236 + <path>
  237 + <item name="adverh5" type="b2602c69:ProjectViewProjectNode" />
  238 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  239 + <item name="application" type="462c0819:PsiDirectoryNode" />
  240 + </path>
  241 + <path>
  242 + <item name="adverh5" type="b2602c69:ProjectViewProjectNode" />
  243 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  244 + <item name="application" type="462c0819:PsiDirectoryNode" />
  245 + <item name="extra" type="462c0819:PsiDirectoryNode" />
  246 + </path>
  247 + <path>
  248 + <item name="adverh5" type="b2602c69:ProjectViewProjectNode" />
  249 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  250 + <item name="public" type="462c0819:PsiDirectoryNode" />
  251 + </path>
  252 + <path>
  253 + <item name="adverh5" type="b2602c69:ProjectViewProjectNode" />
  254 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  255 + <item name="public" type="462c0819:PsiDirectoryNode" />
  256 + <item name="assets" type="462c0819:PsiDirectoryNode" />
  257 + </path>
  258 + <path>
  259 + <item name="adverh5" type="b2602c69:ProjectViewProjectNode" />
  260 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  261 + <item name="public" type="462c0819:PsiDirectoryNode" />
  262 + <item name="assets" type="462c0819:PsiDirectoryNode" />
  263 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  264 + </path>
  265 + <path>
  266 + <item name="adverh5" type="b2602c69:ProjectViewProjectNode" />
  267 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  268 + <item name="public" type="462c0819:PsiDirectoryNode" />
  269 + <item name="assets" type="462c0819:PsiDirectoryNode" />
  270 + <item name="adverh5" type="462c0819:PsiDirectoryNode" />
  271 + <item name="js" type="462c0819:PsiDirectoryNode" />
  272 + </path>
  273 + </expand>
  274 + <select />
  275 + </subPane>
  276 + </pane>
  277 + <pane id="Scope" />
  278 + </panes>
  279 + </component>
  280 + <component name="PropertiesComponent">
  281 + <property name="DefaultHtmlFileTemplate" value="HTML File" />
  282 + <property name="WebServerToolWindowFactoryState" value="false" />
  283 + <property name="last_opened_file_path" value="D:/phpStudy/WWW/weight" />
  284 + <property name="list.type.of.created.stylesheet" value="CSS" />
  285 + <property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
  286 + <property name="nodejs_npm_path_reset_for_default_project" value="true" />
  287 + <property name="settings.editor.selected.configurable" value="terminal" />
  288 + </component>
  289 + <component name="RecentsManager">
  290 + <key name="MoveFile.RECENT_KEYS">
  291 + <recent name="D:\wamp\www\adverh5\application\home\view" />
  292 + <recent name="D:\wamp\www\adverh5\application\index\view\home\index" />
  293 + </key>
  294 + <key name="CopyFile.RECENT_KEYS">
  295 + <recent name="D:\wamp\www\adverh5\public\assets\adverh5\css" />
  296 + <recent name="D:\wamp\www\adverh5\public\assets\adverh5\img" />
  297 + <recent name="D:\wamp\www\adverh5\application\home\view\index" />
  298 + <recent name="D:\wamp\www\adverh5\public\assets\adverh5\js" />
  299 + <recent name="D:\wamp\www\adverh5\public" />
  300 + </key>
  301 + </component>
  302 + <component name="RunDashboard">
  303 + <option name="ruleStates">
  304 + <list>
  305 + <RuleState>
  306 + <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
  307 + </RuleState>
  308 + <RuleState>
  309 + <option name="name" value="StatusDashboardGroupingRule" />
  310 + </RuleState>
  311 + </list>
  312 + </option>
  313 + </component>
  314 + <component name="SvnConfiguration">
  315 + <configuration />
  316 + </component>
  317 + <component name="TaskManager">
  318 + <task active="true" id="Default" summary="Default task">
  319 + <changelist id="3bd62d6a-e12b-4534-8418-e8a5d7424d73" name="默认的" comment="" />
  320 + <created>1571368480027</created>
  321 + <option name="number" value="Default" />
  322 + <option name="presentableId" value="Default" />
  323 + <updated>1571368480027</updated>
  324 + <workItem from="1571368481262" duration="23015000" />
  325 + </task>
  326 + <servers />
  327 + </component>
  328 + <component name="TimeTrackingManager">
  329 + <option name="totallyTimeSpent" value="23015000" />
  330 + </component>
  331 + <component name="ToolWindowManager">
  332 + <frame x="-8" y="-8" width="1936" height="1056" extended-state="6" />
  333 + <editor active="true" />
  334 + <layout>
  335 + <window_info anchor="bottom" id="TODO" order="6" />
  336 + <window_info anchor="bottom" id="调试" />
  337 + <window_info anchor="bottom" id="Event Log" side_tool="true" />
  338 + <window_info anchor="bottom" id="Database Changes" show_stripe_button="false" />
  339 + <window_info anchor="bottom" id="Version Control" show_stripe_button="false" />
  340 + <window_info anchor="bottom" id="运行" />
  341 + <window_info anchor="bottom" id="Terminal" />
  342 + <window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.14658849" />
  343 + <window_info anchor="bottom" id="Docker" show_stripe_button="false" />
  344 + <window_info anchor="right" id="Database" />
  345 + <window_info id="Structure" order="1" side_tool="true" weight="0.25" />
  346 + <window_info id="Favorites" side_tool="true" />
  347 + <window_info anchor="bottom" id="找到" />
  348 + <window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
  349 + <window_info anchor="bottom" id="Find" order="1" />
  350 + <window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
  351 + <window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
  352 + <window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
  353 + <window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
  354 + <window_info anchor="bottom" id="Run" order="2" />
  355 + <window_info anchor="bottom" id="Message" order="0" />
  356 + <window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
  357 + </layout>
  358 + </component>
  359 + <component name="TypeScriptGeneratedFilesManager">
  360 + <option name="version" value="1" />
  361 + </component>
  362 + <component name="VcsContentAnnotationSettings">
  363 + <option name="myLimit" value="2678400000" />
  364 + </component>
  365 + <component name="editorHistoryManager">
  366 + <entry file="file://$PROJECT_DIR$/.gitignore">
  367 + <provider selected="true" editor-type-id="text-editor" />
  368 + </entry>
  369 + <entry file="file://$PROJECT_DIR$/application/index/controller/Index.php">
  370 + <provider selected="true" editor-type-id="text-editor" />
  371 + </entry>
  372 + <entry file="file://$PROJECT_DIR$/application/common/controller/Frontend.php">
  373 + <provider selected="true" editor-type-id="text-editor">
  374 + <state relative-caret-position="153">
  375 + <caret line="14" column="6" selection-start-line="14" selection-start-column="6" selection-end-line="14" selection-end-column="6" />
  376 + </state>
  377 + </provider>
  378 + </entry>
  379 + <entry file="file://$PROJECT_DIR$/public/index.php">
  380 + <provider selected="true" editor-type-id="text-editor" />
  381 + </entry>
  382 + <entry file="file://$PROJECT_DIR$/public/.gitignore">
  383 + <provider selected="true" editor-type-id="text-editor">
  384 + <state>
  385 + <caret column="7" selection-start-column="7" selection-end-column="7" />
  386 + </state>
  387 + </provider>
  388 + </entry>
  389 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/img/10.png">
  390 + <provider selected="true" editor-type-id="images" />
  391 + </entry>
  392 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/img/9.png">
  393 + <provider selected="true" editor-type-id="images" />
  394 + </entry>
  395 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/js/base.js">
  396 + <provider selected="true" editor-type-id="text-editor">
  397 + <state relative-caret-position="340">
  398 + <caret line="20" column="10" selection-start-line="9" selection-end-line="20" selection-end-column="10" />
  399 + </state>
  400 + </provider>
  401 + </entry>
  402 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/css/public.css">
  403 + <provider selected="true" editor-type-id="text-editor">
  404 + <state relative-caret-position="221">
  405 + <caret line="13" column="1" selection-start-line="13" selection-start-column="1" selection-end-line="13" selection-end-column="1" />
  406 + </state>
  407 + </provider>
  408 + </entry>
  409 + <entry file="file://$PROJECT_DIR$/application/admin/view/password/index.html">
  410 + <provider selected="true" editor-type-id="text-editor">
  411 + <state relative-caret-position="340">
  412 + <caret line="20" column="33" selection-start-line="14" selection-start-column="24" selection-end-line="20" selection-end-column="33" />
  413 + </state>
  414 + </provider>
  415 + </entry>
  416 + <entry file="file://$PROJECT_DIR$/application/admin/view/goods/index.html">
  417 + <provider selected="true" editor-type-id="text-editor">
  418 + <state relative-caret-position="340">
  419 + <caret line="20" column="33" selection-start-line="14" selection-start-column="24" selection-end-line="20" selection-end-column="33" />
  420 + </state>
  421 + </provider>
  422 + </entry>
  423 + <entry file="file://$PROJECT_DIR$/public/assets/js/backend/goods.js">
  424 + <provider selected="true" editor-type-id="text-editor">
  425 + <state relative-caret-position="357">
  426 + <caret line="21" column="36" selection-start-line="21" selection-start-column="36" selection-end-line="21" selection-end-column="36" />
  427 + </state>
  428 + </provider>
  429 + </entry>
  430 + <entry file="file://$PROJECT_DIR$/public/assets/js/backend/password.js">
  431 + <provider selected="true" editor-type-id="text-editor">
  432 + <state relative-caret-position="340">
  433 + <caret line="20" column="16" selection-start-line="20" selection-start-column="16" selection-end-line="21" selection-end-column="36" />
  434 + </state>
  435 + </provider>
  436 + </entry>
  437 + <entry file="file://$PROJECT_DIR$/application/admin/view/index/login.html">
  438 + <provider selected="true" editor-type-id="text-editor">
  439 + <state relative-caret-position="626">
  440 + <caret line="100" column="88" lean-forward="true" selection-start-line="100" selection-start-column="88" selection-end-line="100" selection-end-column="97" />
  441 + </state>
  442 + </provider>
  443 + </entry>
  444 + <entry file="file://$PROJECT_DIR$/application/admin/view/index/index.html">
  445 + <provider selected="true" editor-type-id="text-editor">
  446 + <state relative-caret-position="504">
  447 + <caret line="34" column="43" selection-start-line="34" selection-start-column="39" selection-end-line="34" selection-end-column="43" />
  448 + </state>
  449 + </provider>
  450 + </entry>
  451 + <entry file="file://$PROJECT_DIR$/application/admin/view/common/script.html">
  452 + <provider selected="true" editor-type-id="text-editor" />
  453 + </entry>
  454 + <entry file="file://$PROJECT_DIR$/application/admin/view/common/control.html">
  455 + <provider selected="true" editor-type-id="text-editor" />
  456 + </entry>
  457 + <entry file="file://$PROJECT_DIR$/application/admin/view/common/meta.html">
  458 + <provider selected="true" editor-type-id="text-editor" />
  459 + </entry>
  460 + <entry file="file://$PROJECT_DIR$/application/admin/view/dashboard/index.html">
  461 + <provider selected="true" editor-type-id="text-editor">
  462 + <state relative-caret-position="2325">
  463 + <caret line="438" column="44" selection-start-line="435" selection-start-column="36" selection-end-line="438" selection-end-column="44" />
  464 + </state>
  465 + </provider>
  466 + </entry>
  467 + <entry file="file://$PROJECT_DIR$/application/admin/view/common/menu.html">
  468 + <provider selected="true" editor-type-id="text-editor">
  469 + <state relative-caret-position="136">
  470 + <caret line="8" column="32" selection-start-line="8" selection-start-column="32" selection-end-line="8" selection-end-column="32" />
  471 + </state>
  472 + </provider>
  473 + </entry>
  474 + <entry file="file://$PROJECT_DIR$/application/admin/view/common/header.html">
  475 + <provider selected="true" editor-type-id="text-editor">
  476 + <state relative-caret-position="51">
  477 + <caret line="3" column="34" selection-start-line="3" selection-start-column="29" selection-end-line="3" selection-end-column="34" />
  478 + </state>
  479 + </provider>
  480 + </entry>
  481 + <entry file="file://$PROJECT_DIR$/application/common.php">
  482 + <provider selected="true" editor-type-id="text-editor">
  483 + <state relative-caret-position="-306" />
  484 + </provider>
  485 + </entry>
  486 + <entry file="file://$PROJECT_DIR$/application/admin/config.php">
  487 + <provider selected="true" editor-type-id="text-editor" />
  488 + </entry>
  489 + <entry file="file://$PROJECT_DIR$/application/common/controller/Backend.php">
  490 + <provider selected="true" editor-type-id="text-editor">
  491 + <state relative-caret-position="271">
  492 + <caret line="186" column="13" selection-start-line="186" selection-start-column="8" selection-end-line="186" selection-end-column="13" />
  493 + </state>
  494 + </provider>
  495 + </entry>
  496 + <entry file="file://$PROJECT_DIR$/application/extra/site.php">
  497 + <provider selected="true" editor-type-id="text-editor">
  498 + <state relative-caret-position="51">
  499 + <caret line="3" column="17" selection-start-line="3" selection-start-column="17" selection-end-line="3" selection-end-column="17" />
  500 + </state>
  501 + </provider>
  502 + </entry>
  503 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/js/public.js">
  504 + <provider selected="true" editor-type-id="text-editor">
  505 + <state relative-caret-position="102">
  506 + <caret line="6" column="1" selection-start-line="6" selection-start-column="1" selection-end-line="6" selection-end-column="1" />
  507 + </state>
  508 + </provider>
  509 + </entry>
  510 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/css/index.css">
  511 + <provider selected="true" editor-type-id="text-editor">
  512 + <state relative-caret-position="679">
  513 + <caret line="116" column="22" selection-start-line="116" selection-start-column="22" selection-end-line="116" selection-end-column="22" />
  514 + </state>
  515 + </provider>
  516 + </entry>
  517 + <entry file="file://$PROJECT_DIR$/application/home/view/index/index.html">
  518 + <provider selected="true" editor-type-id="text-editor">
  519 + <state relative-caret-position="2210">
  520 + <caret line="130" column="52" selection-start-line="130" selection-start-column="52" selection-end-line="130" selection-end-column="52" />
  521 + <folding>
  522 + <element signature="n#style#0;n#div#5;n#div#2;n#div#0;n#body#0;n#html#0;n#!!top" expanded="true" />
  523 + </folding>
  524 + </state>
  525 + </provider>
  526 + </entry>
  527 + <entry file="file://$PROJECT_DIR$/public/assets/adverh5/css/login.css">
  528 + <provider selected="true" editor-type-id="text-editor">
  529 + <state relative-caret-position="289">
  530 + <caret line="17" column="4" selection-start-line="17" selection-start-column="4" selection-end-line="19" selection-end-column="24" />
  531 + </state>
  532 + </provider>
  533 + </entry>
  534 + <entry file="file://$PROJECT_DIR$/application/config.php">
  535 + <provider selected="true" editor-type-id="text-editor">
  536 + <state relative-caret-position="477">
  537 + <caret line="63" column="38" selection-start-line="63" selection-start-column="38" selection-end-line="63" selection-end-column="38" />
  538 + </state>
  539 + </provider>
  540 + </entry>
  541 + <entry file="file://$PROJECT_DIR$/application/home/view/index/login.html">
  542 + <provider selected="true" editor-type-id="text-editor">
  543 + <state relative-caret-position="487">
  544 + <caret line="75" column="32" selection-start-line="75" selection-start-column="32" selection-end-line="75" selection-end-column="32" />
  545 + </state>
  546 + </provider>
  547 + </entry>
  548 + <entry file="file://$PROJECT_DIR$/application/home/controller/Index.php">
  549 + <provider selected="true" editor-type-id="text-editor">
  550 + <state relative-caret-position="442">
  551 + <caret line="31" column="5" lean-forward="true" selection-start-line="31" selection-start-column="5" selection-end-line="31" selection-end-column="5" />
  552 + <folding>
  553 + <element signature="e#128#163#0#PHP" expanded="true" />
  554 + </folding>
  555 + </state>
  556 + </provider>
  557 + </entry>
  558 + </component>
  559 + <component name="masterDetails">
  560 + <states>
  561 + <state key="ScopeChooserConfigurable.UI">
  562 + <settings>
  563 + <splitter-proportions>
  564 + <option name="proportions">
  565 + <list>
  566 + <option value="0.2" />
  567 + </list>
  568 + </option>
  569 + </splitter-proportions>
  570 + </settings>
  571 + </state>
  572 + </states>
  573 + </component>
  574 +</project>
  1 +Apache License
  2 +Version 2.0, January 2004
  3 +http://www.apache.org/licenses/
  4 +
  5 +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 +
  7 +1. Definitions.
  8 +
  9 +"License" shall mean the terms and conditions for use, reproduction, and
  10 +distribution as defined by Sections 1 through 9 of this document.
  11 +
  12 +"Licensor" shall mean the copyright owner or entity authorized by the copyright
  13 +owner that is granting the License.
  14 +
  15 +"Legal Entity" shall mean the union of the acting entity and all other entities
  16 +that control, are controlled by, or are under common control with that entity.
  17 +For the purposes of this definition, "control" means (i) the power, direct or
  18 +indirect, to cause the direction or management of such entity, whether by
  19 +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
  20 +outstanding shares, or (iii) beneficial ownership of such entity.
  21 +
  22 +"You" (or "Your") shall mean an individual or Legal Entity exercising
  23 +permissions granted by this License.
  24 +
  25 +"Source" form shall mean the preferred form for making modifications, including
  26 +but not limited to software source code, documentation source, and configuration
  27 +files.
  28 +
  29 +"Object" form shall mean any form resulting from mechanical transformation or
  30 +translation of a Source form, including but not limited to compiled object code,
  31 +generated documentation, and conversions to other media types.
  32 +
  33 +"Work" shall mean the work of authorship, whether in Source or Object form, made
  34 +available under the License, as indicated by a copyright notice that is included
  35 +in or attached to the work (an example is provided in the Appendix below).
  36 +
  37 +"Derivative Works" shall mean any work, whether in Source or Object form, that
  38 +is based on (or derived from) the Work and for which the editorial revisions,
  39 +annotations, elaborations, or other modifications represent, as a whole, an
  40 +original work of authorship. For the purposes of this License, Derivative Works
  41 +shall not include works that remain separable from, or merely link (or bind by
  42 +name) to the interfaces of, the Work and Derivative Works thereof.
  43 +
  44 +"Contribution" shall mean any work of authorship, including the original version
  45 +of the Work and any modifications or additions to that Work or Derivative Works
  46 +thereof, that is intentionally submitted to Licensor for inclusion in the Work
  47 +by the copyright owner or by an individual or Legal Entity authorized to submit
  48 +on behalf of the copyright owner. For the purposes of this definition,
  49 +"submitted" means any form of electronic, verbal, or written communication sent
  50 +to the Licensor or its representatives, including but not limited to
  51 +communication on electronic mailing lists, source code control systems, and
  52 +issue tracking systems that are managed by, or on behalf of, the Licensor for
  53 +the purpose of discussing and improving the Work, but excluding communication
  54 +that is conspicuously marked or otherwise designated in writing by the copyright
  55 +owner as "Not a Contribution."
  56 +
  57 +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
  58 +of whom a Contribution has been received by Licensor and subsequently
  59 +incorporated within the Work.
  60 +
  61 +2. Grant of Copyright License.
  62 +
  63 +Subject to the terms and conditions of this License, each Contributor hereby
  64 +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
  65 +irrevocable copyright license to reproduce, prepare Derivative Works of,
  66 +publicly display, publicly perform, sublicense, and distribute the Work and such
  67 +Derivative Works in Source or Object form.
  68 +
  69 +3. Grant of Patent License.
  70 +
  71 +Subject to the terms and conditions of this License, each Contributor hereby
  72 +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
  73 +irrevocable (except as stated in this section) patent license to make, have
  74 +made, use, offer to sell, sell, import, and otherwise transfer the Work, where
  75 +such license applies only to those patent claims licensable by such Contributor
  76 +that are necessarily infringed by their Contribution(s) alone or by combination
  77 +of their Contribution(s) with the Work to which such Contribution(s) was
  78 +submitted. If You institute patent litigation against any entity (including a
  79 +cross-claim or counterclaim in a lawsuit) alleging that the Work or a
  80 +Contribution incorporated within the Work constitutes direct or contributory
  81 +patent infringement, then any patent licenses granted to You under this License
  82 +for that Work shall terminate as of the date such litigation is filed.
  83 +
  84 +4. Redistribution.
  85 +
  86 +You may reproduce and distribute copies of the Work or Derivative Works thereof
  87 +in any medium, with or without modifications, and in Source or Object form,
  88 +provided that You meet the following conditions:
  89 +
  90 +You must give any other recipients of the Work or Derivative Works a copy of
  91 +this License; and
  92 +You must cause any modified files to carry prominent notices stating that You
  93 +changed the files; and
  94 +You must retain, in the Source form of any Derivative Works that You distribute,
  95 +all copyright, patent, trademark, and attribution notices from the Source form
  96 +of the Work, excluding those notices that do not pertain to any part of the
  97 +Derivative Works; and
  98 +If the Work includes a "NOTICE" text file as part of its distribution, then any
  99 +Derivative Works that You distribute must include a readable copy of the
  100 +attribution notices contained within such NOTICE file, excluding those notices
  101 +that do not pertain to any part of the Derivative Works, in at least one of the
  102 +following places: within a NOTICE text file distributed as part of the
  103 +Derivative Works; within the Source form or documentation, if provided along
  104 +with the Derivative Works; or, within a display generated by the Derivative
  105 +Works, if and wherever such third-party notices normally appear. The contents of
  106 +the NOTICE file are for informational purposes only and do not modify the
  107 +License. You may add Your own attribution notices within Derivative Works that
  108 +You distribute, alongside or as an addendum to the NOTICE text from the Work,
  109 +provided that such additional attribution notices cannot be construed as
  110 +modifying the License.
  111 +You may add Your own copyright statement to Your modifications and may provide
  112 +additional or different license terms and conditions for use, reproduction, or
  113 +distribution of Your modifications, or for any such Derivative Works as a whole,
  114 +provided Your use, reproduction, and distribution of the Work otherwise complies
  115 +with the conditions stated in this License.
  116 +
  117 +5. Submission of Contributions.
  118 +
  119 +Unless You explicitly state otherwise, any Contribution intentionally submitted
  120 +for inclusion in the Work by You to the Licensor shall be under the terms and
  121 +conditions of this License, without any additional terms or conditions.
  122 +Notwithstanding the above, nothing herein shall supersede or modify the terms of
  123 +any separate license agreement you may have executed with Licensor regarding
  124 +such Contributions.
  125 +
  126 +6. Trademarks.
  127 +
  128 +This License does not grant permission to use the trade names, trademarks,
  129 +service marks, or product names of the Licensor, except as required for
  130 +reasonable and customary use in describing the origin of the Work and
  131 +reproducing the content of the NOTICE file.
  132 +
  133 +7. Disclaimer of Warranty.
  134 +
  135 +Unless required by applicable law or agreed to in writing, Licensor provides the
  136 +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
  137 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
  138 +including, without limitation, any warranties or conditions of TITLE,
  139 +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
  140 +solely responsible for determining the appropriateness of using or
  141 +redistributing the Work and assume any risks associated with Your exercise of
  142 +permissions under this License.
  143 +
  144 +8. Limitation of Liability.
  145 +
  146 +In no event and under no legal theory, whether in tort (including negligence),
  147 +contract, or otherwise, unless required by applicable law (such as deliberate
  148 +and grossly negligent acts) or agreed to in writing, shall any Contributor be
  149 +liable to You for damages, including any direct, indirect, special, incidental,
  150 +or consequential damages of any character arising as a result of this License or
  151 +out of the use or inability to use the Work (including but not limited to
  152 +damages for loss of goodwill, work stoppage, computer failure or malfunction, or
  153 +any and all other commercial damages or losses), even if such Contributor has
  154 +been advised of the possibility of such damages.
  155 +
  156 +9. Accepting Warranty or Additional Liability.
  157 +
  158 +While redistributing the Work or Derivative Works thereof, You may choose to
  159 +offer, and charge a fee for, acceptance of support, warranty, indemnity, or
  160 +other liability obligations and/or rights consistent with this License. However,
  161 +in accepting such obligations, You may act only on Your own behalf and on Your
  162 +sole responsibility, not on behalf of any other Contributor, and only if You
  163 +agree to indemnify, defend, and hold each Contributor harmless for any liability
  164 +incurred by, or claims asserted against, such Contributor by reason of your
  165 +accepting any such warranty or additional liability.
  166 +
  167 +END OF TERMS AND CONDITIONS
  168 +
  169 +APPENDIX: How to apply the Apache License to your work
  170 +
  171 +To apply the Apache License to your work, attach the following boilerplate
  172 +notice, with the fields enclosed by brackets "{}" replaced with your own
  173 +identifying information. (Don't include the brackets!) The text should be
  174 +enclosed in the appropriate comment syntax for the file format. We also
  175 +recommend that a file or class name and description of purpose be included on
  176 +the same "printed page" as the copyright notice for easier identification within
  177 +third-party archives.
  178 +
  179 + Copyright 2017 Karson
  180 +
  181 + Licensed under the Apache License, Version 2.0 (the "License");
  182 + you may not use this file except in compliance with the License.
  183 + You may obtain a copy of the License at
  184 +
  185 + http://www.apache.org/licenses/LICENSE-2.0
  186 +
  187 + Unless required by applicable law or agreed to in writing, software
  188 + distributed under the License is distributed on an "AS IS" BASIS,
  189 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  190 + See the License for the specific language governing permissions and
  191 + limitations under the License.
  1 +FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
  2 +
  3 +
  4 +## **主要特性**
  5 +
  6 +* 基于`Auth`验证的权限管理系统
  7 + * 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
  8 + * 支持单管理员多角色
  9 + * 支持管理子级数据或个人数据
  10 +* 强大的一键生成功能
  11 + * 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单、回收站等
  12 + * 一键压缩打包JS和CSS文件,一键CDN静态资源部署
  13 + * 一键生成控制器菜单和规则
  14 + * 一键生成API接口文档
  15 +* 完善的前端功能组件开发
  16 + * 基于`AdminLTE`二次开发
  17 + * 基于`Bootstrap`开发,自适应手机、平板、PC
  18 + * 基于`RequireJS`进行JS模块管理,按需加载
  19 + * 基于`Less`进行样式开发
  20 + * 基于`Bower`进行前端组件包管理
  21 +* 强大的插件扩展功能,在线安装卸载升级插件
  22 +* 通用的会员模块和API模块
  23 +* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
  24 +* 二级域名部署支持,同时域名支持绑定到插件
  25 +* 多语言支持,服务端及客户端支持
  26 +* 强大的第三方模块支持([CMS](https://www.fastadmin.net/store/cms.html)[博客](https://www.fastadmin.net/store/blog.html)[知识付费问答](https://www.fastadmin.net/store/ask.html))
  27 +* 整合第三方短信接口(阿里云、腾讯云短信)
  28 +* 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能
  29 +* 第三方富文本编辑器支持(Summernote、Kindeditor、百度编辑器)
  30 +* 第三方登录(QQ、微信、微博)整合
  31 +* 第三方支付(微信、支付宝)无缝整合,微信支持PC端扫码支付
  32 +* 丰富的插件应用市场
  33 +
  34 +## **安装使用**
  35 +
  36 +https://doc.fastadmin.net
  37 +
  38 +## **在线演示**
  39 +
  40 +https://demo.fastadmin.net
  41 +
  42 +用户名:admin
  43 +
  44 +密 码:123456
  45 +
  46 +提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
  47 +
  48 +## **界面截图**
  49 +![控制台](https://gitee.com/uploads/images/2017/0411/113717_e99ff3e7_10933.png "控制台")
  50 +
  51 +## **问题反馈**
  52 +
  53 +在使用中有任何问题,请使用以下联系方式联系我们
  54 +
  55 +交流社区: https://forum.fastadmin.net
  56 +
  57 +QQ群: [636393962](https://jq.qq.com/?_wv=1027&k=487PNBb)() [708784003](https://jq.qq.com/?_wv=1027&k=5ObjtwM)(满) [964776039](https://jq.qq.com/?_wv=1027&k=59qjU2P)(3群)
  58 +
  59 +Email: (karsonzhang#163.com, 把#换成@)
  60 +
  61 +Github: https://github.com/karsonzhang/fastadmin
  62 +
  63 +Gitee: https://gitee.com/karson/fastadmin
  64 +
  65 +## **特别鸣谢**
  66 +
  67 +感谢以下的项目,排名不分先后
  68 +
  69 +ThinkPHP:http://www.thinkphp.cn
  70 +
  71 +AdminLTE:https://adminlte.io
  72 +
  73 +Bootstrap:http://getbootstrap.com
  74 +
  75 +jQuery:http://jquery.com
  76 +
  77 +Bootstrap-table:https://github.com/wenzhixin/bootstrap-table
  78 +
  79 +Nice-validator: https://validator.niceue.com
  80 +
  81 +SelectPage: https://github.com/TerryZ/SelectPage
  82 +
  83 +
  84 +## **版权信息**
  85 +
  86 +FastAdmin遵循Apache2开源协议发布,并提供免费使用。
  87 +
  88 +本项目包含的第三方源码和二进制文件之版权信息另行标注。
  89 +
  90 +版权所有Copyright © 2017-2019 by FastAdmin (https://www.fastadmin.net)
  91 +
  92 +All rights reserved。
  1 +<?php
  2 +
  3 +namespace addons\command;
  4 +
  5 +use app\common\library\Menu;
  6 +use think\Addons;
  7 +
  8 +/**
  9 + * 在线命令插件
  10 + */
  11 +class Command extends Addons
  12 +{
  13 +
  14 + /**
  15 + * 插件安装方法
  16 + * @return bool
  17 + */
  18 + public function install()
  19 + {
  20 + $menu = [
  21 + [
  22 + 'name' => 'command',
  23 + 'title' => '在线命令管理',
  24 + 'icon' => 'fa fa-terminal',
  25 + 'sublist' => [
  26 + ['name' => 'command/index', 'title' => '查看'],
  27 + ['name' => 'command/add', 'title' => '添加'],
  28 + ['name' => 'command/detail', 'title' => '详情'],
  29 + ['name' => 'command/execute', 'title' => '运行'],
  30 + ['name' => 'command/del', 'title' => '删除'],
  31 + ['name' => 'command/multi', 'title' => '批量更新'],
  32 + ]
  33 + ]
  34 + ];
  35 + Menu::create($menu);
  36 + return true;
  37 + }
  38 +
  39 + /**
  40 + * 插件卸载方法
  41 + * @return bool
  42 + */
  43 + public function uninstall()
  44 + {
  45 + Menu::delete('command');
  46 + return true;
  47 + }
  48 +
  49 + /**
  50 + * 插件启用方法
  51 + * @return bool
  52 + */
  53 + public function enable()
  54 + {
  55 + Menu::enable('command');
  56 + return true;
  57 + }
  58 +
  59 + /**
  60 + * 插件禁用方法
  61 + * @return bool
  62 + */
  63 + public function disable()
  64 + {
  65 + Menu::disable('command');
  66 + return true;
  67 + }
  68 +
  69 +}
  1 +<?php
  2 +
  3 +namespace app\admin\controller;
  4 +
  5 +use app\common\controller\Backend;
  6 +use think\Config;
  7 +use think\console\Input;
  8 +use think\Db;
  9 +use think\Exception;
  10 +
  11 +/**
  12 + * 在线命令管理
  13 + *
  14 + * @icon fa fa-circle-o
  15 + */
  16 +class Command extends Backend
  17 +{
  18 +
  19 + /**
  20 + * Command模型对象
  21 + */
  22 + protected $model = null;
  23 + protected $noNeedRight = ['get_controller_list', 'get_field_list'];
  24 +
  25 + public function _initialize()
  26 + {
  27 + parent::_initialize();
  28 + $this->model = model('Command');
  29 + $this->view->assign("statusList", $this->model->getStatusList());
  30 + }
  31 +
  32 + /**
  33 + * 添加
  34 + */
  35 + public function add()
  36 + {
  37 +
  38 + $tableList = [];
  39 + $list = \think\Db::query("SHOW TABLES");
  40 + foreach ($list as $key => $row) {
  41 + $tableList[reset($row)] = reset($row);
  42 + }
  43 +
  44 + $this->view->assign("tableList", $tableList);
  45 + return $this->view->fetch();
  46 + }
  47 +
  48 + /**
  49 + * 获取字段列表
  50 + * @internal
  51 + */
  52 + public function get_field_list()
  53 + {
  54 + $dbname = Config::get('database.database');
  55 + $prefix = Config::get('database.prefix');
  56 + $table = $this->request->request('table');
  57 + //从数据库中获取表字段信息
  58 + $sql = "SELECT * FROM `information_schema`.`columns` "
  59 + . "WHERE TABLE_SCHEMA = ? AND table_name = ? "
  60 + . "ORDER BY ORDINAL_POSITION";
  61 + //加载主表的列
  62 + $columnList = Db::query($sql, [$dbname, $table]);
  63 + $fieldlist = [];
  64 + foreach ($columnList as $index => $item) {
  65 + $fieldlist[] = $item['COLUMN_NAME'];
  66 + }
  67 + $this->success("", null, ['fieldlist' => $fieldlist]);
  68 + }
  69 +
  70 + /**
  71 + * 获取控制器列表
  72 + * @internal
  73 + */
  74 + public function get_controller_list()
  75 + {
  76 + $adminPath = dirname(__DIR__) . DS;
  77 + $controllerDir = $adminPath . 'controller' . DS;
  78 + $files = new \RecursiveIteratorIterator(
  79 + new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
  80 + );
  81 + $list = [];
  82 + foreach ($files as $name => $file) {
  83 + if (!$file->isDir()) {
  84 + $filePath = $file->getRealPath();
  85 + $name = str_replace($controllerDir, '', $filePath);
  86 + $name = str_replace(DS, "/", $name);
  87 + $list[] = ['id' => $name, 'name' => $name];
  88 + }
  89 + }
  90 + $pageNumber = $this->request->request("pageNumber");
  91 + $pageSize = $this->request->request("pageSize");
  92 + return json(['list' => array_slice($list, ($pageNumber - 1) * $pageSize, $pageSize), 'total' => count($list)]);
  93 + }
  94 +
  95 + /**
  96 + * 详情
  97 + */
  98 + public function detail($ids)
  99 + {
  100 + $row = $this->model->get($ids);
  101 + if (!$row)
  102 + $this->error(__('No Results were found'));
  103 + $this->view->assign("row", $row);
  104 + return $this->view->fetch();
  105 + }
  106 +
  107 + /**
  108 + * 执行
  109 + */
  110 + public function execute($ids)
  111 + {
  112 + $row = $this->model->get($ids);
  113 + if (!$row)
  114 + $this->error(__('No Results were found'));
  115 + $result = $this->doexecute($row['type'], json_decode($row['params'], true));
  116 + $this->success("", null, ['result' => $result]);
  117 + }
  118 +
  119 + /**
  120 + * 执行命令
  121 + */
  122 + public function command($action = '')
  123 + {
  124 + $commandtype = $this->request->request("commandtype");
  125 + $params = $this->request->request();
  126 + $allowfields = [
  127 + 'crud' => 'table,controller,model,fields,force,local,delete,menu',
  128 + 'menu' => 'controller,delete',
  129 + 'min' => 'module,resource,optimize',
  130 + 'api' => 'url,module,output,template,force,title,author,class,language',
  131 + ];
  132 + $argv = [];
  133 + $allowfields = isset($allowfields[$commandtype]) ? explode(',', $allowfields[$commandtype]) : [];
  134 + $allowfields = array_filter(array_intersect_key($params, array_flip($allowfields)));
  135 + if (isset($params['local']) && !$params['local']) {
  136 + $allowfields['local'] = $params['local'];
  137 + } else {
  138 + unset($allowfields['local']);
  139 + }
  140 + foreach ($allowfields as $key => $param) {
  141 + $argv[] = "--{$key}=" . (is_array($param) ? implode(',', $param) : $param);
  142 + }
  143 + if ($commandtype == 'crud') {
  144 + $extend = 'setcheckboxsuffix,enumradiosuffix,imagefield,filefield,intdatesuffix,switchsuffix,citysuffix,selectpagesuffix,selectpagessuffix,ignorefields,sortfield,editorsuffix,headingfilterfield';
  145 + $extendArr = explode(',', $extend);
  146 + foreach ($params as $index => $item) {
  147 + if (in_array($index, $extendArr)) {
  148 + foreach (explode(',', $item) as $key => $value) {
  149 + if ($value) {
  150 + $argv[] = "--{$index}={$value}";
  151 + }
  152 + }
  153 + }
  154 + }
  155 + $isrelation = (int)$this->request->request('isrelation');
  156 + if ($isrelation && isset($params['relation'])) {
  157 + foreach ($params['relation'] as $index => $relation) {
  158 + foreach ($relation as $key => $value) {
  159 + $argv[] = "--{$key}=" . (is_array($value) ? implode(',', $value) : $value);
  160 + }
  161 + }
  162 + }
  163 + } else if ($commandtype == 'menu') {
  164 + if (isset($params['allcontroller']) && $params['allcontroller']) {
  165 + $argv[] = "--controller=all-controller";
  166 + } else {
  167 + foreach (explode(',', $params['controllerfile']) as $index => $param) {
  168 + if ($param) {
  169 + $argv[] = "--controller=" . substr($param, 0, -4);
  170 + }
  171 + }
  172 + }
  173 + } else if ($commandtype == 'min') {
  174 +
  175 + } else if ($commandtype == 'api') {
  176 +
  177 + } else {
  178 +
  179 + }
  180 + if ($action == 'execute') {
  181 + $result = $this->doexecute($commandtype, $argv);
  182 + $this->success("", null, ['result' => $result]);
  183 + } else {
  184 + $this->success("", null, ['command' => "php think {$commandtype} " . implode(' ', $argv)]);
  185 + }
  186 +
  187 + return;
  188 + }
  189 +
  190 + protected function doexecute($commandtype, $argv)
  191 + {
  192 + $commandName = "\\app\\admin\\command\\" . ucfirst($commandtype);
  193 + $input = new Input($argv);
  194 + $output = new \addons\command\library\Output();
  195 + $command = new $commandName($commandtype);
  196 + $data = [
  197 + 'type' => $commandtype,
  198 + 'params' => json_encode($argv),
  199 + 'command' => "php think {$commandtype} " . implode(' ', $argv),
  200 + 'executetime' => time(),
  201 + ];
  202 + $this->model->save($data);
  203 + try {
  204 + $command->run($input, $output);
  205 + $result = implode("\n", $output->getMessage());
  206 + $this->model->status = 'successed';
  207 + } catch (Exception $e) {
  208 + $result = implode("\n", $output->getMessage()) . "\n";
  209 + $result .= $e->getMessage();
  210 + $this->model->status = 'failured';
  211 + }
  212 + $result = trim($result);
  213 + $this->model->content = $result;
  214 + $this->model->save();
  215 + return $result;
  216 + }
  217 +
  218 +
  219 +}
  1 +<?php
  2 +
  3 +return [
  4 + 'Id' => 'ID',
  5 + 'Type' => '类型',
  6 + 'Params' => '参数',
  7 + 'Command' => '命令',
  8 + 'Content' => '返回结果',
  9 + 'Executetime' => '执行时间',
  10 + 'Createtime' => '创建时间',
  11 + 'Updatetime' => '更新时间',
  12 + 'Execute again' => '再次执行',
  13 + 'Successed' => '成功',
  14 + 'Failured' => '失败',
  15 + 'Status' => '状态'
  16 +];
  1 +<?php
  2 +
  3 +namespace app\admin\model;
  4 +
  5 +use think\Model;
  6 +
  7 +class Command extends Model
  8 +{
  9 + // 表名
  10 + protected $name = 'command';
  11 +
  12 + // 自动写入时间戳字段
  13 + protected $autoWriteTimestamp = 'int';
  14 +
  15 + // 定义时间戳字段名
  16 + protected $createTime = 'createtime';
  17 + protected $updateTime = 'updatetime';
  18 +
  19 + // 追加属性
  20 + protected $append = [
  21 + 'executetime_text',
  22 + 'type_text',
  23 + 'status_text'
  24 + ];
  25 +
  26 +
  27 + public function getStatusList()
  28 + {
  29 + return ['successed' => __('Successed'), 'failured' => __('Failured')];
  30 + }
  31 +
  32 +
  33 + public function getExecutetimeTextAttr($value, $data)
  34 + {
  35 + $value = $value ? $value : $data['executetime'];
  36 + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
  37 + }
  38 +
  39 + public function getTypeTextAttr($value, $data)
  40 + {
  41 + $value = $value ? $value : $data['type'];
  42 + $list = ['crud' => '一键生成CRUD', 'menu' => '一键生成菜单', 'min' => '一键压缩打包', 'api' => '一键生成文档'];
  43 + return isset($list[$value]) ? $list[$value] : '';
  44 + }
  45 +
  46 + public function getStatusTextAttr($value, $data)
  47 + {
  48 + $value = $value ? $value : $data['status'];
  49 + $list = $this->getStatusList();
  50 + return isset($list[$value]) ? $list[$value] : '';
  51 + }
  52 +
  53 + protected function setExecutetimeAttr($value)
  54 + {
  55 + return $value && !is_numeric($value) ? strtotime($value) : $value;
  56 + }
  57 +
  58 +
  59 +}
  1 +<?php
  2 +
  3 +namespace app\admin\validate;
  4 +
  5 +use think\Validate;
  6 +
  7 +class Command extends Validate
  8 +{
  9 + /**
  10 + * 验证规则
  11 + */
  12 + protected $rule = [
  13 + ];
  14 + /**
  15 + * 提示消息
  16 + */
  17 + protected $message = [
  18 + ];
  19 + /**
  20 + * 验证场景
  21 + */
  22 + protected $scene = [
  23 + 'add' => [],
  24 + 'edit' => [],
  25 + ];
  26 +
  27 +}
  1 +<style>
  2 + .relation-item {margin-top:10px;}
  3 + legend {padding-bottom:5px;font-size:14px;font-weight:600;}
  4 + label {font-weight:normal;}
  5 + .form-control{padding:6px 8px;}
  6 + #extend-zone .col-xs-2 {margin-top:10px;padding-right:0;}
  7 + #extend-zone .col-xs-2:nth-child(6n+0) {padding-right:15px;}
  8 +</style>
  9 +<div class="panel panel-default panel-intro">
  10 + <div class="panel-heading">
  11 + <ul class="nav nav-tabs">
  12 + <li class="active"><a href="#crud" data-toggle="tab">{:__('一键生成CRUD')}</a></li>
  13 + <li><a href="#menu" data-toggle="tab">{:__('一键生成菜单')}</a></li>
  14 + <li><a href="#min" data-toggle="tab">{:__('一键压缩打包')}</a></li>
  15 + <li><a href="#api" data-toggle="tab">{:__('一键生成API文档')}</a></li>
  16 + </ul>
  17 + </div>
  18 + <div class="panel-body">
  19 + <div id="myTabContent" class="tab-content">
  20 + <div class="tab-pane fade active in" id="crud">
  21 + <div class="row">
  22 + <div class="col-xs-12">
  23 + <form role="form">
  24 + <input type="hidden" name="commandtype" value="crud" />
  25 + <div class="form-group">
  26 + <div class="row">
  27 + <div class="col-xs-3">
  28 + <input checked="" name="isrelation" type="hidden" value="0">
  29 + <label class="control-label" data-toggle="tooltip" title="当前只支持生成1对1关联模型,选中后请配置关联表和字段">
  30 + <input name="isrelation" type="checkbox" value="1">
  31 + 关联模型
  32 + </label>
  33 + </div>
  34 + <div class="col-xs-3">
  35 + <input checked="" name="local" type="hidden" value="1">
  36 + <label class="control-label" data-toggle="tooltip" title="默认模型生成在application/admin/model目录下,选中后将生成在application/common/model目录下">
  37 + <input name="local" type="checkbox" value="0"> 全局模型类
  38 + </label>
  39 + </div>
  40 + <div class="col-xs-3">
  41 + <input checked="" name="delete" type="hidden" value="0">
  42 + <label class="control-label" data-toggle="tooltip" title="删除CRUD生成的相关文件">
  43 + <input name="delete" type="checkbox" value="1"> 删除模式
  44 + </label>
  45 + </div>
  46 + <div class="col-xs-3">
  47 + <input checked="" name="force" type="hidden" value="0">
  48 + <label class="control-label" data-toggle="tooltip" title="选中后,如果已经存在同名文件将被覆盖。如果是删除将不再提醒">
  49 + <input name="force" type="checkbox" value="1">
  50 + 强制覆盖模式
  51 + </label>
  52 + </div>
  53 + <!--
  54 + <div class="col-xs-3">
  55 + <input checked="" name="menu" type="hidden" value="0">
  56 + <label class="control-label" data-toggle="tooltip" title="选中后,将同时生成后台菜单规则">
  57 + <input name="menu" type="checkbox" value="1">
  58 + 生成菜单
  59 + </label>
  60 + </div>
  61 + -->
  62 + </div>
  63 + </div>
  64 + <div class="form-group">
  65 + <legend>主表设置</legend>
  66 + <div class="row">
  67 + <div class="col-xs-3">
  68 + <label>请选择主表</label>
  69 + {:build_select('table',$tableList,null,['class'=>'form-control selectpicker']);}
  70 + </div>
  71 + <div class="col-xs-3">
  72 + <label>自定义控制器名</label>
  73 + <input type="text" class="form-control" name="controller" data-toggle="tooltip" title="默认根据表名自动生成,如果需要放在二级目录请手动填写" placeholder="支持目录层级,以/分隔">
  74 + </div>
  75 + <div class="col-xs-3">
  76 + <label>自定义模型名</label>
  77 + <input type="text" class="form-control" name="model" data-toggle="tooltip" title="默认根据表名自动生成" placeholder="不支持目录层级">
  78 + </div>
  79 + <div class="col-xs-3">
  80 + <label>请选择显示字段(默认全部)</label>
  81 + <select name="fields[]" id="fields" multiple style="height:30px;" class="form-control selectpicker"></select>
  82 + </div>
  83 +
  84 + </div>
  85 +
  86 + </div>
  87 +
  88 + <div class="form-group hide" id="relation-zone">
  89 + <legend>关联表设置</legend>
  90 +
  91 + <div class="row" style="margin-top:15px;">
  92 + <div class="col-xs-12">
  93 + <a href="javascript:;" class="btn btn-primary btn-sm btn-newrelation" data-index="1">追加关联模型</a>
  94 + </div>
  95 + </div>
  96 + </div>
  97 +
  98 + <hr>
  99 + <div class="form-group" id="extend-zone">
  100 + <legend>字段识别设置 <span style="font-size:12px;font-weight: normal;">(与之匹配的字段都将生成相应组件)</span></legend>
  101 + <div class="row">
  102 + <div class="col-xs-2">
  103 + <label>复选框后缀</label>
  104 + <input type="text" class="form-control" name="setcheckboxsuffix" placeholder="默认为set类型" />
  105 + </div>
  106 + <div class="col-xs-2">
  107 + <label>单选框后缀</label>
  108 + <input type="text" class="form-control" name="enumradiosuffix" placeholder="默认为enum类型" />
  109 + </div>
  110 + <div class="col-xs-2">
  111 + <label>图片类型后缀</label>
  112 + <input type="text" class="form-control" name="imagefield" placeholder="默认为image,images,avatar,avatars" />
  113 + </div>
  114 + <div class="col-xs-2">
  115 + <label>文件类型后缀</label>
  116 + <input type="text" class="form-control" name="filefield" placeholder="默认为file,files" />
  117 + </div>
  118 + <div class="col-xs-2">
  119 + <label>日期时间后缀</label>
  120 + <input type="text" class="form-control" name="intdatesuffix" placeholder="默认为time" />
  121 + </div>
  122 + <div class="col-xs-2">
  123 + <label>开关后缀</label>
  124 + <input type="text" class="form-control" name="switchsuffix" placeholder="默认为switch" />
  125 + </div>
  126 + <div class="col-xs-2">
  127 + <label>城市选择后缀</label>
  128 + <input type="text" class="form-control" name="citysuffix" placeholder="默认为city" />
  129 + </div>
  130 + <div class="col-xs-2">
  131 + <label>动态下拉后缀(单)</label>
  132 + <input type="text" class="form-control" name="selectpagesuffix" placeholder="默认为_id" />
  133 + </div>
  134 + <div class="col-xs-2">
  135 + <label>动态下拉后缀(多)</label>
  136 + <input type="text" class="form-control" name="selectpagessuffix" placeholder="默认为_ids" />
  137 + </div>
  138 + <div class="col-xs-2">
  139 + <label>忽略的字段</label>
  140 + <input type="text" class="form-control" name="ignorefields" placeholder="默认无" />
  141 + </div>
  142 + <div class="col-xs-2">
  143 + <label>排序字段</label>
  144 + <input type="text" class="form-control" name="sortfield" placeholder="默认为weigh" />
  145 + </div>
  146 + <div class="col-xs-2">
  147 + <label>富文本编辑器</label>
  148 + <input type="text" class="form-control" name="editorsuffix" placeholder="默认为content" />
  149 + </div>
  150 + <div class="col-xs-2">
  151 + <label>选项卡过滤字段</label>
  152 + <input type="text" class="form-control" name="headingfilterfield" placeholder="默认为status" />
  153 + </div>
  154 +
  155 + </div>
  156 +
  157 + </div>
  158 +
  159 + <div class="form-group">
  160 + <legend>生成命令行</legend>
  161 + <textarea class="form-control" data-toggle="tooltip" title="如果在线执行命令失败,可以将命令复制到命令行进行执行" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
  162 + </div>
  163 +
  164 + <div class="form-group">
  165 + <legend>返回结果</legend>
  166 + <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
  167 + </div>
  168 +
  169 + <div class="form-group">
  170 + <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
  171 + <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
  172 + </div>
  173 +
  174 + </form>
  175 + </div>
  176 + </div>
  177 + </div>
  178 + <div class="tab-pane fade" id="menu">
  179 + <div class="row">
  180 + <div class="col-xs-12">
  181 + <form role="form">
  182 + <input type="hidden" name="commandtype" value="menu" />
  183 + <div class="form-group">
  184 + <div class="row">
  185 + <div class="col-xs-3">
  186 + <input checked="" name="allcontroller" type="hidden" value="0">
  187 + <label class="control-label">
  188 + <input name="allcontroller" data-toggle="collapse" data-target="#controller" type="checkbox" value="1"> 一键生成全部控制器
  189 + </label>
  190 + </div>
  191 + <div class="col-xs-3">
  192 + <input checked="" name="delete" type="hidden" value="0">
  193 + <label class="control-label">
  194 + <input name="delete" type="checkbox" value="1"> 删除模式
  195 + </label>
  196 + </div>
  197 + <div class="col-xs-3">
  198 + <input checked="" name="force" type="hidden" value="0">
  199 + <label class="control-label">
  200 + <input name="force" type="checkbox" value="1"> 强制覆盖模式
  201 + </label>
  202 + </div>
  203 + </div>
  204 + </div>
  205 +
  206 + <div class="form-group in" id="controller">
  207 + <legend>控制器设置</legend>
  208 +
  209 + <div class="row" style="margin-top:15px;">
  210 + <div class="col-xs-12">
  211 + <input type="text" name="controllerfile" class="form-control selectpage" style="width:720px;" data-source="command/get_controller_list" data-multiple="true" name="controller" placeholder="请选择控制器" />
  212 + </div>
  213 + </div>
  214 + </div>
  215 +
  216 + <div class="form-group">
  217 + <legend>生成命令行</legend>
  218 + <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
  219 + </div>
  220 +
  221 + <div class="form-group">
  222 + <legend>返回结果</legend>
  223 + <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
  224 + </div>
  225 +
  226 + <div class="form-group">
  227 + <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
  228 + <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
  229 + </div>
  230 +
  231 + </form>
  232 + </div>
  233 + </div>
  234 + </div>
  235 + <div class="tab-pane fade" id="min">
  236 + <div class="row">
  237 + <div class="col-xs-12">
  238 + <form role="form">
  239 + <input type="hidden" name="commandtype" value="min" />
  240 + <div class="form-group">
  241 + <legend>基础设置</legend>
  242 + <div class="row">
  243 + <div class="col-xs-3">
  244 + <label>请选择压缩模块</label>
  245 + <select name="module" class="form-control selectpicker">
  246 + <option value="all" selected>全部</option>
  247 + <option value="backend">后台Backend</option>
  248 + <option value="frontend">前台Frontend</option>
  249 + </select>
  250 + </div>
  251 + <div class="col-xs-3">
  252 + <label>请选择压缩资源</label>
  253 + <select name="resource" class="form-control selectpicker">
  254 + <option value="all" selected>全部</option>
  255 + <option value="js">JS</option>
  256 + <option value="css">CSS</option>
  257 + </select>
  258 + </div>
  259 + <div class="col-xs-3">
  260 + <label>请选择压缩模式</label>
  261 + <select name="optimize" class="form-control selectpicker">
  262 + <option value=""></option>
  263 + <option value="uglify">uglify</option>
  264 + <option value="closure">closure</option>
  265 + </select>
  266 + </div>
  267 + </div>
  268 + </div>
  269 +
  270 + <div class="form-group in">
  271 + <legend>控制器设置</legend>
  272 +
  273 + <div class="row" style="margin-top:15px;">
  274 + <div class="col-xs-12">
  275 +
  276 + </div>
  277 + </div>
  278 + </div>
  279 +
  280 + <div class="form-group">
  281 + <legend>生成命令行</legend>
  282 + <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
  283 + </div>
  284 +
  285 + <div class="form-group">
  286 + <legend>返回结果</legend>
  287 + <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
  288 + </div>
  289 +
  290 + <div class="form-group">
  291 + <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
  292 + <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
  293 + </div>
  294 +
  295 + </form>
  296 + </div>
  297 + </div>
  298 + </div>
  299 + <div class="tab-pane fade" id="api">
  300 + <div class="row">
  301 + <div class="col-xs-12">
  302 + <form role="form">
  303 + <input type="hidden" name="commandtype" value="api" />
  304 + <div class="form-group">
  305 + <div class="row">
  306 + <div class="col-xs-3">
  307 + <input checked="" name="force" type="hidden" value="0">
  308 + <label class="control-label">
  309 + <input name="force" type="checkbox" value="1">
  310 + 覆盖模式
  311 + </label>
  312 + </div>
  313 + </div>
  314 + </div>
  315 + <div class="form-group">
  316 + <legend>文档设置</legend>
  317 + <div class="row">
  318 + <div class="col-xs-3">
  319 + <label>请输入接口URL</label>
  320 + <input type="text" name="url" class="form-control" placeholder="API URL,可留空" />
  321 + </div>
  322 + <div class="col-xs-3">
  323 + <label>接口生成文件</label>
  324 + <input type="text" name="output" class="form-control" placeholder="留空则使用api.html" />
  325 + </div>
  326 + <div class="col-xs-3">
  327 + <label>模板文件</label>
  328 + <input type="text" name="template" class="form-control" placeholder="如果不清楚请留空" />
  329 + </div>
  330 + </div>
  331 + <div class="row" style="margin-top:10px;">
  332 + <div class="col-xs-3">
  333 + <label>文档标题</label>
  334 + <input type="text" name="title" class="form-control" placeholder="默认为FastAdmin" />
  335 + </div>
  336 + <div class="col-xs-3">
  337 + <label>文档作者</label>
  338 + <input type="text" name="author" class="form-control" placeholder="默认为FastAdmin" />
  339 + </div>
  340 + <div class="col-xs-3">
  341 + <label>文档语言</label>
  342 + <select name="language" class="form-control">
  343 + <option value="" selected>请选择语言</option>
  344 + <option value="zh-cn">中文</option>
  345 + <option value="en">英文</option>
  346 + </select>
  347 + </div>
  348 + </div>
  349 + </div>
  350 +
  351 + <div class="form-group">
  352 + <legend>生成命令行</legend>
  353 + <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
  354 + </div>
  355 +
  356 + <div class="form-group">
  357 + <legend>返回结果</legend>
  358 + <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
  359 + </div>
  360 +
  361 + <div class="form-group">
  362 + <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
  363 + <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
  364 + </div>
  365 +
  366 + </form>
  367 + </div>
  368 + </div>
  369 + </div>
  370 + </div>
  371 + </div>
  372 +</div>
  373 +<script id="relationtpl" type="text/html">
  374 + <div class="row relation-item">
  375 + <div class="col-xs-2">
  376 + <label>请选择关联表</label>
  377 + <select name="relation[<%=index%>][relation]" class="form-control relationtable"></select>
  378 + </div>
  379 + <div class="col-xs-2">
  380 + <label>请选择关联类型</label>
  381 + <select name="relation[<%=index%>][relationmode]" class="form-control relationmode"></select>
  382 + </div>
  383 + <div class="col-xs-2">
  384 + <label>关联外键</label>
  385 + <select name="relation[<%=index%>][relationforeignkey]" class="form-control relationforeignkey"></select>
  386 + </div>
  387 + <div class="col-xs-2">
  388 + <label>关联主键</label>
  389 + <select name="relation[<%=index%>][relationprimarykey]" class="form-control relationprimarykey"></select>
  390 + </div>
  391 + <div class="col-xs-2">
  392 + <label>请选择显示字段</label>
  393 + <select name="relation[<%=index%>][relationfields][]" multiple class="form-control relationfields"></select>
  394 + </div>
  395 + <div class="col-xs-2">
  396 + <label>&nbsp;</label>
  397 + <a href="javascript:;" class="btn btn-danger btn-block btn-removerelation">移除</a>
  398 + </div>
  399 + </div>
  400 +</script>
  1 +<table class="table table-striped">
  2 + <thead>
  3 + <tr>
  4 + <th>{:__('Title')}</th>
  5 + <th>{:__('Content')}</th>
  6 + </tr>
  7 + </thead>
  8 + <tbody>
  9 + <tr>
  10 + <td>{:__('Type')}</td>
  11 + <td>{$row.type}({$row.type_text})</td>
  12 + </tr>
  13 + <tr>
  14 + <td>{:__('Params')}</td>
  15 + <td>{$row.params}</td>
  16 + </tr>
  17 + <tr>
  18 + <td>{:__('Command')}</td>
  19 + <td>{$row.command}</td>
  20 + </tr>
  21 + <tr>
  22 + <td>{:__('Content')}</td>
  23 + <td>
  24 + <textarea class="form-control" name="" id="" cols="60" rows="10">{$row.content}</textarea>
  25 + </td>
  26 + </tr>
  27 + <tr>
  28 + <td>{:__('Executetime')}</td>
  29 + <td>{$row.executetime|datetime}</td>
  30 + </tr>
  31 + <tr>
  32 + <td>{:__('Status')}</td>
  33 + <td>{$row.status_text}</td>
  34 + </tr>
  35 + </tbody>
  36 +</table>
  37 +<div class="hide layer-footer">
  38 + <label class="control-label col-xs-12 col-sm-2"></label>
  39 + <div class="col-xs-12 col-sm-8">
  40 + <button type="reset" class="btn btn-primary btn-embossed btn-close" onclick="Layer.closeAll();">{:__('Close')}</button>
  41 + </div>
  42 +</div>
  1 +<div class="panel panel-default panel-intro">
  2 + {:build_heading()}
  3 +
  4 + <div class="panel-body">
  5 + <div id="myTabContent" class="tab-content">
  6 + <div class="tab-pane fade active in" id="one">
  7 + <div class="widget-body no-padding">
  8 + <div id="toolbar" class="toolbar">
  9 + <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
  10 + <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('command/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
  11 + <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('command/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
  12 +
  13 + </div>
  14 + <table id="table" class="table table-striped table-bordered table-hover"
  15 + data-operate-detail="{:$auth->check('command/detail')}"
  16 + data-operate-execute="{:$auth->check('command/execute')}"
  17 + data-operate-del="{:$auth->check('command/del')}"
  18 + width="100%">
  19 + </table>
  20 + </div>
  21 + </div>
  22 +
  23 + </div>
  24 + </div>
  25 +</div>
  1 +<?php
  2 +
  3 +return [
  4 +];
  1 +<?php
  2 +
  3 +namespace addons\command\controller;
  4 +
  5 +use think\addons\Controller;
  6 +
  7 +class Index extends Controller
  8 +{
  9 +
  10 + public function index()
  11 + {
  12 + $this->error("当前插件暂无前台页面");
  13 + }
  14 +
  15 +}
  1 +name = command
  2 +title = 在线命令
  3 +intro = 可在线执行FastAdmin的命令行相关命令
  4 +author = Karson
  5 +website = http://www.fastadmin.net
  6 +version = 1.0.5
  7 +state = 1
  8 +url = /addons/command.html
  1 +CREATE TABLE IF NOT EXISTS `__PREFIX__command` (
  2 + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
  3 + `type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '类型',
  4 + `params` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '参数',
  5 + `command` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '命令',
  6 + `content` text COMMENT '返回结果',
  7 + `executetime` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '执行时间',
  8 + `createtime` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
  9 + `updatetime` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间',
  10 + `status` enum('successed','failured') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'failured' COMMENT '状态',
  11 + PRIMARY KEY (`id`) USING BTREE
  12 +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '在线命令表' ROW_FORMAT = Compact;
  1 +<?php
  2 +
  3 +namespace addons\command\library;
  4 +
  5 +/**
  6 + * Class Output
  7 + */
  8 +class Output extends \think\console\Output
  9 +{
  10 +
  11 + protected $message = [];
  12 +
  13 + public function __construct($driver = 'console')
  14 + {
  15 + parent::__construct($driver);
  16 + }
  17 +
  18 + protected function block($style, $message)
  19 + {
  20 + $this->message[] = $message;
  21 + }
  22 +
  23 + public function getMessage()
  24 + {
  25 + return $this->message;
  26 + }
  27 +
  28 +}
  1 +define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function ($, undefined, Backend, Table, Form, Template) {
  2 +
  3 + var Controller = {
  4 + index: function () {
  5 + // 初始化表格参数配置
  6 + Table.api.init({
  7 + extend: {
  8 + index_url: 'command/index',
  9 + add_url: 'command/add',
  10 + edit_url: '',
  11 + del_url: 'command/del',
  12 + multi_url: 'command/multi',
  13 + table: 'command',
  14 + }
  15 + });
  16 +
  17 + var table = $("#table");
  18 +
  19 + // 初始化表格
  20 + table.bootstrapTable({
  21 + url: $.fn.bootstrapTable.defaults.extend.index_url,
  22 + pk: 'id',
  23 + sortName: 'id',
  24 + columns: [
  25 + [
  26 + {checkbox: true},
  27 + {field: 'id', title: __('Id')},
  28 + {field: 'type', title: __('Type'), formatter: Table.api.formatter.search},
  29 + {field: 'type_text', title: __('Type')},
  30 + {
  31 + field: 'command', title: __('Command'), formatter: function (value, row, index) {
  32 + return '<input type="text" class="form-control" value="' + value + '">';
  33 + }
  34 + },
  35 + {
  36 + field: 'executetime',
  37 + title: __('Executetime'),
  38 + operate: 'RANGE',
  39 + addclass: 'datetimerange',
  40 + formatter: Table.api.formatter.datetime
  41 + },
  42 + {
  43 + field: 'createtime',
  44 + title: __('Createtime'),
  45 + operate: 'RANGE',
  46 + addclass: 'datetimerange',
  47 + formatter: Table.api.formatter.datetime
  48 + },
  49 + {
  50 + field: 'updatetime',
  51 + title: __('Updatetime'),
  52 + operate: 'RANGE',
  53 + addclass: 'datetimerange',
  54 + formatter: Table.api.formatter.datetime
  55 + },
  56 + {
  57 + field: 'status',
  58 + title: __('Status'),
  59 + table: table,
  60 + custom: {"successed": 'success', "failured": 'danger'},
  61 + searchList: {"successed": __('Successed'), "failured": __('Failured')},
  62 + formatter: Table.api.formatter.status
  63 + },
  64 + {
  65 + field: 'operate',
  66 + title: __('Operate'),
  67 + buttons: [
  68 + {
  69 + name: 'execute',
  70 + title: __('Execute again'),
  71 + text: __('Execute again'),
  72 + url: 'command/execute',
  73 + icon: 'fa fa-repeat',
  74 + classname: 'btn btn-success btn-xs btn-execute btn-ajax',
  75 + success: function (data) {
  76 + Layer.alert("<textarea class='form-control' cols='60' rows='5'>" + data.result + "</textarea>", {
  77 + title: __("执行结果"),
  78 + shadeClose: true
  79 + });
  80 + table.bootstrapTable('refresh');
  81 + return false;
  82 + }
  83 + },
  84 + {
  85 + name: 'execute',
  86 + title: __('Detail'),
  87 + text: __('Detail'),
  88 + url: 'command/detail',
  89 + icon: 'fa fa-list',
  90 + classname: 'btn btn-info btn-xs btn-execute btn-dialog'
  91 + }
  92 + ],
  93 + table: table,
  94 + events: Table.api.events.operate,
  95 + formatter: Table.api.formatter.operate
  96 + }
  97 + ]
  98 + ]
  99 + });
  100 +
  101 + // 为表格绑定事件
  102 + Table.api.bindevent(table);
  103 + },
  104 + add: function () {
  105 + require(['bootstrap-select', 'bootstrap-select-lang']);
  106 + var mainfields = [];
  107 + var relationfields = {};
  108 + var maintable = [];
  109 + var relationtable = [];
  110 + var relationmode = ["belongsto", "hasone"];
  111 +
  112 + var renderselect = function (select, data) {
  113 + var html = [];
  114 + for (var i = 0; i < data.length; i++) {
  115 + html.push("<option value='" + data[i] + "'>" + data[i] + "</option>");
  116 + }
  117 + $(select).html(html.join(""));
  118 + select.trigger("change");
  119 + if (select.data("selectpicker")) {
  120 + select.selectpicker('refresh');
  121 + }
  122 + return select;
  123 + };
  124 +
  125 + $("select[name=table] option").each(function () {
  126 + maintable.push($(this).val());
  127 + });
  128 + $(document).on('change', "input[name='isrelation']", function () {
  129 + $("#relation-zone").toggleClass("hide", !$(this).prop("checked"));
  130 + });
  131 + $(document).on('change', "select[name='table']", function () {
  132 + var that = this;
  133 + Fast.api.ajax({
  134 + url: "command/get_field_list",
  135 + data: {table: $(that).val()},
  136 + }, function (data, ret) {
  137 + mainfields = data.fieldlist;
  138 + $("#relation-zone .relation-item").remove();
  139 + renderselect($("#fields"), mainfields);
  140 + return false;
  141 + });
  142 + return false;
  143 + });
  144 + $(document).on('click', "a.btn-newrelation", function () {
  145 + var that = this;
  146 + var index = parseInt($(that).data("index")) + 1;
  147 + var content = Template("relationtpl", {index: index});
  148 + content = $(content.replace(/\[index\]/, index));
  149 + $(this).data("index", index);
  150 + $(content).insertBefore($(that).closest(".row"));
  151 + $('select', content).selectpicker();
  152 + var exists = [$("select[name='table']").val()];
  153 + $("select.relationtable").each(function () {
  154 + exists.push($(this).val());
  155 + });
  156 + relationtable = [];
  157 + $.each(maintable, function (i, j) {
  158 + if ($.inArray(j, exists) < 0) {
  159 + relationtable.push(j);
  160 + }
  161 + });
  162 + renderselect($("select.relationtable", content), relationtable);
  163 + $("select.relationtable", content).trigger("change");
  164 + });
  165 + $(document).on('click', "a.btn-removerelation", function () {
  166 + $(this).closest(".row").remove();
  167 + });
  168 + $(document).on('change', "#relation-zone select.relationmode", function () {
  169 + var table = $("select.relationtable", $(this).closest(".row")).val();
  170 + var that = this;
  171 + Fast.api.ajax({
  172 + url: "command/get_field_list",
  173 + data: {table: table},
  174 + }, function (data, ret) {
  175 + renderselect($(that).closest(".row").find("select.relationprimarykey"), $(that).val() == 'belongsto' ? data.fieldlist : mainfields);
  176 + renderselect($(that).closest(".row").find("select.relationforeignkey"), $(that).val() == 'hasone' ? data.fieldlist : mainfields);
  177 + return false;
  178 + });
  179 + });
  180 + $(document).on('change', "#relation-zone select.relationtable", function () {
  181 + var that = this;
  182 + Fast.api.ajax({
  183 + url: "command/get_field_list",
  184 + data: {table: $(that).val()},
  185 + }, function (data, ret) {
  186 + renderselect($(that).closest(".row").find("select.relationmode"), relationmode);
  187 + renderselect($(that).closest(".row").find("select.relationfields"), mainfields)
  188 + renderselect($(that).closest(".row").find("select.relationforeignkey"), data.fieldlist)
  189 + renderselect($(that).closest(".row").find("select.relationfields"), data.fieldlist)
  190 + return false;
  191 + });
  192 + });
  193 + $(document).on('click', ".btn-command", function () {
  194 + var form = $(this).closest("form");
  195 + var textarea = $("textarea[rel=command]", form);
  196 + textarea.val('');
  197 + Fast.api.ajax({
  198 + url: "command/command/action/command",
  199 + data: form.serialize(),
  200 + }, function (data, ret) {
  201 + textarea.val(data.command);
  202 + return false;
  203 + });
  204 + });
  205 + $(document).on('click', ".btn-execute", function () {
  206 + var form = $(this).closest("form");
  207 + var textarea = $("textarea[rel=result]", form);
  208 + textarea.val('');
  209 + Fast.api.ajax({
  210 + url: "command/command/action/execute",
  211 + data: form.serialize(),
  212 + }, function (data, ret) {
  213 + textarea.val(data.result);
  214 + window.parent.$(".toolbar .btn-refresh").trigger('click');
  215 + top.window.Fast.api.refreshmenu();
  216 + return false;
  217 + }, function () {
  218 + window.parent.$(".toolbar .btn-refresh").trigger('click');
  219 + });
  220 + });
  221 + $("select[name='table']").trigger("change");
  222 + Controller.api.bindevent();
  223 + },
  224 + edit: function () {
  225 + Controller.api.bindevent();
  226 + },
  227 + api: {
  228 + bindevent: function () {
  229 + Form.api.bindevent($("form[role=form]"));
  230 + }
  231 + }
  232 + };
  233 + return Controller;
  234 +});
  1 +deny from all
  1 +<?php
  2 +
  3 +namespace app\admin\behavior;
  4 +
  5 +class AdminLog
  6 +{
  7 + public function run(&$params)
  8 + {
  9 + if (request()->isPost()) {
  10 + \app\admin\model\AdminLog::record();
  11 + }
  12 + }
  13 +}
  1 +<?php
  2 +
  3 +namespace app\admin\command;
  4 +
  5 +use think\addons\AddonException;
  6 +use think\addons\Service;
  7 +use think\Config;
  8 +use think\console\Command;
  9 +use think\console\Input;
  10 +use think\console\input\Option;
  11 +use think\console\Output;
  12 +use think\Db;
  13 +use think\Exception;
  14 +use think\exception\PDOException;
  15 +
  16 +class Addon extends Command
  17 +{
  18 +
  19 + protected function configure()
  20 + {
  21 + $this
  22 + ->setName('addon')
  23 + ->addOption('name', 'a', Option::VALUE_REQUIRED, 'addon name', null)
  24 + ->addOption('action', 'c', Option::VALUE_REQUIRED, 'action(create/enable/disable/install/uninstall/refresh/upgrade/package)', 'create')
  25 + ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', null)
  26 + ->addOption('release', 'r', Option::VALUE_OPTIONAL, 'addon release version', null)
  27 + ->addOption('uid', 'u', Option::VALUE_OPTIONAL, 'fastadmin uid', null)
  28 + ->addOption('token', 't', Option::VALUE_OPTIONAL, 'fastadmin token', null)
  29 + ->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local package', null)
  30 + ->setDescription('Addon manager');
  31 + }
  32 +
  33 + protected function execute(Input $input, Output $output)
  34 + {
  35 + $name = $input->getOption('name') ?: '';
  36 + $action = $input->getOption('action') ?: '';
  37 + if (stripos($name, 'addons/') !== false) {
  38 + $name = explode('/', $name)[1];
  39 + }
  40 + //强制覆盖
  41 + $force = $input->getOption('force');
  42 + //版本
  43 + $release = $input->getOption('release') ?: '';
  44 + //uid
  45 + $uid = $input->getOption('uid') ?: '';
  46 + //token
  47 + $token = $input->getOption('token') ?: '';
  48 +
  49 + include dirname(__DIR__) . DS . 'common.php';
  50 +
  51 + if (!$name) {
  52 + throw new Exception('Addon name could not be empty');
  53 + }
  54 + if (!$action || !in_array($action, ['create', 'disable', 'enable', 'install', 'uninstall', 'refresh', 'upgrade', 'package'])) {
  55 + throw new Exception('Please input correct action name');
  56 + }
  57 +
  58 + // 查询一次SQL,判断连接是否正常
  59 + Db::execute("SELECT 1");
  60 +
  61 + $addonDir = ADDON_PATH . $name . DS;
  62 + switch ($action) {
  63 + case 'create':
  64 + //非覆盖模式时如果存在则报错
  65 + if (is_dir($addonDir) && !$force) {
  66 + throw new Exception("addon already exists!\nIf you need to create again, use the parameter --force=true ");
  67 + }
  68 + //如果存在先移除
  69 + if (is_dir($addonDir)) {
  70 + rmdirs($addonDir);
  71 + }
  72 + mkdir($addonDir, 0755, true);
  73 + mkdir($addonDir . DS . 'controller', 0755, true);
  74 + $menuList = \app\common\library\Menu::export($name);
  75 + $createMenu = $this->getCreateMenu($menuList);
  76 + $prefix = Config::get('database.prefix');
  77 + $createTableSql = '';
  78 + try {
  79 + $result = Db::query("SHOW CREATE TABLE `" . $prefix . $name . "`;");
  80 + if (isset($result[0]) && isset($result[0]['Create Table'])) {
  81 + $createTableSql = $result[0]['Create Table'];
  82 + }
  83 + } catch (PDOException $e) {
  84 +
  85 + }
  86 +
  87 + $data = [
  88 + 'name' => $name,
  89 + 'addon' => $name,
  90 + 'addonClassName' => ucfirst($name),
  91 + 'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu, "\t") . ";\n\tMenu::create(\$menu);" : '',
  92 + 'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '',
  93 + 'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '',
  94 + 'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '',
  95 + ];
  96 + $this->writeToFile("addon", $data, $addonDir . ucfirst($name) . '.php');
  97 + $this->writeToFile("config", $data, $addonDir . 'config.php');
  98 + $this->writeToFile("info", $data, $addonDir . 'info.ini');
  99 + $this->writeToFile("controller", $data, $addonDir . 'controller' . DS . 'Index.php');
  100 + if ($createTableSql) {
  101 + $createTableSql = str_replace("`" . $prefix, '`__PREFIX__', $createTableSql);
  102 + file_put_contents($addonDir . 'install.sql', $createTableSql);
  103 + }
  104 +
  105 + $output->info("Create Successed!");
  106 + break;
  107 + case 'disable':
  108 + case 'enable':
  109 + try {
  110 + //调用启用、禁用的方法
  111 + Service::$action($name, 0);
  112 + } catch (AddonException $e) {
  113 + if ($e->getCode() != -3) {
  114 + throw new Exception($e->getMessage());
  115 + }
  116 + if (!$force) {
  117 + //如果有冲突文件则提醒
  118 + $data = $e->getData();
  119 + foreach ($data['conflictlist'] as $k => $v) {
  120 + $output->warning($v);
  121 + }
  122 + $output->info("Are you sure you want to " . ($action == 'enable' ? 'override' : 'delete') . " all those files? Type 'yes' to continue: ");
  123 + $line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
  124 + if (trim($line) != 'yes') {
  125 + throw new Exception("Operation is aborted!");
  126 + }
  127 + }
  128 + //调用启用、禁用的方法
  129 + Service::$action($name, 1);
  130 + } catch (Exception $e) {
  131 + throw new Exception($e->getMessage());
  132 + }
  133 + $output->info(ucfirst($action) . " Successed!");
  134 + break;
  135 + case 'install':
  136 + //非覆盖模式时如果存在则报错
  137 + if (is_dir($addonDir) && !$force) {
  138 + throw new Exception("addon already exists!\nIf you need to install again, use the parameter --force=true ");
  139 + }
  140 + //如果存在先移除
  141 + if (is_dir($addonDir)) {
  142 + rmdirs($addonDir);
  143 + }
  144 + // 获取本地路径
  145 + $local = $input->getOption('local');
  146 + try {
  147 + Service::install($name, 0, ['version' => $release], $local);
  148 + } catch (AddonException $e) {
  149 + if ($e->getCode() != -3) {
  150 + throw new Exception($e->getMessage());
  151 + }
  152 + if (!$force) {
  153 + //如果有冲突文件则提醒
  154 + $data = $e->getData();
  155 + foreach ($data['conflictlist'] as $k => $v) {
  156 + $output->warning($v);
  157 + }
  158 + $output->info("Are you sure you want to override all those files? Type 'yes' to continue: ");
  159 + $line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
  160 + if (trim($line) != 'yes') {
  161 + throw new Exception("Operation is aborted!");
  162 + }
  163 + }
  164 + Service::install($name, 1, ['version' => $release, 'uid' => $uid, 'token' => $token], $local);
  165 + } catch (Exception $e) {
  166 + throw new Exception($e->getMessage());
  167 + }
  168 +
  169 + $output->info("Install Successed!");
  170 + break;
  171 + case 'uninstall':
  172 + //非覆盖模式时如果存在则报错
  173 + if (!$force) {
  174 + throw new Exception("If you need to uninstall addon, use the parameter --force=true ");
  175 + }
  176 + try {
  177 + Service::uninstall($name, 0);
  178 + } catch (AddonException $e) {
  179 + if ($e->getCode() != -3) {
  180 + throw new Exception($e->getMessage());
  181 + }
  182 + if (!$force) {
  183 + //如果有冲突文件则提醒
  184 + $data = $e->getData();
  185 + foreach ($data['conflictlist'] as $k => $v) {
  186 + $output->warning($v);
  187 + }
  188 + $output->info("Are you sure you want to delete all those files? Type 'yes' to continue: ");
  189 + $line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
  190 + if (trim($line) != 'yes') {
  191 + throw new Exception("Operation is aborted!");
  192 + }
  193 + }
  194 + Service::uninstall($name, 1);
  195 + } catch (Exception $e) {
  196 + throw new Exception($e->getMessage());
  197 + }
  198 +
  199 + $output->info("Uninstall Successed!");
  200 + break;
  201 + case 'refresh':
  202 + Service::refresh();
  203 + $output->info("Refresh Successed!");
  204 + break;
  205 + case 'upgrade':
  206 + Service::upgrade($name, ['version' => $release, 'uid' => $uid, 'token' => $token]);
  207 + $output->info("Upgrade Successed!");
  208 + break;
  209 + case 'package':
  210 + $infoFile = $addonDir . 'info.ini';
  211 + if (!is_file($infoFile)) {
  212 + throw new Exception(__('Addon info file was not found'));
  213 + }
  214 +
  215 + $info = get_addon_info($name);
  216 + if (!$info) {
  217 + throw new Exception(__('Addon info file data incorrect'));
  218 + }
  219 + $infoname = isset($info['name']) ? $info['name'] : '';
  220 + if (!$infoname || !preg_match("/^[a-z]+$/i", $infoname) || $infoname != $name) {
  221 + throw new Exception(__('Addon info name incorrect'));
  222 + }
  223 +
  224 + $infoversion = isset($info['version']) ? $info['version'] : '';
  225 + if (!$infoversion || !preg_match("/^\d+\.\d+\.\d+$/i", $infoversion)) {
  226 + throw new Exception(__('Addon info version incorrect'));
  227 + }
  228 +
  229 + $addonTmpDir = RUNTIME_PATH . 'addons' . DS;
  230 + if (!is_dir($addonTmpDir)) {
  231 + @mkdir($addonTmpDir, 0755, true);
  232 + }
  233 + $addonFile = $addonTmpDir . $infoname . '-' . $infoversion . '.zip';
  234 + if (!class_exists('ZipArchive')) {
  235 + throw new Exception(__('ZinArchive not install'));
  236 + }
  237 + $zip = new \ZipArchive;
  238 + $zip->open($addonFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
  239 +
  240 + $files = new \RecursiveIteratorIterator(
  241 + new \RecursiveDirectoryIterator($addonDir), \RecursiveIteratorIterator::LEAVES_ONLY
  242 + );
  243 +
  244 + foreach ($files as $name => $file) {
  245 + if (!$file->isDir()) {
  246 + $filePath = $file->getRealPath();
  247 + $relativePath = str_replace(DS, '/', substr($filePath, strlen($addonDir)));
  248 + if (!in_array($file->getFilename(), ['.git', '.DS_Store', 'Thumbs.db'])) {
  249 + $zip->addFile($filePath, $relativePath);
  250 + }
  251 + }
  252 + }
  253 + $zip->close();
  254 + $output->info("Package Successed!");
  255 + break;
  256 +
  257 + default :
  258 + break;
  259 + }
  260 + }
  261 +
  262 + /**
  263 + * 获取创建菜单的数组
  264 + * @param array $menu
  265 + * @return array
  266 + */
  267 + protected function getCreateMenu($menu)
  268 + {
  269 + $result = [];
  270 + foreach ($menu as $k => & $v) {
  271 + $arr = [
  272 + 'name' => $v['name'],
  273 + 'title' => $v['title'],
  274 + ];
  275 + if ($v['icon'] != 'fa fa-circle-o') {
  276 + $arr['icon'] = $v['icon'];
  277 + }
  278 + if ($v['ismenu']) {
  279 + $arr['ismenu'] = $v['ismenu'];
  280 + }
  281 + if (isset($v['childlist']) && $v['childlist']) {
  282 + $arr['sublist'] = $this->getCreateMenu($v['childlist']);
  283 + }
  284 + $result[] = $arr;
  285 + }
  286 + return $result;
  287 + }
  288 +
  289 + /**
  290 + * 写入到文件
  291 + * @param string $name
  292 + * @param array $data
  293 + * @param string $pathname
  294 + * @return mixed
  295 + */
  296 + protected function writeToFile($name, $data, $pathname)
  297 + {
  298 + $search = $replace = [];
  299 + foreach ($data as $k => $v) {
  300 + $search[] = "{%{$k}%}";
  301 + $replace[] = $v;
  302 + }
  303 + $stub = file_get_contents($this->getStub($name));
  304 + $content = str_replace($search, $replace, $stub);
  305 +
  306 + if (!is_dir(dirname($pathname))) {
  307 + mkdir(strtolower(dirname($pathname)), 0755, true);
  308 + }
  309 + return file_put_contents($pathname, $content);
  310 + }
  311 +
  312 + /**
  313 + * 获取基础模板
  314 + * @param string $name
  315 + * @return string
  316 + */
  317 + protected function getStub($name)
  318 + {
  319 + return __DIR__ . '/Addon/stubs/' . $name . '.stub';
  320 + }
  321 +
  322 +}
  1 +<?php
  2 +
  3 +namespace addons\{%name%};
  4 +
  5 +use app\common\library\Menu;
  6 +use think\Addons;
  7 +
  8 +/**
  9 + * 插件
  10 + */
  11 +class {%addonClassName%} extends Addons
  12 +{
  13 +
  14 + /**
  15 + * 插件安装方法
  16 + * @return bool
  17 + */
  18 + public function install()
  19 + {
  20 + {%addonInstallMenu%}
  21 + return true;
  22 + }
  23 +
  24 + /**
  25 + * 插件卸载方法
  26 + * @return bool
  27 + */
  28 + public function uninstall()
  29 + {
  30 + {%addonUninstallMenu%}
  31 + return true;
  32 + }
  33 +
  34 + /**
  35 + * 插件启用方法
  36 + * @return bool
  37 + */
  38 + public function enable()
  39 + {
  40 + {%addonEnableMenu%}
  41 + return true;
  42 + }
  43 +
  44 + /**
  45 + * 插件禁用方法
  46 + * @return bool
  47 + */
  48 + public function disable()
  49 + {
  50 + {%addonDisableMenu%}
  51 + return true;
  52 + }
  53 +
  54 + /**
  55 + * 实现钩子方法
  56 + * @return mixed
  57 + */
  58 + public function testhook($param)
  59 + {
  60 + // 调用钩子时候的参数信息
  61 + print_r($param);
  62 + // 当前插件的配置信息,配置信息存在当前目录的config.php文件中,见下方
  63 + print_r($this->getConfig());
  64 + // 可以返回模板,模板文件默认读取的为插件目录中的文件。模板名不能为空!
  65 + //return $this->fetch('view/info');
  66 + }
  67 +
  68 +}
  1 +<?php
  2 +
  3 +return [
  4 + [
  5 + //配置唯一标识
  6 + 'name' => 'usernmae',
  7 + //显示的标题
  8 + 'title' => '用户名',
  9 + //类型
  10 + 'type' => 'string',
  11 + //数据字典
  12 + 'content' => [
  13 + ],
  14 + //值
  15 + 'value' => '',
  16 + //验证规则
  17 + 'rule' => 'required',
  18 + //错误消息
  19 + 'msg' => '',
  20 + //提示消息
  21 + 'tip' => '',
  22 + //成功消息
  23 + 'ok' => '',
  24 + //扩展信息
  25 + 'extend' => ''
  26 + ],
  27 + [
  28 + 'name' => 'password',
  29 + 'title' => '密码',
  30 + 'type' => 'string',
  31 + 'content' => [
  32 + ],
  33 + 'value' => '',
  34 + 'rule' => 'required',
  35 + 'msg' => '',
  36 + 'tip' => '',
  37 + 'ok' => '',
  38 + 'extend' => ''
  39 + ],
  40 +];
  1 +<?php
  2 +
  3 +namespace addons\{%addon%}\controller;
  4 +
  5 +use think\addons\Controller;
  6 +
  7 +class Index extends Controller
  8 +{
  9 +
  10 + public function index()
  11 + {
  12 + $this->error("当前插件暂无前台页面");
  13 + }
  14 +
  15 +}
  1 +name = {%name%}
  2 +title = 插件名称{%name%}
  3 +intro = FastAdmin插件
  4 +author = yourname
  5 +website = https://www.fastadmin.net
  6 +version = 1.0.0
  7 +state = 1
  1 +<?php
  2 +
  3 +namespace app\admin\command;
  4 +
  5 +use app\admin\command\Api\library\Builder;
  6 +use think\Config;
  7 +use think\console\Command;
  8 +use think\console\Input;
  9 +use think\console\input\Option;
  10 +use think\console\Output;
  11 +use think\Exception;
  12 +
  13 +class Api extends Command
  14 +{
  15 + protected function configure()
  16 + {
  17 + $site = Config::get('site');
  18 + $this
  19 + ->setName('api')
  20 + ->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
  21 + ->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
  22 + ->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
  23 + ->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
  24 + ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
  25 + ->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'])
  26 + ->addOption('author', 'a', Option::VALUE_OPTIONAL, 'document author', $site['name'])
  27 + ->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
  28 + ->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
  29 + ->setDescription('Build Api document from controller');
  30 + }
  31 +
  32 + protected function execute(Input $input, Output $output)
  33 + {
  34 + $apiDir = __DIR__ . DS . 'Api' . DS;
  35 +
  36 + $force = $input->getOption('force');
  37 + $url = $input->getOption('url');
  38 + $language = $input->getOption('language');
  39 + $language = $language ? $language : 'zh-cn';
  40 + $langFile = $apiDir . 'lang' . DS . $language . '.php';
  41 + if (!is_file($langFile)) {
  42 + throw new Exception('language file not found');
  43 + }
  44 + $lang = include_once $langFile;
  45 + // 目标目录
  46 + $output_dir = ROOT_PATH . 'public' . DS;
  47 + $output_file = $output_dir . $input->getOption('output');
  48 + if (is_file($output_file) && !$force) {
  49 + throw new Exception("api index file already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  50 + }
  51 + // 模板文件
  52 + $template_dir = $apiDir . 'template' . DS;
  53 + $template_file = $template_dir . $input->getOption('template');
  54 + if (!is_file($template_file)) {
  55 + throw new Exception('template file not found');
  56 + }
  57 + // 额外的类
  58 + $classes = $input->getOption('class');
  59 + // 标题
  60 + $title = $input->getOption('title');
  61 + // 作者
  62 + $author = $input->getOption('author');
  63 + // 模块
  64 + $module = $input->getOption('module');
  65 +
  66 + $moduleDir = APP_PATH . $module . DS;
  67 + if (!is_dir($moduleDir)) {
  68 + throw new Exception('module not found');
  69 + }
  70 +
  71 + if (version_compare(PHP_VERSION, '7.0.0', '<')) {
  72 + if (extension_loaded('Zend OPcache')) {
  73 + $configuration = opcache_get_configuration();
  74 + $directives = $configuration['directives'];
  75 + $configName = request()->isCli() ? 'opcache.enable_cli' : 'opcache.enable';
  76 + if (!$directives[$configName]) {
  77 + throw new Exception("Please make sure {$configName} is turned on, Get help:https://forum.fastadmin.net/d/1321");
  78 + }
  79 + } else {
  80 + throw new Exception("Please make sure opcache already enabled, Get help:https://forum.fastadmin.net/d/1321");
  81 + }
  82 + }
  83 +
  84 + $controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
  85 + $files = new \RecursiveIteratorIterator(
  86 + new \RecursiveDirectoryIterator($controllerDir),
  87 + \RecursiveIteratorIterator::LEAVES_ONLY
  88 + );
  89 +
  90 + foreach ($files as $name => $file) {
  91 + if (!$file->isDir() && $file->getExtension() == 'php') {
  92 + $filePath = $file->getRealPath();
  93 + $classes[] = $this->get_class_from_file($filePath);
  94 + }
  95 + }
  96 + $classes = array_unique(array_filter($classes));
  97 +
  98 + $config = [
  99 + 'title' => $title,
  100 + 'author' => $author,
  101 + 'description' => '',
  102 + 'apiurl' => $url,
  103 + 'language' => $language,
  104 + ];
  105 + $builder = new Builder($classes);
  106 + $content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
  107 +
  108 + if (!file_put_contents($output_file, $content)) {
  109 + throw new Exception('Cannot save the content to ' . $output_file);
  110 + }
  111 + $output->info("Build Successed!");
  112 + }
  113 +
  114 + /**
  115 + * get full qualified class name
  116 + *
  117 + * @param string $path_to_file
  118 + * @author JBYRNE http://jarretbyrne.com/2015/06/197/
  119 + * @return string
  120 + */
  121 + protected function get_class_from_file($path_to_file)
  122 + {
  123 + //Grab the contents of the file
  124 + $contents = file_get_contents($path_to_file);
  125 +
  126 + //Start with a blank namespace and class
  127 + $namespace = $class = "";
  128 +
  129 + //Set helper values to know that we have found the namespace/class token and need to collect the string values after them
  130 + $getting_namespace = $getting_class = false;
  131 +
  132 + //Go through each token and evaluate it as necessary
  133 + foreach (token_get_all($contents) as $token) {
  134 +
  135 + //If this token is the namespace declaring, then flag that the next tokens will be the namespace name
  136 + if (is_array($token) && $token[0] == T_NAMESPACE) {
  137 + $getting_namespace = true;
  138 + }
  139 +
  140 + //If this token is the class declaring, then flag that the next tokens will be the class name
  141 + if (is_array($token) && $token[0] == T_CLASS) {
  142 + $getting_class = true;
  143 + }
  144 +
  145 + //While we're grabbing the namespace name...
  146 + if ($getting_namespace === true) {
  147 +
  148 + //If the token is a string or the namespace separator...
  149 + if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {
  150 +
  151 + //Append the token's value to the name of the namespace
  152 + $namespace .= $token[1];
  153 + } elseif ($token === ';') {
  154 +
  155 + //If the token is the semicolon, then we're done with the namespace declaration
  156 + $getting_namespace = false;
  157 + }
  158 + }
  159 +
  160 + //While we're grabbing the class name...
  161 + if ($getting_class === true) {
  162 +
  163 + //If the token is a string, it's the name of the class
  164 + if (is_array($token) && $token[0] == T_STRING) {
  165 +
  166 + //Store the token's value as the class name
  167 + $class = $token[1];
  168 +
  169 + //Got what we need, stope here
  170 + break;
  171 + }
  172 + }
  173 + }
  174 +
  175 + //Build the fully-qualified class name and return it
  176 + return $namespace ? $namespace . '\\' . $class : $class;
  177 + }
  178 +}
  1 +<?php
  2 +
  3 +return [
  4 + 'Info' => '基础信息',
  5 + 'Sandbox' => '在线测试',
  6 + 'Sampleoutput' => '返回示例',
  7 + 'Headers' => 'Headers',
  8 + 'Parameters' => '参数',
  9 + 'Body' => '正文',
  10 + 'Name' => '名称',
  11 + 'Type' => '类型',
  12 + 'Required' => '必选',
  13 + 'Description' => '描述',
  14 + 'Send' => '提交',
  15 + 'Reset' => '重置',
  16 + 'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中',
  17 + 'Apiurltips' => 'API接口URL',
  18 + 'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中',
  19 + 'ReturnHeaders' => '响应头',
  20 + 'ReturnParameters' => '返回参数',
  21 + 'Response' => '响应输出',
  22 +];
  1 +<?php
  2 +
  3 +namespace app\admin\command\Api\library;
  4 +
  5 +use think\Config;
  6 +
  7 +/**
  8 + * @website https://github.com/calinrada/php-apidoc
  9 + * @author Calin Rada <rada.calin@gmail.com>
  10 + * @author Karson <karsonzhang@163.com>
  11 + */
  12 +class Builder
  13 +{
  14 +
  15 + /**
  16 + *
  17 + * @var \think\View
  18 + */
  19 + public $view = null;
  20 +
  21 + /**
  22 + * parse classes
  23 + * @var array
  24 + */
  25 + protected $classes = [];
  26 +
  27 + /**
  28 + *
  29 + * @param array $classes
  30 + */
  31 + public function __construct($classes = [])
  32 + {
  33 + $this->classes = array_merge($this->classes, $classes);
  34 + $this->view = new \think\View(Config::get('template'), Config::get('view_replace_str'));
  35 + }
  36 +
  37 + protected function extractAnnotations()
  38 + {
  39 + foreach ($this->classes as $class) {
  40 + $classAnnotation = Extractor::getClassAnnotations($class);
  41 + // 如果忽略
  42 + if (isset($classAnnotation['ApiInternal'])) {
  43 + continue;
  44 + }
  45 + Extractor::getClassMethodAnnotations($class);
  46 + }
  47 + $allClassAnnotation = Extractor::getAllClassAnnotations();
  48 + $allClassMethodAnnotation = Extractor::getAllClassMethodAnnotations();
  49 +
  50 +// foreach ($allClassMethodAnnotation as $className => &$methods) {
  51 +// foreach ($methods as &$method) {
  52 +// //权重判断
  53 +// if ($method && !isset($method['ApiWeigh']) && isset($allClassAnnotation[$className]['ApiWeigh'])) {
  54 +// $method['ApiWeigh'] = $allClassAnnotation[$className]['ApiWeigh'];
  55 +// }
  56 +// }
  57 +// }
  58 +// unset($methods);
  59 + return [$allClassAnnotation, $allClassMethodAnnotation];
  60 + }
  61 +
  62 + protected function generateHeadersTemplate($docs)
  63 + {
  64 + if (!isset($docs['ApiHeaders'])) {
  65 + return [];
  66 + }
  67 +
  68 + $headerslist = array();
  69 + foreach ($docs['ApiHeaders'] as $params) {
  70 + $tr = array(
  71 + 'name' => $params['name'],
  72 + 'type' => $params['type'],
  73 + 'sample' => isset($params['sample']) ? $params['sample'] : '',
  74 + 'required' => isset($params['required']) ? $params['required'] : false,
  75 + 'description' => isset($params['description']) ? $params['description'] : '',
  76 + );
  77 + $headerslist[] = $tr;
  78 + }
  79 +
  80 + return $headerslist;
  81 + }
  82 +
  83 + protected function generateParamsTemplate($docs)
  84 + {
  85 + if (!isset($docs['ApiParams'])) {
  86 + return [];
  87 + }
  88 +
  89 + $paramslist = array();
  90 + foreach ($docs['ApiParams'] as $params) {
  91 + $tr = array(
  92 + 'name' => $params['name'],
  93 + 'type' => isset($params['type']) ? $params['type'] : 'string',
  94 + 'sample' => isset($params['sample']) ? $params['sample'] : '',
  95 + 'required' => isset($params['required']) ? $params['required'] : true,
  96 + 'description' => isset($params['description']) ? $params['description'] : '',
  97 + );
  98 + $paramslist[] = $tr;
  99 + }
  100 +
  101 + return $paramslist;
  102 + }
  103 +
  104 + protected function generateReturnHeadersTemplate($docs)
  105 + {
  106 + if (!isset($docs['ApiReturnHeaders'])) {
  107 + return [];
  108 + }
  109 +
  110 + $headerslist = array();
  111 + foreach ($docs['ApiReturnHeaders'] as $params) {
  112 + $tr = array(
  113 + 'name' => $params['name'],
  114 + 'type' => 'string',
  115 + 'sample' => isset($params['sample']) ? $params['sample'] : '',
  116 + 'required' => isset($params['required']) && $params['required'] ? 'Yes' : 'No',
  117 + 'description' => isset($params['description']) ? $params['description'] : '',
  118 + );
  119 + $headerslist[] = $tr;
  120 + }
  121 +
  122 + return $headerslist;
  123 + }
  124 +
  125 + protected function generateReturnParamsTemplate($st_params)
  126 + {
  127 + if (!isset($st_params['ApiReturnParams'])) {
  128 + return [];
  129 + }
  130 +
  131 + $paramslist = array();
  132 + foreach ($st_params['ApiReturnParams'] as $params) {
  133 + $tr = array(
  134 + 'name' => $params['name'],
  135 + 'type' => isset($params['type']) ? $params['type'] : 'string',
  136 + 'sample' => isset($params['sample']) ? $params['sample'] : '',
  137 + 'description' => isset($params['description']) ? $params['description'] : '',
  138 + );
  139 + $paramslist[] = $tr;
  140 + }
  141 +
  142 + return $paramslist;
  143 + }
  144 +
  145 + protected function generateBadgeForMethod($data)
  146 + {
  147 + $method = strtoupper(is_array($data['ApiMethod'][0]) ? $data['ApiMethod'][0]['data'] : $data['ApiMethod'][0]);
  148 + $labes = array(
  149 + 'POST' => 'label-primary',
  150 + 'GET' => 'label-success',
  151 + 'PUT' => 'label-warning',
  152 + 'DELETE' => 'label-danger',
  153 + 'PATCH' => 'label-default',
  154 + 'OPTIONS' => 'label-info'
  155 + );
  156 +
  157 + return isset($labes[$method]) ? $labes[$method] : $labes['GET'];
  158 + }
  159 +
  160 + public function parse()
  161 + {
  162 + list($allClassAnnotations, $allClassMethodAnnotations) = $this->extractAnnotations();
  163 +
  164 + $sectorArr = [];
  165 + foreach ($allClassAnnotations as $index => $allClassAnnotation) {
  166 + $sector = isset($allClassAnnotation['ApiSector']) ? $allClassAnnotation['ApiSector'][0] : $allClassAnnotation['ApiTitle'][0];
  167 + $sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0;
  168 + }
  169 + arsort($sectorArr);
  170 + $routes = include_once CONF_PATH . 'route.php';
  171 + $subdomain = false;
  172 + if (config('url_domain_deploy') && isset($routes['__domain__']) && isset($routes['__domain__']['api']) && $routes['__domain__']['api']) {
  173 + $subdomain = true;
  174 + }
  175 + $counter = 0;
  176 + $section = null;
  177 + $weigh = 0;
  178 + $docslist = [];
  179 + foreach ($allClassMethodAnnotations as $class => $methods) {
  180 + foreach ($methods as $name => $docs) {
  181 + if (isset($docs['ApiSector'][0])) {
  182 + $section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0];
  183 + } else {
  184 + $section = $class;
  185 + }
  186 + if (0 === count($docs)) {
  187 + continue;
  188 + }
  189 + $route = is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0];
  190 + if ($subdomain) {
  191 + $route = substr($route, 4);
  192 + }
  193 + $docslist[$section][$name] = [
  194 + 'id' => $counter,
  195 + 'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
  196 + 'method_label' => $this->generateBadgeForMethod($docs),
  197 + 'section' => $section,
  198 + 'route' => $route,
  199 + 'title' => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
  200 + 'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
  201 + 'body' => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
  202 + 'headerslist' => $this->generateHeadersTemplate($docs),
  203 + 'paramslist' => $this->generateParamsTemplate($docs),
  204 + 'returnheaderslist' => $this->generateReturnHeadersTemplate($docs),
  205 + 'returnparamslist' => $this->generateReturnParamsTemplate($docs),
  206 + 'weigh' => is_array($docs['ApiWeigh'][0]) ? $docs['ApiWeigh'][0]['data'] : $docs['ApiWeigh'][0],
  207 + 'return' => isset($docs['ApiReturn']) ? is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0] : '',
  208 + ];
  209 + $counter++;
  210 + }
  211 + }
  212 +
  213 + //重建排序
  214 + foreach ($docslist as $index => &$methods) {
  215 + $methodSectorArr = [];
  216 + foreach ($methods as $name => $method) {
  217 + $methodSectorArr[$name] = isset($method['weigh']) ? $method['weigh'] : 0;
  218 + }
  219 + arsort($methodSectorArr);
  220 + $methods = array_merge(array_flip(array_keys($methodSectorArr)), $methods);
  221 + }
  222 + $docslist = array_merge(array_flip(array_keys($sectorArr)), $docslist);
  223 +
  224 + return $docslist;
  225 + }
  226 +
  227 + public function getView()
  228 + {
  229 + return $this->view;
  230 + }
  231 +
  232 + /**
  233 + * 渲染
  234 + * @param string $template
  235 + * @param array $vars
  236 + * @return string
  237 + */
  238 + public function render($template, $vars = [])
  239 + {
  240 + $docslist = $this->parse();
  241 +
  242 + return $this->view->display(file_get_contents($template), array_merge($vars, ['docslist' => $docslist]));
  243 + }
  244 +}
  1 +<?php
  2 +
  3 +namespace app\admin\command\Api\library;
  4 +
  5 +use Exception;
  6 +
  7 +/**
  8 + * Class imported from https://github.com/eriknyk/Annotations
  9 + * @author Erik Amaru Ortiz https://github.com/eriknyk‎
  10 + *
  11 + * @license http://opensource.org/licenses/bsd-license.php The BSD License
  12 + * @author Calin Rada <rada.calin@gmail.com>
  13 + */
  14 +class Extractor
  15 +{
  16 +
  17 + /**
  18 + * Static array to store already parsed annotations
  19 + * @var array
  20 + */
  21 + private static $annotationCache;
  22 +
  23 + private static $classAnnotationCache;
  24 +
  25 + private static $classMethodAnnotationCache;
  26 +
  27 + /**
  28 + * Indicates that annotations should has strict behavior, 'false' by default
  29 + * @var boolean
  30 + */
  31 + private $strict = false;
  32 +
  33 + /**
  34 + * Stores the default namespace for Objects instance, usually used on methods like getMethodAnnotationsObjects()
  35 + * @var string
  36 + */
  37 + public $defaultNamespace = '';
  38 +
  39 + /**
  40 + * Sets strict variable to true/false
  41 + * @param bool $value boolean value to indicate that annotations to has strict behavior
  42 + */
  43 + public function setStrict($value)
  44 + {
  45 + $this->strict = (bool)$value;
  46 + }
  47 +
  48 + /**
  49 + * Sets default namespace to use in object instantiation
  50 + * @param string $namespace default namespace
  51 + */
  52 + public function setDefaultNamespace($namespace)
  53 + {
  54 + $this->defaultNamespace = $namespace;
  55 + }
  56 +
  57 + /**
  58 + * Gets default namespace used in object instantiation
  59 + * @return string $namespace default namespace
  60 + */
  61 + public function getDefaultAnnotationNamespace()
  62 + {
  63 + return $this->defaultNamespace;
  64 + }
  65 +
  66 + /**
  67 + * Gets all anotations with pattern @SomeAnnotation() from a given class
  68 + *
  69 + * @param string $className class name to get annotations
  70 + * @return array self::$classAnnotationCache all annotated elements
  71 + */
  72 + public static function getClassAnnotations($className)
  73 + {
  74 + if (!isset(self::$classAnnotationCache[$className])) {
  75 + $class = new \ReflectionClass($className);
  76 + self::$classAnnotationCache[$className] = self::parseAnnotations($class->getDocComment());
  77 + }
  78 +
  79 + return self::$classAnnotationCache[$className];
  80 + }
  81 +
  82 + /**
  83 + * 获取类所有方法的属性配置
  84 + * @param $className
  85 + * @return mixed
  86 + * @throws \ReflectionException
  87 + */
  88 + public static function getClassMethodAnnotations($className)
  89 + {
  90 + $class = new \ReflectionClass($className);
  91 +
  92 + foreach ($class->getMethods() as $object) {
  93 + self::$classMethodAnnotationCache[$className][$object->name] = self::getMethodAnnotations($className, $object->name);
  94 + }
  95 +
  96 + return self::$classMethodAnnotationCache[$className];
  97 + }
  98 +
  99 + public static function getAllClassAnnotations()
  100 + {
  101 + return self::$classAnnotationCache;
  102 + }
  103 +
  104 + public static function getAllClassMethodAnnotations()
  105 + {
  106 + return self::$classMethodAnnotationCache;
  107 + }
  108 +
  109 + /**
  110 + * Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
  111 + *
  112 + * @param string $className class name
  113 + * @param string $methodName method name to get annotations
  114 + * @return array self::$annotationCache all annotated elements of a method given
  115 + */
  116 + public static function getMethodAnnotations($className, $methodName)
  117 + {
  118 + if (!isset(self::$annotationCache[$className . '::' . $methodName])) {
  119 + try {
  120 + $method = new \ReflectionMethod($className, $methodName);
  121 + $class = new \ReflectionClass($className);
  122 + if (!$method->isPublic() || $method->isConstructor()) {
  123 + $annotations = array();
  124 + } else {
  125 + $annotations = self::consolidateAnnotations($method, $class);
  126 + }
  127 + } catch (\ReflectionException $e) {
  128 + $annotations = array();
  129 + }
  130 +
  131 + self::$annotationCache[$className . '::' . $methodName] = $annotations;
  132 + }
  133 +
  134 + return self::$annotationCache[$className . '::' . $methodName];
  135 + }
  136 +
  137 + /**
  138 + * Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
  139 + * and instance its abcAnnotation class
  140 + *
  141 + * @param string $className class name
  142 + * @param string $methodName method name to get annotations
  143 + * @return array self::$annotationCache all annotated objects of a method given
  144 + */
  145 + public function getMethodAnnotationsObjects($className, $methodName)
  146 + {
  147 + $annotations = $this->getMethodAnnotations($className, $methodName);
  148 + $objects = array();
  149 +
  150 + $i = 0;
  151 +
  152 + foreach ($annotations as $annotationClass => $listParams) {
  153 + $annotationClass = ucfirst($annotationClass);
  154 + $class = $this->defaultNamespace . $annotationClass . 'Annotation';
  155 +
  156 + // verify is the annotation class exists, depending if Annotations::strict is true
  157 + // if not, just skip the annotation instance creation.
  158 + if (!class_exists($class)) {
  159 + if ($this->strict) {
  160 + throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class));
  161 + } else {
  162 + // silent skip & continue
  163 + continue;
  164 + }
  165 + }
  166 +
  167 + if (empty($objects[$annotationClass])) {
  168 + $objects[$annotationClass] = new $class();
  169 + }
  170 +
  171 + foreach ($listParams as $params) {
  172 + if (is_array($params)) {
  173 + foreach ($params as $key => $value) {
  174 + $objects[$annotationClass]->set($key, $value);
  175 + }
  176 + } else {
  177 + $objects[$annotationClass]->set($i++, $params);
  178 + }
  179 + }
  180 + }
  181 +
  182 + return $objects;
  183 + }
  184 +
  185 + private static function consolidateAnnotations($method, $class)
  186 + {
  187 + $dockblockClass = $class->getDocComment();
  188 + $docblockMethod = $method->getDocComment();
  189 + $methodName = $method->getName();
  190 +
  191 + $methodAnnotations = self::parseAnnotations($docblockMethod);
  192 + $classAnnotations = self::parseAnnotations($dockblockClass);
  193 + if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') {
  194 + return [];
  195 + }
  196 +
  197 + $properties = $class->getDefaultProperties();
  198 + $noNeedLogin = isset($properties['noNeedLogin']) ? is_array($properties['noNeedLogin']) ? $properties['noNeedLogin'] : [$properties['noNeedLogin']] : [];
  199 + $noNeedRight = isset($properties['noNeedRight']) ? is_array($properties['noNeedRight']) ? $properties['noNeedRight'] : [$properties['noNeedRight']] : [];
  200 +
  201 + preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblockMethod), $methodArr);
  202 + preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $dockblockClass), $classArr);
  203 +
  204 + if (!isset($methodAnnotations['ApiMethod'])) {
  205 + $methodAnnotations['ApiMethod'] = ['get'];
  206 + }
  207 + if (!isset($methodAnnotations['ApiWeigh'])) {
  208 + $methodAnnotations['ApiWeigh'] = [0];
  209 + }
  210 + if (!isset($methodAnnotations['ApiSummary'])) {
  211 + $methodAnnotations['ApiSummary'] = $methodAnnotations['ApiTitle'];
  212 + }
  213 +
  214 + if ($methodAnnotations) {
  215 + foreach ($classAnnotations as $name => $valueClass) {
  216 + if (count($valueClass) !== 1) {
  217 + continue;
  218 + }
  219 +
  220 + if ($name === 'ApiRoute') {
  221 + if (isset($methodAnnotations[$name])) {
  222 + $methodAnnotations[$name] = [rtrim($valueClass[0], '/') . $methodAnnotations[$name][0]];
  223 + } else {
  224 + $methodAnnotations[$name] = [rtrim($valueClass[0], '/') . '/' . $method->getName()];
  225 + }
  226 + }
  227 +
  228 + if ($name === 'ApiSector') {
  229 + $methodAnnotations[$name] = $valueClass;
  230 + }
  231 + }
  232 + }
  233 + if (!isset($methodAnnotations['ApiRoute'])) {
  234 + $urlArr = [];
  235 + $className = $class->getName();
  236 +
  237 + list($prefix, $suffix) = explode('\\' . \think\Config::get('url_controller_layer') . '\\', $className);
  238 + $prefixArr = explode('\\', $prefix);
  239 + $suffixArr = explode('\\', $suffix);
  240 + if ($prefixArr[0] == \think\Config::get('app_namespace')) {
  241 + $prefixArr[0] = '';
  242 + }
  243 + $urlArr = array_merge($urlArr, $prefixArr);
  244 + $urlArr[] = implode('.', array_map(function ($item) {
  245 + return \think\Loader::parseName($item);
  246 + }, $suffixArr));
  247 + $urlArr[] = $method->getName();
  248 +
  249 + $methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
  250 + }
  251 + if (!isset($methodAnnotations['ApiSector'])) {
  252 + $methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : $classAnnotations['ApiTitle'];
  253 + }
  254 + if (!isset($methodAnnotations['ApiParams'])) {
  255 + $params = self::parseCustomAnnotations($docblockMethod, 'param');
  256 + foreach ($params as $k => $v) {
  257 + $arr = explode(' ', preg_replace("/[\s]+/", " ", $v));
  258 + $methodAnnotations['ApiParams'][] = [
  259 + 'name' => isset($arr[1]) ? str_replace('$', '', $arr[1]) : '',
  260 + 'nullable' => false,
  261 + 'type' => isset($arr[0]) ? $arr[0] : 'string',
  262 + 'description' => isset($arr[2]) ? $arr[2] : ''
  263 + ];
  264 + }
  265 + }
  266 + $methodAnnotations['ApiPermissionLogin'] = [!in_array('*', $noNeedLogin) && !in_array($methodName, $noNeedLogin)];
  267 + $methodAnnotations['ApiPermissionRight'] = [!in_array('*', $noNeedRight) && !in_array($methodName, $noNeedRight)];
  268 + return $methodAnnotations;
  269 + }
  270 +
  271 + /**
  272 + * Parse annotations
  273 + *
  274 + * @param string $docblock
  275 + * @param string $name
  276 + * @return array parsed annotations params
  277 + */
  278 + private static function parseCustomAnnotations($docblock, $name = 'param')
  279 + {
  280 + $annotations = array();
  281 +
  282 + $docblock = substr($docblock, 3, -2);
  283 + if (preg_match_all('/@' . $name . '(?:\s*(?:\(\s*)?(.*?)(?:\s*\))?)??\s*(?:\n|\*\/)/', $docblock, $matches)) {
  284 + foreach ($matches[1] as $k => $v) {
  285 + $annotations[] = $v;
  286 + }
  287 + }
  288 + return $annotations;
  289 + }
  290 +
  291 + /**
  292 + * Parse annotations
  293 + *
  294 + * @param string $docblock
  295 + * @return array parsed annotations params
  296 + */
  297 + private static function parseAnnotations($docblock)
  298 + {
  299 + $annotations = array();
  300 +
  301 + // Strip away the docblock header and footer to ease parsing of one line annotations
  302 + $docblock = substr($docblock, 3, -2);
  303 + if (preg_match_all('/@(?<name>[A-Za-z_-]+)[\s\t]*\((?<args>(?:(?!\)).)*)\)\r?/s', $docblock, $matches)) {
  304 + $numMatches = count($matches[0]);
  305 + for ($i = 0; $i < $numMatches; ++$i) {
  306 + $name = $matches['name'][$i];
  307 + $value = '';
  308 + // annotations has arguments
  309 + if (isset($matches['args'][$i])) {
  310 + $argsParts = trim($matches['args'][$i]);
  311 + if ($name == 'ApiReturn') {
  312 + $value = $argsParts;
  313 + } elseif ($matches['args'][$i] != '') {
  314 + $argsParts = preg_replace("/\{(\w+)\}/", '#$1#', $argsParts);
  315 + $value = self::parseArgs($argsParts);
  316 + if (is_string($value)) {
  317 + $value = preg_replace("/\#(\w+)\#/", '{$1}', $argsParts);
  318 + }
  319 + }
  320 + }
  321 +
  322 + $annotations[$name][] = $value;
  323 + }
  324 + }
  325 + if (stripos($docblock, '@ApiInternal') !== false) {
  326 + $annotations['ApiInternal'] = [true];
  327 + }
  328 + if (!isset($annotations['ApiTitle'])) {
  329 + preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblock), $matchArr);
  330 + $title = isset($matchArr[1]) && isset($matchArr[1][0]) ? $matchArr[1][0] : '';
  331 + $annotations['ApiTitle'] = [$title];
  332 + }
  333 +
  334 + return $annotations;
  335 + }
  336 +
  337 + /**
  338 + * Parse individual annotation arguments
  339 + *
  340 + * @param string $content arguments string
  341 + * @return array annotated arguments
  342 + */
  343 + private static function parseArgs($content)
  344 + {
  345 + // Replace initial stars
  346 + $content = preg_replace('/^\s*\*/m', '', $content);
  347 +
  348 + $data = array();
  349 + $len = strlen($content);
  350 + $i = 0;
  351 + $var = '';
  352 + $val = '';
  353 + $level = 1;
  354 +
  355 + $prevDelimiter = '';
  356 + $nextDelimiter = '';
  357 + $nextToken = '';
  358 + $composing = false;
  359 + $type = 'plain';
  360 + $delimiter = null;
  361 + $quoted = false;
  362 + $tokens = array('"', '"', '{', '}', ',', '=');
  363 +
  364 + while ($i <= $len) {
  365 + $prev_c = substr($content, $i - 1, 1);
  366 + $c = substr($content, $i++, 1);
  367 +
  368 + if ($c === '"' && $prev_c !== "\\") {
  369 + $delimiter = $c;
  370 + //open delimiter
  371 + if (!$composing && empty($prevDelimiter) && empty($nextDelimiter)) {
  372 + $prevDelimiter = $nextDelimiter = $delimiter;
  373 + $val = '';
  374 + $composing = true;
  375 + $quoted = true;
  376 + } else {
  377 + // close delimiter
  378 + if ($c !== $nextDelimiter) {
  379 + throw new Exception(sprintf(
  380 + "Parse Error: enclosing error -> expected: [%s], given: [%s]",
  381 + $nextDelimiter,
  382 + $c
  383 + ));
  384 + }
  385 +
  386 + // validating syntax
  387 + if ($i < $len) {
  388 + if (',' !== substr($content, $i, 1) && '\\' !== $prev_c) {
  389 + throw new Exception(sprintf(
  390 + "Parse Error: missing comma separator near: ...%s<--",
  391 + substr($content, ($i - 10), $i)
  392 + ));
  393 + }
  394 + }
  395 +
  396 + $prevDelimiter = $nextDelimiter = '';
  397 + $composing = false;
  398 + $delimiter = null;
  399 + }
  400 + } elseif (!$composing && in_array($c, $tokens)) {
  401 + switch ($c) {
  402 + case '=':
  403 + $prevDelimiter = $nextDelimiter = '';
  404 + $level = 2;
  405 + $composing = false;
  406 + $type = 'assoc';
  407 + $quoted = false;
  408 + break;
  409 + case ',':
  410 + $level = 3;
  411 +
  412 + // If composing flag is true yet,
  413 + // it means that the string was not enclosed, so it is parsing error.
  414 + if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) {
  415 + throw new Exception(sprintf(
  416 + "Parse Error: enclosing error -> expected: [%s], given: [%s]",
  417 + $nextDelimiter,
  418 + $c
  419 + ));
  420 + }
  421 +
  422 + $prevDelimiter = $nextDelimiter = '';
  423 + break;
  424 + case '{':
  425 + $subc = '';
  426 + $subComposing = true;
  427 +
  428 + while ($i <= $len) {
  429 + $c = substr($content, $i++, 1);
  430 +
  431 + if (isset($delimiter) && $c === $delimiter) {
  432 + throw new Exception(sprintf(
  433 + "Parse Error: Composite variable is not enclosed correctly."
  434 + ));
  435 + }
  436 +
  437 + if ($c === '}') {
  438 + $subComposing = false;
  439 + break;
  440 + }
  441 + $subc .= $c;
  442 + }
  443 +
  444 + // if the string is composing yet means that the structure of var. never was enclosed with '}'
  445 + if ($subComposing) {
  446 + throw new Exception(sprintf(
  447 + "Parse Error: Composite variable is not enclosed correctly. near: ...%s'",
  448 + $subc
  449 + ));
  450 + }
  451 +
  452 + $val = self::parseArgs($subc);
  453 + break;
  454 + }
  455 + } else {
  456 + if ($level == 1) {
  457 + $var .= $c;
  458 + } elseif ($level == 2) {
  459 + $val .= $c;
  460 + }
  461 + }
  462 +
  463 + if ($level === 3 || $i === $len) {
  464 + if ($type == 'plain' && $i === $len) {
  465 + $data = self::castValue($var);
  466 + } else {
  467 + $data[trim($var)] = self::castValue($val, !$quoted);
  468 + }
  469 +
  470 + $level = 1;
  471 + $var = $val = '';
  472 + $composing = false;
  473 + $quoted = false;
  474 + }
  475 + }
  476 +
  477 + return $data;
  478 + }
  479 +
  480 + /**
  481 + * Try determinate the original type variable of a string
  482 + *
  483 + * @param string $val string containing possibles variables that can be cast to bool or int
  484 + * @param boolean $trim indicate if the value passed should be trimmed after to try cast
  485 + * @return mixed returns the value converted to original type if was possible
  486 + */
  487 + private static function castValue($val, $trim = false)
  488 + {
  489 + if (is_array($val)) {
  490 + foreach ($val as $key => $value) {
  491 + $val[$key] = self::castValue($value);
  492 + }
  493 + } elseif (is_string($val)) {
  494 + if ($trim) {
  495 + $val = trim($val);
  496 + }
  497 + $val = stripslashes($val);
  498 + $tmp = strtolower($val);
  499 +
  500 + if ($tmp === 'false' || $tmp === 'true') {
  501 + $val = $tmp === 'true';
  502 + } elseif (is_numeric($val)) {
  503 + return $val + 0;
  504 + }
  505 +
  506 + unset($tmp);
  507 + }
  508 +
  509 + return $val;
  510 + }
  511 +}
  1 +<!DOCTYPE html>
  2 +<html lang="{$config.language}">
  3 + <head>
  4 + <meta charset="utf-8">
  5 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7 + <meta name="description" content="">
  8 + <meta name="author" content="{$config.author}">
  9 + <title>{$config.title}</title>
  10 +
  11 + <!-- Bootstrap Core CSS -->
  12 + <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  13 +
  14 + <!-- Plugin CSS -->
  15 + <link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  16 +
  17 + <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
  18 + <!--[if lt IE 9]>
  19 + <script src="https://cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
  20 + <script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
  21 + <![endif]-->
  22 +
  23 + <style type="text/css">
  24 + body {
  25 + padding-top: 70px; margin-bottom: 15px;
  26 + -webkit-font-smoothing: antialiased;
  27 + -moz-osx-font-smoothing: grayscale;
  28 + 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;
  29 + font-weight: 400;
  30 + }
  31 + h2 { font-size: 1.6em; }
  32 + hr { margin-top: 10px; }
  33 + .tab-pane { padding-top: 10px; }
  34 + .mt0 { margin-top: 0px; }
  35 + .footer { font-size: 12px; color: #666; }
  36 + .label { display: inline-block; min-width: 65px; padding: 0.3em 0.6em 0.3em; }
  37 + .string { color: green; }
  38 + .number { color: darkorange; }
  39 + .boolean { color: blue; }
  40 + .null { color: magenta; }
  41 + .key { color: red; }
  42 + .popover { max-width: 400px; max-height: 400px; overflow-y: auto;}
  43 + .list-group.panel > .list-group-item {
  44 + }
  45 + .list-group-item:last-child {
  46 + border-radius:0;
  47 + }
  48 + h4.panel-title a {
  49 + font-weight:normal;
  50 + font-size:14px;
  51 + }
  52 + h4.panel-title a .text-muted {
  53 + font-size:12px;
  54 + font-weight:normal;
  55 + font-family: 'Verdana';
  56 + }
  57 + #sidebar {
  58 + width: 220px;
  59 + position: fixed;
  60 + margin-left: -240px;
  61 + overflow-y:auto;
  62 + }
  63 + #sidebar > .list-group {
  64 + margin-bottom:0;
  65 + }
  66 + #sidebar > .list-group > a{
  67 + text-indent:0;
  68 + }
  69 + #sidebar .child {
  70 + border:1px solid #ddd;
  71 + border-bottom:none;
  72 + }
  73 + #sidebar .child > a {
  74 + border:0;
  75 + }
  76 + #sidebar .list-group a.current {
  77 + background:#f5f5f5;
  78 + }
  79 + @media (max-width: 1620px){
  80 + #sidebar {
  81 + margin:0;
  82 + }
  83 + #accordion {
  84 + padding-left:235px;
  85 + }
  86 + }
  87 + @media (max-width: 768px){
  88 + #sidebar {
  89 + display: none;
  90 + }
  91 + #accordion {
  92 + padding-left:0px;
  93 + }
  94 + }
  95 + .label-primary {
  96 + background-color: #248aff;
  97 + }
  98 +
  99 + </style>
  100 + </head>
  101 + <body>
  102 + <!-- Fixed navbar -->
  103 + <div class="navbar navbar-default navbar-fixed-top" role="navigation">
  104 + <div class="container">
  105 + <div class="navbar-header">
  106 + <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
  107 + <span class="sr-only">Toggle navigation</span>
  108 + <span class="icon-bar"></span>
  109 + <span class="icon-bar"></span>
  110 + <span class="icon-bar"></span>
  111 + </button>
  112 + <a class="navbar-brand" href="https://www.fastadmin.net" target="_blank">{$config.title}</a>
  113 + </div>
  114 + <div class="navbar-collapse collapse">
  115 + <form class="navbar-form navbar-right">
  116 + <div class="form-group">
  117 + Token:
  118 + </div>
  119 + <div class="form-group">
  120 + <input type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Tokentips}" placeholder="token" id="token" />
  121 + </div>
  122 + <div class="form-group">
  123 + Apiurl:
  124 + </div>
  125 + <div class="form-group">
  126 + <input id="apiUrl" type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Apiurltips}" placeholder="https://api.mydomain.com" value="{$config.apiurl}" />
  127 + </div>
  128 + <div class="form-group">
  129 + <button type="button" class="btn btn-success btn-sm" data-toggle="tooltip" title="{$lang.Savetips}" id="save_data">
  130 + <span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
  131 + </button>
  132 + </div>
  133 + </form>
  134 + </div><!--/.nav-collapse -->
  135 + </div>
  136 + </div>
  137 +
  138 + <div class="container">
  139 + <!-- menu -->
  140 + <div id="sidebar">
  141 + <div class="list-group panel">
  142 + {foreach name="docslist" id="docs"}
  143 + <a href="#{$key}" class="list-group-item" data-toggle="collapse" data-parent="#sidebar">{$key} <i class="fa fa-caret-down"></i></a>
  144 + <div class="child collapse" id="{$key}">
  145 + {foreach name="docs" id="api" }
  146 + <a href="javascript:;" data-id="{$api.id}" class="list-group-item">{$api.title}</a>
  147 + {/foreach}
  148 + </div>
  149 + {/foreach}
  150 + </div>
  151 + </div>
  152 + <div class="panel-group" id="accordion">
  153 + {foreach name="docslist" id="docs"}
  154 + <h2>{$key}</h2>
  155 + <hr>
  156 + {foreach name="docs" id="api" }
  157 + <div class="panel panel-default">
  158 + <div class="panel-heading" id="heading-{$api.id}">
  159 + <h4 class="panel-title">
  160 + <span class="label {$api.method_label}">{$api.method|strtoupper}</span>
  161 + <a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.title} <span class="text-muted">{$api.route}</span></a>
  162 + </h4>
  163 + </div>
  164 + <div id="collapseOne{$api.id}" class="panel-collapse collapse">
  165 + <div class="panel-body">
  166 +
  167 + <!-- Nav tabs -->
  168 + <ul class="nav nav-tabs" id="doctab{$api.id}">
  169 + <li class="active"><a href="#info{$api.id}" data-toggle="tab">{$lang.Info}</a></li>
  170 + <li><a href="#sandbox{$api.id}" data-toggle="tab">{$lang.Sandbox}</a></li>
  171 + <li><a href="#sample{$api.id}" data-toggle="tab">{$lang.Sampleoutput}</a></li>
  172 + </ul>
  173 +
  174 + <!-- Tab panes -->
  175 + <div class="tab-content">
  176 +
  177 + <div class="tab-pane active" id="info{$api.id}">
  178 + <div class="well">
  179 + {$api.summary}
  180 + </div>
  181 + <div class="panel panel-default">
  182 + <div class="panel-heading"><strong>{$lang.Headers}</strong></div>
  183 + <div class="panel-body">
  184 + {if $api.headerslist}
  185 + <table class="table table-hover">
  186 + <thead>
  187 + <tr>
  188 + <th>{$lang.Name}</th>
  189 + <th>{$lang.Type}</th>
  190 + <th>{$lang.Required}</th>
  191 + <th>{$lang.Description}</th>
  192 + </tr>
  193 + </thead>
  194 + <tbody>
  195 + {foreach name="api['headerslist']" id="header"}
  196 + <tr>
  197 + <td>{$header.name}</td>
  198 + <td>{$header.type}</td>
  199 + <td>{$header.required?'是':'否'}</td>
  200 + <td>{$header.description}</td>
  201 + </tr>
  202 + {/foreach}
  203 + </tbody>
  204 + </table>
  205 + {else /}
  206 +
  207 + {/if}
  208 + </div>
  209 + </div>
  210 + <div class="panel panel-default">
  211 + <div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
  212 + <div class="panel-body">
  213 + {if $api.paramslist}
  214 + <table class="table table-hover">
  215 + <thead>
  216 + <tr>
  217 + <th>{$lang.Name}</th>
  218 + <th>{$lang.Type}</th>
  219 + <th>{$lang.Required}</th>
  220 + <th>{$lang.Description}</th>
  221 + </tr>
  222 + </thead>
  223 + <tbody>
  224 + {foreach name="api['paramslist']" id="param"}
  225 + <tr>
  226 + <td>{$param.name}</td>
  227 + <td>{$param.type}</td>
  228 + <td>{:$param.required?'是':'否'}</td>
  229 + <td>{$param.description}</td>
  230 + </tr>
  231 + {/foreach}
  232 + </tbody>
  233 + </table>
  234 + {else /}
  235 +
  236 + {/if}
  237 + </div>
  238 + </div>
  239 + <div class="panel panel-default">
  240 + <div class="panel-heading"><strong>{$lang.Body}</strong></div>
  241 + <div class="panel-body">
  242 + {$api.body|default='无'}
  243 + </div>
  244 + </div>
  245 + </div><!-- #info -->
  246 +
  247 + <div class="tab-pane" id="sandbox{$api.id}">
  248 + <div class="row">
  249 + <div class="col-md-12">
  250 + {if $api.headerslist}
  251 + <div class="panel panel-default">
  252 + <div class="panel-heading"><strong>{$lang.Headers}</strong></div>
  253 + <div class="panel-body">
  254 + <div class="headers">
  255 + {foreach name="api['headerslist']" id="param"}
  256 + <div class="form-group">
  257 + <label class="control-label" for="{$param.name}">{$param.name}</label>
  258 + <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}">
  259 + </div>
  260 + {/foreach}
  261 + </div>
  262 + </div>
  263 + </div>
  264 + {/if}
  265 + <div class="panel panel-default">
  266 + <div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
  267 + <div class="panel-body">
  268 + <form enctype="application/x-www-form-urlencoded" role="form" action="{$api.route}" method="{$api.method}" name="form{$api.id}" id="form{$api.id}">
  269 + {if $api.paramslist}
  270 + {foreach name="api['paramslist']" id="param"}
  271 + <div class="form-group">
  272 + <label class="control-label" for="{$param.name}">{$param.name}</label>
  273 + <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}">
  274 + </div>
  275 + {/foreach}
  276 + {else /}
  277 + <div class="form-group">
  278 +
  279 + </div>
  280 + {/if}
  281 + <div class="form-group">
  282 + <button type="submit" class="btn btn-success send" rel="{$api.id}">{$lang.Send}</button>
  283 + <button type="reset" class="btn btn-info" rel="{$api.id}">{$lang.Reset}</button>
  284 + </div>
  285 + </form>
  286 + </div>
  287 + </div>
  288 + <div class="panel panel-default">
  289 + <div class="panel-heading"><strong>{$lang.Response}</strong></div>
  290 + <div class="panel-body">
  291 + <div class="row">
  292 + <div class="col-md-12" style="overflow-x:auto">
  293 + <pre id="response_headers{$api.id}"></pre>
  294 + <pre id="response{$api.id}"></pre>
  295 + </div>
  296 + </div>
  297 + </div>
  298 + </div>
  299 + <div class="panel panel-default">
  300 + <div class="panel-heading"><strong>{$lang.ReturnParameters}</strong></div>
  301 + <div class="panel-body">
  302 + {if $api.returnparamslist}
  303 + <table class="table table-hover">
  304 + <thead>
  305 + <tr>
  306 + <th>{$lang.Name}</th>
  307 + <th>{$lang.Type}</th>
  308 + <th>{$lang.Description}</th>
  309 + </tr>
  310 + </thead>
  311 + <tbody>
  312 + {foreach name="api['returnparamslist']" id="param"}
  313 + <tr>
  314 + <td>{$param.name}</td>
  315 + <td>{$param.type}</td>
  316 + <td>{$param.description}</td>
  317 + </tr>
  318 + {/foreach}
  319 + </tbody>
  320 + </table>
  321 + {else /}
  322 +
  323 + {/if}
  324 + </div>
  325 + </div>
  326 + </div>
  327 + </div>
  328 + </div><!-- #sandbox -->
  329 +
  330 + <div class="tab-pane" id="sample{$api.id}">
  331 + <div class="row">
  332 + <div class="col-md-12">
  333 + <pre id="sample_response{$api.id}">{$api.return|default='无'}</pre>
  334 + </div>
  335 + </div>
  336 + </div><!-- #sample -->
  337 +
  338 + </div><!-- .tab-content -->
  339 + </div>
  340 + </div>
  341 + </div>
  342 + {/foreach}
  343 + {/foreach}
  344 + </div>
  345 +
  346 + <hr>
  347 +
  348 + <div class="row mt0 footer">
  349 + <div class="col-md-6" align="left">
  350 + Generated on {:date('Y-m-d H:i:s')}
  351 + </div>
  352 + <div class="col-md-6" align="right">
  353 + <a href="https://www.fastadmin.net" target="_blank">FastAdmin</a>
  354 + </div>
  355 + </div>
  356 +
  357 + </div> <!-- /container -->
  358 +
  359 + <!-- jQuery -->
  360 + <script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
  361 +
  362 + <!-- Bootstrap Core JavaScript -->
  363 + <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
  364 +
  365 + <script type="text/javascript">
  366 + function syntaxHighlight(json) {
  367 + if (typeof json != 'string') {
  368 + json = JSON.stringify(json, undefined, 2);
  369 + }
  370 + json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  371 + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
  372 + var cls = 'number';
  373 + if (/^"/.test(match)) {
  374 + if (/:$/.test(match)) {
  375 + cls = 'key';
  376 + } else {
  377 + cls = 'string';
  378 + }
  379 + } else if (/true|false/.test(match)) {
  380 + cls = 'boolean';
  381 + } else if (/null/.test(match)) {
  382 + cls = 'null';
  383 + }
  384 + return '<span class="' + cls + '">' + match + '</span>';
  385 + });
  386 + }
  387 +
  388 + function prepareStr(str) {
  389 + try {
  390 + return syntaxHighlight(JSON.stringify(JSON.parse(str.replace(/'/g, '"')), null, 2));
  391 + } catch (e) {
  392 + return str;
  393 + }
  394 + }
  395 + var storage = (function () {
  396 + var uid = new Date;
  397 + var storage;
  398 + var result;
  399 + try {
  400 + (storage = window.localStorage).setItem(uid, uid);
  401 + result = storage.getItem(uid) == uid;
  402 + storage.removeItem(uid);
  403 + return result && storage;
  404 + } catch (exception) {
  405 + }
  406 + }());
  407 +
  408 + $.fn.serializeObject = function ()
  409 + {
  410 + var o = {};
  411 + var a = this.serializeArray();
  412 + $.each(a, function () {
  413 + if (!this.value) {
  414 + return;
  415 + }
  416 + if (o[this.name] !== undefined) {
  417 + if (!o[this.name].push) {
  418 + o[this.name] = [o[this.name]];
  419 + }
  420 + o[this.name].push(this.value || '');
  421 + } else {
  422 + o[this.name] = this.value || '';
  423 + }
  424 + });
  425 + return o;
  426 + };
  427 +
  428 + $(document).ready(function () {
  429 +
  430 + if (storage) {
  431 + storage.getItem('token') && $('#token').val(storage.getItem('token'));
  432 + storage.getItem('apiUrl') && $('#apiUrl').val(storage.getItem('apiUrl'));
  433 + }
  434 +
  435 + $('[data-toggle="tooltip"]').tooltip({
  436 + placement: 'bottom'
  437 + });
  438 +
  439 + $(window).on("resize", function(){
  440 + $("#sidebar").css("max-height", $(window).height()-80);
  441 + });
  442 +
  443 + $(window).trigger("resize");
  444 +
  445 + $(document).on("click", "#sidebar .list-group > .list-group-item", function(){
  446 + $("#sidebar .list-group > .list-group-item").removeClass("current");
  447 + $(this).addClass("current");
  448 + });
  449 + $(document).on("click", "#sidebar .child a", function(){
  450 + var heading = $("#heading-"+$(this).data("id"));
  451 + if(!heading.next().hasClass("in")){
  452 + $("a", heading).trigger("click");
  453 + }
  454 + $("html,body").animate({scrollTop:heading.offset().top-70});
  455 + });
  456 +
  457 + $('code[id^=response]').hide();
  458 +
  459 + $.each($('pre[id^=sample_response],pre[id^=sample_post_body]'), function () {
  460 + if ($(this).html() == 'NA') {
  461 + return;
  462 + }
  463 + var str = prepareStr($(this).html());
  464 + $(this).html(str);
  465 + });
  466 +
  467 + $("[data-toggle=popover]").popover({placement: 'right'});
  468 +
  469 + $('[data-toggle=popover]').on('shown.bs.popover', function () {
  470 + var $sample = $(this).parent().find(".popover-content"),
  471 + str = $(this).data('content');
  472 + if (typeof str == "undefined" || str === "") {
  473 + return;
  474 + }
  475 + var str = prepareStr(str);
  476 + $sample.html('<pre>' + str + '</pre>');
  477 + });
  478 +
  479 + $('body').on('click', '#save_data', function (e) {
  480 + if (storage) {
  481 + storage.setItem('token', $('#token').val());
  482 + storage.setItem('apiUrl', $('#apiUrl').val());
  483 + } else {
  484 + alert('Your browser does not support local storage');
  485 + }
  486 + });
  487 +
  488 + $('body').on('click', '.send', function (e) {
  489 + e.preventDefault();
  490 + var form = $(this).closest('form');
  491 + //added /g to get all the matched params instead of only first
  492 + var matchedParamsInRoute = $(form).attr('action').match(/[^{]+(?=\})/g);
  493 + var theId = $(this).attr('rel');
  494 + //keep a copy of action attribute in order to modify the copy
  495 + //instead of the initial attribute
  496 + var url = $(form).attr('action');
  497 + var method = $(form).prop('method').toLowerCase() || 'get';
  498 +
  499 + var formData = new FormData();
  500 +
  501 + $(form).find('input').each(function (i, input) {
  502 + if ($(input).attr('type').toLowerCase() == 'file') {
  503 + formData.append($(input).attr('name'), $(input)[0].files[0]);
  504 + method = 'post';
  505 + } else {
  506 + formData.append($(input).attr('name'), $(input).val())
  507 + }
  508 + });
  509 +
  510 + var index, key, value;
  511 +
  512 + if (matchedParamsInRoute) {
  513 + var params = {};
  514 + formData.forEach(function(value, key){
  515 + params[key] = value;
  516 + });
  517 + for (index = 0; index < matchedParamsInRoute.length; ++index) {
  518 + try {
  519 + key = matchedParamsInRoute[index];
  520 + value = params[key];
  521 + if (typeof value == "undefined")
  522 + value = "";
  523 + url = url.replace("\{" + key + "\}", value);
  524 + formData.delete(key);
  525 + } catch (err) {
  526 + console.log(err);
  527 + }
  528 + }
  529 + }
  530 +
  531 + var headers = {};
  532 +
  533 + var token = $('#token').val();
  534 + if (token.length > 0) {
  535 + headers['token'] = token;
  536 + }
  537 +
  538 + $("#sandbox" + theId + " .headers input[type=text]").each(function () {
  539 + val = $(this).val();
  540 + if (val.length > 0) {
  541 + headers[$(this).prop('name')] = val;
  542 + }
  543 + });
  544 +
  545 + $.ajax({
  546 + url: $('#apiUrl').val() + url,
  547 + data: method == 'get' ? $(form).serialize() : formData,
  548 + type: method,
  549 + dataType: 'json',
  550 + contentType: false,
  551 + processData: false,
  552 + headers: headers,
  553 + xhrFields: {
  554 + withCredentials: true
  555 + },
  556 + success: function (data, textStatus, xhr) {
  557 + if (typeof data === 'object') {
  558 + var str = JSON.stringify(data, null, 2);
  559 + $('#response' + theId).html(syntaxHighlight(str));
  560 + } else {
  561 + $('#response' + theId).html(data || '');
  562 + }
  563 + $('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
  564 + $('#response' + theId).show();
  565 + },
  566 + error: function (xhr, textStatus, error) {
  567 + try {
  568 + var str = JSON.stringify($.parseJSON(xhr.responseText), null, 2);
  569 + } catch (e) {
  570 + var str = xhr.responseText;
  571 + }
  572 + $('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
  573 + $('#response' + theId).html(syntaxHighlight(str));
  574 + $('#response' + theId).show();
  575 + }
  576 + });
  577 + return false;
  578 + });
  579 + });
  580 + </script>
  581 + </body>
  582 +</html>