You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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="">&nbsp;</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>

关键优化点说明

  1. 减少CFC调用次数:原来的循环中每次调用cfinvoke,现在改成批量查询+批量处理,大大降低了服务器端的调用开销
  2. 前端分页懒加载:用户只加载当前可见的数据,滚动时再加载下一页,首屏加载速度大幅提升
  3. 加载提示反馈:加入加载动画和状态提示,让用户明确知道页面正在处理中
  4. 数据库查询优化:使用<cfqueryparam>防止SQL注入,同时加入LIMIT实现数据库层面的分页,减少数据传输量
  5. 保留原有业务逻辑:完全兼容你现有的单位版本查询、tooltip提示和表单提交功能

内容的提问来源于stack exchange,提问作者coeurdange57

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:17:40