Экспорт нескольких документов в вебе за один подход

21 0

Кейс

Требуется реализовать экспорт нескольких документов. Поиск может быть сложным — а значит, придется задействовать прикладную часть. В итоге получаем, что документы будут экспортироваться на сервере, и нужно их скачать на клиент. И скачивать мы будем не кучу документов последовательно, а оформим в аккуратный zip-архив. Его потом и по почте будет удобнее передавать, правда? Если связанных документов нет, то выгрузим хотя бы сам документ. В этом случае никакой архив нам не потребуется. В качестве примера рассмотрим экспорт связанных документов с самим документом из карточки документа. Потом прикрутим возможность экспорта с обложки, а значит реализация будет в прикладном сценарии. Сценарий может быть сколь угодно сложным.

Доработки в прикладной части

Шаг 1. Сценарий: параметры и возвращаемое значение

Создадим сценарий, который принимает на вход ИД документа и выгружает его со связанными документами в папку. У меня получилось так:

  // Получить параметры
  DocID = GetComponentLaunchParam('DocID'; '')
  if DocID == ''
    Raise(CreateException('WrongRequiredParam'; 'Параметр DocID не передан. Обратитесь к администратору системы.'; ecWarning))
  endif
  if not IsNumeric(DocID)
    Raise(CreateException('WrongRequiredParam'; 'Параметр DocID имеет неверный формат. Обратитесь к администратору системы.'; ecWarning))
  endif
  Directory = GetComponentLaunchParam('Directory'; '')  
  if Directory == ''
    Raise(CreateException('WrongRequiredParam'; 'Параметр Directory не передан. Обратитесь к администратору системы.'; ecWarning))
  endif  
  Directory = IncludeTrailingPathDelimiter(Directory)
  if not DirectoryExists(Directory)
    Raise(CreateException('WrongRequiredParam'; 'Параметр Directory имеет неверный формат. Обратитесь к администратору системы.'; ecWarning))
  endif  
  
  // Выгрузить документ со связанными в папку
  DirectList = CreateStringList()
  
  SelectDirectory = Directory & 'Комплект документов ' & DocID & '\'
  
  Doc = EDocuments.GetDocumentNoLock(DocID)
  DocInfo = Doc.Info

  BoundEDocumentsSearchDescr = Searches.Load("BOUND_EDOCUMENT_SEARCH")
  BoundEDocumentsSearchDescr.InitializeSearch(Doc.Info)
  DocInfos = BoundEDocumentsSearchDescr.Execute
  if DocInfos.Count > 0
    FileName = SelectDirectory & ReplaceFileNameSpecialSymbols(Doc.Name) & '.' & DocInfo.Editor.Extension
    VersionNumber = GetLastActiveEDocumentVersionNum(Doc)
    Doc.Export(VersionNumber; FileName)  
    foreach DocInfo in DocInfos
      DocID = DocInfo.ID
      Doc = EDocuments.GetDocumentNoLock(DocID)
      FileName = SelectDirectory & ReplaceFileNameSpecialSymbols(Doc.Name) & '.' & DocInfo.Editor.Extension
      VersionNumber = GetLastActiveEDocumentVersionNum(Doc)
      Doc.Export(VersionNumber; FileName)    
    endforeach 
    DirectList.Add(SelectDirectory)
    
    Result = DIRZIP(Directory; DirectList)
  else
    FileName = ReplaceFileNameSpecialSymbols(Doc.Name) & '.' & DocInfo.Editor.Extension
    VersionNumber = GetLastActiveEDocumentVersionNum(Doc)
    Doc.Export(VersionNumber; Directory & FileName)  
    Result = FileName
  endif

Обратно в веб вернется имя файла + расширение. Это понадобится для корректной обработки на уровне веб-модуля.

Осталось определиться с загадочной функцией DIRZIP. Я ее использую не только в этом сценарии, поэтому на вход она принимает три параметра:

  • Directory. Путь, где будет создаваться zip-архив.
  • DirectList. Список папок для архивации. Я создаю папки внутри Directory — правда, в сценарии выше папка особо и не требуется. Но это же пример ;)
  • FileName. Имя файла — без расширения и пути, файл создается в Directory. Имя файла не обязательно.

