Compare commits

...

17 Commits

Author SHA1 Message Date
邹景立
ab4845c28c Update Plugs.php 2022-07-11 23:35:02 +08:00
邹景立
150e1f16bf Update full.html 2022-07-11 23:11:27 +08:00
Anyon
464faac034 Update Module.php 2022-07-11 20:14:18 +08:00
Anyon
a0858a03ac Update 2022.07.11.01.md 2022-07-11 20:10:42 +08:00
Anyon
307030ed6f Update 2022.07.11.01.md 2022-07-11 20:08:54 +08:00
Anyon
c08c75d61f 修改版本描述 2022-07-11 20:06:17 +08:00
Anyon
d35a82a74a Update 2022.07.11.01.md 2022-07-11 20:04:34 +08:00
Anyon
bebae75691 修改最新版本描述 2022-07-11 20:02:12 +08:00
Anyon
8bb6262ccc Update upload.js 2022-07-11 19:32:15 +08:00
Anyon
119ca2dd4e Update admin.js 2022-07-11 19:30:47 +08:00
Anyon
64e0b8aa4d 修改图片裁剪工具 2022-07-11 19:27:15 +08:00
Anyon
43a2e04f03 增加图片裁剪参数
data-cut-width
data-cut-height
2022-07-11 19:23:45 +08:00
Anyon
77641b101c 用户头像增加压缩 2022-07-11 19:11:04 +08:00
Anyon
0c6628e1a3 商品图片增加压缩 2022-07-11 19:10:35 +08:00
Anyon
913f491fdf 升级layui版本 2022-07-11 19:05:41 +08:00
Anyon
15ac93059b 修改文件上传 2022-07-11 19:04:14 +08:00
Anyon
a61baf954a Update Upload.php 2022-07-11 16:21:18 +08:00
16 changed files with 82 additions and 110 deletions

View File

