aiyou 4 years ago
parent
commit
f02d5fdc74
100 changed files with 6202 additions and 0 deletions
  1. 10 0
      .bowerrc
  2. 11 0
      .env.sample
  3. 4 0
      .gitignore
  4. 2 0
      .idea/.gitignore
  5. 6 0
      .idea/inspectionProfiles/Project_Default.xml
  6. 16 0
      .idea/jsLinters/jshint.xml
  7. 6 0
      .idea/misc.xml
  8. 8 0
      .idea/modules.xml
  9. 39 0
      .idea/php.xml
  10. 43 0
      .idea/qiche.iml
  11. 7 0
      .idea/symfony2.xml
  12. 7 0
      .idea/vcs.xml
  13. 191 0
      LICENSE
  14. 69 0
      addons/command/Command.php
  15. 236 0
      addons/command/application/admin/controller/Command.php
  16. 16 0
      addons/command/application/admin/lang/zh-cn/command.php
  17. 59 0
      addons/command/application/admin/model/Command.php
  18. 27 0
      addons/command/application/admin/validate/Command.php
  19. 400 0
      addons/command/application/admin/view/command/add.html
  20. 42 0
      addons/command/application/admin/view/command/detail.html
  21. 25 0
      addons/command/application/admin/view/command/index.html
  22. 4 0
      addons/command/config.php
  23. 15 0
      addons/command/controller/Index.php
  24. 8 0
      addons/command/info.ini
  25. 12 0
      addons/command/install.sql
  26. 28 0
      addons/command/library/Output.php
  27. 234 0
      addons/command/public/assets/js/backend/command.js
  28. 65 0
      addons/epay/Epay.php
  29. 91 0
      addons/epay/assets/css/common.css
  30. 20 0
      addons/epay/assets/css/epay.css
  31. 100 0
      addons/epay/assets/css/wechat.css
  32. BIN
      addons/epay/assets/images/alipay.png
  33. BIN
      addons/epay/assets/images/expired.png
  34. BIN
      addons/epay/assets/images/logo-alipay.png
  35. BIN
      addons/epay/assets/images/logo-wechat.png
  36. BIN
      addons/epay/assets/images/logo.png
  37. BIN
      addons/epay/assets/images/paid.png
  38. BIN
      addons/epay/assets/images/scan.png
  39. BIN
      addons/epay/assets/images/tips.png
  40. BIN
      addons/epay/assets/images/wechat.png
  41. 52 0
      addons/epay/assets/js/common.js
  42. 113 0
      addons/epay/assets/less/common.less
  43. 28 0
      addons/epay/assets/less/epay.less
  44. 0 0
      addons/epay/certs/apiclient_cert.pem
  45. 0 0
      addons/epay/certs/apiclient_key.pem
  46. 64 0
      addons/epay/config.php
  47. 229 0
      addons/epay/controller/Api.php
  48. 108 0
      addons/epay/controller/Index.php
  49. 8 0
      addons/epay/info.ini
  50. 17 0
      addons/epay/library/OrderException.php
  51. 267 0
      addons/epay/library/Service.php
  52. 107 0
      addons/epay/library/Wechat.php
  53. 63 0
      addons/epay/library/Yansongda/Pay/Contracts/GatewayInterface.php
  54. 7 0
      addons/epay/library/Yansongda/Pay/Exceptions/Exception.php
  55. 28 0
      addons/epay/library/Yansongda/Pay/Exceptions/GatewayException.php
  56. 7 0
      addons/epay/library/Yansongda/Pay/Exceptions/InvalidArgumentException.php
  57. 291 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/Alipay.php
  58. 46 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/AppGateway.php
  59. 47 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/PosGateway.php
  60. 44 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/ScanGateway.php
  61. 44 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/TransferGateway.php
  62. 48 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/WapGateway.php
  63. 46 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/WebGateway.php
  64. 50 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/AppGateway.php
  65. 82 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/GroupredpackGateway.php
  66. 49 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/MiniappGateway.php
  67. 47 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/MpGateway.php
  68. 46 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/PosGateway.php
  69. 86 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/RedpackGateway.php
  70. 38 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/ScanGateway.php
  71. 78 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/TransferGateway.php
  72. 41 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/WapGateway.php
  73. 69 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/WebGateway.php
  74. 354 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/Wechat.php
  75. 134 0
      addons/epay/library/Yansongda/Pay/Pay.php
  76. 147 0
      addons/epay/library/Yansongda/Pay/Support/Config.php
  77. 119 0
      addons/epay/library/Yansongda/Pay/Traits/HasHttpRequest.php
  78. 86 0
      addons/epay/view/api/wechat.html
  79. 212 0
      addons/epay/view/index/index.html
  80. 115 0
      addons/epay/view/layout/default.html
  81. 183 0
      addons/example/Example.php
  82. 39 0
      addons/example/application/admin/controller/example/Baidumap.php
  83. 131 0
      addons/example/application/admin/controller/example/Bootstraptable.php
  84. 22 0
      addons/example/application/admin/controller/example/Colorbadge.php
  85. 22 0
      addons/example/application/admin/controller/example/Controllerjump.php
  86. 30 0
      addons/example/application/admin/controller/example/Customform.php
  87. 24 0
      addons/example/application/admin/controller/example/Customsearch.php
  88. 21 0
      addons/example/application/admin/controller/example/Cxselect.php
  89. 44 0
      addons/example/application/admin/controller/example/Echarts.php
  90. 90 0
      addons/example/application/admin/controller/example/Multitable.php
  91. 49 0
      addons/example/application/admin/controller/example/Relationmodel.php
  92. 81 0
      addons/example/application/admin/controller/example/Tablelink.php
  93. 58 0
      addons/example/application/admin/controller/example/Tabletemplate.php
  94. 15 0
      addons/example/application/admin/model/Area.php
  95. 21 0
      addons/example/application/admin/view/example/baidumap/index.html
  96. 30 0
      addons/example/application/admin/view/example/baidumap/map.html
  97. 33 0
      addons/example/application/admin/view/example/bootstraptable/detail.html
  98. 49 0
      addons/example/application/admin/view/example/bootstraptable/index.html
  99. 21 0
      addons/example/application/admin/view/example/colorbadge/index.html
  100. 21 0
      addons/example/application/admin/view/example/controllerjump/index.html

+ 10 - 0
.bowerrc

@@ -0,0 +1,10 @@
+{
+  "directory": "public/assets/libs",
+  "ignoredDependencies": [
+    "es6-promise",
+    "file-saver",
+    "html2canvas",
+    "jspdf",
+    "jspdf-autotable"
+  ]
+}

+ 11 - 0
.env.sample

@@ -0,0 +1,11 @@
+[app]
+debug = true
+trace = false
+
+[database]
+hostname = 127.0.0.1
+database = fastadmin
+username = root
+password = root
+hostport = 3306
+prefix = fa_

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+thinkphp/
+application/database.php
+runtime
+public/uploads

+ 2 - 0
.idea/.gitignore

@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml

+ 6 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="JSHint" enabled="true" level="ERROR" enabled_by_default="true" />
+  </profile>
+</component>

+ 16 - 0
.idea/jsLinters/jshint.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JSHintConfiguration" version="2.10.2" use-config-file="true" use-custom-config-file="true" custom-config-file-path="$PROJECT_DIR$/public/assets/libs/Sortable/.jshintrc">
+    <option bitwise="true" />
+    <option browser="true" />
+    <option curly="true" />
+    <option eqeqeq="true" />
+    <option forin="true" />
+    <option maxerr="50" />
+    <option noarg="true" />
+    <option noempty="true" />
+    <option nonew="true" />
+    <option strict="true" />
+    <option undef="true" />
+  </component>
+</project>

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/qiche.iml" filepath="$PROJECT_DIR$/.idea/qiche.iml" />
+    </modules>
+  </component>
+</project>

+ 39 - 0
.idea/php.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PhpIncludePathManager">
+    <include_path>
+      <path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
+      <path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
+      <path value="$PROJECT_DIR$/vendor/karsonzhang/fastadmin-addons" />
+      <path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
+      <path value="$PROJECT_DIR$/vendor/monolog/monolog" />
+      <path value="$PROJECT_DIR$/vendor/psr/http-message" />
+      <path value="$PROJECT_DIR$/vendor/symfony/polyfill-php70" />
+      <path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
+      <path value="$PROJECT_DIR$/vendor/paragonie/random_compat" />
+      <path value="$PROJECT_DIR$/vendor/topthink/think-captcha" />
+      <path value="$PROJECT_DIR$/vendor/pimple/pimple" />
+      <path value="$PROJECT_DIR$/vendor/overtrue/wechat" />
+      <path value="$PROJECT_DIR$/vendor/overtrue/socialite" />
+      <path value="$PROJECT_DIR$/vendor/mtdowling/cron-expression" />
+      <path value="$PROJECT_DIR$/vendor/overtrue/pinyin" />
+      <path value="$PROJECT_DIR$/vendor/endroid/qr-code" />
+      <path value="$PROJECT_DIR$/vendor/composer" />
+      <path value="$PROJECT_DIR$/vendor/doctrine/cache" />
+      <path value="$PROJECT_DIR$/vendor/psr/container" />
+      <path value="$PROJECT_DIR$/vendor/symfony/psr-http-message-bridge" />
+      <path value="$PROJECT_DIR$/vendor/phpmailer/phpmailer" />
+      <path value="$PROJECT_DIR$/vendor/psr/log" />
+      <path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
+      <path value="$PROJECT_DIR$/vendor/topthink/think-installer" />
+      <path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
+      <path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
+      <path value="$PROJECT_DIR$/vendor/markbaker/complex" />
+      <path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
+      <path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
+      <path value="$PROJECT_DIR$/vendor/symfony/polyfill-php72" />
+      <path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
+      <path value="$PROJECT_DIR$/vendor/markbaker/matrix" />
+    </include_path>
+  </component>
+</project>

+ 43 - 0
.idea/qiche.iml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/endroid/qr-code" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/karsonzhang/fastadmin-addons" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/markbaker/complex" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/markbaker/matrix" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/monolog/monolog" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/mtdowling/cron-expression" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/overtrue/pinyin" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/overtrue/socialite" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/overtrue/wechat" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/paragonie/random_compat" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/phpmailer/phpmailer" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/phpoffice/phpspreadsheet" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/pimple/pimple" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/psr/simple-cache" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php70" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/psr-http-message-bridge" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/topthink/think-captcha" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/topthink/think-installer" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 7 - 0
.idea/symfony2.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Symfony2PluginSettings">
+    <option name="directoryToWeb" value="public" />
+    <option name="pluginEnabled" value="true" />
+  </component>
+</project>

+ 7 - 0
.idea/vcs.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 191 - 0
LICENSE

@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "{}" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright 2017 Karson
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 69 - 0
addons/command/Command.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace addons\command;
+
+use app\common\library\Menu;
+use think\Addons;
+
+/**
+ * 在线命令插件
+ */
+class Command extends Addons
+{
+
+    /**
+     * 插件安装方法
+     * @return bool
+     */
+    public function install()
+    {
+        $menu = [
+            [
+                'name'    => 'command',
+                'title'   => '在线命令管理',
+                'icon'    => 'fa fa-terminal',
+                'sublist' => [
+                    ['name' => 'command/index', 'title' => '查看'],
+                    ['name' => 'command/add', 'title' => '添加'],
+                    ['name' => 'command/detail', 'title' => '详情'],
+                    ['name' => 'command/execute', 'title' => '运行'],
+                    ['name' => 'command/del', 'title' => '删除'],
+                    ['name' => 'command/multi', 'title' => '批量更新'],
+                ]
+            ]
+        ];
+        Menu::create($menu);
+        return true;
+    }
+
+    /**
+     * 插件卸载方法
+     * @return bool
+     */
+    public function uninstall()
+    {
+        Menu::delete('command');
+        return true;
+    }
+
+    /**
+     * 插件启用方法
+     * @return bool
+     */
+    public function enable()
+    {
+        Menu::enable('command');
+        return true;
+    }
+
+    /**
+     * 插件禁用方法
+     * @return bool
+     */
+    public function disable()
+    {
+        Menu::disable('command');
+        return true;
+    }
+
+}

+ 236 - 0
addons/command/application/admin/controller/Command.php

@@ -0,0 +1,236 @@
+<?php
+
+namespace app\admin\controller;
+
+use app\common\controller\Backend;
+use think\Config;
+use think\console\Input;
+use think\Db;
+use think\Exception;
+
+/**
+ * 在线命令管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Command extends Backend
+{
+
+    /**
+     * Command模型对象
+     */
+    protected $model = null;
+    protected $noNeedRight = ['get_controller_list', 'get_field_list'];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('Command');
+        $this->view->assign("statusList", $this->model->getStatusList());
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+
+        $tableList = [];
+        $list = \think\Db::query("SHOW TABLES");
+        foreach ($list as $key => $row) {
+            $tableList[reset($row)] = reset($row);
+        }
+
+        $this->view->assign("tableList", $tableList);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 获取字段列表
+     * @internal
+     */
+    public function get_field_list()
+    {
+        $dbname = Config::get('database.database');
+        $prefix = Config::get('database.prefix');
+        $table = $this->request->request('table');
+        //从数据库中获取表字段信息
+        $sql = "SELECT * FROM `information_schema`.`columns` "
+            . "WHERE TABLE_SCHEMA = ? AND table_name = ? "
+            . "ORDER BY ORDINAL_POSITION";
+        //加载主表的列
+        $columnList = Db::query($sql, [$dbname, $table]);
+        $fieldlist = [];
+        foreach ($columnList as $index => $item) {
+            $fieldlist[] = $item['COLUMN_NAME'];
+        }
+        $this->success("", null, ['fieldlist' => $fieldlist]);
+    }
+
+    /**
+     * 获取控制器列表
+     * @internal
+     */
+    public function get_controller_list()
+    {
+        //搜索关键词,客户端输入以空格分开,这里接收为数组
+        $word = (array)$this->request->request("q_word/a");
+        $word = implode('', $word);
+
+        $adminPath = dirname(__DIR__) . DS;
+        $controllerDir = $adminPath . 'controller' . DS;
+        $files = new \RecursiveIteratorIterator(
+            new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
+        );
+        $list = [];
+        foreach ($files as $name => $file) {
+            if (!$file->isDir()) {
+                $filePath = $file->getRealPath();
+                $name = str_replace($controllerDir, '', $filePath);
+                $name = str_replace(DS, "/", $name);
+                if (!preg_match("/(.*)\.php\$/", $name)) {
+                    continue;
+                }
+                if (!$word || stripos($name, $word) !== false) {
+                    $list[] = ['id' => $name, 'name' => $name];
+                }
+            }
+        }
+        $pageNumber = $this->request->request("pageNumber");
+        $pageSize = $this->request->request("pageSize");
+        return json(['list' => array_slice($list, ($pageNumber - 1) * $pageSize, $pageSize), 'total' => count($list)]);
+    }
+
+    /**
+     * 详情
+     */
+    public function detail($ids)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $this->view->assign("row", $row);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 执行
+     */
+    public function execute($ids)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $result = $this->doexecute($row['type'], json_decode($row['params'], true));
+        $this->success("", null, ['result' => $result]);
+    }
+
+    /**
+     * 执行命令
+     */
+    public function command($action = '')
+    {
+        $commandtype = $this->request->request("commandtype");
+        $params = $this->request->request();
+        $allowfields = [
+            'crud' => 'table,controller,model,fields,force,local,delete,menu',
+            'menu' => 'controller,delete',
+            'min'  => 'module,resource,optimize',
+            'api'  => 'url,module,output,template,force,title,author,class,language',
+        ];
+        $argv = [];
+        $allowfields = isset($allowfields[$commandtype]) ? explode(',', $allowfields[$commandtype]) : [];
+        $allowfields = array_filter(array_intersect_key($params, array_flip($allowfields)));
+        if (isset($params['local']) && !$params['local']) {
+            $allowfields['local'] = $params['local'];
+        } else {
+            unset($allowfields['local']);
+        }
+        foreach ($allowfields as $key => $param) {
+            $argv[] = "--{$key}=" . (is_array($param) ? implode(',', $param) : $param);
+        }
+        if ($commandtype == 'crud') {
+            $extend = 'setcheckboxsuffix,enumradiosuffix,imagefield,filefield,intdatesuffix,switchsuffix,citysuffix,selectpagesuffix,selectpagessuffix,ignorefields,sortfield,editorsuffix,headingfilterfield';
+            $extendArr = explode(',', $extend);
+            foreach ($params as $index => $item) {
+                if (in_array($index, $extendArr)) {
+                    foreach (explode(',', $item) as $key => $value) {
+                        if ($value) {
+                            $argv[] = "--{$index}={$value}";
+                        }
+                    }
+                }
+            }
+            $isrelation = (int)$this->request->request('isrelation');
+            if ($isrelation && isset($params['relation'])) {
+                foreach ($params['relation'] as $index => $relation) {
+                    foreach ($relation as $key => $value) {
+                        $argv[] = "--{$key}=" . (is_array($value) ? implode(',', $value) : $value);
+                    }
+                }
+            }
+        } else {
+            if ($commandtype == 'menu') {
+                if (isset($params['allcontroller']) && $params['allcontroller']) {
+                    $argv[] = "--controller=all-controller";
+                } else {
+                    foreach (explode(',', $params['controllerfile']) as $index => $param) {
+                        if ($param) {
+                            $argv[] = "--controller=" . substr($param, 0, -4);
+                        }
+                    }
+                }
+            } else {
+                if ($commandtype == 'min') {
+
+                } else {
+                    if ($commandtype == 'api') {
+
+                    } else {
+
+                    }
+                }
+            }
+        }
+        if ($action == 'execute') {
+            $result = $this->doexecute($commandtype, $argv);
+            $this->success("", null, ['result' => $result]);
+        } else {
+            $this->success("", null, ['command' => "php think {$commandtype} " . implode(' ', $argv)]);
+        }
+
+        return;
+    }
+
+    protected function doexecute($commandtype, $argv)
+    {
+        $commandName = "\\app\\admin\\command\\" . ucfirst($commandtype);
+        $input = new Input($argv);
+        $output = new \addons\command\library\Output();
+        $command = new $commandName($commandtype);
+        $data = [
+            'type'        => $commandtype,
+            'params'      => json_encode($argv),
+            'command'     => "php think {$commandtype} " . implode(' ', $argv),
+            'executetime' => time(),
+        ];
+        $this->model->save($data);
+        try {
+            $command->run($input, $output);
+            $result = implode("\n", $output->getMessage());
+            $this->model->status = 'successed';
+        } catch (Exception $e) {
+            $result = implode("\n", $output->getMessage()) . "\n";
+            $result .= $e->getMessage();
+            $this->model->status = 'failured';
+        }
+        $result = trim($result);
+        $this->model->content = $result;
+        $this->model->save();
+        return $result;
+    }
+
+
+}