Шаг 2. Архивация.

Как архивируются файлы всем известно, поэтому вряд ли содержимое функции кому-то откроет таинства разработки. Но я все равно приведу содержимое функции DIRZIP:

  Result = ''
  if not Assigned(FileName)
    FileName = Object.Name
  endif
  ZipFileName = Directory & FileName & '.zip'
  // Массив байтов для пустого zip-архива
  EmptyZip = ArrayOf(80; 75; 5; 6; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0)
  EmptyZipText = ""
  foreach CharCode in CArrayElement(EmptyZip)
    EmptyZipText = EmptyZipText & Char(CharCode)
  endforeach
  WriteFile(ZipFileName ; EmptyZipText)
  try
    WinShell = CreateObject("shell.application")
    ZipFile = WinShell.Namespace(ZipFileName)
    foreach Folder in DirectList
      ZipFile.CopyHere(Folder; 4)
      // Для исключения ошибки архивации, необходимо приостанавливать выполнение кода
      Sleep(1)
      DeleteFile(Folder)
    endforeach 
    Result = FileName & '.zip'
  finally
    WinShell = nil
  endfinally

Действие в прикладной части нам не нужно, поэтому сразу приступаем к доработкам веб-модуля.

Доработки веб-модуля

Шаг 3. Поиск относительного пути и пути до серверной папки

В сценарий надо передать физический путь, а для выгрузки понадобится относительный. Поэтому нужно создать следующий веб-метод:

  ''' <summary>
  ''' Получить физический и относительный пути для локальной папки пользователя
  ''' </summary>
  ''' <returns> Возвращает массив, где нулевой элемент относительный путь, первый элемент физический путь</returns>
  <WebMethod()>
  Public Function GetUserFolderName() As WebServiceResponse(Of List(Of String))
    Dim Result As New List(Of String)
    Try
      Dim RelativePath = WebSession.Context.TempDirectory.Replace("~", "")
      Dim Server = HttpContext.Current.Server
      Dim LocalUserFolder = Server.MapPath(WebSession.Context.TempDirectory)
      Result.Add(RelativePath)
      Result.Add(LocalUserFolder)
      Return WebServiceResponse(Of List(Of String)).OK(Result)
    Catch ex As Exception
      Log.LogException(ex)
      Return WebServiceResponse(Of List(Of String)).Fail(ex.Message)
    End Try
  End Function

Шаг 4. Функция для запуска сценария

На javascript потребуется вызвать сценарий с параметрами, после чего сформированный файл выгрузить. Выглядит это так:

BaseWebAccess.exportBoundDocument = function () {
  var id = WA.CR.ID;

  // Выгрузить файл в папку на сервере
  WA.SRV.call("BaseService.asmx/GetUserFolderName", {}).done(function (data) {
    var relativePath = data[0];
    var localuserFolder = data[1];
    WA.FC.scripts('MyLittleExportDocument').execute({ DocID: id, Directory: localuserFolder }).success(function (path) {
      if (path) {
        window.open(relativePath + path);
      }
    })
  })
}

Шаг 5. Добавить действие в карточку документа

В XML в узел для нужного типа карточки (или типов карточек) добавим кнопку для инициации экспорта:

<ToolGroup name="TOOLBAR_EXPORT_IMPORT">
  <clear />
  <!--Кнопка Экспорт-->
  <ToolItem name="MyLittleBoundExport"
            icon="documentExport-20"
            jsaction="BaseWebAccess.exportBoundDocument()"/>
</ToolGroup>

Шаг 6. Profit!

В результате мы получили действие, которое:

  • в случае, если с документом не связаны другие документы — выгружает последнюю действующую версию документа локально.
  • в случае, если с документом связаны другие документы — вся совокупность документов экспортируется в один архив и выгружается пользователю локально.
Пока комментариев нет.

Авторизуйтесь, чтобы написать комментарий