正在显示
72 个修改的文件
包含
4784 行增加
和
1 行删除
.bowerrc
0 → 100644
.env.sample
0 → 100644
.gitignore
0 → 100644
LICENSE
0 → 100644
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。 |
addons/address/Address.php
0 → 100644
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 | +} |
addons/address/bootstrap.js
0 → 100644
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 | +}); |
addons/address/config.php
0 → 100644
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 | +); |
addons/address/controller/Index.php
0 → 100644
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 | +} |
addons/address/info.ini
0 → 100644
addons/address/view/index/amap.html
0 → 100644
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> |
addons/address/view/index/baidu.html
0 → 100644
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> |
addons/address/view/index/tencent.html
0 → 100644
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> |
addons/command/Command.php
0 → 100644
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> </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> |
addons/command/config.php
0 → 100644
addons/command/controller/Index.php
0 → 100644
addons/command/info.ini
0 → 100644
addons/command/install.sql
0 → 100644
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; |
addons/command/library/Output.php
0 → 100644
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 | +}); |
addons/database/Database.php
0 → 100644
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> |
addons/database/config.php
0 → 100644
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 | +); |
addons/database/controller/Index.php
0 → 100644
addons/database/info.ini
0 → 100644
addons/database/library/Backup.php
0 → 100644
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 | +}); |
addons/epay/Epay.php
0 → 100644
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 | +} |
addons/epay/assets/css/common.css
0 → 100644
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 | +} |
addons/epay/assets/css/epay.css
0 → 100644
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 | +} |
addons/epay/assets/css/wechat.css
0 → 100644
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 | +} |
addons/epay/assets/images/alipay.png
0 → 100644
3.6 KB
addons/epay/assets/images/expired.png
0 → 100644
4.6 KB
addons/epay/assets/images/logo-alipay.png
0 → 100644
1.6 KB
addons/epay/assets/images/logo-wechat.png
0 → 100644
1.7 KB
addons/epay/assets/images/logo.png
0 → 100644
22.6 KB
addons/epay/assets/images/paid.png
0 → 100644
2.2 KB
addons/epay/assets/images/scan.png
0 → 100644
922 字节
addons/epay/assets/images/tips.png
0 → 100644
21.4 KB
addons/epay/assets/images/wechat.png
0 → 100644
22.2 KB
addons/epay/assets/js/common.js
0 → 100644
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 | +}); |
addons/epay/assets/less/common.less
0 → 100644
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 | +} |
addons/epay/assets/less/epay.less
0 → 100644
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 | +} |
addons/epay/certs/apiclient_cert.p12
0 → 100644
不能预览此文件类型
addons/epay/certs/apiclient_cert.pem
0 → 100644
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----- |
addons/epay/certs/apiclient_key.pem
0 → 100644
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----- |
addons/epay/config.php
0 → 100644
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 | +); |
addons/epay/controller/Api.php
0 → 100644
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 | +} |
addons/epay/controller/Index.php
0 → 100644
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 | +} |
addons/epay/info.ini
0 → 100644
addons/epay/library/OrderException.php
0 → 100644
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 | +} |
addons/epay/library/Service.php
0 → 100644
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 | +} |
addons/epay/library/Yansongda/Pay/Events.php
0 → 100644
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 | +} |
-
请 注册 或 登录 后发表评论