unit ExportExcel;

interface

uses
  Classes, ZjGrid, ScXlsOutput, ScXlsCustomUD, Windows, StdCtrls,
  sdIDTree, sdDB, Graphics, SysUtils, ProgressHintFrm, Forms, Controls;

type
  TExcelExportor = class
  private
    FXlsOutPut: TXlsOutPut;
    FGrid: TZJGrid;
    FTempFile: string;
    FFileName: string;

    procedure InitialPage(AGrid: TZJGrid; AXlsPage: TXlsCustomPage);
  protected
    procedure BeforeExport;
    procedure EndExport;
  public
    constructor Create;
    destructor Destroy; override;
    procedure ExportToXlsPage(AGrid: TZJGrid; AXlsPage: TXlsCustomPage);
    procedure ExportToFile(AGrid: TZJGrid; const AFileName: string);
  end;

  PColInfo = ^TColInfo;

  TColInfo = record
    // 字段名
    FieldName: string;
    // 查询字段名，应用于联合几个数据库的情况，参照dataset的Lookup方式
    KeyField: string;
    LookupKeyField: string;
    // 查询数据库ID
    LookupDataSetIndex: Integer;
    // 列名
    TitleCaption: string;
    // 列宽
    Width: Integer;
    // 对齐方式
    HorTextAlign: TUDHTextAlign;
    //VerTextAlign: TUDVTextAlign;
  end;

  PColInfos = ^TColInfos;

  TColInfos = array [0..30] of TColInfo;

  // 仿照DataSet的Lookup以及数据库的AutoUpdate已达到关于sdIDTree导出数据至Excel的普适性
  // 导出前须根据所需列信息，以及查询数据库（列信息须与查询数据库对等，否则将会报错，并不检查列与数据库是否匹配）
  TIDTreeExcelExportor = class
  private
    FXlsOutPut: TXlsOutPut;
    FDataSetList: TList;
    FColInfos: PColInfos;
    FColCount: Integer;
    FHasLevelCode: Boolean;
    FTree: TsdIDTree;
    FTempFile: string;

    // 当清单数超过3w3k行时，使用Variant会内存溢出
    function GetCellValue(ANode: TsdIDTreeNode; ColInfo: TColInfo): Variant;
    // 故换成直接使用String
    function GetCellStr(ANode: TsdIDTreeNode; ColInfo: TColInfo): string;
    procedure ExportNodeData(ANode: TsdIDTreeNode; AXlsPage: TXlsCustomPage; const ALevelCode: string);
    procedure ExportTreeNode(ANode: TsdIDTreeNode; AXlsPage: TXlsCustomPage; const ALevelCode: string);
    procedure DefineHeader(AXlsPage: TXlsCustomPage);
  protected
    procedure BeforeExport;
    procedure EndExport;
  public
    constructor Create;
    destructor Destroy; override;

    procedure AddLookupDataSet(ADataSet: TsdDataSet);
    procedure DefineCol(AColInfos: PColInfos; AColCount: Integer);

    procedure ExportToXlsPage(ATree: TsdIDTree; AXlsPage: TXlsCustomPage);
    procedure ExportToFile(ATree: TsdIDTree; const AFileName: string);

    property HasLevelCode: Boolean read FHasLevelCode write FHasLevelCode;
  end;