+ 16 - 0
addons/command/application/admin/lang/zh-cn/command.php

@@ -0,0 +1,16 @@
+<?php
+
+return [
+    'Id'            => 'ID',
+    'Type'          => '类型',
+    'Params'        => '参数',
+    'Command'       => '命令',
+    'Content'       => '返回结果',
+    'Executetime'   => '执行时间',
+    'Createtime'    => '创建时间',
+    'Updatetime'    => '更新时间',
+    'Execute again' => '再次执行',
+    'Successed'     => '成功',
+    'Failured'      => '失败',
+    'Status'        => '状态'
+];

+ 59 - 0
addons/command/application/admin/model/Command.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Model;
+
+class Command extends Model
+{
+    // 表名
+    protected $name = 'command';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+
+    // 追加属性
+    protected $append = [
+        'executetime_text',
+        'type_text',
+        'status_text'
+    ];
+
+
+    public function getStatusList()
+    {
+        return ['successed' => __('Successed'), 'failured' => __('Failured')];
+    }
+
+
+    public function getExecutetimeTextAttr($value, $data)
+    {
+        $value = $value ? $value : $data['executetime'];
+        return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
+    }
+
+    public function getTypeTextAttr($value, $data)
+    {
+        $value = $value ? $value : $data['type'];
+        $list = ['crud' => '一键生成CRUD', 'menu' => '一键生成菜单', 'min' => '一键压缩打包', 'api' => '一键生成文档'];
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    public function getStatusTextAttr($value, $data)
+    {
+        $value = $value ? $value : $data['status'];
+        $list = $this->getStatusList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    protected function setExecutetimeAttr($value)
+    {
+        return $value && !is_numeric($value) ? strtotime($value) : $value;
+    }
+
+
+}

+ 27 - 0
addons/command/application/admin/validate/Command.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\admin\validate;
+
+use think\Validate;
+
+class Command extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+    ];
+    /**
+     * 提示消息
+     */
+    protected $message = [
+    ];
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => [],
+        'edit' => [],
+    ];
+
+}

+ 400 - 0
addons/command/application/admin/view/command/add.html

@@ -0,0 +1,400 @@
+<style>
+    .relation-item {margin-top:10px;}
+    legend {padding-bottom:5px;font-size:14px;font-weight:600;}
+    label {font-weight:normal;}
+    .form-control{padding:6px 8px;}
+    #extend-zone .col-xs-2 {margin-top:10px;padding-right:0;}
+    #extend-zone .col-xs-2:nth-child(6n+0) {padding-right:15px;}
+</style>
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <ul class="nav nav-tabs">
+            <li class="active"><a href="#crud" data-toggle="tab">{:__('一键生成CRUD')}</a></li>
+            <li><a href="#menu" data-toggle="tab">{:__('一键生成菜单')}</a></li>
+            <li><a href="#min" data-toggle="tab">{:__('一键压缩打包')}</a></li>
+            <li><a href="#api" data-toggle="tab">{:__('一键生成API文档')}</a></li>
+        </ul>
+    </div>
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="crud">
+                <div class="row">
+                    <div class="col-xs-12">
+                        <form role="form">
+                            <input type="hidden" name="commandtype" value="crud" />
+                            <div class="form-group">
+                                <div class="row">
+                                    <div class="col-xs-3">
+                                        <input checked="" name="isrelation" type="hidden" value="0">
+                                        <label class="control-label" data-toggle="tooltip" title="当前只支持生成1对1关联模型,选中后请配置关联表和字段">
+                                            <input name="isrelation" type="checkbox" value="1">
+                                            关联模型
+                                        </label>
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <input checked="" name="local" type="hidden" value="1">
+                                        <label class="control-label" data-toggle="tooltip" title="默认模型生成在application/admin/model目录下,选中后将生成在application/common/model目录下">
+                                            <input name="local" type="checkbox" value="0"> 全局模型类
+                                        </label>
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <input checked="" name="delete" type="hidden" value="0">
+                                        <label class="control-label" data-toggle="tooltip" title="删除CRUD生成的相关文件">
+                                            <input name="delete" type="checkbox" value="1"> 删除模式
+                                        </label>
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <input checked="" name="force" type="hidden" value="0">
+                                        <label class="control-label" data-toggle="tooltip" title="选中后,如果已经存在同名文件将被覆盖。如果是删除将不再提醒">
+                                            <input name="force" type="checkbox" value="1">
+                                            强制覆盖模式
+                                        </label>
+                                    </div>
+                                    <!--
+                                    <div class="col-xs-3">
+                                        <input checked="" name="menu" type="hidden" value="0">
+                                        <label class="control-label" data-toggle="tooltip" title="选中后,将同时生成后台菜单规则">
+                                            <input name="menu" type="checkbox" value="1">
+                                            生成菜单
+                                        </label>
+                                    </div>
+                                    -->
+                                </div>
+                            </div>
+                            <div class="form-group">
+                                <legend>主表设置</legend>
+                                <div class="row">
+                                    <div class="col-xs-3">
+                                        <label>请选择主表</label>
+                                        {:build_select('table',$tableList,null,['class'=>'form-control selectpicker', 'data-live-search'=>'true']);}
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>自定义控制器名</label>
+                                        <input type="text" class="form-control" name="controller" data-toggle="tooltip" title="默认根据表名自动生成,如果需要放在二级目录请手动填写" placeholder="支持目录层级,以/分隔">
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>自定义模型名</label>
+                                        <input type="text" class="form-control" name="model" data-toggle="tooltip" title="默认根据表名自动生成" placeholder="不支持目录层级">
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>请选择显示字段(默认全部)</label>
+                                        <select name="fields[]" id="fields" multiple style="height:30px;" class="form-control selectpicker"></select>
+                                    </div>
+
+                                </div>
+
+                            </div>
+
+                            <div class="form-group hide" id="relation-zone">
+                                <legend>关联表设置</legend>
+
+                                <div class="row" style="margin-top:15px;">
+                                    <div class="col-xs-12">
+                                        <a href="javascript:;" class="btn btn-primary btn-sm btn-newrelation" data-index="1">追加关联模型</a>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <hr>
+                            <div class="form-group" id="extend-zone">
+                                <legend>字段识别设置 <span style="font-size:12px;font-weight: normal;">(与之匹配的字段都将生成相应组件)</span></legend>
+                                <div class="row">
+                                    <div class="col-xs-2">
+                                        <label>复选框后缀</label>
+                                        <input type="text" class="form-control" name="setcheckboxsuffix" placeholder="默认为set类型" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>单选框后缀</label>
+                                        <input type="text" class="form-control" name="enumradiosuffix" placeholder="默认为enum类型" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>图片类型后缀</label>
+                                        <input type="text" class="form-control" name="imagefield" placeholder="默认为image,images,avatar,avatars" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>文件类型后缀</label>
+                                        <input type="text" class="form-control" name="filefield" placeholder="默认为file,files" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>日期时间后缀</label>
+                                        <input type="text" class="form-control" name="intdatesuffix" placeholder="默认为time" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>开关后缀</label>
+                                        <input type="text" class="form-control" name="switchsuffix" placeholder="默认为switch" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>城市选择后缀</label>
+                                        <input type="text" class="form-control" name="citysuffix" placeholder="默认为city" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>动态下拉后缀(单)</label>
+                                        <input type="text" class="form-control" name="selectpagesuffix" placeholder="默认为_id" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>动态下拉后缀(多)</label>
+                                        <input type="text" class="form-control" name="selectpagessuffix" placeholder="默认为_ids" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>忽略的字段</label>
+                                        <input type="text" class="form-control" name="ignorefields" placeholder="默认无" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>排序字段</label>
+                                        <input type="text" class="form-control" name="sortfield" placeholder="默认为weigh" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>富文本编辑器</label>
+                                        <input type="text" class="form-control" name="editorsuffix" placeholder="默认为content" />
+                                    </div>
+                                    <div class="col-xs-2">
+                                        <label>选项卡过滤字段</label>
+                                        <input type="text" class="form-control" name="headingfilterfield" placeholder="默认为status" />
+                                    </div>
+
+                                </div>
+
+                            </div>
+
+                            <div class="form-group">
+                                <legend>生成命令行</legend>
+                                <textarea class="form-control" data-toggle="tooltip" title="如果在线执行命令失败,可以将命令复制到命令行进行执行" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
+                            </div>
+
+                            <div class="form-group">
+                                <legend>返回结果</legend>
+                                <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
+                            </div>
+
+                            <div class="form-group">
+                                    <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
+                                    <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
+                            </div>
+
+                        </form>
+                    </div>
+                </div>
+            </div>
+            <div class="tab-pane fade" id="menu">
+                <div class="row">
+                    <div class="col-xs-12">
+                        <form role="form">
+                            <input type="hidden" name="commandtype" value="menu" />
+                            <div class="form-group">
+                                <div class="row">
+                                    <div class="col-xs-3">
+                                        <input checked="" name="allcontroller" type="hidden" value="0">
+                                        <label class="control-label">
+                                            <input name="allcontroller" data-toggle="collapse" data-target="#controller" type="checkbox" value="1"> 一键生成全部控制器
+                                        </label>
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <input checked="" name="delete" type="hidden" value="0">
+                                        <label class="control-label">
+                                            <input name="delete" type="checkbox" value="1"> 删除模式
+                                        </label>
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <input checked="" name="force" type="hidden" value="0">
+                                        <label class="control-label">
+                                            <input name="force" type="checkbox" value="1"> 强制覆盖模式
+                                        </label>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="form-group in" id="controller">
+                                <legend>控制器设置</legend>
+
+                                <div class="row" style="margin-top:15px;">
+                                    <div class="col-xs-12">
+                                        <input type="text" name="controllerfile" class="form-control selectpage" style="width:720px;" data-source="command/get_controller_list" data-multiple="true" name="controller" placeholder="请选择控制器" />
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="form-group">
+                                <legend>生成命令行</legend>
+                                <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
+                            </div>
+
+                            <div class="form-group">
+                                <legend>返回结果</legend>
+                                <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
+                            </div>
+
+                            <div class="form-group">
+                                <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
+                                <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
+                            </div>
+
+                        </form>
+                    </div>
+                </div>
+            </div>
+            <div class="tab-pane fade" id="min">
+                <div class="row">
+                    <div class="col-xs-12">
+                        <form role="form">
+                            <input type="hidden" name="commandtype" value="min" />
+                            <div class="form-group">
+                                <legend>基础设置</legend>
+                                <div class="row">
+                                    <div class="col-xs-3">
+                                        <label>请选择压缩模块</label>
+                                        <select name="module" class="form-control selectpicker">
+                                            <option value="all" selected>全部</option>
+                                            <option value="backend">后台Backend</option>
+                                            <option value="frontend">前台Frontend</option>
+                                        </select>
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>请选择压缩资源</label>
+                                        <select name="resource" class="form-control selectpicker">
+                                            <option value="all" selected>全部</option>
+                                            <option value="js">JS</option>
+                                            <option value="css">CSS</option>
+                                        </select>
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>请选择压缩模式</label>
+                                        <select name="optimize" class="form-control selectpicker">
+                                            <option value="">无</option>
+                                            <option value="uglify">uglify</option>
+                                            <option value="closure">closure</option>
+                                        </select>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="form-group in">
+                                <legend>控制器设置</legend>
+
+                                <div class="row" style="margin-top:15px;">
+                                    <div class="col-xs-12">
+
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="form-group">
+                                <legend>生成命令行</legend>
+                                <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
+                            </div>
+
+                            <div class="form-group">
+                                <legend>返回结果</legend>
+                                <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
+                            </div>
+
+                            <div class="form-group">
+                                <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
+                                <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
+                            </div>
+
+                        </form>
+                    </div>
+                </div>
+            </div>
+            <div class="tab-pane fade" id="api">
+                <div class="row">
+                    <div class="col-xs-12">
+                        <form role="form">
+                            <input type="hidden" name="commandtype" value="api" />
+                            <div class="form-group">
+                                <div class="row">
+                                    <div class="col-xs-3">
+                                        <input checked="" name="force" type="hidden" value="0">
+                                        <label class="control-label">
+                                            <input name="force" type="checkbox" value="1">
+                                            覆盖模式
+                                        </label>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="form-group">
+                                <legend>文档设置</legend>
+                                <div class="row">
+                                    <div class="col-xs-3">
+                                        <label>请输入接口URL</label>
+                                        <input type="text" name="url" class="form-control" placeholder="API URL,可留空" />
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>接口生成文件</label>
+                                        <input type="text" name="output" class="form-control" placeholder="留空则使用api.html" />
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>模板文件</label>
+                                        <input type="text" name="template" class="form-control" placeholder="如果不清楚请留空" />
+                                    </div>
+                                </div>
+                                <div class="row" style="margin-top:10px;">
+                                    <div class="col-xs-3">
+                                        <label>文档标题</label>
+                                        <input type="text" name="title" class="form-control" placeholder="默认为FastAdmin" />
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>文档作者</label>
+                                        <input type="text" name="author" class="form-control" placeholder="默认为FastAdmin" />
+                                    </div>
+                                    <div class="col-xs-3">
+                                        <label>文档语言</label>
+                                        <select name="language" class="form-control">
+                                            <option value="" selected>请选择语言</option>
+                                            <option value="zh-cn">中文</option>
+                                            <option value="en">英文</option>
+                                        </select>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="form-group">
+                                <legend>生成命令行</legend>
+                                <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
+                            </div>
+
+                            <div class="form-group">
+                                <legend>返回结果</legend>
+                                <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
+                            </div>
+
+                            <div class="form-group">
+                                <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
+                                <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
+                            </div>
+
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script id="relationtpl" type="text/html">
+    <div class="row relation-item">
+        <div class="col-xs-2">
+            <label>请选择关联表</label>
+            <select name="relation[<%=index%>][relation]" class="form-control relationtable" data-live-search="true"></select>
+        </div>
+        <div class="col-xs-2">
+            <label>请选择关联类型</label>
+            <select name="relation[<%=index%>][relationmode]" class="form-control relationmode"></select>
+        </div>
+        <div class="col-xs-2">
+            <label>关联外键</label>
+            <select name="relation[<%=index%>][relationforeignkey]" class="form-control relationforeignkey"></select>
+        </div>
+        <div class="col-xs-2">
+            <label>关联主键</label>
+            <select name="relation[<%=index%>][relationprimarykey]" class="form-control relationprimarykey"></select>
+        </div>
+        <div class="col-xs-2">
+            <label>请选择显示字段</label>
+            <select name="relation[<%=index%>][relationfields][]" multiple class="form-control relationfields"></select>
+        </div>
+        <div class="col-xs-2">
+            <label>&nbsp;</label>
+            <a href="javascript:;" class="btn btn-danger btn-block btn-removerelation">移除</a>
+        </div>
+    </div>
+</script>

+ 42 - 0
addons/command/application/admin/view/command/detail.html

@@ -0,0 +1,42 @@
+<table class="table table-striped">
+    <thead>
+    <tr>
+        <th>{:__('Title')}</th>
+        <th>{:__('Content')}</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr>
+        <td>{:__('Type')}</td>
+        <td>{$row.type}({$row.type_text})</td>
+    </tr>
+    <tr>
+        <td>{:__('Params')}</td>
+        <td>{$row.params}</td>
+    </tr>
+    <tr>
+        <td>{:__('Command')}</td>
+        <td>{$row.command}</td>
+    </tr>
+    <tr>
+        <td>{:__('Content')}</td>
+        <td>
+            <textarea class="form-control" name="" id="" cols="60" rows="10">{$row.content}</textarea>
+        </td>
+    </tr>
+    <tr>
+        <td>{:__('Executetime')}</td>
+        <td>{$row.executetime|datetime}</td>
+    </tr>
+    <tr>
+        <td>{:__('Status')}</td>
+        <td>{$row.status_text}</td>
+    </tr>
+    </tbody>
+</table>
+<div class="hide layer-footer">
+    <label class="control-label col-xs-12 col-sm-2"></label>
+    <div class="col-xs-12 col-sm-8">
+        <button type="reset" class="btn btn-primary btn-embossed btn-close" onclick="Layer.closeAll();">{:__('Close')}</button>
+    </div>
+</div>

+ 25 - 0
addons/command/application/admin/view/command/index.html

