作者 Karson

新增多图自定义描述数据功能

新增选择附件页上传功能
新增Api跨域判断
优化API文档绑定域名时URL判断
优化后台左侧无权限菜单的显示逻辑
优化验证码失败时自动刷新验证码
修复Windows下离线安装成功后不删除压缩包的BUG
修复通用搜索需要重置2次的BUG
修复表格导出配置不生效的BUG
修复data-table-id不统一的BUG
修复多图片预览时错误的BUG
... ... @@ -167,6 +167,11 @@ class Builder
$sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0;
}
arsort($sectorArr);
$routes = include_once CONF_PATH . 'route.php';
$subdomain = false;
if (config('url_domain_deploy') && isset($routes['__domain__']) && isset($routes['__domain__']['api']) && $routes['__domain__']['api']) {
$subdomain = true;
}
$counter = 0;
$section = null;
$weigh = 0;
... ... @@ -181,12 +186,16 @@ class Builder
if (0 === count($docs)) {
continue;
}
$route = is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0];
if ($subdomain) {
$route = substr($route, 4);
}
$docslist[$section][$name] = [
'id' => $counter,
'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
'method_label' => $this->generateBadgeForMethod($docs),
'section' => $section,
'route' => is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0],
'route' => $route,
'title' => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
'body' => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
... ... @@ -200,7 +209,7 @@ class Builder
$counter++;
}
}
//重建排序
foreach ($docslist as $index => &$methods) {
$methodSectorArr = [];
... ... @@ -223,7 +232,7 @@ class Builder
/**
* 渲染
* @param string $template
* @param array $vars
* @param array $vars
* @return string
*/
public function render($template, $vars = [])
... ...
... ... @@ -245,6 +245,7 @@ class Extractor
return \think\Loader::parseName($item);
}, $suffixArr));
$urlArr[] = $method->getName();
$methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
}
if (!isset($methodAnnotations['ApiSector'])) {
... ...
... ... @@ -428,8 +428,8 @@
$(document).ready(function () {
if (storage) {
$('#token').val(storage.getItem('token'));
$('#apiUrl').val(storage.getItem('apiUrl'));
storage.getItem('token') && $('#token').val(storage.getItem('token'));
storage.getItem('apiUrl') && $('#apiUrl').val(storage.getItem('apiUrl'));
}
$('[data-toggle="tooltip"]').tooltip({
... ...
... ... @@ -191,6 +191,7 @@ class Addon extends Backend
$tmpFile = $addonTmpDir . $info->getSaveName();
try {
Service::unzip($tmpName);
unset($info);
@unlink($tmpFile);
$infoFile = $tmpAddonDir . 'info.ini';
if (!is_file($infoFile)) {
... ... @@ -235,6 +236,7 @@ class Addon extends Backend
throw new Exception(__($e->getMessage()));
}
} catch (Exception $e) {
unset($info);
@unlink($tmpFile);
@rmdirs($tmpAddonDir);
$this->error(__($e->getMessage()));
... ...
... ... @@ -374,12 +374,28 @@ class Auth extends \fast\Auth
$refererUrl = Session::get('referer');
$pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
// 必须将结果集转换为数组
$ruleList = collection(\app\admin\model\AuthRule::where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray();
$ruleList = collection(\app\admin\model\AuthRule::where('status', 'normal')
->where('ismenu', 1)
->order('weigh', 'desc')
->cache("__menu__")
->select())->toArray();
$indexRuleList = \app\admin\model\AuthRule::where('status', 'normal')
->where('ismenu', 0)
->where('name', 'like', '%/index')
->column('name,pid');
$pidArr = array_filter(array_unique(array_map(function ($item) {
return $item['pid'];
}, $ruleList)));
foreach ($ruleList as $k => &$v) {
if (!in_array($v['name'], $userRule)) {
unset($ruleList[$k]);
continue;
}
$indexRuleName = $v['name'] . '/index';
if (isset($indexRuleList[$indexRuleName]) && !in_array($indexRuleName, $userRule)) {
unset($ruleList[$k]);
continue;
}
$v['icon'] = $v['icon'] . ' fa-fw';
$v['url'] = '/' . $module . '/' . $v['name'];
$v['badge'] = isset($badgeList[$v['name']]) ? $badgeList[$v['name']] : '';
... ... @@ -389,6 +405,14 @@ class Auth extends \fast\Auth
$selected = $v['name'] == $fixedPage ? $v : $selected;
$referer = url($v['url']) == $refererUrl ? $v : $referer;
}
$lastArr = array_diff($pidArr, array_filter(array_unique(array_map(function ($item) {
return $item['pid'];
}, $ruleList))));
foreach ($ruleList as $index => $item) {
if (in_array($item['id'], $lastArr)) {
unset($ruleList[$index]);
}
}
if ($selected == $referer) {
$referer = [];
}
... ...
... ... @@ -7,6 +7,7 @@
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
{:build_toolbar('refresh')}
<span><button type="button" id="plupload-image" class="btn btn-success plupload" data-mimetype="{$Think.get.mimetype|default=''}" data-multiple="true"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
{if request()->get('multiple') == 'true'}
<a class="btn btn-danger btn-choose-multi"><i class="fa fa-check"></i> {:__('Choose')}</a>
{/if}
... ...
... ... @@ -11,6 +11,7 @@ use think\Lang;
use think\Loader;
use think\Request;
use think\Response;
use think\Route;
/**
* API控制器基类
... ... @@ -90,6 +91,25 @@ class Api
*/
protected function _initialize()
{
if (Config::get('url_domain_deploy')) {
$domain = Route::rules('domain');
if (isset($domain['api'])) {
if (isset($_SERVER['HTTP_ORIGIN'])) {
header("Access-Control-Allow-Origin: " . $this->request->server('HTTP_ORIGIN'));
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
}
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
}
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
}
}
}
}
//移除HTML标签
$this->request->filter('trim,strip_tags,htmlspecialchars');
... ...
... ... @@ -254,7 +254,7 @@ class Backend extends Controller
$search = $this->request->get("search", '');
$filter = $this->request->get("filter", '');
$op = $this->request->get("op", '', 'trim');
$sort = $this->request->get("sort", "id");
$sort = $this->request->get("sort", $this->model->getPk() ?: 'id');
$order = $this->request->get("order", "DESC");
$offset = $this->request->get("offset", 0);
$limit = $this->request->get("limit", 0);
... ...
... ... @@ -272,7 +272,7 @@ return [
//自动检测更新
'checkupdate' => false,
//版本号
'version' => '1.0.0.20190510_beta',
'version' => '1.0.0.20190628_beta',
//API接口地址
'api_url' => 'https://api.fastadmin.net',
],
... ...
... ... @@ -15,14 +15,14 @@
}
],
"require": {
"php": ">=5.4.0",
"php": ">=5.6.0",
"topthink/framework": "~5.0.24",
"overtrue/wechat": "~3.1",
"endroid/qr-code": "^1.9",
"topthink/think-captcha": "^1.0",
"mtdowling/cron-expression": "^1.2",
"phpmailer/phpmailer": "^5.2",
"karsonzhang/fastadmin-addons": "~1.1.4",
"karsonzhang/fastadmin-addons": "~1.1.9",
"overtrue/pinyin": "~3.0",
"phpoffice/phpspreadsheet": "^1.2"
},
... ...
... ... @@ -3730,7 +3730,7 @@
<div class="row mt0 footer">
<div class="col-md-6" align="left">
Generated on 2019-02-26 17:13:43 </div>
Generated on 2019-06-28 12:14:48 </div>
<div class="col-md-6" align="right">
<a href="https://www.fastadmin.net" target="_blank">FastAdmin</a>
</div>
... ... @@ -3810,8 +3810,8 @@
$(document).ready(function () {
if (storage) {
$('#token').val(storage.getItem('token'));
$('#apiUrl').val(storage.getItem('apiUrl'));
storage.getItem('token') && $('#token').val(storage.getItem('token'));
storage.getItem('apiUrl') && $('#apiUrl').val(storage.getItem('apiUrl'));
}
$('[data-toggle="tooltip"]').tooltip({
... ...
... ... @@ -32,7 +32,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
{field: 'name', title: __('Name'), align: 'left'},
{field: 'nickname', title: __('Nickname')},
{field: 'flag', title: __('Flag'), formatter: Table.api.formatter.flag},
{field: 'image', title: __('Image'), operate: false, formatter: Table.api.formatter.image},
{field: 'image', title: __('Image'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image},
{field: 'weigh', title: __('Weigh')},
{field: 'status', title: __('Status'), operate: false, formatter: Table.api.formatter.status},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
... ...
... ... @@ -71,6 +71,8 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
sortName: 'id',
showToggle: false,
showExport: false,
columns: [
[
{field: 'state', checkbox: true,},
... ... @@ -115,6 +117,11 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
// 为表格绑定事件
Table.api.bindevent(table);
require(['upload'], function (Upload) {
Upload.api.plupload($("#toolbar .plupload"), function () {
$(".btn-refresh").trigger("click");
});
});
},
add: function () {
Controller.api.bindevent();
... ...
... ... @@ -459,6 +459,8 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
avatar: data.avatar
}));
location.href = Backend.api.fixurl(data.url);
}, function (data) {
$("input[name=captcha]").next(".input-group-addon").find("img").trigger("click");
});
}
};
... ...
... ... @@ -30,7 +30,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
{field: 'nickname', title: __('Nickname'), operate: 'LIKE'},
{field: 'email', title: __('Email'), operate: 'LIKE'},
{field: 'mobile', title: __('Mobile'), operate: 'LIKE'},
{field: 'avatar', title: __('Avatar'), formatter: Table.api.formatter.image, operate: false},
{field: 'avatar', title: __('Avatar'), events: Table.api.events.image, formatter: Table.api.formatter.image, operate: false},
{field: 'level', title: __('Level'), operate: 'BETWEEN', sortable: true},
{field: 'gender', title: __('Gender'), visible: false, searchList: {1: __('Male'), 0: __('Female')}},
{field: 'score', title: __('Score'), operate: 'BETWEEN', sortable: true},
... ...
... ... @@ -40,7 +40,9 @@
// 重置搜索
form.on("click", "button[type=reset]", function (event) {
form[0].reset();
that.onCommonSearch();
setTimeout(function () {
that.onCommonSearch();
}, 0);
});
};
... ...
... ... @@ -57,6 +57,8 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
setTimeout(function () {
location.href = ret.url ? ret.url : "/";
}, 1000);
}, function (data) {
$("input[name=captcha]").next(".input-group-addon").find("img").trigger("click");
});
},
changepwd: function () {
... ...
... ... @@ -159,7 +159,13 @@ require(['jquery', 'bootstrap'], function ($, undefined) {
//加载相应模块
if (Config.jsname) {
require([Config.jsname], function (Controller) {
Controller[Config.actionname] != undefined && Controller[Config.actionname]();
if (Controller.hasOwnProperty(Config.actionname)) {
Controller[Config.actionname]();
} else {
if (Controller.hasOwnProperty("_empty")) {
Controller._empty();
}
}
}, function (e) {
console.error(e);
// 这里可捕获模块加载的错误
... ... @@ -5708,9 +5714,9 @@ define('backend',['fast', 'template', 'moment'], function (Fast, Template, Momen
if (url.indexOf("{ids}") > -1) {
var ids = 0;
var tableId = $(elem).data("table-id");
if (tableId && $(tableId).size() > 0 && $(tableId).data("bootstrap.table")) {
if (tableId && $("#" + tableId).size() > 0 && $("#" + tableId).data("bootstrap.table")) {
var Table = require("table");
ids = Table.api.selectedids($(tableId)).join(",");
ids = Table.api.selectedids($("#" + tableId)).join(",");
}
url = url.replace(/\{ids\}/g, ids);
}
... ... @@ -6451,17 +6457,49 @@ define('upload',['jquery', 'bootstrap', 'plupload', 'template'], function ($, un
});
});
}
//刷新隐藏textarea的值
var refresh = function (name) {
var data = {};
var textarea = $("textarea[name='" + name + "']");
var container = textarea.prev("ul");
$.each($("input,select,textarea", container).serializeArray(), function (i, j) {
var reg = /\[?(\w+)\]?\[(\w+)\]$/g;
var match = reg.exec(j.name);
if (!match)
return true;
if (!isNaN(match[2])) {
data[i] = j.value;
} else {
match[1] = "x" + parseInt(match[1]);
if (typeof data[match[1]] === 'undefined') {
data[match[1]] = {};
}
data[match[1]][match[2]] = j.value;
}
});
var result = [];
$.each(data, function (i, j) {
result.push(j);
});
textarea.val(JSON.stringify(result));
};
if (preview_id && input_id) {
$(document.body).on("keyup change", "#" + input_id, function () {
$(document.body).on("keyup change", "#" + input_id, function (e) {
var inputStr = $("#" + input_id).val();
var inputArr = inputStr.split(/\,/);
$("#" + preview_id).empty();
var tpl = $("#" + preview_id).data("template") ? $("#" + preview_id).data("template") : "";
var extend = $("#" + preview_id).next().is("textarea") ? $("#" + preview_id).next("textarea").val() : "{}";
var json = {};
try {
json = JSON.parse(extend);
} catch (e) {
}
$.each(inputArr, function (i, j) {
if (!j) {
return true;
}
var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data()};
var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data(), key: i, index: i, value: (json && typeof json[i] !== 'undefined' ? json[i] : null)};
var html = tpl ? Template(tpl, data) : Template.render(Upload.config.previewtpl, data);
$("#" + preview_id).append(html);
});
... ... @@ -6469,15 +6507,20 @@ define('upload',['jquery', 'bootstrap', 'plupload', 'template'], function ($, un
$("#" + input_id).trigger("change");
}
if (preview_id) {
//监听文本框改变事件
$("#" + preview_id).on('change keyup', "input,textarea,select", function () {
refresh($(this).closest("ul").data("name"));
});
// 监听事件
$(document.body).on("fa.preview.change", "#" + preview_id, function () {
var urlArr = new Array();
var urlArr = [];
$("#" + preview_id + " [data-url]").each(function (i, j) {
urlArr.push($(this).data("url"));
});
if (input_id) {
$("#" + input_id).val(urlArr.join(","));
}
refresh($("#" + preview_id).data("name"));
});
// 移除按钮事件
$(document.body).on("click", "#" + preview_id + " .btn-trash", function () {
... ... @@ -8901,7 +8944,7 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
},
dataFilter: function (data) {
if (data.code === 1) {
return "";
return data.msg ? { "ok": data.msg } : '';
} else {
return data.msg;
}
... ... @@ -9153,7 +9196,7 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
var textarea = $("textarea[name='" + name + "']", form);
var container = textarea.closest("dl");
var template = container.data("template");
$.each($("input,select", container).serializeArray(), function (i, j) {
$.each($("input,select,textarea", container).serializeArray(), function (i, j) {
var reg = /\[(\w+)\]\[(\w+)\]$/g;
var match = reg.exec(j.name);
if (!match)
... ... @@ -9423,7 +9466,9 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
// 重置搜索
form.on("click", "button[type=reset]", function (event) {
form[0].reset();
that.onCommonSearch();
setTimeout(function () {
that.onCommonSearch();
}, 0);
});
};
... ... @@ -9967,6 +10012,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
return __('Choose');
}
}, locales);
$.fn.bootstrapTable.defaults.exportTypes = defaults.exportTypes;
},
// 绑定事件
bindevent: function (table) {
... ... @@ -10035,7 +10081,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
var field = $(this).closest("ul").data("field");
var value = $(this).data("value");
$("select[name='" + field + "'] option[value='" + value + "']", table.closest(".bootstrap-table").find(".commonsearch-table")).prop("selected", true);
table.bootstrapTable('refresh', {});
table.bootstrapTable('refresh', {pageNumber:1});
return false;
});
// 刷新按钮事件
... ... @@ -10141,7 +10187,8 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
pid: pid,
field: Table.config.dragsortfield,
orderway: options.sortOrder,
table: options.extend.table
table: options.extend.table,
pk: options.pk
}
};
Fast.api.ajax(params, function (data, ret) {
... ... @@ -10273,6 +10320,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
});
Layer.photos({
photos: {
"start": $(this).parent().index(),
"data": data
},
anim: 5 //0-6的选择,指定弹出图片动画类型,默认随机(请注意,3.0之前的版本用shift参数)
... ...
... ... @@ -272,7 +272,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
var textarea = $("textarea[name='" + name + "']", form);
var container = textarea.closest("dl");
var template = container.data("template");
$.each($("input,select", container).serializeArray(), function (i, j) {
$.each($("input,select,textarea", container).serializeArray(), function (i, j) {
var reg = /\[(\w+)\]\[(\w+)\]$/g;
var match = reg.exec(j.name);
if (!match)
... ...
... ... @@ -103,6 +103,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
return __('Choose');
}
}, locales);
$.fn.bootstrapTable.defaults.exportTypes = defaults.exportTypes;
},
// 绑定事件
bindevent: function (table) {
... ...
... ... @@ -260,17 +260,49 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
});
});
}
//刷新隐藏textarea的值
var refresh = function (name) {
var data = {};
var textarea = $("textarea[name='" + name + "']");
var container = textarea.prev("ul");
$.each($("input,select,textarea", container).serializeArray(), function (i, j) {
var reg = /\[?(\w+)\]?\[(\w+)\]$/g;
var match = reg.exec(j.name);
if (!match)
return true;
if (!isNaN(match[2])) {
data[i] = j.value;
} else {
match[1] = "x" + parseInt(match[1]);
if (typeof data[match[1]] === 'undefined') {
data[match[1]] = {};
}
data[match[1]][match[2]] = j.value;
}
});
var result = [];
$.each(data, function (i, j) {
result.push(j);
});
textarea.val(JSON.stringify(result));
};
if (preview_id && input_id) {
$(document.body).on("keyup change", "#" + input_id, function () {
$(document.body).on("keyup change", "#" + input_id, function (e) {
var inputStr = $("#" + input_id).val();
var inputArr = inputStr.split(/\,/);
$("#" + preview_id).empty();
var tpl = $("#" + preview_id).data("template") ? $("#" + preview_id).data("template") : "";
var extend = $("#" + preview_id).next().is("textarea") ? $("#" + preview_id).next("textarea").val() : "{}";
var json = {};
try {
json = JSON.parse(extend);
} catch (e) {
}
$.each(inputArr, function (i, j) {
if (!j) {
return true;
}
var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data()};
var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data(), key: i, index: i, value: (json && typeof json[i] !== 'undefined' ? json[i] : null)};
var html = tpl ? Template(tpl, data) : Template.render(Upload.config.previewtpl, data);
$("#" + preview_id).append(html);
});
... ... @@ -278,15 +310,20 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
$("#" + input_id).trigger("change");
}
if (preview_id) {
//监听文本框改变事件
$("#" + preview_id).on('change keyup', "input,textarea,select", function () {
refresh($(this).closest("ul").data("name"));
});
// 监听事件
$(document.body).on("fa.preview.change", "#" + preview_id, function () {
var urlArr = new Array();
var urlArr = [];
$("#" + preview_id + " [data-url]").each(function (i, j) {
urlArr.push($(this).data("url"));
});
if (input_id) {
$("#" + input_id).val(urlArr.join(","));
}
refresh($("#" + preview_id).data("name"));
});
// 移除按钮事件
$(document.body).on("click", "#" + preview_id + " .btn-trash", function () {
... ...