2-1
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace addons\alisms;
|
||||
|
||||
use think\Addons;
|
||||
|
||||
/**
|
||||
* Alisms
|
||||
*/
|
||||
class Alisms extends Addons
|
||||
{
|
||||
|
||||
/**
|
||||
* 插件安装方法
|
||||
* @return bool
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件卸载方法
|
||||
* @return bool
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信发送行为
|
||||
* @param Sms $params
|
||||
* @return boolean
|
||||
*/
|
||||
public function smsSend(&$params)
|
||||
{
|
||||
$config = get_addon_config('alisms');
|
||||
$alisms = new library\Alisms();
|
||||
$result = $alisms->mobile($params->mobile)
|
||||
->template($config['template'][$params->event])
|
||||
->param(['code' => $params->code])
|
||||
->send();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信发送通知
|
||||
* @param array $params
|
||||
* @return boolean
|
||||
*/
|
||||
public function smsNotice(&$params)
|
||||
{
|
||||
$alisms = library\Alisms::instance();
|
||||
$result = $alisms->mobile($params['mobile'])
|
||||
->template($params['template'])
|
||||
->param($params)
|
||||
->send();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测验证是否正确
|
||||
* @param Sms $params
|
||||
* @return boolean
|
||||
*/
|
||||
public function smsCheck(&$params)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
return array (
|
||||
0 =>
|
||||
array (
|
||||
'name' => 'key',
|
||||
'title' => '应用key',
|
||||
'type' => 'string',
|
||||
'content' =>
|
||||
array (
|
||||
),
|
||||
'value' => 'LTAIrWkYK52SWqlA',
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
'name' => 'secret',
|
||||
'title' => '密钥secret',
|
||||
'type' => 'string',
|
||||
'content' =>
|
||||
array (
|
||||
),
|
||||
'value' => 'mAoToIZsRvuzM1n1Z66uj5cQctawCX',
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
),
|
||||
2 =>
|
||||
array (
|
||||
'name' => 'sign',
|
||||
'title' => '签名',
|
||||
'type' => 'string',
|
||||
'content' =>
|
||||
array (
|
||||
),
|
||||
'value' => '吉泰隆',
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
),
|
||||
3 =>
|
||||
array (
|
||||
'name' => 'template',
|
||||
'title' => '短信模板',
|
||||
'type' => 'array',
|
||||
'content' =>
|
||||
array (
|
||||
),
|
||||
'value' =>
|
||||
array (
|
||||
'register' => 'SMS_114000000',
|
||||
'resetpwd' => 'SMS_114000000',
|
||||
'changepwd' => 'SMS_114000000',
|
||||
'短信验证码' => 'SMS_119078242',
|
||||
),
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
),
|
||||
);
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace addons\alisms\controller;
|
||||
|
||||
use think\addons\Controller;
|
||||
|
||||
/**
|
||||
* 二维码生成
|
||||
*
|
||||
*/
|
||||
class Index extends Controller
|
||||
{
|
||||
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
}
|
||||
|
||||
//
|
||||
public function index()
|
||||
{
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
public function send()
|
||||
{
|
||||
$mobile = $this->request->post('mobile');
|
||||
$template = $this->request->post('template');
|
||||
$sign = $this->request->post('sign');
|
||||
$param = (array) json_decode($this->request->post('param'));
|
||||
$alisms = new \addons\alisms\library\Alisms();
|
||||
$ret = $alisms->mobile($mobile)
|
||||
->template($template)
|
||||
->sign($sign)
|
||||
->param($param)
|
||||
->send();
|
||||
if ($ret)
|
||||
{
|
||||
$this->success("发送成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->error("发送失败!失败原因:" . $alisms->getError());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
name = alisms
|
||||
title = 阿里短信发送
|
||||
intro = 阿里短信发送插件
|
||||
author = Karson
|
||||
website = http://www.fastadmin.net
|
||||
version = 1.0.1
|
||||
state = 1
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__mobile_code` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`type` varchar(30) NOT NULL DEFAULT '' COMMENT '类型',
|
||||
`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
|
||||
`code` varchar(10) DEFAULT '' COMMENT '验证码',
|
||||
`times` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '验证次数',
|
||||
`createtime` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='短信验证码表';
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
namespace addons\alisms\library;
|
||||
|
||||
use think\Config;
|
||||
|
||||
/**
|
||||
* 阿里大于SMS短信发送
|
||||
*/
|
||||
class Alisms
|
||||
{
|
||||
|
||||
private $_params = [];
|
||||
public $error = '';
|
||||
protected $config = [];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if ($config = get_addon_config('alisms'))
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
$this->config = array_merge($this->config, is_array($options) ? $options : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单例
|
||||
* @param array $options 参数
|
||||
* @return Alisms
|
||||
*/
|
||||
public static function instance($options = [])
|
||||
{
|
||||
if (is_null(self::$instance))
|
||||
{
|
||||
self::$instance = new static($options);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置签名
|
||||
* @param string $sign
|
||||
* @return Alisms
|
||||
*/
|
||||
public function sign($sign = '')
|
||||
{
|
||||
$this->_params['SignName'] = $sign;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置参数
|
||||
* @param array $param
|
||||
* @return Alisms
|
||||
*/
|
||||
public function param(array $param = [])
|
||||
{
|
||||
foreach ($param as $k => &$v)
|
||||
{
|
||||
$v = (string) $v;
|
||||
}
|
||||
unset($v);
|
||||
$this->_params['TemplateParam'] = json_encode($param);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模板
|
||||
* @param string $code 短信模板
|
||||
* @return Alisms
|
||||
*/
|
||||
public function template($code = '')
|
||||
{
|
||||
$this->_params['TemplateCode'] = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收手机
|
||||
* @param string $mobile 手机号码
|
||||
* @return Alisms
|
||||
*/
|
||||
public function mobile($mobile = '')
|
||||
{
|
||||
$this->_params['PhoneNumbers'] = $mobile;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即发送
|
||||
* @return boolean
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
$this->error = '';
|
||||
$params = $this->_params();
|
||||
$params['Signature'] = $this->_signed($params);
|
||||
$response = $this->_curl($params);
|
||||
if ($response !== FALSE)
|
||||
{
|
||||
$res = (array) json_decode($response, TRUE);
|
||||
if (isset($res['Code']) && $res['Code'] == 'OK')
|
||||
return TRUE;
|
||||
$this->error = isset($res['Message']) ? $res['Message'] : 'InvalidResult';
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->error = 'InvalidResult';
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @return array
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
private function _params()
|
||||
{
|
||||
return array_merge([
|
||||
'AccessKeyId' => $this->config['key'],
|
||||
'SignName' => isset($this->config['sign']) ? $this->config['sign'] : '',
|
||||
'Action' => 'SendSms',
|
||||
'Format' => 'JSON',
|
||||
'Version' => '2017-05-25',
|
||||
'SignatureVersion' => '1.0',
|
||||
'SignatureMethod' => 'HMAC-SHA1',
|
||||
'SignatureNonce' => uniqid(),
|
||||
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
|
||||
], $this->_params);
|
||||
}
|
||||
|
||||
private function percentEncode($string)
|
||||
{
|
||||
$string = urlencode($string);
|
||||
$string = preg_replace('/\+/', '%20', $string);
|
||||
$string = preg_replace('/\*/', '%2A', $string);
|
||||
$string = preg_replace('/%7E/', '~', $string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function _signed($params)
|
||||
{
|
||||
$sign = $this->config['secret'];
|
||||
ksort($params);
|
||||
$canonicalizedQueryString = '';
|
||||
foreach ($params as $key => $value)
|
||||
{
|
||||
$canonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value);
|
||||
}
|
||||
$stringToSign = 'GET&%2F&' . $this->percentencode(substr($canonicalizedQueryString, 1));
|
||||
$signature = base64_encode(hash_hmac('sha1', $stringToSign, $sign . '&', true));
|
||||
return $signature;
|
||||
}
|
||||
|
||||
private function _curl($params)
|
||||
{
|
||||
$uri = 'http://dysmsapi.aliyuncs.com/?' . http_build_query($params);
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
|
||||
curl_setopt($ch, CURLOPT_URL, $uri);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36");
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
|
||||
$reponse = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $reponse;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||
<title>Alisms短信发送示例</title>
|
||||
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="//cdn.fastadmin.net/assets/css/frontend.min.css" rel="stylesheet">
|
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||
<script src="//cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="well" style="margin-top:30px;">
|
||||
<form class="form-horizontal" action="{:addon_url('alisms/index/send')}">
|
||||
<fieldset>
|
||||
<legend>阿里大于短信发送</legend>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-2 control-label">手机号</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="text" class="form-control" name="mobile" placeholder="手机号">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-2 control-label">消息模板</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="text" class="form-control" name="template" placeholder="消息模板">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-2 control-label">签名</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="text" class="form-control" name="sign" placeholder="消息模板(可以留空)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-2 control-label">消息参数</label>
|
||||
<div class="col-lg-10">
|
||||
<textarea name="param" class="form-control" cols="30" rows="10" placeholder="必须是JSON字符串"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-10 col-lg-offset-2">
|
||||
<button type="submit" class="btn btn-primary">发送</button>
|
||||
<button type="reset" class="btn btn-default">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script src="//cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace addons\crontab;
|
||||
|
||||
use app\common\library\Menu;
|
||||
use think\Addons;
|
||||
|
||||
/**
|
||||
* 定时任务
|
||||
*/
|
||||
class Crontab extends Addons
|
||||
{
|
||||
|
||||
/**
|
||||
* 插件安装方法
|
||||
* @return bool
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
$menu = [
|
||||
[
|
||||
'name' => 'general/crontab',
|
||||
'title' => '定时任务',
|
||||
'icon' => 'fa fa-tasks',
|
||||
'remark' => '类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前仅支持三种任务:请求URL、执行SQL、执行Shell',
|
||||
'sublist' => [
|
||||
['name' => 'general/crontab/index', 'title' => '查看'],
|
||||
['name' => 'general/crontab/add', 'title' => '添加'],
|
||||
['name' => 'general/crontab/edit', 'title' => '编辑 '],
|
||||
['name' => 'general/crontab/del', 'title' => '删除'],
|
||||
['name' => 'general/crontab/multi', 'title' => '批量更新'],
|
||||
]
|
||||
]
|
||||
];
|
||||
Menu::create($menu, 'general');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件卸载方法
|
||||
* @return bool
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
Menu::delete('general/crontab');
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace app\admin\controller\general;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use Cron\CronExpression;
|
||||
|
||||
/**
|
||||
* 定时任务
|
||||
*
|
||||
* @icon fa fa-tasks
|
||||
* @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前仅支持三种任务:请求URL、执行SQL、执行Shell
|
||||
*/
|
||||
class Crontab extends Backend
|
||||
{
|
||||
|
||||
protected $model = null;
|
||||
protected $noNeedRight = ['check_schedule', 'get_schedule_future'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('Crontab');
|
||||
$this->view->assign('typedata', \app\common\model\Crontab::getTypeList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax())
|
||||
{
|
||||
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
|
||||
$total = $this->model
|
||||
->where($where)
|
||||
->order($sort, $order)
|
||||
->count();
|
||||
|
||||
$list = $this->model
|
||||
->where($where)
|
||||
->order($sort, $order)
|
||||
->limit($offset, $limit)
|
||||
->select();
|
||||
foreach ($list as $k => &$v)
|
||||
{
|
||||
$cron = CronExpression::factory($v['schedule']);
|
||||
$v['nexttime'] = $cron->getNextRunDate()->getTimestamp();
|
||||
}
|
||||
$result = array("total" => $total, "rows" => $list);
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Crontab格式是否正确
|
||||
* @internal
|
||||
*/
|
||||
public function check_schedule()
|
||||
{
|
||||
$row = $this->request->post("row/a");
|
||||
$schedule = isset($row['schedule']) ? $row['schedule'] : '';
|
||||
if (CronExpression::isValidExpression($schedule))
|
||||
{
|
||||
return json(['ok' => '']);
|
||||
}
|
||||
else
|
||||
{
|
||||
return json(['error' => __('Crontab format invalid')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Crontab表达式读取未来七次的时间
|
||||
* @internal
|
||||
*/
|
||||
public function get_schedule_future()
|
||||
{
|
||||
$time = [];
|
||||
$schedule = $this->request->post('schedule');
|
||||
$days = (int) $this->request->post('days');
|
||||
try
|
||||
{
|
||||
$cron = CronExpression::factory($schedule);
|
||||
for ($i = 0; $i < $days; $i++)
|
||||
{
|
||||
$time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return json(['futuretime' => $time]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'Title' => '任务标题',
|
||||
'Maximums' => '最多执行',
|
||||
'Sleep' => '延迟秒数',
|
||||
'Schedule' => '执行周期',
|
||||
'Executes' => '执行次数',
|
||||
'No limit' => '无限制',
|
||||
'Execute time' => '最后执行时间',
|
||||
'Request Url' => '请求URL',
|
||||
'Execute Sql Script' => '执行SQL',
|
||||
'Execute Shell' => '执行Shell',
|
||||
'Crontab format invalid' => 'Crontab格式错误',
|
||||
'Next execute time' => '下次预计时间',
|
||||
'The next %s times the execution time' => '接下来 %s 次的执行时间',
|
||||
];
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<style type="text/css">
|
||||
#schedulepicker {
|
||||
padding-top:7px;
|
||||
}
|
||||
</style>
|
||||
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" class="form-control" id="title" name="row[title]" value="" data-rule="required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
{:build_select('row[type]', $typedata, null, ['class'=>'form-control', 'data-rule'=>'required'])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="* * * * *" data-rule="required; remote(general/crontab/check_schedule)" />
|
||||
<div id="schedulepicker">
|
||||
<pre><code>* * * * *
|
||||
- - - - -
|
||||
| | | | +--- day of week (0 - 7) (Sunday=0 or 7)
|
||||
| | | +-------- month (1 - 12)
|
||||
| | +------------- day of month (1 - 31)
|
||||
| +------------------ hour (0 - 23)
|
||||
+----------------------- min (0 - 59)</code></pre>
|
||||
<h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
|
||||
<ol id="scheduleresult" class="list-group">
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="number" class="form-control" id="maximums" name="row[maximums]" value="0" data-rule="required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="" data-rule="{:__('Begin time')}:required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group hide layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
|
||||
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<style type="text/css">
|
||||
#schedulepicker {
|
||||
padding-top:7px;
|
||||
}
|
||||
</style>
|
||||
<form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" class="form-control" id="title" name="row[title]" value="{$row.title}" data-rule="required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
{:build_select('row[type]', $typedata, $row['type'], ['class'=>'form-control', 'data-rule'=>'required'])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required">{$row.content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="{$row.schedule}" data-rule="required; remote(general/crontab/check_schedule)" />
|
||||
<div id="schedulepicker">
|
||||
<pre><code>* * * * *
|
||||
- - - - -
|
||||
| | | | +--- day of week (0 - 7) (Sunday=0 or 7)
|
||||
| | | +-------- month (1 - 12)
|
||||
| | +------------- day of month (1 - 31)
|
||||
| +------------------ hour (0 - 23)
|
||||
+----------------------- min (0 - 59)</code></pre>
|
||||
<h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
|
||||
<ol id="scheduleresult" class="list-group">
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="number" class="form-control" id="maximums" name="row[maximums]" value="{$row.maximums}" data-rule="required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="{$row.begintime|datetime}" data-rule="{:__('Begin time')}:required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="{$row.endtime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group hide layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
|
||||
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<div class="panel panel-default panel-intro">
|
||||
{:build_heading()}
|
||||
|
||||
<div class="panel-body">
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div class="tab-pane fade active in" id="one">
|
||||
<div class="widget-body no-padding">
|
||||
<div id="toolbar" class="toolbar">
|
||||
{:build_toolbar()}
|
||||
<div class="dropdown btn-group {:$auth->check('general/crontab/multi')?'':'hide'}">
|
||||
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
|
||||
<ul class="dropdown-menu text-left" role="menu">
|
||||
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
|
||||
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover"
|
||||
data-operate-edit="{:$auth->check('general/crontab/edit')}"
|
||||
data-operate-del="{:$auth->check('general/crontab/del')}"
|
||||
width="100%">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Crontab extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
];
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
'type_text'
|
||||
];
|
||||
|
||||
public static function getTypeList()
|
||||
{
|
||||
return [
|
||||
'url' => __('Request Url'),
|
||||
'sql' => __('Execute Sql Script'),
|
||||
'shell' => __('Execute Shell'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTypeTextAttr($value, $data)
|
||||
{
|
||||
$typelist = self::getTypeList();
|
||||
$value = $value ? $value : $data['type'];
|
||||
return $value && isset($typelist[$value]) ? $typelist[$value] : $value;
|
||||
}
|
||||
|
||||
protected function setBegintimeAttr($value)
|
||||
{
|
||||
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||
}
|
||||
|
||||
protected function setEndtimeAttr($value)
|
||||
{
|
||||
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||
}
|
||||
|
||||
protected function setExecutetimeAttr($value)
|
||||
{
|
||||
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
];
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace addons\crontab\controller;
|
||||
|
||||
use app\common\model\Crontab;
|
||||
use Cron\CronExpression;
|
||||
use fast\Http;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\Log;
|
||||
|
||||
/**
|
||||
* 定时任务接口
|
||||
*
|
||||
* 以Crontab方式每分钟定时执行,且只可以Cli方式运行
|
||||
* @internal
|
||||
*/
|
||||
class Autotask extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* 初始化方法,最前且始终执行
|
||||
*/
|
||||
public function _initialize()
|
||||
{
|
||||
// 只可以以cli方式执行
|
||||
if (!$this->request->isCli())
|
||||
$this->error('Autotask script only work at client!');
|
||||
|
||||
parent::_initialize();
|
||||
|
||||
// 清除错误
|
||||
error_reporting(0);
|
||||
|
||||
// 设置永不超时
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行定时任务
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$time = time();
|
||||
$logDir = LOG_PATH . 'crontab/';
|
||||
if (!is_dir($logDir))
|
||||
{
|
||||
mkdir($logDir, 0755);
|
||||
}
|
||||
//筛选未过期且未完成的任务
|
||||
$crontabList = Crontab::where('status', '=', 'normal')->order('weigh desc,id desc')->select();
|
||||
foreach ($crontabList as $crontab)
|
||||
{
|
||||
$update = [];
|
||||
$execute = FALSE;
|
||||
if ($time < $crontab['begintime'])
|
||||
{
|
||||
//任务未开始
|
||||
continue;
|
||||
}
|
||||
if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums'])
|
||||
{
|
||||
//任务已超过最大执行次数
|
||||
$update['status'] = 'completed';
|
||||
}
|
||||
else if ($crontab['endtime'] > 0 && $time > $crontab['endtime'])
|
||||
{
|
||||
//任务已过期
|
||||
$update['status'] = 'expired';
|
||||
}
|
||||
else
|
||||
{
|
||||
//重复执行
|
||||
//如果未到执行时间则继续循环
|
||||
$cron = CronExpression::factory($crontab['schedule']);
|
||||
if (!$cron->isDue() || date("YmdHi") === date("YmdHi", $crontab['executetime']))
|
||||
continue;
|
||||
$execute = TRUE;
|
||||
}
|
||||
|
||||
// 如果允许执行
|
||||
if ($execute)
|
||||
{
|
||||
$update['executetime'] = $time;
|
||||
$update['executes'] = $crontab['executes'] + 1;
|
||||
$update['status'] = ($crontab['maximums'] > 0 && $update['executes'] >= $crontab['maximums']) ? 'completed' : 'normal';
|
||||
}
|
||||
|
||||
// 如果需要更新状态
|
||||
if (!$update)
|
||||
continue;
|
||||
// 更新状态
|
||||
$crontab->save($update);
|
||||
|
||||
// 将执行放在后面是为了避免超时导致多次执行
|
||||
if (!$execute)
|
||||
continue;
|
||||
try
|
||||
{
|
||||
if ($crontab['type'] == 'url')
|
||||
{
|
||||
if (substr($crontab['content'], 0, 1) == "/")
|
||||
{
|
||||
// 本地项目URL
|
||||
exec('nohup php ' . ROOT_PATH . 'public/index.php ' . $crontab['content'] . ' >> ' . $logDir . date("Y-m-d") . '.log 2>&1 &');
|
||||
}
|
||||
else
|
||||
{
|
||||
// 远程异步调用URL
|
||||
Http::sendAsyncRequest($crontab['content']);
|
||||
}
|
||||
}
|
||||
else if ($crontab['type'] == 'sql')
|
||||
{
|
||||
//这里需要强制重连数据库,使用已有的连接会报2014错误
|
||||
$connect = Db::connect([], true);
|
||||
$connect->execute("select 1");
|
||||
// 执行SQL
|
||||
$connect->getPdo()->exec($crontab['content']);
|
||||
}
|
||||
else if ($crontab['type'] == 'shell')
|
||||
{
|
||||
// 执行Shell
|
||||
exec($crontab['content'] . ' >> ' . $logDir . date("Y-m-d") . '.log 2>&1 &');
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
Log::record($e->getMessage());
|
||||
}
|
||||
}
|
||||
return 'Execute completed!';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace addons\crontab\controller;
|
||||
|
||||
use think\addons\Controller;
|
||||
|
||||
class Index extends Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->error("当前插件暂无前台页面");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
name = crontab
|
||||
title = 定时任务
|
||||
intro = 可在线管理Crontab定时任务
|
||||
author = Karson
|
||||
website = http://www.fastadmin.net
|
||||
version = 1.0.0
|
||||
state = 1
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
CREATE TABLE IF NOT EXISTS `__PREFIX__crontab` (
|
||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`type` varchar(10) NOT NULL DEFAULT '' COMMENT '事件类型',
|
||||
`title` varchar(100) NOT NULL DEFAULT '' COMMENT '事件标题',
|
||||
`content` text NOT NULL COMMENT '事件内容',
|
||||
`schedule` varchar(100) NOT NULL DEFAULT '' COMMENT 'Crontab格式',
|
||||
`sleep` tinyint(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '延迟秒数执行',
|
||||
`maximums` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最大执行次数 0为不限',
|
||||
`executes` 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 '更新时间',
|
||||
`begintime` int(10) NOT NULL DEFAULT '0' COMMENT '开始时间',
|
||||
`endtime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '结束时间',
|
||||
`executetime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最后执行时间',
|
||||
`weigh` int(10) NOT NULL DEFAULT '0' COMMENT '权重',
|
||||
`status` enum('completed','expired','hidden','normal') NOT NULL DEFAULT 'normal' COMMENT '状态',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='定时任务表' ROW_FORMAT=DYNAMIC;
|
||||
|
||||
BEGIN;
|
||||
INSERT INTO `__PREFIX__crontab` (`id`, `type`, `title`, `content`, `schedule`, `sleep`, `maximums`, `executes`, `createtime`, `updatetime`, `begintime`, `endtime`, `executetime`, `weigh`, `status`) VALUES
|
||||
(1, 'url', '请求FastAdmin', 'http://www.fastadmin.net', '* * * * *', 0, 0, 13548, 1497070825, 1501253101, 1483200000, 1546272000, 1501253101, 1, 'normal'),
|
||||
(2, 'sql', '查询一条SQL', 'SELECT 1;', '* * * * *', 0, 0, 13548, 1497071095, 1501253101, 1483200000, 1546272000, 1501253101, 2, 'normal');
|
||||
COMMIT;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
|
||||
|
||||
var Controller = {
|
||||
index: function () {
|
||||
// 初始化表格参数配置
|
||||
Table.api.init({
|
||||
extend: {
|
||||
index_url: 'general/crontab/index',
|
||||
add_url: 'general/crontab/add',
|
||||
edit_url: 'general/crontab/edit',
|
||||
del_url: 'general/crontab/del',
|
||||
multi_url: 'general/crontab/multi',
|
||||
table: 'crontab'
|
||||
}
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
// 初始化表格
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
sortName: 'weigh',
|
||||
columns: [
|
||||
[
|
||||
{field: 'state', checkbox: true, },
|
||||
{field: 'id', title: 'ID'},
|
||||
{field: 'type_text', title: __('Type'), operate: false},
|
||||
{field: 'title', title: __('Title')},
|
||||
{field: 'maximums', title: __('Maximums'), formatter: Controller.api.formatter.maximums},
|
||||
{field: 'executes', title: __('Executes')},
|
||||
{field: 'begintime', title: __('Begin time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
|
||||
{field: 'endtime', title: __('End time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
|
||||
{field: 'nexttime', title: __('Next execute time'), formatter: Table.api.formatter.datetime, operate: false},
|
||||
{field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
|
||||
{field: 'weigh', title: __('Weigh')},
|
||||
{field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
|
||||
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
// 为表格绑定事件
|
||||
Table.api.bindevent(table);
|
||||
},
|
||||
add: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
api: {
|
||||
bindevent: function () {
|
||||
$('#schedule').on('valid.field', function (e, result) {
|
||||
$("#pickdays").trigger("change");
|
||||
});
|
||||
Form.api.bindevent($("form[role=form]"));
|
||||
$(document).on("change", "#pickdays", function () {
|
||||
$("#scheduleresult").html(__('Loading'));
|
||||
$.post("general/crontab/get_schedule_future", {schedule: $("#schedule").val(), days: $(this).val()}, function (ret) {
|
||||
$("#scheduleresult").html("");
|
||||
if (typeof ret.futuretime !== 'undefined' && $.isArray(ret.futuretime)) {
|
||||
$.each(ret.futuretime, function (i, j) {
|
||||
$("#scheduleresult").append("<li class='list-group-item'>" + j + "<span class='badge'>" + (i + 1) + "</span></li>");
|
||||
});
|
||||
}
|
||||
}, 'json');
|
||||
|
||||
});
|
||||
$("#pickdays").trigger("change");
|
||||
},
|
||||
formatter: {
|
||||
maximums: function (value, row, index) {
|
||||
return value === 0 ? __('No limit') : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace addons\properties;
|
||||
|
||||
use think\Addons;
|
||||
|
||||
/**
|
||||
* 插件
|
||||
*/
|
||||
class Properties extends Addons
|
||||
{
|
||||
|
||||
/**
|
||||
* 插件安装方法
|
||||
* @return bool
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件卸载方法
|
||||
* @return bool
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
require(['form', 'upload'], function (Form, Upload) {
|
||||
var _bindevent = Form.events.bindevent;
|
||||
Form.events.bindevent = function (form) {
|
||||
_bindevent.apply(this, [form]);
|
||||
try {
|
||||
//绑定自定义属性组件
|
||||
if ($("textarea[id$=Properties]", form).size() > 0) {
|
||||
require(['../addons/properties/Properties'], function (Properties){
|
||||
var obj=$("textarea[id$=Properties]", form);
|
||||
|
||||
var table=Properties.init(obj);
|
||||
|
||||
$(form).on("click",".property_add",function(event){
|
||||
Properties.create(table);
|
||||
})
|
||||
|
||||
$(form).on("click",".property_del",function(event){
|
||||
Properties.remove(table,$(this).parents("tr").data("index"));
|
||||
})
|
||||
|
||||
$(form).on("click",".property_drag",function(event){
|
||||
Properties.sort(table,$(this).data("index"),$(this).data("direction"));
|
||||
})
|
||||
|
||||
$(form).on("change","input",function(event){
|
||||
Properties.set(table,$(this).parents("tr").data("index"));
|
||||
})
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
];
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace addons\properties\controller;
|
||||
|
||||
use think\addons\Controller;
|
||||
|
||||
class Index extends Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->error("当前插件暂无前台页面");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
name = properties
|
||||
title = 自定义属性插件
|
||||
intro = 自定义属性
|
||||
author = ChrisLeung
|
||||
website = https://github.com/Chrisleung
|
||||
version = 1.0.0
|
||||
state = 1
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
define([],function(){
|
||||
var Properties={
|
||||
formatter:{
|
||||
property: function (value, row, index) {
|
||||
return '<div class="input-group" style="width:100%"><input type="text" class="form-control input-sm" value="'+value+'" ></div>';
|
||||
},
|
||||
operate:function(value,row,index){
|
||||
return '\<div class="btn-group btn-group-justified">\
|
||||
<span class="btn btn-default property_drag" data-index="'+index+'" data-direction="up"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>\
|
||||
<span class="btn btn-default property_drag" data-index="'+index+'" data-direction="down"><i class="fa fa-arrow-down" aria-hidden="true"></i></span>\
|
||||
<span class="btn btn-danger property_del" data-index="'+index+'"><i class="fa fa-times" aria-hidden="true"></i></span></div>';
|
||||
}
|
||||
},
|
||||
//初始化表格
|
||||
init: function(obj) {
|
||||
obj.hide();
|
||||
obj.before("<style>\
|
||||
.swap_done{animation-name: swap;animation-duration: 1s;animation-timing-function: ease-out;animation-direction: alternate;animation-iteration-count: 1;animation-fill-mode: backwards;animation-play-state: running;}\
|
||||
@keyframes swap{0%,100%{background-color:#f1f4f6}50%{background-color:#ddd}}\
|
||||
</style>")
|
||||
.before('<table class="properties"></table>')
|
||||
.before('<button type="button" class="btn btn-success btn-embossed property_add" style="margin-top:15px;">添加参数</button>');
|
||||
var table=obj.parent().find("table.properties")
|
||||
|
||||
if(obj.val()!=""){
|
||||
Properties.create(table);
|
||||
table.bootstrapTable('load',JSON.parse(obj.val()));
|
||||
}
|
||||
|
||||
return table;
|
||||
},
|
||||
//创建表头,若存在则添加一行
|
||||
create:function(table){
|
||||
if(table.find("tbody").size()==0){
|
||||
table.bootstrapTable({
|
||||
showHeader:true,
|
||||
mobileResponsive:true,
|
||||
columns: [{
|
||||
field: 'name',
|
||||
title: '参数名',
|
||||
class: 'property_input',
|
||||
formatter:Properties.formatter.property
|
||||
}, {
|
||||
field: 'value',
|
||||
title: '参数值',
|
||||
class: 'property_input',
|
||||
formatter:Properties.formatter.property
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
title: '操作',
|
||||
width: '150px',
|
||||
formatter:Properties.formatter.operate
|
||||
}],
|
||||
data:[{name:'',value:''}]
|
||||
|
||||
});
|
||||
}else{
|
||||
Properties.append(table);
|
||||
}
|
||||
},
|
||||
//添加
|
||||
append:function(table){
|
||||
var property={
|
||||
name:'',
|
||||
value:''
|
||||
}
|
||||
table.bootstrapTable('append',property)
|
||||
},
|
||||
//删除
|
||||
remove:function(table,index){
|
||||
var data=table.bootstrapTable('getData',true);
|
||||
data.splice(index,1),
|
||||
table.bootstrapTable('load',data)
|
||||
Properties.save(table);
|
||||
},
|
||||
//排序
|
||||
sort:function(table,index,direction){
|
||||
var data=table.bootstrapTable('getData',true);
|
||||
|
||||
switch(direction){
|
||||
case 'up':
|
||||
if(index>0){
|
||||
table.bootstrapTable('load',Properties.swap(data,index,index-1));
|
||||
table.find("tr[data-index="+(index-1)+"]").addClass('swap_done')
|
||||
}else{
|
||||
layer.tips('已经到达顶部',
|
||||
table.find(".property_drag[data-index="+index+"][data-direction="+direction+"]"),
|
||||
{
|
||||
tips:1
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'down':
|
||||
if(index<data.length-1){
|
||||
table.bootstrapTable('load',Properties.swap(data,index,index+1));
|
||||
table.find("tr[data-index="+(index+1)+"]").addClass('swap_done')
|
||||
}else{
|
||||
layer.tips('已经到达底部',
|
||||
table.find(".property_drag[data-index="+index+"][data-direction="+direction+"]"),
|
||||
{
|
||||
tips:1
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Properties.save(table);
|
||||
},
|
||||
|
||||
//设置属性
|
||||
set:function(table,index){
|
||||
|
||||
var property_input=table.find("tr[data-index="+index+"] td.property_input");
|
||||
var property={
|
||||
name:property_input.first().find("input").val(),
|
||||
value:property_input.last().find("input").val()
|
||||
}
|
||||
table.bootstrapTable('updateRow', {
|
||||
index: index,
|
||||
row: property
|
||||
})
|
||||
|
||||
Properties.save(table);
|
||||
},
|
||||
|
||||
//保存
|
||||
save:function(table){
|
||||
table.parents(".bootstrap-table")
|
||||
.siblings('textarea').val(JSON.stringify(table.bootstrapTable('getData',true)))
|
||||
},
|
||||
|
||||
//交换排序
|
||||
swap:function(data, old_pos, new_pos) {
|
||||
data[old_pos] = data.splice(new_pos, 1, data[old_pos])[0];
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
return Properties;
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace addons\summernote;
|
||||
|
||||
use think\Addons;
|
||||
|
||||
/**
|
||||
* Summernote插件
|
||||
*/
|
||||
class Summernote extends Addons
|
||||
{
|
||||
|
||||
/**
|
||||
* 插件安装方法
|
||||
* @return bool
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件卸载方法
|
||||
* @return bool
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
require(['form', 'upload'], function (Form, Upload) {
|
||||
var _bindevent = Form.events.bindevent;
|
||||
Form.events.bindevent = function (form) {
|
||||
_bindevent.apply(this, [form]);
|
||||
try {
|
||||
//绑定summernote事件
|
||||
if ($(".summernote,.editor", form).size() > 0) {
|
||||
require(['summernote'], function () {
|
||||
$(".summernote,.editor", form).summernote({
|
||||
height: 250,
|
||||
lang: 'zh-CN',
|
||||
fontNames: [
|
||||
'Arial', 'Arial Black', 'Serif', 'Sans', 'Courier',
|
||||
'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande',
|
||||
"Open Sans", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆',
|
||||
],
|
||||
fontNamesIgnoreCheck: [
|
||||
"Open Sans", "Microsoft YaHei",
|
||||
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆'
|
||||
],
|
||||
toolbar: [
|
||||
['style', ['style', 'undo', 'redo']],
|
||||
['font', ['bold', 'underline', 'strikethrough', 'clear']],
|
||||
['fontname', ['color', 'fontname', 'fontsize']],
|
||||
['para', ['ul', 'ol', 'paragraph', 'height']],
|
||||
['table', ['table', 'hr']],
|
||||
['insert', ['link', 'picture', 'video']],
|
||||
['view', ['fullscreen', 'codeview', 'help']]
|
||||
],
|
||||
dialogsInBody: true,
|
||||
callbacks: {
|
||||
onChange: function (contents) {
|
||||
$(this).val(contents);
|
||||
$(this).trigger('change');
|
||||
},
|
||||
onInit: function () {
|
||||
},
|
||||
onImageUpload: function (files) {
|
||||
var that = this;
|
||||
//依次上传图片
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
Upload.api.send(files[i], function (data) {
|
||||
var url = Fast.api.cdnurl(data.url);
|
||||
$(that).summernote("insertImage", url, 'filename');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
];
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace addons\summernote\controller;
|
||||
|
||||
use think\addons\Controller;
|
||||
|
||||
class Index extends Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->error("当前插件暂无前台页面");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
name = summernote
|
||||
title = Summernote插件
|
||||
intro = 修改后台默认编辑器为Summernote
|
||||
author = Karson
|
||||
website = http://www.fastadmin.net
|
||||
version = 1.0.1
|
||||
state = 1
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace addons\third;
|
||||
|
||||
use think\Addons;
|
||||
|
||||
/**
|
||||
* 第三方登录
|
||||
*/
|
||||
class Third extends Addons
|
||||
{
|
||||
|
||||
/**
|
||||
* 插件安装方法
|
||||
* @return bool
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件卸载方法
|
||||
* @return bool
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
0 =>
|
||||
array(
|
||||
'name' => 'qq',
|
||||
'title' => 'QQ',
|
||||
'type' => 'array',
|
||||
'content' =>
|
||||
array(
|
||||
'app_id' => '',
|
||||
'app_secret' => '',
|
||||
'scope' => 'get_user_info',
|
||||
),
|
||||
'value' =>
|
||||
array(
|
||||
'app_id' => '100246200',
|
||||
'app_secret' => '0d4d1bf5210f167226c49f4eb3715512',
|
||||
'scope' => 'get_user_info',
|
||||
),
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
),
|
||||
1 =>
|
||||
array(
|
||||
'name' => 'wechat',
|
||||
'title' => '微信',
|
||||
'type' => 'array',
|
||||
'content' =>
|
||||
array(
|
||||
'app_id' => '',
|
||||
'app_secret' => '',
|
||||
'callback' => '',
|
||||
'scope' => 'snsapi_userinfo',
|
||||
),
|
||||
'value' =>
|
||||
array(
|
||||
'app_id' => 'wx91b3fe578d6467ac',
|
||||
'app_secret' => 'aa6726df5cb2b6278d7f373d009510d0',
|
||||
'scope' => 'get_user_info',
|
||||
),
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
),
|
||||
2 =>
|
||||
array(
|
||||
'name' => 'weibo',
|
||||
'title' => '微博',
|
||||
'type' => 'array',
|
||||
'content' =>
|
||||
array(
|
||||
'app_id' => '',
|
||||
'app_secret' => '',
|
||||
'scope' => 'get_user_info',
|
||||
),
|
||||
'value' =>
|
||||
array(
|
||||
'app_id' => '645217067',
|
||||
'app_secret' => '226b4baaf3799e88dec7fcabf5837185',
|
||||
'scope' => 'get_user_info',
|
||||
),
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
),
|
||||
);
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace addons\third\controller;
|
||||
|
||||
/**
|
||||
* 第三方登录
|
||||
*/
|
||||
class Index extends \think\addons\Controller
|
||||
{
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->error("当前插件暂无前台页面");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
name = third
|
||||
title = 第三方登录
|
||||
intro = 使用微信、QQ、微博登录插件
|
||||
author = Karson
|
||||
website = http://www.fastadmin.net
|
||||
version = 1.0.1
|
||||
state = 1
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
CREATE TABLE IF NOT EXISTS `__PREFIX__third` (
|
||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '会员ID',
|
||||
`platform` enum('weibo','wechat','qq') NOT NULL COMMENT '第三方应用',
|
||||
`openid` varchar(50) NOT NULL DEFAULT '' COMMENT '第三方唯一ID',
|
||||
`openname` varchar(50) NOT NULL DEFAULT '' COMMENT '第三方会员昵称',
|
||||
`access_token` varchar(100) NOT NULL DEFAULT '',
|
||||
`refresh_token` varchar(100) NOT NULL DEFAULT '',
|
||||
`expires_in` 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 '更新时间',
|
||||
`logintime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '登录时间',
|
||||
`expiretime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '过期时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `platform` (`platform`,`openid`),
|
||||
KEY `user_id` (`user_id`,`platform`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='第三方登录表' ROW_FORMAT=DYNAMIC;
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace addons\third\library;
|
||||
|
||||
class Application
|
||||
{
|
||||
|
||||
/**
|
||||
* 配置信息
|
||||
* @var array
|
||||
*/
|
||||
private $config = [];
|
||||
|
||||
/**
|
||||
* 服务提供者
|
||||
* @var array
|
||||
*/
|
||||
private $providers = [
|
||||
'qq' => 'Qq',
|
||||
'weibo' => 'Weibo',
|
||||
'wechat' => 'Wechat',
|
||||
];
|
||||
|
||||
/**
|
||||
* 服务对象信息
|
||||
* @var array
|
||||
*/
|
||||
protected $services = [];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
$this->config = array_merge($this->config, is_array($options) ? $options : []);
|
||||
|
||||
//注册服务器提供者
|
||||
$this->registerProviders();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册服务提供者
|
||||
*/
|
||||
private function registerProviders()
|
||||
{
|
||||
foreach ($this->providers as $k => $v)
|
||||
{
|
||||
$this->services[$k] = function() use ($k, $v) {
|
||||
$options = $this->config[$k];
|
||||
$options['app_id'] = isset($options['app_id']) ? $options['app_id'] : '';
|
||||
$options['app_secret'] = isset($options['app_secret']) ? $options['app_secret'] : '';
|
||||
// 如果未定义回调地址则自动生成
|
||||
$options['callback'] = isset($options['callback']) && $options['callback'] ? $options['callback'] : url('/', [], false, true);
|
||||
$objname = __NAMESPACE__ . "\\{$v}";
|
||||
return new $objname($options);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$this->services[$key] = $value;
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->services[$key]($this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace addons\third\library;
|
||||
|
||||
use fast\Http;
|
||||
use think\Config;
|
||||
use think\Session;
|
||||
|
||||
/**
|
||||
* QQ
|
||||
*/
|
||||
class Qq
|
||||
{
|
||||
|
||||
const GET_AUTH_CODE_URL = "https://graph.qq.com/oauth2.0/authorize";
|
||||
const GET_ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token";
|
||||
const GET_USERINFO_URL = "https://graph.qq.com/user/get_user_info";
|
||||
const GET_OPENID_URL = "https://graph.qq.com/oauth2.0/me";
|
||||
|
||||
/**
|
||||
* 配置信息
|
||||
* @var array
|
||||
*/
|
||||
private $config = [];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if ($config = Config::get('third.qq'))
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
$this->config = array_merge($this->config, is_array($options) ? $options : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登陆
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
header("Location:" . $this->getAuthorizeUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取authorize_url
|
||||
*/
|
||||
public function getAuthorizeUrl()
|
||||
{
|
||||
$state = md5(uniqid(rand(), TRUE));
|
||||
Session::set('state', $state);
|
||||
$queryarr = array(
|
||||
"response_type" => "code",
|
||||
"client_id" => $this->config['app_id'],
|
||||
"redirect_uri" => $this->config['callback'],
|
||||
"scope" => $this->config['scope'],
|
||||
"state" => $state,
|
||||
);
|
||||
request()->isMobile() && $queryarr['display'] = 'mobile';
|
||||
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($queryarr);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function getUserInfo($params = [])
|
||||
{
|
||||
$params = $params ? $params : $_GET;
|
||||
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == Session::get('state') && isset($params['code'])))
|
||||
{
|
||||
//获取access_token
|
||||
$data = isset($params['code']) ? $this->getAccessToken($params['code']) : $params;
|
||||
$access_token = isset($data['access_token']) ? $data['access_token'] : '';
|
||||
$refresh_token = isset($data['refresh_token']) ? $data['refresh_token'] : '';
|
||||
$expires_in = isset($data['expires_in']) ? $data['expires_in'] : 0;
|
||||
if ($access_token)
|
||||
{
|
||||
$openid = $this->getOpenId($access_token);
|
||||
//获取用户信息
|
||||
$queryarr = [
|
||||
"access_token" => $access_token,
|
||||
"oauth_consumer_key" => $this->config['app_id'],
|
||||
"openid" => $openid,
|
||||
];
|
||||
$ret = Http::get(self::GET_USERINFO_URL, $queryarr);
|
||||
$userinfo = json_decode($ret, TRUE);
|
||||
if (!$userinfo || !isset($userinfo['ret']) || $userinfo['ret'] !== 0)
|
||||
return [];
|
||||
$userinfo = $userinfo ? $userinfo : [];
|
||||
$userinfo['avatar'] = isset($userinfo['figureurl_qq_2']) ? $userinfo['figureurl_qq_2'] : '';
|
||||
$data = [
|
||||
'access_token' => $access_token,
|
||||
'refresh_token' => $refresh_token,
|
||||
'expires_in' => $expires_in,
|
||||
'openid' => $openid,
|
||||
'userinfo' => $userinfo
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取access_token
|
||||
* @param string $code
|
||||
* @return array
|
||||
*/
|
||||
private function getAccessToken($code = '')
|
||||
{
|
||||
if (!$code)
|
||||
return '';
|
||||
$queryarr = array(
|
||||
"grant_type" => "authorization_code",
|
||||
"client_id" => $this->config['app_id'],
|
||||
"client_secret" => $this->config['app_secret'],
|
||||
"redirect_uri" => $this->config['callback'],
|
||||
"code" => $code,
|
||||
);
|
||||
$ret = Http::get(self::GET_ACCESS_TOKEN_URL, $queryarr);
|
||||
$params = [];
|
||||
parse_str($ret, $params);
|
||||
return $params ? $params : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取open_id
|
||||
* @param string $access_token
|
||||
* @return string
|
||||
*/
|
||||
private function getOpenId($access_token = '')
|
||||
{
|
||||
$response = Http::get(self::GET_OPENID_URL, ['access_token' => $access_token]);
|
||||
if (strpos($response, "callback") !== false)
|
||||
{
|
||||
$lpos = strpos($response, "(");
|
||||
$rpos = strrpos($response, ")");
|
||||
$response = substr($response, $lpos + 1, $rpos - $lpos - 1);
|
||||
}
|
||||
$user = json_decode($response, TRUE);
|
||||
return isset($user['openid']) ? $user['openid'] : '';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace addons\third\library;
|
||||
|
||||
use addons\third\model\Third;
|
||||
use fast\Random;
|
||||
|
||||
/**
|
||||
* 第三方登录服务类
|
||||
*
|
||||
* @author Karson
|
||||
*/
|
||||
class Service
|
||||
{
|
||||
|
||||
/**
|
||||
* 第三方登录
|
||||
* @param string $platform 平台
|
||||
* @param array $params 参数
|
||||
* @param int $keeptime 有效时长
|
||||
* @return boolean
|
||||
*/
|
||||
public static function connect($platform, $params = [], $keeptime = 0)
|
||||
{
|
||||
$time = time();
|
||||
$values = [
|
||||
'platform' => $platform,
|
||||
'openid' => $params['openid'],
|
||||
'openname' => isset($params['userinfo']['nickname']) ? $params['userinfo']['nickname'] : '',
|
||||
'access_token' => $params['access_token'],
|
||||
'refresh_token' => $params['refresh_token'],
|
||||
'expires_in' => $params['expires_in'],
|
||||
'logintime' => $time,
|
||||
'expiretime' => $time + $params['expires_in'],
|
||||
];
|
||||
$auth = \app\common\library\Auth::instance();
|
||||
|
||||
$auth->keeptime($keeptime);
|
||||
$third = Third::get(['platform' => $platform, 'openid' => $params['openid']]);
|
||||
if ($third)
|
||||
{
|
||||
$user = \addons\user\model\User::get($third['user_id']);
|
||||
if (!$user)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
$third->save($values);
|
||||
return $auth->direct($user->id);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 先随机一个用户名,随后再变更为u+数字id
|
||||
$username = Random::alnum(20);
|
||||
$password = Random::alnum(6);
|
||||
// 默认注册一个会员
|
||||
$result = $auth->register($username, $password, '', '', [], $keeptime);
|
||||
if (!$result)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
$user = $auth->getUser();
|
||||
$fields = ['username' => 'u' . $user->id];
|
||||
if (isset($params['userinfo']['nickname']))
|
||||
$fields['nickname'] = $params['userinfo']['nickname'];
|
||||
if (isset($params['userinfo']['avatar']))
|
||||
$fields['avatar'] = $params['userinfo']['avatar'];
|
||||
|
||||
// 更新会员资料
|
||||
$user->save($fields);
|
||||
|
||||
// 保存第三方信息
|
||||
$values['user_id'] = $user->id;
|
||||
Third::create($values);
|
||||
|
||||
// 写入登录Cookies和Token
|
||||
return $auth->direct($user->id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
namespace addons\third\library;
|
||||
|
||||
use fast\Http;
|
||||
use think\Config;
|
||||
use think\Session;
|
||||
|
||||
/**
|
||||
* 微信
|
||||
*/
|
||||
class Wechat
|
||||
{
|
||||
|
||||
const GET_AUTH_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
|
||||
const GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
|
||||
const GET_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo";
|
||||
|
||||
/**
|
||||
* 配置信息
|
||||
* @var array
|
||||
*/
|
||||
private $config = [];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if ($config = Config::get('third.wechat'))
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
$this->config = array_merge($this->config, is_array($options) ? $options : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登陆
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
header("Location:" . $this->getAuthorizeUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取authorize_url
|
||||
*/
|
||||
public function getAuthorizeUrl()
|
||||
{
|
||||
$state = md5(uniqid(rand(), TRUE));
|
||||
Session::set('state', $state);
|
||||
$queryarr = array(
|
||||
"appid" => $this->config['app_id'],
|
||||
"redirect_uri" => $this->config['callback'],
|
||||
"response_type" => "code",
|
||||
"scope" => $this->config['scope'],
|
||||
"state" => $state,
|
||||
);
|
||||
request()->isMobile() && $queryarr['display'] = 'mobile';
|
||||
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($queryarr) . '#wechat_redirect';
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function getUserInfo($params = [])
|
||||
{
|
||||
$params = $params ? $params : $_GET;
|
||||
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == Session::get('state') && isset($params['code'])))
|
||||
{
|
||||
//获取access_token
|
||||
$data = isset($params['code']) ? $this->getAccessToken($params['code']) : $params;
|
||||
$access_token = isset($data['access_token']) ? $data['access_token'] : '';
|
||||
$refresh_token = isset($data['refresh_token']) ? $data['refresh_token'] : '';
|
||||
$expires_in = isset($data['expires_in']) ? $data['expires_in'] : 0;
|
||||
if ($access_token)
|
||||
{
|
||||
$openid = isset($data['openid']) ? $data['openid'] : '';
|
||||
$unionid = isset($data['unionid']) ? $data['unionid'] : '';
|
||||
//获取用户信息
|
||||
$queryarr = [
|
||||
"access_token" => $access_token,
|
||||
"openid" => $openid,
|
||||
"lang" => 'zh_CN'
|
||||
];
|
||||
$ret = Http::post(self::GET_USERINFO_URL, $queryarr);
|
||||
$userinfo = json_decode($ret, TRUE);
|
||||
if (!$userinfo || isset($userinfo['errcode']))
|
||||
return [];
|
||||
$userinfo = $userinfo ? $userinfo : [];
|
||||
$userinfo['avatar'] = isset($userinfo['headimgurl']) ? $userinfo['headimgurl'] : '';
|
||||
$data = [
|
||||
'access_token' => $access_token,
|
||||
'refresh_token' => $refresh_token,
|
||||
'expires_in' => $expires_in,
|
||||
'openid' => $openid,
|
||||
'unionid' => $unionid,
|
||||
'userinfo' => $userinfo
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取access_token
|
||||
* @param string code
|
||||
* @return array
|
||||
*/
|
||||
private function getAccessToken($code = '')
|
||||
{
|
||||
if (!$code)
|
||||
return '';
|
||||
$queryarr = array(
|
||||
"appid" => $this->config['app_id'],
|
||||
"secret" => $this->config['app_secret'],
|
||||
"code" => $code,
|
||||
"grant_type" => "authorization_code",
|
||||
);
|
||||
$response = Http::post(self::GET_ACCESS_TOKEN_URL, $queryarr);
|
||||
$ret = json_decode($response, TRUE);
|
||||
return $ret ? $ret : [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace addons\third\library;
|
||||
|
||||
use fast\Http;
|
||||
use think\Config;
|
||||
use think\Session;
|
||||
|
||||
/**
|
||||
* 微博
|
||||
*/
|
||||
class Weibo
|
||||
{
|
||||
|
||||
const GET_AUTH_CODE_URL = "https://api.weibo.com/oauth2/authorize";
|
||||
const GET_ACCESS_TOKEN_URL = "https://api.weibo.com/oauth2/access_token";
|
||||
const GET_USERINFO_URL = "https://api.weibo.com/2/users/show.json";
|
||||
|
||||
/**
|
||||
* 配置信息
|
||||
* @var array
|
||||
*/
|
||||
private $config = [];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if ($config = Config::get('third.weibo'))
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
$this->config = array_merge($this->config, is_array($options) ? $options : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登陆
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
header("Location:" . $this->getAuthorizeUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取authorize_url
|
||||
*/
|
||||
public function getAuthorizeUrl()
|
||||
{
|
||||
$state = md5(uniqid(rand(), TRUE));
|
||||
Session::set('state', $state);
|
||||
$queryarr = array(
|
||||
"response_type" => "code",
|
||||
"client_id" => $this->config['app_id'],
|
||||
"redirect_uri" => $this->config['callback'],
|
||||
"state" => $state,
|
||||
);
|
||||
request()->isMobile() && $queryarr['display'] = 'mobile';
|
||||
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($queryarr);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function getUserInfo($params = [])
|
||||
{
|
||||
$params = $params ? $params : $_GET;
|
||||
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == Session::get('state') && isset($params['code'])))
|
||||
{
|
||||
//获取access_token
|
||||
$data = isset($params['code']) ? $this->getAccessToken($params['code']) : $params;
|
||||
$access_token = isset($data['access_token']) ? $data['access_token'] : '';
|
||||
$refresh_token = isset($data['refresh_token']) ? $data['refresh_token'] : '';
|
||||
$expires_in = isset($data['expires_in']) ? $data['expires_in'] : 0;
|
||||
if ($access_token)
|
||||
{
|
||||
$uid = isset($data['uid']) ? $data['uid'] : '';
|
||||
//获取用户信息
|
||||
$queryarr = [
|
||||
"access_token" => $access_token,
|
||||
"uid" => $uid,
|
||||
];
|
||||
$ret = Http::get(self::GET_USERINFO_URL, $queryarr);
|
||||
$userinfo = json_decode($ret, TRUE);
|
||||
if (!$userinfo || isset($userinfo['error_code']))
|
||||
return [];
|
||||
$userinfo = $userinfo ? $userinfo : [];
|
||||
$userinfo['nickname'] = isset($userinfo['screen_name']) ? $userinfo['screen_name'] : '';
|
||||
$userinfo['avatar'] = isset($userinfo['profile_image_url']) ? $userinfo['profile_image_url'] : '';
|
||||
$data = [
|
||||
'access_token' => $access_token,
|
||||
'refresh_token' => $refresh_token,
|
||||
'expires_in' => $expires_in,
|
||||
'openid' => $uid,
|
||||
'userinfo' => $userinfo
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取access_token
|
||||
* @param string code
|
||||
* @return array
|
||||
*/
|
||||
private function getAccessToken($code = '')
|
||||
{
|
||||
if (!$code)
|
||||
return '';
|
||||
$queryarr = array(
|
||||
"grant_type" => "authorization_code",
|
||||
"client_id" => $this->config['app_id'],
|
||||
"client_secret" => $this->config['app_secret'],
|
||||
"redirect_uri" => $this->config['callback'],
|
||||
"code" => $code,
|
||||
);
|
||||
$response = Http::post(self::GET_ACCESS_TOKEN_URL, $queryarr);
|
||||
$ret = json_decode($response, TRUE);
|
||||
return $ret ? $ret : [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace addons\third\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 会员模型
|
||||
*/
|
||||
class Third Extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
];
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace app\admin\controller\general;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use Cron\CronExpression;
|
||||
|
||||
/**
|
||||
* 定时任务
|
||||
*
|
||||
* @icon fa fa-tasks
|
||||
* @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前仅支持三种任务:请求URL、执行SQL、执行Shell
|
||||
*/
|
||||
class Crontab extends Backend
|
||||
{
|
||||
|
||||
protected $model = null;
|
||||
protected $noNeedRight = ['check_schedule', 'get_schedule_future'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = model('Crontab');
|
||||
$this->view->assign('typedata', \app\common\model\Crontab::getTypeList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax())
|
||||
{
|
||||
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
|
||||
$total = $this->model
|
||||
->where($where)
|
||||
->order($sort, $order)
|
||||
->count();
|
||||
|
||||
$list = $this->model
|
||||
->where($where)
|
||||
->order($sort, $order)
|
||||
->limit($offset, $limit)
|
||||
->select();
|
||||
foreach ($list as $k => &$v)
|
||||
{
|
||||
$cron = CronExpression::factory($v['schedule']);
|
||||
$v['nexttime'] = $cron->getNextRunDate()->getTimestamp();
|
||||
}
|
||||
$result = array("total" => $total, "rows" => $list);
|
||||
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Crontab格式是否正确
|
||||
* @internal
|
||||
*/
|
||||
public function check_schedule()
|
||||
{
|
||||
$row = $this->request->post("row/a");
|
||||
$schedule = isset($row['schedule']) ? $row['schedule'] : '';
|
||||
if (CronExpression::isValidExpression($schedule))
|
||||
{
|
||||
return json(['ok' => '']);
|
||||
}
|
||||
else
|
||||
{
|
||||
return json(['error' => __('Crontab format invalid')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Crontab表达式读取未来七次的时间
|
||||
* @internal
|
||||
*/
|
||||
public function get_schedule_future()
|
||||
{
|
||||
$time = [];
|
||||
$schedule = $this->request->post('schedule');
|
||||
$days = (int) $this->request->post('days');
|
||||
try
|
||||
{
|
||||
$cron = CronExpression::factory($schedule);
|
||||
for ($i = 0; $i < $days; $i++)
|
||||
{
|
||||
$time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return json(['futuretime' => $time]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'Title' => '任务标题',
|
||||
'Maximums' => '最多执行',
|
||||
'Sleep' => '延迟秒数',
|
||||
'Schedule' => '执行周期',
|
||||
'Executes' => '执行次数',
|
||||
'No limit' => '无限制',
|
||||
'Execute time' => '最后执行时间',
|
||||
'Request Url' => '请求URL',
|
||||
'Execute Sql Script' => '执行SQL',
|
||||
'Execute Shell' => '执行Shell',
|
||||
'Crontab format invalid' => 'Crontab格式错误',
|
||||
'Next execute time' => '下次预计时间',
|
||||
'The next %s times the execution time' => '接下来 %s 次的执行时间',
|
||||
];
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<style type="text/css">
|
||||
#schedulepicker {
|
||||
padding-top:7px;
|
||||
}
|
||||
</style>
|
||||
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" class="form-control" id="title" name="row[title]" value="" data-rule="required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
{:build_select('row[type]', $typedata, null, ['class'=>'form-control', 'data-rule'=>'required'])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="* * * * *" data-rule="required; remote(general/crontab/check_schedule)" />
|
||||
<div id="schedulepicker">
|
||||
<pre><code>* * * * *
|
||||
- - - - -
|
||||
| | | | +--- day of week (0 - 7) (Sunday=0 or 7)
|
||||
| | | +-------- month (1 - 12)
|
||||
| | +------------- day of month (1 - 31)
|
||||
| +------------------ hour (0 - 23)
|
||||
+----------------------- min (0 - 59)</code></pre>
|
||||
<h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
|
||||
<ol id="scheduleresult" class="list-group">
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="number" class="form-control" id="maximums" name="row[maximums]" value="0" data-rule="required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="" data-rule="{:__('Begin time')}:required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group hide layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
|
||||
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<style type="text/css">
|
||||
#schedulepicker {
|
||||
padding-top:7px;
|
||||
}
|
||||
</style>
|
||||
<form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" class="form-control" id="title" name="row[title]" value="{$row.title}" data-rule="required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
{:build_select('row[type]', $typedata, $row['type'], ['class'=>'form-control', 'data-rule'=>'required'])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required">{$row.content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="{$row.schedule}" data-rule="required; remote(general/crontab/check_schedule)" />
|
||||
<div id="schedulepicker">
|
||||
<pre><code>* * * * *
|
||||
- - - - -
|
||||
| | | | +--- day of week (0 - 7) (Sunday=0 or 7)
|
||||
| | | +-------- month (1 - 12)
|
||||
| | +------------- day of month (1 - 31)
|
||||
| +------------------ hour (0 - 23)
|
||||
+----------------------- min (0 - 59)</code></pre>
|
||||
<h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
|
||||
<ol id="scheduleresult" class="list-group">
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="number" class="form-control" id="maximums" name="row[maximums]" value="{$row.maximums}" data-rule="required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="{$row.begintime|datetime}" data-rule="{:__('Begin time')}:required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="{$row.endtime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" size="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group hide layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
|
||||
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<div class="panel panel-default panel-intro">
|
||||
{:build_heading()}
|
||||
|
||||
<div class="panel-body">
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div class="tab-pane fade active in" id="one">
|
||||
<div class="widget-body no-padding">
|
||||
<div id="toolbar" class="toolbar">
|
||||
{:build_toolbar()}
|
||||
<div class="dropdown btn-group {:$auth->check('general/crontab/multi')?'':'hide'}">
|
||||
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
|
||||
<ul class="dropdown-menu text-left" role="menu">
|
||||
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
|
||||
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover"
|
||||
data-operate-edit="{:$auth->check('general/crontab/edit')}"
|
||||
data-operate-del="{:$auth->check('general/crontab/del')}"
|
||||
width="100%">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Crontab extends Model
|
||||
{
|
||||
|
||||
// 开启自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
];
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
'type_text'
|
||||
];
|
||||
|
||||
public static function getTypeList()
|
||||
{
|
||||
return [
|
||||
'url' => __('Request Url'),
|
||||
'sql' => __('Execute Sql Script'),
|
||||
'shell' => __('Execute Shell'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTypeTextAttr($value, $data)
|
||||
{
|
||||
$typelist = self::getTypeList();
|
||||
$value = $value ? $value : $data['type'];
|
||||
return $value && isset($typelist[$value]) ? $typelist[$value] : $value;
|
||||
}
|
||||
|
||||
protected function setBegintimeAttr($value)
|
||||
{
|
||||
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||
}
|
||||
|
||||
protected function setEndtimeAttr($value)
|
||||
{
|
||||
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||
}
|
||||
|
||||
protected function setExecutetimeAttr($value)
|
||||
{
|
||||
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,56 +1,56 @@
|
|||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\Env;
|
||||
|
||||
return [
|
||||
// 数据库类型
|
||||
'type' => Env::get('database.type', 'mysql'),
|
||||
// 服务器地址
|
||||
'hostname' => Env::get('database.hostname', 'localhost'),
|
||||
// 数据库名
|
||||
'database' => Env::get('database.database', 'qulvxing'),
|
||||
// 用户名
|
||||
'username' => Env::get('database.username', 'root'),
|
||||
// 密码
|
||||
'password' => Env::get('database.password', 'root'),
|
||||
// 端口
|
||||
'hostport' => Env::get('database.hostport', ''),
|
||||
// 连接dsn
|
||||
'dsn' => '',
|
||||
// 数据库连接参数
|
||||
'params' => [],
|
||||
// 数据库编码默认采用utf8
|
||||
'charset' => Env::get('database.charset', 'utf8'),
|
||||
// 数据库表前缀
|
||||
'prefix' => Env::get('database.prefix', 'fa_'),
|
||||
// 数据库调试模式
|
||||
'debug' => Env::get('database.debug', true),
|
||||
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
|
||||
'deploy' => 0,
|
||||
// 数据库读写是否分离 主从式有效
|
||||
'rw_separate' => false,
|
||||
// 读写分离后 主服务器数量
|
||||
'master_num' => 1,
|
||||
// 指定从服务器序号
|
||||
'slave_no' => '',
|
||||
// 是否严格检查字段是否存在
|
||||
'fields_strict' => true,
|
||||
// 数据集返回类型
|
||||
'resultset_type' => 'array',
|
||||
// 自动写入时间戳字段
|
||||
'auto_timestamp' => false,
|
||||
// 时间字段取出后的默认时间格式,默认为Y-m-d H:i:s
|
||||
'datetime_format' => false,
|
||||
// 是否需要进行SQL性能分析
|
||||
'sql_explain' => false,
|
||||
];
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\Env;
|
||||
|
||||
return [
|
||||
// 数据库类型
|
||||
'type' => Env::get('database.type', 'mysql'),
|
||||
// 服务器地址
|
||||
'hostname' => Env::get('database.hostname', 'localhost'),
|
||||
// 数据库名
|
||||
'database' => Env::get('database.database', 'qulvxing'),
|
||||
// 用户名
|
||||
'username' => Env::get('database.username', 'jtl'),
|
||||
// 密码
|
||||
'password' => Env::get('database.password', '!jtl123361@GLOD.com'),
|
||||
// 端口
|
||||
'hostport' => Env::get('database.hostport', ''),
|
||||
// 连接dsn
|
||||
'dsn' => '',
|
||||
// 数据库连接参数
|
||||
'params' => [],
|
||||
// 数据库编码默认采用utf8
|
||||
'charset' => Env::get('database.charset', 'utf8'),
|
||||
// 数据库表前缀
|
||||
'prefix' => Env::get('database.prefix', 'fa_'),
|
||||
// 数据库调试模式
|
||||
'debug' => Env::get('database.debug', true),
|
||||
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
|
||||
'deploy' => 0,
|
||||
// 数据库读写是否分离 主从式有效
|
||||
'rw_separate' => false,
|
||||
// 读写分离后 主服务器数量
|
||||
'master_num' => 1,
|
||||
// 指定从服务器序号
|
||||
'slave_no' => '',
|
||||
// 是否严格检查字段是否存在
|
||||
'fields_strict' => true,
|
||||
// 数据集返回类型
|
||||
'resultset_type' => 'array',
|
||||
// 自动写入时间戳字段
|
||||
'auto_timestamp' => false,
|
||||
// 时间字段取出后的默认时间格式,默认为Y-m-d H:i:s
|
||||
'datetime_format' => false,
|
||||
// 是否需要进行SQL性能分析
|
||||
'sql_explain' => false,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,6 +4,18 @@ return array (
|
|||
'autoload' => false,
|
||||
'hooks' =>
|
||||
array (
|
||||
'sms_send' =>
|
||||
array (
|
||||
0 => 'alisms',
|
||||
),
|
||||
'sms_notice' =>
|
||||
array (
|
||||
0 => 'alisms',
|
||||
),
|
||||
'sms_check' =>
|
||||
array (
|
||||
0 => 'alisms',
|
||||
),
|
||||
),
|
||||
'route' =>
|
||||
array (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
return array (
|
||||
'name' => 'FastAdmin',
|
||||
'name' => '趣侣行',
|
||||
'beian' => '',
|
||||
'cdnurl' => '',
|
||||
'version' => '1.0.1',
|
||||
|
|
@ -18,6 +18,7 @@ return array (
|
|||
'default' => 'Default',
|
||||
'page' => 'Page',
|
||||
'article' => 'Article',
|
||||
'product_airport' => 'Product_airport',
|
||||
'test' => 'Test',
|
||||
),
|
||||
'configgroup' =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
define([],function(){
|
||||
var Properties={
|
||||
formatter:{
|
||||
property: function (value, row, index) {
|
||||
return '<div class="input-group" style="width:100%"><input type="text" class="form-control input-sm" value="'+value+'" ></div>';
|
||||
},
|
||||
operate:function(value,row,index){
|
||||
return '\<div class="btn-group btn-group-justified">\
|
||||
<span class="btn btn-default property_drag" data-index="'+index+'" data-direction="up"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>\
|
||||
<span class="btn btn-default property_drag" data-index="'+index+'" data-direction="down"><i class="fa fa-arrow-down" aria-hidden="true"></i></span>\
|
||||
<span class="btn btn-danger property_del" data-index="'+index+'"><i class="fa fa-times" aria-hidden="true"></i></span></div>';
|
||||
}
|
||||
},
|
||||
//初始化表格
|
||||
init: function(obj) {
|
||||
obj.hide();
|
||||
obj.before("<style>\
|
||||
.swap_done{animation-name: swap;animation-duration: 1s;animation-timing-function: ease-out;animation-direction: alternate;animation-iteration-count: 1;animation-fill-mode: backwards;animation-play-state: running;}\
|
||||
@keyframes swap{0%,100%{background-color:#f1f4f6}50%{background-color:#ddd}}\
|
||||
</style>")
|
||||
.before('<table class="properties"></table>')
|
||||
.before('<button type="button" class="btn btn-success btn-embossed property_add" style="margin-top:15px;">添加参数</button>');
|
||||
var table=obj.parent().find("table.properties")
|
||||
|
||||
if(obj.val()!=""){
|
||||
Properties.create(table);
|
||||
table.bootstrapTable('load',JSON.parse(obj.val()));
|
||||
}
|
||||
|
||||
return table;
|
||||
},
|
||||
//创建表头,若存在则添加一行
|
||||
create:function(table){
|
||||
if(table.find("tbody").size()==0){
|
||||
table.bootstrapTable({
|
||||
showHeader:true,
|
||||
mobileResponsive:true,
|
||||
columns: [{
|
||||
field: 'name',
|
||||
title: '参数名',
|
||||
class: 'property_input',
|
||||
formatter:Properties.formatter.property
|
||||
}, {
|
||||
field: 'value',
|
||||
title: '参数值',
|
||||
class: 'property_input',
|
||||
formatter:Properties.formatter.property
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
title: '操作',
|
||||
width: '150px',
|
||||
formatter:Properties.formatter.operate
|
||||
}],
|
||||
data:[{name:'',value:''}]
|
||||
|
||||
});
|
||||
}else{
|
||||
Properties.append(table);
|
||||
}
|
||||
},
|
||||
//添加
|
||||
append:function(table){
|
||||
var property={
|
||||
name:'',
|
||||
value:''
|
||||
}
|
||||
table.bootstrapTable('append',property)
|
||||
},
|
||||
//删除
|
||||
remove:function(table,index){
|
||||
var data=table.bootstrapTable('getData',true);
|
||||
data.splice(index,1),
|
||||
table.bootstrapTable('load',data)
|
||||
Properties.save(table);
|
||||
},
|
||||
//排序
|
||||
sort:function(table,index,direction){
|
||||
var data=table.bootstrapTable('getData',true);
|
||||
|
||||
switch(direction){
|
||||
case 'up':
|
||||
if(index>0){
|
||||
table.bootstrapTable('load',Properties.swap(data,index,index-1));
|
||||
table.find("tr[data-index="+(index-1)+"]").addClass('swap_done')
|
||||
}else{
|
||||
layer.tips('已经到达顶部',
|
||||
table.find(".property_drag[data-index="+index+"][data-direction="+direction+"]"),
|
||||
{
|
||||
tips:1
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'down':
|
||||
if(index<data.length-1){
|
||||
table.bootstrapTable('load',Properties.swap(data,index,index+1));
|
||||
table.find("tr[data-index="+(index+1)+"]").addClass('swap_done')
|
||||
}else{
|
||||
layer.tips('已经到达底部',
|
||||
table.find(".property_drag[data-index="+index+"][data-direction="+direction+"]"),
|
||||
{
|
||||
tips:1
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Properties.save(table);
|
||||
},
|
||||
|
||||
//设置属性
|
||||
set:function(table,index){
|
||||
|
||||
var property_input=table.find("tr[data-index="+index+"] td.property_input");
|
||||
var property={
|
||||
name:property_input.first().find("input").val(),
|
||||
value:property_input.last().find("input").val()
|
||||
}
|
||||
table.bootstrapTable('updateRow', {
|
||||
index: index,
|
||||
row: property
|
||||
})
|
||||
|
||||
Properties.save(table);
|
||||
},
|
||||
|
||||
//保存
|
||||
save:function(table){
|
||||
table.parents(".bootstrap-table")
|
||||
.siblings('textarea').val(JSON.stringify(table.bootstrapTable('getData',true)))
|
||||
},
|
||||
|
||||
//交换排序
|
||||
swap:function(data, old_pos, new_pos) {
|
||||
data[old_pos] = data.splice(new_pos, 1, data[old_pos])[0];
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
return Properties;
|
||||
});
|
||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,3 +1,100 @@
|
|||
define([], function () {
|
||||
|
||||
require(['form', 'upload'], function (Form, Upload) {
|
||||
var _bindevent = Form.events.bindevent;
|
||||
Form.events.bindevent = function (form) {
|
||||
_bindevent.apply(this, [form]);
|
||||
try {
|
||||
//绑定自定义属性组件
|
||||
if ($("textarea[id$=Properties]", form).size() > 0) {
|
||||
require(['../addons/properties/Properties'], function (Properties){
|
||||
var obj=$("textarea[id$=Properties]", form);
|
||||
|
||||
var table=Properties.init(obj);
|
||||
|
||||
$(form).on("click",".property_add",function(event){
|
||||
Properties.create(table);
|
||||
})
|
||||
|
||||
$(form).on("click",".property_del",function(event){
|
||||
Properties.remove(table,$(this).parents("tr").data("index"));
|
||||
})
|
||||
|
||||
$(form).on("click",".property_drag",function(event){
|
||||
Properties.sort(table,$(this).data("index"),$(this).data("direction"));
|
||||
})
|
||||
|
||||
$(form).on("change","input",function(event){
|
||||
Properties.set(table,$(this).parents("tr").data("index"));
|
||||
})
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
require(['form', 'upload'], function (Form, Upload) {
|
||||
var _bindevent = Form.events.bindevent;
|
||||
Form.events.bindevent = function (form) {
|
||||
_bindevent.apply(this, [form]);
|
||||
try {
|
||||
//绑定summernote事件
|
||||
if ($(".summernote,.editor", form).size() > 0) {
|
||||
require(['summernote'], function () {
|
||||
$(".summernote,.editor", form).summernote({
|
||||
height: 250,
|
||||
lang: 'zh-CN',
|
||||
fontNames: [
|
||||
'Arial', 'Arial Black', 'Serif', 'Sans', 'Courier',
|
||||
'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande',
|
||||
"Open Sans", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆',
|
||||
],
|
||||
fontNamesIgnoreCheck: [
|
||||
"Open Sans", "Microsoft YaHei",
|
||||
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆'
|
||||
],
|
||||
toolbar: [
|
||||
['style', ['style', 'undo', 'redo']],
|
||||
['font', ['bold', 'underline', 'strikethrough', 'clear']],
|
||||
['fontname', ['color', 'fontname', 'fontsize']],
|
||||
['para', ['ul', 'ol', 'paragraph', 'height']],
|
||||
['table', ['table', 'hr']],
|
||||
['insert', ['link', 'picture', 'video']],
|
||||
['view', ['fullscreen', 'codeview', 'help']]
|
||||
],
|
||||
dialogsInBody: true,
|
||||
callbacks: {
|
||||
onChange: function (contents) {
|
||||
$(this).val(contents);
|
||||
$(this).trigger('change');
|
||||
},
|
||||
onInit: function () {
|
||||
},
|
||||
onImageUpload: function (files) {
|
||||
var that = this;
|
||||
//依次上传图片
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
Upload.api.send(files[i], function (data) {
|
||||
var url = Fast.api.cdnurl(data.url);
|
||||
$(that).summernote("insertImage", url, 'filename');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
|
||||
|
||||
var Controller = {
|
||||
index: function () {
|
||||
// 初始化表格参数配置
|
||||
Table.api.init({
|
||||
extend: {
|
||||
index_url: 'general/crontab/index',
|
||||
add_url: 'general/crontab/add',
|
||||
edit_url: 'general/crontab/edit',
|
||||
del_url: 'general/crontab/del',
|
||||
multi_url: 'general/crontab/multi',
|
||||
table: 'crontab'
|
||||
}
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
// 初始化表格
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
sortName: 'weigh',
|
||||
columns: [
|
||||
[
|
||||
{field: 'state', checkbox: true, },
|
||||
{field: 'id', title: 'ID'},
|
||||
{field: 'type_text', title: __('Type'), operate: false},
|
||||
{field: 'title', title: __('Title')},
|
||||
{field: 'maximums', title: __('Maximums'), formatter: Controller.api.formatter.maximums},
|
||||
{field: 'executes', title: __('Executes')},
|
||||
{field: 'begintime', title: __('Begin time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
|
||||
{field: 'endtime', title: __('End time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
|
||||
{field: 'nexttime', title: __('Next execute time'), formatter: Table.api.formatter.datetime, operate: false},
|
||||
{field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
|
||||
{field: 'weigh', title: __('Weigh')},
|
||||
{field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
|
||||
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
// 为表格绑定事件
|
||||
Table.api.bindevent(table);
|
||||
},
|
||||
add: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
api: {
|
||||
bindevent: function () {
|
||||
$('#schedule').on('valid.field', function (e, result) {
|
||||
$("#pickdays").trigger("change");
|
||||
});
|
||||
Form.api.bindevent($("form[role=form]"));
|
||||
$(document).on("change", "#pickdays", function () {
|
||||
$("#scheduleresult").html(__('Loading'));
|
||||
$.post("general/crontab/get_schedule_future", {schedule: $("#schedule").val(), days: $(this).val()}, function (ret) {
|
||||
$("#scheduleresult").html("");
|
||||
if (typeof ret.futuretime !== 'undefined' && $.isArray(ret.futuretime)) {
|
||||
$.each(ret.futuretime, function (i, j) {
|
||||
$("#scheduleresult").append("<li class='list-group-item'>" + j + "<span class='badge'>" + (i + 1) + "</span></li>");
|
||||
});
|
||||
}
|
||||
}, 'json');
|
||||
|
||||
});
|
||||
$("#pickdays").trigger("change");
|
||||
},
|
||||
formatter: {
|
||||
maximums: function (value, row, index) {
|
||||
return value === 0 ? __('No limit') : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||