@@ -0,0 +1,25 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                        <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('command/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                        <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>
+
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover" 
+                           data-operate-detail="{:$auth->check('command/detail')}"
+                           data-operate-execute="{:$auth->check('command/execute')}"
+                           data-operate-del="{:$auth->check('command/del')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 4 - 0
addons/command/config.php

@@ -0,0 +1,4 @@
+<?php
+
+return [
+];

+ 15 - 0
addons/command/controller/Index.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace addons\command\controller;
+
+use think\addons\Controller;
+
+class Index extends Controller
+{
+
+    public function index()
+    {
+        $this->error("当前插件暂无前台页面");
+    }
+
+}

+ 8 - 0
addons/command/info.ini

@@ -0,0 +1,8 @@
+name = command
+title = 在线命令
+intro = 可在线执行FastAdmin的命令行相关命令
+author = Karson
+website = https://www.fastadmin.net
+version = 1.0.6
+state = 1
+url = /addons/command

+ 12 - 0
addons/command/install.sql

@@ -0,0 +1,12 @@
+CREATE TABLE IF NOT EXISTS `__PREFIX__command`  (
+  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '类型',
+  `params` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '参数',
+  `command` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '命令',
+  `content` text COMMENT '返回结果',
+  `executetime` int(10) UNSIGNED DEFAULT NULL COMMENT '执行时间',
+  `createtime` int(10) UNSIGNED DEFAULT NULL COMMENT '创建时间',
+  `updatetime` int(10) UNSIGNED DEFAULT NULL COMMENT '更新时间',
+  `status` enum('successed','failured') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'failured' COMMENT '状态',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '在线命令表';

+ 28 - 0
addons/command/library/Output.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace addons\command\library;
+
+/**
+ * Class Output
+ */
+class Output extends \think\console\Output
+{
+
+    protected $message = [];
+
+    public function __construct($driver = 'console')
+    {
+        parent::__construct($driver);
+    }
+
+    protected function block($style, $message)
+    {
+        $this->message[] = $message;
+    }
+
+    public function getMessage()
+    {
+        return $this->message;
+    }
+
+}

+ 234 - 0
addons/command/public/assets/js/backend/command.js

@@ -0,0 +1,234 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function ($, undefined, Backend, Table, Form, Template) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    index_url: 'command/index',
+                    add_url: 'command/add',
+                    edit_url: '',
+                    del_url: 'command/del',
+                    multi_url: 'command/multi',
+                    table: 'command',
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {field: 'type', title: __('Type'), formatter: Table.api.formatter.search},
+                        {field: 'type_text', title: __('Type')},
+                        {
+                            field: 'command', title: __('Command'), formatter: function (value, row, index) {
+                                return '<input type="text" class="form-control" value="' + value + '">';
+                            }
+                        },
+                        {
+                            field: 'executetime',
+                            title: __('Executetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'createtime',
+                            title: __('Createtime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'updatetime',
+                            title: __('Updatetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'status',
+                            title: __('Status'),
+                            table: table,
+                            custom: {"successed": 'success', "failured": 'danger'},
+                            searchList: {"successed": __('Successed'), "failured": __('Failured')},
+                            formatter: Table.api.formatter.status
+                        },
+                        {
+                            field: 'operate',
+                            title: __('Operate'),
+                            buttons: [
+                                {
+                                    name: 'execute',
+                                    title: __('Execute again'),
+                                    text: __('Execute again'),
+                                    url: 'command/execute',
+                                    icon: 'fa fa-repeat',
+                                    classname: 'btn btn-success btn-xs btn-execute btn-ajax',
+                                    success: function (data) {
+                                        Layer.alert("<textarea class='form-control' cols='60' rows='5'>" + data.result + "</textarea>", {
+                                            title: __("执行结果"),
+                                            shadeClose: true
+                                        });
+                                        table.bootstrapTable('refresh');
+                                        return false;
+                                    }
+                                },
+                                {
+                                    name: 'execute',
+                                    title: __('Detail'),
+                                    text: __('Detail'),
+                                    url: 'command/detail',
+                                    icon: 'fa fa-list',
+                                    classname: 'btn btn-info btn-xs btn-execute btn-dialog'
+                                }
+                            ],
+                            table: table,
+                            events: Table.api.events.operate,
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        add: function () {
+            require(['bootstrap-select', 'bootstrap-select-lang']);
+            var mainfields = [];
+            var relationfields = {};
+            var maintable = [];
+            var relationtable = [];
+            var relationmode = ["belongsto", "hasone"];
+
+            var renderselect = function (select, data) {
+                var html = [];
+                for (var i = 0; i < data.length; i++) {
+                    html.push("<option value='" + data[i] + "'>" + data[i] + "</option>");
+                }
+                $(select).html(html.join(""));
+                select.trigger("change");
+                if (select.data("selectpicker")) {
+                    select.selectpicker('refresh');
+                }
+                return select;
+            };
+
+            $("select[name=table] option").each(function () {
+                maintable.push($(this).val());
+            });
+            $(document).on('change', "input[name='isrelation']", function () {
+                $("#relation-zone").toggleClass("hide", !$(this).prop("checked"));
+            });
+            $(document).on('change', "select[name='table']", function () {
+                var that = this;
+                Fast.api.ajax({
+                    url: "command/get_field_list",
+                    data: {table: $(that).val()},
+                }, function (data, ret) {
+                    mainfields = data.fieldlist;
+                    $("#relation-zone .relation-item").remove();
+                    renderselect($("#fields"), mainfields);
+                    return false;
+                });
+                return false;
+            });
+            $(document).on('click', "a.btn-newrelation", function () {
+                var that = this;
+                var index = parseInt($(that).data("index")) + 1;
+                var content = Template("relationtpl", {index: index});
+                content = $(content.replace(/\[index\]/, index));
+                $(this).data("index", index);
+                $(content).insertBefore($(that).closest(".row"));
+                $('select', content).selectpicker();
+                var exists = [$("select[name='table']").val()];
+                $("select.relationtable").each(function () {
+                    exists.push($(this).val());
+                });
+                relationtable = [];
+                $.each(maintable, function (i, j) {
+                    if ($.inArray(j, exists) < 0) {
+                        relationtable.push(j);
+                    }
+                });
+                renderselect($("select.relationtable", content), relationtable);
+                $("select.relationtable", content).trigger("change");
+            });
+            $(document).on('click', "a.btn-removerelation", function () {
+                $(this).closest(".row").remove();
+            });
+            $(document).on('change', "#relation-zone select.relationmode", function () {
+                var table = $("select.relationtable", $(this).closest(".row")).val();
+                var that = this;
+                Fast.api.ajax({
+                    url: "command/get_field_list",
+                    data: {table: table},
+                }, function (data, ret) {
+                    renderselect($(that).closest(".row").find("select.relationprimarykey"), $(that).val() == 'belongsto' ? data.fieldlist : mainfields);
+                    renderselect($(that).closest(".row").find("select.relationforeignkey"), $(that).val() == 'hasone' ? data.fieldlist : mainfields);
+                    return false;
+                });
+            });
+            $(document).on('change', "#relation-zone select.relationtable", function () {
+                var that = this;
+                Fast.api.ajax({
+                    url: "command/get_field_list",
+                    data: {table: $(that).val()},
+                }, function (data, ret) {
+                    renderselect($(that).closest(".row").find("select.relationmode"), relationmode);
+                    renderselect($(that).closest(".row").find("select.relationfields"), mainfields)
+                    renderselect($(that).closest(".row").find("select.relationforeignkey"), data.fieldlist)
+                    renderselect($(that).closest(".row").find("select.relationfields"), data.fieldlist)
+                    return false;
+                });
+            });
+            $(document).on('click', ".btn-command", function () {
+                var form = $(this).closest("form");
+                var textarea = $("textarea[rel=command]", form);
+                textarea.val('');
+                Fast.api.ajax({
+                    url: "command/command/action/command",
+                    data: form.serialize(),
+                }, function (data, ret) {
+                    textarea.val(data.command);
+                    return false;
+                });
+            });
+            $(document).on('click', ".btn-execute", function () {
+                var form = $(this).closest("form");
+                var textarea = $("textarea[rel=result]", form);
+                textarea.val('');
+                Fast.api.ajax({
+                    url: "command/command/action/execute",
+                    data: form.serialize(),
+                }, function (data, ret) {
+                    textarea.val(data.result);
+                    window.parent.$(".toolbar .btn-refresh").trigger('click');
+                    top.window.Fast.api.refreshmenu();
+                    return false;
+                }, function () {
+                    window.parent.$(".toolbar .btn-refresh").trigger('click');
+                });
+            });
+            $("select[name='table']").trigger("change");
+            Controller.api.bindevent();
+        },
+        edit: function () {
+            Controller.api.bindevent();
+        },
+        api: {
+            bindevent: function () {
+                Form.api.bindevent($("form[role=form]"));
+            }
+        }
+    };
+    return Controller;
+});

+ 65 - 0
addons/epay/Epay.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace addons\epay;
+
+use app\common\library\Menu;
+use think\Addons;
+use think\Config;
+use think\Loader;
+
+/**
+ * 微信支付宝整合插件
+ */
+class Epay extends Addons
+{
+
+    /**
+     * 插件安装方法
+     * @return bool
+     */
+    public function install()
+    {
+
+        return true;
+    }
+
+    /**
+     * 插件卸载方法
+     * @return bool
+     */
+    public function uninstall()
+    {
+
+        return true;
+    }
+
+    /**
+     * 插件启用方法
+     * @return bool
+     */
+    public function enable()
+    {
+
+        return true;
+    }
+
+    /**
+     * 插件禁用方法
+     * @return bool
+     */
+    public function disable()
+    {
+
+        return true;
+    }
+
+    /**
+     * 添加命名空间
+     */
+    public function appInit()
+    {
+        //添加支付包的命名空间
+        Loader::addNamespace('Yansongda', ADDON_PATH . 'epay' . DS . 'library' . DS . 'Yansongda' . DS);
+    }
+
+}

+ 91 - 0
addons/epay/assets/css/common.css

@@ -0,0 +1,91 @@
+/*!
+ * Start Bootstrap - Modern Business (http://startbootstrap.com/)
+ * Copyright 2013-2016 Start Bootstrap
+ * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE)
+ */
+/* Global Styles */
+html,
+body {
+  height: 100%;
+}
+body {
+  padding-top: 50px;
+  /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+.img-addon {
+  margin-bottom: 10px;
+  width: 100%;
+}
+.img-hover:hover {
+  opacity: 0.8;
+}
+.display-1 {
+  font-size: 44px;
+}
+.display-4 {
+  font-size: 24px;
+  line-height: 32px;
+}
+/* Home Page Carousel */
+header.carousel {
+  height: 50%;
+}
+header.carousel .item,
+header.carousel .item.active,
+header.carousel .carousel-inner {
+  height: 100%;
+}
+header.carousel .fill {
+  width: 100%;
+  height: 100%;
+}
+.error-404 {
+  font-size: 100px;
+}
+/* Pricing Page Styles */
+.price {
+  display: block;
+  font-size: 50px;
+  line-height: 50px;
+}
+.price sup {
+  top: -20px;
+  left: 2px;
+  font-size: 20px;
+}
+.period {
+  display: block;
+  font-style: italic;
+}
+/* Footer Styles */
+footer {
+  margin: 50px 0;
+}
+/* Responsive Styles */
+@media (max-width: 991px) {
+  .customer-img,
+  .img-related {
+    margin-bottom: 30px;
+  }
+}
+@media (max-width: 767px) {
+  .img-addon {
+    margin-bottom: 15px;
+  }
+  header.carousel .carousel {
+    height: 70%;
+  }
+}
+.carousel-body {
+  position: absolute;
+  width: 100%;
+  top: 25%;
+  text-align: center;
+  color: #fff;
+}
+.addonlist a > p {
+  margin-bottom: 15px;
+}

+ 20 - 0
addons/epay/assets/css/epay.css

@@ -0,0 +1,20 @@
+@import url("../../../css/bootstrap.min.css");
+@import url("../../../libs/font-awesome/css/font-awesome.min.css");
+html,
+body {
+  height: 100%;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  font-weight: 400;
+  overflow-x: hidden;
+  overflow-y: auto;
+  background: #f4f6f8;
+  font-size: 14px;
+  color: #616161;
+}
+.container {
+  max-width: 850px;
+  margin: 0 auto;
+  padding: 50px;
+}

+ 100 - 0
addons/epay/assets/css/wechat.css

@@ -0,0 +1,100 @@
+.wechat {
+    margin-top: 30px;
+}
+
+.wechat h2 {
+    margin: 0 0 15px 0;
+    padding-bottom: 15px;
+    border-bottom: 1px solid #eee;
+    position: relative;
+}
+
+.wechat-body {
+}
+
+.wechat-qrcode {
+    margin-bottom: 20px;
+    position: relative;
+}
+
+.wechat-qrcode img {
+    width: 100%;
+    border: 1px solid #eee;
+}
+
+.wechat-qrcode .expired {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 100%;
+    opacity: .95;
+    background: #fff url(../images/expired.png) center center no-repeat;
+}
+
+.wechat-qrcode .paid {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 100%;
+    opacity: .95;
+    background: #fff url(../images/paid.png) center center no-repeat;
+}
+
+.wechat-scan {
+    padding: 0;
+}
+
+.wechat-scan img {
+    width: 100%;
+}
+
+.wechat-tips {
+    height: 60px;
+    padding: 8px 0 8px 125px;
+    background: #00c800 url(../images/scan.png) 50px 12px no-repeat;
+    background-size: 36px 36px;
+}
+
+.wechat-tips p {
+    margin: 0;
+    font-size: 14px;
+    line-height: 22px;
+    color: #fff;
+    font-weight: 700
+}
+
+.wechat-time {
+    font-size: 14px;
+    margin-bottom: 15px;
+    position: absolute;
+    top: 15px;
+    right: 10px;
+    font-weight: normal;
+    display: none;
+}
+
+.wechat-time span {
+    color: red;
+}
+
+.wechat-order {
+    margin-bottom: 5px;
+}
+
+.wechat-order em {
+    font-style: normal;
+    color: #666;
+}
+
+.wechat-order em.wechat-price {
+    color: #ff3333;
+    font-weight: bold;
+}
+
+@media (max-width: 767px) {
+    .wechat {
+        margin-top: 20px;
+    }
+}

BIN
addons/epay/assets/images/alipay.png


BIN
addons/epay/assets/images/expired.png


BIN
addons/epay/assets/images/logo-alipay.png


BIN
addons/epay/assets/images/logo-wechat.png


BIN
addons/epay/assets/images/logo.png


BIN
addons/epay/assets/images/paid.png


BIN
addons/epay/assets/images/scan.png


BIN
addons/epay/assets/images/tips.png


BIN
addons/epay/assets/images/wechat.png


+ 52 - 0
addons/epay/assets/js/common.js

@@ -0,0 +1,52 @@
+$(function () {
+    $('.carousel').carousel({
+        interval: 5000 //changes the speed
+    });
+    $(".btn-experience").on("click", function () {
+        location.href = "/addons/epay/index/experience?amount=" + $("input[name=amount]").val() + "&type=" + $(this).data("type") + "&method=" + $("#method").val();
+    });
+
+    var si, xhr;
+    if (typeof queryParams != 'undefined') {
+        var queryResult = function () {
+            xhr && xhr.abort();
+            xhr = $.ajax({
+                url: "",
+                type: "post",
+                data: queryParams,
+                dataType: 'json',
+                success: function (ret) {
+                    if (ret.code == 1) {
+                        var data = ret.data;
+                        console.log(data);
+                        if (typeof data.trade_state != 'undefined') {
+                            if (data.trade_state == 'SUCCESS') {
+                                $(".wechat-qrcode .paid").removeClass("hidden");
+                                $(".wechat-tips p").html("支付成功!<br>3秒后将自动跳转...");
+                                setTimeout(function () {
+                                    location.href = queryParams.return_url;
+                                }, 3000);
+                                clearInterval(si);
+                            } else if (data.trade_state == 'REFUND') {
+                                $(".wechat-tips p").html("请求失败!<br>请返回重新发起支付");
+                                clearInterval(si);
+                            } else if (data.trade_state == 'NOTPAY') {
+                            } else if (data.trade_state == 'CLOSED') {
+                                $(".wechat-tips p").html("订单已关闭!<br>请返回重新发起支付");
+                                clearInterval(si);
+                            } else if (data.trade_state == 'USERPAYING') {
+                            } else if (data.trade_state == 'PAYERROR') {
+                                clearInterval(si);
+                            }
+                        }
+                    }
+                }
+            });
+        };
+        si = setInterval(function () {
+            queryResult();
+        }, 3000);
+        queryResult();
+    }
+
+});

+ 113 - 0
addons/epay/assets/less/common.less

@@ -0,0 +1,113 @@
+/*!
+ * Start Bootstrap - Modern Business (http://startbootstrap.com/)
+ * Copyright 2013-2016 Start Bootstrap
+ * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE)
+ */
+
+/* Global Styles */
+
+html,
+body {
+  height: 100%;
+}
+
+body {
+  padding-top: 50px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+
+}
+
+.img-addon {
+  margin-bottom: 10px;
+  width:100%;
+}
+
+.img-hover:hover {
+  opacity: 0.8;
+}
+
+.display-1 {
+  font-size:44px;
+}
+.display-4 {
+  font-size:24px;
+  line-height:32px;
+}
+
+/* Home Page Carousel */
+
+header.carousel {
+  height: 50%;
+}
+
+header.carousel .item,
+header.carousel .item.active,
+header.carousel .carousel-inner {
+  height: 100%;
+}
+
+header.carousel .fill {
+  width: 100%;
+  height: 100%;
+}
+
+.error-404 {
+  font-size: 100px;
+}
+
+/* Pricing Page Styles */
+
+.price {
+  display: block;
+  font-size: 50px;
+  line-height: 50px;
+}
+
+.price sup {
+  top: -20px;
+  left: 2px;
+  font-size: 20px;
+}
+
+.period {
+  display: block;
+  font-style: italic;
+}
+
+/* Footer Styles */
+
+footer {
+  margin: 50px 0;
+}
+
+/* Responsive Styles */
+
+@media(max-width:991px) {
+  .customer-img,
+  .img-related {
+    margin-bottom: 30px;
+  }
+}
+
+@media(max-width:767px) {
+  .img-addon {
+    margin-bottom: 15px;
+  }
+
+  header.carousel .carousel {
+    height: 70%;
+  }
+}
+.carousel-body {
+  position:absolute;
+  width: 100%;
+  top:25%;
+  text-align:center;
+  color:#fff;
+}
+
+.addonlist a > p{
+  margin-bottom:15px;
+}

+ 28 - 0
addons/epay/assets/less/epay.less

@@ -0,0 +1,28 @@
+@import (reference) "../../../../public/assets/less/bootstrap-less/mixins.less";
+@import (reference) "../../../../public/assets/less/bootstrap-less/variables.less";
+@import (reference) "../../../../public/assets/less/fastadmin/mixins.less";
+@import (reference) "../../../../public/assets/less/fastadmin/variables.less";
+@import "../../../../public/assets/less/lesshat.less";
+@import url("../../../css/bootstrap.min.css");
+@import url("../../../libs/font-awesome/css/font-awesome.min.css");
+
+html,
+body {
+  height: 100%;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  font-weight: 400;
+  overflow-x: hidden;
+  overflow-y: auto;
+  background: #f4f6f8;
+  font-size: 14px;
+  color: #616161;
+
+}
+
+.container {
+  max-width: 850px;
+  margin: 0 auto;
+  padding:50px;
+}

+ 0 - 0
addons/epay/certs/apiclient_cert.pem


+ 0 - 0
addons/epay/certs/apiclient_key.pem


+ 64 - 0
addons/epay/config.php

@@ -0,0 +1,64 @@
+<?php
+
+return array(
+    array(
+        'name'    => 'wechat',
+        'title'   => '微信',
+        'type'    => 'array',
+        'content' =>
+            array(),
+        'value'   => [
+            'appid'       => '', // APP APPID
+            'app_id'      => '', // 公众号 APPID
+            'app_secret'  => '', // 公众号 APPSECRET
+            'miniapp_id'  => '', // 小程序 APPID
+            'mch_id'      => '', //支付商户ID
+            'key'         => '',
+            'notify_url'  => '/addons/epay/api/notifyx/type/wechat', //请勿修改此配置
+            'cert_client' => '/epay/certs/apiclient_cert.pem', // 可选, 退款,红包等情况时需要用到
+            'cert_key'    => '/epay/certs/apiclient_key.pem',// 可选, 退款,红包等情况时需要用到
+            'log'         => 1,
+//            'mode'        => 'dev', // optional,设置此参数,将进入沙箱模式
+        ],
+        'rule'    => '',
+        'msg'     => '',
+        'tip'     => '微信参数配置',
+        'ok'      => '',
+        'extend'  => '',
+    ),
+    array(
+        'name'    => 'alipay',
+        'title'   => '支付宝',
+        'type'    => 'array',
+        'content' =>
+            array(),
+        'value'   => [
+            'app_id'         => '',
+            'notify_url'     => '/addons/epay/api/notifyx/type/alipay', //请勿修改此配置
+            'return_url'     => '/addons/epay/api/returnx/type/alipay', //请勿修改此配置
+            'ali_public_key' => '',
+            'private_key'    => '',
+            'log'            => 1,
+            //'mode'           => 'dev', // optional,设置此参数,将进入沙箱模式
+        ],
+        'rule'    => 'required',
+        'msg'     => '',
+        'tip'     => '支付宝参数配置',
+        'ok'      => '',
+        'extend'  => '',
+    ),
+    array(
+
+        'name'    => '__tips__',
+        'title'   => '温馨提示',
+        'type'    => 'array',
+        'content' =>
+            array(),
+        'value'   => '请注意微信支付证书路径位于/addons/epay/certs目录下,请替换成你自己的证书<br>appid:APP的appid<br>app_id:公众号的appid<br>app_secret:公众号的secret<br>miniapp_id:小程序ID<br>mch_id:微信商户ID<br>key:微信商户支付的密钥',
+        'rule'    => '',
+        'msg'     => '',
+        'tip'     => '微信参数配置',
+        'ok'      => '',
+        'extend'  => '',
+    )
+);

+ 229 - 0
addons/epay/controller/Api.php

@@ -0,0 +1,229 @@
+<?php
+
+namespace addons\epay\controller;
+
+use addons\epay\library\Service;
+use addons\epay\library\Wechat;
+use Endroid\QrCode\QrCode;
+use think\addons\Controller;
+use think\Response;
+use think\Session;
+use Yansongda\Pay\Pay;
+
+/**
+ * API接口控制器
+ *
+ * @package addons\epay\controller
+ */
+class Api extends Controller
+{
+
+    protected $layout = 'default';
+    protected $config = [];
+
+    /**
+     * 默认方法
+     */
+    public function index()
+    {
+        $this->error();
+    }
+
+    /**
+     * 外部提交
+     */
+    public function submit()
+    {
+        $out_trade_no = $this->request->request("out_trade_no");
+        $title = $this->request->request("title");
+        $amount = $this->request->request('amount');
+        $type = $this->request->request('type');
+        $method = $this->request->request('method', 'web');
+        $openid = $this->request->request('openid', '');
+        $auth_code = $this->request->request('auth_code', '');
+        $notifyurl = $this->request->request('notifyurl', '');
+        $returnurl = $this->request->request('returnurl', '');
+
+        if (!$amount || $amount < 0) {
+            $this->error("支付金额必须大于0");
+        }
+
+        if (!$type || !in_array($type, ['alipay', 'wechat'])) {
+            $this->error("支付类型错误");
+        }
+
+        $params = [
+            'type'         => $type,
+            'out_trade_no' => $out_trade_no,
+            'title'        => $title,
+            'amount'       => $amount,
+            'method'       => $method,
+            'openid'       => $openid,
+            'auth_code'    => $auth_code,
+            'notifyurl'    => $notifyurl,
+            'returnurl'    => $returnurl,
+        ];
+        return Service::submitOrder($params);
+    }
+
+    /**
+     * 微信支付
+     * @return string
+     */
+    public function wechat()
+    {
+        $config = Service::getConfig('wechat');
+
+        $isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
+        $isMobile = $this->request->isMobile();
+        $this->view->assign("isWechat", $isWechat);
+        $this->view->assign("isMobile", $isMobile);
+
+        if ($isWechat) {
+            //发起公众号(jsapi支付)
+            $orderData = Session::get("wechatorderdata");
+            $openid = Session::get('openid');
+            //如果没有openid
+            if (!$openid) {
+                $wechat = new Wechat($config['wechat']['app_id'], $config['wechat']['app_secret']);
+                $openid = $wechat->getOpenid();
+            }
+
+            $orderData['method'] = 'mp';
+            $orderData['openid'] = $openid;
+            $payData = Service::submitOrder($orderData);
+            $payData = json_decode($payData, true);
+            if (!isset($payData['appId'])) {
+                $this->error("创建订单失败,请返回重试");
+            }
+            $type = 'jsapi';
+            $this->view->assign("orderData", $orderData);
+            $this->view->assign("payData", $payData);
+        } else {
+            //发起PC支付(Native支付)
+            $body = $this->request->request("body");
+            $code_url = $this->request->request("code_url");
+            $out_trade_no = $this->request->request("out_trade_no");
+            $return_url = $this->request->request("return_url");
+            $total_fee = $this->request->request("total_fee");
+
+            $sign = $this->request->request("sign");
+
+            $data = [
+                'body'         => $body,
+                'code_url'     => $code_url,
+                'out_trade_no' => $out_trade_no,
+                'return_url'   => $return_url,
+                'total_fee'    => $total_fee,
+            ];
+            if ($sign != md5(implode('', $data) . $config['wechat']['appid'])) {
+                $this->error("签名不正确");
+            }
+
+            if ($this->request->isAjax()) {
+                $pay = new Pay($config);
+                $result = $pay->driver('wechat')->gateway('scan')->find($out_trade_no);
+                if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
+                    $this->success("", "", ['trade_state' => $result['trade_state']]);
+                } else {
+                    $this->error("查询失败");
+                }
+            }
+            $data['sign'] = $sign;
+            $type = 'pc';
+            $this->view->assign("data", $data);
+        }
+
+        $this->view->assign("type", $type);
+        $this->view->assign("title", "微信支付");
+        return $this->view->fetch();
+    }
+
+    /**
+     * 支付成功回调
+     */
+    public function notifyx()
+    {
+        $type = $this->request->param('type');
+        $data = $this->request->request('', null, 'trim');
+        $config = Service::getConfig($type);
+        $pay = new Pay($config);
+        if (!$pay->driver($type)->gateway()->verify($data)) {
+            echo '签名错误';
+            return;
+        }
+
+        //你可以在这里你的业务处理逻辑,比如处理你的订单状态、给会员加余额等等功能
+        //下面这句必须要执行,且在此之前不能有任何输出
+        echo "success";
+        return;
+    }
+
+    /**
+     * 支付成功返回
+     */
+    public function returnx()
+    {
+        $type = $this->request->param('type');
+        $data = $this->request->request('', null, 'trim');
+        $config = Service::getConfig($type);
+        $pay = new Pay($config);
+        if ($type == 'alipay' && !$pay->driver($type)->gateway()->verify($data)) {
+            echo '签名错误';
+            return;
+        }
+
+        //你可以在这里定义你的提示信息,但切记不可在此编写逻辑
+        $this->success("恭喜你!支付成功!", addon_url("epay/index/index"));
+
+        return;
+    }
+
+    /**
+     * 生成二维码
+     * @return Response
+     */
+    public function qrcode()
+    {
+        $text = $this->request->get('text', 'hello world');
+        $size = $this->request->get('size', 250);
+        $padding = $this->request->get('padding', 15);
+        $errorcorrection = $this->request->get('errorcorrection', 'medium');
+        $foreground = $this->request->get('foreground', "#ffffff");
+        $background = $this->request->get('background', "#000000");
+        $logo = $this->request->get('logo');
+        $logosize = $this->request->get('logosize');
+        $label = $this->request->get('label');
+        $labelfontsize = $this->request->get('labelfontsize');
+        $labelhalign = $this->request->get('labelhalign');
+        $labelvalign = $this->request->get('labelvalign');
+
+        // 前景色
+        list($r, $g, $b) = sscanf($foreground, "#%02x%02x%02x");
+        $foregroundcolor = ['r' => $r, 'g' => $g, 'b' => $b];
+
+        // 背景色
+        list($r, $g, $b) = sscanf($background, "#%02x%02x%02x");
+        $backgroundcolor = ['r' => $r, 'g' => $g, 'b' => $b];
+
+        $qrCode = new QrCode();
+        $qrCode
+            ->setText($text)
+            ->setSize($size)
+            ->setPadding($padding)
+            ->setErrorCorrection($errorcorrection)
+            ->setForegroundColor($foregroundcolor)
+            ->setBackgroundColor($backgroundcolor)
+            ->setLogoSize($logosize)
+            ->setLabelFontPath(ROOT_PATH . 'public/assets/fonts/Times New Roman.ttf')
+            ->setLabel($label)
+            ->setLabelFontSize($labelfontsize)
+            ->setLabelHalign($labelhalign)
+            ->setLabelValign($labelvalign)
+            ->setImageType(QrCode::IMAGE_TYPE_PNG);
+        //也可以直接使用render方法输出结果
+        //$qrCode->render();
+        return new Response($qrCode->get(), 200, ['Content-Type' => $qrCode->getContentType()]);
+    }
+
+}

+ 108 - 0
addons/epay/controller/Index.php

@@ -0,0 +1,108 @@
+<?php
+
+namespace addons\epay\controller;
+
+use addons\epay\library\Service;
+use fast\Random;
+use think\addons\Controller;
+use Yansongda\Pay\Log;
+use Yansongda\Pay\Pay;
+use Exception;
+
+/**
+ * 微信支付宝插件首页
+ *
+ * 此控制器仅用于开发展示说明和体验,建议自行添加一个新的控制器进行处理返回和回调事件,同时删除此控制器文件
+ *
+ * Class Index
+ * @package addons\epay\controller
+ */
+class Index extends Controller
+{
+
+    protected $layout = 'default';
+
+    protected $config = [];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+    }
+
+    public function index()
+    {
+        $this->view->assign("title", "FastAdmin微信支付宝整合插件");
+        return $this->view->fetch();
+    }
+
+    /**
+     * 体验,仅供开发测试
+     */
+    public function experience()
+    {
+        $amount = $this->request->request('amount');
+        $type = $this->request->request('type');
+        $method = $this->request->request('method');
+
+        if (!$amount || $amount < 0) {
+            $this->error("支付金额必须大于0");
+        }
+
+        if (!$type || !in_array($type, ['alipay', 'wechat'])) {
+            $this->error("支付类型不能为空");
+        }
+
+        //订单号
+        $out_trade_no = date("YmdHis") . mt_rand(100000, 999999);
+
+        //订单标题
+        $title = 'FastAdmin测试订单';
+
+        //回调链接
+        $notifyurl = $this->request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type;
+        $returnurl = $this->request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $out_trade_no;
+
+        return Service::submitOrder($amount, $out_trade_no, $type, $title, $notifyurl, $returnurl, $method);
+    }
+
+    /**
+     * 支付成功,仅供开发测试
+     */
+    public function notifyx()
+    {
+        $paytype = $this->request->param('paytype');
+        $pay = \addons\epay\library\Service::checkNotify($paytype);
+        if (!$pay) {
+            echo '签名错误';
+            return;
+        }
+        $data = $pay->verify();
+        try {
+            $payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
+            $out_trade_no = $data['out_trade_no'];
+
+            //你可以在此编写订单逻辑
+        } catch (Exception $e) {
+        }
+        echo $pay->success();
+    }
+
+    /**
+     * 支付返回,仅供开发测试
+     */
+    public function returnx()
+    {
+        $paytype = $this->request->param('paytype');
+        $out_trade_no = $this->request->param('out_trade_no');
+        $pay = \addons\epay\library\Service::checkReturn($paytype);
+        if (!$pay) {
+            $this->error('签名错误');
+        }
+
+        //你可以在这里通过out_trade_no去验证订单状态
+        //但是不可以在此编写订单逻辑!!!
+
+        $this->success("请返回网站查看支付结果", addon_url("epay/index/index"));
+    }
+
+}

+ 8 - 0
addons/epay/info.ini

@@ -0,0 +1,8 @@
+name = epay
+title = 微信支付宝整合
+intro = 可用于整合微信、支付宝付款,快速整合FastAdmin的其它模块
+author = Karson
+website = https://www.fastadmin.net
+version = 1.0.5
+state = 1
+url = /addons/epay.html

+ 17 - 0
addons/epay/library/OrderException.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace addons\epay\library;
+
+
+use think\Exception;
+
+class OrderException extends Exception
+{
+    public function __construct($message = "", $code = 0, $data = [])
+    {
+        $this->message = $message;
+        $this->code = $code;
+        $this->data = $data;
+    }
+
+}

+ 267 - 0
addons/epay/library/Service.php

@@ -0,0 +1,267 @@
+<?php
+
+namespace addons\epay\library;
+
+use Exception;
+use think\Log;
+use think\Response;
+use think\Session;
+use Yansongda\Pay\Pay;
+
+/**
+ * 订单服务类
+ *
+ * @package addons\epay\library
+ */
+class Service
+{
+
+    public static function submitOrder($amount, $orderid = null, $type = null, $title = null, $notifyurl = null, $returnurl = null, $method = null)
+    {
+        if (!is_array($amount)) {
+            $params = [
+                'amount'    => $amount,
+                'orderid'   => $orderid,
+                'type'      => $type,
+                'title'     => $title,
+                'notifyurl' => $notifyurl,
+                'returnurl' => $returnurl,
+                'method'    => $method,
+            ];
+        } else {
+            $params = $amount;
+        }
+        $type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';
+        $method = isset($params['method']) ? $params['method'] : 'web';
+        $orderid = isset($params['orderid']) ? $params['orderid'] : date("YmdHis") . mt_rand(100000, 999999);
+        $amount = isset($params['amount']) ? $params['amount'] : 1;
+        $title = isset($params['title']) ? $params['title'] : "支付";
+        $auth_code = isset($params['auth_code']) ? $params['auth_code'] : '';
+        $openid = isset($params['openid']) ? $params['openid'] : '';
+
+        $request = request();
+        $notifyurl = isset($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'notify';
+        $returnurl = isset($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'return/out_trade_no/' . $orderid;
+        $html = '';
+        $config = Service::getConfig($type);
+        $config[$type]['notify_url'] = $notifyurl;
+        $config[$type]['return_url'] = $returnurl;
+
+        if ($type == 'alipay') {
+            //创建支付对象
+            $pay = new Pay($config);
+            //支付宝支付,请根据你的需求,仅选择你所需要的即可
+            $params = [
+                'out_trade_no' => $orderid,//你的订单号
+                'total_amount' => $amount,//单位元
+                'subject'      => $title,
+            ];
+            //如果是移动端自动切换为wap
+            $method = $request->isMobile() ? 'wap' : $method;
+
+            switch ($method) {
+                case 'web':
+                    //电脑支付,跳转
+                    $html = $pay->driver($type)->gateway('web')->pay($params);
+                    Response::create($html)->send();
+                    break;
+                case 'wap':
+                    //手机网页支付,跳转
+                    $html = $pay->driver($type)->gateway('wap')->pay($params);
+                    Response::create($html)->send();
+                    break;
+                case 'app':
+                    //APP支付,直接返回字符串
+                    $html = $pay->driver($type)->gateway('app')->pay($params);
+                    break;
+                case 'scan':
+                    //扫码支付,直接返回字符串
+                    $html = $pay->driver($type)->gateway('scan')->pay($params);
+                    break;
+                case 'pos':
+                    //刷卡支付,直接返回字符串
+                    //刷卡支付必须要有auth_code
+                    $params['auth_code'] = $auth_code;
+                    $html = $pay->driver($type)->gateway('pos')->pay($params);
+                    break;
+                default:
+                    //其它支付类型请参考:https://docs.pay.yansongda.cn/alipay
+            }
+        } else {
+            //如果是PC支付,判断当前环境,进行跳转
+            if ($method == 'web') {
+                if ((strpos($request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false)) {
+                    Session::delete("openid");
+                    Session::set("wechatorderdata", $params);
+                    $url = addon_url('epay/api/wechat', [], true, true);
+                    header("location:{$url}");
+                    exit;
+                } elseif ($request->isMobile()) {
+                    $method = 'wap';
+                }
+            }
+
+            //创建支付对象
+            $pay = new Pay($config);
+            $params = [
+                'out_trade_no' => $orderid,//你的订单号
+                'body'         => $title,
+                'total_fee'    => $amount * 100, //单位分
+            ];
+            switch ($method) {
+                case 'web':
+                    //电脑支付,跳转到自定义展示页面(FastAdmin独有)
+                    $html = $pay->driver($type)->gateway('web')->pay($params);
+                    Response::create($html)->send();
+                    break;
+                case 'mp':
+                    //公众号支付
+                    //公众号支付必须有openid
+                    $params['openid'] = $openid;
+                    $html = $pay->driver($type)->gateway('mp')->pay($params);
+                    break;
+                case 'wap':
+                    //手机网页支付,跳转
+                    $params['spbill_create_ip'] = $request->ip(0, false);
+                    $html = $pay->driver($type)->gateway('wap')->pay($params);
+                    header("location:{$html}");
+                    exit;
+                    break;
+                case 'app':
+                    //APP支付,直接返回字符串
+                    $html = $pay->driver($type)->gateway('app')->pay($params);
+                    break;
+                case 'scan':
+                    //扫码支付,直接返回字符串
+                    $html = $pay->driver($type)->gateway('scan')->pay($params);
+                    break;
+                case 'pos':
+                    //刷卡支付,直接返回字符串
+                    //刷卡支付必须要有auth_code
+                    $params['auth_code'] = $auth_code;
+                    $html = $pay->driver($type)->gateway('pos')->pay($params);
+                    break;
+                case 'miniapp':
+                    //小程序支付,直接返回字符串
+                    //小程序支付必须要有openid
+                    $params['openid'] = $openid;
+                    $html = $pay->driver($type)->gateway('miniapp')->pay($params);
+                    break;
+                default:
+            }
+        }
+        //返回字符串
+        $html = is_array($html) ? json_encode($html) : $html;
+        return $html;
+    }
+
+    /**
+     * 创建支付对象
+     * @param string $type   支付类型
+     * @param array  $config 配置信息
+     * @return bool
+     */
+    public static function createPay($type, $config = [])
+    {
+        $type = strtolower($type);
+        if (!in_array($type, ['wechat', 'alipay'])) {
+            return false;
+        }
+        $config = self::getConfig($type);
+        $config = array_merge($config[$type], $config);
+        $pay = new Pay($config);
+        return $pay;
+    }
+
+    /**
+     * 验证回调是否成功
+     * @param string $type   支付类型
+     * @param array  $config 配置信息
+     * @return bool|Pay
+     */
+    public static function checkNotify($type, $config = [])
+    {
+        $type = strtolower($type);
+        if (!in_array($type, ['wechat', 'alipay'])) {
+            return false;
+        }
+        try {
+            $pay = new Pay(self::getConfig($type));
+            $data = $type == 'wechat' ? file_get_contents("php://input") : request()->post('', null, 'trim');
+
+            $data = $pay->driver($type)->gateway()->verify($data);
+
+            if ($type == 'alipay') {
+                if (in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
+                    return $pay;
+                }
+            } else {
+                return $pay;
+            }
+        } catch (Exception $e) {
+            return false;
+        }
+
+        return false;
+    }
+
+    /**
+     * 验证返回是否成功
+     * @param string $type   支付类型
+     * @param array  $config 配置信息
+     * @return bool|Pay
+     */
+    public static function checkReturn($type, $config = [])
+    {
+        $type = strtolower($type);
+        if (!in_array($type, ['wechat', 'alipay'])) {
+            return false;
+        }
+        //微信无需验证
+        if ($type == 'wechat') {
+            return true;
+        }
+        try {
+            $pay = new Pay(self::getConfig($type));
+            $data = $type == 'wechat' ? file_get_contents("php://input") : request()->get('', null, 'trim');
+            $data = $pay->driver($type)->gateway()->verify($data);
+            if ($data) {
+                return $pay;
+            }
+        } catch (Exception $e) {
+            return false;
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取配置
+     * @param string $type 支付类型
+     * @return array|mixed
+     */
+    public static function getConfig($type = 'wechat')
+    {
+        $config = get_addon_config('epay');
+        $config = isset($config[$type]) ? $config[$type] : $config['wechat'];
+        if ($config['log']) {
+            $config['log'] = [
+                'file'  => LOG_PATH . '/epaylogs/' . $type . '-' . date("Y-m-d") . '.log',
+                'level' => 'debug'
+            ];
+        }
+        if (isset($config['cert_client']) && substr($config['cert_client'], 0, 6) == '/epay/') {
+            $config['cert_client'] = ADDON_PATH . $config['cert_client'];
+        }
+        if (isset($config['cert_key']) && substr($config['cert_key'], 0, 6) == '/epay/') {
+            $config['cert_key'] = ADDON_PATH . $config['cert_key'];
+        }
+
+        $config['notify_url'] = empty($config['notify_url']) ? addon_url('epay/api/notifyx', [], false) . '/type/' . $type : $config['notify_url'];
+        $config['notify_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['notify_url']) ? request()->root(true) . $config['notify_url'] : $config['notify_url'];
+        $config['return_url'] = empty($config['return_url']) ? addon_url('epay/api/returnx', [], false) . '/type/' . $type : $config['return_url'];
+        $config['return_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['return_url']) ? request()->root(true) . $config['return_url'] : $config['return_url'];
+        return [$type => $config];
+    }
+
+}

+ 107 - 0
addons/epay/library/Wechat.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace addons\epay\library;
+
+use fast\Http;
+use think\Cache;
+use think\Session;
+
+/**
+ * 微信授权
+ *
+ */
+class Wechat
+{
+    private $app_id = '';
+    private $app_secret = '';
+    private $scope = 'snsapi_userinfo';
+
+    public function __construct($app_id, $app_secret)
+    {
+        $this->app_id = $app_id;
+        $this->app_secret = $app_secret;
+    }
+
+    /**
+     * 获取微信授权链接
+     *
+     * @return string
+     */
+    public function getAuthorizeUrl()
+    {
+        $redirect_uri = addon_url('epay/api/wechat', [], true, true);
+        $redirect_uri = urlencode($redirect_uri);
+        $state = \fast\Random::alnum();
+        Session::set('state', $state);
+        return "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->app_id}&redirect_uri={$redirect_uri}&response_type=code&scope={$this->scope}&state={$state}#wechat_redirect";
+    }
+
+    /**
+     * 获取微信openid
+     *
+     * @return mixed|string
+     */
+    public function getOpenid()
+    {
+        $openid = Session::get('openid');
+        if (!$openid) {
+            if (!isset($_GET['code'])) {
+                $url = $this->getAuthorizeUrl();
+
+                Header("Location: $url");
+                exit();
+            } else {
+                $state = Session::get('state');
+                if ($state == $_GET['state']) {
+                    $code = $_GET['code'];
+                    $token = $this->getAccessToken($code);
+                    $openid = isset($token['openid']) ? $token['openid'] : '';
+                    if ($openid) {
+                        Session::set("openid", $openid);
+                    }
+                }
+            }
+        }
+        return $openid;
+    }
+
+    /**
+     * 获取授权token网页授权
+     *
+     * @param string $code
+     * @return mixed|string
+     */
+    public function getAccessToken($code = '')
+    {
+        $params = [
+            'appid'      => $this->app_id,
+            'secret'     => $this->app_secret,
+            'code'       => $code,
+            'grant_type' => 'authorization_code'
+        ];
+        $ret = Http::sendRequest('https://api.weixin.qq.com/sns/oauth2/access_token', $params, 'GET');
+        if ($ret['ret']) {
+            $ar = json_decode($ret['msg'], true);
+            return $ar;
+        }
+        return [];
+    }
+
+    public function getJsticket()
+    {
+        $jsticket = Session::get('jsticket');
+        if (!$jsticket) {
+            $token = $this->getAccessToken($code);
+            $params = [
+                'access_token' => 'token',
+                'type'         => 'jsapi',
+            ];
+            $ret = Http::sendRequest('https://api.weixin.qq.com/cgi-bin/ticket/getticket', $params, 'GET');
+            if ($ret['ret']) {
+                $ar = json_decode($ret['msg'], true);
+                return $ar;
+            }
+        }
+        return $jsticket;
+    }
+}

+ 63 - 0
addons/epay/library/Yansongda/Pay/Contracts/GatewayInterface.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace Yansongda\Pay\Contracts;
+
+interface GatewayInterface
+{
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    public function pay(array $config_biz);
+
+    /**
+     * refund a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array|string $config_biz
+     *
+     * @return array|bool
+     */
+    public function refund($config_biz);
+
+    /**
+     * close a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array|string $config_biz
+     *
+     * @return array|bool
+     */
+    public function close($config_biz);
+
+    /**
+     * find a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $out_trade_no
+     *
+     * @return array|bool
+     */
+    public function find($out_trade_no);
+
+    /**
+     * verify notify.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param mixed  $data
+     * @param string $sign
+     * @param bool   $sync
+     *
+     * @return array|bool
+     */
+    public function verify($data, $sign = null, $sync = false);
+}

+ 7 - 0
addons/epay/library/Yansongda/Pay/Exceptions/Exception.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Yansongda\Pay\Exceptions;
+
+class Exception extends \Exception
+{
+}

+ 28 - 0
addons/epay/library/Yansongda/Pay/Exceptions/GatewayException.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Yansongda\Pay\Exceptions;
+
+class GatewayException extends Exception
+{
+    /**
+     * error raw data.
+     *
+     * @var array
+     */
+    public $raw = [];
+
+    /**
+     * [__construct description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string     $message
+     * @param string|int $code
+     */
+    public function __construct($message, $code, $raw = [])
+    {
+        parent::__construct($message, intval($code));
+
+        $this->raw = $raw;
+    }
+}

+ 7 - 0
addons/epay/library/Yansongda/Pay/Exceptions/InvalidArgumentException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Yansongda\Pay\Exceptions;
+
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}

+ 291 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/Alipay.php

@@ -0,0 +1,291 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+use Yansongda\Pay\Contracts\GatewayInterface;
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+use Yansongda\Pay\Support\Config;
+use Yansongda\Pay\Traits\HasHttpRequest;
+
+abstract class Alipay implements GatewayInterface
+{
+    use HasHttpRequest;
+
+    /**
+     * @var string
+     */
+    protected $gateway = 'https://openapi.alipay.com/gateway.do?charset=UTF-8';
+
+    /**
+     * alipay global config params.
+     *
+     * @var array
+     */
+    protected $config;
+
+    /**
+     * user's config params.
+     *
+     * @var \Yansongda\Pay\Support\Config
+     */
+    protected $user_config;
+
+    /**
+     * [__construct description].
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config [description]
+     */
+    public function __construct(array $config)
+    {
+        $this->user_config = new Config($config);
+
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $this->config = [
+            'app_id'      => $this->user_config->get('app_id'),
+            'method'      => '',
+            'format'      => 'JSON',
+            'charset'     => 'UTF-8',
+            'sign_type'   => 'RSA2',
+            'version'     => '1.0',
+            'return_url'  => $this->user_config->get('return_url', ''),
+            'notify_url'  => $this->user_config->get('notify_url', ''),
+            'timestamp'   => date('Y-m-d H:i:s'),
+            'sign'        => '',
+            'biz_content' => '',
+        ];
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    public function pay(array $config_biz)
+    {
+        $config_biz['product_code'] = $this->getProductCode();
+
+        $this->config['method'] = $this->getMethod();
+        $this->config['biz_content'] = json_encode($config_biz);
+        $this->config['sign'] = $this->getSign();
+    }
+
+    /**
+     * refund a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param mixed $config_biz
+     *
+     * @return array|bool
+     */
+    public function refund($config_biz, $refund_amount = null)
+    {
+        if (!is_array($config_biz)) {
+            $config_biz = [
+                'out_trade_no'  => $config_biz,
+                'refund_amount' => $refund_amount,
+            ];
+        }
+
+        return $this->getResult($config_biz, 'alipay.trade.refund');
+    }
+
+    /**
+     * close a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array|string $config_biz
+     *
+     * @return array|bool
+     */
+    public function close($config_biz)
+    {
+        if (!is_array($config_biz)) {
+            $config_biz = [
+                'out_trade_no' => $config_biz,
+            ];
+        }
+
+        return $this->getResult($config_biz, 'alipay.trade.close');
+    }
+
+    /**
+     * find a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $out_trade_no
+     *
+     * @return array|bool
+     */
+    public function find($out_trade_no = '')
+    {
+        $config_biz = [
+            'out_trade_no' => $out_trade_no,
+        ];
+
+        return $this->getResult($config_biz, 'alipay.trade.query');
+    }
+
+    /**
+     * verify the notify.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array  $data
+     * @param string $sign
+     * @param bool   $sync
+     *
+     * @return array|bool
+     */
+    public function verify($data, $sign = null, $sync = false)
+    {
+        if (is_null($this->user_config->get('ali_public_key'))) {
+            throw new InvalidArgumentException('Missing Config -- [ali_public_key]');
+        }
+
+        $sign = is_null($sign) ? $data['sign'] : $sign;
+
+        $res = "-----BEGIN PUBLIC KEY-----\n".
+                wordwrap($this->user_config->get('ali_public_key'), 64, "\n", true).
+                "\n-----END PUBLIC KEY-----";
+
+        $toVerify = $sync ? json_encode($data) : $this->getSignContent($data, true);
+
+        return openssl_verify($toVerify, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1 ? $data : false;
+    }
+
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    abstract protected function getMethod();
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    abstract protected function getProductCode();
+
+    /**
+     * build pay html.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function buildPayHtml()
+    {
+        $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".$this->gateway."' method='POST'>";
+        foreach ($this->config as $key => $val) {
+            $val = str_replace("'", '&apos;', $val);
+            $sHtml .= "<input type='hidden' name='".$key."' value='".$val."'/>";
+        }
+        $sHtml .= "<input type='submit' value='ok' style='display:none;'></form>";
+        $sHtml .= "<script>document.forms['alipaysubmit'].submit();</script>";
+
+        return $sHtml;
+    }
+
+    /**
+     * get alipay api result.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array  $config_biz
+     * @param string $method
+     *
+     * @return array|bool
+     */
+    protected function getResult($config_biz, $method)
+    {
+        $this->config['biz_content'] = json_encode($config_biz);
+        $this->config['method'] = $method;
+        $this->config['sign'] = $this->getSign();
+
+        $this->config = array_filter($this->config, function ($value) {
+            return $value !== '' && !is_null($value);
+        });
+
+        $method = str_replace('.', '_', $method).'_response';
+
+        $data = json_decode($this->post($this->gateway, $this->config), true);
+
+        if (!isset($data[$method]['code']) || $data[$method]['code'] !== '10000') {
+            throw new GatewayException(
+                'get result error:'.$data[$method]['msg'].' - '.$data[$method]['sub_code'],
+                $data[$method]['code'],
+                $data);
+        }
+
+        return $this->verify($data[$method], $data['sign'], true);
+    }
+
+    /**
+     * get sign.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getSign()
+    {
+        if (is_null($this->user_config->get('private_key'))) {
+            throw new InvalidArgumentException('Missing Config -- [private_key]');
+        }
+
+        $res = "-----BEGIN RSA PRIVATE KEY-----\n".
+                wordwrap($this->user_config->get('private_key'), 64, "\n", true).
+                "\n-----END RSA PRIVATE KEY-----";
+
+        openssl_sign($this->getSignContent($this->config), $sign, $res, OPENSSL_ALGO_SHA256);
+
+        return base64_encode($sign);
+    }
+
+    /**
+     * get signContent that is to be signed.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $toBeSigned
+     * @param bool  $verify
+     *
+     * @return string
+     */
+    protected function getSignContent(array $toBeSigned, $verify = false)
+    {
+        ksort($toBeSigned);
+
+        $stringToBeSigned = '';
+        foreach ($toBeSigned as $k => $v) {
+            if ($verify && $k != 'sign' && $k != 'sign_type') {
+                $stringToBeSigned .= $k.'='.$v.'&';
+            }
+            if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
+                $stringToBeSigned .= $k.'='.$v.'&';
+            }
+        }
+        $stringToBeSigned = substr($stringToBeSigned, 0, -1);
+        unset($k, $v);
+
+        return $stringToBeSigned;
+    }
+}

+ 46 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/AppGateway.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class AppGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.app.pay';
+    }
+
+    /**
+     * get productCode method.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return 'QUICK_MSECURITY_PAY';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        parent::pay($config_biz);
+
+        return http_build_query($this->config);
+    }
+}

+ 47 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/PosGateway.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class PosGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.pay';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return 'FACE_TO_FACE_PAYMENT';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array  $config_biz
+     * @param string $scene
+     *
+     * @return array|bool
+     */
+    public function pay(array $config_biz = [], $scene = 'bar_code')
+    {
+        $config_biz['scene'] = $scene;
+
+        return $this->getResult($config_biz, $this->getMethod());
+    }
+}

+ 44 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/ScanGateway.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class ScanGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.precreate';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return '';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array|bool
+     */
+    public function pay(array $config_biz = [])
+    {
+        return $this->getResult($config_biz, $this->getMethod());
+    }
+}

+ 44 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/TransferGateway.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class TransferGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.fund.trans.toaccount.transfer';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return '';
+    }
+
+    /**
+     * transfer amount to account.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array|bool
+     */
+    public function pay(array $config_biz = [])
+    {
+        return $this->getResult($config_biz, $this->getMethod());
+    }
+}

+ 48 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/WapGateway.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class WapGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @version 2017-08-10
+     *
+     * @return string [description]
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.wap.pay';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return 'QUICK_WAP_WAY';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        parent::pay($config_biz);
+
+        return $this->buildPayHtml();
+    }
+}

+ 46 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/WebGateway.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class WebGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.page.pay';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return 'FAST_INSTANT_TRADE_PAY';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        parent::pay($config_biz);
+
+        return $this->buildPayHtml();
+    }
+}

