新增菜单类upgrade方法

新增上传存储文件名功能
新增文件分片上传功能
优化上传组件及方法
优化系统配置中冗余的JS代码
优化下拉框显示样式
优化大数据列表时表格列表渲染速度
修复在PHP高版本下Token验证错误的BUG
修复在PHP高版本下会员登录页错误的BUG
修复Token::get默认值不生效的BUG
pull/220/head
Karson 2020-08-12 21:49:44 +08:00
parent e29a9e41fc
commit ed20e5ac6f
63 changed files with 5296 additions and 949 deletions

View File

@ -17,17 +17,17 @@ FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
* 基于`Bootstrap`开发自适应手机、平板、PC * 基于`Bootstrap`开发自适应手机、平板、PC
* 基于`RequireJS`进行JS模块管理按需加载 * 基于`RequireJS`进行JS模块管理按需加载
* 基于`Less`进行样式开发 * 基于`Less`进行样式开发
* 基于`Bower`进行前端组件包管理
* 强大的插件扩展功能,在线安装卸载升级插件 * 强大的插件扩展功能,在线安装卸载升级插件
* 通用的会员模块和API模块 * 通用的会员模块和API模块
* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证 * 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
* 二级域名部署支持,同时域名支持绑定到插件 * 二级域名部署支持,同时域名支持绑定到插件
* 多语言支持,服务端及客户端支持 * 多语言支持,服务端及客户端支持
* 强大的第三方模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[博客](https://www.fastadmin.net/store/blog.html)、[知识付费问答](https://www.fastadmin.net/store/ask.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html)) * 支持大文件分片上传、剪切板粘贴上传、拖拽上传,进度条显示,图片上传前压缩
* 强大的第三方应用模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[博客](https://www.fastadmin.net/store/blog.html)、[知识付费问答](https://www.fastadmin.net/store/ask.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html)、[商城系统](https://www.fastadmin.net/store/shopro.html))
* 支持CMS、博客、知识付费问答无缝整合[Xunsearch全文搜索](https://www.fastadmin.net/store/xunsearch.html) * 支持CMS、博客、知识付费问答无缝整合[Xunsearch全文搜索](https://www.fastadmin.net/store/xunsearch.html)
* 第三方小程序支持([预订小程序](https://www.fastadmin.net/store/ball.html)、[问答小程序](https://www.fastadmin.net/store/questions.html)、[活动报名小程序](https://www.fastadmin.net/store/huodong.html)、[商城小程序](https://www.fastadmin.net/store/xshop.html)、[博客小程序](https://www.fastadmin.net/store/blog.html)) * 第三方小程序支持([预订小程序](https://www.fastadmin.net/store/ball.html)、[问答小程序](https://www.fastadmin.net/store/questions.html)、[活动报名小程序](https://www.fastadmin.net/store/huodong.html)、[商城小程序](https://www.fastadmin.net/store/xshop.html)、[博客小程序](https://www.fastadmin.net/store/blog.html))
* 整合第三方短信接口(阿里云、腾讯云短信) * 整合第三方短信接口(阿里云、腾讯云短信)
* 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能 * 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能
* 第三方富文本编辑器支持(Summernote、Kindeditor、百度编辑器) * 第三方富文本编辑器支持(Summernote、Kindeditor、百度编辑器)
* 第三方登录(QQ、微信、微博)整合 * 第三方登录(QQ、微信、微博)整合
* 第三方支付(微信、支付宝)无缝整合微信支持PC端扫码支付 * 第三方支付(微信、支付宝)无缝整合微信支持PC端扫码支付
@ -80,6 +80,10 @@ Nice-validator: https://validator.niceue.com
SelectPage: https://github.com/TerryZ/SelectPage SelectPage: https://github.com/TerryZ/SelectPage
Layer: https://layer.layui.com
DropzoneJS: https://www.dropzonejs.com
## 版权信息 ## 版权信息

View File

@ -1394,12 +1394,12 @@ EOD;
} }
$multiple = substr($field, -1) == 's' ? ' data-multiple="true"' : ' data-multiple="false"'; $multiple = substr($field, -1) == 's' ? ' data-multiple="true"' : ' data-multiple="false"';
$preview = ' data-preview-id="p-' . $field . '"'; $preview = ' data-preview-id="p-' . $field . '"';
$previewcontainer = $preview ? '<ul class="row list-inline plupload-preview" id="p-' . $field . '"></ul>' : ''; $previewcontainer = $preview ? '<ul class="row list-inline faupload-preview" id="p-' . $field . '"></ul>' : '';
return <<<EOD return <<<EOD
<div class="input-group"> <div class="input-group">
{$content} {$content}
<div class="input-group-addon no-border no-padding"> <div class="input-group-addon no-border no-padding">
<span><button type="button" id="plupload-{$field}" class="btn btn-danger plupload" data-input-id="c-{$field}"{$uploadfilter}{$multiple}{$preview}><i class="fa fa-upload"></i> {:__('Upload')}</button></span> <span><button type="button" id="faupload-{$field}" class="btn btn-danger faupload" data-input-id="c-{$field}"{$uploadfilter}{$multiple}{$preview}><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-{$field}" class="btn btn-primary fachoose" data-input-id="c-{$field}"{$selectfilter}{$multiple}><i class="fa fa-list"></i> {:__('Choose')}</button></span> <span><button type="button" id="fachoose-{$field}" class="btn btn-primary fachoose" data-input-id="c-{$field}"{$selectfilter}{$multiple}><i class="fa fa-list"></i> {:__('Choose')}</button></span>
</div> </div>
<span class="msg-box n-right" for="c-{$field}"></span> <span class="msg-box n-right" for="c-{$field}"></span>

View File

@ -441,8 +441,8 @@ CREATE TABLE `fa_user` (
`gender` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '性别', `gender` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '性别',
`birthday` date DEFAULT NULL COMMENT '生日', `birthday` date DEFAULT NULL COMMENT '生日',
`bio` varchar(100) NOT NULL DEFAULT '' COMMENT '格言', `bio` varchar(100) NOT NULL DEFAULT '' COMMENT '格言',
`money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '余额', `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '余额',
`score` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '积分', `score` int(10) NOT NULL DEFAULT '0' COMMENT '积分',
`successions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '连续登录天数', `successions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '连续登录天数',
`maxsuccessions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '最大连续登录天数', `maxsuccessions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '最大连续登录天数',
`prevtime` int(10) DEFAULT NULL COMMENT '上次登录时间', `prevtime` int(10) DEFAULT NULL COMMENT '上次登录时间',

View File

@ -3,6 +3,8 @@
namespace app\admin\controller; namespace app\admin\controller;
use app\common\controller\Backend; use app\common\controller\Backend;
use app\common\exception\UploadException;
use app\common\library\Upload;
use fast\Random; use fast\Random;
use think\addons\Service; use think\addons\Service;
use think\Cache; use think\Cache;
@ -54,99 +56,61 @@ class Ajax extends Backend
public function upload() public function upload()
{ {
Config::set('default_return_type', 'json'); Config::set('default_return_type', 'json');
$file = $this->request->file('file'); $chunkid = $this->request->post("chunkid");
if (empty($file)) { if ($chunkid) {
$this->error(__('No file upload or server upload limit exceeded')); if (!Config::get('upload.chunking')) {
} $this->error(__('Chunk file disabled'));
}
//判断是否已经存在附件 $action = $this->request->post("action");
$sha1 = $file->hash(); $chunkindex = $this->request->post("chunkindex/d");
$extparam = $this->request->post(); $chunkcount = $this->request->post("chunkcount/d");
$filename = $this->request->post("filename");
$upload = Config::get('upload'); $method = $this->request->method(true);
if ($action == 'merge') {
preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches); $attachment = null;
$type = strtolower($matches[2]); //合并分片文件
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3]; try {
$size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0); $upload = new Upload();
$fileInfo = $file->getInfo(); $attachment = $upload->merge($chunkid, $chunkcount, $filename);
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION)); } catch (UploadException $e) {
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file'; $this->error($e->getMessage());
}
$mimetypeArr = explode(',', strtolower($upload['mimetype'])); $this->success(__('Uploaded successful'), '', ['url' => $attachment->url]);
$typeArr = explode('/', $fileInfo['type']); } elseif ($method == 'clean') {
//删除冗余的分片文件
//禁止上传PHP和HTML文件 try {
if (in_array($fileInfo['type'], ['text/x-php', 'text/html']) || in_array($suffix, ['php', 'html', 'htm'])) { $upload = new Upload();
$this->error(__('Uploaded file format is limited')); $upload->clean($chunkid);
} } catch (UploadException $e) {
//验证文件后缀 $this->error($e->getMessage());
if ($upload['mimetype'] !== '*' && }
( $this->success();
!in_array($suffix, $mimetypeArr) } else {
|| (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr))) //上传分片文件
) //默认普通上传文件
) { $file = $this->request->file('file');
$this->error(__('Uploaded file format is limited')); try {
} $upload = new Upload($file);
//验证是否为图片文件 $upload->chunk($chunkid, $chunkindex, $chunkcount);
$imagewidth = $imageheight = 0; } catch (UploadException $e) {
if (in_array($fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) { $this->error($e->getMessage());
$imgInfo = getimagesize($fileInfo['tmp_name']); }
if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) { $this->success();
$this->error(__('Uploaded file is not a valid image'));
} }
$imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
$imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
}
$replaceArr = [
'{year}' => date("Y"),
'{mon}' => date("m"),
'{day}' => date("d"),
'{hour}' => date("H"),
'{min}' => date("i"),
'{sec}' => date("s"),
'{random}' => Random::alnum(16),
'{random32}' => Random::alnum(32),
'{filename}' => $suffix ? substr($fileInfo['name'], 0, strripos($fileInfo['name'], '.')) : $fileInfo['name'],
'{suffix}' => $suffix,
'{.suffix}' => $suffix ? '.' . $suffix : '',
'{filemd5}' => md5_file($fileInfo['tmp_name']),
];
$savekey = $upload['savekey'];
$savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
$uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1);
$fileName = substr($savekey, strripos($savekey, '/') + 1);
//
$splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName);
if ($splInfo) {
$params = array(
'admin_id' => (int)$this->auth->id,
'user_id' => 0,
'filesize' => $fileInfo['size'],
'imagewidth' => $imagewidth,
'imageheight' => $imageheight,
'imagetype' => $suffix,
'imageframes' => 0,
'mimetype' => $fileInfo['type'],
'url' => $uploadDir . $splInfo->getSaveName(),
'uploadtime' => time(),
'storage' => 'local',
'sha1' => $sha1,
'extparam' => json_encode($extparam),
);
$attachment = model("attachment");
$attachment->data(array_filter($params));
$attachment->save();
\think\Hook::listen("upload_after", $attachment);
$this->success(__('Upload successful'), null, [
'url' => $uploadDir . $splInfo->getSaveName()
]);
} else { } else {
// 上传失败获取错误信息 $attachment = null;
$this->error($file->getError()); //默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$attachment = $upload->upload();
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url]);
} }
} }
/** /**

View File

@ -105,9 +105,11 @@ class Attachment extends Backend
{ {
if ($ids) { if ($ids) {
\think\Hook::add('upload_delete', function ($params) { \think\Hook::add('upload_delete', function ($params) {
$attachmentFile = ROOT_PATH . '/public' . $params['url']; if ($params['storage'] == 'local') {
if (is_file($attachmentFile)) { $attachmentFile = ROOT_PATH . '/public' . $params['url'];
@unlink($attachmentFile); if (is_file($attachmentFile)) {
@unlink($attachmentFile);
}
} }
}); });
$attachmentlist = $this->model->where('id', 'in', $ids)->select(); $attachmentlist = $this->model->where('id', 'in', $ids)->select();

View File

@ -143,8 +143,24 @@ return [
'Please enter your username' => '请输入你的用户名', 'Please enter your username' => '请输入你的用户名',
'Please enter your password' => '请输入你的密码', 'Please enter your password' => '请输入你的密码',
'Please login first' => '请登录后操作', 'Please login first' => '请登录后操作',
'Uploaded successful' => '上传成功',
'You can upload up to %d file%s' => '你最多还可以上传%d个文件', 'You can upload up to %d file%s' => '你最多还可以上传%d个文件',
'You can choose up to %d file%s' => '你最多还可以选择%d个文件', 'You can choose up to %d file%s' => '你最多还可以选择%d个文件',
'Chunk file write error' => '分片写入失败',
'Chunk file info error' => '分片文件错误',
'Chunk file merge error' => '分片合并错误',
'Chunk file disabled' => '未开启分片上传功能',
'Cancel upload' => '取消上传',
'Upload canceled' => '上传已取消',
'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'Uploaded file format is limited' => '上传文件格式受限制',
'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Are you sure you want to cancel this upload?' => '确定取消上传?',
'Remove file' => '移除文件',
'You can only upload a maximum of %s files' => '你最多允许上传 %s 个文件',
'You can\'t upload files of this type' => '不允许上传的文件类型',
'Server responded with %s code' => '服务端响应(Code:%s)',
'File is too big (%sMiB), Max filesize: %sMiB' => '当前上传(%sM),最大允许上传文件大小:%sM',
'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中', 'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转', 'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
//菜单 //菜单

View File

@ -1,8 +1,3 @@
<?php <?php
return [ return [];
'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'Uploaded file format is limited' => '上传文件格式受限制',
'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Upload successful' => '上传成功',
];

View File

@ -10,6 +10,7 @@ return [
'Imagetype' => '图片类型', 'Imagetype' => '图片类型',
'Imageframes' => '图片帧数', 'Imageframes' => '图片帧数',
'Preview' => '预览', 'Preview' => '预览',
'Filename' => '文件名',
'Filesize' => '文件大小', 'Filesize' => '文件大小',
'Mimetype' => 'Mime类型', 'Mimetype' => 'Mime类型',
'Extparam' => '透传数据', 'Extparam' => '透传数据',

View File

@ -200,6 +200,7 @@ trait Backend
*/ */
public function del($ids = "") public function del($ids = "")
{ {
$ids = $this->request->post("ids");
if ($ids) { if ($ids) {
$pk = $this->model->getPk(); $pk = $this->model->getPk();
$adminIds = $this->getDataLimitAdminIds(); $adminIds = $this->getDataLimitAdminIds();
@ -307,6 +308,7 @@ trait Backend
public function multi($ids = "") public function multi($ids = "")
{ {
$ids = $ids ? $ids : $this->request->param("ids"); $ids = $ids ? $ids : $this->request->param("ids");
$ids = $this->request->param("ids");
if ($ids) { if ($ids) {
if ($this->request->has('params')) { if ($this->request->has('params')) {
parse_str($this->request->post("params"), $values); parse_str($this->request->post("params"), $values);

View File

@ -70,16 +70,16 @@
{case value="images"} {case value="images"}
<div class="form-inline"> <div class="form-inline">
<input id="c-{$item.name}" class="form-control" size="35" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" data-tip="{$item.tip}"> <input id="c-{$item.name}" class="form-control" size="35" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" 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="faupload-{$item.name}" class="btn btn-danger faupload" 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> <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> <ul class="row list-inline faupload-preview" id="p-{$item.name}"></ul>
</div> </div>
{/case} {/case}
{case value="file" break="0"}{/case} {case value="file" break="0"}{/case}
{case value="files"} {case value="files"}
<div class="form-inline"> <div class="form-inline">
<input id="c-{$item.name}" class="form-control" size="35" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" data-tip="{$item.tip}"> <input id="c-{$item.name}" class="form-control" size="35" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" 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="faupload-{$item.name}" class="btn btn-danger faupload" 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> <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>
{/case} {/case}
@ -105,4 +105,4 @@
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button> <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -77,7 +77,7 @@
<div class="widget-body no-padding"> <div class="widget-body no-padding">
<div id="toolbar" class="toolbar"> <div id="toolbar" class="toolbar">
{:build_toolbar('refresh')} {:build_toolbar('refresh')}
<button type="button" id="plupload-addon" class="btn btn-danger plupload" data-url="addon/local" data-mimetype="application/zip" data-multiple="false"><i class="fa fa-upload"></i> <button type="button" id="faupload-addon" class="btn btn-danger faupload" data-url="addon/local" data-mimetype="application/zip" data-multiple="false"><i class="fa fa-upload"></i>
{:__('Offline install')} {:__('Offline install')}
</button> </button>
{if $Think.config.fastadmin.api_url} {if $Think.config.fastadmin.api_url}
@ -293,4 +293,4 @@
<% } %> <% } %>
</div> </div>
</script> </script>
<!--@formatter:on--> <!--@formatter:on-->

View File

@ -58,12 +58,12 @@
<div class="input-group"> <div class="input-group">
<input id="c-image" class="form-control" size="35" name="row[image]" type="text" value=""> <input id="c-image" class="form-control" size="35" name="row[image]" type="text" value="">
<div class="input-group-addon no-border no-padding"> <div class="input-group-addon no-border no-padding">
<span><button type="button" id="plupload-image" class="btn btn-danger plupload" data-input-id="c-image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span> <span><button type="button" id="faupload-image" class="btn btn-danger faupload" data-input-id="c-image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-image" class="btn btn-primary fachoose" data-input-id="c-image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span> <span><button type="button" id="fachoose-image" class="btn btn-primary fachoose" data-input-id="c-image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
</div> </div>
<span class="msg-box n-right"></span> <span class="msg-box n-right"></span>
</div> </div>
<ul class="row list-inline plupload-preview" id="p-image"></ul> <ul class="row list-inline faupload-preview" id="p-image"></ul>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -54,12 +54,12 @@
<div class="input-group"> <div class="input-group">
<input id="c-image" class="form-control" size="35" name="row[image]" type="text" value="{$row.image}"> <input id="c-image" class="form-control" size="35" name="row[image]" type="text" value="{$row.image}">
<div class="input-group-addon no-border no-padding"> <div class="input-group-addon no-border no-padding">
<span><button type="button" id="plupload-image" class="btn btn-danger plupload" data-input-id="c-image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span> <span><button type="button" id="faupload-image" class="btn btn-danger faupload" data-input-id="c-image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-image" class="btn btn-primary fachoose" data-input-id="c-image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span> <span><button type="button" id="fachoose-image" class="btn btn-primary fachoose" data-input-id="c-image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
</div> </div>
<span class="msg-box n-right"></span> <span class="msg-box n-right"></span>
</div> </div>
<ul class="row list-inline plupload-preview" id="p-image"></ul> <ul class="row list-inline faupload-preview" id="p-image"></ul>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -10,7 +10,7 @@
<div class="form-group"> <div class="form-group">
<label for="c-third" class="control-label col-xs-12 col-sm-2"></label> <label for="c-third" class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8"> <div class="col-xs-12 col-sm-8">
<button id="plupload-third" class="btn btn-danger plupload" data-multiple="true" data-input-id="c-third" ><i class="fa fa-upload"></i> {:__("Upload to third")}</button> <button type="button" id="faupload-third" class="btn btn-danger faupload" data-multiple="true" data-input-id="c-third" ><i class="fa fa-upload"></i> {:__("Upload to third")}</button>
</div> </div>
</div> </div>
{/if} {/if}
@ -25,7 +25,7 @@
<div class="form-group"> <div class="form-group">
<label for="c-local" class="control-label col-xs-12 col-sm-2"></label> <label for="c-local" class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8"> <div class="col-xs-12 col-sm-8">
<button id="plupload-local" class="btn btn-primary plupload" data-input-id="c-local" data-url="{:url('ajax/upload')}"><i class="fa fa-upload"></i> {:__("Upload to local")}</button> <button type="button" id="faupload-local" class="btn btn-primary faupload" data-input-id="c-local" data-url="{:url('ajax/upload')}"><i class="fa fa-upload"></i> {:__("Upload to local")}</button>
</div> </div>
</div> </div>

View File

@ -30,6 +30,12 @@
<input type="number" name="row[imageframes]" value="{$row.imageframes}" id="c-imageframes" class="form-control" /> <input type="number" name="row[imageframes]" value="{$row.imageframes}" id="c-imageframes" class="form-control" />
</div> </div>
</div> </div>
<div class="form-group">
<label for="c-filename" class="control-label col-xs-12 col-sm-2">{:__('Filename')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" name="row[filename]" value="{$row.filename|htmlentities}" id="c-filename" class="form-control" />
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="c-filesize" class="control-label col-xs-12 col-sm-2">{:__('Filesize')}:</label> <label for="c-filesize" class="control-label col-xs-12 col-sm-2">{:__('Filesize')}:</label>
<div class="col-xs-12 col-sm-8"> <div class="col-xs-12 col-sm-8">

View File

@ -17,7 +17,7 @@
<div class="widget-body no-padding"> <div class="widget-body no-padding">
<div id="toolbar" class="toolbar"> <div id="toolbar" class="toolbar">
{:build_toolbar('refresh')} {: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> <span><button type="button" id="faupload-image" class="btn btn-success faupload" data-mimetype="{$Think.get.mimetype|default=''}" data-multiple="true"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
{if request()->get('multiple') == 'true'} {if request()->get('multiple') == 'true'}
<a class="btn btn-danger btn-choose-multi"><i class="fa fa-check"></i> {:__('Choose')}</a> <a class="btn btn-danger btn-choose-multi"><i class="fa fa-check"></i> {:__('Choose')}</a>
{/if} {/if}

View File

@ -111,17 +111,17 @@
{case value="images"} {case value="images"}
<div class="form-inline"> <div class="form-inline">
<input id="c-{$item.name}" class="form-control" size="50" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" data-tip="{$item.tip}"> <input id="c-{$item.name}" class="form-control" size="50" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" 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="faupload-{$item.name}" class="btn btn-danger faupload" 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> <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>
<span class="msg-box n-right" for="c-{$item.name}"></span> <span class="msg-box n-right" for="c-{$item.name}"></span>
<ul class="row list-inline plupload-preview" id="p-{$item.name}"></ul> <ul class="row list-inline faupload-preview" id="p-{$item.name}"></ul>
</div> </div>
{/case} {/case}
{case value="file" break="0"}{/case} {case value="file" break="0"}{/case}
{case value="files"} {case value="files"}
<div class="form-inline"> <div class="form-inline">
<input id="c-{$item.name}" class="form-control" size="50" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" data-tip="{$item.tip}"> <input id="c-{$item.name}" class="form-control" size="50" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" 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="faupload-{$item.name}" class="btn btn-danger faupload" 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> <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>
<span class="msg-box n-right" for="c-{$item.name}"></span> <span class="msg-box n-right" for="c-{$item.name}"></span>
</div> </div>

View File

@ -51,9 +51,9 @@
<div class="box-body box-profile"> <div class="box-body box-profile">
<div class="profile-avatar-container"> <div class="profile-avatar-container">
<img class="profile-user-img img-responsive img-circle plupload" src="{$admin.avatar|cdnurl|htmlentities}" alt=""> <img class="profile-user-img img-responsive img-circle faupload" src="{$admin.avatar|cdnurl|htmlentities}" alt="">
<div class="profile-avatar-text img-circle">{:__('Click to edit')}</div> <div class="profile-avatar-text img-circle">{:__('Click to edit')}</div>
<button id="plupload-avatar" class="plupload" data-input-id="c-avatar"><i class="fa fa-upload"></i> {:__('Upload')}</button> <button id="faupload-avatar" class="faupload" data-input-id="c-avatar"><i class="fa fa-upload"></i> {:__('Upload')}</button>
</div> </div>
<h3 class="profile-username text-center">{$admin.username|htmlentities}</h3> <h3 class="profile-username text-center">{$admin.username|htmlentities}</h3>

View File

@ -85,7 +85,7 @@
</span> </span>
</div> </div>
{/if} {/if}
<div class="form-group"> <div class="form-group checkbox">
<label class="inline" for="keeplogin"> <label class="inline" for="keeplogin">
<input type="checkbox" name="keeplogin" id="keeplogin" value="1" /> <input type="checkbox" name="keeplogin" id="keeplogin" value="1" />
{:__('Keep login')} {:__('Keep login')}
@ -102,4 +102,4 @@
</div> </div>
{include file="common/script" /} {include file="common/script" /}
</body> </body>
</html> </html>

View File

@ -43,12 +43,12 @@
<div class="input-group"> <div class="input-group">
<input id="c-avatar" data-rule="" class="form-control" size="50" name="row[avatar]" type="text" value="{$row.avatar}"> <input id="c-avatar" data-rule="" class="form-control" size="50" name="row[avatar]" type="text" value="{$row.avatar}">
<div class="input-group-addon no-border no-padding"> <div class="input-group-addon no-border no-padding">
<span><button type="button" id="plupload-avatar" class="btn btn-danger plupload" data-input-id="c-avatar" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-avatar"><i class="fa fa-upload"></i> {:__('Upload')}</button></span> <span><button type="button" id="faupload-avatar" class="btn btn-danger faupload" data-input-id="c-avatar" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-avatar"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-avatar" class="btn btn-primary fachoose" data-input-id="c-avatar" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span> <span><button type="button" id="fachoose-avatar" class="btn btn-primary fachoose" data-input-id="c-avatar" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
</div> </div>
<span class="msg-box n-right" for="c-avatar"></span> <span class="msg-box n-right" for="c-avatar"></span>
</div> </div>
<ul class="row list-inline plupload-preview" id="p-avatar"></ul> <ul class="row list-inline faupload-preview" id="p-avatar"></ul>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -3,6 +3,8 @@
namespace app\api\controller; namespace app\api\controller;
use app\common\controller\Api; use app\common\controller\Api;
use app\common\exception\UploadException;
use app\common\library\Upload;
use app\common\model\Area; use app\common\model\Area;
use app\common\model\Version; use app\common\model\Version;
use fast\Random; use fast\Random;
@ -47,96 +49,61 @@ class Common extends Api
*/ */
public function upload() public function upload()
{ {
$file = $this->request->file('file'); Config::set('default_return_type', 'json');
if (empty($file)) { $chunkid = $this->request->post("chunkid");
$this->error(__('No file upload or server upload limit exceeded')); if ($chunkid) {
} if (!Config::get('upload.chunking')) {
$this->error(__('Chunk file disabled'));
//判断是否已经存在附件 }
$sha1 = $file->hash(); $action = $this->request->post("action");
$chunkindex = $this->request->post("chunkindex/d");
$upload = Config::get('upload'); $chunkcount = $this->request->post("chunkcount/d");
$filename = $this->request->post("filename");
preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches); $method = $this->request->method(true);
$type = strtolower($matches[2]); if ($action == 'merge') {
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3]; $attachment = null;
$size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0); //合并分片文件
$fileInfo = $file->getInfo(); try {
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION)); $upload = new Upload();
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file'; $attachment = $upload->merge($chunkid, $chunkcount, $filename);
} catch (UploadException $e) {
$mimetypeArr = explode(',', strtolower($upload['mimetype'])); $this->error($e->getMessage());
$typeArr = explode('/', $fileInfo['type']); }
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url]);
//禁止上传PHP和HTML文件 } elseif ($method == 'clean') {
if (in_array($fileInfo['type'], ['text/x-php', 'text/html']) || in_array($suffix, ['php', 'html', 'htm'])) { //删除冗余的分片文件
$this->error(__('Uploaded file format is limited')); try {
} $upload = new Upload();
//验证文件后缀 $upload->clean($chunkid);
if ($upload['mimetype'] !== '*' && } catch (UploadException $e) {
( $this->error($e->getMessage());
!in_array($suffix, $mimetypeArr) }
|| (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr))) $this->success();
) } else {
) { //上传分片文件
$this->error(__('Uploaded file format is limited')); //默认普通上传文件
} $file = $this->request->file('file');
//验证是否为图片文件 try {
$imagewidth = $imageheight = 0; $upload = new Upload($file);
if (in_array($fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) { $upload->chunk($chunkid, $chunkindex, $chunkcount);
$imgInfo = getimagesize($fileInfo['tmp_name']); } catch (UploadException $e) {
if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) { $this->error($e->getMessage());
$this->error(__('Uploaded file is not a valid image')); }
$this->success();
} }
$imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
$imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
}
$replaceArr = [
'{year}' => date("Y"),
'{mon}' => date("m"),
'{day}' => date("d"),
'{hour}' => date("H"),
'{min}' => date("i"),
'{sec}' => date("s"),
'{random}' => Random::alnum(16),
'{random32}' => Random::alnum(32),
'{filename}' => $suffix ? substr($fileInfo['name'], 0, strripos($fileInfo['name'], '.')) : $fileInfo['name'],
'{suffix}' => $suffix,
'{.suffix}' => $suffix ? '.' . $suffix : '',
'{filemd5}' => md5_file($fileInfo['tmp_name']),
];
$savekey = $upload['savekey'];
$savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
$uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1);
$fileName = substr($savekey, strripos($savekey, '/') + 1);
//
$splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName);
if ($splInfo) {
$params = array(
'admin_id' => 0,
'user_id' => (int)$this->auth->id,
'filesize' => $fileInfo['size'],
'imagewidth' => $imagewidth,
'imageheight' => $imageheight,
'imagetype' => $suffix,
'imageframes' => 0,
'mimetype' => $fileInfo['type'],
'url' => $uploadDir . $splInfo->getSaveName(),
'uploadtime' => time(),
'storage' => 'local',
'sha1' => $sha1,
);
$attachment = model("attachment");
$attachment->data(array_filter($params));
$attachment->save();
\think\Hook::listen("upload_after", $attachment);
$this->success(__('Upload successful'), [
'url' => $uploadDir . $splInfo->getSaveName()
]);
} else { } else {
// 上传失败获取错误信息 $attachment = null;
$this->error($file->getError()); //默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$attachment = $upload->upload();
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url]);
} }
} }
} }

