环境:thinkphp8.1\php8.3\layui2.10; layui的版本必须是2.8.8版本以上;
javascript
layui.define(['jquery', 'upload', 'layer'], function (exports) {
const $ = layui.jquery;
const upload = layui.upload;
const layer = layui.layer;
const multiUpload = {
/**
* 初始化上传组件
* @param {Object} options
*/
init: function (options) {
const opts = $.extend({
elemSelect: '#uploadBtn',
elemPreview: '#upload-preview',
elemStart: '#startUpload',
elemHidden: '#imagePaths',
uploadUrl: '/api/index/upload'
}, options);
let fileMap = new Map();
let imgIndex = 0;
// 选择图片 + 本地预览
upload.render({
elem: opts.elemSelect,
auto: false,
multiple: true,
choose: function (obj) {
const previewContainer = $(opts.elemPreview);
obj.preview(function (index, file, result) {
imgIndex++;
const id = 'img-' + imgIndex;
fileMap.set(id, file);
const card = $(`
<div class="img-card" id="${id}">
<img src="${result}" alt="${file.name}">
<button type="button" class="delete-btn" title="删除图片">×</button>
</div>
`);
card.find('.delete-btn').on('click', function () {
fileMap.delete(id);
card.remove();
updateImageList();
});
previewContainer.append(card);
});
}
});
// 拖拽排序
$(opts.elemPreview).sortable({
items: '.img-card',
cursor: 'move',
opacity: 0.7,
tolerance: 'pointer',
update: function () {
updateImageList();
}
});
// 更新隐藏字段
function updateImageList() {
const ids = $(opts.elemPreview + ' .img-card').map(function () {
return this.id;
}).get();
$(opts.elemHidden).val(ids.join('|'));
}
// 更新URL(仅相对路径)
function updateImageListUrls() {
const urls = [];
$(opts.elemPreview + ' .img-card').each(function () {
let url = $(this).attr('data-url');
if (url) {
url = url.replace(/^https?:\/\/[^/]+\/storage\//, '');
urls.push(url);
}
});
$(opts.elemHidden).val(urls.join('|'));
console.log('当前图片相对路径顺序:', urls.join('|'));
}
// 点击上传
$(opts.elemStart).on('click', function () {
const orderedIds = $(opts.elemPreview + ' .img-card').map(function () {
return this.id;
}).get();
if (orderedIds.length === 0) {
layer.msg('请先选择图片', { icon: 0 });
return;
}
const formData = new FormData();
for (const id of orderedIds) {
const file = fileMap.get(id);
if (file) formData.append('file[]', file);
}
layer.msg('上传中...', { icon: 16, shade: 0.3, time: 0 });
$.ajax({
url: opts.uploadUrl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
dataType: 'json'
})
.done(function (res) {
layer.closeAll('loading');
if (res.code === 0) {
const baseUrl = window.location.origin + '/storage/';
const urls = res.data.urls.map(p => baseUrl + p);
$(opts.elemPreview + ' .img-card').each(function (i) {
const img = $(this).find('img');
if (urls[i]) img.attr('src', urls[i]);
$(this).attr('data-url', urls[i]);
});
updateImageListUrls();
layer.msg('上传成功,共 ' + urls.length + ' 张', { icon: 1 });
} else {
layer.msg('上传失败:' + res.msg, { icon: 2 });
}
})
.fail(function (xhr, status, error) {
layer.closeAll('loading');
console.error('AJAX 错误:', status, error);
layer.msg('上传接口异常', { icon: 2 });
})
.always(function () {
console.log('上传请求结束');
});
});
// 暴露可用方法
return {
reload: updateImageListUrls,
clear: function () {
$(opts.elemPreview).empty();
$(opts.elemHidden).val('');
fileMap.clear();
}
};
}
};
// 输出模块
exports('multiUpload', multiUpload);
});
定义多图上传模块,放到 layui\modules\multiUpload.js;
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多图上传,预览,删除,排序(顺序同步)</title>
<link href="__ADMIN__/layui/css/layui.css" rel="stylesheet">
<link href="__INDEX__/jquery-ui/themes/base/jquery-ui.css" rel="stylesheet">
<script src="__INDEX__/jquery/jquery.js"></script>
<script src="__INDEX__/jquery-ui/jquery-ui.min.js"></script>
<script src="__ADMIN__/layui/layui.js"></script>
<link href="__INDEX__/upload/upload.css" rel="stylesheet">
</head>
<body>
<div class="layui-container">
<div class="layui-upload">
<div class="layui-btn-container">
<button type="button" class="layui-btn layui-btn-normal" id="uploadBtn">
<i class="layui-icon layui-icon-upload"></i> 选择多图
</button>
<button type="button" class="layui-btn layui-btn-danger" id="startUpload">
<i class="layui-icon layui-icon-release"></i> 开始上传
</button>
</div>
<blockquote class="layui-elem-quote layui-quote-nm">
预览图(可拖拽排序):
<div id="upload-preview" class="layui-upload-list"></div>
</blockquote>
<input type="hidden" id="imagePaths" name="images" value="">
</div>
</div>
<script>
layui.config({
base: '__ADMIN__/' // 模块目录路径
}).use(['index', 'multiUpload'], function () {
const multiUpload = layui.multiUpload;
// 初始化
multiUpload.init({
elemSelect: '#uploadBtn',
elemPreview: '#upload-preview',
elemStart: '#startUpload',
elemHidden: '#imagePaths',
uploadUrl: '/api/index/upload'
});
});
</script>
</body>
</html>
排序:这里我用到jquery-ui组件的排序功能,版本:1.14;jQuery3.7;
php
/**
* 多图上传接口
* 路径:/api/index/upload
*/
public function upload(): Json
{
// 获取前端统一上传字段 file[]
$files = Request::file('file');
if (empty($files)) {
return json(['code' => 1, 'msg' => '未选择文件']);
}
$paths = [];
try {
foreach ($files as $file) {
// 存入 storage/topic/YYYYMMDD/
$savePath = Filesystem::putFile("", $file);
// 统一路径格式为 /
$paths[] = str_replace('\\', '/', $savePath);
}
} catch (\Throwable $e) {
return json([
'code' => 2,
'msg' => '上传失败:' . $e->getMessage(),
]);
}
// 生成可访问 URL
$urls = array_map(fn($p) => $p, $paths);
return json([
'code' => 0,
'msg' => '上传成功',
'data' => [
'paths' => implode('|', $paths),
'urls' => $urls,
'count' => count($paths),
],
]);
}
这是后端的代码,欢迎大家指正!