+ 50 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/AppGateway.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class AppGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'APP';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('appid'))) {
+            throw new InvalidArgumentException('Missing Config -- [appid]');
+        }
+
+        $this->config['appid'] = $this->user_config->get('appid');
+
+        $payRequest = [
+            'appid'     => $this->user_config->get('appid'),
+            'partnerid' => $this->user_config->get('mch_id'),
+            'prepayid'  => $this->preOrder($config_biz)['prepay_id'],
+            'timestamp' => strval(time()),
+            'noncestr'  => $this->createNonceStr(),
+            'package'   => 'Sign=WXPay',
+        ];
+        $payRequest['sign'] = $this->getSign($payRequest);
+
+        return $payRequest;
+    }
+}

+ 82 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/GroupredpackGateway.php

@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * 发放裂变红包
+ * Class GroupredpackGateway
+ * Date: 2017/12/21
+ * Time: 19:23
+ * Com:萌点云科技(深圳)有限公司.
+ *
+ * Author:陈老司机
+ *
+ * Email:690712575@qq.com
+ */
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class GroupredpackGateway extends Wechat
+{
+    /**
+     * @var string
+     */
+    protected $gateway_transfer = 'mmpaymkttransfers/sendgroupredpack';
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+        unset($this->config['sign_type']);
+        unset($this->config['trade_type']);
+        unset($this->config['notify_url']);
+        unset($this->config['app_id']);
+        unset($this->config['appid']);
+        $this->config = array_merge($this->config, $config_biz);
+        $this->config['sign'] = $this->getSign($this->config);
+        $data = $this->fromXml($this->post(
+            $this->endpoint.$this->gateway_transfer,
+            $this->toXml($this->config),
+            [
+                'cert'    => $this->user_config->get('cert_client', ''),
+                'ssl_key' => $this->user_config->get('cert_key', ''),
+            ]
+        ));
+        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
+            $error = 'getResult error:'.$data['return_msg'];
+            $error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
+        }
+
+        if (isset($error)) {
+            throw new GatewayException(
+                $error,
+                20000,
+                $data);
+        }
+
+        return $data;
+    }
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return '';
+    }
+}

