作者 Karson

新增前台会员Money字段和余额日志表

新增插件配置温馨提示的功能
新增后台登录入口为默认时的安全提示
新增余额增减的静态方法
优化前台默认布局的展示
优化附件管理中非图片资源的图标显示
优化移动端左侧菜单栏滑动体验
修复语言包在加载失败时页面不加载的BUG
修复通用搜索在重置后分页的BUG
修复管理员禁用后仍然能登录后台的BUG
修复一键打包插件在Win下打包后目录路径错误的BUG
修复API接口Token无法刷新的BUG
... ... @@ -33,7 +33,7 @@ class Addon extends Command
{
$name = $input->getOption('name') ?: '';
$action = $input->getOption('action') ?: '';
if(stripos($name, 'addons/')!==false){
if (stripos($name, 'addons/') !== false) {
$name = explode('/', $name)[1];
}
//强制覆盖
... ... @@ -241,7 +241,7 @@ class Addon extends Command
foreach ($files as $name => $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($addonDir));
$relativePath = str_replace(DS, '/', substr($filePath, strlen($addonDir)));
if (!in_array($file->getFilename(), ['.git', '.DS_Store', 'Thumbs.db'])) {
$zip->addFile($filePath, $relativePath);
}
... ...
... ... @@ -9,15 +9,15 @@
<title>{$config.title}</title>
<!-- Bootstrap Core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- Plugin CSS -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<script src="https://cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<style type="text/css">
... ... @@ -354,10 +354,10 @@
</div> <!-- /container -->
<!-- jQuery -->
<script src="https://cdn.jsdelivr.net/npm/jquery@2.1.4/dist/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
function syntaxHighlight(json) {
... ...
... ... @@ -29,17 +29,13 @@ CREATE TABLE `fa_admin` (
`status` varchar(30) NOT NULL DEFAULT 'normal' COMMENT '状态',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='管理员表';
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='管理员表';
-- ----------------------------
-- Records of fa_admin
-- ----------------------------
BEGIN;
INSERT INTO `fa_admin` VALUES (1, 'admin', 'Admin', '075eaec83636846f51c152f29b98a2fd', 's4f3', '/assets/img/avatar.png', 'admin@fastadmin.net', 0, 1502029281, 1492186163, 1502029281, 'd3992c3b-5ecc-4ecb-9dc2-8997780fcadc', 'normal');
INSERT INTO `fa_admin` VALUES (2, 'admin2', 'admin2', '9a28ce07ce875fbd14172a9ca5357d3c', '2dHDmj', '/assets/img/avatar.png', 'admin2@fastadmin.net', 0, 1505450906, 1492186163, 1505450906, 'df45fdd5-26f4-45ca-83b3-47e4491a315a', 'normal');
INSERT INTO `fa_admin` VALUES (3, 'admin3', 'admin3', '1c11f945dfcd808a130a8c2a8753fe62', 'WOKJEn', '/assets/img/avatar.png', 'admin3@fastadmin.net', 0, 1501980868, 1492186201, 1501982377, '', 'normal');
INSERT INTO `fa_admin` VALUES (4, 'admin22', 'admin22', '1c1a0aa0c3c56a8c1a908aab94519648', 'Aybcn5', '/assets/img/avatar.png', 'admin22@fastadmin.net', 0, 0, 1492186240, 1492186240, '', 'normal');
INSERT INTO `fa_admin` VALUES (5, 'admin32', 'admin32', 'ade94d5d7a7033afa7d84ac3066d0a02', 'FvYK0u', '/assets/img/avatar.png', 'admin32@fastadmin.net', 0, 0, 1492186263, 1492186263, '', 'normal');
COMMIT;
-- ----------------------------
... ... @@ -134,10 +130,6 @@ CREATE TABLE `fa_auth_group_access` (
-- ----------------------------
BEGIN;
INSERT INTO `fa_auth_group_access` VALUES (1, 1);
INSERT INTO `fa_auth_group_access` VALUES (2, 2);
INSERT INTO `fa_auth_group_access` VALUES (3, 3);
INSERT INTO `fa_auth_group_access` VALUES (4, 5);
INSERT INTO `fa_auth_group_access` VALUES (5, 5);
COMMIT;
-- ----------------------------
... ... @@ -431,6 +423,7 @@ CREATE TABLE `fa_user` (
`gender` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '性别',
`birthday` date COMMENT '生日',
`bio` varchar(100) NOT NULL DEFAULT '' COMMENT '格言',
`money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '余额',
`score` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '积分',
`successions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '连续登录天数',
`maxsuccessions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '最大连续登录天数',
... ... @@ -480,6 +473,21 @@ INSERT INTO `fa_user_group` VALUES (1, '默认组', '1,2,3,4,5,6,7,8,9,10,11,12'
COMMIT;
-- ----------------------------
-- Table structure for fa_user_money_log
-- ----------------------------
DROP TABLE IF EXISTS `fa_user_money_log`;
CREATE TABLE `fa_user_money_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '会员ID',
`money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变更余额',
`before` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变更前余额',
`after` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变更后余额',
`memo` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
`createtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='会员余额变动表';
-- ----------------------------
-- Table structure for fa_user_rule
-- ----------------------------
DROP TABLE IF EXISTS `fa_user_rule`;
... ...
... ... @@ -81,7 +81,14 @@ class Addon extends Backend
}
$this->error(__('Parameter %s can not be empty', ''));
}
$this->view->assign("addon", ['info' => $info, 'config' => $config]);
$tips = [];
foreach ($config as $index => &$item) {
if ($item['name'] == '__tips__') {
$tips = $item;
unset($config[$index]);
}
}
$this->view->assign("addon", ['info' => $info, 'config' => $config, 'tips' => $tips]);
$configFile = ADDON_PATH . $name . DS . 'config.html';
$viewFile = is_file($configFile) ? $configFile : '';
return $this->view->fetch($viewFile);
... ...
... ... @@ -43,6 +43,8 @@ return [
'Last month' => '上月',
'This month' => '本月',
'Loading' => '加载中',
'Money' => '余额',
'Score' => '积分',
'More' => '更多',
'Yes' => '是',
'No' => '否',
... ...
... ... @@ -42,4 +42,5 @@ return [
'Cdn url' => '静态资源CDN',
'Timezone' => '时区',
'Language' => '语言',
'Security tips' => '<i class="fa fa-warning"></i> 安全提示:你正在使用默认的后台登录入口,为了你的网站安全,建议你修改后台登录入口,<a href="https://forum.fastadmin.net/thread/7640" target="_blank">点击查看修改方法</a>',
];
... ...
... ... @@ -26,6 +26,7 @@ return [
'Username or password is incorrect' => '用户名或密码不正确',
'Username is incorrect' => '用户名不正确',
'Password is incorrect' => '密码不正确',
'Admin is forbidden' => '管理员已经被禁止登录',
'Please try again after 1 day' => '请于1天后再尝试登录',
'Login successful' => '登录成功!',
'Logout successful' => '退出成功!',
... ...
... ... @@ -43,6 +43,10 @@ class Auth extends \fast\Auth
$this->setError('Username is incorrect');
return false;
}
if ($admin['status'] == 'hidden') {
$this->setError('Admin is forbidden');
return false;
}
if (Config::get('fastadmin.login_failure_retry') && $admin->loginfailure >= 10 && time() - $admin->updatetime < 86400) {
$this->setError('Please try again after 1 day');
return false;
... ... @@ -134,6 +138,7 @@ class Auth extends \fast\Auth
* 检测当前控制器和方法是否匹配传递的数组
*
* @param array $arr 需要验证权限的数组
* @return bool
*/
public function match($arr = [])
{
... ... @@ -432,7 +437,7 @@ class Auth extends \fast\Auth
/**
* 设置错误信息
*
* @param $error 错误信息
* @param string $error 错误信息
* @return Auth
*/
public function setError($error)
... ...
... ... @@ -227,8 +227,10 @@ trait Backend
if ($ids) {
if ($this->request->has('params')) {
parse_str($this->request->post("params"), $values);
$values = array_intersect_key($values, array_flip(is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields)));
if ($values || $this->auth->isSuperAdmin()) {
if (!$this->auth->isSuperAdmin()) {
$values = array_intersect_key($values, array_flip(is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields)));
}
if ($values) {
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$this->model->where($this->dataLimitField, 'in', $adminIds);
... ...
... ... @@ -2,6 +2,7 @@
namespace app\admin\model;
use app\common\model\MoneyLog;
use think\Model;
class User extends Model
... ... @@ -36,6 +37,15 @@ class User extends Model
}
}
});
self::beforeUpdate(function ($row) {
$changedata = $row->getChangedData();
if (isset($changedata['money'])) {
$origin = $row->getOriginData();
MoneyLog::create(['user_id' => $row['id'], 'money' => $changedata['money'] - $origin['money'], 'memo' => '管理员变更金额']);
}
});
}
public function getGenderList()
... ...
<form id="config-form" class="edit-form form-horizontal" role="form" data-toggle="validator" method="POST" action="">
{if $addon.tips}
<div class="alert {$addon.tips.extend|default='alert-info-light'}" style="margin-bottom:10px;">
<b>{$addon.tips.title}</b><br>
{$addon.tips.value}
</div>
{/if}
<table class="table table-striped">
<thead>
<tr>
<th width="15%">{:__('Title')}</th>
<th width="85%">{:__('Value')}</th>
</tr>
<tr>
<th width="15%">{:__('Title')}</th>
<th width="85%">{:__('Value')}</th>
</tr>
</thead>
<tbody>
{foreach $addon.config as $item}
<tr>
<td>{$item.title}</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{switch $item.type}
{case string}
<input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value}" class="form-control" data-rule="{$item.rule}" data-tip="{$item.tip}" />
{/case}
{case text}
<textarea {$item.extend} name="row[{$item.name}]" class="form-control" data-rule="{$item.rule}" rows="5" data-tip="{$item.tip}">{$item.value}</textarea>
{/case}
{case array}
<dl class="fieldlist" data-name="row[{$item.name}]">
<dd>
<ins>{:__('Array key')}</ins>
<ins>{:__('Array value')}</ins>
</dd>
<dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
<textarea name="row[{$item.name}]" cols="30" rows="5" class="hide">{$item.value|json_encode}</textarea>
</dl>
{/case}
{case datetime}
<input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value}" class="form-control datetimepicker" data-tip="{$item.tip}" data-rule="{$item.rule}" />
{/case}
{case number}
<input {$item.extend} type="number" name="row[{$item.name}]" value="{$item.value}" class="form-control" data-tip="{$item.tip}" data-rule="{$item.rule}" />
{/case}
{case checkbox}
{foreach name="item.content" item="vo"}
<label for="row[{$item.name}][]-{$key}"><input id="row[{$item.name}][]-{$key}" name="row[{$item.name}][]" type="checkbox" value="{$key}" data-tip="{$item.tip}" {in name="key" value="$item.value"}checked{/in} /> {$vo}</label>
{/foreach}
{/case}
{case radio}
{foreach $addon.config as $item}
<tr>
<td>{$item.title}</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{switch $item.type}
{case string}
<input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value}" class="form-control" data-rule="{$item.rule}" data-tip="{$item.tip}"/>
{/case}
{case text}
<textarea {$item.extend} name="row[{$item.name}]" class="form-control" data-rule="{$item.rule}" rows="5" data-tip="{$item.tip}">{$item.value}</textarea>
{/case}
{case array}
<dl class="fieldlist" data-name="row[{$item.name}]">
<dd>
<ins>{:__('Array key')}</ins>
<ins>{:__('Array value')}</ins>
</dd>
<dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
<textarea name="row[{$item.name}]" cols="30" rows="5" class="hide">{$item.value|json_encode}</textarea>
</dl>
{/case}
{case datetime}
<input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value}" class="form-control datetimepicker" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
{/case}
{case number}
<input {$item.extend} type="number" name="row[{$item.name}]" value="{$item.value}" class="form-control" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
{/case}
{case checkbox}
{foreach name="item.content" item="vo"}
<label for="row[{$item.name}][]-{$key}"><input id="row[{$item.name}][]-{$key}" name="row[{$item.name}][]" type="checkbox" value="{$key}" data-tip="{$item.tip}" {in name="key" value="$item.value" }checked{/in} /> {$vo}</label>
{/foreach}
{/case}
{case radio}
{foreach name="item.content" item="vo"}
<label for="row[{$item.name}]-{$key}"><input id="row[{$item.name}]-{$key}" name="row[{$item.name}]" type="radio" value="{$key}" data-tip="{$item.tip}" {in name="key" value="$item.value" }checked{/in} /> {$vo}</label>
{/foreach}
{/case}
{case value="select" break="0"}{/case}
{case value="selects"}
<select {$item.extend} name="row[{$item.name}]{$item.type=='selects'?'[]':''}" class="form-control selectpicker" data-tip="{$item.tip}" {$item.type=='selects'?'multiple':''}>
{foreach name="item.content" item="vo"}
<label for="row[{$item.name}]-{$key}"><input id="row[{$item.name}]-{$key}" name="row[{$item.name}]" type="radio" value="{$key}" data-tip="{$item.tip}" {in name="key" value="$item.value"}checked{/in} /> {$vo}</label>
<option value="{$key}" {in name="key" value="$item.value" }selected{
/in}>{$vo}</option>
{/foreach}
{/case}
{case value="select" break="0"}{/case}
{case value="selects"}
<select {$item.extend} name="row[{$item.name}]{$item.type=='selects'?'[]':''}" class="form-control selectpicker" data-tip="{$item.tip}" {$item.type=='selects'?'multiple':''}>
{foreach name="item.content" item="vo"}
<option value="{$key}" {in name="key" value="$item.value"}selected{/in}>{$vo}</option>
{/foreach}
</select>
{/case}
{case value="image" break="0"}{/case}
{case value="images"}
<div class="form-inline">
<input id="c-{$item.name}" class="form-control" size="37" name="row[{$item.name}]" type="text" value="{$item.value}" data-tip="{$item.tip}">
<span><button type="button" id="plupload-{$item.name}" class="btn btn-danger plupload" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}" data-preview-id="p-{$item.name}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
<ul class="row list-inline plupload-preview" id="p-{$item.name}"></ul>
</div>
{/case}
{case value="file" break="0"}{/case}
{case value="files"}
<div class="form-inline">
<input id="c-{$item.name}" class="form-control" size="37" name="row[{$item.name}]" type="text" value="{$item.value}" data-tip="{$item.tip}">
<span><button type="button" id="plupload-{$item.name}" class="btn btn-danger plupload" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
</div>
{/case}
{case bool}
<label for="row[{$item.name}]-yes"><input id="row[{$item.name}]-yes" name="row[{$item.name}]" type="radio" value="1" {$item.value?'checked':''} data-tip="{$item.tip}" /> {:__('Yes')}</label>
<label for="row[{$item.name}]-no"><input id="row[{$item.name}]-no" name="row[{$item.name}]" type="radio" value="0" {$item.value?'':'checked'} data-tip="{$item.tip}" /> {:__('No')}</label>
{/case}
{/switch}
</select>
{/case}
{case value="image" break="0"}{/case}
{case value="images"}
<div class="form-inline">
<input id="c-{$item.name}" class="form-control" size="37" name="row[{$item.name}]" type="text" value="{$item.value}" data-tip="{$item.tip}">
<span><button type="button" id="plupload-{$item.name}" class="btn btn-danger plupload" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}" data-preview-id="p-{$item.name}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
<ul class="row list-inline plupload-preview" id="p-{$item.name}"></ul>
</div>
{/case}
{case value="file" break="0"}{/case}
{case value="files"}
<div class="form-inline">
<input id="c-{$item.name}" class="form-control" size="37" name="row[{$item.name}]" type="text" value="{$item.value}" data-tip="{$item.tip}">
<span><button type="button" id="plupload-{$item.name}" class="btn btn-danger plupload" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
</div>
<div class="col-sm-4"></div>
{/case}
{case bool}
<label for="row[{$item.name}]-yes"><input id="row[{$item.name}]-yes" name="row[{$item.name}]" type="radio" value="1" {$item.value?'checked':''} data-tip="{$item.tip}" /> {:__('Yes')}</label>
<label for="row[{$item.name}]-no"><input id="row[{$item.name}]-no" name="row[{$item.name}]" type="radio" value="0" {$item.value?'':'checked'} data-tip="{$item.tip}" /> {:__('No')}</label>
{/case}
{default /}{$item.value}
{/switch}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
{/foreach}
</td>
</tr>
{/foreach}
</tbody>
</table>
<div class="form-group layer-footer">
... ...
... ... @@ -122,6 +122,11 @@
padding:30px 0;
}
</style>
{if preg_match('/\/admin\/|admin\.php|admin_d75KABNWt\.php/i', url())}
<div class="alert alert-danger-light">
{:__('Security tips')}
</div>
{/if}
<div class="panel panel-default panel-intro">
<div class="panel-heading">
{:build_heading(null, false)}
... ...
... ... @@ -75,6 +75,12 @@
</div>
</div>
<div class="form-group">
<label for="c-money" class="control-label col-xs-12 col-sm-2">{:__('Money')}:</label>
<div class="col-xs-12 col-sm-4">
<input id="c-money" data-rule="required" class="form-control" name="row[money]" type="number" value="{$row.money}">
</div>
</div>
<div class="form-group">
<label for="c-score" class="control-label col-xs-12 col-sm-2">{:__('Score')}:</label>
<div class="col-xs-12 col-sm-4">
<input id="c-score" data-rule="required" class="form-control" name="row[score]" type="number" value="{$row.score}">
... ...
... ... @@ -3,6 +3,7 @@
namespace app\api\controller;
use app\common\controller\Api;
use fast\Random;
/**
* Token接口
... ... @@ -35,10 +36,13 @@ class Token extends Api
*/
public function refresh()
{
//删除源Token
$token = $this->auth->getToken();
\app\common\library\Token::delete($token);
//创建新Token
$token = Random::uuid();
\app\common\library\Token::set($token, $this->auth->id, 2592000);
$tokenInfo = \app\common\library\Token::get($token);
$tokenInfo->expiretime = time() + 2592000;
$tokenInfo->save();
$this->success('', ['token' => $tokenInfo['token'], 'expires_in' => $tokenInfo['expires_in']]);
}
... ...
... ... @@ -32,7 +32,7 @@ class Auth
{
if ($config = Config::get('user'))
{
$this->options = array_merge($this->config, $config);
$this->config = array_merge($this->config, $config);
}
$this->options = array_merge($this->config, $options);
}
... ...
... ... @@ -14,9 +14,9 @@ class Area extends Model
/**
* 根据经纬度获取当前地区信息
*
* @param string $lng 经度
* @param string $lat 纬度
* @return array 城市信息
* @param string $lng 经度
* @param string $lat 纬度
* @return Area 城市信息
*/
public static function getAreaFromLngLat($lng, $lat, $level = 3)
{
... ... @@ -24,17 +24,14 @@ class Area extends Model
$rangearr = [1 => 15000, 2 => 1000, 3 => 200];
$geoname = isset($namearr[$level]) ? $namearr[$level] : $namearr[3];
$georange = isset($rangearr[$level]) ? $rangearr[$level] : $rangearr[3];
$neararea = [];
// 读取范围内的ID
$redis = Cache::store('redis')->handler();
$georadiuslist = [];
if (method_exists($redis, 'georadius'))
{
if (method_exists($redis, 'georadius')) {
$georadiuslist = $redis->georadius($geoname, $lng, $lat, $georange, 'km', ['WITHDIST', 'COUNT' => 5, 'ASC']);
}
if ($georadiuslist)
{
if ($georadiuslist) {
list($id, $distance) = $georadiuslist[0];
}
$id = isset($id) && $id ? $id : 3;
... ... @@ -44,16 +41,15 @@ class Area extends Model
/**
* 根据经纬度获取省份
*
* @param string $lng 经度
* @param string $lat 纬度
* @return array
* @param string $lng 经度
* @param string $lat 纬度
* @return Area
*/
public static function getProvinceFromLngLat($lng, $lat)
{
$provincedata = [];
$provincedata = null;
$citydata = self::getCityFromLngLat($lng, $lat);
if ($citydata)
{
if ($citydata) {
$provincedata = self::get($citydata['pid']);
}
return $provincedata;
... ... @@ -62,16 +58,15 @@ class Area extends Model
/**
* 根据经纬度获取城市
*
* @param string $lng 经度
* @param string $lat 纬度
* @return array
* @param string $lng 经度
* @param string $lat 纬度
* @return Area
*/
public static function getCityFromLngLat($lng, $lat)
{
$citydata = [];
$citydata = null;
$districtdata = self::getDistrictFromLngLat($lng, $lat);
if ($districtdata)
{
if ($districtdata) {
$citydata = self::get($districtdata['pid']);
}
return $citydata;
... ... @@ -80,9 +75,9 @@ class Area extends Model
/**
* 根据经纬度获取地区
*
* @param string $lng 经度
* @param string $lat 纬度
* @return array
* @param string $lng 经度
* @param string $lat 纬度
* @return Area
*/
public static function getDistrictFromLngLat($lng, $lat)
{
... ...
<?php
namespace app\common\model;
use think\Model;
/**
* 会员余额日志模型
*/
class MoneyLog Extends Model
{
// 表名
protected $name = 'user_money_log';
// 开启自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = '';
// 追加属性
protected $append = [
];
}
... ...
... ... @@ -75,6 +75,26 @@ class User Extends Model
}
/**
* 变更会员余额
* @param int $money 余额
* @param int $user_id 会员ID
* @param string $memo 备注
*/
public static function money($money, $user_id, $memo)
{
$user = self::get($user_id);
if ($user)
{
$before = $user->money;
$after = $user->money + $money;
//更新会员信息
$user->save(['money' => $after]);
//写入日志
MoneyLog::create(['user_id' => $user_id, 'money' => $money, 'before' => $before, 'after' => $after, 'memo' => $memo]);
}
}
/**
* 变更会员积分
* @param int $score 积分
* @param int $user_id 会员ID
... ...
... ... @@ -260,8 +260,8 @@ return [
//是否开启前台会员中心
'usercenter' => true,
//登录验证码
'login_captcha' => false,
//登录失败超过10则1天后重试
'login_captcha' => true,
//登录失败超过10次则1天后重试
'login_failure_retry' => true,
//是否同一账号同一时间只能在一个地方登录
'login_unique' => false,
... ... @@ -272,7 +272,7 @@ return [
//自动检测更新
'checkupdate' => false,
//版本号
'version' => '1.0.0.20181031_beta',
'version' => '1.0.0.20181127_beta',
//API接口地址
'api_url' => 'https://api.fastadmin.net',
],
... ...
... ... @@ -27,6 +27,8 @@ return [
'OK' => '确定',
'Cancel' => '取消',
'Loading' => '加载中',
'Money' => '余额',
'Score' => '积分',
'More' => '更多',
'Normal' => '正常',
'Hidden' => '隐藏',
... ...
... ... @@ -12,17 +12,17 @@
<title>FastAdmin - {:__('The fastest framework based on ThinkPHP5 and Bootstrap')}</title>
<!-- Bootstrap Core CSS -->
<link href="//cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="__CDN__/assets/css/index.css" rel="stylesheet">
<!-- Plugin CSS -->
<link href="//cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="//cdn.jsdelivr.net/npm/simple-line-icons@2.4.1/css/simple-line-icons.min.css" rel="stylesheet">
<link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://cdn.staticfile.org/simple-line-icons/2.4.1/css/simple-line-icons.min.css" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="//cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<script src="https://cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
... ... @@ -163,13 +163,13 @@
</footer>
<!-- jQuery -->
<script src="//cdn.jsdelivr.net/npm/jquery@2.1.4/dist/jquery.min.js"></script>
<script src=https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js></script>
<!-- Bootstrap Core JavaScript -->
<script src="//cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- Plugin JavaScript -->
<script src="//cdn.jsdelivr.net/npm/jquery.easing@1.4.1/jquery.easing.min.js"></script>
<script src="https://cdn.staticfile.org/jquery-easing/1.4.1/jquery.easing.min.js"></script>
<script>
$(function () {
... ...
... ... @@ -44,11 +44,13 @@
<!-- Success -->
<div class="basicinfo">
<div class="row">
<div class="col-xs-4 col-md-2">{:__('Lv')}</div>
<div class="col-xs-8 col-md-4"><a href="javascript:;" class="viewlv">{$user.level}</a>
<div class="col-xs-4 col-md-2">{:__('Money')}</div>
<div class="col-xs-8 col-md-4">
<a href="javascript:;" class="viewmoney">{$user.money}</a>
</div>
<div class="col-xs-4 col-md-2">{:__('Score')}</div>
<div class="col-xs-8 col-md-4"><a href="javascript:;" class="viewscore">{$user.score}</a>
<div class="col-xs-8 col-md-4">
<a href="javascript:;" class="viewscore">{$user.score}</a>
</div>
</div>
<div class="row">
... ...
... ... @@ -365,11 +365,12 @@ class Tree
/**
* 特殊
* @param integer $myid 要查询的ID
* @param string $itemtpl1 第一种HTML代码方式
* @param string $itemtpl2 第二种HTML代码方式
* @param mixed $selectedids 默认选中
* @param mixed $disabledids 禁用
* @param integer $itemprefix 前缀
* @param string $itemtpl1 第一种HTML代码方式
* @param string $itemtpl2 第二种HTML代码方式
* @param mixed $selectedids 默认选中
* @param mixed $disabledids 禁用
* @param string $itemprefix 前缀
* @return string
*/
public function getTreeSpecial($myid, $itemtpl1, $itemtpl2, $selectedids = 0, $disabledids = 0, $itemprefix = '')
{
... ... @@ -413,9 +414,8 @@ class Tree
*
* 获取树状数组
* @param string $myid 要查询的ID
* @param string $nametpl 名称条目模板
* @param string $itemprefix 前缀
* @return string
* @return array
*/
public function getTreeArray($myid, $itemprefix = '')
{
... ...
... ... @@ -9,15 +9,15 @@
<title>FastAdmin</title>
<!-- Bootstrap Core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- Plugin CSS -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<script src="https://cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<style type="text/css">
... ... @@ -3736,10 +3736,10 @@
</div> <!-- /container -->
<!-- jQuery -->
<script src="https://cdn.jsdelivr.net/npm/jquery@2.1.4/dist/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
function syntaxHighlight(json) {
... ...
... ... @@ -558,14 +558,14 @@ form.form-horizontal .control-label {
float: left;
background: none;
margin-left: 0;
width: 80px;
min-width: 80px;
clear: none;
}
#treeview .jstree-leaf {
float: left;
margin-left: 0;
padding-left: 24px;
width: 80px;
min-width: 80px;
clear: none;
color: #777;
}
... ... @@ -959,7 +959,7 @@ table.table-nowrap thead > tr > th {
}
.main-sidebar,
.left-side {
padding-top: 94px;
padding-top: 50px;
}
.n-bootstrap .n-right {
margin-top: 0;
... ...
... ... @@ -252,9 +252,6 @@ footer.footer {
color: #aaa;
background: #555;
margin-top: 25px;
position: fixed;
bottom: 0;
z-index: 99;
}
footer.footer .copyright {
line-height: 50px;
... ... @@ -370,12 +367,9 @@ footer.footer .copyright a:hover {
main.content {
width: 100%;
overflow: auto;
top: 0;
position: absolute;
z-index: 10;
bottom: 50px;
padding: 15px;
padding-top: 67px;
padding-top: 20px;
min-height: calc(100vh - 125px);
}
.sidenav {
padding: 20px 0 10px 0;
... ...
... ... @@ -152,6 +152,11 @@ $(function () {
AdminLTEOptions);
}
if ('ontouchstart' in document.documentElement) {
$.AdminLTE.options.sidebarSlimScroll = false;
$(".main-sidebar").css({height: ($(window).height() - $(".main-header").height()) + "px", overflow: "scroll"});
}
//Easy access to options
var o = $.AdminLTE.options;
... ...
... ... @@ -132,7 +132,7 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
var style = row.storage == 'upyun' ? '!/fwfh/120x90' : '';
return '<a href="' + row.fullurl + '" target="_blank"><img src="' + row.fullurl + style + '" alt="" style="max-height:90px;max-width:120px"></a>';
} else {
return '<a href="' + row.fullurl + '" target="_blank">' + __('None') + '</a>';
return '<a href="' + row.fullurl + '" target="_blank"><img src="https://tool.fastadmin.net/icon/' + row.imagetype + '.png" alt=""></a>';
}
},
url: function (value, row, index) {
... ...
... ... @@ -19,24 +19,17 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
//快捷搜索
$(".menuresult").width($("form.sidebar-form > .input-group").width());
var isAndroid = /(android)/i.test(navigator.userAgent);
var searchResult = $(".menuresult");
$("form.sidebar-form").on("blur", "input[name=q]", function () {
searchResult.addClass("hide");
if (isAndroid) {
$.AdminLTE.options.sidebarSlimScroll = true;
}
}).on("focus", "input[name=q]", function () {
if (isAndroid) {
$.AdminLTE.options.sidebarSlimScroll = false;
}
if ($("a", searchResult).size() > 0) {
searchResult.removeClass("hide");
}
}).on("keyup", "input[name=q]", function () {
searchResult.html('');
var val = $(this).val();
var html = new Array();
var html = [];
if (val != '') {
$("ul.sidebar-menu li a[addtabs]:not([href^='javascript:;'])").each(function () {
if ($("span:first", this).text().indexOf(val) > -1 || $(this).attr("py").indexOf(val) > -1 || $(this).attr("pinyin").indexOf(val) > -1) {
... ... @@ -122,7 +115,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
Layer.open({
title: __('Discover new version'),
maxHeight: 400,
content: '<h5 style="background-color:#f7f7f7; font-size:14px; padding: 10px;">' + __('Your current version') + ':' + ret.data.version + ',' + __('New version') + ':' + ret.data.newversion + '</h5><span class="label label-danger">'+__('Release notes')+'</span><br/>' + ret.data.upgradetext,
content: '<h5 style="background-color:#f7f7f7; font-size:14px; padding: 10px;">' + __('Your current version') + ':' + ret.data.version + ',' + __('New version') + ':' + ret.data.newversion + '</h5><span class="label label-danger">' + __('Release notes') + '</span><br/>' + ret.data.upgradetext,
btn: [__('Go to download'), __('Ignore this version'), __('Do not remind again')],
btn2: function (index, layero) {
localStorage.setItem("ignoreversion", ret.data.newversion);
... ... @@ -304,8 +297,8 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
//这一行需要放在点击左侧链接事件之前
var addtabs = Config.referer ? localStorage.getItem("addtabs") : null;
//绑定tabs事件,如果需要点击强制刷新iframe,则请将iframeForceRefresh置为true
nav.addtabs({iframeHeight: "100%", iframeForceRefresh: false, nav: nav});
//绑定tabs事件,如果需要点击强制刷新iframe,则请将iframeForceRefresh置为true,iframeForceRefreshTable只强制刷新表格
nav.addtabs({iframeHeight: "100%", iframeForceRefresh: false, iframeForceRefreshTable: true, nav: nav});
if ($("ul.sidebar-menu li.active a").size() > 0) {
$("ul.sidebar-menu li.active a").trigger("click");
... ...
... ... @@ -350,6 +350,7 @@
var searchQuery = getSearchQuery(this);
this.trigger('common-search', this, searchQuery);
this.options.pageNumber = 1;
this.options.pageSize = $.fn.bootstrapTable.defaults.pageSize;
this.refresh({});
};
... ...
... ... @@ -250,7 +250,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
i = 1;
string = string.toLowerCase();
//string = typeof Lang[string] != 'undefined' ? Lang[string] : string;
if (typeof Lang[string] != 'undefined') {
if (typeof Lang !== 'undefined' && typeof Lang[string] !== 'undefined') {
if (typeof Lang[string] == 'object')
return Lang[string];
string = Lang[string];
... ...
... ... @@ -904,7 +904,7 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
i = 1;
string = string.toLowerCase();
//string = typeof Lang[string] != 'undefined' ? Lang[string] : string;
if (typeof Lang[string] != 'undefined') {
if (typeof Lang !== 'undefined' && typeof Lang[string] !== 'undefined') {
if (typeof Lang[string] == 'object')
return Lang[string];
string = Lang[string];
... ... @@ -8433,7 +8433,7 @@ define('validator',['validator-core', 'validator-lang'], function (Validator, un
define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, Upload, Validator) {
var Form = {
config: {
fieldlisttpl: '<dd class="form-inline"><input type="text" name="<%=name%>[<%=index%>][key]" class="form-control" value="<%=row.key%>" size="10" /> <input type="text" name="<%=name%>[<%=index%>][value]" class="form-control" value="<%=row.value%>" size="40" /> <span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span> <span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span></dd>'
fieldlisttpl: '<dd class="form-inline"><input type="text" name="<%=name%>[<%=index%>][key]" class="form-control" value="<%=row.key%>" size="10" /> <input type="text" name="<%=name%>[<%=index%>][value]" class="form-control" value="<%=row.value%>" size="30" /> <span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span> <span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span></dd>'
},
events: {
validator: function (form, success, error, submit) {
... ... @@ -9258,6 +9258,7 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
var searchQuery = getSearchQuery(this);
this.trigger('common-search', this, searchQuery);
this.options.pageNumber = 1;
this.options.pageSize = $.fn.bootstrapTable.defaults.pageSize;
this.refresh({});
};
... ... @@ -10454,7 +10455,8 @@ define("drop", function(){});
tab: '.tab-addtabs',
iframeUse: true, //使用iframe还是ajax
iframeHeight: $(window).height() - 50, //固定TAB中IFRAME高度,根据需要自己修改
iframeForceRefresh: false, //点击后强制刷新对应的iframe
iframeForceRefresh: false, //点击后强制加载对应的iframe
iframeForceRefreshTable: false, //点击后强制刷新对应的iframe中的table
callback: function () {
//关闭后回调函数
}
... ... @@ -10579,6 +10581,15 @@ define("drop", function(){});
$("#" + conid + " iframe").attr('src', function (i, val) {
return val;
});
} else if (options.iframeForceRefreshTable) {
try {
//检测iframe中是否存在刷新按钮
if ($("#" + conid + " iframe").contents().find(".btn-refresh").size() > 0) {
$("#" + conid + " iframe")[0].contentWindow.$(".btn-refresh").trigger("click");
}
} catch (e) {
}
}
}
localStorage.setItem("addtabs", $(this).prop('outerHTML'));
... ...
define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, Upload, Validator) {
var Form = {
config: {
fieldlisttpl: '<dd class="form-inline"><input type="text" name="<%=name%>[<%=index%>][key]" class="form-control" value="<%=row.key%>" size="10" /> <input type="text" name="<%=name%>[<%=index%>][value]" class="form-control" value="<%=row.value%>" size="40" /> <span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span> <span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span></dd>'
fieldlisttpl: '<dd class="form-inline"><input type="text" name="<%=name%>[<%=index%>][key]" class="form-control" value="<%=row.key%>" size="10" /> <input type="text" name="<%=name%>[<%=index%>][value]" class="form-control" value="<%=row.value%>" size="30" /> <span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span> <span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span></dd>'
},
events: {
validator: function (form, success, error, submit) {
... ...
... ... @@ -904,7 +904,7 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
i = 1;
string = string.toLowerCase();
//string = typeof Lang[string] != 'undefined' ? Lang[string] : string;
if (typeof Lang[string] != 'undefined') {
if (typeof Lang !== 'undefined' && typeof Lang[string] !== 'undefined') {
if (typeof Lang[string] == 'object')
return Lang[string];
string = Lang[string];
... ...
... ... @@ -641,14 +641,14 @@ form.form-horizontal .control-label {
float: left;
background: none;
margin-left: 0;
width: 80px;
min-width: 80px;
clear: none;
}
.jstree-leaf {
float: left;
margin-left: 0;
padding-left: 24px;
width: 80px;
min-width: 80px;
clear: none;
color: #777;
}
... ... @@ -1092,7 +1092,7 @@ table.table-nowrap {
}
.main-sidebar, .left-side {
padding-top: 94px;
padding-top: 50px;
}
.n-bootstrap {
... ...
... ... @@ -261,7 +261,7 @@ body {
}
footer.footer{
width:100%;color: #aaa;background: #555;margin-top:25px;position: fixed;bottom: 0;z-index:99;
width:100%;color: #aaa;background: #555;margin-top:25px;
.copyright{
line-height: 50px;text-align: center;background: #393939;margin:0;
a{
... ... @@ -345,12 +345,9 @@ footer.footer{
main.content {
width:100%;
overflow:auto;
top:0;
position:absolute;
z-index:10;
bottom:50px;
padding:15px;
padding-top:67px;
padding-top:20px;
min-height:calc(~ '100vh - 125px');
}
.sidenav {
... ...
... ... @@ -409,7 +409,7 @@ if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') {
</form>
<!-- jQuery -->
<script src="https://cdn.jsdelivr.net/npm/jquery@2.1.4/dist/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<script>
$(function () {
... ...