@ -64,7 +64,7 @@ class Module extends Controller
$pattern = "|^(\d{4})\.(\d{2})\.(\d{2})\.(\d+)$|";
$this->module['change'] = array_reverse($this->module['change']);
foreach ($this->module['change'] as $version => &$change) {
$change = ['content' => $change, 'version' => preg_replace($pattern, '$1年$2月$3日 第 $4 次更新', $version)];
$change = ['content' => $change, 'version' => preg_replace($pattern, '$1年$2月$3日 更新', $version)];
}
$this->fetch();
} else {

View File

@ -47,16 +47,14 @@ class Plugs extends Controller
*/
public function script(): \think\Response
{
$debug = $this->app->isDebug() ? 'true' : 'false';
$editor = sysconf("base.editor") ?: "ckeditor4";
$taRoot = sysuri("admin/index/index", [], false);
$taDebug = $this->app->isDebug() ? 'true' : 'false';
$taAdmin = sysuri('admin/index/index', [], false);
$taEditor = sysconf('base.editor') ?: 'ckeditor4';
return response(join("\n", [
"window.taDebug = {$debug};",
"window.taAdmin = '{$taRoot}';",
"window.taEditor = '{$editor}';",
]))->header([
'Content-Type' => 'application/x-javascript'
]);
"window.taDebug = {$taDebug};",
"window.taAdmin = '{$taAdmin}';",
"window.taEditor = '{$taEditor}';",
]))->contentType('application/x-javascript');
}
/**

View File

@ -77,9 +77,10 @@ class Upload extends Controller
'hash.require' => '哈希不能为空!',
'xext.require' => '后缀不能为空!',
'size.require' => '大小不能为空!',
'mime.require' => "类型不能为空!",
'mime.default' => '',
'status.value' => 1
]));
if (empty($file['mime'])) $file['mime'] = Storage::mime($file['xext']);
if ($info = Storage::instance($data['uptype'])->info($data['key'], $safe, $name)) {
$file->save(['xurl' => $info['url'], 'isfast' => 1, 'issafe' => $data['safe']]);
$extr = ['id' => $file->id ?? 0, 'url' => $info['url'], 'key' => $info['key']];
@ -128,8 +129,8 @@ class Upload extends Controller
$data = $this->_vali([
'id.require' => '编号不能为空!',
'hash.require' => '哈希不能为空!',
'uuid.value' => AdminService::getUserId()
]);
$data['uuid'] = AdminService::getUserId();
$file = SystemFile::mk()->where($data)->findOrEmpty();
if ($file->isEmpty()) $this->error('文件不存在!');
if ($file->save(['status' => 2])) {

View File

@ -1,2 +0,0 @@
* 系统模块初始化成功
* 这次更新了许多内容哦

View File

@ -1 +1,7 @@
* 少量更新修复部分BUG
* 系统模块初始化成功
* 这次更新了许多内容哦
* 少量更新修复部分BUG
* 修正系统用户搜索
* 系统菜单支持外链
* 修复任意下载问题
* 优化模块管理机制

View File

@ -1,2 +0,0 @@
* 修正系统用户搜索
* 系统菜单支持外链

View File

@ -1,2 +0,0 @@
* 修复任意下载问题
* 优化模块管理机制

View File

@ -0,0 +1,13 @@
* 增加 CORS 跨域规则配置,配置参数置放于 config/app.php需要更新 ThinkLibrary。
* 修复 layui.table 导致基于 ThinkPHP 模板输出自动转义 XSS 过滤机制失效,需要更新 ThinkLibrary。
* 修复在模板中使用 {:input(NAME)} 取值而产生的 XSS 问题,模板取值更换为 {$get.NAME|default=''}。
* 修复 CKEDITOR4 配置文件禁用所有标签的on事件阻止 xss 脚本注入,需要更新 ckeditor/config.js。
* 修复上传入口的后缀验证,读取真实文件后缀与配置对比,阻止不合法的文件上传并存储到本地服务器。
* 修改 JsonRpc 接口异常处理机制,当服务端绑定 Exception 时,客户端将能收到 error 消息及异常数据。
* 修改 location.hash 访问机制禁止直接访问外部URL资源链接防止外部XSS攻击读取本地缓存数据。
* 增加后台主题样式配置,支持全局默认+用户个性配置,需要更新 ThinkLibrary, static, admin 组件及模块。
* 升级 layui 版本到 2.7.4,引入 jszip 插件
* 框架核心服务层静态化重构,增加后台皮肤样式选择与配置
* 重构文件上传机制,增加文件记录,增加进度 tips 提示,增加图片压缩
* 引入 ckeditor5 富文本编辑器,同时保存原 ckeditor4 版本,可后台切换
* 全局改写 layTable 动态数据表格,支持静态刷新,同时兼容原生 table 方式

View File

@ -1,6 +1,6 @@
{
"name": "admin",
"author": "Anyon",
"version": "2022.03.06.01",
"version": "2022.07.11.01",
"content": "ThinkAdmin 系统基础模块"
}

View File

@ -15,6 +15,8 @@ define(['md5', 'notify'], function (SparkMD5, Notify, allowMime) {
this.option.quality = parseFloat(this.option.elem.data('quality') || '1.0');
this.option.maxWidth = parseInt(this.option.elem.data('max-width') || '0');
this.option.maxHeight = parseInt(this.option.elem.data('max-height') || '0');
this.option.cutWidth = parseInt(this.option.elem.data('cut-width') || '0');
this.option.cutHeight = parseInt(this.option.elem.data('cut-height') || '0');
/*! 查找表单元素, 如果没有找到将不会自动写值 */
if (!this.option.elem.data('input') && this.option.elem.data('field')) {
@ -29,17 +31,20 @@ define(['md5', 'notify'], function (SparkMD5, Notify, allowMime) {
/*! 初始化上传组件 */
this.adapter = new Adapter(this.option, layui.upload.render({
url: '{:url("admin/api.upload/file")}', auto: false, elem: elem, accept: 'file', multiple: this.option.mult, exts: this.option.exts.join('|'), acceptMime: this.option.mimes.join(','), choose: function (object) {
object.files = object.pushFile();
layui.each(object.files, function (idx, file) {
url: '{:url("admin/api.upload/file")}', auto: false, elem: elem, accept: 'file', multiple: this.option.mult, exts: this.option.exts.join('|'), acceptMime: this.option.mimes.join(','), choose: function (obj) {
obj.items = [], obj.files = obj.pushFile();
layui.each(obj.files, function (idx, file) {
obj.items.push(file);
file.quality = that.option.quality;
file.maxWidth = that.option.maxWidth;
file.maxHeight = that.option.maxHeight;
file.cutWidth = that.option.cutWidth;
file.cutHeight = that.option.cutHeight;
});
that.adapter.event('upload.choose', object.files);
that.adapter.upload(object.files, done);
layui.each(object.files, function (idx) {
delete object.files[idx];
that.adapter.event('upload.choose', obj.items);
that.adapter.upload(obj.items, done);
layui.each(obj.files, function (idx) {
delete obj.files[idx];
});
}
}));
@ -74,14 +79,17 @@ define(['md5', 'notify'], function (SparkMD5, Notify, allowMime) {
// 禁传异常状态文件
if (typeof file.xstate === 'number' && file.xstate === -1) return;
// 图片限宽限高压缩
if (/^image\/*$/.test(file.type) && file.maxWidth > 0 || file.maxHeight > 0 || file.quality !== 1) {
FileToBase64(file).then(function (base64) {
ImageToThumb(base64, file).then(function (base64) {
files[index] = Base64ToFile(base64, file.name);
files[index].notify = file.notify;
that.hash(files[index]).then(function (file) {
that.event('upload.hash', file).request(file, done);
});
if (/^image\//.test(file.type) && (file.maxWidth + file.maxHeight + file.cutWidth + file.cutHeight > 0 || file.quality !== 1)) {
require(['compressor'], function (Compressor) {
new Compressor(file, {
quality: file.quality, resize: 'cover', width: file.cutWidth || 0, height: file.cutHeight || 0, maxWidth: file.maxWidth, maxHeight: file.maxHeight, success(blob) {
files[index] = blob, blob.index = file.index, files[index].notify = file.notify;
that.hash(files[index]).then(function (file) {
that.event('upload.hash', file).request(file, done);
});
}, error: function () {
that.event('upload.error', {file: file}, file, '压缩失败');
}
});
});
} else {
@ -132,7 +140,7 @@ define(['md5', 'notify'], function (SparkMD5, Notify, allowMime) {
uploader.form.append('authorization', ret.data['authorization']);
uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
}
uploader.form.append('file', file), jQuery.ajax({
uploader.form.append('file', file, file.name), jQuery.ajax({
url: uploader.url, data: uploader.form, type: 'post', xhr: function (xhr) {
xhr = new XMLHttpRequest();
return xhr.upload.addEventListener('progress', function (event) {
@ -263,61 +271,6 @@ define(['md5', 'notify'], function (SparkMD5, Notify, allowMime) {
return UploadAdapter;
/**
* Base64 File 对象
* @param {String} base64 Base64内容
* @param {String} filename 新文件名称
* @return {File}
*/
function Base64ToFile(base64, filename) {
var arr = base64.split(',');
var mime = arr[0].match(/:(.*?);/)[1], suffix = mime.split('/')[1];
var bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) u8arr[n] = bstr.charCodeAt(n);
return new File([u8arr], filename + '.' + suffix, {type: mime});
}
/**
* File 对象转 Base64
* @param {File} file 文件对象
* @return {Promise}
*/
function FileToBase64(file) {
var defer = jQuery.Deferred(), reader = new FileReader();
return (reader.onload = function () {
defer.resolve(this.result);
}), reader.readAsDataURL(file), defer.promise();
}
/**
* 图片压缩处理
* @param {String} url 图片链接
* @param {Object} option 压缩参数
* @return {Promise}
*/
function ImageToThumb(url, option) {
var defer = jQuery.Deferred(), image = new Image();
image.src = url, image.onload = function () {
var canvas = document.createElement('canvas'), context = canvas.getContext('2d');
option.maxWidth = option.maxWidth || this.width, option.maxHeight = option.maxHeight || this.height;
var originWidth = this.width, originHeight = this.height, targetWidth = originWidth, targetHeight = originHeight;
if (originWidth > option.maxWidth || originHeight > option.maxHeight) {
if (originWidth / option.maxWidth > option.maxWidth / option.maxHeight) {
targetWidth = option.maxWidth;
targetHeight = Math.round(option.maxWidth * (originHeight / originWidth));
} else {
targetHeight = option.maxHeight;
targetWidth = Math.round(option.maxHeight * (originWidth / originHeight));
}
}
canvas.width = targetWidth, canvas.height = targetHeight;
context.clearRect(0, 0, targetWidth, targetHeight);
context.drawImage(this, 0, 0, targetWidth, targetHeight);
defer.resolve(canvas.toDataURL('image/jpeg', option.quality || 0.9));
};
return defer.promise();
}
/**
* 上传状态提示扩展插件
* @param {File} file 文件对象

View File

@ -15,11 +15,7 @@
<link rel="stylesheet" href="__ROOT__/static/extra/style.css?at={:date('md')}">
{block name="style"}{/block}
<script src="__ROOT__/static/plugs/jquery/pace.min.js"></script>
<script>
window.taDebug = '{:intval($app->isDebug())}' > 0;
window.taEditor = '{:sysconf("base.editor")?:"ckeditor4"}';
window.tapiRoot = '{:sysuri("admin/index/index",[],false)}';
</script>
<script src="{:url('admin/api.plugs/script',[],false,false)}"></script>
</head>
<body class="layui-layout-body">
{block name='body'}

View File

@ -6,7 +6,7 @@
<div class="layui-row layui-col-space15">
<div class="layui-col-xs2 text-center padding-top-15">
<input type="hidden" name="headimg" value="{$vo.headimg|default=''}">
<input type="hidden" data-cut-width="500" data-cut-height="500" data-max-width="500" data-max-height="500" name="headimg" value="{$vo.headimg|default=''}">
<script>$('[name=headimg]').uploadOneImage();</script>
</div>
<div class="layui-col-xs5">

View File

@ -82,13 +82,13 @@
<tr>
<td class="text-center text-top padding-0">
<div class="help-images">
<input name="cover" type="hidden" value="{$vo.cover|default=''}">
<input name="cover" data-max-width="500" data-max-height="500" type="hidden" value="{$vo.cover|default=''}">
<script>$('[name="cover"]').uploadOneImage();</script>
</div>
</td>
<td class="text-left padding-0">
<div class="help-images">
<input name="slider" type="hidden" value="{$vo.slider|default=''}">
<input name="slider" data-max-width="2048" data-max-height="1024" type="hidden" value="{$vo.slider|default=''}">
<script>$('[name="slider"]').uploadMultipleImage();</script>
</div>
</td>

View File

@ -70,6 +70,7 @@ require.config({
'filesaver': ['plugs/jquery/filesaver.min'],
'websocket': ['plugs/socket/websocket'],
'pcasunzips': ['plugs/jquery/pcasunzips'],
'compressor': ['plugs/jquery/compressor.min'],
'sortablejs': ['plugs/sortable/sortable.min'],
'vue.sortable': ['plugs/sortable/vue.draggable.min'],
'jquery.ztree': ['plugs/ztree/ztree.all.min'],
@ -604,7 +605,11 @@ $(function () {
event.stopPropagation(), $in.val() && $.previewImage(encodeURI($in.val()));
}).on('click', 'i.layui-icon-close', function (event) {
event.stopPropagation(), $bt.attr('style', ''), $in.val('');
}).find('[data-file]').data('input', this).attr('data-size', $in.data('size') || 0).attr('data-type', $in.data('type') || 'png,jpg,gif,jpeg');
}).find('[data-file]').data('input', this).attr({
'data-size': $in.data('size') || 0, 'data-type': $in.data('type') || 'gif,png,jpg,jpeg',
'data-max-width': $in.data('max-width') || 0, 'data-max-height': $in.data('max-height') || 0,
'data-cut-width': $in.data('cut-width') || 0, 'data-cut-height': $in.data('cut-height') || 0,
});
});
};
@ -614,7 +619,11 @@ $(function () {
if ($(this).data('inited')) return true; else $(this).data('inited', true);
var $bt = $('<div class="uploadimage"><span><a data-file="mul" class="layui-icon layui-icon-upload-drag"></a></span><span data-file="images"></span></div>');
var ims = this.value ? this.value.split('|') : [], $in = $(this).after($bt);
$bt.find('[data-file]').attr('data-size', $in.data('size') || 0).attr('data-type', $in.data('type') || 'gif,png,jpg,jpeg').on('push', function (evt, src) {
$bt.find('[data-file]').attr({
'data-size': $in.data('size') || 0, 'data-type': $in.data('type') || 'gif,png,jpg,jpeg',
'data-max-width': $in.data('max-width') || 0, 'data-max-height': $in.data('max-height') || 0,
'data-cut-width': $in.data('cut-width') || 0, 'data-cut-height': $in.data('cut-height') || 0,
}).on('push', function (evt, src) {
ims.push(src), $in.val(ims.join('|')), showImageContainer([src]);
}) && (ims.length > 0 && showImageContainer(ims));
@ -879,14 +888,6 @@ $(function () {
if (typeof this.dataset.file === 'string' && /^images?$/.test(this.dataset.file)) {
return $.form.modal(tapiRoot + '/api.upload/image', this.dataset, '图片选择器')
}
// 上传视频,支持单视频或多视频选择,分别是 video|videos
if (typeof this.dataset.file === 'string' && /^videos?$/.test(this.dataset.file)) {
return $.form.modal(tapiRoot + '/api.upload/video', this.dataset, '视频选择器')
}
// 上传文档,支持单视频或多视频选择,分别是 document|documents
if (typeof this.dataset.file === 'string' && /^videos?$/.test(this.dataset.file)) {
return $.form.modal(tapiRoot + '/api.upload/document', this.dataset, '文档选择器')
}
// 其他文件上传
if ($(this).data('inited') !== true) (function (that) {
that.uploadFile(undefined, function () {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long