+ 49 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/MiniappGateway.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class MiniappGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string [description]
+     */
+    protected function getTradeType()
+    {
+        return 'JSAPI';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('miniapp_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [miniapp_id]');
+        }
+
+        $this->config['appid'] = $this->user_config->get('miniapp_id');
+
+        $payRequest = [
+            'appId'     => $this->user_config->get('miniapp_id'),
+            'timeStamp' => strval(time()),
+            'nonceStr'  => $this->createNonceStr(),
+            'package'   => 'prepay_id='.$this->preOrder($config_biz)['prepay_id'],
+            'signType'  => 'MD5',
+        ];
+        $payRequest['paySign'] = $this->getSign($payRequest);
+
+        return $payRequest;
+    }
+}

+ 47 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/MpGateway.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class MpGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'JSAPI';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $payRequest = [
+            'appId'     => $this->user_config->get('app_id'),
+            'timeStamp' => strval(time()),
+            'nonceStr'  => $this->createNonceStr(),
+            'package'   => 'prepay_id='.$this->preOrder($config_biz)['prepay_id'],
+            'signType'  => 'MD5',
+        ];
+        $payRequest['paySign'] = $this->getSign($payRequest);
+
+        return $payRequest;
+    }
+}

+ 46 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/PosGateway.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class PosGateway extends Wechat
+{
+    /**
+     * @var string
+     */
+    protected $gateway_order = 'pay/micropay';
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'MICROPAY';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        unset($this->config['trade_type']);
+        unset($this->config['notify_url']);
+
+        return $this->preOrder($config_biz);
+    }
+}