View File

@ -1,84 +1,102 @@
<?php <?php
return [ return [
'Keep login' => '保持会话', 'Keep login' => '保持会话',
'Username' => '用户名', 'Username' => '用户名',
'User id' => '会员ID', 'User id' => '会员ID',
'Nickname' => '昵称', 'Nickname' => '昵称',
'Password' => '密码', 'Password' => '密码',
'Sign up' => '注 册', 'Sign up' => '注 册',
'Sign in' => '登 录', 'Sign in' => '登 录',
'Sign out' => '注 销', 'Sign out' => '注 销',
'Guest' => '游客', 'Guest' => '游客',
'Welcome' => '%s你好', 'Welcome' => '%s你好',
'Add' => '添加', 'Add' => '添加',
'Edit' => '编辑', 'Edit' => '编辑',
'Delete' => '删除', 'Delete' => '删除',
'Move' => '移动', 'Move' => '移动',
'Name' => '名称', 'Name' => '名称',
'Status' => '状态', 'Status' => '状态',
'Weigh' => '权重', 'Weigh' => '权重',
'Operate' => '操作', 'Operate' => '操作',
'Warning' => '温馨提示', 'Warning' => '温馨提示',
'Default' => '默认', 'Default' => '默认',
'Article' => '文章', 'Article' => '文章',
'Page' => '单页', 'Page' => '单页',
'OK' => '确定', 'OK' => '确定',
'Cancel' => '取消', 'Cancel' => '取消',
'Loading' => '加载中', 'Loading' => '加载中',
'More' => '更多', 'More' => '更多',
'Normal' => '正常', 'Normal' => '正常',
'Hidden' => '隐藏', 'Hidden' => '隐藏',
'Submit' => '提交', 'Submit' => '提交',
'Reset' => '重置', 'Reset' => '重置',
'Execute' => '执行', 'Execute' => '执行',
'Close' => '关闭', 'Close' => '关闭',
'Search' => '搜索', 'Search' => '搜索',
'Refresh' => '刷新', 'Refresh' => '刷新',
'First' => '首页', 'First' => '首页',
'Previous' => '上一页', 'Previous' => '上一页',
'Next' => '下一页', 'Next' => '下一页',
'Last' => '末页', 'Last' => '末页',
'None' => '无', 'None' => '无',
'Home' => '主页', 'Home' => '主页',
'Online' => '在线', 'Online' => '在线',
'Logout' => '注销', 'Logout' => '注销',
'Profile' => '个人资料', 'Profile' => '个人资料',
'Index' => '首页', 'Index' => '首页',
'Hot' => '热门', 'Hot' => '热门',
'Recommend' => '推荐', 'Recommend' => '推荐',
'Dashboard' => '控制台', 'Dashboard' => '控制台',
'Code' => '编号', 'Code' => '编号',
'Message' => '内容', 'Message' => '内容',
'Line' => '行号', 'Line' => '行号',
'File' => '文件', 'File' => '文件',
'Menu' => '菜单', 'Menu' => '菜单',
'Type' => '类型', 'Type' => '类型',
'Title' => '标题', 'Title' => '标题',
'Content' => '内容', 'Content' => '内容',
'Append' => '追加', 'Append' => '追加',
'Memo' => '备注', 'Memo' => '备注',
'Parent' => '父级', 'Parent' => '父级',
'Params' => '参数', 'Params' => '参数',
'Permission' => '权限', 'Permission' => '权限',
'Advance search' => '高级搜索', 'Advance search' => '高级搜索',
'Check all' => '选中全部', 'Check all' => '选中全部',
'Expand all' => '展开全部', 'Expand all' => '展开全部',
'Begin time' => '开始时间', 'Begin time' => '开始时间',
'End time' => '结束时间', 'End time' => '结束时间',
'Create time' => '创建时间', 'Create time' => '创建时间',
'Flag' => '标志', 'Flag' => '标志',
'Please login first' => '请登录后操作', 'Please login first' => '请登录后操作',
'Redirect now' => '立即跳转', 'Uploaded successful' => '上传成功',
'Operation completed' => '操作成功!', 'You can upload up to %d file%s' => '你最多还可以上传%d个文件',
'Operation failed' => '操作失败!', 'You can choose up to %d file%s' => '你最多还可以选择%d个文件',
'Unknown data format' => '未知的数据格式!', 'Chunk file write error' => '分片写入失败',
'Network error' => '网络错误!', 'Chunk file info error' => '分片文件错误',
'Advanced search' => '高级搜索', 'Chunk file merge error' => '分片合并错误',
'Invalid parameters' => '未知参数', 'Chunk file disabled' => '未开启分片上传功能',
'No results were found' => '记录未找到', 'Cancel upload' => '取消上传',
'Parameter %s can not be empty' => '参数%s不能为空', 'Upload canceled' => '上传已取消',
'You have no permission' => '你没有权限访问', 'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中', 'Uploaded file format is limited' => '上传文件格式受限制',
'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转', 'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Are you sure you want to cancel this upload?' => '确定取消上传?',
'Remove file' => '移除文件',
'You can only upload a maximum of %s files' => '你最多允许上传 %s 个文件',
'You can\'t upload files of this type' => '不允许上传的文件类型',
'Server responded with %s code' => '服务端响应(Code:%s)',
'File is too big (%sMiB), Max filesize: %sMiB' => '当前上传(%sM),最大允许上传文件大小:%sM',
'Redirect now' => '立即跳转',
'Operation completed' => '操作成功!',
'Operation failed' => '操作失败!',
'Unknown data format' => '未知的数据格式!',
'Network error' => '网络错误!',
'Advanced search' => '高级搜索',
'Invalid parameters' => '未知参数',
'No results were found' => '记录未找到',
'Parameter %s can not be empty' => '参数%s不能为空',
'You have no permission' => '你没有权限访问',
'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
]; ];