const  
  ciLedger: array [0..8] of TColInfo =(
    (FieldName: 'Code'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '项目节编号'; Width: 120; HorTextAlign: htaLeft),
    (FieldName: 'B_Code'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '清单编号'; Width: 80; HorTextAlign: htaLeft),
    (FieldName: 'Name'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '名称'; Width: 200; HorTextAlign: htaLeft),
    (FieldName: 'Units'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '单位'; Width: 40; HorTextAlign: htaCenter),
    (FieldName: 'Price'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '单价'; Width: 60; HorTextAlign: htaRight),
    (FieldName: 'OrgQuantity'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '数量'; Width: 80; HorTextAlign: htaRight),
    (FieldName: 'OrgTotalPrice'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '金额'; Width: 80; HorTextAlign: htaRight),
    (FieldName: 'DrawingCode'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '图号'; Width: 100; HorTextAlign: htaLeft),
    (FieldName: 'MemoStr'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '备注'; Width: 80; HorTextAlign: htaLeft)
  );

  ciFxBills: array [0..10] of TColInfo =(
    (FieldName: 'Code'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '预算项目节'; Width: 120; HorTextAlign: htaLeft),
    (FieldName: 'B_Code'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '清单子目号'; Width: 80; HorTextAlign: htaLeft),
    (FieldName: 'Name'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '名称'; Width: 200; HorTextAlign: htaLeft),
    (FieldName: 'Units'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '单位'; Width: 40; HorTextAlign: htaCenter),
    (FieldName: 'OrgQuantity'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '清单数量'; Width: 80; HorTextAlign: htaRight),
    (FieldName: 'DgnQuantity1'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '设计数量1'; Width: 80; HorTextAlign: htaRight),
    (FieldName: 'DgnQuantity2'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '设计数量2'; Width: 80; HorTextAlign: htaRight),
    (FieldName: 'Price'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '单价'; Width: 60; HorTextAlign: htaRight),
    (FieldName: 'OrgTotalPrice'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '合价'; Width: 80; HorTextAlign: htaRight),
    (FieldName: 'DrawingCode'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '图号'; Width: 100; HorTextAlign: htaLeft),
    (FieldName: 'MemoStr'; KeyField: ''; LookupKeyField: ''; LookupDataSetIndex: -1; TitleCaption: '备注'; Width: 80; HorTextAlign: htaLeft)
  );

implementation

uses
  ZhAPI, Variants, UtilMethods;

{ TExcelExportor }

procedure TExcelExportor.BeforeExport;
begin
  Screen.Cursor := crHourGlass;
  ShowProgressHint('导出Excel表格数据', FGrid.RowCount);
end;

constructor TExcelExportor.Create;
begin
  FXlsOutPut := TXlsOutPut.Create;
  FTempFile := GetTempFileName;
end;

destructor TExcelExportor.Destroy;
begin
  if FileExists(FTempFile) then
    DeleteFileOrFolder(FTempFile);
  FXlsOutPut.Free;
  inherited;
end;

procedure TExcelExportor.EndExport;
begin
  CloseProgressHint;
  Screen.Cursor := crDefault;
end;

procedure TExcelExportor.ExportToFile(AGrid: TZJGrid;
  const AFileName: string);
begin
  FFileName := AFileName;
  FGrid := AGrid;
  BeforeExport;
  try
    ExportToXlsPage(AGrid, FXlsOutPut.AddPage);
    FXlsOutPut.SaveToFile(FTempFile);
    if not FileExists(FFileName) or QuestMessage('存在同名文件，是否替换？') then
      CopyFile(PChar(FTempFile), PChar(FFileName), False);
  finally
    EndExport;
  end;
end;

