当前位置: 首页 > news >正文

layui 表格行级 upload 上传操作

一、背景:

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

二、实现逻辑:

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

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

<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:定位当前行的上传元素

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

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

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

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

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

// 1. 销毁旧实例(避免多实例冲突)
if (self.uploadInst) {self.uploadInst.destroy();
}// 2. 重新初始化upload组件(绑定当前行元素+当前pluginId)
self.uploadInst = upload.render({elem: $uploadBtn, // 绑定当前行的文件选择按钮url: '/AuxiliaryTool/AuxiliaryToolFileUploadService?pluginId=' + pluginId, // 携带当前行的pluginIdauto: 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 回调,执行以下操作:

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

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

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

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

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

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 回调):关闭加载层,提示错误:

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

附完整代码:

<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>
}

http://www.dtcms.com/a/402939.html

相关文章:

  • 【Unity 入门教程】三、如何设置自定义字体(解决中文乱码问题)
  • STM32开发(FreeRTOS实时操作系统)
  • RocketMQ-生产常见问题汇总
  • 成都网站托管外包施工企业科技宣传片
  • 小厂 Java 面试,难度怎么样?
  • Webpack5 第一节
  • 【深入理解JVM】常见的垃圾回收器
  • 东莞企业建设网站官网有限公司百度一下百度网页版主页
  • 【大模型:知识图谱】--7.Neo4j数据库的导入和导出
  • 数据结构与算法(栈)
  • Coze源码分析-资源库-创建数据库-后端源码-基础设施/数据存储层
  • PySpark 安装教程及 WordCount 实战与任务提交
  • 制作网站的公司八大建筑央企排名
  • zynq纯PL读取XADC
  • 【FastMCP】中间件
  • bigo二面总结
  • 个人网站建设思路省级别网站建设方案
  • 测试自动化教程:Parasoft如何流重定向与单元测试自动化
  • 开源AI大模型、AI智能名片与S2B2C商城小程序在价值观型社群构建与运营中的价值与应用
  • 郑州 网站建设公司阿里企业邮箱收费标准一年多少钱
  • Day03:小程序的常用操作
  • 交互的脉络:小程序事件系统详解
  • 自助建站免费平台深圳建设管理中心网站首页
  • LVS虚拟调度器学习
  • 【LVS入门宝典】LVS-TUN模式原理与配置:跨越网络界限的负载均衡解决方案
  • 【LVS入门宝典】LVS-TUN模式配置实战以及配置关键点:Real Server的路由表调整、ipip模块加载
  • LVS、Nginx、HAProxy 的区别
  • 是什么让边缘电脑真正工业化?
  • html5手机网站开发环境怎样建设淘宝客导购网站
  • 国检集团官网UI设计展示——专业界面设计实力呈现