View File

@ -1,8 +1,3 @@
<?php <?php
return [ return [];
'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'Uploaded file format is limited' => '上传文件格式受限制',
'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Upload successful' => '上传成功',
];

View File

@ -228,7 +228,7 @@ class Backend extends Controller
*/ */
protected function loadlang($name) protected function loadlang($name)
{ {
$name = Loader::parseName($name); $name = Loader::parseName($name);
Lang::load(APP_PATH . $this->request->module() . '/lang/' . $this->request->langset() . '/' . str_replace('.', '/', $name) . '.php'); Lang::load(APP_PATH . $this->request->module() . '/lang/' . $this->request->langset() . '/' . str_replace('.', '/', $name) . '.php');
} }
@ -456,8 +456,11 @@ class Backend extends Controller
$where = function ($query) use ($word, $andor, $field, $searchfield, $custom) { $where = function ($query) use ($word, $andor, $field, $searchfield, $custom) {
$logic = $andor == 'AND' ? '&' : '|'; $logic = $andor == 'AND' ? '&' : '|';
$searchfield = is_array($searchfield) ? implode($logic, $searchfield) : $searchfield; $searchfield = is_array($searchfield) ? implode($logic, $searchfield) : $searchfield;
foreach ($word as $k => $v) { $word = array_filter($word);
$query->where(str_replace(',', $logic, $searchfield), "like", "%{$v}%"); if ($word) {
foreach ($word as $k => $v) {
$query->where(str_replace(',', $logic, $searchfield), "like", "%{$v}%");
}
} }
if ($custom && is_array($custom)) { if ($custom && is_array($custom)) {
foreach ($custom as $k => $v) { foreach ($custom as $k => $v) {
@ -517,7 +520,7 @@ class Backend extends Controller
$token = $this->request->post('__token__'); $token = $this->request->post('__token__');
//验证Token //验证Token
if (!Validate::is($token, "token", ['__token__' => $token])) { if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) {
$this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]); $this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]);
} }

View File

@ -145,7 +145,7 @@ class Frontend extends Controller
$token = $this->request->post('__token__'); $token = $this->request->post('__token__');
//验证Token //验证Token
if (!Validate::is($token, "token", ['__token__' => $token])) { if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) {
$this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]); $this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]);
} }

View File

@ -0,0 +1,17 @@
<?php
namespace app\common\exception;
use think\Exception;
use Throwable;
class UploadException extends Exception
{
public function __construct($message = "", $code = 0, $data = [])
{
$this->message = $message;
$this->code = $code;
$this->data = $data;
}
}

View File

@ -89,6 +89,21 @@ class Menu
return true; return true;
} }
/**
* 升级菜单
* @param string $name 插件名称
* @param array $menu 新菜单
* @return bool
*/
public static function upgrade($name, $menu)
{
$old = AuthRule::where('name', 'like', "{$name}%")->select();
$old = collection($old)->toArray();
$old = array_column($old, null, 'name');
self::menuUpdate($menu, $old);
return true;
}
/** /**
* 导出指定名称的菜单规则 * 导出指定名称的菜单规则
* @param string $name * @param string $name
@ -109,6 +124,44 @@ class Menu
return $menuList; return $menuList;
} }
/**
* 菜单升级
* @param array $newMenu
* @param array $oldMenu
* @param int $parent
* @throws Exception
*/
private static function menuUpdate($newMenu, $oldMenu, $parent = 0)
{
if (!is_numeric($parent)) {
$parentRule = AuthRule::getByName($parent);
$pid = $parentRule ? $parentRule['id'] : 0;
} else {
$pid = $parent;
}
$allow = array_flip(['file', 'name', 'title', 'icon', 'condition', 'remark', 'ismenu', 'weigh']);
foreach ($newMenu as $k => $v) {
$hasChild = isset($v['sublist']) && $v['sublist'] ? true : false;
$data = array_intersect_key($v, $allow);
$data['ismenu'] = isset($data['ismenu']) ? $data['ismenu'] : ($hasChild ? 1 : 0);
$data['icon'] = isset($data['icon']) ? $data['icon'] : ($hasChild ? 'fa fa-list' : 'fa fa-circle-o');
$data['pid'] = $pid;
$data['status'] = 'normal';
try {
if (!isset($oldMenu[$data['name']])) {
$menu = AuthRule::create($data);
} else {
$menu = $oldMenu[$data['name']];
}
if ($hasChild) {
self::menuUpdate($v['sublist'], $oldMenu, $menu['id']);
}
} catch (PDOException $e) {
throw new Exception($e->getMessage());
}
}
}
/** /**
* 根据名称获取规则IDS * 根据名称获取规则IDS
* @param string $name * @param string $name

View File

@ -0,0 +1,323 @@
<?php
namespace app\common\library;
use app\common\exception\UploadException;
use app\common\model\Attachment;
use fast\Random;
use FilesystemIterator;
use think\Config;
use think\File;
use think\Hook;
/**
* 文件上传类
*/
class Upload
{
/**
* 验证码有效时长
* @var int
*/
protected static $expire = 120;
/**
* 最大允许检测的次数
* @var int
*/
protected static $maxCheckNums = 10;
protected $chunkDir = null;
protected $config = [];
protected $error = '';
/**
* @var \think\File
*/
protected $file = null;
protected $fileInfo = null;
public function __construct($file = null)
{
$this->config = Config::get('upload');
$this->chunkDir = RUNTIME_PATH . 'chunks';
if ($file) {
$this->setFile($file);
}
}
public function setChunkDir($dir)
{
$this->chunkDir = $dir;
}
public function getFile()
{
return $this->file;
}
public function setFile($file)
{
if (empty($file)) {
throw new UploadException(__('No file upload or server upload limit exceeded'));
}
$fileInfo = $file->getInfo();
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
$fileInfo['suffix'] = $suffix;
$fileInfo['imagewidth'] = 0;
$fileInfo['imageheight'] = 0;
$this->file = $file;
$this->fileInfo = $fileInfo;
}
protected function checkExecutable()
{
//禁止上传PHP和HTML文件
if (in_array($this->fileInfo['type'], ['text/x-php', 'text/html']) || in_array($this->fileInfo['suffix'], ['php', 'html', 'htm'])) {
throw new UploadException(__('Uploaded file format is limited'));
}
return true;
}
protected function checkMimetype()
{
$mimetypeArr = explode(',', strtolower($this->config['mimetype']));
$typeArr = explode('/', $this->fileInfo['type']);
//验证文件后缀
if ($this->config['mimetype'] !== '*' &&
(!in_array($this->fileInfo['suffix'], $mimetypeArr) || (stripos($typeArr[0] . '/', $this->config['mimetype']) !== false && (!in_array($this->fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr))))
) {
throw new UploadException(__('Uploaded file format is limited'));
return false;
}
return true;
}
protected function checkImage($force = false)
{
//验证是否为图片文件
if (in_array($this->fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($this->fileInfo['suffix'], ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) {
$imgInfo = getimagesize($this->fileInfo['tmp_name']);
if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
throw new UploadException(__('Uploaded file is not a valid image'));
}
$this->fileInfo['imagewidth'] = isset($imgInfo[0]) ? $imgInfo[0] : 0;
$this->fileInfo['imageheight'] = isset($imgInfo[1]) ? $imgInfo[1] : 0;
return true;
} else {
return !$force;
}
}
protected function checkSize()
{
preg_match('/([0-9\.]+)(\w+)/', $this->config['maxsize'], $matches);
$size = $matches ? $matches[1] : $this->config['maxsize'];
$type = $matches ? strtolower($matches[2]) : 'b';
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
$size = (int)($size * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0));
if ($this->fileInfo['size'] > $size) {
throw new UploadException(__('File is too big (%sMiB). Max filesize: %sMiB.',
round($this->fileInfo['size'] / pow(1024, 2), 2),
round($size / pow(1024, 2), 2)));
}
}
public function getSuffix()
{
return $this->fileInfo['suffix'] ?: 'file';
}
public function getSavekey($savekey = null, $filename = null, $md5 = null)
{
if ($filename) {
$suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
} else {
$suffix = $this->fileInfo['suffix'];
}
$filename = $filename ? $filename : ($suffix ? substr($this->fileInfo['name'], 0, strripos($this->fileInfo['name'], '.')) : $this->fileInfo['name']);
$md5 = $md5 ? $md5 : md5_file($this->fileInfo['tmp_name']);
$replaceArr = [
'{year}' => date("Y"),
'{mon}' => date("m"),
'{day}' => date("d"),
'{hour}' => date("H"),
'{min}' => date("i"),
'{sec}' => date("s"),
'{random}' => Random::alnum(16),
'{random32}' => Random::alnum(32),
'{filename}' => $filename,
'{suffix}' => $suffix,
'{.suffix}' => $suffix ? '.' . $suffix : '',
'{filemd5}' => $md5,
];
$savekey = $savekey ? $savekey : $this->config['savekey'];
$savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
return $savekey;
}
/**
* 清理分片文件
* @param $chunkid
*/
public function clean($chunkid)
{
$iterator = new \GlobIterator($this->chunkDir . DS . $chunkid . '-*', FilesystemIterator::KEY_AS_FILENAME);
$array = iterator_to_array($iterator);
var_dump($array);
}
public function merge($chunkid, $chunkcount, $filename)
{
$filePath = $this->chunkDir . DS . $chunkid;
$completed = true;
//检查所有分片是否都存在
for ($i = 0; $i < $chunkcount; $i++) {
if (!file_exists("{$filePath}-{$i}.part")) {
$completed = false;
break;
}
}
if (!$completed) {
throw new UploadException(__('Chunk file info error'));
}
//如果所有文件分片都上传完毕,开始合并
$uploadPath = $filePath;
if (!$destFile = @fopen($uploadPath, "wb")) {
throw new UploadException(__('Chunk file merge error'));
}
if (flock($destFile, LOCK_EX)) { // 进行排他型锁定
for ($i = 0; $i < $chunkcount; $i++) {
$partFile = "{$filePath}-{$i}.part";
if (!$handle = @fopen($partFile, "rb")) {
break;
}
while ($buff = fread($handle, filesize($partFile))) {
fwrite($destFile, $buff);
}
@fclose($handle);
@unlink($partFile); //删除分片
}
flock($destFile, LOCK_UN);
}
@fclose($destFile);
$file = new File($uploadPath);
$info = [
'name' => $filename,
'type' => $file->getMime(),
'tmp_name' => $uploadPath,
'error' => 0,
'size' => $file->getSize()
];
$file->setUploadInfo($info);
$file->isTest(true);
//重新设置文件
$this->setFile($file);
//允许大文件
$this->config['maxsize'] = "1024G";
return $this->upload();
}
/**
* 分片上传
* @throws UploadException
*/
public function chunk($chunkid, $chunkindex, $chunkcount, $chunkfilesize = null, $chunkfilename = null, $direct = false)
{
if ($this->fileInfo['type'] != 'application/octet-stream') {
throw new UploadException(__('Uploaded file format is limited'));
}
$destDir = RUNTIME_PATH . 'chunks';
$fileName = $chunkid . "-" . $chunkindex . '.part';
$destFile = $destDir . DS . $fileName;
if (!move_uploaded_file($this->file->getPathname(), $destFile)) {
throw new UploadException(__('Chunk file write error'));
}
$file = new File($destFile);
$this->setFile($file);
return $file;
}
/**
* 普通上传
* @return \app\common\model\attachment|\think\Model
* @throws UploadException
*/
public function upload($savekey = null)
{
if (empty($this->file)) {
throw new UploadException(__('No file upload or server upload limit exceeded'));
}
$this->checkSize();
$this->checkExecutable();
$this->checkMimetype();
$this->checkImage();
$savekey = $savekey ? $savekey : $this->getSavekey();
$savekey = '/' . ltrim($savekey, '/');
$uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1);
$fileName = substr($savekey, strripos($savekey, '/') + 1);
$destDir = ROOT_PATH . 'public' . $uploadDir;
$sha1 = $this->file->hash();
$file = $this->file->move($destDir, $fileName);
if (!$file) {
// 上传失败获取错误信息
throw new UploadException($this->file->getError());
}
$this->file = $file;
$params = array(
'admin_id' => (int)session('admin.id'),
'user_id' => (int)cookie('uid'),
'filename' => htmlspecialchars(strip_tags($this->fileInfo['name'])),
'filesize' => $this->fileInfo['size'],
'imagewidth' => $this->fileInfo['imagewidth'],
'imageheight' => $this->fileInfo['imageheight'],
'imagetype' => $this->fileInfo['suffix'],
'imageframes' => 0,
'mimetype' => $this->fileInfo['type'],
'url' => $uploadDir . $file->getSaveName(),
'uploadtime' => time(),
'storage' => 'local',
'sha1' => $sha1,
'extparam' => '',
);
$attachment = new Attachment();
$attachment->data(array_filter($params));
$attachment->save();
\think\Hook::listen("upload_after", $attachment);
return $attachment;
}
public function setError($msg)
{
$this->error = $msg;
}
public function getError()
{
return $this->error;
}
}