procedure TExcelExportor.ExportToXlsPage(AGrid: TZJGrid;
  AXlsPage: TXlsCustomPage);

  procedure SetXlsCellTextAlign(AXlsCell: TXlsCustomCell; AGridCell: TzjCell);
  begin
    case AGridCell.TextAlign of
      gaTopLeft:
      begin
        AXlsCell.VTextAlign := vtaTop;
        AXlsCell.HTextAlign := htaLeft;
      end;
      gaTopCenter:
      begin
        AXlsCell.VTextAlign := vtaTop;
        AXlsCell.HTextAlign := htaCenter;
      end;
      gaTopRight:
      begin
        AXlsCell.VTextAlign := vtaTop;
        AXlsCell.HTextAlign := htaRight;
      end;
      gaCenterLeft:
      begin
        AXlsCell.VTextAlign := vtaCenter;
        AXlsCell.HTextAlign := htaLeft;
      end;
      gaCenterCenter:
      begin
        AXlsCell.VTextAlign := vtaCenter;
        AXlsCell.HTextAlign := htaCenter;
      end;
      gaCenterRight:
      begin
        AXlsCell.VTextAlign := vtaCenter;
        AXlsCell.HTextAlign := htaRight;
      end;
      gaBottomLeft:
      begin
        AXlsCell.VTextAlign := vtaBottom;
        AXlsCell.HTextAlign := htaLeft;
      end;
      gaBottomCenter:
      begin
        AXlsCell.VTextAlign := vtaBottom;
        AXlsCell.HTextAlign := htaCenter;
      end;
      gaBottomRight:
      begin
        AXlsCell.VTextAlign := vtaBottom;
        AXlsCell.HTextAlign := htaRight;
      end;
    end;
    if goWarpText in AGridCell.Grid.Options then
      AXlsCell.WartText := True;
  end;

  procedure ExportGridCell(AGridCell: TzjCell);
  var
    XlsCell: TXlsCustomCell;
  begin
    if AGridCell = nil then Exit;
    XlsCell := AXlsPage.AddCell(AGridCell.Col, AGridCell.Row, AGridCell.Text);
    SetXlsCellTextAlign(XlsCell, AGridCell);
    XlsCell.Font.Name := AGridCell.Font.Name;
    XlsCell.Font.Size := AGridCell.Font.Size;
    XlsCell.Width := AGridCell.Width;
    XlsCell.Height := AGridCell.Height;
  end;

var
  iColumn, iRow: Integer;
begin
  InitialPage(AGrid, AXlsPage);
  for iRow := 0 to AGrid.RowCount - 1 do
  begin
    UpdateProgressHint(Format('导出第%d行数据', [iRow + 1]));
    UpdateProgressHint(1);
    for iColumn := 0 to AGrid.ColCount - 1 do
      ExportGridCell(AGrid.Cells[iColumn, iRow]);
  end;
end;

procedure TExcelExportor.InitialPage(AGrid: TZJGrid;
  AXlsPage: TXlsCustomPage);

  procedure InitialColumnWidth;
  var
    iColumn: Integer;
  begin
    for iColumn := 0 to AGrid.ColCount - 1 do
      AXlsPage.Widths[iColumn] := AGrid.ColWidths[iColumn];
  end;

  procedure InitialRowHeight;
  var
    iRow: Integer;
  begin
    for iRow := 0 to AGrid.RowCount - 1 do
      AXlsPage.Heights.Items[iRow] := AGrid.RowHeights[iRow];
  end;

begin
  InitialColumnWidth;
  InitialRowHeight;
end;

{ TIDTreeExcelExportor }

constructor TIDTreeExcelExportor.Create;
begin
  FXlsOutPut := TXlsOutPut.Create;
  FDataSetList := TList.Create;
  FTempFile := GetTempFileName;
end;

destructor TIDTreeExcelExportor.Destroy;
begin
  if FileExists(FTempFile) then
    DeleteFileOrFolder(FTempFile);
  FDataSetList.Free;
  FXlsOutPut.Free;
  inherited;
end;

procedure TIDTreeExcelExportor.ExportToFile(ATree: TsdIDTree;
  const AFileName: string);
begin
  FTree := ATree;
  BeforeExport;
  try
    ExportToXlsPage(ATree, FXlsOutPut.AddPage);
    UpdateProgressHint('保存0号台账Excel数据');
    FXlsOutPut.SaveToFile(FTempFile);
    if not FileExists(AFileName) or QuestMessage('存在同名文件，是否替换？') then
      CopyFile(PChar(FTempFile), PChar(AFileName), False);
  finally
    EndExport;
  end;
end;

