作者 景龙

增加废品回收文件

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

要显示太多修改。

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

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