+ 86 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/RedpackGateway.php

@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * 发放普通红包
+ * Class RedPackGateway
+ * Date: 2017/12/21
+ * Time: 19:23
+ * Com:萌点云科技(深圳)有限公司.
+ *
+ * Author:陈老司机
+ *
+ * Email:690712575@qq.com
+ */
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class RedpackGateway extends Wechat
+{
+    /**
+     * @var string
+     */
+    protected $gateway_transfer = 'mmpaymkttransfers/sendredpack';
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+        unset($this->config['sign_type']);
+        unset($this->config['trade_type']);
+        unset($this->config['notify_url']);
+        unset($this->config['app_id']);
+        unset($this->config['appid']);
+
+        $this->config = array_merge($this->config, $config_biz);
+
+        $this->config['sign'] = $this->getSign($this->config);
+
+        $data = $this->fromXml($this->post(
+            $this->endpoint.$this->gateway_transfer,
+            $this->toXml($this->config),
+            [
+                'cert'    => $this->user_config->get('cert_client', ''),
+                'ssl_key' => $this->user_config->get('cert_key', ''),
+            ]
+        ));
+
+        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
+            $error = 'getResult error:'.$data['return_msg'];
+            $error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
+        }
+
+        if (isset($error)) {
+            throw new GatewayException(
+                $error,
+                20000,
+                $data);
+        }
+
+        return $data;
+    }
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return '';
+    }
+}

+ 38 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/ScanGateway.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class ScanGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'NATIVE';
+    }
+
+    /**
+     * pay a order using modelTWO.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        return $this->preOrder($config_biz)['code_url'];
+    }
+}

+ 78 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/TransferGateway.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class TransferGateway extends Wechat
+{
+    /**
+     * @var string
+     */
+    protected $gateway_transfer = 'mmpaymkttransfers/promotion/transfers';
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return '';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $config_biz['mch_appid'] = $this->config['appid'];
+        $config_biz['mchid'] = $this->config['mch_id'];
+
+        unset($this->config['appid']);
+        unset($this->config['mch_id']);
+        unset($this->config['sign_type']);
+        unset($this->config['trade_type']);
+        unset($this->config['notify_url']);
+
+        $this->config = array_merge($this->config, $config_biz);
+
+        $this->config['sign'] = $this->getSign($this->config);
+
+        $data = $this->fromXml($this->post(
+            $this->endpoint.$this->gateway_transfer,
+            $this->toXml($this->config),
+            [
+                'cert'    => $this->user_config->get('cert_client', ''),
+                'ssl_key' => $this->user_config->get('cert_key', ''),
+            ]
+        ));
+
+        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
+            $error = 'getResult error:'.$data['return_msg'];
+            $error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
+        }
+
+        if (isset($error)) {
+            throw new GatewayException(
+                $error,
+                20000,
+                $data);
+        }
+
+        return $data;
+    }
+}

+ 41 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/WapGateway.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class WapGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'MWEB';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $data = $this->preOrder($config_biz);
+
+        return is_null($this->user_config->get('return_url')) ? $data['mweb_url'] : $data['mweb_url'].
+                        '&redirect_url='.urlencode($this->user_config->get('return_url'));
+    }
+}

+ 69 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/WebGateway.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class WebGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'NATIVE';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $code_url = $this->preOrder($config_biz)['code_url'];
+        $params = [
+            'body'         => $config_biz['body'],
+            'code_url'     => $code_url,
+            'out_trade_no' => $config_biz['out_trade_no'],
+            'return_url'   => $this->user_config->get('return_url'),
+            'total_fee'    => $config_biz['total_fee'],
+        ];
+        $params['sign'] = md5(implode('', $params) . $this->user_config->get('app_id'));
+        $endpoint = addon_url("epay/api/wechat");
+
+        return $this->buildPayHtml($endpoint, $params);
+    }
+
+    /**
+     * build pay html.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function buildPayHtml($endpoint, $params)
+    {
+        $sHtml = "<form id='alipaysubmit' name='wechatsubmit' action='" . $endpoint . "' method='POST'>";
+        foreach ($params as $key => $val) {
+            $val = str_replace("'", '&apos;', $val);
+            $sHtml .= "<input type='hidden' name='" . $key . "' value='" . $val . "'/>";
+        }
+        $sHtml .= "<input type='submit' value='ok' style='display:none;'></form>";
+        $sHtml .= "<script>document.forms['wechatsubmit'].submit();</script>";
+
+        return $sHtml;
+    }
+}

+ 354 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/Wechat.php

@@ -0,0 +1,354 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Contracts\GatewayInterface;
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+use Yansongda\Pay\Support\Config;
+use Yansongda\Pay\Traits\HasHttpRequest;
+
+abstract class Wechat implements GatewayInterface
+{
+    use HasHttpRequest;
+
+    /**
+     * @var string
+     */
+    protected $endpoint = 'https://api.mch.weixin.qq.com/';
+
+    /**
+     * @var string
+     */
+    protected $gateway_order = 'pay/unifiedorder';
+
+    /**
+     * @var string
+     */
+    protected $gateway_query = 'pay/orderquery';
+
+    /**
+     * @var string
+     */
+    protected $gateway_close = 'pay/closeorder';
+
+    /**
+     * @var string
+     */
+    protected $gateway_refund = 'secapi/pay/refund';
+
+    /**
+     * @var array
+     */
+    protected $config;
+
+    /**
+     * @var \Yansongda\Pay\Support\Config
+     */
+    protected $user_config;
+
+    /**
+     * [__construct description].
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config
+     */
+    public function __construct(array $config)
+    {
+        $this->user_config = new Config($config);
+
+        $this->config = [
+            'appid'      => $this->user_config->get('app_id', ''),
+            'mch_id'     => $this->user_config->get('mch_id', ''),
+            'nonce_str'  => $this->createNonceStr(),
+            'sign_type'  => 'MD5',
+            'notify_url' => $this->user_config->get('notify_url', ''),
+            'trade_type' => $this->getTradeType(),
+        ];
+
+        if ($endpoint = $this->user_config->get('endpoint_url')) {
+            $this->endpoint = $endpoint;
+        }
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    abstract public function pay(array $config_biz = []);
+
+    /**
+     * refund.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string|bool
+     */
+    public function refund($config_biz = [])
+    {
+        if (isset($config_biz['miniapp'])) {
+            $this->config['appid'] = $this->user_config->get('miniapp_id');
+            unset($config_biz['miniapp']);
+        }
+
+        $this->config = array_merge($this->config, $config_biz);
+
+        $this->unsetTradeTypeAndNotifyUrl();
+
+        return $this->getResult($this->gateway_refund, true);
+    }
+
+    /**
+     * close a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return array|bool
+     */
+    public function close($out_trade_no = '')
+    {
+        $this->config['out_trade_no'] = $out_trade_no;
+
+        $this->unsetTradeTypeAndNotifyUrl();
+
+        return $this->getResult($this->gateway_close);
+    }
+
+    /**
+     * find a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $out_trade_no
+     *
+     * @return array|bool
+     */
+    public function find($out_trade_no = '')
+    {
+        $this->config['out_trade_no'] = $out_trade_no;
+
+        $this->unsetTradeTypeAndNotifyUrl();
+
+        return $this->getResult($this->gateway_query);
+    }
+
+    /**
+     * verify the notify.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $data
+     * @param string $sign
+     * @param bool   $sync
+     *
+     * @return array|bool
+     */
+    public function verify($data, $sign = null, $sync = false)
+    {
+        $data = $this->fromXml($data);
+
+        $sign = is_null($sign) ? $data['sign'] : $sign;
+
+        return $this->getSign($data) === $sign ? $data : false;
+    }
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    abstract protected function getTradeType();
+
+    /**
+     * pre order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    protected function preOrder($config_biz = [])
+    {
+        $this->config = array_merge($this->config, $config_biz);
+
+        return $this->getResult($this->gateway_order);
+    }
+
+    /**
+     * get api result.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $path
+     * @param bool   $cert
+     *
+     * @return array
+     */
+    protected function getResult($path, $cert = false)
+    {
+        $this->config['sign'] = $this->getSign($this->config);
+
+        if ($cert) {
+            $data = $this->fromXml($this->post(
+                $this->endpoint.$path,
+                $this->toXml($this->config),
+                [
+                    'cert'    => $this->user_config->get('cert_client', ''),
+                    'ssl_key' => $this->user_config->get('cert_key', ''),
+                ]
+            ));
+        } else {
+            $data = $this->fromXml($this->post($this->endpoint.$path, $this->toXml($this->config)));
+        }
+
+        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
+            $error = 'getResult error:'.$data['return_msg'];
+            $error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
+        }
+
+        if (!isset($error) && $this->getSign($data) !== $data['sign']) {
+            $error = 'getResult error: return data sign error';
+        }
+
+        if (isset($error)) {
+            throw new GatewayException(
+                $error,
+                20000,
+                $data);
+        }
+
+        return $data;
+    }
+
+    /**
+     * sign.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $data
+     *
+     * @return string
+     */
+    protected function getSign($data)
+    {
+        if (is_null($this->user_config->get('key'))) {
+            throw new InvalidArgumentException('Missing Config -- [key]');
+        }
+
+        ksort($data);
+
+        $string = md5($this->getSignContent($data).'&key='.$this->user_config->get('key'));
+
+        return strtoupper($string);
+    }
+
+    /**
+     * get sign content.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $data
+     *
+     * @return string
+     */
+    protected function getSignContent($data)
+    {
+        $buff = '';
+
+        foreach ($data as $k => $v) {
+            $buff .= ($k != 'sign' && $v != '' && !is_array($v)) ? $k.'='.$v.'&' : '';
+        }
+
+        return trim($buff, '&');
+    }
+
+    /**
+     * create random string.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param int $length
+     *
+     * @return string
+     */
+    protected function createNonceStr($length = 16)
+    {
+        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+        $str = '';
+        for ($i = 0; $i < $length; $i++) {
+            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
+        }
+
+        return $str;
+    }
+
+    /**
+     * convert to xml.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $data
+     *
+     * @return string
+     */
+    protected function toXml($data)
+    {
+        if (!is_array($data) || count($data) <= 0) {
+            throw new InvalidArgumentException('convert to xml error!invalid array!');
+        }
+
+        $xml = '<xml>';
+        foreach ($data as $key => $val) {
+            $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' :
+                                       '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
+        }
+        $xml .= '</xml>';
+
+        return $xml;
+    }
+
+    /**
+     * convert to array.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $xml
+     *
+     * @return array
+     */
+    protected function fromXml($xml)
+    {
+        if (!$xml) {
+            throw new InvalidArgumentException('convert to array error !invalid xml');
+        }
+
+        libxml_disable_entity_loader(true);
+
+        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
+    }
+
+    /**
+     * delete trade_type and notify_url.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return bool
+     */
+    protected function unsetTradeTypeAndNotifyUrl()
+    {
+        unset($this->config['notify_url']);
+        unset($this->config['trade_type']);
+
+        return true;
+    }
+}

+ 134 - 0
addons/epay/library/Yansongda/Pay/Pay.php

@@ -0,0 +1,134 @@
+<?php
+
+namespace Yansongda\Pay;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+use Yansongda\Pay\Support\Config;
+
+class Pay
+{
+    /**
+     * @var \Yansongda\Pay\Support\Config
+     */
+    private $config;
+
+    /**
+     * @var string
+     */
+    private $drivers;
+
+    /**
+     * @var \Yansongda\Pay\Contracts\GatewayInterface
+     */
+    private $gateways;
+
+    /**
+     * construct method.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->config = new Config($config);
+    }
+
+    /**
+     * set pay's driver.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $driver
+     *
+     * @return Pay
+     */
+    public function driver($driver)
+    {
+        if (is_null($this->config->get($driver))) {
+            throw new InvalidArgumentException("Driver [$driver]'s Config is not defined.");
+        }
+
+        $this->drivers = $driver;
+
+        return $this;
+    }
+
+    /**
+     * set pay's gateway.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $gateway
+     *
+     * @return \Yansongda\Pay\Contracts\GatewayInterface
+     */
+    public function gateway($gateway = 'web')
+    {
+        if (!isset($this->drivers)) {
+            throw new InvalidArgumentException('Driver is not defined.');
+        }
+
+        $this->gateways = $this->createGateway($gateway);
+
+        return $this->gateways;
+    }
+
+    /**
+     * create pay's gateway.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $gateway
+     *
+     * @return \Yansongda\Pay\Contracts\GatewayInterface
+     */
+    protected function createGateway($gateway)
+    {
+        if (!file_exists(__DIR__ . '/Gateways/' . ucfirst($this->drivers) . '/' . ucfirst($gateway) . 'Gateway.php')) {
+            throw new InvalidArgumentException("Gateway [$gateway] is not supported.");
+        }
+
+        $gateway = __NAMESPACE__ . '\\Gateways\\' . ucfirst($this->drivers) . '\\' . ucfirst($gateway) . 'Gateway';
+
+        return $this->build($gateway);
+    }
+
+    /**
+     * build pay's gateway.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $gateway
+     *
+     * @return \Yansongda\Pay\Contracts\GatewayInterface
+     */
+    protected function build($gateway)
+    {
+        return new $gateway($this->config->get($this->drivers));
+    }
+
+    public function verify()
+    {
+        if ($this->drivers == 'wechat') {
+            return $this->gateway()->verify(file_get_contents("php://input"));
+        } else {
+            $request = request();
+            $data = $request->get('app_id') && $request->get('out_trade_no') ? $request->get('', null, 'trim') : $request->post('', null, 'trim');
+            return $this->gateway()->verify($data);
+        }
+    }
+
+    public function success()
+    {
+        if ($this->drivers == 'wechat') {
+            echo '<xml>
+  <return_code><![CDATA[SUCCESS]]></return_code>
+  <return_msg><![CDATA[OK]]></return_msg>
+</xml>';
+        } else {
+            echo 'success';
+        }
+        return;
+    }
+}

+ 147 - 0
addons/epay/library/Yansongda/Pay/Support/Config.php

@@ -0,0 +1,147 @@
+<?php
+
+namespace Yansongda\Pay\Support;
+
+use ArrayAccess;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class Config implements ArrayAccess
+{
+    /**
+     * @var array
+     */
+    protected $config;
+
+    /**
+     * Config constructor.
+     *
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * get a config.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $key
+     * @param string $default
+     *
+     * @return mixed
+     */
+    public function get($key = null, $default = null)
+    {
+        $config = $this->config;
+
+        if (is_null($key)) {
+            return $config;
+        }
+
+        if (isset($config[$key])) {
+            return $config[$key];
+        }
+
+        foreach (explode('.', $key) as $segment) {
+            if (!is_array($config) || !array_key_exists($segment, $config)) {
+                return $default;
+            }
+            $config = $config[$segment];
+        }
+
+        return $config;
+    }
+
+    /**
+     * set a config.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $key
+     * @param array  $value
+     */
+    public function set(string $key, $value)
+    {
+        if ($key == '') {
+            throw new InvalidArgumentException('Invalid config key.');
+        }
+
+        // 只支持三维数组,多余无意义
+        $keys = explode('.', $key);
+        switch (count($keys)) {
+            case '1':
+                $this->config[$key] = $value;
+                break;
+            case '2':
+                $this->config[$keys[0]][$keys[1]] = $value;
+                break;
+            case '3':
+                $this->config[$keys[0]][$keys[1]][$keys[2]] = $value;
+                break;
+
+            default:
+                throw new InvalidArgumentException('Invalid config key.');
+        }
+
+        return $this->config;
+    }
+
+    /**
+     * [offsetExists description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $offset
+     *
+     * @return bool
+     */
+    public function offsetExists($offset)
+    {
+        return array_key_exists($offset, $this->config);
+    }
+
+    /**
+     * [offsetGet description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $offset
+     *
+     * @return mixed
+     */
+    public function offsetGet($offset)
+    {
+        return $this->get($offset);
+    }
+
+    /**
+     * [offsetSet description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $offset
+     * @param string $value
+     *
+     * @return array
+     */
+    public function offsetSet($offset, $value)
+    {
+        $this->set($offset, $value);
+    }
+
+    /**
+     * [offsetUnset description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $offset
+     *
+     * @return array
+     */
+    public function offsetUnset($offset)
+    {
+        $this->set($offset, null);
+    }
+}