procedure TIDTreeExcelExportor.ExportTreeNode(ANode: TsdIDTreeNode;
  AXlsPage: TXlsCustomPage; const ALevelCode: string);

  function GetFirstChildLevelCode(const ACode: string): string;
  begin
    Result := ACode + '.1';
  end;

  function GetNextSiblingLevelCode(const ACode: string): string;
  var
    strPreCode, strLastCode: string;
    iNextCode: Integer;
  begin
    if Pos('.', ACode) = 0 then
      Result := IntToStr(StrToIntDef(ACode, 1) + 1)
    else
    begin
      strPreCode := GetPrefixOfCode(ACode, '.');
      strLastCode := GetLastSetmentOfCode(ACode, '.');
      iNextCode := StrToIntDef(strLastCode, 1) + 1;
      Result := strPreCode + '.' + IntToStr(iNextCode);
    end;
  end;

var
  sHint: string;
begin
  if not Assigned(ANode) then Exit;

  if ANode.Rec.ValueByName('Code').AsString <> '' then
    sHint := '正在导出清单 ' + ANode.Rec.ValueByName('Code').AsString
  else if ANode.Rec.ValueByName('B_Code').AsString <> '' then
    sHint := '正在导出清单 ' + ANode.Rec.ValueByName('B_Code').AsString
  else
    sHint := '正在导出清单 ' + ANode.Rec.ValueByName('Name').AsString;
  UpdateProgressHint(sHint);
  UpdateProgressHint(1);

  ExportNodeData(ANode, AXlsPage, ALevelCode);
  ExportTreeNode(ANode.FirstChild, AXlsPage, GetFirstChildLevelCode(ALevelCode));
  ExportTreeNode(ANode.NextSibling, AXlsPage, GetNextSiblingLevelCode(ALevelCode));
end;

procedure TIDTreeExcelExportor.ExportNodeData(ANode: TsdIDTreeNode;
  AXlsPage: TXlsCustomPage; const ALevelCode: string);

  function ExportCell(ACol, ARow: Integer; AValue: Variant): TXlsCustomCell;
  begin
    Result := nil;

    // -----------
    if VarIsNull(AValue) then Exit;
    // -----------
    // 当数据超过3w3k行时，运行至某行时，AddCell会内存溢出
    // 可能是Cell的数目超过某个限度时，报错
    // 如果AValue为Null时不AddCell，则3w3k行可以安全度过

    case VarType(AValue) of
      varSmallInt, varInteger, varSingle, varDouble,
      varCurrency, varShortInt, varByte, varWord,
      varLongWord, varInt64:
      begin
        if AValue <> 0 then
          Result := AXlsPage.AddCell(ACol, ARow, AValue);
      end
      else Result := AXlsPage.AddCell(ACol, ARow, AValue);
    end;
  end;

var
  iCol: Integer;
  ColInfo: TColInfo;
  XlsCell: TXlsCustomCell;
  sStr: string;
begin
  if not Assigned(ANode) then Exit;
  for iCol := 0 to FColCount - 1 do
  begin
    ColInfo := FColInfos[iCol];
    XlsCell := ExportCell(iCol, ANode.MajorIndex + 1, GetCellValue(ANode, ColInfo));
    {sStr := GetCellStr(ANode, ColInfo);
    if sStr = '' then Continue;
    XlsCell := AXlsPage.AddCell(iCol, ANode.MajorIndex + 1, sStr);}
    if Assigned(XlsCell) then
    begin
      XlsCell.HTextAlign := ColInfo.HorTextAlign;
      //XlsCell.VTextAlign := ColInfo.VerTextAlign;
      XlsCell.Font.Name := 'SmartSimSun';
      XlsCell.Font.Size := 9;
    end;
  end;
  if HasLevelCode then
  begin
    XlsCell := ExportCell(FColCount, ANode.MajorIndex + 1, ALevelCode);
    XlsCell.Font.Name := 'SmartSimSun';
    XlsCell.Font.Size := 9;
  end;
end;