View File

@ -38,7 +38,7 @@ class Attachment extends Model
{ {
// 如果已经上传该资源,则不再记录 // 如果已经上传该资源,则不再记录
self::beforeInsert(function ($model) { self::beforeInsert(function ($model) {
if (self::where('url', '=', $model['url'])->find()) { if (self::where('url', '=', $model['url'])->where('storage', $model['storage'])->find()) {
return false; return false;
} }
}); });

View File

@ -29,24 +29,27 @@ class Config extends Model
public static function getTypeList() public static function getTypeList()
{ {
$typeList = [ $typeList = [
'string' => __('String'), 'string' => __('String'),
'text' => __('Text'), 'text' => __('Text'),
'editor' => __('Editor'), 'editor' => __('Editor'),
'number' => __('Number'), 'number' => __('Number'),
'date' => __('Date'), 'date' => __('Date'),
'time' => __('Time'), 'time' => __('Time'),
'datetime' => __('Datetime'), 'datetime' => __('Datetime'),
'select' => __('Select'), 'select' => __('Select'),
'selects' => __('Selects'), 'selects' => __('Selects'),
'image' => __('Image'), 'image' => __('Image'),
'images' => __('Images'), 'images' => __('Images'),
'file' => __('File'), 'file' => __('File'),
'files' => __('Files'), 'files' => __('Files'),
'switch' => __('Switch'), 'switch' => __('Switch'),
'checkbox' => __('Checkbox'), 'checkbox' => __('Checkbox'),
'radio' => __('Radio'), 'radio' => __('Radio'),
'array' => __('Array'), 'city' => __('City'),
'custom' => __('Custom'), 'selectpage' => __('Selectpage'),
'selectpages' => __('Selectpages'),
'array' => __('Array'),
'custom' => __('Custom'),
]; ];
return $typeList; return $typeList;
} }

View File

@ -276,7 +276,7 @@ return [
//自动检测更新 //自动检测更新
'checkupdate' => false, 'checkupdate' => false,
//版本号 //版本号
'version' => '1.1.0.20200612_beta', 'version' => '1.2.0',
//API接口地址 //API接口地址
'api_url' => 'https://api.fastadmin.net', 'api_url' => 'https://api.fastadmin.net',
], ],

View File

@ -26,4 +26,8 @@ return [
* 是否支持批量上传 * 是否支持批量上传
*/ */
'multiple' => false, 'multiple' => false,
/**
* 是否支持分片上传
*/
'chunking' => false,
]; ];

View File

@ -107,6 +107,24 @@ return [
'User center' => '会员中心', 'User center' => '会员中心',
'Change password' => '修改密码', 'Change password' => '修改密码',
'Please login first' => '请登录后再操作', 'Please login first' => '请登录后再操作',
'Uploaded successful' => '上传成功',
'You can upload up to %d file%s' => '你最多还可以上传%d个文件',
'You can choose up to %d file%s' => '你最多还可以选择%d个文件',
'Chunk file write error' => '分片写入失败',
'Chunk file info error' => '分片文件错误',
'Chunk file merge error' => '分片合并错误',
'Chunk file disabled' => '未开启分片上传功能',
'Cancel upload' => '取消上传',
'Upload canceled' => '上传已取消',
'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'Uploaded file format is limited' => '上传文件格式受限制',
'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Are you sure you want to cancel this upload?' => '确定取消上传?',
'Remove file' => '移除文件',
'You can only upload a maximum of %s files' => '你最多允许上传 %s 个文件',
'You can\'t upload files of this type' => '不允许上传的文件类型',
'Server responded with %s code' => '服务端响应(Code:%s)',
'File is too big (%sMiB), Max filesize: %sMiB' => '当前上传(%sM),最大允许上传文件大小:%sM',
'Send verification code' => '发送验证码', 'Send verification code' => '发送验证码',
'Redirect now' => '立即跳转', 'Redirect now' => '立即跳转',
'Operation completed' => '操作成功!', 'Operation completed' => '操作成功!',

View File

@ -1,8 +1,3 @@
<?php <?php
return [ return [];
'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'Uploaded file format is limited' => '上传文件格式受限制',
'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Upload successful' => '上传成功',
];

View File

@ -55,7 +55,6 @@ return [
'New mobile' => '新手机号', 'New mobile' => '新手机号',
'Change password successful' => '修改密码成功', 'Change password successful' => '修改密码成功',
'Captcha is incorrect' => '验证码不正确', 'Captcha is incorrect' => '验证码不正确',
'Upload successful' => '上传成功',
'Logged in successful' => '登录成功', 'Logged in successful' => '登录成功',
'Logout successful' => '注销成功', 'Logout successful' => '注销成功',
'User center already closed' => '会员中心已经关闭', 'User center already closed' => '会员中心已经关闭',

View File

@ -1,9 +1,9 @@
<div id="content-container" class="container"> <div id="content-container" class="container">
<div class="user-section login-section"> <div class="user-section login-section">
<div class="logon-tab clearfix"> <a class="active">{:__('Sign in')}</a> <a href="{:url('user/register')}?url={$url|urlencode}">{:__('Sign up')}</a> </div> <div class="logon-tab clearfix"><a class="active">{:__('Sign in')}</a> <a href="{:url('user/register')}?url={$url|urlencode}">{:__('Sign up')}</a></div>
<div class="login-main"> <div class="login-main">
<form name="form" id="login-form" class="form-vertical" method="POST" action=""> <form name="form" id="login-form" class="form-vertical" method="POST" action="">
<input type="hidden" name="url" value="{$url}" /> <input type="hidden" name="url" value="{$url}"/>
{:token()} {:token()}
<div class="form-group"> <div class="form-group">
<label class="control-label" for="account">{:__('Account')}</label> <label class="control-label" for="account">{:__('Account')}</label>
@ -20,7 +20,11 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="controls"> <div class="controls">
<input type="checkbox" name="keeplogin" checked="checked" value="1"> {:__('Keep login')} <div class="checkbox inline">
<label>
<input type="checkbox" name="keeplogin" checked="checked" value="1"> {:__('Keep login')}
</label>
</div>
<div class="pull-right"><a href="javascript:;" class="btn-forgot">{:__('Forgot password')}</a></div> <div class="pull-right"><a href="javascript:;" class="btn-forgot">{:__('Forgot password')}</a></div>
</div> </div>
</div> </div>
@ -34,27 +38,27 @@
<script type="text/html" id="resetpwdtpl"> <script type="text/html" id="resetpwdtpl">
<form id="resetpwd-form" class="form-horizontal form-layer" method="POST" action="{:url('api/user/resetpwd')}"> <form id="resetpwd-form" class="form-horizontal form-layer" method="POST" action="{:url('api/user/resetpwd')}">
<div class="form-body"> <div class="form-body">
<input type="hidden" name="action" value="resetpwd" /> <input type="hidden" name="action" value="resetpwd"/>
<div class="form-group"> <div class="form-group">
<label class="control-label col-xs-12 col-sm-3">{:__('Type')}:</label> <label class="control-label col-xs-12 col-sm-3">{:__('Type')}:</label>
<div class="col-xs-12 col-sm-8"> <div class="col-xs-12 col-sm-8">
<div class="radio"> <div class="radio">
<label for="type-email"><input id="type-email" checked="checked" name="type" data-send-url="{:url('api/ems/send')}" data-check-url="{:url('api/validate/check_ems_correct')}" type="radio" value="email"> {:__('Reset password by email')}</label> <label for="type-email"><input id="type-email" checked="checked" name="type" data-send-url="{:url('api/ems/send')}" data-check-url="{:url('api/validate/check_ems_correct')}" type="radio" value="email"> {:__('Reset password by email')}</label>
<label for="type-mobile"><input id="type-mobile" name="type" type="radio" data-send-url="{:url('api/sms/send')}" data-check-url="{:url('api/validate/check_sms_correct')}" value="mobile"> {:__('Reset password by mobile')}</label> <label for="type-mobile"><input id="type-mobile" name="type" type="radio" data-send-url="{:url('api/sms/send')}" data-check-url="{:url('api/validate/check_sms_correct')}" value="mobile"> {:__('Reset password by mobile')}</label>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group" data-type="email"> <div class="form-group" data-type="email">
<label for="email" class="control-label col-xs-12 col-sm-3">{:__('Email')}:</label> <label for="email" class="control-label col-xs-12 col-sm-3">{:__('Email')}:</label>
<div class="col-xs-12 col-sm-8"> <div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="email" name="email" value="" data-rule="required(#type-email:checked);email;remote({:url('api/validate/check_email_exist')}, event=resetpwd, id={$user.id})" placeholder=""> <input type="text" class="form-control" id="email" name="email" value="" data-rule="required(#type-email:checked);email;remote({:url('api/validate/check_email_exist')}, event=resetpwd, id=0)" placeholder="">
<span class="msg-box"></span> <span class="msg-box"></span>
</div> </div>
</div> </div>
<div class="form-group hide" data-type="mobile"> <div class="form-group hide" data-type="mobile">
<label for="mobile" class="control-label col-xs-12 col-sm-3">{:__('Mobile')}:</label> <label for="mobile" class="control-label col-xs-12 col-sm-3">{:__('Mobile')}:</label>
<div class="col-xs-12 col-sm-8"> <div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="mobile" name="mobile" value="" data-rule="required(#type-mobile:checked);mobile;remote({:url('api/validate/check_mobile_exist')}, event=resetpwd, id={$user.id})" placeholder=""> <input type="text" class="form-control" id="mobile" name="mobile" value="" data-rule="required(#type-mobile:checked);mobile;remote({:url('api/validate/check_mobile_exist')}, event=resetpwd, id=0)" placeholder="">
<span class="msg-box"></span> <span class="msg-box"></span>
</div> </div>
</div> </div>
@ -62,7 +66,7 @@
<label for="captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label> <label for="captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
<div class="col-xs-12 col-sm-8"> <div class="col-xs-12 col-sm-8">
<div class="input-group"> <div class="input-group">
<input type="text" name="captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_ems_correct')}, event=resetpwd, email:#email)" /> <input type="text" name="captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_ems_correct')}, event=resetpwd, email:#email)"/>
<span class="input-group-btn" style="padding:0;border:none;"> <span class="input-group-btn" style="padding:0;border:none;">
<a href="javascript:;" class="btn btn-info btn-captcha" data-url="{:url('api/ems/send')}" data-type="email" data-event="resetpwd">{:__('Send verification code')}</a> <a href="javascript:;" class="btn btn-info btn-captcha" data-url="{:url('api/ems/send')}" data-type="email" data-event="resetpwd">{:__('Send verification code')}</a>
</span> </span>
@ -85,4 +89,4 @@
</div> </div>
</div> </div>
</form> </form>
</script> </script>

View File

@ -44,9 +44,9 @@
<label class="control-label col-xs-12 col-sm-2"></label> <label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-4"> <div class="col-xs-12 col-sm-4">
<div class="profile-avatar-container"> <div class="profile-avatar-container">
<img class="profile-user-img img-responsive img-circle plupload" src="{$user.avatar|cdnurl}" alt=""> <img class="profile-user-img img-responsive img-circle" src="{$user.avatar|cdnurl}" alt="">
<div class="profile-avatar-text img-circle">{:__('Click to edit')}</div> <div class="profile-avatar-text img-circle">{:__('Click to edit')}</div>
<button id="plupload-avatar" class="plupload" data-mimetype="png,jpg,jpeg,gif" data-input-id="c-avatar"><i class="fa fa-upload"></i> {:__('Upload')}</button> <button type="button" id="faupload-avatar" class="faupload" data-mimetype="png,jpg,jpeg,gif" data-input-id="c-avatar"><i class="fa fa-upload"></i> {:__('Upload')}</button>
</div> </div>
</div> </div>
</div> </div>
@ -199,4 +199,4 @@
margin-left:0; margin-left:0;
margin-right:0; margin-right:0;
} }
</style> </style>

View File

@ -12,7 +12,6 @@
"bootstrap-table": "fastadmin-bootstraptable#~1.11.3", "bootstrap-table": "fastadmin-bootstraptable#~1.11.3",
"jstree": "~3.3.2", "jstree": "~3.3.2",
"moment": "^2.20.1", "moment": "^2.20.1",
"plupload": "~2.2.0",
"toastr": "~2.1.3", "toastr": "~2.1.3",
"jcrop": "~2.0.4", "jcrop": "~2.0.4",
"eonasdan-bootstrap-datetimepicker": "~4.17.43", "eonasdan-bootstrap-datetimepicker": "~4.17.43",

View File