+ 119 - 0
addons/epay/library/Yansongda/Pay/Traits/HasHttpRequest.php

@@ -0,0 +1,119 @@
+<?php
+
+/*
+ * (c) overtrue <i@overtrue.me>
+ *
+ * Modified By yansongda <me@yansongda.cn>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Yansongda\Pay\Traits;
+
+use GuzzleHttp\Client;
+use Psr\Http\Message\ResponseInterface;
+
+trait HasHttpRequest
+{
+    /**
+     * Make a get request.
+     *
+     * @param string $endpoint
+     * @param array  $query
+     * @param array  $headers
+     *
+     * @return array
+     */
+    protected function get($endpoint, $query = [], $headers = [])
+    {
+        return $this->request('get', $endpoint, [
+            'headers' => $headers,
+            'query'   => $query,
+        ]);
+    }
+
+    /**
+     * Make a post request.
+     *
+     * @param string $endpoint
+     * @param mixed  $params
+     * @param array  $options
+     *
+     * @return string
+     */
+    protected function post($endpoint, $params = [], ...$options)
+    {
+        $options = isset($options[0]) ? $options[0] : [];
+
+        if (!is_array($params)) {
+            $options['body'] = $params;
+        } else {
+            $options['form_params'] = $params;
+        }
+
+        return $this->request('post', $endpoint, $options);
+    }
+
+    /**
+     * Make a http request.
+     *
+     * @param string $method
+     * @param string $endpoint
+     * @param array  $options  http://docs.guzzlephp.org/en/latest/request-options.html
+     *
+     * @return array
+     */
+    protected function request($method, $endpoint, $options = [])
+    {
+        return $this->unwrapResponse($this->getHttpClient($this->getBaseOptions())->{$method}($endpoint, $options));
+    }
+
+    /**
+     * Return base Guzzle options.
+     *
+     * @return array
+     */
+    protected function getBaseOptions()
+    {
+        $options = [
+            'base_uri' => method_exists($this, 'getBaseUri') ? $this->getBaseUri() : '',
+            'timeout'  => property_exists($this, 'timeout') ? $this->timeout : 5.0,
+        ];
+
+        return $options;
+    }
+
+    /**
+     * Return http client.
+     *
+     * @param array $options
+     *
+     * @return \GuzzleHttp\Client
+     */
+    protected function getHttpClient(array $options = [])
+    {
+        return new Client($options);
+    }
+
+    /**
+     * Convert response contents to json.
+     *
+     * @param \Psr\Http\Message\ResponseInterface $response
+     *
+     * @return array
+     */
+    protected function unwrapResponse(ResponseInterface $response)
+    {
+        $contentType = $response->getHeaderLine('Content-Type');
+        $contents = $response->getBody()->getContents();
+
+        if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) {
+            return json_decode($contents, true);
+        } elseif (false !== stripos($contentType, 'xml')) {
+            return json_decode(json_encode(simplexml_load_string($contents)), true);
+        }
+
+        return $contents;
+    }
+}

+ 86 - 0
addons/epay/view/api/wechat.html

@@ -0,0 +1,86 @@
+<link rel="stylesheet" href="__ADDON__/css/wechat.css" />
+
+{if $type=='jsapi'}
+<div class="container">
+    <div class="row" style="margin-top:20px;">
+        <div class="col-xs-12">
+            <button type="button" class="btn btn-success btn-lg btn-block">正在发起微信支付</button>
+            <button type="button" class="btn btn-default btn-lg btn-block" onclick="location.href='{$orderData.returnurl}'">如果页面未自动跳转</button>
+        </div>
+    </div>
+</div>
+<script>
+    function onBridgeReady(){
+        WeixinJSBridge.invoke(
+            'getBrandWCPayRequest', {$payData|json_encode},
+            function(res){
+                if (res.err_msg == "get_brand_wcpay_request:ok") {
+                    layer.msg('支付成功!');
+                } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
+                    layer.msg('您取消了支付');
+                } else if (res.err_msg == "get_brand_wcpay_request:fail") {
+                    layer.msg('支付失败');
+                }
+                setTimeout(function () {
+                    location.href = '{$orderData.returnurl}';
+                }, 1500);
+            });
+    }
+    if (typeof WeixinJSBridge == "undefined"){
+        if( document.addEventListener ){
+            document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
+        }else if (document.attachEvent){
+            document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
+            document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
+        }
+    }else{
+        onBridgeReady();
+    }
+</script>
+{elseif $type=='pc' /}
+<div class="container">
+    <div class="wechat">
+        <div class="row">
+            <div class="col-xs-12 col-sm-12">
+                <h2>
+                    <img src="__ADDON__/images/logo-wechat.png" alt="" height="32" class="pull-left" style="margin-right:5px;"> 微信支付
+                    <div class="wechat-time">
+                        请在 <span>60</span> 秒内完成支付
+                    </div>
+                </h2>
+
+                <div class="row">
+                    <div class="col-xs-12 col-sm-5">
+                        <div class="wechat-body">
+                            <div class="wechat-order clearfix">
+                                <p>订单标题:<em>{$data.body}</em></p>
+                                <p>订单编号:<em>{$data.out_trade_no}</em></p>
+                                <p>订单价格:<em class="wechat-price">¥{$data.total_fee/100}</em> 元</p>
+                            </div>
+                            <div class="wechat-qrcode">
+                                <img src="{:addon_url('epay/api/qrcode',[],false)}?text={$data.code_url}">
+                                <div class="expired hidden"></div>
+                                <div class="paid hidden"></div>
+                            </div>
+                            <div class="wechat-tips">
+                                <p>请使用微信扫一扫<br>扫描二维码支付</p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-sm-1"></div>
+                    <div class="col-sm-6 hidden-xs">
+                        <div class="wechat-scan">
+                            <img src="__ADDON__/images/tips.png" class="img-responsive" alt=""/>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
+
+<script>
+    var queryParams = {$data|json_encode};
+</script>
+{/if}

+ 212 - 0
addons/epay/view/index/index.html

@@ -0,0 +1,212 @@
+<!-- Header Carousel -->
+<header id="myCarousel" class="carousel slide">
+    <!-- Indicators -->
+    <ol class="carousel-indicators">
+        <li data-target="#myCarousel" data-slide-to="0" class="active"></li>
+        <li data-target="#myCarousel" data-slide-to="1"></li>
+        <li data-target="#myCarousel" data-slide-to="2"></li>
+        <li data-target="#myCarousel" data-slide-to="3"></li>
+    </ol>
+
+    <!-- Wrapper for slides -->
+    <div class="carousel-inner">
+        <div class="item active">
+            <a href="https://www.fastadmin.net/store/epay.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=9b59b6');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">微信支付宝整合插件</h1>
+                        <h2 class="display-4 text-white">打通微信、支付宝付款功能,支持CMS、余额充值、知识问答插件</h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+        <div class="item">
+            <a href="https://www.fastadmin.net/store/cms.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=3498db');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">CMS内容管理系统(含小程序)</h1>
+                        <h2 class="display-4 text-white">简单强大的内容管理系统,支持付费阅读、可自定义内容模型、标签、伪静态、区块、个性化模板等功能<br>包含完整的小程序CMS客户端,拥有完善的资讯模块、产品模块、评论模块、会员模块
+                        </h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+        <div class="item">
+            <a href="https://www.fastadmin.net/store/ask.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=3498db');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">知识问答系统</h1>
+                        <h2 class="display-4 text-white">简单强大的问答社区,拥有强大的问答模块、付费问答、话题归类、付费阅读</h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+        <div class="item">
+            <a href="https://www.fastadmin.net/store/blog.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=2ecc71');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">简单博客(含小程序)</h1>
+                        <h2 class="display-4 text-white">响应式博客插件,包含日志、评论、分类、归档,包含完善的后台管理和前台功能<br/>包含完整的小程序博客客户端,拥有博客日志列表、日志详情、评论等功能
+                        </h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+        <div class="item">
+            <a href="https://www.fastadmin.net/store/docs.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=9c88ff');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">Markdown文档生成插件</h1>
+                        <h2 class="display-4 text-white">将Github或本地Git环境中的Markdown文件解析并生成HTML,可在线浏览或导出为HTML离线浏览</h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+    </div>
+</header>
+
+<!-- Page Content -->
+<div class="container">
+
+    <div class="row">
+        <div class="col-lg-12">
+            <h2 class="page-header">
+                开始接入
+            </h2>
+        </div>
+        <div class="col-md-4">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h4><i class="fa fa-fw fa-check"></i> 准备工作</h4>
+                </div>
+                <div class="panel-body">
+                    <p><a href="https://b.alipay.com/" target="_blank">申请支付宝相应的支付产品,并获取相关配置</a></p>
+                    <p><a href="https://pay.weixin.qq.com" target="_blank">申请微信相应的支付产品,并获取相关配置</a></p>
+                    <p>插件管理中配置相应的微信或支付宝参数</p>
+                    <a href="https://www.fastadmin.net/store/epay.html" target="_blank" class="btn btn-success"><i class="fa fa-download"></i> 下载插件</a>
+                </div>
+            </div>
+        </div>
+        <div class="col-md-4">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h4><i class="fa fa-fw fa-gift"></i> 开发工作</h4>
+                </div>
+                <div class="panel-body">
+                    <p>在你的PHP代码中调用相关代码进行支付,请参考控制器代码</p>
+                    <p>目前FastAdmin中已经支持
+                        <a href="https://www.fastadmin.net/store/cms.html" target="_blank">CMS内容管理系统</a>、
+                        <a href="https://www.fastadmin.net/store/recharge.html" target="_blank">会员充值插件</a>、
+                        <a href="https://www.fastadmin.net/store/ask.html" target="_blank">知识问答系统</a>
+                        支持无缝接入此插件</p>
+                    <a href="https://www.fastadmin.net/store/epay.html" target="_blank" class="btn btn-success"><i class="fa fa-list"></i> 查看文档</a>
+                </div>
+            </div>
+        </div>
+        <div class="col-md-4">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h4><i class="fa fa-fw fa-compass"></i> 立即体验</h4>
+                </div>
+                <div class="panel-body">
+                    <p>请选择对应的支付金额和支付方式</p>
+                    <p>
+                        <span class="input-group">
+                            <input type="number" name="amount" step="2" value="{:mt_rand(1, 99)/100}"
+                                   class="form-control" placeholder="请输入一个随机金额"/>
+                            <span class="input-group-addon" style="padding:0;width:100px;">
+                                <select class="form-control" name="method" id="method" style="border:none;height: 32px;">
+                                    <option value="web">PC网页支付</option>
+                                    <option value="wap">H5手机网页支付</option>
+                                    <option value="app">APP支付</option>
+                                    <option value="scan">扫码支付</option>
+                                    <option value="mp">公众号支付(不支持支付宝)</option>
+                                    <option value="miniapp">小程序支付(不支持支付宝)</option>
+                                </select>
+                            </span>
+                        </span>
+                    </p>
+                    <button data-type="alipay" class="btn btn-info btn-experience"><i class="fa fa-money"></i> 支付宝支付</button>
+                    <button data-type="wechat" class="btn btn-success btn-experience"><i class="fa fa-wechat"></i> 微信支付</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!-- /.row -->
+
+    <div class="row addonlist">
+        <div class="col-lg-12">
+            <h2 class="page-header">模块&插件</h2>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/blog.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/blog.png"
+                     alt="">
+                <p>简单博客(含小程序)</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/cms.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/cms.png"
+                     alt="">
+                <p>CMS内容管理系统(含小程序)</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/ask.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/ask.png" alt="">
+                <p>知识付费问答社区</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/docs.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/docs.png"
+                     alt="">
+                <p>文档生成模块</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/filemanager.html" target="_blank">
+                <img class="img-responsive img-addon img-hover"
+                     src="https://cdn.fastadmin.net/uploads/addons/filemanager.png" alt="">
+                <p>文件管理器插件</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/qiniu.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/qiniu.png"
+                     alt="">
+                <p>七牛上传插件</p>
+            </a>
+        </div>
+    </div>
+    <!-- /.row -->
+
+    <hr>
+
+    <!-- Call to Action Section -->
+    <div class="well">
+        <div class="row">
+            <div class="col-md-8">
+                <p>感谢你对FastAdmin的支持!如果你在使用FastAdmin开发插件的过程中有任何疑问或需要寻求帮助,请前往FastAdmin交流社区与小伙伴们一起交流。</p>
+            </div>
+            <div class="col-md-4">
+                <a class="btn btn-lg btn-default btn-block" href="https://forum.fastadmin.net"
+                   target="_blank">立即前往社区</a>
+            </div>
+        </div>
+    </div>
+
+    <hr>
+
+</div>

+ 115 - 0
addons/epay/view/layout/default.html

@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="description" content="">
+    <meta name="author" content="">
+
+    <title>{$title} - FastAdmin</title>
+
+    <!-- Bootstrap Core CSS -->
+    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+
+    <!-- Custom CSS -->
+    <link href="__ADDON__/css/common.css" rel="stylesheet">
+
+    <!-- Plugin CSS -->
+    <link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
+    <link href="https://cdn.staticfile.org/simple-line-icons/2.4.1/css/simple-line-icons.min.css" rel="stylesheet">
+
+    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
+    <!--[if lt IE 9]>
+    <script src="https://cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
+    <script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
+    <![endif]-->
+
+</head>
+
+<body>
+
+<!-- Navigation -->
+<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+    <div class="container">
+        <!-- Brand and toggle get grouped for better mobile display -->
+        <div class="navbar-header">
+            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+                <span class="sr-only">Toggle navigation</span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </button>
+            <a class="navbar-brand" href="{:addon_url('epay/index/index')}">FastAdmin</a>
+        </div>
+        <!-- Collect the nav links, forms, and other content for toggling -->
+        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+            <ul class="nav navbar-nav navbar-right">
+                <li>
+                </li>
+                {if $user}
+                <li class="dropdown">
+                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">欢迎你! {$user.nickname}<b class="caret"></b></a>
+                    <ul class="dropdown-menu">
+                        <li>
+                            <a href="{:url('index/user/index')}">会员中心</a>
+                        </li>
+                        <li>
+                            <a href="{:url('index/user/profile')}">个人资料</a>
+                        </li>
+                        <li>
+                            <a href="{:url('index/user/logout')}">退出登录</a>
+                        </li>
+                    </ul>
+                </li>
+                {else /}
+                <li class="dropdown">
+                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">会员中心 <b class="caret"></b></a>
+                    <ul class="dropdown-menu">
+                        <li>
+                            <a href="{:url('index/user/login')}">登录</a>
+                        </li>
+                        <li>
+                            <a href="{:url('index/user/register')}">注册</a>
+                        </li>
+                    </ul>
+                </li>
+                {/if}
+            </ul>
+        </div>
+        <!-- /.navbar-collapse -->
+    </div>
+    <!-- /.container -->
+</nav>
+
+{__CONTENT__}
+
+<div class="container">
+    <!-- Footer -->
+    <footer>
+        <div class="row">
+            <div class="col-lg-12">
+                <hr>
+                <p>Copyright &copy; <a href="https://www.fastadmin.net">FastAdmin</a> 2017-1019</p>
+            </div>
+        </div>
+    </footer>
+
+</div>
+<!-- /.container -->
+
+<!-- jQuery -->
+<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
+
+<!-- Bootstrap Core JavaScript -->
+<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
+
+<script src="__CDN__/assets/libs/fastadmin-layer/dist/layer.js"></script>
+
+<script src="__ADDON__/js/common.js"></script>
+
+</body>
+
+</html>

+ 183 - 0
addons/example/Example.php

