使用 CFspreadsheet 编写电子表格以避免错误此文件可能已损坏

Writing a spreadsheet using CFspreadsheet to avoid error this file might be corrupted

我想知道如何将我的 html table 添加到 CF 电子表格以显示在 excel 中。我在网上找到的所有示例都没有我的那么疯狂(只有一个简单的基本查询)。对此的任何帮助将不胜感激。这是我到目前为止 我的电子表格 :

<cfset objSpreadsheet = SpreadsheetNew()>
<cfset filename = expandPath("./myexcel.xls")>

<!--- Create and format the header row. --->
<cfset SpreadsheetAddRow( objSpreadsheet, "Associate Name,Location,Checklists Generated by Associate,Checklists Generated by Selected Location(s),Associate Percentage of Location Total" )>
<cfset SpreadsheetFormatRow( objSpreadsheet, {bold=true, alignment="center"}, 1 )>


<cfheader name="Content-Disposition" value="attachment; filename=#filename#"> 
<cfcontent type="application/vnd.ms-excel" variable="#SpreadsheetReadBinary( objSpreadsheet )#"> 

我的 table 正在尝试转换:

<table class="table table-hover">
    <thead>
        <th><strong>Associate Name</strong></th>
        <th><strong>Location</strong></th>
        <th><strong>Checklists Generated by Associate</strong></th>
        <th><strong>Checklists Generated by Selected Location(s)</strong></th>
        <th><strong>Associate Percentage of Location Total</strong></th>  
    </thead>
    <tbody>
        <cfoutput query="GetEmployeeInfo">
      <tr>
          <td><cfif rnA EQ 1><strong>#assoc_name#</strong></cfif></td>
          <td><cfif rnL EQ 1>#trans_location#</cfif></td>
          <td>#checklistsByAssocLoc#</td> 
          <td>#assocChecklistsByLoc#</td> 
          <td>#DecimalFormat(totalChecklistsByAssocLocPct)# %</td> 
          <!---<td> rnA: #rnA# | rnL: #rnL# | rnTotAssoc: #rnTotAssoc# </td> --->
      </tr>
      <cfif rnTotAssoc EQ 1> 
      <tr> 
          <td>Associate Total</td> 
          <td></td> 
          <td>#totalChecklistsByAssoc#</td> 
          <td>#totalAssocChecklistsByAllFilteredLoc#</td> 
          <td>#DecimalFormat(totalChecklistsByLocPct)# %</td>
      </tr>
      </cfif>
    </cfoutput>
    </tbody>
</table>

我的疯狂查询!:

<cfquery datasource="#application.dsn#" name="GetEmployeeInfo"> 
    SELECT s4.associate /* Associate's ID */ 
        , s4.assoc_name /* Associate's Name */ 
        , s4.trans_location /* Associate's Location */ 
        , s4.checklistsByAssocLoc /* Gives you a count of Checklists by Associate for a specific Location. */ 
        , s4.assocChecklistsByLoc /* Gives you a count of Total Checklists by All Associates in a Location. */ 
        , s4.totalChecklistsByAssoc /** Gives you a count of Total Checklists by Specific Associate in All Locations. */ 
        , s4.totalAssocChecklistsByAllFilteredLoc /* Gives you a count of Total Checklists by Specific Associates in All Locations. */ 
        , CASE WHEN ( coalesce(s4.assocChecklistsByLoc,0) > 0 ) THEN (CAST(s4.checklistsByAssocLoc AS decimal(8,2))/s4.assocChecklistsByLoc) * 100 ELSE 0 END AS totalChecklistsByAssocLocPct /* This gives you a percent of associate location checklists over count of checklists by Associate in a Location. */ 
        , CASE WHEN ( coalesce(s4.totalAssocChecklistsByAllFilteredLoc,0) > 0 ) THEN (CAST(s4.totalChecklistsByAssoc AS decimal(8,2))/s4.totalAssocChecklistsByAllFilteredLoc) * 100 ELSE 0 END AS totalChecklistsByLocPct /* This gives you a percent of Total Associate Checklists in All Locations over count of Checklists by All Associate in All Locations. */ 
        , s4.rnA /* Placeholder for a record to display the Associate Name. */ 
        , s4.rnL /* Placeholder for a record to display the Location. */ 
        , s4.rnTotAssoc /* Placeholder for the last Associate Location row. The next row should be an Associate Total. */ 
    FROM ( 
    SELECT s3.* 
        , SUM(s3.assocChecklistsByLoc) OVER (PARTITION BY s3.associate) AS totalAssocChecklistsByAllFilteredLoc /* Gives you a count of Total Checklists by Specific Associates in All Locations. */ 
    FROM ( 

    SELECT s2.* 
        FROM ( 
            SELECT a.assoc_name 
                , s1.associate 
                , s1.trans_location 
                , s1.checklistsByAssocLoc 
                , s1.assocChecklistsByLoc 
                , s1.totalChecklistsByAssoc 
                , ROW_NUMBER() OVER (PARTITION BY s1.associate ORDER BY s1.associate, s1.trans_location) AS rnA /* Placeholder for a record to display the Associate Name */ 
                , ROW_NUMBER() OVER (PARTITION BY s1.associate, s1.trans_location ORDER BY s1.associate, s1.trans_location) AS rnL /* Placeholder for a record to display the Location */ 
                , ROW_NUMBER() OVER (PARTITION BY s1.associate ORDER BY s1.trans_location DESC) AS rnTotAssoc /* Placeholder for the last Associate Location row. The next row should be an Associate Total. */ 
    FROM ( 
    SELECT c.associate 
        , c.trans_location 
        , COUNT(*) OVER (PARTITION BY c.associate, c.trans_location) AS checklistsByAssocLoc /* Gives you a count of Checklists by Associate for a specific Location. */ 
        , COUNT(*) OVER (PARTITION BY c.associate) AS totalChecklistsByAssoc /* Gives you a count of Total Checklists by Associate in All Locations. */ 
        , COUNT(*) OVER (PARTITION BY c.trans_location) AS assocChecklistsByLoc /* Gives you a count of Total Checklists by All Associates in a Location. */ 
    FROM cl_checklists c 
    LEFT OUTER JOIN tco_associates a ON c.associate = a.assoc_id 
        AND a.assoc_id IN ( <cfqueryparam value="#FORM.EmployeeName#" cfsqltype="cf_sql_varchar" list="true" />  ) /* SELECTED ASSOCIATE IDs */ 
            WHERE c.[DATE] >= <cfqueryparam value="#date1#" cfsqltype="cf_sql_timestamp" /> /* SELECTED DATES */ 
                AND c.[DATE] <= <cfqueryparam value="#date2#" cfsqltype="cf_sql_timestamp" /> 
                AND c.trans_location IN ( <cfqueryparam value="#locList#" cfsqltype="cf_sql_varchar" list="true" />  ) /* SELECTED LOCATIONS */ 
    ) s1 
    INNER JOIN tco_associates a ON s1.associate = a.assoc_id 
        AND a.assoc_id IN ( <cfqueryparam value="#FORM.EmployeeName#" cfsqltype="cf_sql_varchar" list="true" />  ) /* SELECTED ASSOCIATE IDs */ 

    ) s2 
    WHERE s2.rnA = 1 OR s2.rnL = 1 /* There will be a final Location (rnL=1 and rnTotAssoc=1). This is the final row. */ 
    ) s3 
    ) s4 
    ORDER BY s4.assoc_name, s4.trans_location
</cfquery> 

这是我想的路径,但我真的不明白调用行和列。我什至有正确的想法还是我偏离了方向?

