thinkphp8+layui多图上传,带删除\排序功能

环境: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="删除图片">&times;</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),
            ],
        ]);
    }

这是后端的代码,欢迎大家指正!

相关推荐
李鸿耀3 小时前
React 项目 SVG 图标太难管?用这套自动化方案一键搞定!
前端
闲蛋小超人笑嘻嘻3 小时前
树形结构渲染 + 选择(Vue3 + ElementPlus)
前端·javascript·vue.js
叶梅树3 小时前
从零构建A股量化交易工具:基于Qlib的全栈系统指南
前端·后端·算法
巴博尔3 小时前
uniapp的IOS中首次进入,无网络问题
前端·javascript·ios·uni-app
焚 城3 小时前
UniApp 实现双语功能
javascript·vue.js·uni-app
Asthenia04124 小时前
技术复盘:从一次UAT环境CORS故障看配置冗余的危害与最佳实践
前端
csj504 小时前
前端基础之《React(1)—webpack简介》
前端·react
被巨款砸中4 小时前
前端 20 个零依赖浏览器原生 API 实战清单
前端·javascript·vue.js·web
文韬_武略4 小时前
web vue之状态管理Pinia
前端·javascript·vue.js