layui 表格行级 upload 上传操作

一、背景:

根据业务需要,要求在数据表格的行级操作和表级操作实现上传文件 的功能。表级上传操作使用layui自带的upload组件,通过绑定DOM元素就可以实现。而行操作却不行,因为upload的elem属性是必填项,必须要绑定DOM元素。由于行数据是动态且不确定的,经过一番研究,有了以下解决方案。

二、实现逻辑:

1、核心前提:行内上传元素的模板设计

首先,在表格操作列模板(#operationBar)中,为每一行都预留了独立的上传相关隐藏元素,这是行内上传的基础:

javascript 复制代码
<script type="text/html" id="operationBar">
    <div class="layui-clear-space">
        <!-- 操作下拉触发器 -->
        <a class="layui-btn layui-btn-xs" lay-event="operation">操作<i class="layui-icon layui-icon-down"></i></a >
        <!-- 行内独立的隐藏上传元素(用class而非id,避免重复) -->
        <input type="file" class="uploadBtn" style="display:none"> <!-- 文件选择按钮 -->
        <button class="uploadAction" style="display:none"></button> <!-- 上传触发按钮 -->
    </div>
</script>

每个表格行的上传元素是「独立的」,通过 class 标识(而非 id),避免多行列元素 ID 重复导致的绑定冲突。用户看不到这些元素,仅通过下拉菜单操作间接触发。

2、完整流程拆解:从点击到上传完成

阶段 1:用户触发「上传插件」操作

  • 下拉菜单触发 :用户点击某一行的「操作」按钮,触发 table.on('tool(pluginRegistrTable)') 事件(表格工具事件)。
  • 下拉菜单渲染 :通过 dropdown.render() 渲染包含「上传插件」的下拉菜单,点击「上传插件」时进入 menudata.id == 'uploadFile' 分支。

阶段 2:定位当前行的上传元素

进入「上传插件」分支后,首先要找到当前行专属的上传元素(避免操作其他行):

javascript 复制代码
// $this 是当前行的「操作」按钮元素
var $uploadBtn = $this.siblings('.uploadBtn'); // 当前行的文件选择按钮
var $uploadAction = $this.siblings('.uploadAction'); // 当前行的上传触发按钮

siblings() 方法定位:基于当前行的「操作」按钮,找到同一父容器下的 .uploadBtn.uploadAction,确保是当前行的元素。

阶段 3:销毁旧实例 + 重新初始化上传组件

由于每行的 pluginId 不同(上传接口需要携带当前插件的 ID),必须动态初始化 upload 组件(而非页面加载时初始化):

javascript 复制代码
// 1. 销毁旧实例(避免多实例冲突)
if (self.uploadInst) {
    self.uploadInst.destroy();
}

// 2. 重新初始化upload组件(绑定当前行元素+当前pluginId)
self.uploadInst = upload.render({
    elem: $uploadBtn, // 绑定当前行的文件选择按钮
    url: '/AuxiliaryTool/AuxiliaryToolFileUploadService?pluginId=' + pluginId, // 携带当前行的pluginId
    auto: false, // 关闭自动上传(需手动触发)
    bindAction: $uploadAction, // 绑定当前行的上传触发按钮
    accept: 'file', // 允许所有文件类型(可限制为zip)
    exts: 'zip', // 仅允许zip格式文件
    // 以下是回调函数,按执行顺序触发
    before: function(obj) { /* 上传前触发 */ },
    choose: function(obj) { /* 选择文件后触发 */ },
    done: function(res) { /* 上传成功后触发 */ },
    error: function(index, upload) { /* 上传失败后触发 */ }
});

阶段 4:触发文件选择 + 上传

触发文件选择 :通过 $uploadBtn.click() 模拟点击隐藏的文件选择按钮,弹出系统文件选择窗口。

用户选择文件后 :进入 choose 回调,执行以下操作:

javascript 复制代码
choose: function(obj) {
    console.log('选择文件', obj); // 调试日志
    // 预览文件(可选,这里用于确认文件信息)
    obj.preview(function(index, file, result) {
        console.log('当前选择的文件', file); // 打印文件名、大小等信息
        $uploadAction.click(); // 手动触发上传(因为auto:false)
    });
}

阶段 5:上传过程中的回调执行

上传前(before 回调):显示加载层,提示用户 "正在上传":

javascript 复制代码
before: function(obj) {
    console.log('开始上传', obj);
    layer.load(); // 显示Layui加载层
}

上传成功(done 回调):关闭加载层,处理后端返回结果:

javascript 复制代码
done: function(res) {
    layer.closeAll('loading'); // 关闭加载层
    console.log('上传结果', res); // 打印后端返回的结果
    if (res.Code != 0) { // 后端返回失败(假设Code=0为成功)
        layer.alert(res.Data || res.Msg, { icon: 2 }); // 提示失败原因
        return;
    }
    // 上传成功:提示+刷新表格
    layer.msg("上传成功!", { icon: 1 });
    table.reloadData('pluginRegistrTable'); // 刷新表格,显示最新状态
}

上传失败(error 回调):关闭加载层,提示错误:

javascript 复制代码
error: function(index, upload) {
    layer.closeAll('loading');
    console.error('上传错误', index, upload); // 打印错误信息(方便调试)
    layer.alert('上传失败,请重试', { icon: 2 });
}

附完整代码:

html 复制代码
<style>
    .layui-table {
        margin-top: -20px;
    }
</style>

<table class="layui-table" id="pluginRegistrTable" lay-filter="pluginRegistrTable" style="margin-top:2px;"></table>

<script type="text/html" id="IsPluginSet">
    <input type="checkbox" title="是|否" lay-skin="switch" disabled {{ d.IsPluginSet?`checked`:`` }}>
</script>

<script type="text/html" id="headToolbar">
    <div class="layui-btn-container layui-clear">
        <a class="layui-btn" lay-event="add" href=" " title="注册插件"><i class="layui-icon layui-icon-addition"></i>注册插件</a >
        <a class="layui-btn" lay-event="delete" href="javascript:;" title="删除插件" style="background-color:red;"><i class="layui-icon layui-icon-delete"></i>删除插件</a >
    </div>
</script>

<!-- 修复:将ID改为class,避免重复 -->
<script type="text/html" id="operationBar">
    <div class="layui-clear-space">
        <a class="layui-btn layui-btn-xs" lay-event="operation">
            操作
            <i class="layui-icon layui-icon-down"></i>
        </a >
        <!-- 隐藏的上传元素(使用class而非id) -->
        <input type="file" class="uploadBtn" style="display:none">
        <button class="uploadAction" style="display:none"></button>
    </div>
</script>

@section Script{
    <script src="~/Content/static/js/promise.js"></script>
    <script src="~/Content/static/js/fingerprintjs.js"></script>
    <script type="text/javascript">
        layui.use(['form', 'table', 'jquery', 'laypage', 'laytpl', "urp", "element", "dropdown", "openSelect", "format",'upload'], function () {
            var $ = layui.jquery;
            var form = layui.form;
            var table = layui.table;
            var urp = layui.urp;
            var dropdown = layui.dropdown;
            var format = layui.format;
            var upload = layui.upload;
            var layer = layui.layer; // 补充layer引用

            var module = {
                trData: {},
                uploadInst: null, // 上传实例变量
                init: function () {
                    this.renderTable();
                    this.initEvent();
                    this.callback();
                }
                ,renderTable: function () {
                    table.render({
                        elem: '#pluginRegistrTable',
                        method: 'post',
                        url: '/AuxiliaryTool/GetPluginRegistrListService?pluginId=' + "" + "&parentPluginId=",
                        height: 'full-35',
                        toolbar: "#headToolbar",
                        defaultToolbar: "",
                        page: true,
                        autoSort: false,
                        limit: 20,
                        limits: [10, 20, 25, 30, 50, 100, 200],
                        cols: [[
                            { checkbox: true, fixed: true }
                            , { field: 'PluginId', title: '插件Id', width: 300 }
                            , { field: 'PluginName', title: '插件名称', width: 180, }
                            , { field: 'PluginDescription', title: '插件说明', width: 600, }
                            , { field: 'ReleaseStatusName', title: '发布状态', width: 180, }
                            , { field: 'IsPluginSet', title: '插件集', width: 100, templet: "#IsPluginSet" }
                            , { field: 'Operation', title: '操作', toolbar: '#operationBar', align: 'center', fixed: 'right', width: 120, minWidth: 120 }
                        ]],
                        parseData: function (res) {
                            var count = res.Data == null ? 0 : res.Data.length;
                            return {
                                "code": res.Code,
                                "msg": res.Msg,
                                "data": res.Data,
                                "count": count,
                            };
                        }
                    });
                }
                , registerPlugin: function (isReadonly, pluginId, operateType) {
                    var title = "";
                    if (isReadonly) {
                        title = "查看插件详情";
                    }
                    else {
                        if (operateType == "add") {
                            title = "注册插件";
                        } else if (operateType == "edit") {
                            title = "编辑插件";
                        }
                    }

                    urp.openWindow({
                        href: "/AuxiliaryTool/RegistPlugin?pluginId=" + pluginId + '&isReadonly=' + isReadonly + "&operateType=" + operateType,
                        title: title,
                        area: ["80%", "80%"],
                        callbackFunction: 'fn',
                        target: 'parent',
                        initOpt: {
                            maxmin: false,
                        },
                    });
                }
                , initEvent: function () {
                    var self = this;

                    //行双击事件
                    table.on('rowDouble(pluginRegistrTable)', function (obj) {
                        var data = obj.data;
                        var id = data.PluginId;
                        if (id == "" || id == undefined) {
                            return;
                        }
                        self.registerPlugin(true, id,"display");
                    });

                    // 表格上方工具栏事件
                    table.on('toolbar(pluginRegistrTable)', function (obj) {
                        switch (obj.event) {
                            case 'add': {
                                self.registerPlugin(false, "", "add");
                                break;
                            }
                            case 'delete': {
                                var checkStatus = table.checkStatus('pluginRegistrTable')
                                var data = checkStatus.data;
                                if (data.length <= 0) {
                                    layer.msg("当前没有选择要删除的数据,请选择数据!");
                                    return;
                                }
                                if (data.length > 1) {
                                    layer.msg("当前操作只能选择一条数据!");
                                    return;
                                }
                                var pluginId = data[0].PluginId;
                                if (pluginId == "") {
                                    layer.alert("插件ID不能为空!", { icon: 2 });
                                    return;
                                }
                                urp.post("/AuxiliaryTool/DeletePluginRegistrService", { pluginId: pluginId }, function (data) {
                                    if (data.Code != 0) {
                                        layer.alert(data.Msg, { icon: 2 });
                                        return;
                                    }
                                    layer.msg(data.Msg, { icon: 1 });
                                    table.reloadData('pluginRegistrTable');
                                });
                                break;
                            }
                        };
                    });

                    //操作按钮事件(移到最后,确保元素已渲染)
                    table.on('tool(pluginRegistrTable)', function (obj) {
                        var data = obj.data;
                        self.trData = data;
                        var $this = $(this); // 当前操作元素

                        if (obj.event === 'operation') {
                            dropdown.render({
                                elem: $this,
                                show: true,
                                data: [{
                                    title: '编辑插件',
                                    id: 'edit'
                                }, {
                                    title: '查看插件',
                                    id: 'display'
                                },
                                {
                                    title: '上传插件',
                                    id: 'uploadFile',
                                },
                                {
                                    title: '发布',
                                    id: 'release'
                                }],
                                click: function (menudata) {
                                    var pluginId = data.PluginId;
                                    if (pluginId === "") {
                                        layer.msg("请选择数据!", { icon: 2 });
                                        return;
                                    }
                                    
                                    if (menudata.id == 'edit') {
                                        self.registerPlugin(false, pluginId, "edit");
                                    }
                                    else if (menudata.id == 'display') {
                                        self.registerPlugin(true, pluginId, "display");
                                    }
                                    else if (menudata.id == 'uploadFile') {
                                        // 修复:获取当前行的上传元素
                                        var $uploadBtn = $this.siblings('.uploadBtn');
                                        var $uploadAction = $this.siblings('.uploadAction');
                                        
                                        // 销毁旧实例,创建新实例(确保使用最新的pluginId)
                                        if (self.uploadInst) {
                                            self.uploadInst.destroy();
                                        }
                                        
                                        // 重新初始化上传组件
                                        self.uploadInst = upload.render({
                                            elem: $uploadBtn,
                                            url: '/AuxiliaryTool/AuxiliaryToolFileUploadService?pluginId=' + pluginId,
                                            auto: false,
                                            bindAction: $uploadAction,
                                            accept: 'file',
                                            exts: 'zip',
                                            // 增加调试信息
                                            before: function(obj){
                                                console.log('开始上传', obj);
                                                layer.load(); // 显示加载层
                                            },
                                            choose: function(obj){
                                                console.log('选择文件', obj);
                                                // 选择后自动触发上传
                                                obj.preview(function(index, file, result){
                                                    console.log('预览文件', file);
                                                    $uploadAction.click(); // 触发上传
                                                });
                                            },
                                            done: function (res) {
                                                layer.closeAll('loading'); // 关闭加载层
                                                console.log('上传结果', res);
                                                if (res.Code != 0) {
                                                    layer.alert(res.Data || res.Msg, { icon: 2 });
                                                    return;
                                                }
                                                layer.msg("上传成功!", { icon: 1 });
                                                table.reloadData('pluginRegistrTable');
                                            },
                                            error: function(index, upload){
                                                layer.closeAll('loading');
                                                console.error('上传错误', index, upload);
                                                layer.alert('上传失败,请重试', { icon: 2 });
                                            }
                                        });
                                        
                                        // 触发文件选择
                                        $uploadBtn.click();
                                    } else if (menudata.id == 'release') {
                                        urp.post("/AuxiliaryTool/ReleasePluginService", { pluginId: pluginId }, function (data) {
                                            if (data.Code != 0) {
                                                layer.alert(data.Msg, { icon: 2 });
                                                return;
                                            }
                                            layer.msg(data.Msg, { icon: 1 });
                                            table.reloadData('pluginRegistrTable');
                                        });
                                    }
                                }
                            });
                        }
                    });
                }
                , callback: function () {
                    var self = this;
                    urp.callbackFunction.fn = function (data) {
                        self.renderTable();
                    };
                }
            }
            module.init();
        });
    </script>
}
相关推荐
奶糖 肥晨3 小时前
前端Bug实录:为什么表格筛选条件在刷新时神秘消失?
前端·bug
艾小码3 小时前
告别复制粘贴!掌握这7个原则,让你的Vue组件复用性翻倍
前端·javascript·vue.js
我是ed6 小时前
# vite + vue3 实现打包后 dist 文件夹可以直接打开 html 文件预览
前端
小白64027 小时前
前端梳理体系从常问问题去完善-工程篇(webpack,vite)
前端·webpack·node.js
不老刘7 小时前
从构建工具到状态管理:React项目全栈技术选型指南
前端·react.js·前端框架
mCell9 小时前
ECharts 万字入门指南
前端·echarts·数据可视化
X01动力装甲9 小时前
@scqilin/phone-ui 手机外观组件库
前端·javascript·ui·智能手机·数据可视化
Dontla9 小时前
Edge浏览器CSDN文章编辑时一按shift就乱了(Edge shift键)欧路翻译问题(按Shift翻译鼠标所在段落)
前端·edge
lggirls9 小时前
私有证书不被edge浏览器认可的问题的解决-Debian13环境下
前端·edge