ColdFusion大数据加载优化:实现懒加载与加载提示
优化CFM页面懒加载性能的方案
问题分析
你当前的页面性能瓶颈主要在于:
- 在
<cfloop>中逐个调用cfinvoke,每次调用都是同步请求,累积起来导致整体加载时间过长 - 页面需要等待所有数据处理完成后才会逐步渲染,用户体验差
结合你的业务场景(会议关联单位历史版本),我们可以通过前端AJAX懒加载+后端批量数据处理的方式来优化,同时加入加载提示提升用户体验。
步骤1:重构后端CFC,支持批量分页查询
首先,修改你的service/meetings和service/units相关方法,或者新增一个批量处理分页数据的CFC方法,避免循环调用cfinvoke。比如新增一个getPaginatedMeetings方法,一次性返回分页的会议数据,同时包含对应的单位版本信息:
<!--- service/meetings.cfc 新增方法 ---> <cffunction name="getPaginatedMeetings" access="remote" returntype="struct" output="false"> <cfargument name="yearSource" type="string" required="true"> <cfargument name="searchStatut" type="string" default=""> <cfargument name="searchNumber" type="string" default=""> <cfargument name="page" type="numeric" default="1"> <cfargument name="pageSize" type="numeric" default="10"> <cfset var result = {}> <cfset var qry_reunion = ""> <cfset var reunionIdList = ""> <cfset var meetingsUnit = ""> <cfset var meetingData = []> <!--- 1. 查询分页的会议数据 ---> <cftry> <cfquery name="qry_reunion" datasource="#application.datasource#"> SELECT R.* FROM REUNION R LEFT JOIN MODULES_V M on M.MOD_ID = R.I_MODULE_SIC_ID INNER JOIN APP_GEN_CODE_T B on B.ST_CODE = R.REUN_CNRE_ST_CODE INNER JOIN APP_GEN_CODE_T D on d.ST_CODE = R.CSRE_ST_CODE WHERE R.EXRC_NUMREXRC = <cfqueryparam value="#arguments.yearSource#" cfsqltype="cf_sql_varchar"> <cfif len(arguments.searchStatut)> AND R.CSRE_ST_CODE = <cfqueryparam value="#arguments.searchStatut#" cfsqltype="cf_sql_varchar"> </cfif> <cfif len(arguments.searchNumber)> AND R.REUN_NUMREURO = <cfqueryparam value="#arguments.searchNumber#" cfsqltype="cf_sql_varchar"> </cfif> ORDER BY R.REUN_NUMRREUN LIMIT <cfqueryparam value="#(arguments.page-1)*arguments.pageSize#" cfsqltype="cf_sql_integer">, <cfqueryparam value="#arguments.pageSize#" cfsqltype="cf_sql_integer"> </cfquery> <!--- 2. 批量获取单位标签数据 ---> <cfif qry_reunion.recordcount gt 0> <cfset reunionIdList = valueList(qry_reunion.REUN_NUMRREUN)> <cfinvoke component="service/meetings" method="getMeetingsUnitLabel" returnvariable="meetingsUnit"> <cfinvokeargument name="meetingsNumber" value="#reunionIdList#"> </cfinvoke> <!--- 3. 批量处理每个会议的单位版本信息 ---> <cfloop query="qry_reunion"> <cfset var unitVersion = {}> <cfinvoke component="service/units" method="getUnitVersionFromMeetingOrgId" returnvariable="unitVersion"> <cfinvokeargument name="meetingOrgId" value="#qry_reunion.ORG_ID#"> <cfinvokeargument name="meetingId" value="#qry_reunion.REUN_NUMRREUN#"> <cfinvokeargument name="unitsArray" value="#SerializeJSON(meetingsUnit.VALUES)#"> </cfinvoke> <cfset var unitInfo = {}> <cfif not StructIsEmpty(unitVersion)> <cfset unitInfo.unitLabel = unitVersion.VALUES.ORG_CD> <cfset unitInfo.isObsolete = (unitVersion.VALUES.OBSOLETE eq 1)> <cfset unitInfo.isEstat = (unitVersion.VALUES.IS_ESTAT eq 1)> <cfset unitInfo.validFrom = unitVersion.VALUES.UNIT_VALID_FROM> <cfset unitInfo.validTo = unitVersion.VALUES.UNIT_VALID_TO> <cfelse> <cfset unitInfo.unitLabel = "Error"> <cfset unitInfo.isObsolete = false> </cfif> <!--- 组装会议数据 ---> <cfset arrayAppend(meetingData, { reunNumReuro = qry_reunion.REUN_NUMREURO, reunObjet = qry_reunion.REUN_OBJET, nature = qry_reunion.NATURE, vilCd = qry_reunion.VIL_CD, reunNumRreun = qry_reunion.REUN_NUMRREUN, datecrea = DateFormat(qry_reunion.DATECREA, "dd/mm/yyyy"), unitInfo = unitInfo })> </cfloop> </cfif> <!--- 返回分页结果 ---> <cfset result.data = meetingData> <cfset result.total = qry_reunion.recordcount> <cfset result.page = arguments.page> <cfset result.pageSize = arguments.pageSize> <cfcatch type="database"> <cfset SESSION.errorDBquery = 1> <cfset result.error = "Database error occurred"> </cfcatch> </cftry> <cfreturn result> </cffunction>
步骤2:前端页面改造,实现懒加载+加载提示
把原来的服务器端渲染表格改成前端AJAX加载,初始加载第一页数据,滚动到底部时自动加载下一页,同时显示加载动画:
<!DOCTYPE html> <html> <head> <title>Meetings List</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <style> .dataTable { width: 100%; border-collapse: collapse; margin: 20px 0; } .dataTable th, .dataTable td { border: 1px solid #ddd; padding: 8px; text-align: left; } .dataTable th { background-color: #f2f2f2; } .loading-row { text-align: center; padding: 10px; color: #666; } .loading-icon { display: inline-block; width: 20px; height: 20px; border: 2px solid #ccc; border-top-color: #333; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } </style> </head> <body> <h1>Meetings List</h1> <!--- 搜索表单(保留原有的搜索逻辑) ---> <form id="searchForm" method="post"> <!--- 原有的搜索字段:year_source, Searchstatut, Searchnumber, searchorg ---> <input type="hidden" name="year_source" value="#form.year_source#"> <input type="hidden" name="Searchstatut" value="#Searchstatut#"> <input type="hidden" name="Searchnumber" value="#Searchnumber#"> <input type="hidden" name="searchorg" value="#searchorg#"> </form> <!--- 数据表格 ---> <table class="dataTable"> <thead> <tr> <th title="#application.ui_lib_numero#">#application.ui_lib_numero#</th> <th title="#application.ui_lib_organisateur#">#application.ui_lib_organisateur#</th> <th title="#application.ui_lib_objet#">#application.ui_lib_objet#</th> <th title="#application.ui_lib_statut#">#application.ui_lib_statut#</th> <th title="#application.ui_lib_ville#">#application.ui_lib_ville#</th> <th title=""> </th> </tr> </thead> <tbody id="meetingsTableBody"> <!--- 初始加载数据会插入这里 ---> </tbody> </table> <!--- 加载提示行 ---> <div id="loadingIndicator" style="display:none;" class="loading-row"> <span class="loading-icon"></span> Loading more meetings... </div> <script> var currentPage = 1; var pageSize = 10; var isLoading = false; var hasMoreData = true; // 初始加载第一页数据 loadMeetings(); // 滚动监听,加载下一页 $(window).scroll(function() { if ($(window).scrollTop() + $(window).height() >= $(document).height() - 100 && !isLoading && hasMoreData) { loadMeetings(); } }); // 加载会议数据的AJAX函数 function loadMeetings() { isLoading = true; $("#loadingIndicator").show(); var formData = $("#searchForm").serializeArray(); formData.push( {name: "page", value: currentPage}, {name: "pageSize", value: pageSize} ); $.ajax({ url: "service/meetings.cfc?method=getPaginatedMeetings&returnformat=json", type: "POST", data: formData, dataType: "json", success: function(response) { if (response.error) { alert("Error loading data: " + response.error); $("#loadingIndicator").hide(); isLoading = false; return; } // 渲染数据到表格 renderMeetings(response.data); // 更新分页状态 currentPage++; hasMoreData = (response.data.length === pageSize); isLoading = false; $("#loadingIndicator").hide(); // 如果没有更多数据,隐藏加载提示 if (!hasMoreData) { $("#loadingIndicator").html("No more meetings to load.").show(); } }, error: function(xhr, status, error) { alert("AJAX error: " + error); $("#loadingIndicator").hide(); isLoading = false; } }); } // 渲染会议数据到表格 function renderMeetings(meetings) { var tbody = $("#meetingsTableBody"); $.each(meetings, function(index, meeting) { var unitHtml = meeting.unitInfo.unitLabel; if (meeting.unitInfo.isObsolete) { unitHtml += " (obsolete)"; if (meeting.unitInfo.isEstat) { // 添加tooltipster提示 var tooltipId = "obsoleteMeetingUnit_" + (currentPage-1)*pageSize + index; unitHtml += ` <span id="${tooltipId}" style="line-height:30px;"> <img class='my-tooltip' src='pictures/questionmark.gif' style='vertical-align:middle;'> </span> `; // 初始化tooltipster $(document).ready(function() { $("#" + tooltipId).tooltipster({ contentAsHTML: true, interactive: true, animation: 'grow', content: ` <span>${meeting.unitInfo.unitLabel} was <em>(valid from ${meeting.unitInfo.validFrom} to ${meeting.unitInfo.validTo})</em> when the meeting has been created on ${meeting.datecrea}.</span> `, theme: 'tooltipster-shadow' }); }); } } else if (meeting.unitInfo.unitLabel === "Error") { unitHtml = "<span style='color:red;'>Error</span>"; } // 组装表格行HTML var rowHtml = ` <tr> <td class="cc">${meeting.reunNumReuro}</td> <td class="cc">${unitHtml}</td> <td class="cc">${meeting.reunObjet}</td> <td class="cc">${meeting.nature}</td> <td class="cc">${meeting.vilCd}</td> <td class="cc"> <form action="reunion_status.cfm?mode=generalite_sta" method="post"> <input type="hidden" name="REUN_NUMRREUN" value="${meeting.reunNumRreun}"> <input name="${application.ui_lib_chgt_status}" type="submit" value="${application.ui_lib_chgt_status}"> </form> </td> </tr> `; tbody.append(rowHtml); }); } </script> </body> </html>
关键优化点说明
- 减少CFC调用次数:原来的循环中每次调用
cfinvoke,现在改成批量查询+批量处理,大大降低了服务器端的调用开销 - 前端分页懒加载:用户只加载当前可见的数据,滚动时再加载下一页,首屏加载速度大幅提升
- 加载提示反馈:加入加载动画和状态提示,让用户明确知道页面正在处理中
- 数据库查询优化:使用
<cfqueryparam>防止SQL注入,同时加入LIMIT实现数据库层面的分页,减少数据传输量 - 保留原有业务逻辑:完全兼容你现有的单位版本查询、tooltip提示和表单提交功能
内容的提问来源于stack exchange,提问作者coeurdange57
相关产品推荐
相关产品推荐