procedure TIDTreeExcelExportor.ExportToXlsPage(ATree: TsdIDTree;
  AXlsPage: TXlsCustomPage);
begin
  DefineHeader(AXlsPage);
  ExportTreeNode(ATree.FirstNode, AXlsPage, '1');
end;

function TIDTreeExcelExportor.GetCellValue(ANode: TsdIDTreeNode;
  ColInfo: TColInfo): Variant;

  function GetRec: TsdDataRecord;
  var
    DataSet: TsdDataSet;
  begin
    Result := nil;
    DataSet := TsdDataSet(FDataSetList.Items[ColInfo.LookupDataSetIndex]);
    if not Assigned(DataSet) then Exit;
    Result := DataSet.Locate(ColInfo.LookupKeyField, ANode.Rec.ValueByName(ColInfo.KeyField).Value);
  end;

var
  ARec: TsdDataRecord;
begin
  Result := '';
  if not Assigned(ANode) then Exit;
  if ColInfo.LookupDataSetIndex = -1 then
    ARec := ANode.Rec
  else
    ARec := GetRec;
  if Assigned(ARec) then
    Result := ARec.ValueByName(ColInfo.FieldName).Value;
end;

procedure TIDTreeExcelExportor.DefineHeader(AXlsPage: TXlsCustomPage);
var
  iCol: Integer;
  ColInfo: TColInfo;
  XlsCell: TXlsCustomCell;
begin
  for iCol := 0 to FColCount - 1 do
  begin
    ColInfo := FColInfos[iCol];
    XlsCell := AXlsPage.AddCell(iCol, 0, ColInfo.TitleCaption);
    XlsCell.HTextAlign := htaCenter;
    XlsCell.Font.Name := '黑体';
    XlsCell.Font.Size := 10;
    XlsCell.Font.Style := [fsBold];
    AXlsPage.Widths[iCol] := ColInfo.Width;
  end;
  if HasLevelCode then
  begin
    XlsCell := AXlsPage.AddCell(iCol, 0, '层次编号');
    XlsCell.HTextAlign := htaCenter;
    XlsCell.Font.Name := '黑体';
    XlsCell.Font.Size := 10;
    XlsCell.Font.Style := [fsBold];
  end;
end;

procedure TIDTreeExcelExportor.AddLookupDataSet(ADataSet: TsdDataSet);
begin
  FDataSetList.Add(ADataSet);
end;

procedure TIDTreeExcelExportor.DefineCol(AColInfos: PColInfos;
  AColCount: Integer);
begin
  FColInfos := AColInfos;
  FColCount := AColCount;
end;

procedure TIDTreeExcelExportor.BeforeExport;
begin
  Screen.Cursor := crHourGlass;
  ShowProgressHint('导出0号台账Excel数据', FTree.Count);
end;

procedure TIDTreeExcelExportor.EndExport;
begin
  CloseProgressHint;
  Screen.Cursor := crDefault;
end;

function TIDTreeExcelExportor.GetCellStr(ANode: TsdIDTreeNode;
  ColInfo: TColInfo): string;

  function GetRec: TsdDataRecord;
  var
    DataSet: TsdDataSet;
  begin
    Result := nil;
    DataSet := TsdDataSet(FDataSetList.Items[ColInfo.LookupDataSetIndex]);
    if not Assigned(DataSet) then Exit;
    Result := DataSet.Locate(ColInfo.LookupKeyField, ANode.Rec.ValueByName(ColInfo.KeyField).Value);
  end;

var
  ARec: TsdDataRecord;
begin
  Result := '';
  if not Assigned(ANode) then Exit;
  if ColInfo.LookupDataSetIndex = -1 then
    ARec := ANode.Rec
  else
    ARec := GetRec;
  if Assigned(ARec) then
    Result := ARec.ValueByName(ColInfo.FieldName).AsString;
  if SameText(Result, '0') then
    Result := '';
end;

end.
