新增菜单类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
* 基于`RequireJS`进行JS模块管理按需加载
* 基于`Less`进行样式开发
* 基于`Bower`进行前端组件包管理
* 强大的插件扩展功能,在线安装卸载升级插件
* 通用的会员模块和API模块
* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
* 二级域名部署支持,同时域名支持绑定到插件
* 多语言支持,服务端及客户端支持
* 强大的第三方模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[博客](https://www.fastadmin.net/store/blog.html)、[知识付费问答](https://www.fastadmin.net/store/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)
* 第三方小程序支持([预订小程序](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、百度编辑器)
* 第三方登录(QQ、微信、微博)整合
* 第三方支付(微信、支付宝)无缝整合微信支持PC端扫码支付
@ -80,6 +80,10 @@ Nice-validator: https://validator.niceue.com
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"';
$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
<div class="input-group">
{$content}
<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>
</div>
<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 '性别',
`birthday` date DEFAULT NULL COMMENT '生日',
`bio` varchar(100) NOT NULL DEFAULT '' COMMENT '格言',
`money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '余额',
`score` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '积分',
`money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '余额',
`score` int(10) NOT NULL DEFAULT '0' COMMENT '积分',
`successions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '连续登录天数',
`maxsuccessions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '最大连续登录天数',
`prevtime` int(10) DEFAULT NULL COMMENT '上次登录时间',

View File

@ -3,6 +3,8 @@
namespace app\admin\controller;
use app\common\controller\Backend;
use app\common\exception\UploadException;
use app\common\library\Upload;
use fast\Random;
use think\addons\Service;
use think\Cache;
@ -54,99 +56,61 @@ class Ajax extends Backend
public function upload()
{
Config::set('default_return_type', 'json');
$file = $this->request->file('file');
if (empty($file)) {
$this->error(__('No file upload or server upload limit exceeded'));
}
//判断是否已经存在附件
$sha1 = $file->hash();
$extparam = $this->request->post();
$upload = Config::get('upload');
preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches);
$type = strtolower($matches[2]);
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
$size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
$fileInfo = $file->getInfo();
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
$mimetypeArr = explode(',', strtolower($upload['mimetype']));
$typeArr = explode('/', $fileInfo['type']);
//禁止上传PHP和HTML文件
if (in_array($fileInfo['type'], ['text/x-php', 'text/html']) || in_array($suffix, ['php', 'html', 'htm'])) {
$this->error(__('Uploaded file format is limited'));
}
//验证文件后缀
if ($upload['mimetype'] !== '*' &&
(
!in_array($suffix, $mimetypeArr)
|| (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr)))
)
) {
$this->error(__('Uploaded file format is limited'));
}
//验证是否为图片文件
$imagewidth = $imageheight = 0;
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'])) {
$imgInfo = getimagesize($fileInfo['tmp_name']);
if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
$this->error(__('Uploaded file is not a valid image'));
$chunkid = $this->request->post("chunkid");
if ($chunkid) {
if (!Config::get('upload.chunking')) {
$this->error(__('Chunk file disabled'));
}
$action = $this->request->post("action");
$chunkindex = $this->request->post("chunkindex/d");
$chunkcount = $this->request->post("chunkcount/d");
$filename = $this->request->post("filename");
$method = $this->request->method(true);
if ($action == 'merge') {
$attachment = null;
//合并分片文件
try {
$upload = new Upload();
$attachment = $upload->merge($chunkid, $chunkcount, $filename);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url]);
} elseif ($method == 'clean') {
//删除冗余的分片文件
try {
$upload = new Upload();
$upload->clean($chunkid);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
//上传分片文件
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$upload->chunk($chunkid, $chunkindex, $chunkcount);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$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' => (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 {
// 上传失败获取错误信息
$this->error($file->getError());
$attachment = null;
//默认普通上传文件
$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) {
\think\Hook::add('upload_delete', function ($params) {
$attachmentFile = ROOT_PATH . '/public' . $params['url'];
if (is_file($attachmentFile)) {
@unlink($attachmentFile);
if ($params['storage'] == 'local') {
$attachmentFile = ROOT_PATH . '/public' . $params['url'];
if (is_file($attachmentFile)) {
@unlink($attachmentFile);
}
}
});
$attachmentlist = $this->model->where('id', 'in', $ids)->select();

View File

@ -143,8 +143,24 @@ return [
'Please enter your username' => '请输入你的用户名',
'Please enter your password' => '请输入你的密码',
'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',
'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
//菜单

View File

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

View File

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

View File

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

View File

@ -70,16 +70,16 @@
{case value="images"}
<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}">
<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>
<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>
{/case}
{case value="file" break="0"}{/case}
{case value="files"}
<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}">
<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>
</div>
{/case}

View File

@ -77,7 +77,7 @@
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
{: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')}
</button>
{if $Think.config.fastadmin.api_url}

View File

@ -58,12 +58,12 @@
<div class="input-group">
<input id="c-image" class="form-control" size="35" name="row[image]" type="text" value="">
<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>
</div>
<span class="msg-box n-right"></span>
</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 class="form-group">

View File

@ -54,12 +54,12 @@
<div class="input-group">
<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">
<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>
</div>
<span class="msg-box n-right"></span>
</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 class="form-group">

View File

@ -10,7 +10,7 @@
<div class="form-group">
<label for="c-third" class="control-label col-xs-12 col-sm-2"></label>
<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>
{/if}
@ -25,7 +25,7 @@
<div class="form-group">
<label for="c-local" class="control-label col-xs-12 col-sm-2"></label>
<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>

View File

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

View File

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

View File

@ -111,17 +111,17 @@
{case value="images"}
<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}">
<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 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>
{/case}
{case value="file" break="0"}{/case}
{case value="files"}
<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}">
<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 class="msg-box n-right" for="c-{$item.name}"></span>
</div>

View File

@ -51,9 +51,9 @@
<div class="box-body box-profile">
<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>
<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>
<h3 class="profile-username text-center">{$admin.username|htmlentities}</h3>

View File

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

View File

@ -43,12 +43,12 @@
<div class="input-group">
<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">
<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>
</div>
<span class="msg-box n-right" for="c-avatar"></span>
</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 class="form-group">

View File

@ -3,6 +3,8 @@
namespace app\api\controller;
use app\common\controller\Api;
use app\common\exception\UploadException;
use app\common\library\Upload;
use app\common\model\Area;
use app\common\model\Version;
use fast\Random;
@ -47,96 +49,61 @@ class Common extends Api
*/
public function upload()
{
$file = $this->request->file('file');
if (empty($file)) {
$this->error(__('No file upload or server upload limit exceeded'));
}
//判断是否已经存在附件
$sha1 = $file->hash();
$upload = Config::get('upload');
preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches);
$type = strtolower($matches[2]);
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
$size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
$fileInfo = $file->getInfo();
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
$mimetypeArr = explode(',', strtolower($upload['mimetype']));
$typeArr = explode('/', $fileInfo['type']);
//禁止上传PHP和HTML文件
if (in_array($fileInfo['type'], ['text/x-php', 'text/html']) || in_array($suffix, ['php', 'html', 'htm'])) {
$this->error(__('Uploaded file format is limited'));
}
//验证文件后缀
if ($upload['mimetype'] !== '*' &&
(
!in_array($suffix, $mimetypeArr)
|| (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr)))
)
) {
$this->error(__('Uploaded file format is limited'));
}
//验证是否为图片文件
$imagewidth = $imageheight = 0;
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'])) {
$imgInfo = getimagesize($fileInfo['tmp_name']);
if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
$this->error(__('Uploaded file is not a valid image'));
Config::set('default_return_type', 'json');
$chunkid = $this->request->post("chunkid");
if ($chunkid) {
if (!Config::get('upload.chunking')) {
$this->error(__('Chunk file disabled'));
}
$action = $this->request->post("action");
$chunkindex = $this->request->post("chunkindex/d");
$chunkcount = $this->request->post("chunkcount/d");
$filename = $this->request->post("filename");
$method = $this->request->method(true);
if ($action == 'merge') {
$attachment = null;
//合并分片文件
try {
$upload = new Upload();
$attachment = $upload->merge($chunkid, $chunkcount, $filename);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url]);
} elseif ($method == 'clean') {
//删除冗余的分片文件
try {
$upload = new Upload();
$upload->clean($chunkid);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
//上传分片文件
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$upload->chunk($chunkid, $chunkindex, $chunkcount);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$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 {
// 上传失败获取错误信息
$this->error($file->getError());
$attachment = null;
//默认普通上传文件
$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
return [
'Keep login' => '保持会话',
'Username' => '用户名',
'User id' => '会员ID',
'Nickname' => '昵称',
'Password' => '密码',
'Sign up' => '注 册',
'Sign in' => '登 录',
'Sign out' => '注 销',
'Guest' => '游客',
'Welcome' => '%s你好',
'Add' => '添加',
'Edit' => '编辑',
'Delete' => '删除',
'Move' => '移动',
'Name' => '名称',
'Status' => '状态',
'Weigh' => '权重',
'Operate' => '操作',
'Warning' => '温馨提示',
'Default' => '默认',
'Article' => '文章',
'Page' => '单页',
'OK' => '确定',
'Cancel' => '取消',
'Loading' => '加载中',
'More' => '更多',
'Normal' => '正常',
'Hidden' => '隐藏',
'Submit' => '提交',
'Reset' => '重置',
'Execute' => '执行',
'Close' => '关闭',
'Search' => '搜索',
'Refresh' => '刷新',
'First' => '首页',
'Previous' => '上一页',
'Next' => '下一页',
'Last' => '末页',
'None' => '无',
'Home' => '主页',
'Online' => '在线',
'Logout' => '注销',
'Profile' => '个人资料',
'Index' => '首页',
'Hot' => '热门',
'Recommend' => '推荐',
'Dashboard' => '控制台',
'Code' => '编号',
'Message' => '内容',
'Line' => '行号',
'File' => '文件',
'Menu' => '菜单',
'Type' => '类型',
'Title' => '标题',
'Content' => '内容',
'Append' => '追加',
'Memo' => '备注',
'Parent' => '父级',
'Params' => '参数',
'Permission' => '权限',
'Advance search' => '高级搜索',
'Check all' => '选中全部',
'Expand all' => '展开全部',
'Begin time' => '开始时间',
'End time' => '结束时间',
'Create time' => '创建时间',
'Flag' => '标志',
'Please login first' => '请登录后操作',
'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 秒后自动跳转',
'Keep login' => '保持会话',
'Username' => '用户名',
'User id' => '会员ID',
'Nickname' => '昵称',
'Password' => '密码',
'Sign up' => '注 册',
'Sign in' => '登 录',
'Sign out' => '注 销',
'Guest' => '游客',
'Welcome' => '%s你好',
'Add' => '添加',
'Edit' => '编辑',
'Delete' => '删除',
'Move' => '移动',
'Name' => '名称',
'Status' => '状态',
'Weigh' => '权重',
'Operate' => '操作',
'Warning' => '温馨提示',
'Default' => '默认',
'Article' => '文章',
'Page' => '单页',
'OK' => '确定',
'Cancel' => '取消',
'Loading' => '加载中',
'More' => '更多',
'Normal' => '正常',
'Hidden' => '隐藏',
'Submit' => '提交',
'Reset' => '重置',
'Execute' => '执行',
'Close' => '关闭',
'Search' => '搜索',
'Refresh' => '刷新',
'First' => '首页',
'Previous' => '上一页',
'Next' => '下一页',
'Last' => '末页',
'None' => '无',
'Home' => '主页',
'Online' => '在线',
'Logout' => '注销',
'Profile' => '个人资料',
'Index' => '首页',
'Hot' => '热门',
'Recommend' => '推荐',
'Dashboard' => '控制台',
'Code' => '编号',
'Message' => '内容',
'Line' => '行号',
'File' => '文件',
'Menu' => '菜单',
'Type' => '类型',
'Title' => '标题',
'Content' => '内容',
'Append' => '追加',
'Memo' => '备注',
'Parent' => '父级',
'Params' => '参数',
'Permission' => '权限',
'Advance search' => '高级搜索',
'Check all' => '选中全部',
'Expand all' => '展开全部',
'Begin time' => '开始时间',
'End time' => '结束时间',
'Create time' => '创建时间',
'Flag' => '标志',
'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',
'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
return [
'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'Uploaded file format is limited' => '上传文件格式受限制',
'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Upload successful' => '上传成功',
];
return [];

View File

@ -228,7 +228,7 @@ class Backend extends Controller
*/
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');
}
@ -456,8 +456,11 @@ class Backend extends Controller
$where = function ($query) use ($word, $andor, $field, $searchfield, $custom) {
$logic = $andor == 'AND' ? '&' : '|';
$searchfield = is_array($searchfield) ? implode($logic, $searchfield) : $searchfield;
foreach ($word as $k => $v) {
$query->where(str_replace(',', $logic, $searchfield), "like", "%{$v}%");
$word = array_filter($word);
if ($word) {
foreach ($word as $k => $v) {
$query->where(str_replace(',', $logic, $searchfield), "like", "%{$v}%");
}
}
if ($custom && is_array($custom)) {
foreach ($custom as $k => $v) {
@ -517,7 +520,7 @@ class Backend extends Controller
$token = $this->request->post('__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()]);
}

View File

@ -145,7 +145,7 @@ class Frontend extends Controller
$token = $this->request->post('__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()]);
}

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;
}
/**
* 升级菜单
* @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
@ -109,6 +124,44 @@ class Menu
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
* @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) {
if (self::where('url', '=', $model['url'])->find()) {
if (self::where('url', '=', $model['url'])->where('storage', $model['storage'])->find()) {
return false;
}
});

View File

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

View File

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

View File

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

View File

@ -107,6 +107,24 @@ return [
'User center' => '会员中心',
'Change password' => '修改密码',
'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' => '发送验证码',
'Redirect now' => '立即跳转',
'Operation completed' => '操作成功!',

View File

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

View File

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

View File

@ -1,9 +1,9 @@
<div id="content-container" class="container">
<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">
<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()}
<div class="form-group">
<label class="control-label" for="account">{:__('Account')}</label>
@ -20,7 +20,11 @@
</div>
<div class="form-group">
<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>
</div>
@ -34,7 +38,7 @@
<script type="text/html" id="resetpwdtpl">
<form id="resetpwd-form" class="form-horizontal form-layer" method="POST" action="{:url('api/user/resetpwd')}">
<div class="form-body">
<input type="hidden" name="action" value="resetpwd" />
<input type="hidden" name="action" value="resetpwd"/>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-3">{:__('Type')}:</label>
<div class="col-xs-12 col-sm-8">
@ -47,14 +51,14 @@
<div class="form-group" data-type="email">
<label for="email" class="control-label col-xs-12 col-sm-3">{:__('Email')}:</label>
<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>
</div>
</div>
<div class="form-group hide" data-type="mobile">
<label for="mobile" class="control-label col-xs-12 col-sm-3">{:__('Mobile')}:</label>
<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>
</div>
</div>
@ -62,7 +66,7 @@
<label for="captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
<div class="col-xs-12 col-sm-8">
<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;">
<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>

View File

@ -44,9 +44,9 @@
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-4">
<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>
<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>

View File

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

View File

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

View File

@ -186,11 +186,12 @@
<div class="child collapse" id="验证接口">
<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="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="27" 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="27" 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="30" class="list-group-item">检测邮箱验证码</a>
</div>
</div>
</div>
@ -3137,7 +3138,7 @@
<div class="panel-heading" id="heading-25">
<h4 class="panel-title">
<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>
</div>
<div id="collapseOne25" class="panel-collapse collapse">
@ -3155,7 +3156,7 @@
<div class="tab-pane active" id="info25">
<div class="well">
检测手机 </div>
检测昵称 </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Headers</strong></div>
<div class="panel-body">
@ -3176,10 +3177,10 @@
</thead>
<tbody>
<tr>
<td>mobile</td>
<td>nickname</td>
<td>string</td>
<td></td>
<td>手机号</td>
<td>昵称</td>
</tr>
<tr>
<td>id</td>
@ -3204,10 +3205,10 @@
<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_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">
<label class="control-label" for="mobile">mobile</label>
<input type="string" class="form-control input-sm" id="mobile" required placeholder="手机号" name="mobile">
<label class="control-label" for="nickname">nickname</label>
<input type="string" class="form-control input-sm" id="nickname" required placeholder="昵称" name="nickname">
</div>
<div class="form-group">
<label class="control-label" for="id">id</label>
@ -3257,7 +3258,7 @@
<div class="panel-heading" id="heading-26">
<h4 class="panel-title">
<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>
</div>
<div id="collapseOne26" class="panel-collapse collapse">
@ -3300,6 +3301,12 @@
<td>string</td>
<td></td>
<td>手机号</td>
</tr>
<tr>
<td>id</td>
<td>string</td>
<td></td>
<td>排除会员ID</td>
</tr>
</tbody>
</table>
@ -3318,10 +3325,14 @@
<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_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">
<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">
<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 class="form-group">
<button type="submit" class="btn btn-success send" rel="26">提交</button>
@ -3367,7 +3378,7 @@
<div class="panel-heading" id="heading-27">
<h4 class="panel-title">
<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>
</div>
<div id="collapseOne27" class="panel-collapse collapse">
@ -3385,7 +3396,7 @@
<div class="tab-pane active" id="info27">
<div class="well">
检测邮箱 </div>
检测手机 </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Headers</strong></div>
<div class="panel-body">
@ -3409,7 +3420,7 @@
<td>mobile</td>
<td>string</td>
<td></td>
<td>邮箱</td>
<td>手机号</td>
</tr>
</tbody>
</table>
@ -3428,10 +3439,10 @@
<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="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">
<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">
<button type="submit" class="btn btn-success send" rel="27">提交</button>
@ -3477,7 +3488,7 @@
<div class="panel-heading" id="heading-28">
<h4 class="panel-title">
<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>
</div>
<div id="collapseOne28" class="panel-collapse collapse">
@ -3494,6 +3505,116 @@
<div class="tab-content">
<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>
<div class="panel panel-default">
@ -3544,13 +3665,13 @@
</div>
</div><!-- #info -->
<div class="tab-pane" id="sandbox28">
<div class="tab-pane" id="sandbox29">
<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_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">
<label class="control-label" for="mobile">mobile</label>
<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">
</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>
<button type="submit" class="btn btn-success send" rel="29">提交</button>
<button type="reset" class="btn btn-info" rel="29">重置</button>
</div>
</form>
</div>
@ -3575,8 +3696,8 @@
<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>
<pre id="response_headers29"></pre>
<pre id="response29"></pre>
</div>
</div>
</div>
@ -3591,10 +3712,10 @@
</div>
</div><!-- #sandbox -->
<div class="tab-pane" id="sample28">
<div class="tab-pane" id="sample29">
<div class="row">
<div class="col-md-12">
<pre id="sample_response28"></pre>
<pre id="sample_response29"></pre>
</div>
</div>
</div><!-- #sample -->
@ -3604,26 +3725,26 @@
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" id="heading-29">
<div class="panel-heading" id="heading-30">
<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_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>
</div>
<div id="collapseOne29" class="panel-collapse collapse">
<div id="collapseOne30" 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 class="nav nav-tabs" id="doctab30">
<li class="active"><a href="#info30" data-toggle="tab">基础信息</a></li>
<li><a href="#sandbox30" data-toggle="tab">在线测试</a></li>
<li><a href="#sample30" data-toggle="tab">返回示例</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane active" id="info29">
<div class="tab-pane active" id="info30">
<div class="well">
检测邮箱验证码 </div>
<div class="panel panel-default">
@ -3674,13 +3795,13 @@
</div>
</div><!-- #info -->
<div class="tab-pane" id="sandbox29">
<div class="tab-pane" id="sandbox30">
<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_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">
<label class="control-label" for="email">email</label>
<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">
</div>
<div class="form-group">
<button type="submit" class="btn btn-success send" rel="29">提交</button>
<button type="reset" class="btn btn-info" rel="29">重置</button>
<button type="submit" class="btn btn-success send" rel="30">提交</button>
<button type="reset" class="btn btn-info" rel="30">重置</button>
</div>
</form>
</div>
@ -3705,8 +3826,8 @@
<div class="panel-body">
<div class="row">
<div class="col-md-12" style="overflow-x:auto">
<pre id="response_headers29"></pre>
<pre id="response29"></pre>
<pre id="response_headers30"></pre>
<pre id="response30"></pre>
</div>
</div>
</div>
@ -3721,10 +3842,10 @@
</div>
</div><!-- #sandbox -->
<div class="tab-pane" id="sample29">
<div class="tab-pane" id="sample30">
<div class="row">
<div class="col-md-12">
<pre id="sample_response29"></pre>
<pre id="sample_response30"></pre>
</div>
</div>
</div><!-- #sample -->
@ -3739,7 +3860,7 @@
<div class="row mt0 footer">
<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">
<a href="./" target="_blank">我的网站</a>
</div>

View File

@ -119,6 +119,20 @@ table.table-template {
right: 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 {
top: 62px;
right: 12px;
@ -133,6 +147,12 @@ table.table-template {
margin-bottom: -5px;
padding: 10px 20px;
}
select.bs-select-hidden,
select.selectpicker {
display: inherit !important;
max-height: 31px;
overflow: hidden;
}
.img-center {
margin: 0 auto;
display: inline;
@ -599,6 +619,7 @@ form.form-horizontal .control-label {
.nice-validator select,
.nice-validator textarea,
.nice-validator [contenteditable] {
vertical-align: top;
display: inline-block;
*display: inline;
*zoom: 1;
@ -608,23 +629,29 @@ form.form-horizontal .control-label {
display: inherit;
}
/*预览区域*/
.plupload-preview {
.plupload-preview,
.faupload-preview {
padding: 0 10px;
margin-bottom: 0;
}
.plupload-preview li {
.plupload-preview li,
.faupload-preview li {
margin-top: 15px;
}
.plupload-preview .thumbnail {
.plupload-preview .thumbnail,
.faupload-preview .thumbnail {
margin-bottom: 10px;
}
.plupload-preview a {
.plupload-preview a,
.faupload-preview a {
display: block;
}
.plupload-preview a:first-child {
.plupload-preview a:first-child,
.faupload-preview a:first-child {
height: 90px;
}
.plupload-preview a img {
.plupload-preview a img,
.faupload-preview a img {
height: 80px;
object-fit: cover;
}
@ -877,7 +904,7 @@ table.table-nowrap thead > tr > th {
margin: 0;
padding: 0;
}
.n-bootstrap .input-group > .n-right {
.input-group > .msg-box.n-right {
position: absolute;
}
@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*/
.dropdown-menu {
box-shadow: none;
border-color: #eee;
border: none;
}
.dropdown-menu > li > a {
/*color: #777;*/
@ -5829,4 +5828,4 @@ fieldset[disabled] .btn-yahoo.active {
white-space: normal !important;
}
}
/*# sourceMappingURL=../css/fastadmin.css.map */
/*# sourceMappingURL=fastadmin.css.map */

View File

@ -21,7 +21,7 @@ body {
.navbar {
border: none;
}
.navbar-nav li > a {
.navbar-nav > li > a {
font-size: 14px;
}
.toast-top-center {
@ -37,23 +37,29 @@ body {
display: inherit;
}
/*预览区域*/
.plupload-preview {
.plupload-preview,
.faupload-preview {
padding: 0 10px;
margin-bottom: 0;
}
.plupload-preview li {
.plupload-preview li,
.faupload-preview li {
margin-top: 10px;
}
.plupload-preview .thumbnail {
.plupload-preview .thumbnail,
.faupload-preview .thumbnail {
margin-bottom: 10px;
}
.plupload-preview a {
.plupload-preview a,
.faupload-preview a {
display: block;
}
.plupload-preview a:first-child {
.plupload-preview a:first-child,
.faupload-preview a:first-child {
height: 90px;
}
.plupload-preview a img {
.plupload-preview a img,
.faupload-preview a img {
height: 80px;
object-fit: cover;
}
@ -70,6 +76,18 @@ body {
padding: 12px 25px;
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 {
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) {
Upload.api.plupload("#plupload-addon", function (data, ret) {
Upload.api.upload("#faupload-addon", function (data, ret) {
Config['addons'][data.addon.name] = data.addon;
Toastr.success(ret.msg);
operate(data.addon.name, 'enable', false);

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: '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: __('Url'), formatter: Controller.api.formatter.url},
{field: 'imagewidth', title: __('Imagewidth'), sortable: true},
{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: 'url', title: __('Url'), formatter: Controller.api.formatter.url, visible: false},
{field: 'filename', title: __('Filename'), formatter: Table.api.formatter.search, operate: 'like'},
{
field: 'filesize', title: __('Filesize'), operate: 'BETWEEN', sortable: true, formatter: function (value, row, index) {
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];
}
},
{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: '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: 'user_id', title: __('User_id'), formatter: Table.api.formatter.search, visible: 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: 'imageheight', title: __('Imageheight'), operate: false},
{
@ -143,7 +145,7 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
// 为表格绑定事件
Table.api.bindevent(table);
require(['upload'], function (Upload) {
Upload.api.plupload($("#toolbar .plupload"), function () {
Upload.api.faupload($("#toolbar .faupload"), function () {
$(".btn-refresh").trigger("click");
});
});

View File

@ -2,47 +2,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
var Controller = {
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", {
display: function (elem) {
return $(elem).closest('tr').find("td:first").text();

View File

@ -38,7 +38,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'upload'], function (
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);
$(".profile-user-img").prop("src", url);
Toastr.success("上传成功!");

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 () {
// 给上传按钮添加上传成功事件
$("#plupload-avatar").data("upload-success", function (data) {
$("#faupload-avatar").data("upload-success", function (data) {
var url = Fast.api.cdnurl(data.url);
$(".profile-user-img").prop("src", url);
Toastr.success(__('Upload successful'));

View File

@ -4,8 +4,7 @@ require.config({
name: 'moment',
location: '../libs/moment',
main: 'moment'
}
],
}],
//在打包压缩时将会把include中的模块合并到主文件中
include: ['css', 'layer', 'toastr', 'fast', 'backend', 'backend-init', 'table', 'form', 'dragsort', 'drag', 'drop', 'addtabs', 'selectpage'],
paths: {
@ -16,6 +15,7 @@ require.config({
'validator': 'require-validator',
'drag': 'jquery.drag.min',
'drop': 'jquery.drop.min',
'dropzone': 'dropzone.min',
'echarts': 'echarts.min',
'echarts-theme': 'echarts-theme',
'adminlte': 'adminlte',
@ -42,7 +42,6 @@ require.config({
'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll',
'validator-core': '../libs/nice-validator/dist/jquery.validator',
'validator-lang': '../libs/nice-validator/dist/local/zh-CN',
'plupload': '../libs/plupload/js/plupload.min',
'toastr': '../libs/toastr/toastr',
'jstree': '../libs/jstree/dist/jstree.min',
'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-lang': ['bootstrap-select'],
// 'toastr': ['css!../libs/toastr/toastr.min.css'],
'jstree': ['css!../libs/jstree/dist/themes/default/style.css',],
'plupload': {
deps: ['../libs/plupload/js/moxie.min'],
exports: "plupload"
},
'jstree': ['css!../libs/jstree/dist/themes/default/style.css'],
// 'layer': ['css!../libs/fastadmin-layer/dist/theme/default/layer.css'],
// 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'],
'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上传元素事件
if ($(".plupload", form).size() > 0) {
Upload.api.plupload($(".plupload", form));
Form.events.faupload(form);
},
/**
* 绑定上传事件
* @param form
*/
faupload: function (form) {
//绑定上传元素事件
if ($(".plupload,.faupload", form).size() > 0) {
Upload.api.upload($(".plupload,.faupload", form));
}
},
faselect: function (form) {
@ -495,7 +507,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
events.datetimepicker(form);
events.plupload(form);
events.faupload(form);
events.faselect(form);

View File

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

View File

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

View File

@ -57,7 +57,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
valign: 'middle',
},
config: {
firsttd: 'tbody tr td:first-child:not(:has(div.card-views))',
firsttd: 'tbody>tr>td.bs-checkbox',
toolbar: '.toolbar',
refreshbtn: '.btn-refresh',
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.config.refreshbtn, toolbar).find(".fa").removeClass("fa-spin");
$(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 () {
$(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);
}).drag(function (ev, dd) {
$(dd.proxy).css({
@ -184,7 +185,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
}).drag("end", function (ev, dd) {
$(dd.proxy).remove();
});
$(Table.config.firsttd, table).drop("start", function () {
firsttd.drop("start", function () {
Table.api.toggleattr(this);
}).drop(function () {
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) {
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({
url: options.extend.import_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 data = element ? $(element).data() : {};
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);
url = this.replaceurl(url, {ids: ids}, table);
var url = typeof data.url !== "undefined" ? Table.api.replaceurl(data.url, {ids: ids}, table) : (action == "del" ? options.extend.del_url : options.extend.multi_url);
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}};
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 = {
list: {},
options: {},
config: {
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>',
},
events: {
onInit: function (up) {
//修复少数安卓浏览器无法上传图片的Bug
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) {
//初始化
onInit: function () {
},
//上传成功的回调
onUploadSuccess: function (up, ret) {
var button = up.settings.button;
var onUploadSuccess = up.settings.onUploadSuccess;
onUploadSuccess: function (up, ret, file) {
var button = up.element;
var onUploadSuccess = up.options.onUploadSuccess;
var data = typeof ret.data !== 'undefined' ? ret.data : null;
//上传成功后回调
if (button) {
@ -92,9 +51,9 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
}
},
//上传错误的回调
onUploadError: function (up, ret) {
var button = up.settings.button;
var onUploadError = up.settings.onUploadError;
onUploadError: function (up, ret, file) {
var button = up.element;
var onUploadError = up.options.onUploadError;
var data = typeof ret.data !== 'undefined' ? ret.data : null;
if (button) {
var onDomUploadError = $(button).data("upload-error");
@ -119,7 +78,7 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
Toastr.error(ret.msg + "(code:" + ret.code + ")");
},
//服务器响应数据后
onUploadResponse: function (response) {
onUploadResponse: function (response, up, file) {
try {
var ret = typeof response === 'object' ? response : JSON.parse(response);
if (!ret.hasOwnProperty('code')) {
@ -132,8 +91,8 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
},
//上传全部结束后
onUploadComplete: function (up, files) {
var button = up.settings.button;
var onUploadComplete = up.settings.onUploadComplete;
var button = up.element;
var onUploadComplete = up.options.onUploadComplete;
if (button) {
var onDomUploadComplete = $(button).data("upload-complete");
if (onDomUploadComplete) {
@ -157,8 +116,8 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
}
},
api: {
//Plupload上传
plupload: function (element, onUploadSuccess, onUploadError, onUploadComplete) {
//上传接口
upload: function (element, onUploadSuccess, onUploadError, onUploadComplete) {
element = typeof element === 'undefined' ? Upload.config.classname : element;
$(element, Upload.config.container).each(function () {
if ($(this).attr("initialized")) {
@ -169,6 +128,7 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
var id = $(this).prop("id");
var url = $(this).data("url");
var maxsize = $(this).data("maxsize");
var maxcount = $(this).data("maxcount");
var mimetype = $(this).data("mimetype");
var multipart = $(this).data("multipart");
var multiple = $(this).data("multiple");
@ -189,65 +149,136 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
multipart = typeof multipart !== "undefined" ? multipart : Config.upload.multipart;
//是否支持批量上传
multiple = typeof multiple !== "undefined" ? multiple : Config.upload.multiple;
var mimetypeArr = new Array();
//支持后缀和Mimetype格式,以,分隔
if (mimetype && mimetype !== "*" && mimetype.indexOf("/") === -1) {
var tempArr = mimetype.split(',');
for (var i = 0; i < tempArr.length; i++) {
mimetypeArr.push({title: __('Files'), extensions: tempArr[i]});
}
mimetype = mimetypeArr;
}
//生成Plupload实例
Upload.list[id] = new Plupload.Uploader({
runtimes: 'html5,flash,silverlight,html4',
multi_selection: multiple, //是否允许多选批量上传
browse_button: id, // 浏览按钮的ID
container: $(this).parent().get(0), //取按钮的上级元素
flash_swf_url: '/assets/libs/plupload/js/Moxie.swf',
silverlight_xap_url: '/assets/libs/plupload/js/Moxie.xap',
drop_element: [id, $(this).data("input-id")],
filters: {
max_file_size: maxsize,
mime_types: mimetype,
},
//后缀特殊处理
mimetype = mimetype.split(",").map(function (k) {
return k.indexOf("/") > -1 ? k : (!k || k === "*" || k.charAt(0) === "." ? k : "." + k);
}).join(",");
//最大文件限制转换成mb
var maxFilesize = (function (maxsize) {
var matches = maxsize.toString().match(/^([0-9\.]+)(\w+)$/);
var size = matches ? parseFloat(matches[1]) : parseFloat(maxsize),
unit = matches ? matches[2].toLowerCase() : 'b';
var unitDict = {'b': 0, 'k': 1, 'kb': 1, 'm': 2, 'mb': 2, 'gb': 3, 'g': 3, 'tb': 4, 't': 4};
var y = typeof unitDict[unit] !== 'undefined' ? unitDict[unit] : 0;
var bytes = size * Math.pow(1024, y);
return bytes / Math.pow(1024, 2);
}(maxsize));
var options = $("#" + id).data() || {};
delete options.success;
delete options.url;
multipart = $.isArray(multipart) ? {} : multipart;
Upload.list[id] = new Dropzone("#" + id, $.extend({
url: url,
multipart_params: $.isArray(multipart) ? {} : multipart,
init: {
Init: Upload.events.onInit,
PostInit: Upload.events.onPostInit,
FilesAdded: Upload.events.onFileAdded,
BeforeUpload: Upload.events.onBeforeUpload,
UploadProgress: function (up, file) {
var button = up.settings.button;
$(button).prop("disabled", true).html("<i class='fa fa-upload'></i> " + __('Upload') + file.percent + "%");
Upload.events.onUploadProgress(up, file);
},
FileUploaded: function (up, file, info) {
var button = up.settings.button;
//还原按钮文字及状态
$(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);
params: function (files, xhr, chunk) {
var params = multipart;
if (chunk) {
return $.extend({}, params, {
filesize: chunk.file.size,
filename: chunk.file.name,
chunkid: chunk.file.upload.uuid,
chunkindex: chunk.index,
chunkcount: chunk.file.upload.totalChunkCount,
chunksize: this.options.chunkSize,
chunkfilesize: chunk.dataBlock.data.size,
width: chunk.file.width || 0,
height: chunk.file.height || 0,
type: chunk.file.type,
});
}
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,
onUploadError: onUploadError,
onUploadComplete: onUploadComplete,
button: that
});
}, Upload.options, options));
//拖动排序
if (preview_id && multiple) {
@ -335,70 +366,46 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
});
}
if (input_id) {
//粘贴上传
$("body").on('paste', "#" + input_id, function (event) {
var that = this;
var image, pasteEvent;
pasteEvent = event.originalEvent;
if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
image = Upload.api.getImageFromClipboard(pasteEvent);
if (image) {
event.preventDefault();
var button = $(".plupload[data-input-id='" + $(that).attr("id") + "']");
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");
});
//粘贴上传、拖拽上传
$("body").on('paste drop', "#" + input_id, function (event) {
var originEvent = event.originalEvent;
var button = $(".plupload[data-input-id='" + $(this).attr("id") + "'],.faupload[data-input-id='" + $(this).attr("id") + "']");
if (event.type === 'paste' && originEvent.clipboardData && originEvent.clipboardData.items) {
var items = originEvent.clipboardData.items;
if ((items.length === 1 && items[0].type.indexOf("text") > -1) || (items.length === 2 && items[1].type.indexOf("text") > -1)) {
} else {
Upload.list[button.attr("id")].paste(originEvent);
return false;
}
}
});
//拖拽上传
$("body").on('drop', "#" + input_id, function (event) {
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");
});
});
}
if (event.type === 'drop' && originEvent.dataTransfer && originEvent.dataTransfer.files) {
Upload.list[button.attr("id")].drop(originEvent);
return false;
}
});
}
Upload.list[id].init();
});
},
/**
* @deprecated Use upload instead.
*/
plupload: function (element, onUploadSuccess, onUploadError, onUploadComplete) {
return Upload.api.upload(element, onUploadSuccess, onUploadError, onUploadComplete);
},
// AJAX异步上传
send: function (file, onUploadSuccess, onUploadError, onUploadComplete) {
var index = Layer.msg(__('Uploading'), {offset: 't', time: 0});
var id = Plupload.guid();
var _onPostInit = Upload.events.onPostInit;
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");
var id = "dropzone-" + Dropzone.uuidv4();
$('<button type="button" id="' + id + '" class="btn btn-danger hidden faupload" />').appendTo("body");
$("#" + id).data("upload-complete", function (files) {
Upload.events.onPostInit = _onPostInit;
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: {
//自定义上传完成回调
@ -406,31 +413,6 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
console.log(this, response);
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;
}
.sp_container .msg-box {
position: absolute;
right: 0;
top: 0;
.sp_container {
.msg-box {
position: absolute;
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 {
@ -173,6 +193,12 @@ table.table-template {
padding: 10px 20px;
}
select.bs-select-hidden, select.selectpicker {
display: inherit !important;
max-height: 31px;
overflow: hidden;
}
.img-center {
margin: 0 auto;
display: inline;
@ -738,6 +764,7 @@ form.form-horizontal .control-label {
/*修复nice-validator新版下的一处BUG*/
.nice-validator {
input, select, textarea, [contenteditable] {
vertical-align: top;
display: inline-block;
*display: inline;
*zoom: 1;
@ -750,7 +777,7 @@ form.form-horizontal .control-label {
}
/*预览区域*/
.plupload-preview {
.plupload-preview, .faupload-preview {
padding: 0 10px;
margin-bottom: 0;
@ -1081,10 +1108,8 @@ table.table-nowrap {
}
}
.n-bootstrap {
.input-group > .n-right {
position: absolute;
}
.input-group > .msg-box.n-right {
position: absolute;
}
@media (min-width: 564px) {

View File

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

View File

@ -40,7 +40,7 @@ body {
border:none;
}
.navbar-nav {
li > a {
> li > a {
font-size:14px;
}
}
@ -57,7 +57,7 @@ body {
}
/*预览区域*/
.plupload-preview {
.plupload-preview,.faupload-preview {
padding:0 10px;
margin-bottom:0;
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 {
min-width:94px;
}