@ -19,12 +19,12 @@
"topthink/framework": "~5.0.24", "topthink/framework": "~5.0.24",
"topthink/think-captcha": "^1.0", "topthink/think-captcha": "^1.0",
"phpmailer/phpmailer": "~6.1.6", "phpmailer/phpmailer": "~6.1.6",
"karsonzhang/fastadmin-addons": "~1.1.11", "karsonzhang/fastadmin-addons": "~1.2.0",
"overtrue/pinyin": "~3.0", "overtrue/pinyin": "~3.0",
"phpoffice/phpspreadsheet": "^1.2", "phpoffice/phpspreadsheet": "^1.2",
"overtrue/wechat": "4.2.11", "overtrue/wechat": "4.2.11",
"league/oauth2-server": "8.0", "ext-json": "*",
"topthink/think-queue": "v1.1.6" "ext-curl": "*"
}, },
"config": { "config": {
"preferred-install": "dist" "preferred-install": "dist"

View File

@ -186,11 +186,12 @@
<div class="child collapse" id="验证接口"> <div class="child collapse" id="验证接口">
<a href="javascript:;" data-id="23" class="list-group-item">检测邮箱</a> <a href="javascript:;" data-id="23" class="list-group-item">检测邮箱</a>
<a href="javascript:;" data-id="24" class="list-group-item">检测用户名</a> <a href="javascript:;" data-id="24" class="list-group-item">检测用户名</a>
<a href="javascript:;" data-id="25" class="list-group-item">检测手机</a> <a href="javascript:;" data-id="25" class="list-group-item">检测昵称</a>
<a href="javascript:;" data-id="26" class="list-group-item">检测手机</a> <a href="javascript:;" data-id="26" class="list-group-item">检测手机</a>
<a href="javascript:;" data-id="27" class="list-group-item">检测邮箱</a> <a href="javascript:;" data-id="27" class="list-group-item">检测手机</a>
<a href="javascript:;" data-id="28" class="list-group-item">检测手机验证码</a> <a href="javascript:;" data-id="28" class="list-group-item">检测邮箱</a>
<a href="javascript:;" data-id="29" class="list-group-item">检测邮箱验证码</a> <a href="javascript:;" data-id="29" class="list-group-item">检测手机验证码</a>
<a href="javascript:;" data-id="30" class="list-group-item">检测邮箱验证码</a>
</div> </div>
</div> </div>
</div> </div>
@ -3137,7 +3138,7 @@
<div class="panel-heading" id="heading-25"> <div class="panel-heading" id="heading-25">
<h4 class="panel-title"> <h4 class="panel-title">
<span class="label label-success">GET</span> <span class="label label-success">GET</span>
<a data-toggle="collapse" data-parent="#accordion25" href="#collapseOne25"> 检测手机 <span class="text-muted">/api/validate/check_mobile_available</span></a> <a data-toggle="collapse" data-parent="#accordion25" href="#collapseOne25"> 检测昵称 <span class="text-muted">/api/validate/check_nickname_available</span></a>
</h4> </h4>
</div> </div>
<div id="collapseOne25" class="panel-collapse collapse"> <div id="collapseOne25" class="panel-collapse collapse">
@ -3155,7 +3156,7 @@
<div class="tab-pane active" id="info25"> <div class="tab-pane active" id="info25">
<div class="well"> <div class="well">
检测手机 </div> 检测昵称 </div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Headers</strong></div> <div class="panel-heading"><strong>Headers</strong></div>
<div class="panel-body"> <div class="panel-body">
@ -3176,10 +3177,10 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>mobile</td> <td>nickname</td>
<td>string</td> <td>string</td>
<td></td> <td></td>
<td>手机号</td> <td>昵称</td>
</tr> </tr>
<tr> <tr>
<td>id</td> <td>id</td>
@ -3204,10 +3205,10 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>参数</strong></div> <div class="panel-heading"><strong>参数</strong></div>
<div class="panel-body"> <div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_mobile_available" method="get" name="form25" id="form25"> <form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_nickname_available" method="get" name="form25" id="form25">
<div class="form-group"> <div class="form-group">
<label class="control-label" for="mobile">mobile</label> <label class="control-label" for="nickname">nickname</label>
<input type="string" class="form-control input-sm" id="mobile" required placeholder="手机号" name="mobile"> <input type="string" class="form-control input-sm" id="nickname" required placeholder="昵称" name="nickname">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label" for="id">id</label> <label class="control-label" for="id">id</label>
@ -3257,7 +3258,7 @@
<div class="panel-heading" id="heading-26"> <div class="panel-heading" id="heading-26">
<h4 class="panel-title"> <h4 class="panel-title">
<span class="label label-success">GET</span> <span class="label label-success">GET</span>
<a data-toggle="collapse" data-parent="#accordion26" href="#collapseOne26"> 检测手机 <span class="text-muted">/api/validate/check_mobile_exist</span></a> <a data-toggle="collapse" data-parent="#accordion26" href="#collapseOne26"> 检测手机 <span class="text-muted">/api/validate/check_mobile_available</span></a>
</h4> </h4>
</div> </div>
<div id="collapseOne26" class="panel-collapse collapse"> <div id="collapseOne26" class="panel-collapse collapse">
@ -3300,6 +3301,12 @@
<td>string</td> <td>string</td>
<td></td> <td></td>
<td>手机号</td> <td>手机号</td>
</tr>
<tr>
<td>id</td>
<td>string</td>
<td></td>
<td>排除会员ID</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -3318,10 +3325,14 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>参数</strong></div> <div class="panel-heading"><strong>参数</strong></div>
<div class="panel-body"> <div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_mobile_exist" method="get" name="form26" id="form26"> <form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_mobile_available" method="get" name="form26" id="form26">
<div class="form-group"> <div class="form-group">
<label class="control-label" for="mobile">mobile</label> <label class="control-label" for="mobile">mobile</label>
<input type="string" class="form-control input-sm" id="mobile" required placeholder="手机号" name="mobile"> <input type="string" class="form-control input-sm" id="mobile" required placeholder="手机号" name="mobile">
</div>
<div class="form-group">
<label class="control-label" for="id">id</label>
<input type="string" class="form-control input-sm" id="id" required placeholder="排除会员ID" name="id">
</div> </div>
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-success send" rel="26">提交</button> <button type="submit" class="btn btn-success send" rel="26">提交</button>
@ -3367,7 +3378,7 @@
<div class="panel-heading" id="heading-27"> <div class="panel-heading" id="heading-27">
<h4 class="panel-title"> <h4 class="panel-title">
<span class="label label-success">GET</span> <span class="label label-success">GET</span>
<a data-toggle="collapse" data-parent="#accordion27" href="#collapseOne27"> 检测邮箱 <span class="text-muted">/api/validate/check_email_exist</span></a> <a data-toggle="collapse" data-parent="#accordion27" href="#collapseOne27"> 检测手机 <span class="text-muted">/api/validate/check_mobile_exist</span></a>
</h4> </h4>
</div> </div>
<div id="collapseOne27" class="panel-collapse collapse"> <div id="collapseOne27" class="panel-collapse collapse">
@ -3385,7 +3396,7 @@
<div class="tab-pane active" id="info27"> <div class="tab-pane active" id="info27">
<div class="well"> <div class="well">
检测邮箱 </div> 检测手机 </div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Headers</strong></div> <div class="panel-heading"><strong>Headers</strong></div>
<div class="panel-body"> <div class="panel-body">
@ -3409,7 +3420,7 @@
<td>mobile</td> <td>mobile</td>
<td>string</td> <td>string</td>
<td></td> <td></td>
<td>邮箱</td> <td>手机号</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -3428,10 +3439,10 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>参数</strong></div> <div class="panel-heading"><strong>参数</strong></div>
<div class="panel-body"> <div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_email_exist" method="get" name="form27" id="form27"> <form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_mobile_exist" method="get" name="form27" id="form27">
<div class="form-group"> <div class="form-group">
<label class="control-label" for="mobile">mobile</label> <label class="control-label" for="mobile">mobile</label>
<input type="string" class="form-control input-sm" id="mobile" required placeholder="邮箱" name="mobile"> <input type="string" class="form-control input-sm" id="mobile" required placeholder="手机号" name="mobile">
</div> </div>
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-success send" rel="27">提交</button> <button type="submit" class="btn btn-success send" rel="27">提交</button>
@ -3477,7 +3488,7 @@
<div class="panel-heading" id="heading-28"> <div class="panel-heading" id="heading-28">
<h4 class="panel-title"> <h4 class="panel-title">
<span class="label label-success">GET</span> <span class="label label-success">GET</span>
<a data-toggle="collapse" data-parent="#accordion28" href="#collapseOne28"> 检测手机验证码 <span class="text-muted">/api/validate/check_sms_correct</span></a> <a data-toggle="collapse" data-parent="#accordion28" href="#collapseOne28"> 检测邮箱 <span class="text-muted">/api/validate/check_email_exist</span></a>
</h4> </h4>
</div> </div>
<div id="collapseOne28" class="panel-collapse collapse"> <div id="collapseOne28" class="panel-collapse collapse">
@ -3494,6 +3505,116 @@
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="info28"> <div class="tab-pane active" id="info28">
<div class="well">
检测邮箱 </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Headers</strong></div>
<div class="panel-body">
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>参数</strong></div>
<div class="panel-body">
<table class="table table-hover">
<thead>
<tr>
<th>名称</th>
<th>类型</th>
<th>必选</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>mobile</td>
<td>string</td>
<td></td>
<td>邮箱</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>正文</strong></div>
<div class="panel-body">
</div>
</div>
</div><!-- #info -->
<div class="tab-pane" id="sandbox28">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading"><strong>参数</strong></div>
<div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_email_exist" method="get" name="form28" id="form28">
<div class="form-group">
<label class="control-label" for="mobile">mobile</label>
<input type="string" class="form-control input-sm" id="mobile" required placeholder="邮箱" name="mobile">
</div>
<div class="form-group">
<button type="submit" class="btn btn-success send" rel="28">提交</button>
<button type="reset" class="btn btn-info" rel="28">重置</button>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>响应输出</strong></div>
<div class="panel-body">
<div class="row">
<div class="col-md-12" style="overflow-x:auto">
<pre id="response_headers28"></pre>
<pre id="response28"></pre>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>返回参数</strong></div>
<div class="panel-body">
</div>
</div>
</div>
</div>
</div><!-- #sandbox -->
<div class="tab-pane" id="sample28">
<div class="row">
<div class="col-md-12">
<pre id="sample_response28"></pre>
</div>
</div>
</div><!-- #sample -->
</div><!-- .tab-content -->
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" id="heading-29">
<h4 class="panel-title">
<span class="label label-success">GET</span>
<a data-toggle="collapse" data-parent="#accordion29" href="#collapseOne29"> 检测手机验证码 <span class="text-muted">/api/validate/check_sms_correct</span></a>
</h4>
</div>
<div id="collapseOne29" class="panel-collapse collapse">
<div class="panel-body">
<!-- Nav tabs -->
<ul class="nav nav-tabs" id="doctab29">
<li class="active"><a href="#info29" data-toggle="tab">基础信息</a></li>
<li><a href="#sandbox29" data-toggle="tab">在线测试</a></li>
<li><a href="#sample29" data-toggle="tab">返回示例</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane active" id="info29">
<div class="well"> <div class="well">
检测手机验证码 </div> 检测手机验证码 </div>
<div class="panel panel-default"> <div class="panel panel-default">
@ -3544,13 +3665,13 @@
</div> </div>
</div><!-- #info --> </div><!-- #info -->
<div class="tab-pane" id="sandbox28"> <div class="tab-pane" id="sandbox29">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>参数</strong></div> <div class="panel-heading"><strong>参数</strong></div>
<div class="panel-body"> <div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_sms_correct" method="get" name="form28" id="form28"> <form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_sms_correct" method="get" name="form29" id="form29">
<div class="form-group"> <div class="form-group">
<label class="control-label" for="mobile">mobile</label> <label class="control-label" for="mobile">mobile</label>
<input type="string" class="form-control input-sm" id="mobile" required placeholder="手机号" name="mobile"> <input type="string" class="form-control input-sm" id="mobile" required placeholder="手机号" name="mobile">
@ -3564,8 +3685,8 @@
<input type="string" class="form-control input-sm" id="event" required placeholder="事件" name="event"> <input type="string" class="form-control input-sm" id="event" required placeholder="事件" name="event">
</div> </div>
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-success send" rel="28">提交</button> <button type="submit" class="btn btn-success send" rel="29">提交</button>
<button type="reset" class="btn btn-info" rel="28">重置</button> <button type="reset" class="btn btn-info" rel="29">重置</button>
</div> </div>
</form> </form>
</div> </div>
@ -3575,8 +3696,8 @@
<div class="panel-body"> <div class="panel-body">
<div class="row"> <div class="row">
<div class="col-md-12" style="overflow-x:auto"> <div class="col-md-12" style="overflow-x:auto">
<pre id="response_headers28"></pre> <pre id="response_headers29"></pre>
<pre id="response28"></pre> <pre id="response29"></pre>
</div> </div>
</div> </div>
</div> </div>
@ -3591,10 +3712,10 @@
</div> </div>
</div><!-- #sandbox --> </div><!-- #sandbox -->
<div class="tab-pane" id="sample28"> <div class="tab-pane" id="sample29">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<pre id="sample_response28"></pre> <pre id="sample_response29"></pre>
</div> </div>
</div> </div>
</div><!-- #sample --> </div><!-- #sample -->
@ -3604,26 +3725,26 @@
</div> </div>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading" id="heading-29"> <div class="panel-heading" id="heading-30">
<h4 class="panel-title"> <h4 class="panel-title">
<span class="label label-success">GET</span> <span class="label label-success">GET</span>
<a data-toggle="collapse" data-parent="#accordion29" href="#collapseOne29"> 检测邮箱验证码 <span class="text-muted">/api/validate/check_ems_correct</span></a> <a data-toggle="collapse" data-parent="#accordion30" href="#collapseOne30"> 检测邮箱验证码 <span class="text-muted">/api/validate/check_ems_correct</span></a>
</h4> </h4>
</div> </div>
<div id="collapseOne29" class="panel-collapse collapse"> <div id="collapseOne30" class="panel-collapse collapse">
<div class="panel-body"> <div class="panel-body">
<!-- Nav tabs --> <!-- Nav tabs -->
<ul class="nav nav-tabs" id="doctab29"> <ul class="nav nav-tabs" id="doctab30">
<li class="active"><a href="#info29" data-toggle="tab">基础信息</a></li> <li class="active"><a href="#info30" data-toggle="tab">基础信息</a></li>
<li><a href="#sandbox29" data-toggle="tab">在线测试</a></li> <li><a href="#sandbox30" data-toggle="tab">在线测试</a></li>
<li><a href="#sample29" data-toggle="tab">返回示例</a></li> <li><a href="#sample30" data-toggle="tab">返回示例</a></li>
</ul> </ul>
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="info29"> <div class="tab-pane active" id="info30">
<div class="well"> <div class="well">
检测邮箱验证码 </div> 检测邮箱验证码 </div>
<div class="panel panel-default"> <div class="panel panel-default">
@ -3674,13 +3795,13 @@
</div> </div>
</div><!-- #info --> </div><!-- #info -->
<div class="tab-pane" id="sandbox29"> <div class="tab-pane" id="sandbox30">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>参数</strong></div> <div class="panel-heading"><strong>参数</strong></div>
<div class="panel-body"> <div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_ems_correct" method="get" name="form29" id="form29"> <form enctype="application/x-www-form-urlencoded" role="form" action="/api/validate/check_ems_correct" method="get" name="form30" id="form30">
<div class="form-group"> <div class="form-group">
<label class="control-label" for="email">email</label> <label class="control-label" for="email">email</label>
<input type="string" class="form-control input-sm" id="email" required placeholder="邮箱" name="email"> <input type="string" class="form-control input-sm" id="email" required placeholder="邮箱" name="email">
@ -3694,8 +3815,8 @@
<input type="string" class="form-control input-sm" id="event" required placeholder="事件" name="event"> <input type="string" class="form-control input-sm" id="event" required placeholder="事件" name="event">
</div> </div>
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-success send" rel="29">提交</button> <button type="submit" class="btn btn-success send" rel="30">提交</button>
<button type="reset" class="btn btn-info" rel="29">重置</button> <button type="reset" class="btn btn-info" rel="30">重置</button>
</div> </div>
</form> </form>
</div> </div>
@ -3705,8 +3826,8 @@
<div class="panel-body"> <div class="panel-body">
<div class="row"> <div class="row">
<div class="col-md-12" style="overflow-x:auto"> <div class="col-md-12" style="overflow-x:auto">
<pre id="response_headers29"></pre> <pre id="response_headers30"></pre>
<pre id="response29"></pre> <pre id="response30"></pre>
</div> </div>
</div> </div>
</div> </div>
@ -3721,10 +3842,10 @@
</div> </div>
</div><!-- #sandbox --> </div><!-- #sandbox -->
<div class="tab-pane" id="sample29"> <div class="tab-pane" id="sample30">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<pre id="sample_response29"></pre> <pre id="sample_response30"></pre>
</div> </div>
</div> </div>
</div><!-- #sample --> </div><!-- #sample -->
@ -3739,7 +3860,7 @@
<div class="row mt0 footer"> <div class="row mt0 footer">
<div class="col-md-6" align="left"> <div class="col-md-6" align="left">
Generated on 2020-02-16 16:01:35 </div> Generated on 2020-08-12 21:00:33 </div>
<div class="col-md-6" align="right"> <div class="col-md-6" align="right">
<a href="./" target="_blank">我的网站</a> <a href="./" target="_blank">我的网站</a>
</div> </div>

View File

@ -119,6 +119,20 @@ table.table-template {
right: 0; right: 0;
top: 0; top: 0;
} }
.sp_container .sp_element_box {
overflow: unset;
}
.sp_container .sp_element_box > li.input_box {
position: unset;
}
.sp_container .sp_element_box .msg-box {
right: -24px;
}
@media (max-width: 767px) {
.sp_container .sp_element_box .msg-box {
left: inherit;
}
}
.toast-top-right-index { .toast-top-right-index {
top: 62px; top: 62px;
right: 12px; right: 12px;
@ -133,6 +147,12 @@ table.table-template {
margin-bottom: -5px; margin-bottom: -5px;
padding: 10px 20px; padding: 10px 20px;
} }
select.bs-select-hidden,
select.selectpicker {
display: inherit !important;
max-height: 31px;
overflow: hidden;
}
.img-center { .img-center {
margin: 0 auto; margin: 0 auto;
display: inline; display: inline;
@ -599,6 +619,7 @@ form.form-horizontal .control-label {
.nice-validator select, .nice-validator select,
.nice-validator textarea, .nice-validator textarea,
.nice-validator [contenteditable] { .nice-validator [contenteditable] {
vertical-align: top;
display: inline-block; display: inline-block;
*display: inline; *display: inline;
*zoom: 1; *zoom: 1;
@ -608,23 +629,29 @@ form.form-horizontal .control-label {
display: inherit; display: inherit;
} }
/*预览区域*/ /*预览区域*/
.plupload-preview { .plupload-preview,
.faupload-preview {
padding: 0 10px; padding: 0 10px;
margin-bottom: 0; margin-bottom: 0;
} }
.plupload-preview li { .plupload-preview li,
.faupload-preview li {
margin-top: 15px; margin-top: 15px;
} }
.plupload-preview .thumbnail { .plupload-preview .thumbnail,
.faupload-preview .thumbnail {
margin-bottom: 10px; margin-bottom: 10px;
} }
.plupload-preview a { .plupload-preview a,
.faupload-preview a {
display: block; display: block;
} }
.plupload-preview a:first-child { .plupload-preview a:first-child,
.faupload-preview a:first-child {
height: 90px; height: 90px;
} }
.plupload-preview a img { .plupload-preview a img,
.faupload-preview a img {
height: 80px; height: 80px;
object-fit: cover; object-fit: cover;
} }
@ -877,7 +904,7 @@ table.table-nowrap thead > tr > th {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.n-bootstrap .input-group > .n-right { .input-group > .msg-box.n-right {
position: absolute; position: absolute;
} }
@media (min-width: 564px) { @media (min-width: 564px) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -943,8 +943,7 @@ a:focus {
*/ */
/*Dropdowns in general*/ /*Dropdowns in general*/
.dropdown-menu { .dropdown-menu {
box-shadow: none; border: none;
border-color: #eee;
} }
.dropdown-menu > li > a { .dropdown-menu > li > a {
/*color: #777;*/ /*color: #777;*/
@ -5829,4 +5828,4 @@ fieldset[disabled] .btn-yahoo.active {
white-space: normal !important; white-space: normal !important;
} }
} }
/*# sourceMappingURL=../css/fastadmin.css.map */ /*# sourceMappingURL=fastadmin.css.map */

View File

@ -21,7 +21,7 @@ body {
.navbar { .navbar {
border: none; border: none;
} }
.navbar-nav li > a { .navbar-nav > li > a {
font-size: 14px; font-size: 14px;
} }
.toast-top-center { .toast-top-center {
@ -37,23 +37,29 @@ body {
display: inherit; display: inherit;
} }
/*预览区域*/ /*预览区域*/
.plupload-preview { .plupload-preview,
.faupload-preview {
padding: 0 10px; padding: 0 10px;
margin-bottom: 0; margin-bottom: 0;
} }
.plupload-preview li { .plupload-preview li,
.faupload-preview li {
margin-top: 10px; margin-top: 10px;
} }
.plupload-preview .thumbnail { .plupload-preview .thumbnail,
.faupload-preview .thumbnail {
margin-bottom: 10px; margin-bottom: 10px;
} }
.plupload-preview a { .plupload-preview a,
.faupload-preview a {
display: block; display: block;
} }
.plupload-preview a:first-child { .plupload-preview a:first-child,
.faupload-preview a:first-child {
height: 90px; height: 90px;
} }
.plupload-preview a img { .plupload-preview a img,
.faupload-preview a img {
height: 80px; height: 80px;
object-fit: cover; object-fit: cover;
} }
@ -70,6 +76,18 @@ body {
padding: 12px 25px; padding: 12px 25px;
text-align: center; text-align: center;
} }
.input-group > .msg-box.n-right {
position: absolute;
}
/*修复radio和checkbox样式对齐*/
.radio > label,
.checkbox > label {
margin-right: 10px;
}
.radio > label > input,
.checkbox > label > input {
margin: 2px 0 0;
}
#header-navbar li.dropdown ul.dropdown-menu { #header-navbar li.dropdown ul.dropdown-menu {
min-width: 94px; min-width: 94px;
} }

File diff suppressed because one or more lines are too long

View File

@ -164,7 +164,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
// 离线安装 // 离线安装
require(['upload'], function (Upload) { require(['upload'], function (Upload) {
Upload.api.plupload("#plupload-addon", function (data, ret) { Upload.api.upload("#faupload-addon", function (data, ret) {
Config['addons'][data.addon.name] = data.addon; Config['addons'][data.addon.name] = data.addon;
Toastr.success(ret.msg); Toastr.success(ret.msg);
operate(data.addon.name, 'enable', false); operate(data.addon.name, 'enable', false);
@ -585,4 +585,4 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
} }
}; };
return Controller; return Controller;
}); });

View File

@ -27,11 +27,8 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
{field: 'admin_id', title: __('Admin_id'), visible: false, addClass: "selectpage", extend: "data-source='auth/admin/index' data-field='nickname'"}, {field: 'admin_id', title: __('Admin_id'), visible: false, addClass: "selectpage", extend: "data-source='auth/admin/index' data-field='nickname'"},
{field: 'user_id', title: __('User_id'), visible: false, addClass: "selectpage", extend: "data-source='user/user/index' data-field='nickname'"}, {field: 'user_id', title: __('User_id'), visible: false, addClass: "selectpage", extend: "data-source='user/user/index' data-field='nickname'"},
{field: 'url', title: __('Preview'), formatter: Controller.api.formatter.thumb, operate: false}, {field: 'url', title: __('Preview'), formatter: Controller.api.formatter.thumb, operate: false},
{field: 'url', title: __('Url'), formatter: Controller.api.formatter.url}, {field: 'url', title: __('Url'), formatter: Controller.api.formatter.url, visible: false},
{field: 'imagewidth', title: __('Imagewidth'), sortable: true}, {field: 'filename', title: __('Filename'), formatter: Table.api.formatter.search, operate: 'like'},
{field: 'imageheight', title: __('Imageheight'), sortable: true},
{field: 'imagetype', title: __('Imagetype'), formatter: Table.api.formatter.search},
{field: 'storage', title: __('Storage'), formatter: Table.api.formatter.search},
{ {
field: 'filesize', title: __('Filesize'), operate: 'BETWEEN', sortable: true, formatter: function (value, row, index) { field: 'filesize', title: __('Filesize'), operate: 'BETWEEN', sortable: true, formatter: function (value, row, index) {
var size = parseFloat(value); var size = parseFloat(value);
@ -39,6 +36,10 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
return (size / Math.pow(1024, i)).toFixed(i < 2 ? 0 : 2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i]; return (size / Math.pow(1024, i)).toFixed(i < 2 ? 0 : 2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
} }
}, },
{field: 'imagewidth', title: __('Imagewidth'), sortable: true},
{field: 'imageheight', title: __('Imageheight'), sortable: true},
{field: 'imagetype', title: __('Imagetype'), formatter: Table.api.formatter.search, operate: 'like'},
{field: 'storage', title: __('Storage'), formatter: Table.api.formatter.search, operate: 'like'},
{field: 'mimetype', title: __('Mimetype'), formatter: Table.api.formatter.search}, {field: 'mimetype', title: __('Mimetype'), formatter: Table.api.formatter.search},
{ {
field: 'createtime', field: 'createtime',
@ -105,6 +106,7 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
{field: 'admin_id', title: __('Admin_id'), formatter: Table.api.formatter.search, visible: false}, {field: 'admin_id', title: __('Admin_id'), formatter: Table.api.formatter.search, visible: false},
{field: 'user_id', title: __('User_id'), formatter: Table.api.formatter.search, visible: false}, {field: 'user_id', title: __('User_id'), formatter: Table.api.formatter.search, visible: false},
{field: 'url', title: __('Preview'), formatter: Controller.api.formatter.thumb, operate: false}, {field: 'url', title: __('Preview'), formatter: Controller.api.formatter.thumb, operate: false},
{field: 'filename', title: __('Filename'), formatter: Table.api.formatter.search, operate: 'like'},
{field: 'imagewidth', title: __('Imagewidth'), operate: false}, {field: 'imagewidth', title: __('Imagewidth'), operate: false},
{field: 'imageheight', title: __('Imageheight'), operate: false}, {field: 'imageheight', title: __('Imageheight'), operate: false},
{ {
@ -143,7 +145,7 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
// 为表格绑定事件 // 为表格绑定事件
Table.api.bindevent(table); Table.api.bindevent(table);
require(['upload'], function (Upload) { require(['upload'], function (Upload) {
Upload.api.plupload($("#toolbar .plupload"), function () { Upload.api.faupload($("#toolbar .faupload"), function () {
$(".btn-refresh").trigger("click"); $(".btn-refresh").trigger("click");
}); });
}); });
@ -175,4 +177,4 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
}; };
return Controller; return Controller;
}); });

View File

@ -2,47 +2,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
var Controller = { var Controller = {
index: function () { index: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
index_url: 'general/config/index',
add_url: 'general/config/add',
edit_url: 'general/config/edit',
del_url: 'general/config/del',
multi_url: 'general/config/multi',
table: 'config',
}
});
var table = $("#table");
// 初始化表格
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
columns: [
[
{field: 'state', checkbox: true},
{field: 'id', title: __('Id')},
{field: 'name', title: __('Name')},
{field: 'intro', title: __('Intro')},
{field: 'group', title: __('Group')},
{field: 'type', title: __('Type')},
{
field: 'operate',
title: __('Operate'),
table: table,
events: Table.api.events.operate,
formatter: Table.api.formatter.operate
}
]
]
});
// 为表格绑定事件
Table.api.bindevent(table);
$("form.edit-form").data("validator-options", { $("form.edit-form").data("validator-options", {
display: function (elem) { display: function (elem) {
return $(elem).closest('tr').find("td:first").text(); return $(elem).closest('tr').find("td:first").text();
@ -131,4 +90,4 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
} }
}; };
return Controller; return Controller;
}); });

View File

@ -38,12 +38,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'upload'], function (
Table.api.bindevent(table);//当内容渲染完成后 Table.api.bindevent(table);//当内容渲染完成后
// 给上传按钮添加上传成功事件 // 给上传按钮添加上传成功事件
$("#plupload-avatar").data("upload-success", function (data) { $("#faupload-avatar").data("upload-success", function (data) {
var url = Backend.api.cdnurl(data.url); var url = Backend.api.cdnurl(data.url);
$(".profile-user-img").prop("src", url); $(".profile-user-img").prop("src", url);
Toastr.success("上传成功!"); Toastr.success("上传成功!");
}); });
// 给表单绑定事件 // 给表单绑定事件
Form.api.bindevent($("#update-form"), function () { Form.api.bindevent($("#update-form"), function () {
$("input[name='row[password]']").val(''); $("input[name='row[password]']").val('');
@ -54,4 +54,4 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'upload'], function (
}, },
}; };
return Controller; return Controller;
}); });

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -74,7 +74,7 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
}, },
profile: function () { profile: function () {
// 给上传按钮添加上传成功事件 // 给上传按钮添加上传成功事件
$("#plupload-avatar").data("upload-success", function (data) { $("#faupload-avatar").data("upload-success", function (data) {
var url = Fast.api.cdnurl(data.url); var url = Fast.api.cdnurl(data.url);
$(".profile-user-img").prop("src", url); $(".profile-user-img").prop("src", url);
Toastr.success(__('Upload successful')); Toastr.success(__('Upload successful'));
@ -101,4 +101,4 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
} }
}; };
return Controller; return Controller;
}); });