@@ -0,0 +1,183 @@
+<?php
+
+namespace addons\example;
+
+use app\common\library\Menu;
+use think\Addons;
+
+/**
+ * Example
+ */
+class Example extends Addons
+{
+
+    /**
+     * 插件安装方法
+     * @return bool
+     */
+    public function install()
+    {
+        $menu = [
+            [
+                'name'    => 'example',
+                'title'   => '开发示例管理',
+                'icon'    => 'fa fa-magic',
+                'sublist' => [
+                    [
+                        'name'    => 'example/bootstraptable',
+                        'title'   => '表格完整示例',
+                        'icon'    => 'fa fa-table',
+                        'sublist' => [
+                            ['name' => 'example/bootstraptable/index', 'title' => '查看'],
+                            ['name' => 'example/bootstraptable/detail', 'title' => '详情'],
+                            ['name' => 'example/bootstraptable/change', 'title' => '变更'],
+                            ['name' => 'example/bootstraptable/del', 'title' => '删除'],
+                            ['name' => 'example/bootstraptable/multi', 'title' => '批量更新'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/customsearch',
+                        'title'   => '自定义搜索',
+                        'icon'    => 'fa fa-table',
+                        'sublist' => [
+                            ['name' => 'example/customsearch/index', 'title' => '查看'],
+                            ['name' => 'example/customsearch/del', 'title' => '删除'],
+                            ['name' => 'example/customsearch/multi', 'title' => '批量更新'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/customform',
+                        'title'   => '自定义表单示例',
+                        'icon'    => 'fa fa-edit',
+                        'sublist' => [
+                            ['name' => 'example/customform/index', 'title' => '查看'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/tablelink',
+                        'title'   => '表格联动示例',
+                        'icon'    => 'fa fa-table',
+                        'remark'  => '点击左侧日志列表,右侧的表格数据会显示指定管理员的日志列表',
+                        'sublist' => [
+                            ['name' => 'example/tablelink/index', 'title' => '查看'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/colorbadge',
+                        'title'   => '彩色角标',
+                        'icon'    => 'fa fa-table',
+                        'remark'  => '左侧彩色的角标会根据当前数据量的大小进行更新',
+                        'sublist' => [
+                            ['name' => 'example/colorbadge/index', 'title' => '查看'],
+                            ['name' => 'example/colorbadge/del', 'title' => '删除'],
+                            ['name' => 'example/colorbadge/multi', 'title' => '批量更新'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/controllerjump',
+                        'title'   => '控制器间跳转',
+                        'icon'    => 'fa fa-table',
+                        'remark'  => '点击IP地址可以跳转到新的选项卡中查看指定IP的数据',
+                        'sublist' => [
+                            ['name' => 'example/controllerjump/index', 'title' => '查看'],
+                            ['name' => 'example/controllerjump/del', 'title' => '删除'],
+                            ['name' => 'example/controllerjump/multi', 'title' => '批量更新'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/cxselect',
+                        'title'   => '多级联动',
+                        'icon'    => 'fa fa-table',
+                        'remark'  => '基于jquery.cxselect实现的多级联动',
+                        'sublist' => [
+                            ['name' => 'example/cxselect/index', 'title' => '查看'],
+                            ['name' => 'example/cxselect/del', 'title' => '删除'],
+                            ['name' => 'example/cxselect/multi', 'title' => '批量更新'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/multitable',
+                        'title'   => '多表格示例',
+                        'icon'    => 'fa fa-table',
+                        'remark'  => '展示在一个页面显示多个Bootstrap-table表格',
+                        'sublist' => [
+                            ['name' => 'example/multitable/index', 'title' => '查看'],
+                            ['name' => 'example/multitable/del', 'title' => '删除'],
+                            ['name' => 'example/multitable/multi', 'title' => '批量更新'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/relationmodel',
+                        'title'   => '关联模型示例',
+                        'icon'    => 'fa fa-table',
+                        'remark'  => '列表中的头像、用户名和昵称字段均从关联表中取出',
+                        'sublist' => [
+                            ['name' => 'example/relationmodel/index', 'title' => '查看'],
+                            ['name' => 'example/relationmodel/del', 'title' => '删除'],
+                            ['name' => 'example/relationmodel/multi', 'title' => '批量更新'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/tabletemplate',
+                        'title'   => '表格模板示例',
+                        'icon'    => 'fa fa-table',
+                        'remark'  => '',
+                        'sublist' => [
+                            ['name' => 'example/tabletemplate/index', 'title' => '查看'],
+                            ['name' => 'example/tabletemplate/detail', 'title' => '详情'],
+                            ['name' => 'example/tabletemplate/del', 'title' => '删除'],
+                            ['name' => 'example/tabletemplate/multi', 'title' => '批量更新'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/baidumap',
+                        'title'   => '百度地图示例',
+                        'icon'    => 'fa fa-map-pin',
+                        'sublist' => [
+                            ['name' => 'example/baidumap/index', 'title' => '查看'],
+                            ['name' => 'example/baidumap/map', 'title' => '详情'],
+                            ['name' => 'example/baidumap/del', 'title' => '删除'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'example/echarts',
+                        'title'   => '统计图表示例',
+                        'icon'    => 'fa fa-bar-chart',
+                        'sublist' => [
+                            ['name' => 'example/echarts/index', 'title' => '查看'],
+                        ]
+                    ],
+                ]
+            ]
+        ];
+        Menu::create($menu);
+        return true;
+    }
+
+    /**
+     * 插件卸载方法
+     * @return bool
+     */
+    public function uninstall()
+    {
+        Menu::delete('example');
+        return true;
+    }
+
+    /**
+     * 插件启用方法
+     */
+    public function enable()
+    {
+        Menu::enable('example');
+    }
+
+    /**
+     * 插件禁用方法
+     */
+    public function disable()
+    {
+        Menu::disable('example');
+    }
+
+}

+ 39 - 0
addons/example/application/admin/controller/example/Baidumap.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 百度地图
+ *
+ * @icon fa fa-map
+ * @remark 可以搜索百度位置,调用百度地图的相关API
+ */
+class Baidumap extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+
+    /**
+     * 查找地图
+     */
+    public function map()
+    {
+        return $this->view->fetch();
+    }
+
+    /**
+     * 搜索列表
+     */
+    public function selectpage()
+    {
+        $this->model = model('Area');
+        return parent::selectpage();
+    }
+}

+ 131 - 0
addons/example/application/admin/controller/example/Bootstraptable.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 表格完整示例
+ *
+ * @icon fa fa-table
+ * @remark 在使用Bootstrap-table中的常用方式,更多使用方式可查看:http://bootstrap-table.wenzhixin.net.cn/zh-cn/
+ */
+class Bootstraptable extends Backend
+{
+    protected $model = null;
+    /**
+     * 无需鉴权的方法(需登录)
+     * @var array
+     */
+    protected $noNeedRight = ['start', 'pause', 'change', 'detail', 'cxselect', 'searchlist'];
+    /**
+     * 快捷搜索的字段
+     * @var string
+     */
+    protected $searchFields = 'id,title,url';
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams(null);
+            $total = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->count();
+            $list = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+            $result = array("total" => $total, "rows" => $list, "extend" => ['money' => mt_rand(100000, 999999), 'price' => 200]);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 详情
+     */
+    public function detail($ids)
+    {
+        $row = $this->model->get(['id' => $ids]);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        if ($this->request->isAjax()) {
+            $this->success("Ajax请求成功", null, ['id' => $ids]);
+        }
+        $this->view->assign("row", $row->toArray());
+        return $this->view->fetch();
+    }
+
+    /**
+     * 启用
+     */
+    public function start($ids = '')
+    {
+        $this->success("模拟启动成功");
+    }
+
+    /**
+     * 暂停
+     */
+    public function pause($ids = '')
+    {
+        $this->success("模拟暂停成功");
+    }
+
+    /**
+     * 切换
+     */
+    public function change($ids = '')
+    {
+        //你需要在此做具体的操作逻辑
+
+        $this->success("模拟切换成功");
+    }
+
+    /**
+     * 联动搜索
+     */
+    public function cxselect()
+    {
+        $type = $this->request->get('type');
+        $group_id = $this->request->get('group_id');
+        $list = null;
+        if ($group_id !== '') {
+            if ($type == 'group') {
+                $groupIds = $this->auth->getChildrenGroupIds(true);
+                $list = \app\admin\model\AuthGroup::where('id', 'in', $groupIds)->field('id as value, name')->select();
+            } else {
+                $adminIds = \app\admin\model\AuthGroupAccess::where('group_id', 'in', $group_id)->column('uid');
+                $list = \app\admin\model\Admin::where('id', 'in', $adminIds)->field('id as value, username AS name')->select();
+            }
+        }
+        $this->success('', null, $list);
+    }
+
+    /**
+     * 搜索下拉列表
+     */
+    public function searchlist()
+    {
+        $result = $this->model->limit(10)->select();
+        $searchlist = [];
+        foreach ($result as $key => $value) {
+            $searchlist[] = ['id' => $value['url'], 'name' => $value['url']];
+        }
+        $data = ['searchlist' => $searchlist];
+        $this->success('', null, $data);
+    }
+}

+ 22 - 0
addons/example/application/admin/controller/example/Colorbadge.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 彩色角标
+ *
+ * @icon fa fa-table
+ * @remark 在JS端控制角标的显示与隐藏,请注意左侧菜单栏角标的数值变化
+ */
+class Colorbadge extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+}

+ 22 - 0
addons/example/application/admin/controller/example/Controllerjump.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 控制器间跳转
+ *
+ * @icon fa fa-table
+ * @remark FastAdmin支持在控制器间跳转,点击后将切换到另外一个TAB中,无需刷新当前页面
+ */
+class Controllerjump extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+}

+ 30 - 0
addons/example/application/admin/controller/example/Customform.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 自定义表单示例
+ *
+ * @icon   fa fa-table
+ * @remark FastAdmin支持在控制器间跳转,点击后将切换到另外一个TAB中,无需刷新当前页面
+ */
+class Customform extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+
+    public function index()
+    {
+        if ($this->request->isPost()) {
+            $this->success("提交成功", null, ['data' => json_encode($this->request->post("row/a"), JSON_UNESCAPED_UNICODE)]);
+        }
+        return $this->view->fetch();
+    }
+}

+ 24 - 0
addons/example/application/admin/controller/example/Customsearch.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 自定义搜索
+ *
+ * @icon fa fa-search
+ * @remark 自定义列表的搜索
+ */
+class Customsearch extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+        $ipList = $this->model->whereTime('createtime', '-37 days')->group("ip")->column("ip,ip as aa");
+        $this->view->assign("ipList", $ipList);
+    }
+}

+ 21 - 0
addons/example/application/admin/controller/example/Cxselect.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 多级联动
+ *
+ * @icon fa fa-table
+ * @remark FastAdmin使用了jQuery-cxselect实现多级联动,更多文档请参考https://github.com/karsonzhang/cxSelect
+ */
+class Cxselect extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+    }
+}

+ 44 - 0
addons/example/application/admin/controller/example/Echarts.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 统计图表示例
+ *
+ * @icon   fa fa-charts
+ * @remark 展示在FastAdmin中使用Echarts展示丰富多彩的统计图表
+ */
+class Echarts extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+
+        return $this->view->fetch();
+    }
+
+    /**
+     * 详情
+     */
+    public function detail($ids)
+    {
+        $row = $this->model->get(['id' => $ids]);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $this->view->assign("row", $row->toArray());
+        return $this->view->fetch();
+    }
+}

+ 90 - 0
addons/example/application/admin/controller/example/Multitable.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 多表格示例
+ *
+ * @icon fa fa-table
+ * @remark 当一个页面上存在多个Bootstrap-table时该如何控制按钮和表格
+ */
+class Multitable extends Backend
+{
+    protected $model = null;
+    protected $noNeedRight = ['table1', 'table2'];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        $this->loadlang('general/attachment');
+        $this->loadlang('general/crontab');
+        return $this->view->fetch();
+    }
+
+    public function table1()
+    {
+        $this->model = model('Attachment');
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                    ->where($where)
+                    ->order($sort, $order)
+                    ->count();
+
+            $list = $this->model
+                    ->where($where)
+                    ->order($sort, $order)
+                    ->limit($offset, $limit)
+                    ->select();
+
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch('index');
+    }
+
+    public function table2()
+    {
+        $this->model = model('AdminLog');
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                    ->where($where)
+                    ->order($sort, $order)
+                    ->count();
+
+            $list = $this->model
+                    ->where($where)
+                    ->order($sort, $order)
+                    ->limit($offset, $limit)
+                    ->select();
+
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch('index');
+    }
+}

+ 49 - 0
addons/example/application/admin/controller/example/Relationmodel.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 关联模型
+ *
+ * @icon fa fa-table
+ * @remark 当使用到关联模型时需要重载index方法
+ */
+class Relationmodel extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        $this->relationSearch = true;
+        $this->searchFields = "admin.username,id";
+        if ($this->request->isAjax()) {
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                    ->with("admin")
+                    ->where($where)
+                    ->order($sort, $order)
+                    ->count();
+            $list = $this->model
+                    ->with("admin")
+                    ->where($where)
+                    ->order($sort, $order)
+                    ->limit($offset, $limit)
+                    ->select();
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+}

+ 81 - 0
addons/example/application/admin/controller/example/Tablelink.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 表格联动
+ *
+ * @icon fa fa-table
+ */
+class Tablelink extends Backend
+{
+    protected $model = null;
+    protected $noNeedRight = ['table1', 'table2'];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+
+
+    public function table1()
+    {
+        $this->model = model('Admin');
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->count();
+
+            $list = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch('index');
+    }
+
+    public function table2()
+    {
+        $this->model = model('AdminLog');
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->count();
+
+            $list = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch('index');
+    }
+}

+ 58 - 0
addons/example/application/admin/controller/example/Tabletemplate.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace app\admin\controller\example;
+
+use app\common\controller\Backend;
+
+/**
+ * 表格模板示例
+ *
+ * @icon   fa fa-table
+ * @remark 可以通过使用表格模板将表格中的行渲染成一样的展现方式,基于此功能可以任意定制自己想要的展示列表
+ */
+class Tabletemplate extends Backend
+{
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams(null);
+            $total = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->count();
+            $list = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 详情
+     */
+    public function detail($ids)
+    {
+        $row = $this->model->get(['id' => $ids]);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $this->view->assign("row", $row->toArray());
+        return $this->view->fetch();
+    }
+}

+ 15 - 0
addons/example/application/admin/model/Area.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Model;
+
+class Area extends Model
+{
+
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = false;
+    // 定义时间戳字段名
+    protected $createTime = false;
+    protected $updateTime = false;
+}

+ 21 - 0
addons/example/application/admin/view/example/baidumap/index.html

@@ -0,0 +1,21 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh,delete')}
+                        <a href="example/baidumap/map" class="btn btn-info btn-dialog" title="地图"><i class="fa fa-map-signs"></i> 地图</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover" width="100%">
+
+                    </table>
+
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 30 - 0
addons/example/application/admin/view/example/baidumap/map.html

@@ -0,0 +1,30 @@
+{__NOLAYOUT__}
+<!DOCTYPE html>
+<html lang="{$config.language}">
+    <head>
+        {include file="common/meta" /}
+        <style type="text/css">
+            body, html,#allmap{width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}
+            #search{position:absolute;top:20px;left:20px;}
+        </style>
+    </head>
+
+    <body class="inside-header inside-aside {:defined('IS_DIALOG') && IS_DIALOG ? 'is-dialog' : ''}">
+        <div class="container-fluid" id="search">
+            <div class="row">
+                <div class="col-xs-12 col-sm-4">
+                    <form role="form" action="">
+                        <div class="input-group" style="width:300px;">
+                            <input type="text" id="searchaddress" class="form-control selectpage" data-primary-key="name" data-source="example/baidumap/selectpage" />
+                            <span class="input-group-btn">
+                                <button class="btn btn-success btn-search" type="button">搜索</button>
+                            </span>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+        <div id='allmap'></div>
+        {include file="common/script" /}
+    </body>
+</html>

+ 33 - 0
addons/example/application/admin/view/example/bootstraptable/detail.html

@@ -0,0 +1,33 @@
+<table class="table table-striped">
+    <thead>
+        <tr>
+            <th>{:__('Title')}</th>
+            <th>{:__('Content')}</th>
+        </tr>
+    </thead>
+    <tbody>
+        {volist name="row" id="vo"  }
+        <tr>
+            <td>{$key}</td>
+            <td>{$vo}</td>
+        </tr>
+        {/volist}
+        {if $Think.get.dialog}
+        <tr>
+            <td>回传数据</td>
+            <td>
+                <div class="input-group">
+                    <input name="callback" class="form-control" value="test" />
+                    <span class="input-group-btn"><a href="javascript:;" class="btn btn-success btn-callback" >回传数据</a></span>
+                </div>
+            </td>
+        </tr>
+        {/if}
+    </tbody>
+</table>
+<div class="hide layer-footer">
+    <label class="control-label col-xs-12 col-sm-2"></label>
+    <div class="col-xs-12 col-sm-8">
+        <button type="reset" class="btn btn-primary btn-embossed btn-close" onclick="Layer.closeAll();">{:__('Close')}</button>
+    </div>
+</div>

+ 49 - 0
addons/example/application/admin/view/example/bootstraptable/index.html

@@ -0,0 +1,49 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh,delete')}
+                        <a class="btn btn-info btn-disabled disabled btn-selected" href="javascript:;"><i class="fa fa-leaf"></i> 获取选中项</a>
+                        <div class="dropdown btn-group">
+                            <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> <?= __('More') ?></a>
+                            <ul class="dropdown-menu text-left" role="menu">
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
+                            </ul>
+                        </div>
+                        <a class="btn btn-success btn-singlesearch" href="javascript:;"><i class="fa fa-user"></i> 自定义搜索</a>
+                        <a class="btn btn-success btn-change btn-start" data-params="action=start" data-url="example/bootstraptable/start" href="javascript:;"><i class="fa fa-play"></i> 启动</a>
+                        <a class="btn btn-danger btn-change btn-pause" data-params="action=pause" data-url="example/bootstraptable/pause" href="javascript:;"><i class="fa fa-pause"></i> 暂停</a>
+                        <a href="javascript:;" class="btn btn-default" style="font-size:14px;color:dodgerblue;">
+                            <i class="fa fa-dollar"></i>
+                            <span class="extend">
+                                金额:<span id="money">0</span>
+                                单价:<span id="price">0</span>
+                            </span>
+                        </a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover" width="100%">
+
+                    </table>
+
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script id="categorytpl" type="text/html">
+    <div class="row">
+        <div class="col-xs-12">
+            <div class="form-inline" data-toggle="cxselect" data-selects="group,admin">
+                <select class="group form-control" name="group" data-url="example/bootstraptable/cxselect?type=group"></select>
+                <select class="admin form-control" name="admin_id" data-url="example/bootstraptable/cxselect?type=admin" data-query-name="group_id"></select>
+                <input type="hidden" class="operate" data-name="admin_id" value="=" />
+            </div>
+        </div>
+    </div>
+</script>

+ 21 - 0
addons/example/application/admin/view/example/colorbadge/index.html

@@ -0,0 +1,21 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh,delete')}
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover" width="100%">
+
+                    </table>
+
+
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 21 - 0
addons/example/application/admin/view/example/controllerjump/index.html

@@ -0,0 +1,21 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh,delete')}
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover" width="100%">
+
+                    </table>
+
+
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

Some files were not shown because too many files changed in this diff