<cfoutput query="GetEmployeeInfo">
    <cfif rnA EQ 1><cfset SpreadsheetSetCellValue( objSpreadsheet, #assoc_name#, 2, 1) ></cfif>
    <cfif rnL EQ 1><cfset SpreadsheetSetCellValue( objSpreadsheet, #trans_location#, 2, 1) ></cfif>
    <cfset SpreadsheetSetCellValue( objSpreadsheet, #checklistsByAssocLoc#, 2, 1) >
    <cfset SpreadsheetSetCellValue( objSpreadsheet, #assocChecklistsByLoc#, 2, 1) >
    <cfset SpreadsheetSetCellValue( objSpreadsheet, #DecimalFormat(totalChecklistsByAssocLocPct)# %, 2, 1) >
    <cfif rnTotAssoc EQ 1>
        <cfset SpreadsheetSetCellValue( objSpreadsheet, 'Associate Total', 2, 1) >
        <cfset SpreadsheetSetCellValue( objSpreadsheet, '', 2, 1) >
        <cfset SpreadsheetSetCellValue( objSpreadsheet, #totalChecklistsByAssoc#, 2, 1) >
        <cfset SpreadsheetSetCellValue( objSpreadsheet, #totalAssocChecklistsByAllFilteredLoc#, 2, 1) >
        <cfset SpreadsheetSetCellValue( objSpreadsheet, #DecimalFormat(totalChecklistsByLocPct)# %, 2, 1) >
    </cfif>
</cfoutput>

也尝试过:

<cfoutput query="GetEmployeeInfo">
    <cfset SpreadsheetAddRow( objSpreadsheet, "<cfif rnA EQ 1>#assoc_name#</cfif>,<cfif rnL EQ 1>#trans_location#</cfif>,#checklistsByAssocLoc#,#assocChecklistsByLoc#,#DecimalFormat(totalChecklistsByAssocLocPct)# %" )>
    <cfif rnTotAssoc EQ 1>
        <cfset SpreadsheetAddRow( objSpreadsheet, "Associate Total,'',#totalChecklistsByAssoc#,#totalAssocChecklistsByAllFilteredLoc#,#DecimalFormat(totalChecklistsByLocPct)# %" )>
    </cfif>
</cfoutput>

为了向您的毅力表示敬意,这是我几天前做的一个。 visitDataheaderscolumnstitle 变量在程序中较早设置,因为它们也应用于 html 输出。

<cfscript>
filePath = "d:\dw\dwweb\work\";
fileName = title  & " " & getTickCount() & ".xlsx";

sheet = spreadSheetNew("data", true);
HeaderFormat = {};
HeaderFormat.bold = true;

spreadSheetAddRow(sheet, headers);
SpreadSheetFormatRow(sheet, HeaderFormat, 1);
SpreadSheetAddFreezePane(sheet, 0,1);

for (queryRow = 1; queryRow <= visitData.recordcount; queryRow ++) {
rowNumber = queryRow + 1;
for (columnNumber = 1; columnNumber <= listLen(columns); columnNumber ++) {
thisColumn = listGetAt(columns, columnNumber);
thisValue = visitData[thisColumn][queryrow];
SpreadSheetSetCellValue(sheet, thisValue, rowNumber, columnNumber);
} // columns
} // rows

SpreadSheetWrite(sheet,filePath & fileName, true);
</cfscript>
<cfheader name="content-disposition"  value="Attachment;filename=#fileName#">
<cfcontent  file="#filePath & fileName#"  type="application/vnd.ms-excel">

注意我在最后两个标签中使用的变量。 <cfheader> 标签只有文件名,没有路径。我之前犯的一个错误是只使用了一个同时具有两者的变量。结果是向用户发送了不需要的文件名。

对于您的最终代码段,ColdFusion 标签将不会在字符串文字中进行评估。对于 if else 语句,您可以使用三元运算符来切换某些输出。此外,如果您的任何数据包含 comma/s,它会在单元格之间拆分数据。为了解决这个问题,请尝试将每个单元格值用引号引起来。这可能会将您的文本保留在一个单元格中。 Others still have had issues with this fix.

<cfset rowNumber = 0 />
<cfoutput query="GetEmployeeInfo">
    <cfset rowNumber++ />
    <cfset rowList = "'#(rnA eq 1)?assoc_name:''#', '#(rnl eq 1)?trans_location:''#', '#checklistsByAssocLoc#,#assocChecklistsByLoc#', '#DecimalFormat(totalChecklistsByAssocLocPct)# %'">
    <cfset SpreadsheetAddRow( objSpreadsheet, rowList)>
    <cfset spreadsheetFormatCell( objSpreadsheet, {bold: true}, rowNumber, 1 )>
    <cfif rnTotAssoc EQ 1>
        <cfset rowNumber++ />
        <cfset rowList = "'Associate Total','','#totalChecklistsByAssoc#','#totalAssocChecklistsByAllFilteredLoc#','#DecimalFormat(totalChecklistsByLocPct)# %'" >
        <cfset SpreadsheetAddRow( objSpreadsheet, rowList )>
    </cfif>
</cfoutput>

就我个人而言,由于使用许多(几百+)spreadsheetAddRows 来创建传播 sheet 的渲染时间很慢,并且使用字符串数据列表可能很痛苦,我几乎总是将我的 spreadsheet 数据放入查询 object 中。一旦数据在查询 object 中,只需调用 spreadsheetAddRows 即可将数据放入 spreadsheet.

qReportData = queryNew("Name, Age",
    "varchar, integer",
    [{name: "Tom", age: 25},{name: "Dick", age: 40},{name: "Harry", age: 55}]
);
sheet = SpreadsheetNew(false);

//Add and format headers
bold = {bold: true};
spreadsheetAddRow(sheet, "Name, Age");
spreadsheetFormatRow(sheet, bold, 1);

spreadsheetAddRows(sheet, qReportData);

鉴于您的某些报告数据可能位于多行,在特定情况下,您将无法仅导出报告查询,因此我们必须使用代码构建一个新的。我们将遍历报告并在我们的 spreadsheet 查询中生成行。在我的示例中,只要此人超过 40 岁,我就会添加一个额外的行。

qSheetOutput = queryNew("Name, Age");
for(row in qReportData){
    queryAddRow(qSheetOutput, {
        name: row.name,
        age: row.age
    });
    if(row.age > 40){
        queryAddRow(qSheetOutput, {
            name: row.name & " is over 40"
        });
    }
}
// now writing the generated query to the spreadsheet
spreadsheetAddRows(sheet, qSheetOutput);

最后一步是迭代和格式化传播的单元格sheet。当我遍历输出时,我必须通过 sheet 中 header 的计数来偏移我正在处理的行,本例中的 是 1。同样对于这个例子, 超过 40 岁的人的额外行将不会加粗,并且将跨越 2 个单元格。

for(row in qSheetOutput){
    if(!len(row.age)){
        spreadsheetFormatCell(sheet, {dataformat="@", alignment="center"}, qSheetOutput.currentRow + 1, 1);
        spreadsheetFormatCell(sheet, qSheetOutput.currentRow + 1, qSheetOutput.currentRow + 1, 1, 2);
    }
    else{
        spreadsheetFormatCell( sheet, bold, qSheetOutput.currentRow + 1, 1 );
    }
}

如果查看输出太难确定该行所需的正确 format/s,您可以迭代需要特定 format/s 的 array/s 行号。再次注意我再次使用 header 计数作为偏移量。

dataRows = [];
messageRows = [];
for(row in qReportData){
    queryAddRow(qSheetOutput, {
        name: row.name,
        age: row.age
    });
    arrayAppend(dataRows, qSheetOutput.recordCount + 1);
    if(row.age > 40){
        queryAddRow(qSheetOutput, {
            name: row.name & " is over 40"
        });
        arrayAppend(messageRows, qSheetOutput.recordCount + 1);
    }
}
...
for(rowNumber in dataRows){
    spreadsheetFormatCell( sheet, bold, rowNumber, 1 );
}
for(rowNumber in messageRows){
    spreadsheetFormatCell(sheet, {dataformat="@", alignment="center"}, rowNumber, 1);
    spreadsheetFormatCell(sheet, rowNumber, rowNumber, 1, 2);
}

这是 TryCF.com

上的完整工作代码