View File

@ -4,8 +4,7 @@ require.config({
name: 'moment', name: 'moment',
location: '../libs/moment', location: '../libs/moment',
main: 'moment' main: 'moment'
} }],
],
//在打包压缩时将会把include中的模块合并到主文件中 //在打包压缩时将会把include中的模块合并到主文件中
include: ['css', 'layer', 'toastr', 'fast', 'backend', 'backend-init', 'table', 'form', 'dragsort', 'drag', 'drop', 'addtabs', 'selectpage'], include: ['css', 'layer', 'toastr', 'fast', 'backend', 'backend-init', 'table', 'form', 'dragsort', 'drag', 'drop', 'addtabs', 'selectpage'],
paths: { paths: {
@ -16,6 +15,7 @@ require.config({
'validator': 'require-validator', 'validator': 'require-validator',
'drag': 'jquery.drag.min', 'drag': 'jquery.drag.min',
'drop': 'jquery.drop.min', 'drop': 'jquery.drop.min',
'dropzone': 'dropzone.min',
'echarts': 'echarts.min', 'echarts': 'echarts.min',
'echarts-theme': 'echarts-theme', 'echarts-theme': 'echarts-theme',
'adminlte': 'adminlte', 'adminlte': 'adminlte',
@ -42,7 +42,6 @@ require.config({
'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll', 'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll',
'validator-core': '../libs/nice-validator/dist/jquery.validator', 'validator-core': '../libs/nice-validator/dist/jquery.validator',
'validator-lang': '../libs/nice-validator/dist/local/zh-CN', 'validator-lang': '../libs/nice-validator/dist/local/zh-CN',
'plupload': '../libs/plupload/js/plupload.min',
'toastr': '../libs/toastr/toastr', 'toastr': '../libs/toastr/toastr',
'jstree': '../libs/jstree/dist/jstree.min', 'jstree': '../libs/jstree/dist/jstree.min',
'layer': '../libs/fastadmin-layer/dist/layer', 'layer': '../libs/fastadmin-layer/dist/layer',
@ -114,11 +113,7 @@ require.config({
// 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css',], // 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css',],
'bootstrap-select-lang': ['bootstrap-select'], 'bootstrap-select-lang': ['bootstrap-select'],
// 'toastr': ['css!../libs/toastr/toastr.min.css'], // 'toastr': ['css!../libs/toastr/toastr.min.css'],
'jstree': ['css!../libs/jstree/dist/themes/default/style.css',], 'jstree': ['css!../libs/jstree/dist/themes/default/style.css'],
'plupload': {
deps: ['../libs/plupload/js/moxie.min'],
exports: "plupload"
},
// 'layer': ['css!../libs/fastadmin-layer/dist/theme/default/layer.css'], // 'layer': ['css!../libs/fastadmin-layer/dist/theme/default/layer.css'],
// 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'], // 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'],
'validator-lang': ['validator-core'], 'validator-lang': ['validator-core'],

File diff suppressed because one or more lines are too long

View File

@ -221,10 +221,22 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
}); });
} }
}, },
/**
* 绑定上传事件
* @param form
* @deprecated Use faupload instead.
*/
plupload: function (form) { plupload: function (form) {
//绑定plupload上传元素事件 Form.events.faupload(form);
if ($(".plupload", form).size() > 0) { },
Upload.api.plupload($(".plupload", form)); /**
* 绑定上传事件
* @param form
*/
faupload: function (form) {
//绑定上传元素事件
if ($(".plupload,.faupload", form).size() > 0) {
Upload.api.upload($(".plupload,.faupload", form));
} }
}, },
faselect: function (form) { faselect: function (form) {
@ -495,7 +507,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
events.datetimepicker(form); events.datetimepicker(form);
events.plupload(form); events.faupload(form);
events.faselect(form); events.faselect(form);

View File

@ -1,11 +1,10 @@
require.config({ require.config({
urlArgs: "v=" + requirejs.s.contexts._.config.config.site.version, urlArgs: "v=" + requirejs.s.contexts._.config.config.site.version,
packages: [{ packages: [{
name: 'moment', name: 'moment',
location: '../libs/moment', location: '../libs/moment',
main: 'moment' main: 'moment'
} }],
],
//在打包压缩时将会把include中的模块合并到主文件中 //在打包压缩时将会把include中的模块合并到主文件中
include: ['css', 'layer', 'toastr', 'fast', 'frontend', 'frontend-init'], include: ['css', 'layer', 'toastr', 'fast', 'frontend', 'frontend-init'],
paths: { paths: {
@ -16,6 +15,7 @@ require.config({
'validator': 'require-validator', 'validator': 'require-validator',
'drag': 'jquery.drag.min', 'drag': 'jquery.drag.min',
'drop': 'jquery.drop.min', 'drop': 'jquery.drop.min',
'dropzone': 'dropzone.min',
'echarts': 'echarts.min', 'echarts': 'echarts.min',
'echarts-theme': 'echarts-theme', 'echarts-theme': 'echarts-theme',
'adminlte': 'adminlte', 'adminlte': 'adminlte',
@ -41,7 +41,6 @@ require.config({
'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll', 'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll',
'validator-core': '../libs/nice-validator/dist/jquery.validator', 'validator-core': '../libs/nice-validator/dist/jquery.validator',
'validator-lang': '../libs/nice-validator/dist/local/zh-CN', 'validator-lang': '../libs/nice-validator/dist/local/zh-CN',
'plupload': '../libs/plupload/js/plupload.min',
'toastr': '../libs/toastr/toastr', 'toastr': '../libs/toastr/toastr',
'jstree': '../libs/jstree/dist/jstree.min', 'jstree': '../libs/jstree/dist/jstree.min',
'layer': '../libs/fastadmin-layer/dist/layer', 'layer': '../libs/fastadmin-layer/dist/layer',
@ -113,11 +112,7 @@ require.config({
// 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ], // 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ],
'bootstrap-select-lang': ['bootstrap-select'], 'bootstrap-select-lang': ['bootstrap-select'],
// 'toastr': ['css!../libs/toastr/toastr.min.css'], // 'toastr': ['css!../libs/toastr/toastr.min.css'],
'jstree': ['css!../libs/jstree/dist/themes/default/style.css', ], 'jstree': ['css!../libs/jstree/dist/themes/default/style.css'],
'plupload': {
deps: ['../libs/plupload/js/moxie.min'],
exports: "plupload"
},
// 'layer': ['css!../libs/fastadmin-layer/dist/theme/default/layer.css'], // 'layer': ['css!../libs/fastadmin-layer/dist/theme/default/layer.css'],
// 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'], // 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'],
'validator-lang': ['validator-core'], 'validator-lang': ['validator-core'],

View File

@ -14,11 +14,10 @@ define("bootstrap", ["jquery"], function(){});
require.config({ require.config({
urlArgs: "v=" + requirejs.s.contexts._.config.config.site.version, urlArgs: "v=" + requirejs.s.contexts._.config.config.site.version,
packages: [{ packages: [{
name: 'moment', name: 'moment',
location: '../libs/moment', location: '../libs/moment',
main: 'moment' main: 'moment'
} }],
],
//在打包压缩时将会把include中的模块合并到主文件中 //在打包压缩时将会把include中的模块合并到主文件中
include: ['css', 'layer', 'toastr', 'fast', 'frontend', 'frontend-init'], include: ['css', 'layer', 'toastr', 'fast', 'frontend', 'frontend-init'],
paths: { paths: {
@ -29,6 +28,7 @@ require.config({
'validator': 'require-validator', 'validator': 'require-validator',
'drag': 'jquery.drag.min', 'drag': 'jquery.drag.min',
'drop': 'jquery.drop.min', 'drop': 'jquery.drop.min',
'dropzone': 'dropzone.min',
'echarts': 'echarts.min', 'echarts': 'echarts.min',
'echarts-theme': 'echarts-theme', 'echarts-theme': 'echarts-theme',
'adminlte': 'adminlte', 'adminlte': 'adminlte',
@ -54,7 +54,6 @@ require.config({
'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll', 'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll',
'validator-core': '../libs/nice-validator/dist/jquery.validator', 'validator-core': '../libs/nice-validator/dist/jquery.validator',
'validator-lang': '../libs/nice-validator/dist/local/zh-CN', 'validator-lang': '../libs/nice-validator/dist/local/zh-CN',
'plupload': '../libs/plupload/js/plupload.min',
'toastr': '../libs/toastr/toastr', 'toastr': '../libs/toastr/toastr',
'jstree': '../libs/jstree/dist/jstree.min', 'jstree': '../libs/jstree/dist/jstree.min',
'layer': '../libs/fastadmin-layer/dist/layer', 'layer': '../libs/fastadmin-layer/dist/layer',
@ -126,11 +125,7 @@ require.config({
// 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ], // 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ],
'bootstrap-select-lang': ['bootstrap-select'], 'bootstrap-select-lang': ['bootstrap-select'],
// 'toastr': ['css!../libs/toastr/toastr.min.css'], // 'toastr': ['css!../libs/toastr/toastr.min.css'],
'jstree': ['css!../libs/jstree/dist/themes/default/style.css', ], 'jstree': ['css!../libs/jstree/dist/themes/default/style.css'],
'plupload': {
deps: ['../libs/plupload/js/moxie.min'],
exports: "plupload"
},
// 'layer': ['css!../libs/fastadmin-layer/dist/theme/default/layer.css'], // 'layer': ['css!../libs/fastadmin-layer/dist/theme/default/layer.css'],
// 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'], // 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'],
'validator-lang': ['validator-core'], 'validator-lang': ['validator-core'],

View File

@ -57,7 +57,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
valign: 'middle', valign: 'middle',
}, },
config: { config: {
firsttd: 'tbody tr td:first-child:not(:has(div.card-views))', firsttd: 'tbody>tr>td.bs-checkbox',
toolbar: '.toolbar', toolbar: '.toolbar',
refreshbtn: '.btn-refresh', refreshbtn: '.btn-refresh',
addbtn: '.btn-add', addbtn: '.btn-add',
@ -169,10 +169,11 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
table.on('post-body.bs.table', function (e, settings, json, xhr) { table.on('post-body.bs.table', function (e, settings, json, xhr) {
$(Table.config.refreshbtn, toolbar).find(".fa").removeClass("fa-spin"); $(Table.config.refreshbtn, toolbar).find(".fa").removeClass("fa-spin");
$(Table.config.disabledbtn, toolbar).toggleClass('disabled', true); $(Table.config.disabledbtn, toolbar).toggleClass('disabled', true);
if ($(Table.config.firsttd, table).find("input[type='checkbox'][data-index]").size() > 0) { if ($(Table.config.firsttd + ":first", table).find("input[type='checkbox'][data-index]").size() > 0) {
// 拽选择,需要重新绑定事件 // 拽选择,需要重新绑定事件
require(['drag', 'drop'], function () { require(['drag', 'drop'], function () {
$(Table.config.firsttd, table).drag("start", function (ev, dd) { var firsttd = $(Table.config.firsttd, table);
firsttd.drag("start", function (ev, dd) {
return $('<div class="selection" />').css('opacity', .65).appendTo(document.body); return $('<div class="selection" />').css('opacity', .65).appendTo(document.body);
}).drag(function (ev, dd) { }).drag(function (ev, dd) {
$(dd.proxy).css({ $(dd.proxy).css({
@ -184,7 +185,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
}).drag("end", function (ev, dd) { }).drag("end", function (ev, dd) {
$(dd.proxy).remove(); $(dd.proxy).remove();
}); });
$(Table.config.firsttd, table).drop("start", function () { firsttd.drop("start", function () {
Table.api.toggleattr(this); Table.api.toggleattr(this);
}).drop(function () { }).drop(function () {
Table.api.toggleattr(this); Table.api.toggleattr(this);
@ -231,7 +232,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
// 导入按钮事件 // 导入按钮事件
if ($(Table.config.importbtn, toolbar).size() > 0) { if ($(Table.config.importbtn, toolbar).size() > 0) {
require(['upload'], function (Upload) { require(['upload'], function (Upload) {
Upload.api.plupload($(Table.config.importbtn, toolbar), function (data, ret) { Upload.api.upload($(Table.config.importbtn, toolbar), function (data, ret) {
Fast.api.ajax({ Fast.api.ajax({
url: options.extend.import_url, url: options.extend.import_url,
data: {file: data.url}, data: {file: data.url},
@ -382,8 +383,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
var options = table.bootstrapTable('getOptions'); var options = table.bootstrapTable('getOptions');
var data = element ? $(element).data() : {}; var data = element ? $(element).data() : {};
var ids = ($.isArray(ids) ? ids.join(",") : ids); var ids = ($.isArray(ids) ? ids.join(",") : ids);
var url = typeof data.url !== "undefined" ? data.url : (action == "del" ? options.extend.del_url : options.extend.multi_url); var url = typeof data.url !== "undefined" ? Table.api.replaceurl(data.url, {ids: ids}, table) : (action == "del" ? options.extend.del_url : options.extend.multi_url);
url = this.replaceurl(url, {ids: ids}, table);
var params = typeof data.params !== "undefined" ? (typeof data.params == 'object' ? $.param(data.params) : data.params) : ''; var params = typeof data.params !== "undefined" ? (typeof data.params == 'object' ? $.param(data.params) : data.params) : '';
var options = {url: url, data: {action: action, ids: ids, params: params}}; var options = {url: url, data: {action: action, ids: ids, params: params}};
Fast.api.ajax(options, function (data, ret) { Fast.api.ajax(options, function (data, ret) {

View File

@ -1,62 +1,21 @@
define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined, Plupload, Template) { define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined, Dropzone, Template) {
var Upload = { var Upload = {
list: {}, list: {},
options: {},
config: { config: {
container: document.body, container: document.body,
classname: '.plupload:not([initialized])', classname: '.plupload:not([initialized]),.faupload:not([initialized])',
previewtpl: '<li class="col-xs-3"><a href="<%=fullurl%>" data-url="<%=url%>" target="_blank" class="thumbnail"><img src="<%=fullurl%>" onerror="this.src=\'' + Fast.api.fixurl("ajax/icon") + '?suffix=<%=suffix%>\';this.onerror=null;" class="img-responsive"></a><a href="javascript:;" class="btn btn-danger btn-xs btn-trash"><i class="fa fa-trash"></i></a></li>', previewtpl: '<li class="col-xs-3"><a href="<%=fullurl%>" data-url="<%=url%>" target="_blank" class="thumbnail"><img src="<%=fullurl%>" onerror="this.src=\'' + Fast.api.fixurl("ajax/icon") + '?suffix=<%=suffix%>\';this.onerror=null;" class="img-responsive"></a><a href="javascript:;" class="btn btn-danger btn-xs btn-trash"><i class="fa fa-trash"></i></a></li>',
}, },
events: { events: {
onInit: function (up) { //初始化
//修复少数安卓浏览器无法上传图片的Bug onInit: function () {
var input = $("input[type=file]", up.settings.container);
if (input && input.prop("accept") && input.prop("accept").match(/image\//)) {
input.prop("accept", "image/jpg," + input.prop("accept"));
}
},
//初始化完成
onPostInit: function (up) {
},
//文件添加成功后
onFileAdded: function (up, files) {
var button = up.settings.button;
$(button).data("bakup-html", $(button).html());
var maxcount = $(button).data("maxcount");
var input_id = $(button).data("input-id") ? $(button).data("input-id") : "";
maxcount = typeof maxcount !== "undefined" ? maxcount : 0;
if (maxcount > 0 && input_id) {
var inputObj = $("#" + input_id);
if (inputObj.size() > 0) {
var value = $.trim(inputObj.val());
var nums = value === '' ? 0 : value.split(/\,/).length;
var remains = maxcount - nums;
if (files.length > remains) {
for (var i = 0; i < files.length; i++) {
up.removeFile(files[i]);
}
Toastr.error(__('You can upload up to %d file%s', remains));
return false;
}
}
}
//添加后立即上传
setTimeout(function () {
up.start();
}, 1);
},
//上传进行中的回调
onUploadProgress: function (up, file) {
},
//上传之前的回调
onBeforeUpload: function (up, file) {
}, },
//上传成功的回调 //上传成功的回调
onUploadSuccess: function (up, ret) { onUploadSuccess: function (up, ret, file) {
var button = up.settings.button; var button = up.element;
var onUploadSuccess = up.settings.onUploadSuccess; var onUploadSuccess = up.options.onUploadSuccess;
var data = typeof ret.data !== 'undefined' ? ret.data : null; var data = typeof ret.data !== 'undefined' ? ret.data : null;
//上传成功后回调 //上传成功后回调
if (button) { if (button) {
@ -92,9 +51,9 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
} }
}, },
//上传错误的回调 //上传错误的回调
onUploadError: function (up, ret) { onUploadError: function (up, ret, file) {
var button = up.settings.button; var button = up.element;
var onUploadError = up.settings.onUploadError; var onUploadError = up.options.onUploadError;
var data = typeof ret.data !== 'undefined' ? ret.data : null; var data = typeof ret.data !== 'undefined' ? ret.data : null;
if (button) { if (button) {
var onDomUploadError = $(button).data("upload-error"); var onDomUploadError = $(button).data("upload-error");
@ -119,7 +78,7 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
Toastr.error(ret.msg + "(code:" + ret.code + ")"); Toastr.error(ret.msg + "(code:" + ret.code + ")");
}, },
//服务器响应数据后 //服务器响应数据后
onUploadResponse: function (response) { onUploadResponse: function (response, up, file) {
try { try {
var ret = typeof response === 'object' ? response : JSON.parse(response); var ret = typeof response === 'object' ? response : JSON.parse(response);
if (!ret.hasOwnProperty('code')) { if (!ret.hasOwnProperty('code')) {
@ -132,8 +91,8 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
}, },
//上传全部结束后 //上传全部结束后
onUploadComplete: function (up, files) { onUploadComplete: function (up, files) {
var button = up.settings.button; var button = up.element;
var onUploadComplete = up.settings.onUploadComplete; var onUploadComplete = up.options.onUploadComplete;
if (button) { if (button) {
var onDomUploadComplete = $(button).data("upload-complete"); var onDomUploadComplete = $(button).data("upload-complete");
if (onDomUploadComplete) { if (onDomUploadComplete) {
@ -157,8 +116,8 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
} }
}, },
api: { api: {
//Plupload上传 //上传接口
plupload: function (element, onUploadSuccess, onUploadError, onUploadComplete) { upload: function (element, onUploadSuccess, onUploadError, onUploadComplete) {
element = typeof element === 'undefined' ? Upload.config.classname : element; element = typeof element === 'undefined' ? Upload.config.classname : element;
$(element, Upload.config.container).each(function () { $(element, Upload.config.container).each(function () {
if ($(this).attr("initialized")) { if ($(this).attr("initialized")) {
@ -169,6 +128,7 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
var id = $(this).prop("id"); var id = $(this).prop("id");
var url = $(this).data("url"); var url = $(this).data("url");
var maxsize = $(this).data("maxsize"); var maxsize = $(this).data("maxsize");
var maxcount = $(this).data("maxcount");
var mimetype = $(this).data("mimetype"); var mimetype = $(this).data("mimetype");
var multipart = $(this).data("multipart"); var multipart = $(this).data("multipart");
var multiple = $(this).data("multiple"); var multiple = $(this).data("multiple");
@ -189,65 +149,136 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
multipart = typeof multipart !== "undefined" ? multipart : Config.upload.multipart; multipart = typeof multipart !== "undefined" ? multipart : Config.upload.multipart;
//是否支持批量上传 //是否支持批量上传
multiple = typeof multiple !== "undefined" ? multiple : Config.upload.multiple; multiple = typeof multiple !== "undefined" ? multiple : Config.upload.multiple;
var mimetypeArr = new Array(); //后缀特殊处理
//支持后缀和Mimetype格式,以,分隔 mimetype = mimetype.split(",").map(function (k) {
if (mimetype && mimetype !== "*" && mimetype.indexOf("/") === -1) { return k.indexOf("/") > -1 ? k : (!k || k === "*" || k.charAt(0) === "." ? k : "." + k);
var tempArr = mimetype.split(','); }).join(",");
for (var i = 0; i < tempArr.length; i++) {
mimetypeArr.push({title: __('Files'), extensions: tempArr[i]}); //最大文件限制转换成mb
} var maxFilesize = (function (maxsize) {
mimetype = mimetypeArr; var matches = maxsize.toString().match(/^([0-9\.]+)(\w+)$/);
} var size = matches ? parseFloat(matches[1]) : parseFloat(maxsize),
//生成Plupload实例 unit = matches ? matches[2].toLowerCase() : 'b';
Upload.list[id] = new Plupload.Uploader({ var unitDict = {'b': 0, 'k': 1, 'kb': 1, 'm': 2, 'mb': 2, 'gb': 3, 'g': 3, 'tb': 4, 't': 4};
runtimes: 'html5,flash,silverlight,html4', var y = typeof unitDict[unit] !== 'undefined' ? unitDict[unit] : 0;
multi_selection: multiple, //是否允许多选批量上传 var bytes = size * Math.pow(1024, y);
browse_button: id, // 浏览按钮的ID return bytes / Math.pow(1024, 2);
container: $(this).parent().get(0), //取按钮的上级元素 }(maxsize));
flash_swf_url: '/assets/libs/plupload/js/Moxie.swf',
silverlight_xap_url: '/assets/libs/plupload/js/Moxie.xap', var options = $("#" + id).data() || {};
drop_element: [id, $(this).data("input-id")], delete options.success;
filters: { delete options.url;
max_file_size: maxsize, multipart = $.isArray(multipart) ? {} : multipart;
mime_types: mimetype,
}, Upload.list[id] = new Dropzone("#" + id, $.extend({
url: url, url: url,
multipart_params: $.isArray(multipart) ? {} : multipart, params: function (files, xhr, chunk) {
init: { var params = multipart;
Init: Upload.events.onInit, if (chunk) {
PostInit: Upload.events.onPostInit, return $.extend({}, params, {
FilesAdded: Upload.events.onFileAdded, filesize: chunk.file.size,
BeforeUpload: Upload.events.onBeforeUpload, filename: chunk.file.name,
UploadProgress: function (up, file) { chunkid: chunk.file.upload.uuid,
var button = up.settings.button; chunkindex: chunk.index,
$(button).prop("disabled", true).html("<i class='fa fa-upload'></i> " + __('Upload') + file.percent + "%"); chunkcount: chunk.file.upload.totalChunkCount,
Upload.events.onUploadProgress(up, file); chunksize: this.options.chunkSize,
}, chunkfilesize: chunk.dataBlock.data.size,
FileUploaded: function (up, file, info) { width: chunk.file.width || 0,
var button = up.settings.button; height: chunk.file.height || 0,
//还原按钮文字及状态 type: chunk.file.type,
$(button).prop("disabled", false).html($(button).data("bakup-html")); });
var ret = Upload.events.onUploadResponse(info.response, info, up, file);
file.ret = ret;
if (ret.code === 1) {
Upload.events.onUploadSuccess(up, ret, file);
} else {
Upload.events.onUploadError(up, ret, file);
}
},
UploadComplete: Upload.events.onUploadComplete,
Error: function (up, err) {
var button = up.settings.button;
$(button).prop("disabled", false).html($(button).data("bakup-html"));
var ret = {code: err.code, msg: err.message, data: null};
Upload.events.onUploadError(up, ret);
} }
return params;
},
maxFilesize: maxFilesize,
acceptedFiles: mimetype,
maxFiles: (maxcount && parseInt(maxcount) > 1 ? maxcount : (multiple ? null : 1)),
previewsContainer: false,
dictDefaultMessage: __("Drop files here to upload"),
dictFallbackMessage: __("Your browser does not support drag'n'drop file uploads"),
dictFallbackText: __("Please use the fallback form below to upload your files like in the olden days"),
dictFileTooBig: __("File is too big (%sMiB), Max filesize: %sMiB", "{{filesize}}", "{{maxFilesize}}"),
dictInvalidFileType: __("You can't upload files of this type"),
dictResponseError: __("Server responded with %s code.", "{{statusCode}}"),
dictCancelUpload: __("Cancel upload"),
dictUploadCanceled: __("Upload canceled"),
dictCancelUploadConfirmation: __("Are you sure you want to cancel this upload?"),
dictRemoveFile: __("Remove file"),
dictMaxFilesExceeded: __("You can only upload a maximum of %s files", "{{maxFiles}}"),
init: function () {
Upload.events.onInit.call(this);
//必须添加dz-message否则点击icon无法唤起上传窗口
$(">i", this.element).addClass("dz-message");
this.options.elementHtml = $(this.element).html();
},
addedfiles: function (files) {
if (this.options.maxFiles && this.options.maxFiles > 0 && this.options.inputId) {
var inputObj = $("#" + this.options.inputId);
if (inputObj.size() > 0) {
var value = $.trim(inputObj.val());
var nums = value === '' ? 0 : value.split(/\,/).length;
var remain = this.options.maxFiles - nums;
if (remain === 0 || files.length > remain) {
files = Array.prototype.slice.call(files, remain);
for (var i = 0; i < files.length; i++) {
this.removeFile(files[i]);
}
Toastr.error(__("You can only upload a maximum of %s files", this.options.maxFiles));
}
}
}
},
success: function (file, response) {
var ret = Upload.events.onUploadResponse(response, this, file);
file.ret = ret;
if (ret.code === 1) {
Upload.events.onUploadSuccess(this, ret, file);
} else {
Upload.events.onUploadError(this, ret, file);
}
},
error: function (file, response, xhr) {
var ret = {code: 0, data: null, msg: response};
Upload.events.onUploadError(this, ret, file);
},
uploadprogress: function (file, progress, bytesSent) {
},
totaluploadprogress: function (progress, bytesSent) {
if (this.getActiveFiles().length > 0) {
$(this.element).prop("disabled", true).html("<i class='fa fa-upload'></i> " + __('Upload') + Math.floor(progress) + "%");
}
},
queuecomplete: function () {
Upload.events.onUploadComplete(this, this.files);
this.removeAllFiles(true);
$(this.element).prop("disabled", false).html(this.options.elementHtml);
},
chunkSuccess: function (chunk, file, response) {
},
chunksUploaded: function (file, done) {
var that = this;
Fast.api.ajax({
url: this.options.url,
data: {
action: 'merge',
filesize: file.size,
filename: file.name,
chunkid: file.upload.uuid,
chunkcount: file.upload.totalChunkCount,
}
}, function (data, ret) {
done(JSON.stringify(ret));
return false;
}, function (data, ret) {
file.accepted = false;
that._errorProcessing([file], ret.msg);
});
}, },
onUploadSuccess: onUploadSuccess, onUploadSuccess: onUploadSuccess,
onUploadError: onUploadError, onUploadError: onUploadError,
onUploadComplete: onUploadComplete, onUploadComplete: onUploadComplete,
button: that }, Upload.options, options));
});
//拖动排序 //拖动排序
if (preview_id && multiple) { if (preview_id && multiple) {
@ -335,70 +366,46 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
}); });
} }
if (input_id) { if (input_id) {
//粘贴上传 //粘贴上传、拖拽上传
$("body").on('paste', "#" + input_id, function (event) { $("body").on('paste drop', "#" + input_id, function (event) {
var that = this; var originEvent = event.originalEvent;
var image, pasteEvent; var button = $(".plupload[data-input-id='" + $(this).attr("id") + "'],.faupload[data-input-id='" + $(this).attr("id") + "']");
pasteEvent = event.originalEvent; if (event.type === 'paste' && originEvent.clipboardData && originEvent.clipboardData.items) {
if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) { var items = originEvent.clipboardData.items;
image = Upload.api.getImageFromClipboard(pasteEvent); if ((items.length === 1 && items[0].type.indexOf("text") > -1) || (items.length === 2 && items[1].type.indexOf("text") > -1)) {
if (image) {
event.preventDefault(); } else {
var button = $(".plupload[data-input-id='" + $(that).attr("id") + "']"); Upload.list[button.attr("id")].paste(originEvent);
Upload.api.send(image, function (data) { return false;
var urlArr = [];
if (button && button.data("multiple") && $(that).val() !== '') {
urlArr.push($(that).val());
}
urlArr.push(data.url);
$(that).val(urlArr.join(",")).trigger("change").trigger("validate");
});
} }
} }
}); if (event.type === 'drop' && originEvent.dataTransfer && originEvent.dataTransfer.files) {
//拖拽上传 Upload.list[button.attr("id")].drop(originEvent);
$("body").on('drop', "#" + input_id, function (event) { return false;
var that = this;
var images, pasteEvent;
pasteEvent = event.originalEvent;
if (pasteEvent.dataTransfer && pasteEvent.dataTransfer.files) {
images = Upload.api.getImageFromDrop(pasteEvent);
if (images.length > 0) {
event.preventDefault();
var button = $(".plupload[data-input-id='" + $(that).attr("id") + "']");
$.each(images, function (i, image) {
Upload.api.send(image, function (data) {
var urlArr = [];
if (button && button.data("multiple") && $(that).val() !== '') {
urlArr.push($(that).val());
}
urlArr.push(data.url);
$(that).val(urlArr.join(",")).trigger("change").trigger("validate");
});
});
}
} }
}); });
} }
Upload.list[id].init();
}); });
}, },
/**
* @deprecated Use upload instead.
*/
plupload: function (element, onUploadSuccess, onUploadError, onUploadComplete) {
return Upload.api.upload(element, onUploadSuccess, onUploadError, onUploadComplete);
},
// AJAX异步上传 // AJAX异步上传
send: function (file, onUploadSuccess, onUploadError, onUploadComplete) { send: function (file, onUploadSuccess, onUploadError, onUploadComplete) {
var index = Layer.msg(__('Uploading'), {offset: 't', time: 0}); var index = Layer.msg(__('Uploading'), {offset: 't', time: 0});
var id = Plupload.guid(); var id = "dropzone-" + Dropzone.uuidv4();
var _onPostInit = Upload.events.onPostInit; $('<button type="button" id="' + id + '" class="btn btn-danger hidden faupload" />').appendTo("body");
Upload.events.onPostInit = function () {
// 当加载完成后添加文件并上传
Upload.list[id].addFile(file);
//Upload.list[id].start();
};
$('<button type="button" id="' + id + '" class="btn btn-danger hidden plupload" />').appendTo("body");
$("#" + id).data("upload-complete", function (files) { $("#" + id).data("upload-complete", function (files) {
Upload.events.onPostInit = _onPostInit;
Layer.close(index); Layer.close(index);
Upload.list[id].removeAllFiles(true);
}); });
Upload.api.plupload("#" + id, onUploadSuccess, onUploadError, onUploadComplete); Upload.api.upload("#" + id, onUploadSuccess, onUploadError, onUploadComplete);
setTimeout(function () {
Upload.list[id].addFile(file);
}, 1);
}, },
custom: { custom: {
//自定义上传完成回调 //自定义上传完成回调
@ -406,31 +413,6 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
console.log(this, response); console.log(this, response);
alert("Custom Callback,Response URL:" + response.url); alert("Custom Callback,Response URL:" + response.url);
}, },
},
getImageFromClipboard: function (data) {
var i, item;
i = 0;
while (i < data.clipboardData.items.length) {
item = data.clipboardData.items[i];
if (item.type.indexOf("image") !== -1) {
return item.getAsFile() || false;
}
i++;
}
return false;
},
getImageFromDrop: function (data) {
var i, item, images;
i = 0;
images = [];
while (i < data.dataTransfer.files.length) {
item = data.dataTransfer.files[i];
if (item.type.indexOf("image") !== -1) {
images.push(item);
}
i++;
}
return images;
} }
} }
} }

View File

@ -151,10 +151,30 @@ table.table-template {
overflow: hidden; overflow: hidden;
} }
.sp_container .msg-box { .sp_container {
position: absolute; .msg-box {
right: 0; position: absolute;
top: 0; right: 0;
top: 0;
}
.sp_element_box {
overflow: unset;
> li.input_box {
position: unset;
}
.msg-box {
right: -24px;
}
}
}
@media (max-width: 767px) {
.sp_container .sp_element_box .msg-box {
left: inherit;
}
} }
.toast-top-right-index { .toast-top-right-index {
@ -173,6 +193,12 @@ table.table-template {
padding: 10px 20px; padding: 10px 20px;
} }
select.bs-select-hidden, select.selectpicker {
display: inherit !important;
max-height: 31px;
overflow: hidden;
}
.img-center { .img-center {
margin: 0 auto; margin: 0 auto;
display: inline; display: inline;
@ -738,6 +764,7 @@ form.form-horizontal .control-label {
/*修复nice-validator新版下的一处BUG*/ /*修复nice-validator新版下的一处BUG*/
.nice-validator { .nice-validator {
input, select, textarea, [contenteditable] { input, select, textarea, [contenteditable] {
vertical-align: top;
display: inline-block; display: inline-block;
*display: inline; *display: inline;
*zoom: 1; *zoom: 1;
@ -750,7 +777,7 @@ form.form-horizontal .control-label {
} }
/*预览区域*/ /*预览区域*/
.plupload-preview { .plupload-preview, .faupload-preview {
padding: 0 10px; padding: 0 10px;
margin-bottom: 0; margin-bottom: 0;
@ -1081,10 +1108,8 @@ table.table-nowrap {
} }
} }
.n-bootstrap { .input-group > .msg-box.n-right {
.input-group > .n-right { position: absolute;
position: absolute;
}
} }
@media (min-width: 564px) { @media (min-width: 564px) {

View File

@ -5,8 +5,7 @@
/*Dropdowns in general*/ /*Dropdowns in general*/
.dropdown-menu { .dropdown-menu {
box-shadow: none; border:none;
border-color: #eee;
> li > a { > li > a {
/*color: #777;*/ /*color: #777;*/
} }

View File

@ -40,7 +40,7 @@ body {
border:none; border:none;
} }
.navbar-nav { .navbar-nav {
li > a { > li > a {
font-size:14px; font-size:14px;
} }
} }
@ -57,7 +57,7 @@ body {
} }
/*预览区域*/ /*预览区域*/
.plupload-preview { .plupload-preview,.faupload-preview {
padding:0 10px; padding:0 10px;
margin-bottom:0; margin-bottom:0;
li { li {
@ -92,6 +92,21 @@ body {
} }
} }
.input-group > .msg-box.n-right {
position: absolute;
}
/*修复radio和checkbox样式对齐*/
.radio, .checkbox {
> label {
margin-right: 10px;
> input {
margin: 2px 0 0;
}
}
}
#header-navbar li.dropdown ul.dropdown-menu { #header-navbar li.dropdown ul.dropdown-menu {
min-width:94px; min-width:94px;
} }
@ -485,4 +500,4 @@ main.content {
margin-right: 5px; margin-right: 5px;
text-align: center; text-align: center;
display: inline-block; display: inline-block;
} }