作者 景龙

增加废品回收文件

正在显示 72 个修改的文件 包含 4784 行增加1 行删除

要显示太多修改。

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

  1 +{
  2 + "directory" : "public/assets/libs",
  3 + "ignoredDependencies": [
  4 + "file-saver",
  5 + "html2canvas",
  6 + "jspdf",
  7 + "jspdf-autotable"
  8 + ]
  9 +}
  1 +[app]
  2 +debug = false
  3 +trace = false
  4 +
  5 +[database]
  6 +hostname = 127.0.0.1
  7 +database = fastadmin
  8 +username = root
  9 +password = root
  10 +hostport = 3306
  11 +prefix = fa_
  1 +.idea/
  2 +runtime/cache
  3 +runtime/log
  1 +Apache License
  2 +Version 2.0, January 2004
  3 +http://www.apache.org/licenses/
  4 +
  5 +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 +
  7 +1. Definitions.
  8 +
  9 +"License" shall mean the terms and conditions for use, reproduction, and
  10 +distribution as defined by Sections 1 through 9 of this document.
  11 +
  12 +"Licensor" shall mean the copyright owner or entity authorized by the copyright
  13 +owner that is granting the License.
  14 +
  15 +"Legal Entity" shall mean the union of the acting entity and all other entities
  16 +that control, are controlled by, or are under common control with that entity.
  17 +For the purposes of this definition, "control" means (i) the power, direct or
  18 +indirect, to cause the direction or management of such entity, whether by
  19 +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
  20 +outstanding shares, or (iii) beneficial ownership of such entity.
  21 +
  22 +"You" (or "Your") shall mean an individual or Legal Entity exercising
  23 +permissions granted by this License.
  24 +
  25 +"Source" form shall mean the preferred form for making modifications, including
  26 +but not limited to software source code, documentation source, and configuration
  27 +files.
  28 +
  29 +"Object" form shall mean any form resulting from mechanical transformation or
  30 +translation of a Source form, including but not limited to compiled object code,
  31 +generated documentation, and conversions to other media types.
  32 +
  33 +"Work" shall mean the work of authorship, whether in Source or Object form, made
  34 +available under the License, as indicated by a copyright notice that is included
  35 +in or attached to the work (an example is provided in the Appendix below).
  36 +
  37 +"Derivative Works" shall mean any work, whether in Source or Object form, that
  38 +is based on (or derived from) the Work and for which the editorial revisions,
  39 +annotations, elaborations, or other modifications represent, as a whole, an
  40 +original work of authorship. For the purposes of this License, Derivative Works
  41 +shall not include works that remain separable from, or merely link (or bind by
  42 +name) to the interfaces of, the Work and Derivative Works thereof.
  43 +
  44 +"Contribution" shall mean any work of authorship, including the original version
  45 +of the Work and any modifications or additions to that Work or Derivative Works
  46 +thereof, that is intentionally submitted to Licensor for inclusion in the Work
  47 +by the copyright owner or by an individual or Legal Entity authorized to submit
  48 +on behalf of the copyright owner. For the purposes of this definition,
  49 +"submitted" means any form of electronic, verbal, or written communication sent
  50 +to the Licensor or its representatives, including but not limited to
  51 +communication on electronic mailing lists, source code control systems, and
  52 +issue tracking systems that are managed by, or on behalf of, the Licensor for
  53 +the purpose of discussing and improving the Work, but excluding communication
  54 +that is conspicuously marked or otherwise designated in writing by the copyright
  55 +owner as "Not a Contribution."
  56 +
  57 +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
  58 +of whom a Contribution has been received by Licensor and subsequently
  59 +incorporated within the Work.
  60 +
  61 +2. Grant of Copyright License.
  62 +
  63 +Subject to the terms and conditions of this License, each Contributor hereby
  64 +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
  65 +irrevocable copyright license to reproduce, prepare Derivative Works of,
  66 +publicly display, publicly perform, sublicense, and distribute the Work and such
  67 +Derivative Works in Source or Object form.
  68 +
  69 +3. Grant of Patent License.
  70 +
  71 +Subject to the terms and conditions of this License, each Contributor hereby
  72 +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
  73 +irrevocable (except as stated in this section) patent license to make, have
  74 +made, use, offer to sell, sell, import, and otherwise transfer the Work, where
  75 +such license applies only to those patent claims licensable by such Contributor
  76 +that are necessarily infringed by their Contribution(s) alone or by combination
  77 +of their Contribution(s) with the Work to which such Contribution(s) was
  78 +submitted. If You institute patent litigation against any entity (including a
  79 +cross-claim or counterclaim in a lawsuit) alleging that the Work or a
  80 +Contribution incorporated within the Work constitutes direct or contributory
  81 +patent infringement, then any patent licenses granted to You under this License
  82 +for that Work shall terminate as of the date such litigation is filed.
  83 +
  84 +4. Redistribution.
  85 +
  86 +You may reproduce and distribute copies of the Work or Derivative Works thereof
  87 +in any medium, with or without modifications, and in Source or Object form,
  88 +provided that You meet the following conditions:
  89 +
  90 +You must give any other recipients of the Work or Derivative Works a copy of
  91 +this License; and
  92 +You must cause any modified files to carry prominent notices stating that You
  93 +changed the files; and
  94 +You must retain, in the Source form of any Derivative Works that You distribute,
  95 +all copyright, patent, trademark, and attribution notices from the Source form
  96 +of the Work, excluding those notices that do not pertain to any part of the
  97 +Derivative Works; and
  98 +If the Work includes a "NOTICE" text file as part of its distribution, then any
  99 +Derivative Works that You distribute must include a readable copy of the
  100 +attribution notices contained within such NOTICE file, excluding those notices
  101 +that do not pertain to any part of the Derivative Works, in at least one of the
  102 +following places: within a NOTICE text file distributed as part of the
  103 +Derivative Works; within the Source form or documentation, if provided along
  104 +with the Derivative Works; or, within a display generated by the Derivative
  105 +Works, if and wherever such third-party notices normally appear. The contents of
  106 +the NOTICE file are for informational purposes only and do not modify the
  107 +License. You may add Your own attribution notices within Derivative Works that
  108 +You distribute, alongside or as an addendum to the NOTICE text from the Work,
  109 +provided that such additional attribution notices cannot be construed as
  110 +modifying the License.
  111 +You may add Your own copyright statement to Your modifications and may provide
  112 +additional or different license terms and conditions for use, reproduction, or
  113 +distribution of Your modifications, or for any such Derivative Works as a whole,
  114 +provided Your use, reproduction, and distribution of the Work otherwise complies
  115 +with the conditions stated in this License.
  116 +
  117 +5. Submission of Contributions.
  118 +
  119 +Unless You explicitly state otherwise, any Contribution intentionally submitted
  120 +for inclusion in the Work by You to the Licensor shall be under the terms and
  121 +conditions of this License, without any additional terms or conditions.
  122 +Notwithstanding the above, nothing herein shall supersede or modify the terms of
  123 +any separate license agreement you may have executed with Licensor regarding
  124 +such Contributions.
  125 +
  126 +6. Trademarks.
  127 +
  128 +This License does not grant permission to use the trade names, trademarks,
  129 +service marks, or product names of the Licensor, except as required for
  130 +reasonable and customary use in describing the origin of the Work and
  131 +reproducing the content of the NOTICE file.
  132 +
  133 +7. Disclaimer of Warranty.
  134 +
  135 +Unless required by applicable law or agreed to in writing, Licensor provides the
  136 +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
  137 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
  138 +including, without limitation, any warranties or conditions of TITLE,
  139 +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
  140 +solely responsible for determining the appropriateness of using or
  141 +redistributing the Work and assume any risks associated with Your exercise of
  142 +permissions under this License.
  143 +
  144 +8. Limitation of Liability.
  145 +
  146 +In no event and under no legal theory, whether in tort (including negligence),
  147 +contract, or otherwise, unless required by applicable law (such as deliberate
  148 +and grossly negligent acts) or agreed to in writing, shall any Contributor be
  149 +liable to You for damages, including any direct, indirect, special, incidental,
  150 +or consequential damages of any character arising as a result of this License or
  151 +out of the use or inability to use the Work (including but not limited to
  152 +damages for loss of goodwill, work stoppage, computer failure or malfunction, or
  153 +any and all other commercial damages or losses), even if such Contributor has
  154 +been advised of the possibility of such damages.
  155 +
  156 +9. Accepting Warranty or Additional Liability.
  157 +
  158 +While redistributing the Work or Derivative Works thereof, You may choose to
  159 +offer, and charge a fee for, acceptance of support, warranty, indemnity, or
  160 +other liability obligations and/or rights consistent with this License. However,
  161 +in accepting such obligations, You may act only on Your own behalf and on Your
  162 +sole responsibility, not on behalf of any other Contributor, and only if You
  163 +agree to indemnify, defend, and hold each Contributor harmless for any liability
  164 +incurred by, or claims asserted against, such Contributor by reason of your
  165 +accepting any such warranty or additional liability.
  166 +
  167 +END OF TERMS AND CONDITIONS
  168 +
  169 +APPENDIX: How to apply the Apache License to your work
  170 +
  171 +To apply the Apache License to your work, attach the following boilerplate
  172 +notice, with the fields enclosed by brackets "{}" replaced with your own
  173 +identifying information. (Don't include the brackets!) The text should be
  174 +enclosed in the appropriate comment syntax for the file format. We also
  175 +recommend that a file or class name and description of purpose be included on
  176 +the same "printed page" as the copyright notice for easier identification within
  177 +third-party archives.
  178 +
  179 + Copyright 2017 Karson
  180 +
  181 + Licensed under the Apache License, Version 2.0 (the "License");
  182 + you may not use this file except in compliance with the License.
  183 + You may obtain a copy of the License at
  184 +
  185 + http://www.apache.org/licenses/LICENSE-2.0
  186 +
  187 + Unless required by applicable law or agreed to in writing, software
  188 + distributed under the License is distributed on an "AS IS" BASIS,
  189 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  190 + See the License for the specific language governing permissions and
  191 + limitations under the License.
1 -Ϸ÷  
  1 +FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
  2 +
  3 +
  4 +## **主要特性**
  5 +
  6 +* 基于`Auth`验证的权限管理系统
  7 + * 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
  8 + * 支持单管理员多角色
  9 + * 支持管理子级数据或个人数据
  10 +* 强大的一键生成功能
  11 + * 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单等
  12 + * 一键压缩打包JS和CSS文件,一键CDN静态资源部署
  13 + * 一键生成控制器菜单和规则
  14 + * 一键生成API接口文档
  15 +* 完善的前端功能组件开发
  16 + * 基于`AdminLTE`二次开发
  17 + * 基于`Bootstrap`开发,自适应手机、平板、PC
  18 + * 基于`RequireJS`进行JS模块管理,按需加载
  19 + * 基于`Less`进行样式开发
  20 + * 基于`Bower`进行前端组件包管理
  21 +* 强大的插件扩展功能,在线安装卸载升级插件
  22 +* 通用的会员模块和API模块
  23 +* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
  24 +* 二级域名部署支持,同时域名支持绑定到插件
  25 +* 多语言支持,服务端及客户端支持
  26 +* 强大的第三方模块支持([CMS](https://www.fastadmin.net/store/cms.html)[博客](https://www.fastadmin.net/store/blog.html)[文档生成](https://www.fastadmin.net/store/docs.html))
  27 +* 整合第三方短信接口(阿里云、腾讯云短信)
  28 +* 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能
  29 +* 第三方富文本编辑器支持(Summernote、Tinymce、百度编辑器)
  30 +* 第三方登录(QQ、微信、微博)整合
  31 +* Ucenter整合第三方应用
  32 +
  33 +## **安装使用**
  34 +
  35 +https://doc.fastadmin.net
  36 +
  37 +## **在线演示**
  38 +
  39 +https://demo.fastadmin.net
  40 +
  41 +用户名:admin
  42 +
  43 +密 码:123456
  44 +
  45 +提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
  46 +
  47 +## **界面截图**
  48 +![控制台](https://gitee.com/uploads/images/2017/0411/113717_e99ff3e7_10933.png "控制台")
  49 +
  50 +## **问题反馈**
  51 +
  52 +在使用中有任何问题,请使用以下联系方式联系我们
  53 +
  54 +交流社区: https://forum.fastadmin.net
  55 +
  56 +QQ群: [636393962](https://jq.qq.com/?_wv=1027&k=487PNBb)() [708784003](https://jq.qq.com/?_wv=1027&k=5ObjtwM)(满) [964776039](https://jq.qq.com/?_wv=1027&k=59qjU2P)(3群)
  57 +
  58 +Email: (karsonzhang#163.com, 把#换成@)
  59 +
  60 +Github: https://github.com/karsonzhang/fastadmin
  61 +
  62 +Gitee: https://gitee.com/karson/fastadmin
  63 +
  64 +## **特别鸣谢**
  65 +
  66 +感谢以下的项目,排名不分先后
  67 +
  68 +ThinkPHP:http://www.thinkphp.cn
  69 +
  70 +AdminLTE:https://adminlte.io
  71 +
  72 +Bootstrap:http://getbootstrap.com
  73 +
  74 +jQuery:http://jquery.com
  75 +
  76 +Bootstrap-table:https://github.com/wenzhixin/bootstrap-table
  77 +
  78 +Nice-validator: https://validator.niceue.com
  79 +
  80 +SelectPage: https://github.com/TerryZ/SelectPage
  81 +
  82 +
  83 +## **版权信息**
  84 +
  85 +FastAdmin遵循Apache2开源协议发布,并提供免费使用。
  86 +
  87 +本项目包含的第三方源码和二进制文件之版权信息另行标注。
  88 +
  89 +版权所有Copyright © 2017-2018 by FastAdmin (https://www.fastadmin.net)
  90 +
  91 +All rights reserved。
  1 +<?php
  2 +
  3 +namespace addons\address;
  4 +
  5 +use think\Addons;
  6 +
  7 +/**
  8 + * 地址选择
  9 + * @author [MiniLing] <[laozheyouxiang@163.com]>
  10 + */
  11 +class Address extends Addons
  12 +{
  13 +
  14 + /**
  15 + * 插件安装方法
  16 + * @return bool
  17 + */
  18 + public function install()
  19 + {
  20 + return true;
  21 + }
  22 +
  23 + /**
  24 + * 插件卸载方法
  25 + * @return bool
  26 + */
  27 + public function uninstall()
  28 + {
  29 + return true;
  30 + }
  31 +
  32 +}
  1 +require([], function () {
  2 + //绑定data-toggle=addresspicker属性点击事件
  3 +
  4 + $(document).on('click', "[data-toggle='addresspicker']", function () {
  5 + var that = this;
  6 + var callback = $(that).data('callback');
  7 + var input_id = $(that).data("input-id") ? $(that).data("input-id") : "";
  8 + var lat_id = $(that).data("lat-id") ? $(that).data("lat-id") : "";
  9 + var lng_id = $(that).data("lng-id") ? $(that).data("lng-id") : "";
  10 + var lat = lat_id ? $("#" + lat_id).val() : '';
  11 + var lng = lng_id ? $("#" + lng_id).val() : '';
  12 + var url = "/addons/address/index/select";
  13 + url += (lat && lng) ? '?lat=' + lat + '&lng=' + lng : '';
  14 + Fast.api.open(url, '位置选择', {
  15 + callback: function (res) {
  16 + input_id && $("#" + input_id).val(res.address);
  17 + lat_id && $("#" + lat_id).val(res.lat);
  18 + lng_id && $("#" + lng_id).val(res.lng);
  19 + try {
  20 + //执行回调函数
  21 + if (typeof callback === 'function') {
  22 + callback.call(that, res);
  23 + }
  24 + } catch (e) {
  25 +
  26 + }
  27 + }
  28 + });
  29 + });
  30 +});
  1 +<?php
  2 +
  3 +return array (
  4 + 0 =>
  5 + array (
  6 + 'name' => 'maptype',
  7 + 'title' => '默认地图类型',
  8 + 'type' => 'radio',
  9 + 'content' =>
  10 + array (
  11 + 'baidu' => '百度地图',
  12 + 'amap' => '高德地图',
  13 + 'tencent' => '腾讯地图',
  14 + ),
  15 + 'value' => 'tencent',
  16 + 'rule' => 'required',
  17 + 'msg' => '',
  18 + 'tip' => '',
  19 + 'ok' => '',
  20 + 'extend' => '',
  21 + ),
  22 + 1 =>
  23 + array (
  24 + 'name' => 'location',
  25 + 'title' => '默认检索城市',
  26 + 'type' => 'string',
  27 + 'content' =>
  28 + array (
  29 + ),
  30 + 'value' => '北京',
  31 + 'rule' => 'required',
  32 + 'msg' => '',
  33 + 'tip' => '',
  34 + 'ok' => '',
  35 + 'extend' => '',
  36 + ),
  37 + 2 =>
  38 + array (
  39 + 'name' => 'zoom',
  40 + 'title' => '默认缩放级别',
  41 + 'type' => 'string',
  42 + 'content' =>
  43 + array (
  44 + ),
  45 + 'value' => '12',
  46 + 'rule' => 'required',
  47 + 'msg' => '',
  48 + 'tip' => '',
  49 + 'ok' => '',
  50 + 'extend' => '',
  51 + ),
  52 + 3 =>
  53 + array (
  54 + 'name' => 'lat',
  55 + 'title' => '默认Lat',
  56 + 'type' => 'string',
  57 + 'content' =>
  58 + array (
  59 + ),
  60 + 'value' => '39.919990',
  61 + 'rule' => 'required',
  62 + 'msg' => '',
  63 + 'tip' => '',
  64 + 'ok' => '',
  65 + 'extend' => '',
  66 + ),
  67 + 4 =>
  68 + array (
  69 + 'name' => 'lng',
  70 + 'title' => '默认Lng',
  71 + 'type' => 'string',
  72 + 'content' =>
  73 + array (
  74 + ),
  75 + 'value' => '116.456270',
  76 + 'rule' => 'required',
  77 + 'msg' => '',
  78 + 'tip' => '',
  79 + 'ok' => '',
  80 + 'extend' => '',
  81 + ),
  82 + 5 =>
  83 + array (
  84 + 'name' => 'baidukey',
  85 + 'title' => '百度地图KEY',
  86 + 'type' => 'string',
  87 + 'content' =>
  88 + array (
  89 + ),
  90 + 'value' => 'hAeMFHmpyHa2ZjaCH9VVridl',
  91 + 'rule' => 'required',
  92 + 'msg' => '',
  93 + 'tip' => '',
  94 + 'ok' => '',
  95 + 'extend' => '',
  96 + ),
  97 + 6 =>
  98 + array (
  99 + 'name' => 'amapkey',
  100 + 'title' => '高德地图KEY',
  101 + 'type' => 'string',
  102 + 'content' =>
  103 + array (
  104 + ),
  105 + 'value' => '608d75903d29ad471362f8c58c550daf',
  106 + 'rule' => 'required',
  107 + 'msg' => '',
  108 + 'tip' => '',
  109 + 'ok' => '',
  110 + 'extend' => '',
  111 + ),
  112 + 7 =>
  113 + array (
  114 + 'name' => 'tencentkey',
  115 + 'title' => '腾讯地图KEY',
  116 + 'type' => 'string',
  117 + 'content' =>
  118 + array (
  119 + ),
  120 + 'value' => 'P4UBZ-MAX64-LGHUG-XILUW-YHTDH-AOBZU',
  121 + 'rule' => 'required',
  122 + 'msg' => '',
  123 + 'tip' => '',
  124 + 'ok' => '',
  125 + 'extend' => '',
  126 + ),
  127 + 8 =>
  128 + array (
  129 + 'name' => '__tips__',
  130 + 'title' => '温馨提示',
  131 + 'type' => '',
  132 + 'content' =>
  133 + array (
  134 + ),
  135 + 'value' => '请先申请对应地图的Key,配置后再使用',
  136 + 'rule' => '',
  137 + 'msg' => '',
  138 + 'tip' => '',
  139 + 'ok' => '',
  140 + 'extend' => 'alert-danger-light',
  141 + ),
  142 +);
  1 +<?php
  2 +
  3 +namespace addons\address\controller;
  4 +
  5 +use think\addons\Controller;
  6 +
  7 +class Index extends Controller
  8 +{
  9 +
  10 + public function index()
  11 + {
  12 + $this->error("当前插件暂无前台页面");
  13 + }
  14 +
  15 + public function select()
  16 + {
  17 + $config = get_addon_config('address');
  18 + $lat = $this->request->get('lat', $config['lat']);
  19 + $lng = $this->request->get('lng', $config['lng']);
  20 + $this->assign('lat', $lat);
  21 + $this->assign('lng', $lng);
  22 + $this->assign('location', $config['location']);
  23 + return $this->fetch('index/' . $config['maptype']);
  24 + }
  25 +
  26 +}
  1 +name = address
  2 +title = 地址选择
  3 +intro = 地图位置选择插件,可返回地址和经纬度
  4 +author = Karson
  5 +website = http://www.fastadmin.net
  6 +version = 1.0.2
  7 +state = 1
  8 +url = /addons/address.html
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  5 + <title>地址选择器</title>
  6 + <link rel="stylesheet" href="__CDN__/assets/css/bootstrap.min.css"/>
  7 + <link rel="stylesheet" href="__CDN__/assets/css/fastadmin.min.css"/>
  8 + <link rel="stylesheet" href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css"/>
  9 + <style type="text/css">
  10 + body {
  11 + margin: 0;
  12 + padding: 0;
  13 + }
  14 +
  15 + #container {
  16 + position: absolute;
  17 + left: 0;
  18 + top: 0;
  19 + right: 0;
  20 + bottom: 0;
  21 + }
  22 +
  23 + .confirm {
  24 + position: absolute;
  25 + bottom: 30px;
  26 + right: 4%;
  27 + z-index: 99;
  28 + height: 50px;
  29 + width: 50px;
  30 + line-height: 50px;
  31 + font-size: 15px;
  32 + text-align: center;
  33 + background-color: white;
  34 + background: #1ABC9C;
  35 + color: white;
  36 + border: none;
  37 + cursor: pointer;
  38 + border-radius: 50%;
  39 + }
  40 +
  41 + .search {
  42 + position: absolute;
  43 + width: 400px;
  44 + top: 0;
  45 + left: 50%;
  46 + padding: 5px;
  47 + margin-left: -200px;
  48 + }
  49 +
  50 + .amap-marker-label {
  51 + border: 0;
  52 + background-color: transparent;
  53 + }
  54 +
  55 + .info {
  56 + padding: .75rem 1.25rem;
  57 + margin-bottom: 1rem;
  58 + border-radius: .25rem;
  59 + position: fixed;
  60 + top: 2rem;
  61 + background-color: white;
  62 + width: auto;
  63 + min-width: 22rem;
  64 + border-width: 0;
  65 + left: 1.8rem;
  66 + box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
  67 + }
  68 + </style>
  69 +</head>
  70 +<body>
  71 +<div class="search">
  72 + <div class="input-group">
  73 + <input type="text" id="place" name="q" class="form-control" placeholder="输入地点"/>
  74 + <span class="input-group-btn">
  75 + <button type="submit" name="search" id="search-btn" class="btn btn-success">
  76 + <i class="fa fa-search"></i>
  77 + </button>
  78 + </span>
  79 + </div>
  80 +</div>
  81 +<div class="confirm">确定</div>
  82 +<div id="container"></div>
  83 +<script type="text/javascript" src="//webapi.amap.com/maps?v=1.4.11&key={$config.amapkey|default=''}&plugin=AMap.ToolBar,AMap.Autocomplete,AMap.PlaceSearch,AMap.Geocoder"></script>
  84 +<!-- UI组件库 1.0 -->
  85 +<script src="//webapi.amap.com/ui/1.0/main.js?v=1.0.11"></script>
  86 +<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
  87 +<script type="text/javascript">
  88 + $(function () {
  89 + var as, x, y, address, map, lat, lng, geocoder;
  90 + var init = function () {
  91 + AMapUI.loadUI(['misc/PositionPicker', 'misc/PoiPicker'], function (PositionPicker, PoiPicker) {
  92 + //加载PositionPicker,loadUI的路径参数为模块名中 'ui/' 之后的部分
  93 + map = new AMap.Map('container', {
  94 + zoom: parseInt('{$config.zoom}')
  95 + });
  96 + geocoder = new AMap.Geocoder({
  97 + radius: 1000 //范围,默认:500
  98 + });
  99 + var positionPicker = new PositionPicker({
  100 + mode: 'dragMarker',//设定为拖拽地图模式,可选'dragMap'、'dragMarker',默认为'dragMap'
  101 + map: map//依赖地图对象
  102 + });
  103 + //输入提示
  104 + var autoOptions = {
  105 + input: "place"
  106 + };
  107 +
  108 + var auto = new AMap.Autocomplete(autoOptions);
  109 +
  110 + //构造地点查询类
  111 + var placeSearch = new AMap.PlaceSearch({
  112 + map: map
  113 + });
  114 +
  115 + //注册监听,当选中某条记录时会触发
  116 + AMap.event.addListener(auto, "select", function (e) {
  117 + placeSearch.setCity(e.poi.adcode);
  118 + placeSearch.search(e.poi.name); //关键字查询查询
  119 + });
  120 + AMap.event.addListener(map, 'click', function (e) {
  121 + map.panTo([e.lnglat.lng, e.lnglat.lat]);
  122 + positionPicker.start(e.lnglat);
  123 + geocoder.getAddress(e.lnglat.lng + ',' + e.lnglat.lat, function (status, result) {
  124 + if (status === 'complete' && result.regeocode) {
  125 + var address = result.regeocode.formattedAddress;
  126 + var label = '<div class="info">地址:' + address + '<br>经度:' + e.lnglat.lng + '<br>纬度:' + e.lnglat.lat + '</div>';
  127 + positionPicker.marker.setLabel({
  128 + content: label //显示内容
  129 + });
  130 + } else {
  131 + alert(JSON.stringify(result))
  132 + }
  133 + });
  134 +
  135 + });
  136 +
  137 + //加载工具条
  138 + var tool = new AMap.ToolBar();
  139 + map.addControl(tool);
  140 +
  141 + var poiPicker = new PoiPicker({
  142 + input: 'place',
  143 + placeSearchOptions: {
  144 + map: map,
  145 + pageSize: 6 //关联搜索分页
  146 + }
  147 + });
  148 + poiPicker.on('poiPicked', function (poiResult) {
  149 + poiPicker.hideSearchResults();
  150 + lat = poiResult.item.location.lat
  151 + lng = poiResult.item.location.lng
  152 + $('.poi .nearpoi').text(poiResult.item.name)
  153 + $('.address .info').text(poiResult.item.address)
  154 + $('#address').val(poiResult.item.address)
  155 + map.panTo([lng, lat]);
  156 + });
  157 +
  158 + positionPicker.on('success', function (positionResult) {
  159 + as = positionResult.position;
  160 + address = positionResult.address;
  161 + x = as.lat;
  162 + y = as.lng;
  163 + });
  164 + positionPicker.on('fail', function (positionResult) {
  165 + address = '';
  166 + console.log(positionResult);
  167 + });
  168 + positionPicker.start();
  169 + });
  170 + };
  171 +
  172 + //点击确定后执行回调赋值
  173 + var close = function (data) {
  174 + var index = parent.Layer.getFrameIndex(window.name);
  175 + var callback = parent.$("#layui-layer" + index).data("callback");
  176 + //再执行关闭
  177 + parent.Layer.close(index);
  178 + //再调用回传函数
  179 + if (typeof callback === 'function') {
  180 + callback.call(undefined, data);
  181 + }
  182 + };
  183 +
  184 + //点击搜索按钮
  185 + $(document).on('click', '.confirm', function () {
  186 + var zoom = map.getZoom();
  187 + var data = {lat: x, lng: y, zoom: zoom, address: address};
  188 + close(data);
  189 + });
  190 + init();
  191 + });
  192 +</script>
  193 +</body>
  194 +</html>
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  5 + <title>地址选择器</title>
  6 + <link rel="stylesheet" href="__CDN__/assets/css/bootstrap.min.css"/>
  7 + <link rel="stylesheet" href="__CDN__/assets/css/fastadmin.min.css"/>
  8 + <link rel="stylesheet" href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css"/>
  9 + <style type="text/css">
  10 + body {
  11 + margin: 0;
  12 + padding: 0;
  13 + }
  14 +
  15 + #container {
  16 + position: absolute;
  17 + left: 0;
  18 + top: 0;
  19 + right: 0;
  20 + bottom: 0;
  21 + }
  22 +
  23 + .confirm {
  24 + position: absolute;
  25 + bottom: 30px;
  26 + right: 4%;
  27 + z-index: 99;
  28 + height: 50px;
  29 + width: 50px;
  30 + line-height: 50px;
  31 + font-size: 15px;
  32 + text-align: center;
  33 + background-color: white;
  34 + background: #1ABC9C;
  35 + color: white;
  36 + border: none;
  37 + cursor: pointer;
  38 + border-radius: 50%;
  39 + }
  40 +
  41 + .search {
  42 + position: absolute;
  43 + width: 400px;
  44 + top: 0;
  45 + left: 50%;
  46 + padding: 5px;
  47 + margin-left: -200px;
  48 + }
  49 +
  50 + label.BMapLabel {
  51 + max-width: inherit;
  52 + padding: .75rem 1.25rem;
  53 + margin-bottom: 1rem;
  54 + background-color: white;
  55 + width: auto;
  56 + min-width: 22rem;
  57 + border: none;
  58 + box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
  59 + }
  60 +
  61 + </style>
  62 +</head>
  63 +<body>
  64 +<div class="search">
  65 + <div class="input-group">
  66 + <input type="text" id="place" name="q" class="form-control" placeholder="输入地点"/>
  67 + <div id="searchResultPanel" style="border:1px solid #C0C0C0;width:150px;height:auto; display:none;"></div>
  68 + <span class="input-group-btn">
  69 + <button type="button" name="search" id="address" class="btn btn-success">
  70 + <i class="fa fa-search"></i>
  71 + </button>
  72 + </span>
  73 + </div>
  74 +</div>
  75 +<div class="confirm">确定</div>
  76 +<div id="container"></div>
  77 +<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak={$config.baidukey|default=''}"></script>
  78 +<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
  79 +<script type="text/javascript">
  80 + $(function () {
  81 + // 百度地图API功能
  82 + function G(id) {
  83 + return document.getElementById(id);
  84 + }
  85 +
  86 + var map, marker, searchService, address = null, lng, lat;
  87 +
  88 + var init = function () {
  89 + map = new BMap.Map("container"); // 创建地图实例
  90 + var point = new BMap.Point({$lng}, {$lat}); // 创建点坐标
  91 + map.enableScrollWheelZoom(true); //开启鼠标滚轮缩放
  92 + map.centerAndZoom(point, parseInt("{$config.zoom}")); // 初始化地图,设置中心点坐标和地图级别
  93 +
  94 + var size = new BMap.Size(10, 20);
  95 + map.addControl(new BMap.CityListControl({
  96 + anchor: BMAP_ANCHOR_TOP_LEFT,
  97 + offset: size,
  98 + }));
  99 +
  100 + ac = new BMap.Autocomplete({"input": "place", "location": map}); //建立一个自动完成的对象
  101 + ac.addEventListener("onhighlight", function (e) { //鼠标放在下拉列表上的事件
  102 + var str = "";
  103 + var _value = e.fromitem.value;
  104 + var value = "";
  105 + if (e.fromitem.index > -1) {
  106 + value = _value.province + _value.city + _value.district + _value.street + _value.business;
  107 + }
  108 + str = "FromItem<br />index = " + e.fromitem.index + "<br />value = " + value;
  109 +
  110 + value = "";
  111 + if (e.toitem.index > -1) {
  112 + _value = e.toitem.value;
  113 + value = _value.province + _value.city + _value.district + _value.street + _value.business;
  114 + }
  115 + str += "<br />ToItem<br />index = " + e.toitem.index + "<br />value = " + value;
  116 + G("searchResultPanel").innerHTML = str;
  117 + });
  118 + ac.addEventListener("onconfirm", function (e) { //鼠标点击下拉列表后的事件
  119 + var _value = e.item.value;
  120 + myValue = _value.province + _value.city + _value.district + _value.street + _value.business;
  121 + G("searchResultPanel").innerHTML = "onconfirm<br />index = " + e.item.index + "<br />myValue = " + myValue;
  122 + setPlace();
  123 + });
  124 +
  125 + function setPlace() {
  126 + map.clearOverlays(); //清除地图上所有覆盖物
  127 + function myFun() {
  128 + var pp = local.getResults().getPoi(0).point; //获取第一个智能搜索的结果
  129 + map.centerAndZoom(pp, 18);
  130 + map.addOverlay(new BMap.Marker(pp)); //添加标注
  131 + }
  132 +
  133 + var local = new BMap.LocalSearch(map, { //智能搜索
  134 + onSearchComplete: myFun
  135 + });
  136 + local.search(myValue);
  137 + }
  138 +
  139 + var geoc = new BMap.Geocoder();
  140 + map.addEventListener("click", function (e) {
  141 + //通过点击百度地图,可以获取到对应的point, 由point的lng、lat属性就可以获取对应的经度纬度
  142 + var pt = e.point;
  143 + geoc.getLocation(pt, function (rs) {
  144 + //对象可以获取到详细的地址信息
  145 + address = rs.address;
  146 + deletePoint();
  147 + var mk = new BMap.Marker(pt);
  148 + map.addOverlay(mk);
  149 + map.panTo(pt);
  150 + var label = new BMap.Label('<div class="info">地址:' + address + '<br>经度:' + pt.lng + '<br>纬度:' + pt.lat + '</div>', {offset: new BMap.Size(16, 20)});
  151 + label.setStyle({
  152 + border: 'none',
  153 + padding: '.75rem 1.25rem'
  154 + });
  155 + mk.setLabel(label);
  156 + //将对应的HTML元素设置值
  157 + lng = pt.lng;
  158 + lat = pt.lat;
  159 + });
  160 + });
  161 +
  162 + /**
  163 + * 清除覆盖物
  164 + */
  165 + function deletePoint() {
  166 + var allOverlay = map.getOverlays();
  167 + for (var i = 0; i < allOverlay.length; i++) {
  168 + map.removeOverlay(allOverlay[i]);
  169 + }
  170 + }
  171 + };
  172 +
  173 + var close = function (data) {
  174 + var index = parent.Layer.getFrameIndex(window.name);
  175 + var callback = parent.$("#layui-layer" + index).data("callback");
  176 + //再执行关闭
  177 + parent.Layer.close(index);
  178 + //再调用回传函数
  179 + if (typeof callback === 'function') {
  180 + callback.call(undefined, data);
  181 + }
  182 + };
  183 +
  184 + //点击确定后执行回调赋值
  185 + $(document).on('click', '.confirm', function () {
  186 + var zoom = map.getZoom();
  187 + var data = {lat: lat, lng: lng, zoom: zoom, address: address};
  188 + close(data);
  189 + });
  190 +
  191 + init();
  192 + });
  193 +</script>
  194 +</body>
  195 +</html>
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  5 + <title>地址选择器</title>
  6 + <link rel="stylesheet" href="__CDN__/assets/css/bootstrap.min.css"/>
  7 + <link rel="stylesheet" href="__CDN__/assets/css/fastadmin.min.css"/>
  8 + <link rel="stylesheet" href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css"/>
  9 + <style type="text/css">
  10 + body {
  11 + margin: 0;
  12 + padding: 0;
  13 + }
  14 +
  15 + #container {
  16 + position: absolute;
  17 + left: 0;
  18 + top: 0;
  19 + right: 0;
  20 + bottom: 0;
  21 + }
  22 +
  23 + .confirm {
  24 + position: absolute;
  25 + bottom: 30px;
  26 + right: 4%;
  27 + z-index: 99;
  28 + height: 50px;
  29 + width: 50px;
  30 + line-height: 50px;
  31 + font-size: 15px;
  32 + text-align: center;
  33 + background-color: white;
  34 + background: #1ABC9C;
  35 + color: white;
  36 + border: none;
  37 + cursor: pointer;
  38 + border-radius: 50%;
  39 + }
  40 +
  41 + .search {
  42 + position: absolute;
  43 + width: 400px;
  44 + top: 0;
  45 + left: 50%;
  46 + padding: 5px;
  47 + margin-left: -200px;
  48 + }
  49 + </style>
  50 +</head>
  51 +<body>
  52 +<div class="search">
  53 + <div class="input-group">
  54 + <input type="text" id="place" name="q" class="form-control" placeholder="输入地点"/>
  55 + <span class="input-group-btn">
  56 + <button type="submit" name="search" id="search-btn" class="btn btn-success">
  57 + <i class="fa fa-search"></i>
  58 + </button>
  59 + </span>
  60 + </div>
  61 +</div>
  62 +<div class="confirm">确定</div>
  63 +<div id="container"></div>
  64 +<script charset="utf-8" src="http://map.qq.com/api/js?v=2.exp&libraries=place&key={$config.tencentkey|default=''}"></script>
  65 +<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
  66 +<script type="text/javascript">
  67 + $(function () {
  68 + var map, marker, geocoder, infoWin, searchService, address = null;
  69 + var init = function () {
  70 + var center = new qq.maps.LatLng({$lat}, {$lng});
  71 + map = new qq.maps.Map(document.getElementById('container'), {
  72 + center: center,
  73 + zoom: parseInt("{$config.zoom}")
  74 + });
  75 + //初始化marker
  76 + initmarker(center);
  77 +
  78 + //实例化信息窗口
  79 + infoWin = new qq.maps.InfoWindow({
  80 + map: map
  81 + });
  82 + geocoder = new qq.maps.Geocoder({
  83 + complete: function (result) {
  84 + infoWin.open();
  85 + address = result.detail.addressComponents.province +
  86 + result.detail.addressComponents.city +
  87 + result.detail.addressComponents.district;
  88 + if (result.detail.addressComponents.streetNumber == '') {
  89 + address += result.detail.addressComponents.street;
  90 + } else {
  91 + address += result.detail.addressComponents.streetNumber;
  92 + }
  93 + infoWin.setContent(address);
  94 + infoWin.setPosition(result.detail.location);
  95 + }
  96 + });
  97 + //显示当前marker的位置信息窗口
  98 + geocoder.getAddress(center);
  99 +
  100 + var latlngBounds = new qq.maps.LatLngBounds();
  101 + //查询poi类信息
  102 + searchService = new qq.maps.SearchService({
  103 + complete: function (results) {
  104 + var pois = results.detail.pois;
  105 + for (var i = 0, l = pois.length; i < l; i++) {
  106 + var poi = pois[i];
  107 + latlngBounds.extend(poi.latLng);
  108 + initmarker(poi.latLng);
  109 + //显示当前marker的位置信息窗口
  110 + geocoder.getAddress(poi.latLng);
  111 + }
  112 + map.fitBounds(latlngBounds);
  113 + }
  114 + });
  115 + //实例化自动完成
  116 + var ap = new qq.maps.place.Autocomplete(document.getElementById('place'));
  117 + //添加监听事件
  118 + qq.maps.event.addListener(ap, "confirm", function (res) {
  119 + searchKeyword();
  120 + });
  121 + qq.maps.event.addListener(
  122 + map,
  123 + 'click',
  124 + function (event) {
  125 + try {
  126 + infoWin.setContent('<div style="text-align:center;white-space:nowrap;margin:10px;">加载中</div>');
  127 + var latLng = event.latLng,
  128 + lat = latLng.getLat().toFixed(5),
  129 + lng = latLng.getLng().toFixed(5);
  130 + var location = new qq.maps.LatLng(lat, lng);
  131 + //调用获取位置方法
  132 + geocoder.getAddress(location);
  133 + infoWin.setPosition(location);
  134 + marker.setPosition(location);
  135 + } catch (e) {
  136 + console.log(e);
  137 + }
  138 + }
  139 + );
  140 + };
  141 +
  142 + //实例化marker和监听拖拽结束事件
  143 + var initmarker = function (latLng) {
  144 + marker = new qq.maps.Marker({
  145 + map: map,
  146 + position: latLng,
  147 + draggable: true,
  148 + title: '拖动图标选择位置'
  149 + });
  150 + //监听拖拽结束
  151 + qq.maps.event.addListener(marker, 'dragend', function (event) {
  152 + var latLng = event.latLng,
  153 + lat = latLng.getLat().toFixed(5),
  154 + lng = latLng.getLng().toFixed(5);
  155 + var location = new qq.maps.LatLng(lat, lng);
  156 + //调用获取位置方法
  157 + geocoder.getAddress(location);
  158 + });
  159 + };
  160 + var close = function (data) {
  161 + var index = parent.Layer.getFrameIndex(window.name);
  162 + var callback = parent.$("#layui-layer" + index).data("callback");
  163 + //再执行关闭
  164 + parent.Layer.close(index);
  165 + //再调用回传函数
  166 + if (typeof callback === 'function') {
  167 + callback.call(undefined, data);
  168 + }
  169 + };
  170 +
  171 + //执行搜索方法
  172 + var searchKeyword = function () {
  173 + searchService.clear();//先清除
  174 + marker.setMap(null);
  175 + infoWin.close();
  176 + var keyword = $("#place").val();
  177 + searchService.setLocation("{$location}");//设置默认检索范围(默认为全国),类型可以是坐标或指定的城市名称。
  178 + searchService.setPageIndex(0);//设置检索的特定页数。
  179 + searchService.setPageCapacity(1);//设置每页返回的结果数量。
  180 + searchService.search(keyword);//开始查询
  181 + };
  182 +
  183 + //点击确定后执行回调赋值
  184 + $(document).on('click', '.confirm', function () {
  185 + var as = marker.getPosition();
  186 + var x = as.getLat().toFixed(5);
  187 + var y = as.getLng().toFixed(5);
  188 + var zoom = map.getZoom();
  189 + var data = {lat: x, lng: y, zoom: zoom, address: address};
  190 + close(data);
  191 + });
  192 +
  193 + //点击搜索按钮
  194 + $(document).on('click', '#search-btn', function () {
  195 + if ($("#place").val() == '')
  196 + return;
  197 + searchKeyword();
  198 + });
  199 +
  200 + init();
  201 + });
  202 +</script>
  203 +</body>
  204 +</html>
  1 +<?php
  2 +
  3 +namespace addons\command;
  4 +
  5 +use app\common\library\Menu;
  6 +use think\Addons;
  7 +
  8 +/**
  9 + * 在线命令插件
  10 + */
  11 +class Command extends Addons
  12 +{
  13 +
  14 + /**
  15 + * 插件安装方法
  16 + * @return bool
  17 + */
  18 + public function install()
  19 + {
  20 + $menu = [
  21 + [
  22 + 'name' => 'command',
  23 + 'title' => '在线命令管理',
  24 + 'icon' => 'fa fa-terminal',
  25 + 'sublist' => [
  26 + ['name' => 'command/index', 'title' => '查看'],
  27 + ['name' => 'command/add', 'title' => '添加'],
  28 + ['name' => 'command/detail', 'title' => '详情'],
  29 + ['name' => 'command/execute', 'title' => '运行'],
  30 + ['name' => 'command/del', 'title' => '删除'],
  31 + ['name' => 'command/multi', 'title' => '批量更新'],
  32 + ]
  33 + ]
  34 + ];
  35 + Menu::create($menu);
  36 + return true;
  37 + }
  38 +
  39 + /**
  40 + * 插件卸载方法
  41 + * @return bool
  42 + */
  43 + public function uninstall()
  44 + {
  45 + Menu::delete('command');
  46 + return true;
  47 + }
  48 +
  49 + /**
  50 + * 插件启用方法
  51 + * @return bool
  52 + */
  53 + public function enable()
  54 + {
  55 + Menu::enable('command');
  56 + return true;
  57 + }
  58 +
  59 + /**
  60 + * 插件禁用方法
  61 + * @return bool
  62 + */
  63 + public function disable()
  64 + {
  65 + Menu::disable('command');
  66 + return true;
  67 + }
  68 +
  69 +}
  1 +<?php
  2 +
  3 +namespace app\admin\controller;
  4 +
  5 +use app\common\controller\Backend;
  6 +use think\Config;
  7 +use think\console\Input;
  8 +use think\Db;
  9 +use think\Exception;
  10 +
  11 +/**
  12 + * 在线命令管理
  13 + *
  14 + * @icon fa fa-circle-o
  15 + */
  16 +class Command extends Backend
  17 +{
  18 +
  19 + /**
  20 + * Command模型对象
  21 + */
  22 + protected $model = null;
  23 + protected $noNeedRight = ['get_controller_list', 'get_field_list'];
  24 +
  25 + public function _initialize()
  26 + {
  27 + parent::_initialize();
  28 + $this->model = model('Command');
  29 + $this->view->assign("statusList", $this->model->getStatusList());
  30 + }
  31 +
  32 + /**
  33 + * 添加
  34 + */
  35 + public function add()
  36 + {
  37 +
  38 + $tableList = [];
  39 + $list = \think\Db::query("SHOW TABLES");
  40 + foreach ($list as $key => $row) {
  41 + $tableList[reset($row)] = reset($row);
  42 + }
  43 +
  44 + $this->view->assign("tableList", $tableList);
  45 + return $this->view->fetch();
  46 + }
  47 +
  48 + /**
  49 + * 获取字段列表
  50 + * @internal
  51 + */
  52 + public function get_field_list()
  53 + {
  54 + $dbname = Config::get('database.database');
  55 + $prefix = Config::get('database.prefix');
  56 + $table = $this->request->request('table');
  57 + //从数据库中获取表字段信息
  58 + $sql = "SELECT * FROM `information_schema`.`columns` "
  59 + . "WHERE TABLE_SCHEMA = ? AND table_name = ? "
  60 + . "ORDER BY ORDINAL_POSITION";
  61 + //加载主表的列
  62 + $columnList = Db::query($sql, [$dbname, $table]);
  63 + $fieldlist = [];
  64 + foreach ($columnList as $index => $item) {
  65 + $fieldlist[] = $item['COLUMN_NAME'];
  66 + }
  67 + $this->success("", null, ['fieldlist' => $fieldlist]);
  68 + }
  69 +
  70 + /**
  71 + * 获取控制器列表
  72 + * @internal
  73 + */
  74 + public function get_controller_list()
  75 + {
  76 + $adminPath = dirname(__DIR__) . DS;
  77 + $controllerDir = $adminPath . 'controller' . DS;
  78 + $files = new \RecursiveIteratorIterator(
  79 + new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
  80 + );
  81 + $list = [];
  82 + foreach ($files as $name => $file) {
  83 + if (!$file->isDir()) {
  84 + $filePath = $file->getRealPath();
  85 + $name = str_replace($controllerDir, '', $filePath);
  86 + $name = str_replace(DS, "/", $name);
  87 + $list[] = ['id' => $name, 'name' => $name];
  88 + }
  89 + }
  90 + $pageNumber = $this->request->request("pageNumber");
  91 + $pageSize = $this->request->request("pageSize");
  92 + return json(['list' => array_slice($list, ($pageNumber - 1) * $pageSize, $pageSize), 'total' => count($list)]);
  93 + }
  94 +
  95 + /**
  96 + * 详情
  97 + */
  98 + public function detail($ids)
  99 + {
  100 + $row = $this->model->get($ids);
  101 + if (!$row)
  102 + $this->error(__('No Results were found'));
  103 + $this->view->assign("row", $row);
  104 + return $this->view->fetch();
  105 + }
  106 +
  107 + /**
  108 + * 执行
  109 + */
  110 + public function execute($ids)
  111 + {
  112 + $row = $this->model->get($ids);
  113 + if (!$row)
  114 + $this->error(__('No Results were found'));
  115 + $result = $this->doexecute($row['type'], json_decode($row['params'], true));
  116 + $this->success("", null, ['result' => $result]);
  117 + }
  118 +
  119 + /**
  120 + * 执行命令
  121 + */
  122 + public function command($action = '')
  123 + {
  124 + $commandtype = $this->request->request("commandtype");
  125 + $params = $this->request->request();
  126 + $allowfields = [
  127 + 'crud' => 'table,controller,model,fields,force,local,delete,menu',
  128 + 'menu' => 'controller,delete',
  129 + 'min' => 'module,resource,optimize',
  130 + 'api' => 'url,module,output,template,force,title,author,class,language',
  131 + ];
  132 + $argv = [];
  133 + $allowfields = isset($allowfields[$commandtype]) ? explode(',', $allowfields[$commandtype]) : [];
  134 + $allowfields = array_filter(array_intersect_key($params, array_flip($allowfields)));
  135 + if (isset($params['local']) && !$params['local']) {
  136 + $allowfields['local'] = $params['local'];
  137 + } else {
  138 + unset($allowfields['local']);
  139 + }
  140 + foreach ($allowfields as $key => $param) {
  141 + $argv[] = "--{$key}=" . (is_array($param) ? implode(',', $param) : $param);
  142 + }
  143 + if ($commandtype == 'crud') {
  144 + $extend = 'setcheckboxsuffix,enumradiosuffix,imagefield,filefield,intdatesuffix,switchsuffix,citysuffix,selectpagesuffix,selectpagessuffix,ignorefields,sortfield,editorsuffix,headingfilterfield';
  145 + $extendArr = explode(',', $extend);
  146 + foreach ($params as $index => $item) {
  147 + if (in_array($index, $extendArr)) {
  148 + foreach (explode(',', $item) as $key => $value) {
  149 + if ($value) {
  150 + $argv[] = "--{$index}={$value}";
  151 + }
  152 + }
  153 + }
  154 + }
  155 + $isrelation = (int)$this->request->request('isrelation');
  156 + if ($isrelation && isset($params['relation'])) {
  157 + foreach ($params['relation'] as $index => $relation) {
  158 + foreach ($relation as $key => $value) {
  159 + $argv[] = "--{$key}=" . (is_array($value) ? implode(',', $value) : $value);
  160 + }
  161 + }
  162 + }
  163 + } else if ($commandtype == 'menu') {
  164 + if (isset($params['allcontroller']) && $params['allcontroller']) {
  165 + $argv[] = "--controller=all-controller";
  166 + } else {
  167 + foreach (explode(',', $params['controllerfile']) as $index => $param) {
  168 + if ($param) {
  169 + $argv[] = "--controller=" . substr($param, 0, -4);
  170 + }
  171 + }
  172 + }
  173 + } else if ($commandtype == 'min') {
  174 +
  175 + } else if ($commandtype == 'api') {
  176 +
  177 + } else {
  178 +
  179 + }
  180 + if ($action == 'execute') {
  181 + $result = $this->doexecute($commandtype, $argv);
  182 + $this->success("", null, ['result' => $result]);
  183 + } else {
  184 + $this->success("", null, ['command' => "php think {$commandtype} " . implode(' ', $argv)]);
  185 + }
  186 +
  187 + return;
  188 + }
  189 +
  190 + protected function doexecute($commandtype, $argv)
  191 + {
  192 + $commandName = "\\app\\admin\\command\\" . ucfirst($commandtype);
  193 + $input = new Input($argv);
  194 + $output = new \addons\command\library\Output();
  195 + $command = new $commandName($commandtype);
  196 + $data = [
  197 + 'type' => $commandtype,
  198 + 'params' => json_encode($argv),
  199 + 'command' => "php think {$commandtype} " . implode(' ', $argv),
  200 + 'executetime' => time(),
  201 + ];
  202 + $this->model->save($data);
  203 + try {
  204 + $command->run($input, $output);
  205 + $result = implode("\n", $output->getMessage());
  206 + $this->model->status = 'successed';
  207 + } catch (Exception $e) {
  208 + $result = implode("\n", $output->getMessage()) . "\n";
  209 + $result .= $e->getMessage();
  210 + $this->model->status = 'failured';
  211 + }
  212 + $result = trim($result);
  213 + $this->model->content = $result;
  214 + $this->model->save();
  215 + return $result;
  216 + }
  217 +
  218 +
  219 +}
  1 +<?php
  2 +
  3 +return [
  4 + 'Id' => 'ID',
  5 + 'Type' => '类型',
  6 + 'Params' => '参数',
  7 + 'Command' => '命令',
  8 + 'Content' => '返回结果',
  9 + 'Executetime' => '执行时间',
  10 + 'Createtime' => '创建时间',
  11 + 'Updatetime' => '更新时间',
  12 + 'Execute again' => '再次执行',
  13 + 'Successed' => '成功',
  14 + 'Failured' => '失败',
  15 + 'Status' => '状态'
  16 +];
  1 +<?php
  2 +
  3 +namespace app\admin\model;
  4 +
  5 +use think\Model;
  6 +
  7 +class Command extends Model
  8 +{
  9 + // 表名
  10 + protected $name = 'command';
  11 +
  12 + // 自动写入时间戳字段
  13 + protected $autoWriteTimestamp = 'int';
  14 +
  15 + // 定义时间戳字段名
  16 + protected $createTime = 'createtime';
  17 + protected $updateTime = 'updatetime';
  18 +
  19 + // 追加属性
  20 + protected $append = [
  21 + 'executetime_text',
  22 + 'type_text',
  23 + 'status_text'
  24 + ];
  25 +
  26 +
  27 + public function getStatusList()
  28 + {
  29 + return ['successed' => __('Successed'), 'failured' => __('Failured')];
  30 + }
  31 +
  32 +
  33 + public function getExecutetimeTextAttr($value, $data)
  34 + {
  35 + $value = $value ? $value : $data['executetime'];
  36 + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
  37 + }
  38 +
  39 + public function getTypeTextAttr($value, $data)
  40 + {
  41 + $value = $value ? $value : $data['type'];
  42 + $list = ['crud' => '一键生成CRUD', 'menu' => '一键生成菜单', 'min' => '一键压缩打包', 'api' => '一键生成文档'];
  43 + return isset($list[$value]) ? $list[$value] : '';
  44 + }
  45 +
  46 + public function getStatusTextAttr($value, $data)
  47 + {
  48 + $value = $value ? $value : $data['status'];
  49 + $list = $this->getStatusList();
  50 + return isset($list[$value]) ? $list[$value] : '';
  51 + }
  52 +
  53 + protected function setExecutetimeAttr($value)
  54 + {
  55 + return $value && !is_numeric($value) ? strtotime($value) : $value;
  56 + }
  57 +
  58 +
  59 +}
  1 +<?php
  2 +
  3 +namespace app\admin\validate;
  4 +
  5 +use think\Validate;
  6 +
  7 +class Command extends Validate
  8 +{
  9 + /**
  10 + * 验证规则
  11 + */
  12 + protected $rule = [
  13 + ];
  14 + /**
  15 + * 提示消息
  16 + */
  17 + protected $message = [
  18 + ];
  19 + /**
  20 + * 验证场景
  21 + */
  22 + protected $scene = [
  23 + 'add' => [],
  24 + 'edit' => [],
  25 + ];
  26 +
  27 +}
  1 +<style>
  2 + .relation-item {margin-top:10px;}
  3 + legend {padding-bottom:5px;font-size:14px;font-weight:600;}
  4 + label {font-weight:normal;}
  5 + .form-control{padding:6px 8px;}
  6 + #extend-zone .col-xs-2 {margin-top:10px;padding-right:0;}
  7 + #extend-zone .col-xs-2:nth-child(6n+0) {padding-right:15px;}
  8 +</style>
  9 +<div class="panel panel-default panel-intro">
  10 + <div class="panel-heading">
  11 + <ul class="nav nav-tabs">
  12 + <li class="active"><a href="#crud" data-toggle="tab">{:__('一键生成CRUD')}</a></li>
  13 + <li><a href="#menu" data-toggle="tab">{:__('一键生成菜单')}</a></li>
  14 + <li><a href="#min" data-toggle="tab">{:__('一键压缩打包')}</a></li>
  15 + <li><a href="#api" data-toggle="tab">{:__('一键生成API文档')}</a></li>
  16 + </ul>
  17 + </div>
  18 + <div class="panel-body">
  19 + <div id="myTabContent" class="tab-content">
  20 + <div class="tab-pane fade active in" id="crud">
  21 + <div class="row">
  22 + <div class="col-xs-12">
  23 + <form role="form">
  24 + <input type="hidden" name="commandtype" value="crud" />
  25 + <div class="form-group">
  26 + <div class="row">
  27 + <div class="col-xs-3">
  28 + <input checked="" name="isrelation" type="hidden" value="0">
  29 + <label class="control-label" data-toggle="tooltip" title="当前只支持生成1对1关联模型,选中后请配置关联表和字段">
  30 + <input name="isrelation" type="checkbox" value="1">
  31 + 关联模型
  32 + </label>
  33 + </div>
  34 + <div class="col-xs-3">
  35 + <input checked="" name="local" type="hidden" value="1">
  36 + <label class="control-label" data-toggle="tooltip" title="默认模型生成在application/admin/model目录下,选中后将生成在application/common/model目录下">
  37 + <input name="local" type="checkbox" value="0"> 全局模型类
  38 + </label>
  39 + </div>
  40 + <div class="col-xs-3">
  41 + <input checked="" name="delete" type="hidden" value="0">
  42 + <label class="control-label" data-toggle="tooltip" title="删除CRUD生成的相关文件">
  43 + <input name="delete" type="checkbox" value="1"> 删除模式
  44 + </label>
  45 + </div>
  46 + <div class="col-xs-3">
  47 + <input checked="" name="force" type="hidden" value="0">
  48 + <label class="control-label" data-toggle="tooltip" title="选中后,如果已经存在同名文件将被覆盖。如果是删除将不再提醒">
  49 + <input name="force" type="checkbox" value="1">
  50 + 强制覆盖模式
  51 + </label>
  52 + </div>
  53 + <!--
  54 + <div class="col-xs-3">
  55 + <input checked="" name="menu" type="hidden" value="0">
  56 + <label class="control-label" data-toggle="tooltip" title="选中后,将同时生成后台菜单规则">
  57 + <input name="menu" type="checkbox" value="1">
  58 + 生成菜单
  59 + </label>
  60 + </div>
  61 + -->
  62 + </div>
  63 + </div>
  64 + <div class="form-group">
  65 + <legend>主表设置</legend>
  66 + <div class="row">
  67 + <div class="col-xs-3">
  68 + <label>请选择主表</label>
  69 + {:build_select('table',$tableList,null,['class'=>'form-control selectpicker']);}
  70 + </div>
  71 + <div class="col-xs-3">
  72 + <label>自定义控制器名</label>
  73 + <input type="text" class="form-control" name="controller" data-toggle="tooltip" title="默认根据表名自动生成,如果需要放在二级目录请手动填写" placeholder="支持目录层级,以/分隔">
  74 + </div>
  75 + <div class="col-xs-3">
  76 + <label>自定义模型名</label>
  77 + <input type="text" class="form-control" name="model" data-toggle="tooltip" title="默认根据表名自动生成" placeholder="不支持目录层级">
  78 + </div>
  79 + <div class="col-xs-3">
  80 + <label>请选择显示字段(默认全部)</label>
  81 + <select name="fields[]" id="fields" multiple style="height:30px;" class="form-control selectpicker"></select>
  82 + </div>
  83 +
  84 + </div>
  85 +
  86 + </div>
  87 +
  88 + <div class="form-group hide" id="relation-zone">
  89 + <legend>关联表设置</legend>
  90 +
  91 + <div class="row" style="margin-top:15px;">
  92 + <div class="col-xs-12">
  93 + <a href="javascript:;" class="btn btn-primary btn-sm btn-newrelation" data-index="1">追加关联模型</a>
  94 + </div>
  95 + </div>
  96 + </div>
  97 +
  98 + <hr>
  99 + <div class="form-group" id="extend-zone">
  100 + <legend>字段识别设置 <span style="font-size:12px;font-weight: normal;">(与之匹配的字段都将生成相应组件)</span></legend>
  101 + <div class="row">
  102 + <div class="col-xs-2">
  103 + <label>复选框后缀</label>
  104 + <input type="text" class="form-control" name="setcheckboxsuffix" placeholder="默认为set类型" />
  105 + </div>
  106 + <div class="col-xs-2">
  107 + <label>单选框后缀</label>
  108 + <input type="text" class="form-control" name="enumradiosuffix" placeholder="默认为enum类型" />
  109 + </div>
  110 + <div class="col-xs-2">
  111 + <label>图片类型后缀</label>
  112 + <input type="text" class="form-control" name="imagefield" placeholder="默认为image,images,avatar,avatars" />
  113 + </div>
  114 + <div class="col-xs-2">
  115 + <label>文件类型后缀</label>
  116 + <input type="text" class="form-control" name="filefield" placeholder="默认为file,files" />
  117 + </div>
  118 + <div class="col-xs-2">
  119 + <label>日期时间后缀</label>
  120 + <input type="text" class="form-control" name="intdatesuffix" placeholder="默认为time" />
  121 + </div>
  122 + <div class="col-xs-2">
  123 + <label>开关后缀</label>
  124 + <input type="text" class="form-control" name="switchsuffix" placeholder="默认为switch" />
  125 + </div>
  126 + <div class="col-xs-2">
  127 + <label>城市选择后缀</label>
  128 + <input type="text" class="form-control" name="citysuffix" placeholder="默认为city" />
  129 + </div>
  130 + <div class="col-xs-2">
  131 + <label>动态下拉后缀(单)</label>
  132 + <input type="text" class="form-control" name="selectpagesuffix" placeholder="默认为_id" />
  133 + </div>
  134 + <div class="col-xs-2">
  135 + <label>动态下拉后缀(多)</label>
  136 + <input type="text" class="form-control" name="selectpagessuffix" placeholder="默认为_ids" />
  137 + </div>
  138 + <div class="col-xs-2">
  139 + <label>忽略的字段</label>
  140 + <input type="text" class="form-control" name="ignorefields" placeholder="默认无" />
  141 + </div>
  142 + <div class="col-xs-2">
  143 + <label>排序字段</label>
  144 + <input type="text" class="form-control" name="sortfield" placeholder="默认为weigh" />
  145 + </div>
  146 + <div class="col-xs-2">
  147 + <label>富文本编辑器</label>
  148 + <input type="text" class="form-control" name="editorsuffix" placeholder="默认为content" />
  149 + </div>
  150 + <div class="col-xs-2">
  151 + <label>选项卡过滤字段</label>
  152 + <input type="text" class="form-control" name="headingfilterfield" placeholder="默认为status" />
  153 + </div>
  154 +
  155 + </div>
  156 +
  157 + </div>
  158 +
  159 + <div class="form-group">
  160 + <legend>生成命令行</legend>
  161 + <textarea class="form-control" data-toggle="tooltip" title="如果在线执行命令失败,可以将命令复制到命令行进行执行" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
  162 + </div>
  163 +
  164 + <div class="form-group">
  165 + <legend>返回结果</legend>
  166 + <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
  167 + </div>
  168 +
  169 + <div class="form-group">
  170 + <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
  171 + <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
  172 + </div>
  173 +
  174 + </form>
  175 + </div>
  176 + </div>
  177 + </div>
  178 + <div class="tab-pane fade" id="menu">
  179 + <div class="row">
  180 + <div class="col-xs-12">
  181 + <form role="form">
  182 + <input type="hidden" name="commandtype" value="menu" />
  183 + <div class="form-group">
  184 + <div class="row">
  185 + <div class="col-xs-3">
  186 + <input checked="" name="allcontroller" type="hidden" value="0">
  187 + <label class="control-label">
  188 + <input name="allcontroller" data-toggle="collapse" data-target="#controller" type="checkbox" value="1"> 一键生成全部控制器
  189 + </label>
  190 + </div>
  191 + <div class="col-xs-3">
  192 + <input checked="" name="delete" type="hidden" value="0">
  193 + <label class="control-label">
  194 + <input name="delete" type="checkbox" value="1"> 删除模式
  195 + </label>
  196 + </div>
  197 + <div class="col-xs-3">
  198 + <input checked="" name="force" type="hidden" value="0">
  199 + <label class="control-label">
  200 + <input name="force" type="checkbox" value="1"> 强制覆盖模式
  201 + </label>
  202 + </div>
  203 + </div>
  204 + </div>
  205 +
  206 + <div class="form-group in" id="controller">
  207 + <legend>控制器设置</legend>
  208 +
  209 + <div class="row" style="margin-top:15px;">
  210 + <div class="col-xs-12">
  211 + <input type="text" name="controllerfile" class="form-control selectpage" style="width:720px;" data-source="command/get_controller_list" data-multiple="true" name="controller" placeholder="请选择控制器" />
  212 + </div>
  213 + </div>
  214 + </div>
  215 +
  216 + <div class="form-group">
  217 + <legend>生成命令行</legend>
  218 + <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
  219 + </div>
  220 +
  221 + <div class="form-group">
  222 + <legend>返回结果</legend>
  223 + <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
  224 + </div>
  225 +
  226 + <div class="form-group">
  227 + <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
  228 + <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
  229 + </div>
  230 +
  231 + </form>
  232 + </div>
  233 + </div>
  234 + </div>
  235 + <div class="tab-pane fade" id="min">
  236 + <div class="row">
  237 + <div class="col-xs-12">
  238 + <form role="form">
  239 + <input type="hidden" name="commandtype" value="min" />
  240 + <div class="form-group">
  241 + <legend>基础设置</legend>
  242 + <div class="row">
  243 + <div class="col-xs-3">
  244 + <label>请选择压缩模块</label>
  245 + <select name="module" class="form-control selectpicker">
  246 + <option value="all" selected>全部</option>
  247 + <option value="backend">后台Backend</option>
  248 + <option value="frontend">前台Frontend</option>
  249 + </select>
  250 + </div>
  251 + <div class="col-xs-3">
  252 + <label>请选择压缩资源</label>
  253 + <select name="resource" class="form-control selectpicker">
  254 + <option value="all" selected>全部</option>
  255 + <option value="js">JS</option>
  256 + <option value="css">CSS</option>
  257 + </select>
  258 + </div>
  259 + <div class="col-xs-3">
  260 + <label>请选择压缩模式</label>
  261 + <select name="optimize" class="form-control selectpicker">
  262 + <option value=""></option>
  263 + <option value="uglify">uglify</option>
  264 + <option value="closure">closure</option>
  265 + </select>
  266 + </div>
  267 + </div>
  268 + </div>
  269 +
  270 + <div class="form-group in">
  271 + <legend>控制器设置</legend>
  272 +
  273 + <div class="row" style="margin-top:15px;">
  274 + <div class="col-xs-12">
  275 +
  276 + </div>
  277 + </div>
  278 + </div>
  279 +
  280 + <div class="form-group">
  281 + <legend>生成命令行</legend>
  282 + <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
  283 + </div>
  284 +
  285 + <div class="form-group">
  286 + <legend>返回结果</legend>
  287 + <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
  288 + </div>
  289 +
  290 + <div class="form-group">
  291 + <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
  292 + <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
  293 + </div>
  294 +
  295 + </form>
  296 + </div>
  297 + </div>
  298 + </div>
  299 + <div class="tab-pane fade" id="api">
  300 + <div class="row">
  301 + <div class="col-xs-12">
  302 + <form role="form">
  303 + <input type="hidden" name="commandtype" value="api" />
  304 + <div class="form-group">
  305 + <div class="row">
  306 + <div class="col-xs-3">
  307 + <input checked="" name="force" type="hidden" value="0">
  308 + <label class="control-label">
  309 + <input name="force" type="checkbox" value="1">
  310 + 覆盖模式
  311 + </label>
  312 + </div>
  313 + </div>
  314 + </div>
  315 + <div class="form-group">
  316 + <legend>文档设置</legend>
  317 + <div class="row">
  318 + <div class="col-xs-3">
  319 + <label>请输入接口URL</label>
  320 + <input type="text" name="url" class="form-control" placeholder="API URL,可留空" />
  321 + </div>
  322 + <div class="col-xs-3">
  323 + <label>接口生成文件</label>
  324 + <input type="text" name="output" class="form-control" placeholder="留空则使用api.html" />
  325 + </div>
  326 + <div class="col-xs-3">
  327 + <label>模板文件</label>
  328 + <input type="text" name="template" class="form-control" placeholder="如果不清楚请留空" />
  329 + </div>
  330 + </div>
  331 + <div class="row" style="margin-top:10px;">
  332 + <div class="col-xs-3">
  333 + <label>文档标题</label>
  334 + <input type="text" name="title" class="form-control" placeholder="默认为FastAdmin" />
  335 + </div>
  336 + <div class="col-xs-3">
  337 + <label>文档作者</label>
  338 + <input type="text" name="author" class="form-control" placeholder="默认为FastAdmin" />
  339 + </div>
  340 + <div class="col-xs-3">
  341 + <label>文档语言</label>
  342 + <select name="language" class="form-control">
  343 + <option value="" selected>请选择语言</option>
  344 + <option value="zh-cn">中文</option>
  345 + <option value="en">英文</option>
  346 + </select>
  347 + </div>
  348 + </div>
  349 + </div>
  350 +
  351 + <div class="form-group">
  352 + <legend>生成命令行</legend>
  353 + <textarea class="form-control" rel="command" rows="1" placeholder="请点击生成命令行"></textarea>
  354 + </div>
  355 +
  356 + <div class="form-group">
  357 + <legend>返回结果</legend>
  358 + <textarea class="form-control" rel="result" rows="5" placeholder="请点击立即执行"></textarea>
  359 + </div>
  360 +
  361 + <div class="form-group">
  362 + <button type="button" class="btn btn-info btn-embossed btn-command">{:__('生成命令行')}</button>
  363 + <button type="button" class="btn btn-success btn-embossed btn-execute">{:__('立即执行')}</button>
  364 + </div>
  365 +
  366 + </form>
  367 + </div>
  368 + </div>
  369 + </div>
  370 + </div>
  371 + </div>
  372 +</div>
  373 +<script id="relationtpl" type="text/html">
  374 + <div class="row relation-item">
  375 + <div class="col-xs-2">
  376 + <label>请选择关联表</label>
  377 + <select name="relation[<%=index%>][relation]" class="form-control relationtable"></select>
  378 + </div>
  379 + <div class="col-xs-2">
  380 + <label>请选择关联类型</label>
  381 + <select name="relation[<%=index%>][relationmode]" class="form-control relationmode"></select>
  382 + </div>
  383 + <div class="col-xs-2">
  384 + <label>关联外键</label>
  385 + <select name="relation[<%=index%>][relationforeignkey]" class="form-control relationforeignkey"></select>
  386 + </div>
  387 + <div class="col-xs-2">
  388 + <label>关联主键</label>
  389 + <select name="relation[<%=index%>][relationprimarykey]" class="form-control relationprimarykey"></select>
  390 + </div>
  391 + <div class="col-xs-2">
  392 + <label>请选择显示字段</label>
  393 + <select name="relation[<%=index%>][relationfields][]" multiple class="form-control relationfields"></select>
  394 + </div>
  395 + <div class="col-xs-2">
  396 + <label>&nbsp;</label>
  397 + <a href="javascript:;" class="btn btn-danger btn-block btn-removerelation">移除</a>
  398 + </div>
  399 + </div>
  400 +</script>
  1 +<table class="table table-striped">
  2 + <thead>
  3 + <tr>
  4 + <th>{:__('Title')}</th>
  5 + <th>{:__('Content')}</th>
  6 + </tr>
  7 + </thead>
  8 + <tbody>
  9 + <tr>
  10 + <td>{:__('Type')}</td>
  11 + <td>{$row.type}({$row.type_text})</td>
  12 + </tr>
  13 + <tr>
  14 + <td>{:__('Params')}</td>
  15 + <td>{$row.params}</td>
  16 + </tr>
  17 + <tr>
  18 + <td>{:__('Command')}</td>
  19 + <td>{$row.command}</td>
  20 + </tr>
  21 + <tr>
  22 + <td>{:__('Content')}</td>
  23 + <td>
  24 + <textarea class="form-control" name="" id="" cols="60" rows="10">{$row.content}</textarea>
  25 + </td>
  26 + </tr>
  27 + <tr>
  28 + <td>{:__('Executetime')}</td>
  29 + <td>{$row.executetime|datetime}</td>
  30 + </tr>
  31 + <tr>
  32 + <td>{:__('Status')}</td>
  33 + <td>{$row.status_text}</td>
  34 + </tr>
  35 + </tbody>
  36 +</table>
  37 +<div class="hide layer-footer">
  38 + <label class="control-label col-xs-12 col-sm-2"></label>
  39 + <div class="col-xs-12 col-sm-8">
  40 + <button type="reset" class="btn btn-primary btn-embossed btn-close" onclick="Layer.closeAll();">{:__('Close')}</button>
  41 + </div>
  42 +</div>
  1 +<div class="panel panel-default panel-intro">
  2 + {:build_heading()}
  3 +
  4 + <div class="panel-body">
  5 + <div id="myTabContent" class="tab-content">
  6 + <div class="tab-pane fade active in" id="one">
  7 + <div class="widget-body no-padding">
  8 + <div id="toolbar" class="toolbar">
  9 + <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
  10 + <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('command/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
  11 + <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('command/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
  12 +
  13 + </div>
  14 + <table id="table" class="table table-striped table-bordered table-hover"
  15 + data-operate-detail="{:$auth->check('command/detail')}"
  16 + data-operate-execute="{:$auth->check('command/execute')}"
  17 + data-operate-del="{:$auth->check('command/del')}"
  18 + width="100%">
  19 + </table>
  20 + </div>
  21 + </div>
  22 +
  23 + </div>
  24 + </div>
  25 +</div>
  1 +<?php
  2 +
  3 +return [
  4 +];
  1 +<?php
  2 +
  3 +namespace addons\command\controller;
  4 +
  5 +use think\addons\Controller;
  6 +
  7 +class Index extends Controller
  8 +{
  9 +
  10 + public function index()
  11 + {
  12 + $this->error("当前插件暂无前台页面");
  13 + }
  14 +
  15 +}
  1 +name = command
  2 +title = 在线命令
  3 +intro = 可在线执行FastAdmin的命令行相关命令
  4 +author = Karson
  5 +website = http://www.fastadmin.net
  6 +version = 1.0.5
  7 +state = 1
  8 +url = /addons/command.html
  1 +CREATE TABLE IF NOT EXISTS `__PREFIX__command` (
  2 + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
  3 + `type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '类型',
  4 + `params` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '参数',
  5 + `command` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '命令',
  6 + `content` text COMMENT '返回结果',
  7 + `executetime` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '执行时间',
  8 + `createtime` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
  9 + `updatetime` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间',
  10 + `status` enum('successed','failured') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'failured' COMMENT '状态',
  11 + PRIMARY KEY (`id`) USING BTREE
  12 +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '在线命令表' ROW_FORMAT = Compact;
  1 +<?php
  2 +
  3 +namespace addons\command\library;
  4 +
  5 +/**
  6 + * Class Output
  7 + */
  8 +class Output extends \think\console\Output
  9 +{
  10 +
  11 + protected $message = [];
  12 +
  13 + public function __construct($driver = 'console')
  14 + {
  15 + parent::__construct($driver);
  16 + }
  17 +
  18 + protected function block($style, $message)
  19 + {
  20 + $this->message[] = $message;
  21 + }
  22 +
  23 + public function getMessage()
  24 + {
  25 + return $this->message;
  26 + }
  27 +
  28 +}
  1 +define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function ($, undefined, Backend, Table, Form, Template) {
  2 +
  3 + var Controller = {
  4 + index: function () {
  5 + // 初始化表格参数配置
  6 + Table.api.init({
  7 + extend: {
  8 + index_url: 'command/index',
  9 + add_url: 'command/add',
  10 + edit_url: '',
  11 + del_url: 'command/del',
  12 + multi_url: 'command/multi',
  13 + table: 'command',
  14 + }
  15 + });
  16 +
  17 + var table = $("#table");
  18 +
  19 + // 初始化表格
  20 + table.bootstrapTable({
  21 + url: $.fn.bootstrapTable.defaults.extend.index_url,
  22 + pk: 'id',
  23 + sortName: 'id',
  24 + columns: [
  25 + [
  26 + {checkbox: true},
  27 + {field: 'id', title: __('Id')},
  28 + {field: 'type', title: __('Type'), formatter: Table.api.formatter.search},
  29 + {field: 'type_text', title: __('Type')},
  30 + {
  31 + field: 'command', title: __('Command'), formatter: function (value, row, index) {
  32 + return '<input type="text" class="form-control" value="' + value + '">';
  33 + }
  34 + },
  35 + {
  36 + field: 'executetime',
  37 + title: __('Executetime'),
  38 + operate: 'RANGE',
  39 + addclass: 'datetimerange',
  40 + formatter: Table.api.formatter.datetime
  41 + },
  42 + {
  43 + field: 'createtime',
  44 + title: __('Createtime'),
  45 + operate: 'RANGE',
  46 + addclass: 'datetimerange',
  47 + formatter: Table.api.formatter.datetime
  48 + },
  49 + {
  50 + field: 'updatetime',
  51 + title: __('Updatetime'),
  52 + operate: 'RANGE',
  53 + addclass: 'datetimerange',
  54 + formatter: Table.api.formatter.datetime
  55 + },
  56 + {
  57 + field: 'status',
  58 + title: __('Status'),
  59 + table: table,
  60 + custom: {"successed": 'success', "failured": 'danger'},
  61 + searchList: {"successed": __('Successed'), "failured": __('Failured')},
  62 + formatter: Table.api.formatter.status
  63 + },
  64 + {
  65 + field: 'operate',
  66 + title: __('Operate'),
  67 + buttons: [
  68 + {
  69 + name: 'execute',
  70 + title: __('Execute again'),
  71 + text: __('Execute again'),
  72 + url: 'command/execute',
  73 + icon: 'fa fa-repeat',
  74 + classname: 'btn btn-success btn-xs btn-execute btn-ajax',
  75 + success: function (data) {
  76 + Layer.alert("<textarea class='form-control' cols='60' rows='5'>" + data.result + "</textarea>", {
  77 + title: __("执行结果"),
  78 + shadeClose: true
  79 + });
  80 + table.bootstrapTable('refresh');
  81 + return false;
  82 + }
  83 + },
  84 + {
  85 + name: 'execute',
  86 + title: __('Detail'),
  87 + text: __('Detail'),
  88 + url: 'command/detail',
  89 + icon: 'fa fa-list',
  90 + classname: 'btn btn-info btn-xs btn-execute btn-dialog'
  91 + }
  92 + ],
  93 + table: table,
  94 + events: Table.api.events.operate,
  95 + formatter: Table.api.formatter.operate
  96 + }
  97 + ]
  98 + ]
  99 + });
  100 +
  101 + // 为表格绑定事件
  102 + Table.api.bindevent(table);
  103 + },
  104 + add: function () {
  105 + require(['bootstrap-select', 'bootstrap-select-lang']);
  106 + var mainfields = [];
  107 + var relationfields = {};
  108 + var maintable = [];
  109 + var relationtable = [];
  110 + var relationmode = ["belongsto", "hasone"];
  111 +
  112 + var renderselect = function (select, data) {
  113 + var html = [];
  114 + for (var i = 0; i < data.length; i++) {
  115 + html.push("<option value='" + data[i] + "'>" + data[i] + "</option>");
  116 + }
  117 + $(select).html(html.join(""));
  118 + select.trigger("change");
  119 + if (select.data("selectpicker")) {
  120 + select.selectpicker('refresh');
  121 + }
  122 + return select;
  123 + };
  124 +
  125 + $("select[name=table] option").each(function () {
  126 + maintable.push($(this).val());
  127 + });
  128 + $(document).on('change', "input[name='isrelation']", function () {
  129 + $("#relation-zone").toggleClass("hide", !$(this).prop("checked"));
  130 + });
  131 + $(document).on('change', "select[name='table']", function () {
  132 + var that = this;
  133 + Fast.api.ajax({
  134 + url: "command/get_field_list",
  135 + data: {table: $(that).val()},
  136 + }, function (data, ret) {
  137 + mainfields = data.fieldlist;
  138 + $("#relation-zone .relation-item").remove();
  139 + renderselect($("#fields"), mainfields);
  140 + return false;
  141 + });
  142 + return false;
  143 + });
  144 + $(document).on('click', "a.btn-newrelation", function () {
  145 + var that = this;
  146 + var index = parseInt($(that).data("index")) + 1;
  147 + var content = Template("relationtpl", {index: index});
  148 + content = $(content.replace(/\[index\]/, index));
  149 + $(this).data("index", index);
  150 + $(content).insertBefore($(that).closest(".row"));
  151 + $('select', content).selectpicker();
  152 + var exists = [$("select[name='table']").val()];
  153 + $("select.relationtable").each(function () {
  154 + exists.push($(this).val());
  155 + });
  156 + relationtable = [];
  157 + $.each(maintable, function (i, j) {
  158 + if ($.inArray(j, exists) < 0) {
  159 + relationtable.push(j);
  160 + }
  161 + });
  162 + renderselect($("select.relationtable", content), relationtable);
  163 + $("select.relationtable", content).trigger("change");
  164 + });
  165 + $(document).on('click', "a.btn-removerelation", function () {
  166 + $(this).closest(".row").remove();
  167 + });
  168 + $(document).on('change', "#relation-zone select.relationmode", function () {
  169 + var table = $("select.relationtable", $(this).closest(".row")).val();
  170 + var that = this;
  171 + Fast.api.ajax({
  172 + url: "command/get_field_list",
  173 + data: {table: table},
  174 + }, function (data, ret) {
  175 + renderselect($(that).closest(".row").find("select.relationprimarykey"), $(that).val() == 'belongsto' ? data.fieldlist : mainfields);
  176 + renderselect($(that).closest(".row").find("select.relationforeignkey"), $(that).val() == 'hasone' ? data.fieldlist : mainfields);
  177 + return false;
  178 + });
  179 + });
  180 + $(document).on('change', "#relation-zone select.relationtable", function () {
  181 + var that = this;
  182 + Fast.api.ajax({
  183 + url: "command/get_field_list",
  184 + data: {table: $(that).val()},
  185 + }, function (data, ret) {
  186 + renderselect($(that).closest(".row").find("select.relationmode"), relationmode);
  187 + renderselect($(that).closest(".row").find("select.relationfields"), mainfields)
  188 + renderselect($(that).closest(".row").find("select.relationforeignkey"), data.fieldlist)
  189 + renderselect($(that).closest(".row").find("select.relationfields"), data.fieldlist)
  190 + return false;
  191 + });
  192 + });
  193 + $(document).on('click', ".btn-command", function () {
  194 + var form = $(this).closest("form");
  195 + var textarea = $("textarea[rel=command]", form);
  196 + textarea.val('');
  197 + Fast.api.ajax({
  198 + url: "command/command/action/command",
  199 + data: form.serialize(),
  200 + }, function (data, ret) {
  201 + textarea.val(data.command);
  202 + return false;
  203 + });
  204 + });
  205 + $(document).on('click', ".btn-execute", function () {
  206 + var form = $(this).closest("form");
  207 + var textarea = $("textarea[rel=result]", form);
  208 + textarea.val('');
  209 + Fast.api.ajax({
  210 + url: "command/command/action/execute",
  211 + data: form.serialize(),
  212 + }, function (data, ret) {
  213 + textarea.val(data.result);
  214 + window.parent.$(".toolbar .btn-refresh").trigger('click');
  215 + top.window.Fast.api.refreshmenu();
  216 + return false;
  217 + }, function () {
  218 + window.parent.$(".toolbar .btn-refresh").trigger('click');
  219 + });
  220 + });
  221 + $("select[name='table']").trigger("change");
  222 + Controller.api.bindevent();
  223 + },
  224 + edit: function () {
  225 + Controller.api.bindevent();
  226 + },
  227 + api: {
  228 + bindevent: function () {
  229 + Form.api.bindevent($("form[role=form]"));
  230 + }
  231 + }
  232 + };
  233 + return Controller;
  234 +});
  1 +<?php
  2 +
  3 +namespace addons\database;
  4 +
  5 +use app\common\library\Menu;
  6 +use think\Addons;
  7 +
  8 +/**
  9 + * 数据库插件
  10 + */
  11 +class Database extends Addons
  12 +{
  13 +
  14 + /**
  15 + * 插件安装方法
  16 + * @return bool
  17 + */
  18 + public function install()
  19 + {
  20 + $menu = [
  21 + [
  22 + 'name' => 'general/database',
  23 + 'title' => '数据库管理',
  24 + 'icon' => 'fa fa-database',
  25 + 'remark' => '可在线进行一些简单的数据库表优化或修复,查看表结构和数据。也可以进行SQL语句的操作',
  26 + 'sublist' => [
  27 + ['name' => 'general/database/index', 'title' => '查看'],
  28 + ['name' => 'general/database/query', 'title' => '查询'],
  29 + ['name' => 'general/database/backup', 'title' => '备份'],
  30 + ['name' => 'general/database/restore', 'title' => '恢复'],
  31 + ]
  32 + ]
  33 + ];
  34 + Menu::create($menu, 'general');
  35 + return true;
  36 + }
  37 +
  38 + /**
  39 + * 插件卸载方法
  40 + * @return bool
  41 + */
  42 + public function uninstall()
  43 + {
  44 +
  45 + Menu::delete('general/database');
  46 + return true;
  47 + }
  48 +
  49 + /**
  50 + * 插件启用方法
  51 + */
  52 + public function enable()
  53 + {
  54 + Menu::enable('general/database');
  55 + }
  56 +
  57 + /**
  58 + * 插件禁用方法
  59 + */
  60 + public function disable()
  61 + {
  62 + Menu::disable('general/database');
  63 + }
  64 +
  65 +}
  1 +<?php
  2 +
  3 +namespace app\admin\controller\general;
  4 +
  5 +use addons\database\library\Backup;
  6 +use app\common\controller\Backend;
  7 +use think\Db;
  8 +use think\Debug;
  9 +use think\Exception;
  10 +use think\exception\PDOException;
  11 +use ZipArchive;
  12 +
  13 +/**
  14 + * 数据库管理
  15 + *
  16 + * @icon fa fa-database
  17 + * @remark 可在线进行一些简单的数据库表优化或修复,查看表结构和数据。也可以进行SQL语句的操作
  18 + */
  19 +class Database extends Backend
  20 +{
  21 +
  22 + protected $noNeedRight = ['backuplist'];
  23 +
  24 + /**
  25 + * 查看
  26 + */
  27 + function index()
  28 + {
  29 + $tables_data_length = $tables_index_length = $tables_free_length = $tables_data_count = 0;
  30 + $tables = $list = [];
  31 + $list = Db::query("SHOW TABLES");
  32 + foreach ($list as $key => $row) {
  33 + $tables[] = ['name' => reset($row), 'rows' => 0];
  34 + }
  35 + $data['tables'] = $tables;
  36 + $data['saved_sql'] = [];
  37 + $this->view->assign($data);
  38 + return $this->view->fetch();
  39 + }
  40 +
  41 + /**
  42 + * SQL查询
  43 + */
  44 + public function query()
  45 + {
  46 + $do_action = $this->request->post('do_action');
  47 +
  48 + echo '<style type="text/css">
  49 + xmp,body{margin:0;padding:0;line-height:18px;font-size:12px;font-family:"Helvetica Neue", Helvetica, Microsoft Yahei, Hiragino Sans GB, WenQuanYi Micro Hei, sans-serif;}
  50 + hr{height:1px;margin:5px 1px;background:#e3e3e3;border:none;}
  51 + </style>';
  52 + if ($do_action == '')
  53 + exit(__('Invalid parameters'));
  54 +
  55 + $tablename = $this->request->post("tablename/a");
  56 +
  57 + if (in_array($do_action, array('doquery', 'optimizeall', 'repairall'))) {
  58 + $this->$do_action();
  59 + } else if (count($tablename) == 0) {
  60 + exit(__('Invalid parameters'));
  61 + } else {
  62 + foreach ($tablename as $table) {
  63 + $this->$do_action($table);
  64 + echo "<br />";
  65 + }
  66 + }
  67 + }
  68 +
  69 + /**
  70 + * 备份列表
  71 + * @internal
  72 + */
  73 + public function backuplist()
  74 + {
  75 + $config = get_addon_config('database');
  76 + $backupDir = ROOT_PATH . 'public' . DS . $config['backupDir'];
  77 +
  78 + $backuplist = [];
  79 + foreach (glob($backupDir . "*.zip") as $filename) {
  80 + $time = filemtime($filename);
  81 + $backuplist[$time] =
  82 + [
  83 + 'file' => str_replace($backupDir, '', $filename),
  84 + 'date' => date("Y-m-d H:i:s", $time),
  85 + 'size' => format_bytes(filesize($filename))
  86 + ];
  87 + }
  88 + krsort($backuplist);
  89 +
  90 + $this->success("", null, ['backuplist' => array_values($backuplist)]);
  91 + }
  92 +
  93 + /**
  94 + * 还原
  95 + */
  96 + public function restore($ids = '')
  97 + {
  98 + $config = get_addon_config('database');
  99 + $backupDir = ROOT_PATH . 'public' . DS . $config['backupDir'];
  100 + if ($this->request->isPost()) {
  101 +
  102 + $action = $this->request->request('action');
  103 + $file = $this->request->request('file');
  104 + $file = $backupDir . $file;
  105 + if ($action == 'restore') {
  106 + try {
  107 + $dir = RUNTIME_PATH . 'database' . DS;
  108 + if (!is_dir($dir)) {
  109 + mkdir($dir, 0755);
  110 + }
  111 +
  112 + if (class_exists('ZipArchive')) {
  113 + $zip = new ZipArchive;
  114 + if ($zip->open($file) !== TRUE) {
  115 + throw new Exception(__('Can not open zip file'));
  116 + }
  117 + if (!$zip->extractTo($dir)) {
  118 + $zip->close();
  119 + throw new Exception(__('Can not unzip file'));
  120 + }
  121 + $zip->close();
  122 + $filename = basename($file);
  123 + $sqlFile = $dir . str_replace('.zip', '.sql', $filename);
  124 + if (!is_file($sqlFile)) {
  125 + throw new Exception(__('Sql file not found'));
  126 + }
  127 + $filesize = filesize($sqlFile);
  128 + $list = Db::query('SELECT @@global.max_allowed_packet');
  129 + if (isset($list[0]['@@global.max_allowed_packet']) && $filesize >= $list[0]['@@global.max_allowed_packet']) {
  130 + Db::execute('SET @@global.max_allowed_packet = ' . ($filesize + 1024));
  131 + //throw new Exception('备份文件超过配置max_allowed_packet大小,请修改Mysql服务器配置');
  132 + }
  133 + $sql = file_get_contents($sqlFile);
  134 +
  135 + Db::clear();
  136 + //必须重连一次
  137 + Db::connect([], true)->query("select 1");
  138 + Db::getPdo()->exec($sql);
  139 +
  140 + }
  141 + } catch (Exception $e) {
  142 + $this->error($e->getMessage());
  143 + } catch (PDOException $e) {
  144 + $this->error($e->getMessage());
  145 + }
  146 + $this->success(__('Restore successful'));
  147 +
  148 + } else if ($action == 'delete') {
  149 + unlink($file);
  150 + $this->success(__('Delete successful'));
  151 + }
  152 + }
  153 + }
  154 +
  155 + /**
  156 + * 备份
  157 + */
  158 + public function backup()
  159 + {
  160 + $config = get_addon_config('database');
  161 + $backupDir = ROOT_PATH . 'public' . DS . $config['backupDir'];
  162 + if ($this->request->isPost()) {
  163 + $database = config('database');
  164 + try {
  165 + $backup = new Backup($database['hostname'], $database['username'], $database['database'], $database['password'], $database['hostport']);
  166 + $backup->setIgnoreTable($config['backupIgnoreTables'])->backup($backupDir);
  167 + } catch (Exception $e) {
  168 + $this->error($e->getMessage());
  169 + }
  170 + $this->success(__('Backup successful'));
  171 + }
  172 + return;
  173 + }
  174 +
  175 + private function viewinfo($name)
  176 + {
  177 + $row = Db::query("SHOW CREATE TABLE `{$name}`");
  178 + $row = array_values($row[0]);
  179 + $info = $row[1];
  180 + echo "<xmp>{$info};</xmp>";
  181 + }
  182 +
  183 + private function viewdata($name = '')
  184 + {
  185 + $sqlquery = "SELECT * FROM `{$name}`";
  186 + $this->doquery($sqlquery);
  187 + }
  188 +
  189 + private function optimize($name = '')
  190 + {
  191 + if (Db::execute("OPTIMIZE TABLE `{$name}`")) {
  192 + echo __('Optimize table %s done', $name);
  193 + } else {
  194 + echo __('Optimize table %s fail', $name);
  195 + }
  196 + }
  197 +
  198 + private function optimizeall($name = '')
  199 + {
  200 + $list = Db::query("SHOW TABLES");
  201 + foreach ($list as $key => $row) {
  202 + $name = reset($row);
  203 + if (Db::execute("OPTIMIZE TABLE {$name}")) {
  204 + echo __('Optimize table %s done', $name);
  205 + } else {
  206 + echo __('Optimize table %s fail', $name);
  207 + }
  208 + echo "<br />";
  209 + }
  210 + }
  211 +
  212 + private function repair($name = '')
  213 + {
  214 + if (Db::execute("REPAIR TABLE `{$name}`")) {
  215 + echo __('Repair table %s done', $name);
  216 + } else {
  217 + echo __('Repair table %s fail', $name);
  218 + }
  219 + }
  220 +
  221 + private function repairall($name = '')
  222 + {
  223 + $list = Db::query("SHOW TABLES");
  224 + foreach ($list as $key => $row) {
  225 + $name = reset($row);
  226 + if (Db::execute("REPAIR TABLE {$name}")) {
  227 + echo __('Repair table %s done', $name);
  228 + } else {
  229 + echo __('Repair table %s fail', $name);
  230 + }
  231 + echo "<br />";
  232 + }
  233 + }
  234 +
  235 + private function doquery($sql = null)
  236 + {
  237 + $sqlquery = $sql ? $sql : $this->request->post('sqlquery');
  238 + if ($sqlquery == '')
  239 + exit(__('SQL can not be empty'));
  240 + $sqlquery = str_replace("\r", "", $sqlquery);
  241 + $sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery);
  242 + $maxreturn = 100;
  243 + $r = '';
  244 + foreach ($sqls as $key => $val) {
  245 + if (trim($val) == '')
  246 + continue;
  247 + $val = rtrim($val, ';');
  248 + $r .= "SQL:<span style='color:green;'>{$val}</span> ";
  249 + if (preg_match("/^(select|explain)(.*)/i ", $val)) {
  250 + Debug::remark("begin");
  251 + $limit = stripos(strtolower($val), "limit") !== false ? true : false;
  252 + $count = Db::execute($val);
  253 + if ($count > 0) {
  254 + $resultlist = Db::query($val . (!$limit && $count > $maxreturn ? ' LIMIT ' . $maxreturn : ''));
  255 + } else {
  256 + $resultlist = [];
  257 + }
  258 + Debug::remark("end");
  259 + $time = Debug::getRangeTime('begin', 'end', 4);
  260 +
  261 + $usedseconds = __('Query took %s seconds', $time) . "<br />";
  262 + if ($count <= 0) {
  263 + $r .= __('Query returned an empty result');
  264 + } else {
  265 + $r .= (__('Total:%s', $count) . (!$limit && $count > $maxreturn ? ',' . __('Max output:%s', $maxreturn) : ""));
  266 + }
  267 + $r = $r . ',' . $usedseconds;
  268 + $j = 0;
  269 + foreach ($resultlist as $m => $n) {
  270 + $j++;
  271 + if (!$limit && $j > $maxreturn)
  272 + break;
  273 + $r .= "<hr/>";
  274 + $r .= "<font color='red'>" . __('Row:%s', $j) . "</font><br />";
  275 + foreach ($n as $k => $v) {
  276 + $r .= "<font color='blue'>{$k}:</font>{$v}<br/>\r\n";
  277 + }
  278 + }
  279 + } else {
  280 + Debug::remark("begin");
  281 + $count = Db::execute($val);
  282 + Debug::remark("end");
  283 + $time = Debug::getRangeTime('begin', 'end', 4);
  284 + $r .= __('Query affected %s rows and took %s seconds', $count, $time) . "<br />";
  285 + }
  286 + }
  287 + echo $r;
  288 + }
  289 +
  290 +}
  1 +<?php
  2 +
  3 +return [
  4 + 'SQL Result' => '查询结果',
  5 + 'Basic query' => '基础查询',
  6 + 'View structure' => '查看表结构',
  7 + 'View data' => '查看表数据',
  8 + 'Backup and Restore' => '备份与还原',
  9 + 'Backup now' => '立即备份',
  10 + 'File' => '文件',
  11 + 'Size' => '大小',
  12 + 'Date' => '备份日期',
  13 + 'Restore' => '还原',
  14 + 'Delete' => '删除',
  15 + 'Optimize' => '优化表',
  16 + 'Repair' => '修复表',
  17 + 'Optimize all' => '优化全部表',
  18 + 'Repair all' => '修复全部表',
  19 + 'Backup successful' => '备份成功',
  20 + 'Restore successful' => '还原成功',
  21 + 'Delete successful' => '删除成功',
  22 + 'Can not open zip file' => '无法打开备份文件',
  23 + 'Can not unzip file' => '无法解压备份文件',
  24 + 'Sql file not found' => '未找到SQL文件',
  25 + 'Table:%s' => '总计:%s个表',
  26 + 'Record:%s' => '记录:%s条',
  27 + 'Data:%s' => '占用:%s',
  28 + 'Index:%s' => '索引:%s',
  29 + 'SQL Result:' => '查询结果:',
  30 + 'SQL can not be empty' => 'SQL语句不能为空',
  31 + 'Max output:%s' => '最大返回%s条',
  32 + 'Total:%s' => '共有%s条记录! ',
  33 + 'Row:%s' => '记录:%s',
  34 + 'Executes one or multiple queries which are concatenated by a semicolon' => '请输入SQL语句,支持批量查询,多条SQL以分号(;)分格',
  35 + 'Query affected %s rows and took %s seconds' => '共影响%s条记录! 耗时:%s秒!',
  36 + 'Query returned an empty result' => '返回结果为空!',
  37 + 'Query took %s seconds' => '耗时%s秒!',
  38 + 'Optimize table %s done' => '优化表[%s]成功',
  39 + 'Repair table %s done' => '修复表[%s]成功',
  40 + 'Optimize table %s fail' => '优化表[%s]失败',
  41 + 'Repair table %s fail' => '修复表[%s]失败'
  42 +];
  43 +
  1 +<style type="text/css">
  2 + #searchfloat {position:absolute;top:40px;right:20px;background:#F7F0A0;padding:10px;}
  3 + #saved {position: relative;}
  4 + #saved_sql {position:absolute;bottom:0;height:300px;background:#F7F0A0;width:100%;overflow:auto;display:none;}
  5 + #saved_sql li {display:block;clear:both;width:100%;float:left;line-height:18px;padding:1px 0}
  6 + #saved_sql li a{float:left;text-decoration: none;display:block;padding:0 5px;}
  7 + #saved_sql li i{display:none;float:left;color:#06f;font-size: 14px;font-style: normal;margin-left:2px;line-height:18px;}
  8 + #saved_sql li:hover{background:#fff;}
  9 + #saved_sql li:hover i{display:block;cursor:pointer;}
  10 + #database #tablename {height:205px;width:100%;padding:5px;}
  11 + #database #tablename option{height:18px;}
  12 + #database #subaction {height:210px;width:100%;}
  13 + #database .select-striped > option:nth-of-type(odd) {background-color: #f9f9f9;}
  14 + #database .dropdown-menu ul {margin:-3px 0;}
  15 + #database .dropdown-menu ul li{margin:3px 0;}
  16 + #database .dropdown-menu.row .col-xs-6{padding:0 5px;}
  17 + #sqlquery {font-size:12px;color:#444;}
  18 + #resultparent {padding:5px;}
  19 +</style>
  20 +<div class="panel panel-default panel-intro">
  21 + {:build_heading()}
  22 +
  23 + <div class="panel-body">
  24 + <div id="database" class="tab-content">
  25 + <div class="tab-pane fade active in" id="one">
  26 + <div class="widget-body no-padding">
  27 + {if $auth->check('general/database/query')}
  28 + <div class="row">
  29 + <div class="col-xs-4">
  30 + <h4>{:__('SQL Result')}:</h4>
  31 + </div>
  32 + <div class="col-xs-8 text-right">
  33 + <form action="{:url('general.database/query')}" method="post" name="infoform" target="resultframe">
  34 + <input type="hidden" name="do_action" id="topaction" />
  35 + <a href="javascript:;" class="btn btn-success btn-compress"><i class="fa fa-compress"></i> {:__('Backup and Restore')}</a>
  36 + <div class="btn-group">
  37 + <button data-toggle="dropdown" class="btn btn-primary btn-embossed dropdown-toggle" type="button">{:__('Basic query')} <span class="caret"></span></button>
  38 + <div class="row dropdown-menu pull-right" style="width:300px;">
  39 + <div class="col-xs-6">
  40 + <select class="form-control select-striped" id="tablename" name="tablename[]" multiple="multiple">
  41 + {foreach $tables as $table}
  42 + <option value="{$table.name}" title="">{$table.name}<!--({$table.rows})--></option>
  43 + {/foreach}
  44 + </select>
  45 + </div>
  46 + <div class="col-xs-6">
  47 + <ul id="subaction" class="list-unstyled">
  48 + <li><input type="submit" name="submit1" value="{:__('View structure')}" rel="viewinfo" class="btn btn-primary btn-embossed btn-sm btn-block"/></li>
  49 + <li><input type="submit" name="submit2" value="{:__('View data')}" rel="viewdata" class="btn btn-primary btn-embossed btn-sm btn-block"/></li>
  50 + <li><input type="submit" name="submit3" value="{:__('Optimize')}" rel="optimize" class="btn btn-primary btn-embossed btn-sm btn-block" /></li>
  51 + <li><input type="submit" name="submit4" value="{:__('Repair')}" rel="repair" class="btn btn-primary btn-embossed btn-sm btn-block"/></li>
  52 + <li><input type="submit" name="submit5" value="{:__('Optimize all')}" rel="optimizeall" class="btn btn-primary btn-embossed btn-sm btn-block" /></li>
  53 + <li><input type="submit" name="submit6" value="{:__('Repair all')}" rel="repairall" class="btn btn-primary btn-embossed btn-sm btn-block" /></li>
  54 + </ul>
  55 + </div>
  56 + <div class="clear"></div>
  57 + </div>
  58 +
  59 + </div>
  60 + </form>
  61 + </div>
  62 +
  63 + </div>
  64 + <div class="well" id="resultparent">
  65 + <iframe name="resultframe" frameborder="0" id="resultframe" style="height:100%;" width="100%" height="100%"></iframe>
  66 + </div>
  67 + <form action="{:url('general.database/query')}" method="post" id="sqlexecute" name="form1" target="resultframe">
  68 + <input type="hidden" name="do_action" value="doquery" />
  69 + <div class="form-group">
  70 + <textarea name="sqlquery" placeholder="{:__('Executes one or multiple queries which are concatenated by a semicolon')}" cols="60" rows="5" class="form-control" id="sqlquery"></textarea>
  71 + </div>
  72 +
  73 + <input type="submit" class="btn btn-success btn-embossed" value="{:__('Execute')}" />
  74 + <input type="reset" class="btn btn-default btn-embossed" value="{:__('Reset')}" />
  75 + </form>
  76 + {else /}
  77 + <div id="backuplist"></div>
  78 + {/if}
  79 + </div>
  80 + </div>
  81 +
  82 + </div>
  83 + </div>
  84 +</div>
  85 +
  86 +<script id="backuptpl" type="text/html">
  87 + <p><a href="javascript:;" class="btn btn-success btn-backup"><i class="fa fa-compress"></i> {:__('Backup now')}</a></p>
  88 +
  89 + <table id="dt_basic" class="table table-striped table-bordered table-hover" width="100%" style="min-width:500px;font-size:12px;">
  90 + <thead>
  91 + <tr>
  92 + <th>ID</th>
  93 + <th>{:__('File')}</th>
  94 + <th>{:__('Size')}</th>
  95 + <th>{:__('Date')}</th>
  96 + <th>{:__('Operate')}</th>
  97 + </tr>
  98 + </thead>
  99 + <tbody>
  100 + <%for (var i=0;i<backuplist.length;i++){%>
  101 + <tr>
  102 + <td><%=i+1%></td>
  103 + <td><%=backuplist[i].file%></td>
  104 + <td><%=backuplist[i].size%></td>
  105 + <td><%=backuplist[i].date%></td>
  106 + <td>
  107 + <a href="javascript:;" class="btn btn-primary btn-restore btn-xs" data-file="<%=backuplist[i].file%>"><i class="fa fa-reply"></i> {:__('Restore')}</a>
  108 + <a href="javascript:;" class="btn btn-danger btn-delete btn-xs" data-file="<%=backuplist[i].file%>"><i class="fa fa-times"></i> {:__('Delete')}</a>
  109 + </td>
  110 + </tr>
  111 + <%}%>
  112 + </tbody>
  113 + </table>
  114 +</script>
  1 +<?php
  2 +
  3 +return array (
  4 + 0 =>
  5 + array (
  6 + 'name' => 'backupDir',
  7 + 'title' => '备份存放目录',
  8 + 'type' => 'string',
  9 + 'content' =>
  10 + array (
  11 + ),
  12 + 'value' => '../data/',
  13 + 'rule' => 'required',
  14 + 'msg' => '',
  15 + 'tip' => '备份目录,请使用相对目录',
  16 + 'ok' => '',
  17 + 'extend' => '',
  18 + ),
  19 + 1 =>
  20 + array (
  21 + 'name' => 'backupIgnoreTables',
  22 + 'title' => '备份忽略的表',
  23 + 'type' => 'string',
  24 + 'content' =>
  25 + array (
  26 + ),
  27 + 'value' => 'fa_admin_log',
  28 + 'rule' => '',
  29 + 'msg' => '',
  30 + 'tip' => '忽略备份的表,多个表以,进行分隔',
  31 + 'ok' => '',
  32 + 'extend' => '',
  33 + ),
  34 + 2 =>
  35 + array (
  36 + 'name' => '__tips__',
  37 + 'title' => '温馨提示',
  38 + 'type' => '',
  39 + 'content' =>
  40 + array (
  41 + ),
  42 + 'value' => '请做好数据库离线备份工作,建议此插件仅用于开发阶段,项目正式上线建议卸载此插件',
  43 + 'rule' => '',
  44 + 'msg' => '',
  45 + 'tip' => '',
  46 + 'ok' => '',
  47 + 'extend' => '',
  48 + ),
  49 +);
  1 +<?php
  2 +
  3 +namespace addons\database\controller;
  4 +
  5 +use think\addons\Controller;
  6 +
  7 +class Index extends Controller
  8 +{
  9 +
  10 + public function index()
  11 + {
  12 + $this->error("当前插件暂无前台页面");
  13 + }
  14 +
  15 +}
  16 +
  1 +name = database
  2 +title = 数据库管理
  3 +intro = 数据库管理插件
  4 +author = Karson
  5 +website = http://www.fastadmin.net
  6 +version = 1.0.3
  7 +state = 1
  8 +url = /addons/database.html
  1 +<?php
  2 +
  3 +namespace addons\database\library;
  4 +
  5 +use Exception;
  6 +use PDO;
  7 +use ZipArchive;
  8 +
  9 +class Backup
  10 +{
  11 +
  12 + private $host = '';
  13 + private $user = '';
  14 + private $name = '';
  15 + private $pass = '';
  16 + private $port = '';
  17 + private $tables = ['*'];
  18 + private $ignoreTables = [];
  19 + private $db;
  20 + private $ds = "\n";
  21 +
  22 + public function __construct($host = NULL, $user = NULL, $name = NULL, $pass = NULL, $port = 3306)
  23 + {
  24 + if ($host !== NULL) {
  25 + $this->host = $host;
  26 + $this->name = $name;
  27 + $this->port = $port;
  28 + $this->pass = $pass;
  29 + $this->user = $user;
  30 + }
  31 + $this->db = new PDO('mysql:host=' . $this->host . ';dbname=' . $this->name . '; port=' . $port, $this->user, $this->pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
  32 +
  33 + $this->db->exec('SET NAMES "utf8"');
  34 + }
  35 +
  36 + /**
  37 + * 设置备份表
  38 + * @param $table
  39 + * @return $this
  40 + */
  41 + public function setTable($table)
  42 + {
  43 + if ($table) {
  44 + $this->tables = is_array($table) ? $table : explode(',', $table);
  45 + }
  46 + return $this;
  47 + }
  48 +
  49 + /**
  50 + * 设置忽略备份的表
  51 + * @param $table
  52 + * @return $this
  53 + */
  54 + public function setIgnoreTable($table)
  55 + {
  56 + if ($table) {
  57 + $this->ignoreTables = is_array($table) ? $table : explode(',', $table);
  58 + }
  59 + return $this;
  60 + }
  61 +
  62 + public function backup($backUpdir = 'download/')
  63 + {
  64 + $sql = $this->_init();
  65 + $zip = new ZipArchive();
  66 + $date = date('YmdHis');
  67 + if (!is_dir($backUpdir)) {
  68 + @mkdir($backUpdir, 0755);
  69 + }
  70 + $name = "backup-{$this->name}-{$date}";
  71 + $filename = $backUpdir . $name . ".zip";
  72 +
  73 + if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) {
  74 + throw new Exception("Could not open <$filename>\n");
  75 + }
  76 + $zip->addFromString($name . ".sql", $sql);
  77 + $zip->close();
  78 + }
  79 +
  80 + private function _init()
  81 + {
  82 + # COUNT
  83 + $ct = 0;
  84 + # CONTENT
  85 + $sqldump = '';
  86 + # COPYRIGHT & OPTIONS
  87 + $sqldump .= "-- SQL Dump by Erik Edgren\n";
  88 + $sqldump .= "-- version 1.0\n";
  89 + $sqldump .= "-- http://erik-edgren.nu/ (swedish blog)\n";
  90 + $sqldump .= "--\n";
  91 + $sqldump .= "-- SQL Dump created: " . date('F jS, Y \@ g:i a') . "\n\n";
  92 + $sqldump .= "SET SQL_MODE=\"NO_AUTO_VALUE_ON_ZERO\";";
  93 + $sqldump .= "\n\n\n\n-- --------------------------------------------------------\n\n\n\n";
  94 + $tables = $this->db->query("SHOW TABLES");
  95 + # LOOP: Get the tables
  96 + foreach ($tables AS $table) {
  97 + # COUNT
  98 + $ct++;
  99 + /** ** ** ** ** **/
  100 + # DATABASE: Count the rows in each tables
  101 + $count_rows = $this->db->prepare("SELECT * FROM " . $table[0]);
  102 + $count_rows->execute();
  103 + $c_rows = $count_rows->columnCount();
  104 + # DATABASE: Count the columns in each tables
  105 + $count_columns = $this->db->prepare("SELECT COUNT(*) FROM " . $table[0]);
  106 + $count_columns->execute();
  107 + $c_columns = $count_columns->fetchColumn();
  108 + /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/
  109 + # MYSQL DUMP: Remove tables if they exists
  110 + $sqldump .= "--\n";
  111 + $sqldump .= "-- Remove the table if it exists\n";
  112 + $sqldump .= "--\n\n";
  113 + $sqldump .= "DROP TABLE IF EXISTS `" . $table[0] . "`;\n\n\n";
  114 + /** ** ** ** ** **/
  115 + # MYSQL DUMP: Create table if they do not exists
  116 + $sqldump .= "--\n";
  117 + $sqldump .= "-- Create the table if it not exists\n";
  118 + $sqldump .= "--\n\n";
  119 + # LOOP: Get the fields for the table
  120 + foreach ($this->db->query("SHOW CREATE TABLE " . $table[0]) AS $field) {
  121 + $sqldump .= str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $field['Create Table']);
  122 + }
  123 + # MYSQL DUMP: New rows
  124 + $sqldump .= ";\n\n\n";
  125 + /** ** ** ** ** **/
  126 + # CHECK: There are one or more columns
  127 + if ($c_columns != 0) {
  128 + # MYSQL DUMP: List the data for each table
  129 + $sqldump .= "--\n";
  130 + $sqldump .= "-- List the data for the table\n";
  131 + $sqldump .= "--\n\n";
  132 + # MYSQL DUMP: Insert into each table
  133 + $sqldump .= "INSERT INTO `" . $table[0] . "` (";
  134 + # ARRAY
  135 + $rows = Array();
  136 + # LOOP: Get the tables
  137 + foreach ($this->db->query("DESCRIBE " . $table[0]) AS $row) {
  138 + $rows[] = "`" . $row[0] . "`";
  139 + }
  140 + $sqldump .= implode(', ', $rows);
  141 + $sqldump .= ") VALUES\n";
  142 + # COUNT
  143 + $c = 0;
  144 + # LOOP: Get the tables
  145 + foreach ($this->db->query("SELECT * FROM " . $table[0]) AS $data) {
  146 + # COUNT
  147 + $c++;
  148 + /** ** ** ** ** **/
  149 + $sqldump .= "(";
  150 + # ARRAY
  151 + $cdata = Array();
  152 + # LOOP
  153 + for ($i = 0; $i < $c_rows; $i++) {
  154 + $new_lines = preg_replace('/\s\s+/', '\r\n\r\n', addslashes($data[$i]));
  155 + $cdata[] = "'" . $new_lines . "'";
  156 + }
  157 + $sqldump .= implode(', ', $cdata);
  158 + $sqldump .= ")";
  159 + $sqldump .= ($c % 600 != 0 ? ($c_columns != $c ? ',' : ';') : '');
  160 + # CHECK
  161 + if ($c % 600 == 0) {
  162 + $sqldump .= ";\n\n";
  163 + } else {
  164 + $sqldump .= "\n";
  165 + }
  166 + # CHECK
  167 + if ($c % 600 == 0) {
  168 + $sqldump .= "INSERT INTO " . $table[0] . "(";
  169 + # ARRAY
  170 + $rows = Array();
  171 + # LOOP: Get the tables
  172 + foreach ($this->db->query("DESCRIBE " . $table[0]) AS $row) {
  173 + $rows[] = "`" . $row[0] . "`";
  174 + }
  175 + $sqldump .= implode(', ', $rows);
  176 + $sqldump .= ") VALUES\n";
  177 + }
  178 + }
  179 + }
  180 + }
  181 + return $sqldump;
  182 +
  183 + }
  184 +
  185 +}
  1 +define(['jquery', 'bootstrap', 'backend', 'template'], function ($, undefined, Backend, Template) {
  2 +
  3 + var Controller = {
  4 + index: function () {
  5 +
  6 + //如果有备份权限
  7 + if ($("#backuplist").size() > 0) {
  8 + Fast.api.ajax({
  9 + url: "general/database/backuplist",
  10 + type: 'get'
  11 + }, function (data) {
  12 + $("#backuplist").html(Template("backuptpl", {backuplist: data.backuplist}));
  13 + return false;
  14 + });
  15 + return false;
  16 + }
  17 +
  18 + //禁止在操作select元素时关闭dropdown的关闭事件
  19 + $("#database").on('click', '.dropdown-menu input, .dropdown-menu label, .dropdown-menu select', function (e) {
  20 + e.stopPropagation();
  21 + });
  22 +
  23 + //提交时检查是否有删除或清空操作
  24 + $("#database").on("submit", "#sqlexecute", function () {
  25 + var v = $("#sqlquery").val().toLowerCase();
  26 + if ((v.indexOf("delete ") >= 0 || v.indexOf("truncate ") >= 0) && !confirm(__('Are you sure you want to delete or turncate?'))) {
  27 + return false;
  28 + }
  29 + });
  30 +
  31 + //事件按钮操作
  32 + $("#database").on("click", "ul#subaction li input", function () {
  33 + $("#topaction").val($(this).attr("rel"));
  34 + return true;
  35 + });
  36 +
  37 + //窗口变更的时候重设结果栏高度
  38 + $(window).on("resize", function () {
  39 + $("#database .well").height($(window).height() - $("#database #sqlexecute").height() - $("#ribbon").outerHeight() - $(".panel-heading").outerHeight() - 130);
  40 + });
  41 +
  42 + //修复iOS下iframe无法滚动的BUG
  43 + if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
  44 + $("#resultparent").css({"-webkit-overflow-scrolling": "touch", "overflow": "auto"});
  45 + }
  46 +
  47 + $(document).on("click", ".btn-compress", function () {
  48 + Fast.api.ajax({
  49 + url: "general/database/backuplist",
  50 + type: 'get'
  51 + }, function (data) {
  52 + Layer.open({
  53 + area: ["680px", "500px"],
  54 + btn: [],
  55 + title: "备份与还原",
  56 + content: Template("backuptpl", {backuplist: data.backuplist})
  57 + });
  58 + return false;
  59 + });
  60 + return false;
  61 + });
  62 +
  63 + $(document).on("click", ".btn-backup", function () {
  64 + Fast.api.ajax({
  65 + url: "general/database/backup",
  66 + data: {action: 'backup'}
  67 + }, function (data) {
  68 + Layer.closeAll();
  69 + $(".btn-compress").trigger("click");
  70 + });
  71 + });
  72 +
  73 + $(document).on("click", ".btn-restore", function () {
  74 + var that = this;
  75 + Layer.confirm("确定恢复备份?<br><font color='red'>建议先备份当前数据后再进行恢复操作!!!</font><br><font color='red'>当前数据库将被清空覆盖,请谨慎操作!!!</font>", {
  76 + type: 5,
  77 + skin: 'layui-layer-dialog layui-layer-fast'
  78 + }, function (index) {
  79 + Fast.api.ajax({
  80 + url: "general/database/restore",
  81 + data: {action: 'restore', file: $(that).data('file')}
  82 + }, function (data) {
  83 + Layer.closeAll();
  84 + Fast.api.ajax({
  85 + url: 'ajax/wipecache',
  86 + data: {type: 'all'},
  87 + }, function () {
  88 + Layer.alert("备份恢复成功,点击确定将刷新页面", function () {
  89 + top.location.reload();
  90 + });
  91 + return false;
  92 + });
  93 +
  94 + });
  95 + });
  96 + });
  97 +
  98 + $(document).on("click", ".btn-delete", function () {
  99 + var that = this;
  100 + Layer.confirm("确定删除备份?", {type: 5, skin: 'layui-layer-dialog layui-layer-fast'}, function (index) {
  101 + Fast.api.ajax({
  102 + url: "general/database/restore",
  103 + data: {action: 'delete', file: $(that).data('file')}
  104 + }, function (data) {
  105 + Layer.closeAll();
  106 + $(".btn-compress").trigger("click");
  107 + });
  108 + });
  109 + });
  110 +
  111 + $(window).resize();
  112 + }
  113 + };
  114 + return Controller;
  115 +});
  1 +<?php
  2 +
  3 +namespace addons\epay;
  4 +
  5 +use app\common\library\Menu;
  6 +use think\Addons;
  7 +use think\Config;
  8 +use think\Loader;
  9 +
  10 +/**
  11 + * 微信支付宝
  12 + */
  13 +class Epay extends Addons
  14 +{
  15 +
  16 + /**
  17 + * 插件安装方法
  18 + * @return bool
  19 + */
  20 + public function install()
  21 + {
  22 +
  23 + return true;
  24 + }
  25 +
  26 + /**
  27 + * 插件卸载方法
  28 + * @return bool
  29 + */
  30 + public function uninstall()
  31 + {
  32 +
  33 + return true;
  34 + }
  35 +
  36 + /**
  37 + * 插件启用方法
  38 + * @return bool
  39 + */
  40 + public function enable()
  41 + {
  42 +
  43 + return true;
  44 + }
  45 +
  46 + /**
  47 + * 插件禁用方法
  48 + * @return bool
  49 + */
  50 + public function disable()
  51 + {
  52 +
  53 + return true;
  54 + }
  55 +
  56 + /**
  57 + * 添加命名空间
  58 + */
  59 + public function appInit()
  60 + {
  61 + //添加支付包的命名空间
  62 + Loader::addNamespace('Yansongda', ADDON_PATH . 'epay' . DS . 'library' . DS . 'Yansongda' . DS);
  63 + }
  64 +
  65 +}
  1 +/*!
  2 + * Start Bootstrap - Modern Business (http://startbootstrap.com/)
  3 + * Copyright 2013-2016 Start Bootstrap
  4 + * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE)
  5 + */
  6 +/* Global Styles */
  7 +html,
  8 +body {
  9 + height: 100%;
  10 +}
  11 +body {
  12 + padding-top: 50px;
  13 + /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */
  14 + -webkit-font-smoothing: antialiased;
  15 + -moz-osx-font-smoothing: grayscale;
  16 + font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  17 +}
  18 +.img-addon {
  19 + margin-bottom: 10px;
  20 + width: 100%;
  21 +}
  22 +.img-hover:hover {
  23 + opacity: 0.8;
  24 +}
  25 +.display-1 {
  26 + font-size: 44px;
  27 +}
  28 +.display-4 {
  29 + font-size: 24px;
  30 + line-height: 32px;
  31 +}
  32 +/* Home Page Carousel */
  33 +header.carousel {
  34 + height: 50%;
  35 +}
  36 +header.carousel .item,
  37 +header.carousel .item.active,
  38 +header.carousel .carousel-inner {
  39 + height: 100%;
  40 +}
  41 +header.carousel .fill {
  42 + width: 100%;
  43 + height: 100%;
  44 +}
  45 +.error-404 {
  46 + font-size: 100px;
  47 +}
  48 +/* Pricing Page Styles */
  49 +.price {
  50 + display: block;
  51 + font-size: 50px;
  52 + line-height: 50px;
  53 +}
  54 +.price sup {
  55 + top: -20px;
  56 + left: 2px;
  57 + font-size: 20px;
  58 +}
  59 +.period {
  60 + display: block;
  61 + font-style: italic;
  62 +}
  63 +/* Footer Styles */
  64 +footer {
  65 + margin: 50px 0;
  66 +}
  67 +/* Responsive Styles */
  68 +@media (max-width: 991px) {
  69 + .customer-img,
  70 + .img-related {
  71 + margin-bottom: 30px;
  72 + }
  73 +}
  74 +@media (max-width: 767px) {
  75 + .img-addon {
  76 + margin-bottom: 15px;
  77 + }
  78 + header.carousel .carousel {
  79 + height: 70%;
  80 + }
  81 +}
  82 +.carousel-body {
  83 + position: absolute;
  84 + width: 100%;
  85 + top: 25%;
  86 + text-align: center;
  87 + color: #fff;
  88 +}
  89 +.addonlist a > p {
  90 + margin-bottom: 15px;
  91 +}
  1 +@import url("../../../css/bootstrap.min.css");
  2 +@import url("../../../libs/font-awesome/css/font-awesome.min.css");
  3 +html,
  4 +body {
  5 + height: 100%;
  6 + -webkit-font-smoothing: antialiased;
  7 + -moz-osx-font-smoothing: grayscale;
  8 + font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  9 + font-weight: 400;
  10 + overflow-x: hidden;
  11 + overflow-y: auto;
  12 + background: #f4f6f8;
  13 + font-size: 14px;
  14 + color: #616161;
  15 +}
  16 +.container {
  17 + max-width: 850px;
  18 + margin: 0 auto;
  19 + padding: 50px;
  20 +}
  1 +.wechat {
  2 + margin-top: 30px;
  3 +}
  4 +
  5 +.wechat h2 {
  6 + margin: 0 0 15px 0;
  7 + padding-bottom: 15px;
  8 + border-bottom: 1px solid #eee;
  9 + position: relative;
  10 +}
  11 +
  12 +.wechat-body {
  13 +}
  14 +
  15 +.wechat-qrcode {
  16 + margin-bottom: 20px;
  17 + position: relative;
  18 +}
  19 +
  20 +.wechat-qrcode img {
  21 + width: 100%;
  22 + border: 1px solid #eee;
  23 +}
  24 +
  25 +.wechat-qrcode .expired {
  26 + position: absolute;
  27 + top: 0;
  28 + left: 0;
  29 + height: 100%;
  30 + width: 100%;
  31 + opacity: .95;
  32 + background: #fff url(../images/expired.png) center center no-repeat;
  33 +}
  34 +
  35 +.wechat-qrcode .paid {
  36 + position: absolute;
  37 + top: 0;
  38 + left: 0;
  39 + height: 100%;
  40 + width: 100%;
  41 + opacity: .95;
  42 + background: #fff url(../images/paid.png) center center no-repeat;
  43 +}
  44 +
  45 +.wechat-scan {
  46 + padding: 0;
  47 +}
  48 +
  49 +.wechat-scan img {
  50 + width: 100%;
  51 +}
  52 +
  53 +.wechat-tips {
  54 + height: 60px;
  55 + padding: 8px 0 8px 125px;
  56 + background: #00c800 url(../images/scan.png) 50px 12px no-repeat;
  57 + background-size: 36px 36px;
  58 +}
  59 +
  60 +.wechat-tips p {
  61 + margin: 0;
  62 + font-size: 14px;
  63 + line-height: 22px;
  64 + color: #fff;
  65 + font-weight: 700
  66 +}
  67 +
  68 +.wechat-time {
  69 + font-size: 14px;
  70 + margin-bottom: 15px;
  71 + position: absolute;
  72 + top: 15px;
  73 + right: 10px;
  74 + font-weight: normal;
  75 + display: none;
  76 +}
  77 +
  78 +.wechat-time span {
  79 + color: red;
  80 +}
  81 +
  82 +.wechat-order {
  83 + margin-bottom: 5px;
  84 +}
  85 +
  86 +.wechat-order em {
  87 + font-style: normal;
  88 + color: #666;
  89 +}
  90 +
  91 +.wechat-order em.wechat-price {
  92 + color: #ff3333;
  93 + font-weight: bold;
  94 +}
  95 +
  96 +@media (max-width: 767px) {
  97 + .wechat {
  98 + margin-top: 20px;
  99 + }
  100 +}
  1 +$(function () {
  2 + $('.carousel').carousel({
  3 + interval: 5000 //changes the speed
  4 + });
  5 + $(".btn-experience").on("click", function () {
  6 + location.href = "/addons/epay/index/experience?amount=" + $("input[name=amount]").val() + "&type=" + $(this).data("type") + "&method=" + $("#method").val();
  7 + });
  8 +
  9 + var si, xhr;
  10 + if (typeof queryParams != 'undefined') {
  11 + var queryResult = function () {
  12 + xhr && xhr.abort();
  13 + xhr = $.ajax({
  14 + url: "",
  15 + type: "post",
  16 + data: queryParams,
  17 + dataType: 'json',
  18 + success: function (ret) {
  19 + if (ret.code == 1) {
  20 + var data = ret.data;
  21 + console.log(data);
  22 + if (typeof data.trade_state != 'undefined') {
  23 + if (data.trade_state == 'SUCCESS') {
  24 + $(".wechat-qrcode .paid").removeClass("hidden");
  25 + $(".wechat-tips p").html("支付成功!<br>3秒后将自动跳转...");
  26 + setTimeout(function () {
  27 + location.href = queryParams.return_url;
  28 + }, 3000);
  29 + clearInterval(si);
  30 + } else if (data.trade_state == 'REFUND') {
  31 + $(".wechat-tips p").html("请求失败!<br>请返回重新发起支付");
  32 + clearInterval(si);
  33 + } else if (data.trade_state == 'NOTPAY') {
  34 + } else if (data.trade_state == 'CLOSED') {
  35 + $(".wechat-tips p").html("订单已关闭!<br>请返回重新发起支付");
  36 + clearInterval(si);
  37 + } else if (data.trade_state == 'USERPAYING') {
  38 + } else if (data.trade_state == 'PAYERROR') {
  39 + clearInterval(si);
  40 + }
  41 + }
  42 + }
  43 + }
  44 + });
  45 + };
  46 + si = setInterval(function () {
  47 + queryResult();
  48 + }, 3000);
  49 + queryResult();
  50 + }
  51 +
  52 +});
  1 +/*!
  2 + * Start Bootstrap - Modern Business (http://startbootstrap.com/)
  3 + * Copyright 2013-2016 Start Bootstrap
  4 + * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE)
  5 + */
  6 +
  7 +/* Global Styles */
  8 +
  9 +html,
  10 +body {
  11 + height: 100%;
  12 +}
  13 +
  14 +body {
  15 + padding-top: 50px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */
  16 + -webkit-font-smoothing: antialiased;
  17 + -moz-osx-font-smoothing: grayscale;
  18 + font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  19 +
  20 +}
  21 +
  22 +.img-addon {
  23 + margin-bottom: 10px;
  24 + width:100%;
  25 +}
  26 +
  27 +.img-hover:hover {
  28 + opacity: 0.8;
  29 +}
  30 +
  31 +.display-1 {
  32 + font-size:44px;
  33 +}
  34 +.display-4 {
  35 + font-size:24px;
  36 + line-height:32px;
  37 +}
  38 +
  39 +/* Home Page Carousel */
  40 +
  41 +header.carousel {
  42 + height: 50%;
  43 +}
  44 +
  45 +header.carousel .item,
  46 +header.carousel .item.active,
  47 +header.carousel .carousel-inner {
  48 + height: 100%;
  49 +}
  50 +
  51 +header.carousel .fill {
  52 + width: 100%;
  53 + height: 100%;
  54 +}
  55 +
  56 +.error-404 {
  57 + font-size: 100px;
  58 +}
  59 +
  60 +/* Pricing Page Styles */
  61 +
  62 +.price {
  63 + display: block;
  64 + font-size: 50px;
  65 + line-height: 50px;
  66 +}
  67 +
  68 +.price sup {
  69 + top: -20px;
  70 + left: 2px;
  71 + font-size: 20px;
  72 +}
  73 +
  74 +.period {
  75 + display: block;
  76 + font-style: italic;
  77 +}
  78 +
  79 +/* Footer Styles */
  80 +
  81 +footer {
  82 + margin: 50px 0;
  83 +}
  84 +
  85 +/* Responsive Styles */
  86 +
  87 +@media(max-width:991px) {
  88 + .customer-img,
  89 + .img-related {
  90 + margin-bottom: 30px;
  91 + }
  92 +}
  93 +
  94 +@media(max-width:767px) {
  95 + .img-addon {
  96 + margin-bottom: 15px;
  97 + }
  98 +
  99 + header.carousel .carousel {
  100 + height: 70%;
  101 + }
  102 +}
  103 +.carousel-body {
  104 + position:absolute;
  105 + width: 100%;
  106 + top:25%;
  107 + text-align:center;
  108 + color:#fff;
  109 +}
  110 +
  111 +.addonlist a > p{
  112 + margin-bottom:15px;
  113 +}
  1 +@import (reference) "../../../../public/assets/less/bootstrap-less/mixins.less";
  2 +@import (reference) "../../../../public/assets/less/bootstrap-less/variables.less";
  3 +@import (reference) "../../../../public/assets/less/fastadmin/mixins.less";
  4 +@import (reference) "../../../../public/assets/less/fastadmin/variables.less";
  5 +@import "../../../../public/assets/less/lesshat.less";
  6 +@import url("../../../css/bootstrap.min.css");
  7 +@import url("../../../libs/font-awesome/css/font-awesome.min.css");
  8 +
  9 +html,
  10 +body {
  11 + height: 100%;
  12 + -webkit-font-smoothing: antialiased;
  13 + -moz-osx-font-smoothing: grayscale;
  14 + font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  15 + font-weight: 400;
  16 + overflow-x: hidden;
  17 + overflow-y: auto;
  18 + background: #f4f6f8;
  19 + font-size: 14px;
  20 + color: #616161;
  21 +
  22 +}
  23 +
  24 +.container {
  25 + max-width: 850px;
  26 + margin: 0 auto;
  27 + padding:50px;
  28 +}
不能预览此文件类型
  1 +-----BEGIN CERTIFICATE-----
  2 +MIIEczCCA9ygAwIBAgIEAjpYmTANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
  3 +Q04xEjAQBgNVBAgTCUd1YW5nZG9uZzERMA8GA1UEBxMIU2hlbnpoZW4xEDAOBgNV
  4 +BAoTB1RlbmNlbnQxDDAKBgNVBAsTA1dYRzETMBEGA1UEAxMKTW1wYXltY2hDQTEf
  5 +MB0GCSqGSIb3DQEJARYQbW1wYXltY2hAdGVuY2VudDAeFw0xODA5MjMwODU5MDda
  6 +Fw0yODA5MjAwODU5MDdaMIGiMQswCQYDVQQGEwJDTjESMBAGA1UECBMJR3Vhbmdk
  7 +b25nMREwDwYDVQQHEwhTaGVuemhlbjEQMA4GA1UEChMHVGVuY2VudDEOMAwGA1UE
  8 +CxMFTU1QYXkxNjA0BgNVBAMULeW5v+ilv+Wwj+e6uOearuWGjeeUn+i1hOa6kOWb
  9 +nuaUtuaciemZkOWFrOWPuDESMBAGA1UEBBMJMTAyNDc0NTU3MIIBIjANBgkqhkiG
  10 +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtCiZ9NGAYBPhCIO0I1aHTNCEYCpi0qkBxiqg
  11 +T4lvw/miU/kWCZz+F2H1Wv74xStMv+hFV8H0zZUO9t1Jw6Irj3BxsjoKKUDmhMFX
  12 +ftyu29FEXJHH4qBdcQW8WBhXjo8vUy6BAWWBg4UA3f5FqVSE80vks2Q3qkMLJLUV
  13 +7VTGuReaGB0LrXsOPP5Q4SjxGxy0WcAz6nNMncgVcJUSuAp+gHMKOpE3O48nQxbn
  14 +QNrk6PjHv43SdvqxvqWjLJKnp9BKKnokTD/oinyTzEDGmgOadwA9DFARNyLod59W
  15 +etduk6Idzmb644RU7g9nbMSZ7P+nrFQyBmF/mKAvAotqa8u9DwIDAQABo4IBRjCC
  16 +AUIwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdIkNFUy1DQSBHZW5lcmF0ZSBD
  17 +ZXJ0aWZpY2F0ZSIwHQYDVR0OBBYEFGzGoejfxesthx8ecNlEyhr4fWHyMIG/BgNV
  18 +HSMEgbcwgbSAFD4FJvYiYrQVW4jNZH6w1GKn5YZ0oYGQpIGNMIGKMQswCQYDVQQG
  19 +EwJDTjESMBAGA1UECBMJR3Vhbmdkb25nMREwDwYDVQQHEwhTaGVuemhlbjEQMA4G
  20 +A1UEChMHVGVuY2VudDEMMAoGA1UECxMDV1hHMRMwEQYDVQQDEwpNbXBheW1jaENB
  21 +MR8wHQYJKoZIhvcNAQkBFhBtbXBheW1jaEB0ZW5jZW50ggkAu1SXK7wA6FcwDgYD
  22 +VR0PAQH/BAQDAgbAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEB
  23 +BQUAA4GBAJSHb7HeBv+XlhnvxnVCmeWePrasFcUtPp/yE6yGqm6MIUGKgeuZkI0U
  24 +gqKZrBi4oJc+2COIYwb0F4NgACql5vAxd4Szpbe6Vq7RLVrjcxP/FIqQD/kXHcnM
  25 +HTsdbX4uLIUUCxO2+YA+LmKEAK9jCI0z4sf1pKICDKMQFJjTWEyw
  26 +-----END CERTIFICATE-----
  1 +-----BEGIN PRIVATE KEY-----
  2 +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0KJn00YBgE+EI
  3 +g7QjVodM0IRgKmLSqQHGKqBPiW/D+aJT+RYJnP4XYfVa/vjFK0y/6EVXwfTNlQ72
  4 +3UnDoiuPcHGyOgopQOaEwVd+3K7b0URckcfioF1xBbxYGFeOjy9TLoEBZYGDhQDd
  5 +/kWpVITzS+SzZDeqQwsktRXtVMa5F5oYHQutew48/lDhKPEbHLRZwDPqc0ydyBVw
  6 +lRK4Cn6Acwo6kTc7jydDFudA2uTo+Me/jdJ2+rG+paMskqen0EoqeiRMP+iKfJPM
  7 +QMaaA5p3AD0MUBE3Iuh3n1Z6126Toh3OZvrjhFTuD2dsxJns/6esVDIGYX+YoC8C
  8 +i2pry70PAgMBAAECggEAL7PkeA+VB1ucJU4CP60krDE2K+RtQExh3N/ijMzeXCzY
  9 +T5XPwQHhwb09YzfKFSMO8m2FFuSTdm/2g2U6p+Nj11o1sLRvhlbZ8uLi0QOMBUZW
  10 +7I/pNleyBkA3i7f+TxAYvtS8ces3rORlw2IVfe2UOnuo9dseZiXUaIbOZIYjDFUC
  11 +o/L3lrVK9NyPazYxqu2Re9XX4Qb0xeUdwUfeRYd+5XD2HnauqHELBvgJToYLydoS
  12 +6dpAYX6GPKraoKV0NNmcZ9DTygWKyCVct0RAIebQv9PSFo2drC1WBOJuJBjIuAcM
  13 +/uyUjkzRaX0h1fOIZx0tNk9x/ft9MuckHPM+meDumQKBgQDiaQGRsdo7aS6Tvp+N
  14 +BY2yT0f8TIpaWPu2N/BCdVdPXUG8tuXI3lM+9M47MiA4J84hzHSJaI+iFBSaUamn
  15 +m91v9kwpjF6Z6m8kzuoCBiJlSmLb+zEM7GXmh38VKzEXDqPyIDKKHIUPo8sGiKW+
  16 +dDKPw1iERDLTnoc8DrGw59uYnQKBgQDLtCiRm0B+pbtYwycck7uzsv9Ll/eVcsEE
  17 +iCaJ7fcN148EYUUP0EsJXa89+x4wxq+hc4CFFoTLI1vNoZh0KLf8c+v0AoboISY/
  18 +0Q1tZyajWeWBXovuW6jHbSWQzfYzKEqlrxijx0lkNPEUxFpdF61MBgGemfW9Pw9y
  19 +vL6hhqjOmwKBgGkmhYEJzaXL+sLJREZ1btKCOZd2YMcHDpgZMK9c2djVIeOoOd3p
  20 +S1Yw0dryM76A1h4iW3k5o4ONuefVx2o9XEFUbRjaxVDMbSP2KP4ZpT3Pp0wtRCGR
  21 +ZN4EzcOiFKF1vjSEOZSlHDMgSflV20wxoQ3dlq2PEt/vfzUoSeQ0OmuNAoGBAIt3
  22 +T4dRajjLs5c0FeUk6JBB7zSMWUCDDs/Rf8FToaBig1KWXjhTfextrfuboLH4dmrt
  23 +r4JvRn/mN4Z0KvLspfs8SsIHsOHhQFTVBoJu6y9P7yhB2UBalRXlDqEzwmqIHYOO
  24 +fCo12XO4I476WHwAJ/Ay9IzoEC1/rU37F/FzRsQdAoGADH4rQb/rSo9ZjZxb5fu2
  25 +z1I6jEw7vFMtWToZsOdF3nnVhNn5a5Lojj7HzCoeKSCTW9wsCoCBIUwVnStRZpqE
  26 +BdtwWs53uq1ERThKXmkVEgUgD8FL8PT/khSL8N44KeuB3MJ5uL4fKhRQDV3LeqxC
  27 +Cd58ve4U1mn+QVokNnguEM0=
  28 +-----END PRIVATE KEY-----
  1 +<?php
  2 +
  3 +return array(
  4 + array(
  5 + 'name' => 'wechat',
  6 + 'title' => '微信',
  7 + 'type' => 'array',
  8 + 'content' =>
  9 + array(),
  10 + 'value' => [
  11 + 'appid' => 'wxb3fxxxxxxxxxxx', // APP APPID
  12 + 'app_id' => 'wxb3fxxxxxxxxxxx', // 公众号 APPID
  13 + 'miniapp_id' => 'wxcce140865f93090b', // 小程序 APPID
  14 + 'mch_id' => '1515355581', //支付商户ID
  15 + 'key' => 'cccccccccccccccccccccccccccccc00',
  16 +// 'notify_url' => '/addons/epay/api/notify/type/wechat', //请勿修改此配置
  17 + 'notify_url' => 'http://feipin.w.brotop.cn/api/Wxpay/notify/type/wechat', //请勿修改此配置
  18 + 'cert_client' => '/epay/certs/apiclient_cert.pem', // 可选, 退款,红包等情况时需要用到
  19 + 'cert_key' => '/epay/certs/apiclient_key.pem',// 可选, 退款,红包等情况时需要用到
  20 + 'log' => 1,
  21 +// 'mode' => 'dev', // optional,设置此参数,将进入沙箱模式
  22 + ],
  23 + 'rule' => '',
  24 + 'msg' => '',
  25 + 'tip' => '微信参数配置',
  26 + 'ok' => '',
  27 + 'extend' => '',
  28 + ),
  29 + array(
  30 + 'name' => 'alipay',
  31 + 'title' => '支付宝',
  32 + 'type' => 'array',
  33 + 'content' =>
  34 + array(),
  35 + 'value' => [
  36 + 'app_id' => '2016082000295641',
  37 + 'notify_url' => '/addons/epay/api/notify/type/alipay', //请勿修改此配置
  38 + 'return_url' => '/addons/epay/api/returnx/type/alipay', //请勿修改此配置
  39 + 'ali_public_key' => 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuWJKrQ6SWvS6niI+4vEVZiYfjkCfLQfoFI2nCp9ZLDS42QtiL4Ccyx8scgc3nhVwmVRte8f57TFvGhvJD0upT4O5O/lRxmTjechXAorirVdAODpOu0mFfQV9y/T9o9hHnU+VmO5spoVb3umqpq6D/Pt8p25Yk852/w01VTIczrXC4QlrbOEe3sr1E9auoC7rgYjjCO6lZUIDjX/oBmNXZxhRDrYx4Yf5X7y8FRBFvygIE2FgxV4Yw+SL3QAa2m5MLcbusJpxOml9YVQfP8iSurx41PvvXUMo49JG3BDVernaCYXQCoUJv9fJwbnfZd7J5YByC+5KM4sblJTq7bXZWQIDAQAB',
  40 + // 加密方式: **RSA2**
  41 + 'private_key' => 'MIIEpAIBAAKCAQEAs6+F2leOgOrvj9jTeDhb5q46GewOjqLBlGSs/bVL4Z3fMr3p+Q1Tux/6uogeVi/eHd84xvQdfpZ87A1SfoWnEGH5z15yorccxSOwWUI+q8gz51IWqjgZxhWKe31BxNZ+prnQpyeMBtE25fXp5nQZ/pftgePyUUvUZRcAUisswntobDQKbwx28VCXw5XB2A+lvYEvxmMv/QexYjwKK4M54j435TuC3UctZbnuynSPpOmCu45ZhEYXd4YMsGMdZE5/077ZU1aU7wx/gk07PiHImEOCDkzqsFo0Buc/knGcdOiUDvm2hn2y1XvwjyFOThsqCsQYi4JmwZdRa8kvOf57nwIDAQABAoIBAQCw5QCqln4VTrTvcW+msB1ReX57nJgsNfDLbV2dG8mLYQemBa9833DqDK6iynTLNq69y88ylose33o2TVtEccGp8Dqluv6yUAED14G6LexS43KtrXPgugAtsXE253ZDGUNwUggnN1i0MW2RcMqHdQ9ORDWvJUCeZj/AEafgPN8AyiLrZeL07jJz/uaRfAuNqkImCVIarKUX3HBCjl9TpuoMjcMhz/MsOmQ0agtCatO1eoH1sqv5Odvxb1i59c8Hvq/mGEXyRuoiDo05SE6IyXYXr84/Nf2xvVNHNQA6kTckj8shSi+HGM4mO1Y4Pbb7XcnxNkT0Inn6oJMSiy56P+CpAoGBAO1O+5FE1ZuVGuLb48cY+0lHCD+nhSBd66B5FrxgPYCkFOQWR7pWyfNDBlmO3SSooQ8TQXA25blrkDxzOAEGX57EPiipXr/hy5e+WNoukpy09rsO1TMsvC+v0FXLvZ+TIAkqfnYBgaT56ku7yZ8aFGMwdCPL7WJYAwUIcZX8wZ3dAoGBAMHWplAqhe4bfkGOEEpfs6VvEQxCqYMYVyR65K0rI1LiDZn6Ij8fdVtwMjGKFSZZTspmsqnbbuCE/VTyDzF4NpAxdm3cBtZACv1Lpu2Om+aTzhK2PI6WTDVTKAJBYegXaahBCqVbSxieR62IWtmOMjggTtAKWZ1P5LQcRwdkaB2rAoGAWnAPT318Kp7YcDx8whOzMGnxqtCc24jvk2iSUZgb2Dqv+3zCOTF6JUsV0Guxu5bISoZ8GdfSFKf5gBAo97sGFeuUBMsHYPkcLehM1FmLZk1Q+ljcx3P1A/ds3kWXLolTXCrlpvNMBSN5NwOKAyhdPK/qkvnUrfX8sJ5XK2H4J8ECgYAGIZ0HIiE0Y+g9eJnpUFelXvsCEUW9YNK4065SD/BBGedmPHRC3OLgbo8X5A9BNEf6vP7fwpIiRfKhcjqqzOuk6fueA/yvYD04v+Da2MzzoS8+hkcqF3T3pta4I4tORRdRfCUzD80zTSZlRc/h286Y2eTETd+By1onnFFe2X01mwKBgQDaxo4PBcLL2OyVT5DoXiIdTCJ8KNZL9+kV1aiBuOWxnRgkDjPngslzNa1bK+klGgJNYDbQqohKNn1HeFX3mYNfCUpuSnD2Yag53Dd/1DLO+NxzwvTu4D6DCUnMMMBVaF42ig31Bs0jI3JQZVqeeFzSET8fkoFopJf3G6UXlrIEAQ==',
  42 + 'log' => 1,
  43 + 'mode' => 'dev', // optional,设置此参数,将进入沙箱模式
  44 + ],
  45 + 'rule' => 'required',
  46 + 'msg' => '',
  47 + 'tip' => '支付宝参数配置',
  48 + 'ok' => '',
  49 + 'extend' => '',
  50 + ),
  51 + array(
  52 +
  53 + 'name' => '__tips__',
  54 + 'title' => '温馨提示',
  55 + 'type' => 'array',
  56 + 'content' =>
  57 + array(),
  58 + 'value' => '请注意微信支付证书路径位于/addons/epay/certs目录下,请替换成你自己的证书<br>微信:mch_id为微信商户ID,appid为APP的appid,app_id为公众号的appid,miniapp_id为小程序ID,key为微信商户支付的密钥',
  59 + 'rule' => '',
  60 + 'msg' => '',
  61 + 'tip' => '微信参数配置',
  62 + 'ok' => '',
  63 + 'extend' => '',
  64 + )
  65 +);
  1 +<?php
  2 +
  3 +namespace addons\epay\controller;
  4 +
  5 +use addons\epay\library\Service;
  6 +use think\addons\Controller;
  7 +use app\admin\model\Porder;
  8 +use Yansongda\Pay\Pay;
  9 +
  10 +/**
  11 + * API接口控制器
  12 + *
  13 + * @package addons\epay\controller
  14 + */
  15 +class Api extends Controller
  16 +{
  17 +
  18 + protected $layout = 'default';
  19 + protected $config = [];
  20 +
  21 + public function _initialize()
  22 + {
  23 + parent::_initialize();
  24 + }
  25 +
  26 + /**
  27 + * 默认方法
  28 + */
  29 + public function index()
  30 + {
  31 + $this->error();
  32 + }
  33 +
  34 + public function pay(){
  35 + //创建支付对象
  36 + $pay = Pay::wechat(Service::getConfig('wechat'));
  37 +
  38 + //构建订单信息
  39 + $order = [
  40 + 'out_trade_no' => time(),
  41 + 'body' => '废品回收',
  42 + 'total_fee' => '1',//单位:分
  43 + 'openid' => 'onkVf1FjWS5SBxxxxxxxx',
  44 + ];
  45 +
  46 + //跳转或输出
  47 + return $pay->miniapp($order)->send();
  48 + }
  49 +
  50 + /**
  51 + * 微信支付扫码支付
  52 + * @return string
  53 + */
  54 + public function wechat()
  55 + {
  56 + $config = Service::getConfig('wechat');
  57 +
  58 + $body = $this->request->request("body");
  59 + $code_url = $this->request->request("code_url");
  60 + $out_trade_no = $this->request->request("out_trade_no");
  61 + $return_url = $this->request->request("return_url");
  62 + $total_fee = $this->request->request("total_fee");
  63 +
  64 + $sign = $this->request->request("sign");
  65 +
  66 + $data = [
  67 + 'body' => $body,
  68 + 'code_url' => $code_url,
  69 + 'out_trade_no' => $out_trade_no,
  70 + 'return_url' => $return_url,
  71 + 'total_fee' => $total_fee,
  72 + ];
  73 + if ($sign != md5(implode('', $data) . $config['appid'])) {
  74 + $this->error("签名不正确");
  75 + }
  76 +
  77 + if ($this->request->isAjax()) {
  78 + $wechat = Pay::wechat($config);
  79 + $order = [
  80 + 'out_trade_no' => $out_trade_no
  81 + ];
  82 + $result = $wechat->find($order);
  83 + if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS') {
  84 + $this->success("", "", ['trade_state' => $result->trade_state]);
  85 + } else {
  86 + $this->error("查询失败");
  87 + }
  88 + }
  89 + $data['sign'] = $sign;
  90 + $this->view->assign("isWechat", stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false);
  91 + $this->view->assign("isMobile", $this->request->isMobile());
  92 + $this->view->assign("data", $data);
  93 + $this->view->assign("title", "微信支付");
  94 + return $this->view->fetch();
  95 + }
  96 +
  97 + /**
  98 + * 支付成功回调
  99 + */
  100 + public function notify()
  101 + {
  102 + $type = $this->request->param('type');
  103 + $pay = Service::checkNotify($type);
  104 + if (!$pay) {
  105 + echo '签名错误';
  106 + return;
  107 + }
  108 + //你可以在这里你的业务处理逻辑,比如处理你的订单状态、给会员加余额等等功能
  109 +// $porderModel = new Porder();
  110 +// $porderModel->where(['pay_order_sn' => $pay->out_trade_no, 'uid' => $this->user_id])->update(['status'=>$this->order_status[1]]);
  111 + //下面这句必须要执行,且在此之前不能有任何输出
  112 + echo $pay->success();
  113 + return;
  114 + }
  115 +
  116 + /**
  117 + * 支付成功返回
  118 + */
  119 + public function returnx()
  120 + {
  121 + $type = $this->request->param('type');
  122 + $result = Service::checkReturn($type);
  123 + if (!$result) {
  124 + $this->error('签名错误');
  125 + }
  126 + //你可以在这里定义你的提示信息,但切记不可在此编写逻辑
  127 + $this->success("恭喜你!支付成功!", addon_url("epay/index/index"));
  128 +
  129 + return;
  130 + }
  131 +
  132 +}
  1 +<?php
  2 +
  3 +namespace addons\epay\controller;
  4 +
  5 +use addons\epay\library\Service;
  6 +use fast\Random;
  7 +use think\addons\Controller;
  8 +use Yansongda\Pay\Log;
  9 +use Yansongda\Pay\Pay;
  10 +use Exception;
  11 +
  12 +/**
  13 + * 微信支付宝插件首页
  14 + *
  15 + * 此控制器仅用于开发展示说明和体验,建议自行添加一个新的控制器进行处理返回和回调事件,同时删除此控制器文件
  16 + *
  17 + * Class Index
  18 + * @package addons\epay\controller
  19 + */
  20 +class Index extends Controller
  21 +{
  22 +
  23 + protected $layout = 'default';
  24 +
  25 + protected $config = [];
  26 +
  27 + public function _initialize()
  28 + {
  29 + parent::_initialize();
  30 + }
  31 +
  32 + public function index()
  33 + {
  34 + $this->view->assign("title", "微信支付宝企业收款插件");
  35 + return $this->view->fetch();
  36 + }
  37 +
  38 + /**
  39 + * 体验(仅供开发测试体验)
  40 + */
  41 + public function experience()
  42 + {
  43 + $amount = $this->request->request('amount');
  44 + $type = $this->request->request('type');
  45 + $method = $this->request->request('method');
  46 +
  47 + if (!$amount || $amount < 0) {
  48 + $this->error("支付金额必须大于0");
  49 + }
  50 +
  51 + if (!$type || !in_array($type, ['alipay', 'wechat'])) {
  52 + $this->error("参数不能为空");
  53 + }
  54 +
  55 + //订单号
  56 + $out_trade_no = date("YmdHis") . Random::alnum(6);
  57 +
  58 + //订单标题
  59 + $title = 'FastAdmin企业支付插件测试订单';
  60 +
  61 + if ($type == 'alipay') {
  62 + //创建支付对象
  63 + $pay = Pay::alipay(Service::getConfig('alipay'));
  64 + //支付宝支付,请根据你的需求,仅选择你所需要的即可
  65 + $params = [
  66 + 'out_trade_no' => $out_trade_no,//你的订单号
  67 + 'total_amount' => $amount,//单位元
  68 + 'subject' => $title,
  69 + 'notify_url' => $this->request->root(true) . '/addons/epay/index/alipaynotify',
  70 + 'return_url' => $this->request->root(true) . '/addons/epay/index/alipayreturn'
  71 + ];
  72 +
  73 + switch ($method) {
  74 + case 'web':
  75 + //电脑支付,跳转
  76 + return $pay->web($params)->send();
  77 + case 'wap':
  78 + //手机网页支付,跳转
  79 + return $pay->wap($params)->send();
  80 + case 'app':
  81 + //APP支付,直接返回字符串
  82 + return $pay->app($params)->send();
  83 + case 'scan':
  84 + //扫码支付,直接返回字符串
  85 + return $pay->scan($params);
  86 + case 'pos':
  87 + //刷卡支付,直接返回字符串
  88 + //刷卡支付必须要有auth_code
  89 + $params['auth_code'] = '289756915257123456';
  90 + return $pay->pos($params);
  91 + default:
  92 + //其它支付类型请参考:https://docs.pay.yansongda.cn/alipay
  93 + }
  94 + } else {
  95 + //创建支付对象
  96 + $pay = Pay::wechat(Service::getConfig('wechat'));
  97 + //微信支付,请根据你的需求,仅选择你所需要的即可
  98 + $params = [
  99 + 'out_trade_no' => $out_trade_no,//你的订单号
  100 + 'body' => $title,
  101 + 'total_fee' => $amount * 100, //单位分
  102 + 'notify_url' => $this->request->root(true) . '/addons/epay/index/wechatnofity',
  103 + 'return_url' => $this->request->root(true) . '/addons/epay/index/wechatreturn/out_trade_no/' . $out_trade_no,
  104 + ];
  105 +
  106 + switch ($method) {
  107 + case 'web':
  108 + //电脑支付,跳转到自定义展示页面(FastAdmin独有)
  109 + return $pay->web($params)->send();
  110 + case 'mp':
  111 + //公众号支付
  112 + //公众号支付必须有openid
  113 + $params['openid'] = 'onkVf1FjWS5SBxxxxxxxx';
  114 + return $pay->mp($params);
  115 + case 'wap':
  116 + //手机网页支付,跳转
  117 + return $pay->wap($params)->send();
  118 + case 'app':
  119 + //APP支付,直接返回字符串
  120 + return $pay->app($params)->send();
  121 + case 'scan':
  122 + //扫码支付,直接返回字符串
  123 + return $pay->scan($params);
  124 + case 'pos':
  125 + //刷卡支付,直接返回字符串
  126 + //刷卡支付必须要有auth_code
  127 + $params['auth_code'] = '289756915257123456';
  128 + return $pay->pos($params);
  129 + case 'miniapp':
  130 + //小程序支付,直接返回字符串
  131 + //小程序支付必须要有openid
  132 + $params['openid'] = 'onkVf1FjWS5SBxxxxxxxx';
  133 + return $pay->miniapp($params);
  134 + default:
  135 + //其它支付类型请参考:https://docs.pay.yansongda.cn/wechat
  136 + }
  137 + }
  138 + $this->error("未找到支付类型[{$type}][{$method}]");
  139 + }
  140 +
  141 + /**
  142 + * 支付宝异步通知
  143 + */
  144 + public function alipaynotify()
  145 + {
  146 + $alipay = Pay::alipay(Service::getConfig('wechat'));
  147 + try {
  148 + $data = $alipay->verify();
  149 + Log::debug('wechat notify', $data->all());
  150 + if (!in_array($data->trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
  151 + echo "验签失败";
  152 + return;
  153 + }
  154 + } catch (Exception $e) {
  155 + echo "验签失败";
  156 + return;
  157 + }
  158 + //支付宝可以获取到$pay->out_trade_no,$pay->total_amount等信息
  159 + echo $alipay->success();
  160 + return;
  161 + }
  162 +
  163 + /**
  164 + * 支付宝返回通知
  165 + */
  166 + public function alipayreturn()
  167 + {
  168 + $alipay = Pay::alipay(Service::getConfig('alipay'));
  169 + try {
  170 + $alipay->verify();
  171 + } catch (Exception $e) {
  172 + $this->error("支付失败", "");
  173 + return;
  174 + }
  175 +
  176 + $this->success("支付成功", "");
  177 + return;
  178 + }
  179 +
  180 + /**
  181 + * 微信异步通知
  182 + */
  183 + public function wechatnotify()
  184 + {
  185 + $wechat = Pay::wechat(Service::getConfig('wechat'));
  186 + try {
  187 + $data = $wechat->verify();
  188 + Log::debug('wechat notify', $data->all());
  189 + } catch (Exception $e) {
  190 + echo "验签失败";
  191 + return;
  192 + }
  193 + //微信可以获取到$pay->out_trade_no,$pay->total_fee等信息
  194 + echo $wechat->success();
  195 + return;
  196 + }
  197 +
  198 + /**
  199 + * 微信返回通知
  200 + */
  201 + public function wechatreturn()
  202 + {
  203 + $out_trade_no = $this->request->param('out_trade_no');
  204 + if (!$out_trade_no) {
  205 + $this->error("订单号不正确");
  206 + }
  207 + $wechat = Pay::wechat(Service::getConfig('wechat'));
  208 + $order = [
  209 + 'out_trade_no' => $out_trade_no
  210 + ];
  211 + $result = $wechat->find($order);
  212 + if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS' && $result->trade_state == 'SUCCESS') {
  213 + $this->success("支付成功", "");
  214 + } else {
  215 + $this->error("支付失败", "");
  216 + }
  217 +
  218 + $this->success("请返回网站查看支付结果");
  219 + }
  220 +
  221 +}
  1 +name = epay
  2 +title = 微信支付宝整合
  3 +intro = 可用于整合微信、支付宝付款,快速整合FastAdmin的其它模块
  4 +author = Karson
  5 +website = https://www.fastadmin.net
  6 +version = 1.0.2
  7 +state = 1
  8 +url = /addons/epay
  1 +<?php
  2 +
  3 +namespace addons\epay\library;
  4 +
  5 +
  6 +use think\Exception;
  7 +
  8 +class OrderException extends Exception
  9 +{
  10 + public function __construct($message = "", $code = 0, $data = [])
  11 + {
  12 + $this->message = $message;
  13 + $this->code = $code;
  14 + $this->data = $data;
  15 + }
  16 +
  17 +}
  1 +<?php
  2 +
  3 +namespace addons\epay\library;
  4 +
  5 +use Exception;
  6 +use Yansongda\Pay\Log;
  7 +use Yansongda\Pay\Pay;
  8 +
  9 +/**
  10 + * 订单服务类
  11 + *
  12 + * @package addons\epay\library
  13 + */
  14 +class Service
  15 +{
  16 +
  17 + /**
  18 + * 创建支付对象
  19 + * @param string $type 支付类型
  20 + * @param array $config 配置信息
  21 + * @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat
  22 + */
  23 + public static function createPay($type, $config = [])
  24 + {
  25 + $type = strtolower($type);
  26 + if (!in_array($type, ['wechat', 'alipay'])) {
  27 + return false;
  28 + }
  29 + $pay = Pay::$type(array_merge(self::getConfig($type), $config));
  30 + return $pay;
  31 + }
  32 +
  33 + /**
  34 + * 验证回调是否成功
  35 + * @param string $type 支付类型
  36 + * @param array $config 配置信息
  37 + * @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat
  38 + */
  39 + public static function checkNotify($type, $config = [])
  40 + {
  41 + $type = strtolower($type);
  42 + if (!in_array($type, ['wechat', 'alipay'])) {
  43 + return false;
  44 + }
  45 + try {
  46 + $pay = Pay::$type(array_merge(self::getConfig($type), $config));
  47 + $data = $pay->verify();
  48 + Log::debug($type . ' notify', $data->all());
  49 +
  50 + if ($type == 'alipay') {
  51 + if (in_array($data->trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
  52 + return $pay;
  53 + }
  54 + } else {
  55 + return $pay;
  56 + }
  57 + } catch (Exception $e) {
  58 + return false;
  59 + }
  60 +
  61 + return $pay;
  62 + }
  63 +
  64 + /**
  65 + * 验证返回是否成功
  66 + * @param string $type 支付类型
  67 + * @param array $config 配置信息
  68 + * @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat
  69 + */
  70 + public static function checkReturn($type, $config = [])
  71 + {
  72 + $type = strtolower($type);
  73 + if (!in_array($type, ['wechat', 'alipay'])) {
  74 + return false;
  75 + }
  76 + try {
  77 + $pay = Pay::$type(array_merge(self::getConfig($type), $config))->verify();
  78 + } catch (Exception $e) {
  79 + return false;
  80 + }
  81 +
  82 + return $pay;
  83 + }
  84 +
  85 + /**
  86 + * 获取配置
  87 + * @param string $type 支付类型
  88 + * @return array|mixed
  89 + */
  90 + public static function getConfig($type = 'wechat')
  91 + {
  92 + $config = get_addon_config('epay');
  93 + $config = isset($config[$type]) ? $config[$type] : $config['wechat'];
  94 + if ($config['log']) {
  95 + $config['log'] = [
  96 + 'file' => LOG_PATH . '/epaylogs/' . $type . '-' . date("Y-m-d") . '.log',
  97 + 'level' => 'debug'
  98 + ];
  99 + }
  100 +
  101 + $config['notify_url'] = empty($config['notify_url']) ? addon_url('epay/api/notify', [], false) . '/type/' . $type : $config['notify_url'];
  102 + $config['notify_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['notify_url']) ? request()->root(true) . $config['notify_url'] : $config['notify_url'];
  103 + //只有支付宝才配置return_url
  104 + if ($type == 'alipay') {
  105 + $config['return_url'] = empty($config['return_url']) ? addon_url('epay/api/returnx', [], false) . '/type/' . $type : $config['return_url'];
  106 + $config['return_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['return_url']) ? request()->root(true) . $config['return_url'] : $config['return_url'];
  107 + }
  108 + return $config;
  109 + }
  110 +
  111 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Contracts;
  4 +
  5 +interface GatewayApplicationInterface
  6 +{
  7 + /**
  8 + * To pay.
  9 + *
  10 + * @author yansongda <me@yansonga.cn>
  11 + *
  12 + * @param string $gateway
  13 + * @param array $params
  14 + *
  15 + * @return \Yansongda\Supports\Collection|\Symfony\Component\HttpFoundation\Response
  16 + */
  17 + public function pay($gateway, $params);
  18 +
  19 + /**
  20 + * Query an order.
  21 + *
  22 + * @author yansongda <me@yansongda.cn>
  23 + *
  24 + * @param string|array $order
  25 + * @param bool $refund
  26 + *
  27 + * @return \Yansongda\Supports\Collection
  28 + */
  29 + public function find($order, $refund);
  30 +
  31 + /**
  32 + * Refund an order.
  33 + *
  34 + * @author yansongda <me@yansongda.cn>
  35 + *
  36 + * @param array $order
  37 + *
  38 + * @return \Yansongda\Supports\Collection
  39 + */
  40 + public function refund($order);
  41 +
  42 + /**
  43 + * Cancel an order.
  44 + *
  45 + * @author yansongda <me@yansongda.cn>
  46 + *
  47 + * @param string|array $order
  48 + *
  49 + * @return \Yansongda\Supports\Collection
  50 + */
  51 + public function cancel($order);
  52 +
  53 + /**
  54 + * Close an order.
  55 + *
  56 + * @author yansongda <me@yansongda.cn>
  57 + *
  58 + * @param string|array $order
  59 + *
  60 + * @return \Yansongda\Supports\Collection
  61 + */
  62 + public function close($order);
  63 +
  64 + /**
  65 + * Verify a request.
  66 + *
  67 + * @author yansongda <me@yansongda.cn>
  68 + *
  69 + * @param string|null $content
  70 + * @param bool $refund
  71 + *
  72 + * @return \Yansongda\Supports\Collection
  73 + */
  74 + public function verify($content, $refund);
  75 +
  76 + /**
  77 + * Echo success to server.
  78 + *
  79 + * @author yansongda <me@yansongda.cn>
  80 + *
  81 + * @return \Symfony\Component\HttpFoundation\Response
  82 + */
  83 + public function success();
  84 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Contracts;
  4 +
  5 +interface GatewayInterface
  6 +{
  7 + /**
  8 + * Pay an order.
  9 + *
  10 + * @author yansongda <me@yansongda.cn>
  11 + *
  12 + * @param string $endpoint
  13 + * @param array $payload
  14 + *
  15 + * @return \Yansongda\Supports\Collection|\Symfony\Component\HttpFoundation\Response
  16 + */
  17 + public function pay($endpoint, array $payload);
  18 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay;
  4 +
  5 +use Symfony\Component\EventDispatcher\Event;
  6 +use Symfony\Component\EventDispatcher\EventDispatcher;
  7 +use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  8 +
  9 +/**
  10 + * @author yansongda <me@yansongda.cn>
  11 + *
  12 + * @method static Event dispatch($eventName, Event $event = null) Dispatches an event to all registered listeners
  13 + * @method static array getListeners($eventName = null) Gets the listeners of a specific event or all listeners sorted by descending priority.
  14 + * @method static int|null getListenerPriority($eventName, $listener) Gets the listener priority for a specific event.
  15 + * @method static bool hasListeners($eventName = null) Checks whether an event has any registered listeners.
  16 + * @method static addListener($eventName, $listener, $priority = 0) Adds an event listener that listens on the specified events.
  17 + * @method static removeListener($eventName, $listener) Removes an event listener from the specified events.
  18 + * @method static addSubscriber(EventSubscriberInterface $subscriber) Adds an event subscriber.
  19 + * @method static removeSubscriber(EventSubscriberInterface $subscriber)
  20 + */
  21 +class Events
  22 +{
  23 + /**
  24 + * Start pay.
  25 + *
  26 + * @Event("Yansongda\Pay\Events\PayStarting")
  27 + */
  28 + const PAY_STARTING = 'yansongda.pay.starting';
  29 +
  30 + /**
  31 + * Before pay.
  32 + *
  33 + * @Event("Yansongda\Pay\Events\PayStarted")
  34 + */
  35 + const PAY_STARTED = 'yansongda.pay.started';
  36 +
  37 + /**
  38 + * Paying.
  39 + *
  40 + * @Event("Yansongda\Pay\Events\ApiRequesting")
  41 + */
  42 + const API_REQUESTING = 'yansongda.pay.api.requesting';
  43 +
  44 + /**
  45 + * Paid.
  46 + *
  47 + * @Event("Yansongda\Pay\Events\ApiRequested")
  48 + */
  49 + const API_REQUESTED = 'yansongda.pay.api.requested';
  50 +
  51 + /**
  52 + * Sign error.
  53 + *
  54 + * @Event("Yansongda\Pay\Events\SignFailed")
  55 + */
  56 + const SIGN_FAILED = 'yansongda.pay.sign.failed';
  57 +
  58 + /**
  59 + * Receive request.
  60 + *
  61 + * @Event("Yansongda\Pay\Events\RequestReceived")
  62 + */
  63 + const REQUEST_RECEIVED = 'yansongda.pay.request.received';
  64 +
  65 + /**
  66 + * Method called.
  67 + *
  68 + * @Event("Yansongda\Pay\Events\MethodCalled")
  69 + */
  70 + const METHOD_CALLED = 'yansongda.pay.method.called';
  71 +
  72 + /**
  73 + * dispatcher.
  74 + *
  75 + * @var EventDispatcher
  76 + */
  77 + protected static $dispatcher;
  78 +
  79 + /**
  80 + * Forward call.
  81 + *
  82 + * @author yansongda <me@yansongda.cn>
  83 + *
  84 + * @param string $method
  85 + * @param array $args
  86 + *
  87 + * @throws \Exception
  88 + *
  89 + * @return mixed
  90 + */
  91 + public static function __callStatic($method, $args)
  92 + {
  93 +// return call_user_func_array([self::getDispatcher(), $method], $args);
  94 + }
  95 +
  96 + /**
  97 + * Forward call.
  98 + *
  99 + * @author yansongda <me@yansongda.cn>
  100 + *
  101 + * @param string $method
  102 + * @param array $args
  103 + *
  104 + * @throws \Exception
  105 + *
  106 + * @return mixed
  107 + */
  108 + public function __call($method, $args)
  109 + {
  110 +// return call_user_func_array([self::getDispatcher(), $method], $args);
  111 + }
  112 +
  113 + /**
  114 + * setDispatcher.
  115 + *
  116 + * @author yansongda <me@yansongda.cn>
  117 + *
  118 + * @param EventDispatcher $dispatcher
  119 + *
  120 + * @return void
  121 + */
  122 + public static function setDispatcher(EventDispatcher $dispatcher)
  123 + {
  124 + self::$dispatcher = $dispatcher;
  125 + }
  126 +
  127 + /**
  128 + * getDispatcher.
  129 + *
  130 + * @author yansongda <me@yansongda.cn>
  131 + *
  132 + * @return EventDispatcher
  133 + */
  134 + public static function getDispatcher(): EventDispatcher
  135 + {
  136 + if (self::$dispatcher) {
  137 + return self::$dispatcher;
  138 + }
  139 +
  140 + return self::$dispatcher = self::createDispatcher();
  141 + }
  142 +
  143 + /**
  144 + * createDispatcher.
  145 + *
  146 + * @author yansongda <me@yansongda.cn>
  147 + *
  148 + * @return EventDispatcher
  149 + */
  150 + public static function createDispatcher(): EventDispatcher
  151 + {
  152 + return new EventDispatcher();
  153 + }
  154 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Events;
  4 +
  5 +class ApiRequested extends Event
  6 +{
  7 + /**
  8 + * Endpoint.
  9 + *
  10 + * @var string
  11 + */
  12 + public $endpoint;
  13 +
  14 + /**
  15 + * Result.
  16 + *
  17 + * @var array
  18 + */
  19 + public $result;
  20 +
  21 + /**
  22 + * Bootstrap.
  23 + *
  24 + * @param string $driver
  25 + * @param string $gateway
  26 + * @param string $endpoint
  27 + * @param array $result
  28 + */
  29 + public function __construct(string $driver, string $gateway, string $endpoint, array $result)
  30 + {
  31 + $this->endpoint = $endpoint;
  32 + $this->result = $result;
  33 +
  34 + parent::__construct($driver, $gateway);
  35 + }
  36 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Events;
  4 +
  5 +class ApiRequesting extends Event
  6 +{
  7 + /**
  8 + * Endpoint.
  9 + *
  10 + * @var string
  11 + */
  12 + public $endpoint;
  13 +
  14 + /**
  15 + * Payload.
  16 + *
  17 + * @var array
  18 + */
  19 + public $payload;
  20 +
  21 + /**
  22 + * Bootstrap.
  23 + *
  24 + * @param string $driver
  25 + * @param string $gateway
  26 + * @param string $endpoint
  27 + * @param array $payload
  28 + */
  29 + public function __construct(string $driver, string $gateway, string $endpoint, array $payload)
  30 + {
  31 + $this->endpoint = $endpoint;
  32 + $this->payload = $payload;
  33 +
  34 + parent::__construct($driver, $gateway);
  35 + }
  36 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Events;
  4 +
  5 +class Event
  6 +{
  7 + /**
  8 + * Driver.
  9 + *
  10 + * @var string
  11 + */
  12 + public $driver;
  13 +
  14 + /**
  15 + * Method.
  16 + *
  17 + * @var string
  18 + */
  19 + public $gateway;
  20 +
  21 + /**
  22 + * Extra attributes.
  23 + *
  24 + * @var mixed
  25 + */
  26 + public $attributes;
  27 +
  28 + /**
  29 + * Bootstrap.
  30 + *
  31 + * @author yansongda <me@yansongda.cn>
  32 + *
  33 + * @param string $driver
  34 + * @param string $gateway
  35 + */
  36 + public function __construct(string $driver, string $gateway)
  37 + {
  38 + $this->driver = $driver;
  39 + $this->gateway = $gateway;
  40 + }
  41 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Events;
  4 +
  5 +class MethodCalled extends Event
  6 +{
  7 + /**
  8 + * endpoint.
  9 + *
  10 + * @var string
  11 + */
  12 + public $endpoint;
  13 +
  14 + /**
  15 + * payload.
  16 + *
  17 + * @var array
  18 + */
  19 + public $payload;
  20 +
  21 + /**
  22 + * Bootstrap.
  23 + *
  24 + * @author yansongda <me@yansongda.cn>
  25 + *
  26 + * @param string $driver
  27 + * @param string $gateway
  28 + * @param string $endpoint
  29 + * @param array $payload
  30 + */
  31 + public function __construct(string $driver, string $gateway, string $endpoint, array $payload = [])
  32 + {
  33 + $this->endpoint = $endpoint;
  34 + $this->payload = $payload;
  35 +
  36 + parent::__construct($driver, $gateway);
  37 + }
  38 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Events;
  4 +
  5 +class PayStarted extends Event
  6 +{
  7 + /**
  8 + * Endpoint.
  9 + *
  10 + * @var string
  11 + */
  12 + public $endpoint;
  13 +
  14 + /**
  15 + * Payload.
  16 + *
  17 + * @var array
  18 + */
  19 + public $payload;
  20 +
  21 + /**
  22 + * Bootstrap.
  23 + *
  24 + * @param string $driver
  25 + * @param string $gateway
  26 + * @param string $endpoint
  27 + * @param array $payload
  28 + */
  29 + public function __construct(string $driver, string $gateway, string $endpoint, array $payload)
  30 + {
  31 + $this->endpoint = $endpoint;
  32 + $this->payload = $payload;
  33 +
  34 + parent::__construct($driver, $gateway);
  35 + }
  36 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Events;
  4 +
  5 +class PayStarting extends Event
  6 +{
  7 + /**
  8 + * Params.
  9 + *
  10 + * @var array
  11 + */
  12 + public $params;
  13 +
  14 + /**
  15 + * Bootstrap.
  16 + *
  17 + * @param string $driver
  18 + * @param string $gateway
  19 + * @param array $params
  20 + */
  21 + public function __construct(string $driver, string $gateway, array $params)
  22 + {
  23 + $this->params = $params;
  24 +
  25 + parent::__construct($driver, $gateway);
  26 + }
  27 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Events;
  4 +
  5 +class RequestReceived extends Event
  6 +{
  7 + /**
  8 + * Received data.
  9 + *
  10 + * @var array
  11 + */
  12 + public $data;
  13 +
  14 + /**
  15 + * Bootstrap.
  16 + *
  17 + * @author yansongda <me@yansongda.cn>
  18 + *
  19 + * @param string $driver
  20 + * @param string $gateway
  21 + * @param array $data
  22 + */
  23 + public function __construct(string $driver, string $gateway, array $data)
  24 + {
  25 + $this->data = $data;
  26 +
  27 + parent::__construct($driver, $gateway);
  28 + }
  29 +}
  1 +<?php
  2 +
  3 +namespace Yansongda\Pay\Events;
  4 +
  5 +class SignFailed extends Event
  6 +{
  7 + /**
  8 + * Received data.
  9 + *
  10 + * @var array
  11 + */
  12 + public $data;
  13 +
  14 + /**
  15 + * Bootstrap.
  16 + *
  17 + * @author yansongda <me@yansongda.cn>
  18 + *
  19 + * @param string $driver
  20 + * @param string $gateway
  21 + * @param array $data
  22 + */
  23 + public function __construct(string $driver, string $gateway, array $data)
  24 + {
  25 + $this->data = $data;
  26 +
  27 + parent::__construct($driver, $gateway);
  28 + }
  29 +}