Прикрепление файлов

Напишите нам, если не нашли ответ в Справке. Вы можете приложить скриншоты или видео, чтобы ускорить помощь:

Написать в чат  Написать в Telegram

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

Как это выглядит на примере шаблона "Мониторинг объектов в организациях"

Было:

Стало:

Для вашего удобства мы подготовили код для шаблона «Мониторинг объектов в организациях», в котором каждое из полей добавлено по одному разу в первую кнопку для ответа. Используйте этот код для самопроверки. Наши вставки в этом коде вы можете найти поиском слова «кастомизация».

Готовый код
Блок HTML
{{#if reviewMode}}
    <div class="header-review">
        <div class="header-review__title">
            not_var{{texts.task_title}}
        </div>
        <div class="header-review__buttons">
            {{#if (equal verdict "ok")}}
                <div class="header-review__btn header-review__btn_green">
                    not_var{{texts.btn_ok.title}}
                </div>
            {{/if}}
            {{#if (equal verdict "no_obj")}}
                <div class="header-review__btn header-review__btn_red">
                    not_var{{texts.btn_no_obj.title}}
                </div>
            {{/if}}
            {{#if (equal verdict "no_org")}}
                <div class="header-review__btn header-review__btn_red">
                    not_var{{texts.btn_no_org.title}}
                </div>
            {{/if}}
        </div>
    </div>
not_var{{else}}
    <div class="header">
        not_var{{texts.task_title}}
    </div>
{{/if}}

<div class="info">
    {{#if reviewMode}}
        <div class="info__review">
            <div class="info__review-block">
                <div class="info__title">
                    not_var{{texts.info_name}}
                </div>
                <div class="info__content">
                    not_var{{name}}
                </div>
            </div>
            <div class="info__review-block">
                <div class="info__title">
                    not_var{{texts.info_address}}
                </div>
                <div class="info__content">
                    not_var{{address}}
                </div>
            </div>
        </div>
    not_var{{else}}
        <div class="info__block">
            <div class="info__title">
                not_var{{texts.info_name}}
            </div>
            <div class="info__content">
                not_var{{name}}
            </div>
        </div>
        <div class="info__block">
            <div class="info__title">
                not_var{{texts.info_address}}
            </div>
            <div class="info__content">
                not_var{{address}}
            </div>
        </div>
    {{/if}}
    <div class="info__block">
        <div class="info__title">
            not_var{{texts.info_description}}
        </div>
        <div class="info__content">
            not_var{{product}}
        </div>
    </div>
    <div class="info__block">
        <div class="info__content">
            <a href=not_var{{image}} target="_blank" class="info__link">Ссылка на изображение товара</a>
        </div>
    </div>
</div>

{{#if reviewMode}}
    <div class="review">
        <div class="review__map">
            <div id="{{concat 'map_' id}}" style="width: 100%; height: 400px;"></div>
        </div>
        {{#if (equal verdict "ok")}}
            <div class="review__block">
                <div class="review__title">
                    not_var{{texts.btn_ok.question_1.title}}
                </div>
                <div class="review__imgs-grid">
                    {{#each imgs_facade}}
                        <div class="review__grid-item">
                            <div class="review__grid-inner">
                                <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
                                <div class="review__rotate-panel">
                                    <span class="review__rotate review__rotate_left">&larr;</span>
                                    <span class="review__rotate review__rotate_right">&rarr;</span>
                                </div>
                            </div>
                        </div>
                    {{/each}}
                </div>
            </div>
            <div class="review__block">
                <div class="review__title">
                    not_var{{texts.btn_ok.question_2.title}}
                </div>
                <div class="review__imgs-grid">
                    {{#each imgs_obj}}
                        <div class="review__grid-item">
                            <div class="review__grid-inner">
                                <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
                                <div class="review__rotate-panel">
                                    <span class="review__rotate review__rotate_left">&larr;</span>
                                    <span class="review__rotate review__rotate_right">&rarr;</span>
                                </div>
                            </div>
                        </div>
                    {{/each}}
                </div>
            </div>

            <!-- кастомизация начало фрагмента -->
            <!-- фото -->
            <div class="review__block">
              <div class="review__title">
                not_var{{texts.btn_ok.question_new_imgs.title}}
              </div>
              <div class="review__imgs-grid">
                {{#each new_imgs}}
                  <div class="review__grid-item">
                    <div class="review__grid-inner">
                      <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
                      <div class="review__rotate-panel">
                        <span class="review__rotate review__rotate_left">&larr;</span>
                        <span class="review__rotate review__rotate_right">&rarr;</span>
                      </div>
                    </div>
                  </div>
                {{/each}}
              </div>
            </div>

            <!-- видео -->
            <div class="review__block">
              <div class="review__title">
                not_var{{texts.btn_ok.question_new_video.title}}
              </div>
              <div class="review__video-grid">
                {{#each new_video}}
                  <div class="review__video-grid-item">
                    <video width="50%" controls>
                      <source src=not_var{{this}} type="video/mp4">
                    </video>
                  </div>
                {{/each}}
              </div>
            </div>

            <!-- аудио -->
            <div class="review__block">
              <div class="review__title">
                not_var{{texts.btn_ok.question_new_audio.title}}
              </div>
              <div class="review__audio-grid">
                {{#each new_audio}}
                  <div class="review__audio-grid-item">
                    <audio src=not_var{{this}} controls type="audio/wav">Невозможно воспроизвести</audio>
                  </div>
                {{/each}}
              </div>
            </div>
            <!-- кастомизация конец фрагмента -->

        {{/if}}
        {{#if (equal verdict "no_obj")}}
            <div class="review__block">
                <div class="review__title">
                    not_var{{texts.btn_no_obj.question_1.title}}
                </div>
                <div class="review__imgs-grid">
                    {{#each imgs_facade}}
                        <div class="review__grid-item">
                            <div class="review__grid-inner">
                                <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
                                <div class="review__rotate-panel">
                                    <span class="review__rotate review__rotate_left">&larr;</span>
                                    <span class="review__rotate review__rotate_right">&rarr;</span>
                                </div>
                            </div>
                        </div>
                    {{/each}}
                </div>
            </div>
            <div class="review__block">
                <div class="review__title">
                    not_var{{texts.btn_no_obj.question_2.title}}
                </div>
                <div class="review__imgs-grid">
                    {{#each imgs_plate_or_address}}
                        <div class="review__grid-item">
                            <div class="review__grid-inner">
                                <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
                                <div class="review__rotate-panel">
                                    <span class="review__rotate review__rotate_left">&larr;</span>
                                    <span class="review__rotate review__rotate_right">&rarr;</span>
                                </div>
                            </div>
                        </div>
                    {{/each}}
                </div>
            </div>
            <div class="review__block">
                <div class="review__title">
                    not_var{{texts.btn_no_obj.question_3.title}}
                </div>
                <div class="review__imgs-grid">
                    {{#each imgs_around_obj}}
                        <div class="review__grid-item">
                            <div class="review__grid-inner">
                                <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
                                <div class="review__rotate-panel">
                                    <span class="review__rotate review__rotate_left">&larr;</span>
                                    <span class="review__rotate review__rotate_right">&rarr;</span>
                                </div>
                            </div>
                        </div>
                    {{/each}}
                </div>
            </div>
            {{#if comment}}
                <div class="review__block">
                    <div class="review__title">
                        not_var{{texts.btn_no_obj.question_4.title}}
                    </div>
                    <div class="review__comment">
                        {{field type="textarea" name="comment" width="100%" rows=5}}
                    </div>
                </div>
            {{/if}}
        {{/if}}
        {{#if (equal verdict "no_org")}}
            <div class="review__block">
                <div class="review__title">
                    not_var{{texts.btn_no_org.question_1.title}}
                </div>
                <div class="review__imgs-grid">
                    {{#each imgs_around_org}}
                        <div class="review__grid-item">
                            <div class="review__grid-inner">
                                <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
                                <div class="review__rotate-panel">
                                    <span class="review__rotate review__rotate_left">&larr;</span>
                                    <span class="review__rotate review__rotate_right">&rarr;</span>
                                </div>
                            </div>
                        </div>
                    {{/each}}
                </div>
            </div>
            <div class="review__block">
                <div class="review__title">
                    not_var{{texts.btn_no_org.question_2.title}}
                </div>
                <div class="review__imgs-grid">
                    {{#each imgs_plate_or_address}}
                        <div class="review__grid-item">
                            <div class="review__grid-inner">
                                <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
                                <div class="review__rotate-panel">
                                    <span class="review__rotate review__rotate_left">&larr;</span>
                                    <span class="review__rotate review__rotate_right">&rarr;</span>
                                </div>
                            </div>
                        </div>
                    {{/each}}
                </div>
            </div>
            <div class="review__block">
                <div class="review__title">
                    not_var{{texts.btn_no_org.question_3.title}}
                </div>
                <div class="review__comment">
                    {{field type="textarea" name="comment" width="100%" rows=5}}
                </div>
            </div>
        {{/if}}
    </div>
not_var{{else}}
    <div class="main">
        <div class="main__title">
            Выберите вариант выполнения задания:
        </div>
        <div class="main__container">
            <div class="main__popup main__popup_hidden">Не выбран ни один вариант ответа</div>
            <div class="main__block">
                <div class="main__btn main__btn_green">
                    not_var{{texts.btn_ok.title}}
                </div>
                <div class="main__content">
                    <div class="main__content-block">
                        <div class="main__content-title main__content-title_req">
                            not_var{{texts.btn_ok.question_1.title}}
                        </div>
                        <div class="main__text">
                            not_var{{texts.btn_ok.question_1.description}}
                        </div>
                        <div class="main__ex">
                            <a href="not_var{{texts.btn_ok.question_1.example_link_1}}" target="_blank" class="main__ex-link">Пример</a>
                        </div>
                        <div class="main__imgs">
                            {{field type="file-img" name="imgs_facade" camera=true preview=true compress=false validation-show="top-left"}}
                        </div>
                    </div>
                    <div class="main__content-block">
                        <div class="main__content-title main__content-title_req">
                            not_var{{texts.btn_ok.question_2.title}}
                        </div>
                        <div class="main__text">
                            not_var{{texts.btn_ok.question_2.description}}
                        </div>
                        <div class="main__ex">
                            <a href="not_var{{texts.btn_ok.question_2.example_link_1}}" target="_blank" class="main__ex-link">Пример</a>
                        </div>
                        <div class="main__imgs">
                            {{field type="file-img" name="imgs_obj" camera=true preview=true compress=false validation-show="top-left"}}
                        </div>
                    </div>

                    <!-- кастомизация начало фрагмента -->
                    <!-- фото -->
                    <div class="main__content-block">
                      <div class="main__content-title main__content-title_req">
                        not_var{{texts.btn_ok.question_new_imgs.title}}
                      </div>
                      <div class="main__text">
                        not_var{{texts.btn_ok.question_new_imgs.description}}
                      </div>
                      <div class="main__ex">
                        <a href="not_var{{texts.btn_ok.question_new_imgs.example_link_1}}" target="_blank" class="main__ex-link">Пример</a>
                      </div>
                      <div class="main__imgs">
                        {{field type="file-img" name="new_imgs" camera=true validation-show="top-left"}}
                      </div>
                    </div>

                    <!-- видео -->
                    <div class="main__content-block">
                      <div class="main__content-title main__content-title_req">
                        not_var{{texts.btn_ok.question_new_video.title}}
                      </div>
                      <div class="main__text">
                        not_var{{texts.btn_ok.question_new_video.description}}
                      </div>
                      <div class="main__video">
                        {{field type="file" name="new_video" sources="CAMERA GALLERY" fileType="VIDEO" camera=true validation-show="top-left"}}
                      </div>
                    </div>

                    <!-- аудио -->
                    <div class="main__content-block">
                      <div class="main__content-title main__content-title_req">
                        not_var{{texts.btn_ok.question_new_audio.title}}
                      </div>
                      <div class="main__text">
                        not_var{{texts.btn_ok.question_new_audio.description}}
                      </div>
                      <div class="main__audio">
                        {{field type="file" name="new_audio" sources="RECORDER FILE_MANAGER" fileType="AUDIO" validation-show="top-left"}}
                      </div>
                    </div>
                    <!-- кастомизация конец фрагмента -->

                </div>
            </div>
            <div class="main__block">
                <div class="main__btn main__btn_red">
                    not_var{{texts.btn_no_obj.title}}
                </div>
                <div class="main__content">
                    <div class="main__content-block">
                        <div class="main__content-title main__content-title_req">
                            not_var{{texts.btn_no_obj.question_1.title}}
                        </div>
                        <div class="main__text">
                            not_var{{texts.btn_no_obj.question_1.description}}
                        </div>
                        <div class="main__ex">
                            <a href="not_var{{texts.btn_no_obj.question_1.example_link_1}}" target="_blank" class="main__ex-link">Пример</a>
                        </div>
                        <div class="main__imgs">
                            {{field type="file-img" name="imgs_facade" camera=true validation-show="top-left"}}
                        </div>
                    </div>
                    <div class="main__content-block">
                        <div class="main__content-title main__content-title_req">
                            not_var{{texts.btn_no_obj.question_2.title}}
                        </div>
                        <div class="main__text">
                            not_var{{texts.btn_no_obj.question_2.description}}
                        </div>
                        <div class="main__ex">
                            <a href="not_var{{texts.btn_no_obj.question_2.example_link_1}}" target="_blank" class="main__ex-link">Пример</a>
                        </div>
                        <div class="main__imgs">
                            {{field type="file-img" name="imgs_plate_or_address" camera=true validation-show="top-left"}}
                        </div>
                    </div>
                    <div class="main__content-block">
                        <div class="main__content-title main__content-title_req">
                            not_var{{texts.btn_no_obj.question_3.title}}
                        </div>
                        <div class="main__text">
                            not_var{{texts.btn_no_obj.question_3.description}}
                        </div>
                        <div class="main__ex">
                            <a href="not_var{{texts.btn_no_obj.question_3.example_link_1}}" target="_blank" class="main__ex-link">Пример</a>
                        </div>
                        <div class="main__imgs">
                            {{field type="file-img" name="imgs_around_obj" camera=true validation-show="top-left"}}
                        </div>
                    </div>
                    <div class="main__content-block">
                        <div class="main__content-title">
                            not_var{{texts.btn_no_obj.question_4.title}}
                        </div>
                        <div class="main__text">
                            not_var{{texts.btn_no_obj.question_4.description}}
                        </div>
                        <div class="main__comment">
                            {{field type="textarea" name="comment" width="100%" rows=5 validation-show="top-left"}}
                        </div>
                    </div>
                </div>
            </div>
            <div class="main__block">
                <div class="main__btn main__btn_red">
                    not_var{{texts.btn_no_org.title}}
                </div>
                <div class="main__content">
                    <div class="main__content-block">
                        <div class="main__text main__text_req">
                            not_var{{texts.btn_no_org.question_1.description}}
                        </div>
                        <div class="main__imgs">
                            {{field type="file-img" name="imgs_around_org" camera=true validation-show="top-left"}}
                        </div>
                    </div>
                    <div class="main__content-block">
                        <div class="main__text main__text_req">
                            not_var{{texts.btn_no_org.question_2.description}}
                        </div>
                        <div class="main__ex">
                            <a href="not_var{{texts.btn_no_org.question_2.example_link_1}}" target="_blank" class="main__ex-link">Пример</a>
                        </div>
                        <div class="main__imgs">
                            {{field type="file-img" name="imgs_plate_or_address" camera=true validation-show="top-left"}}
                        </div>
                    </div>
                    <div class="main__content-block">
                        <div class="main__content-title main__content-title_req">
                            not_var{{texts.btn_no_org.question_3.title}}
                        </div>
                        <div class="main__text">
                            not_var{{texts.btn_no_org.question_3.description}}
                        </div>
                        <div class="main__comment">
                            {{field type="textarea" name="comment" width="100%" rows=5 validation-show="top-left"}}
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
{{/if}}
Блок JS
var texts = {
    'task_title': 'Мониторинг объектов в организациях',
    'info_name': 'Название организации:',
    'info_address': 'Адрес:',
    'info_description': 'Описание объекта:',
    'btn_ok': {
        'title': 'Я нашел объект',
        'question_1': {
            'title': 'Фото фасада организации',
            'description': 'Сделайте 2 фото фасада организации с разных сторон так, чтобы на фото была видна вывеска и название хорошо читалось.',
            'example_link_1': 'https://mt-content-public.s3.yandex.net/instructions/toloka_field_templates/obj_org_1_3-min.png'
        },
        'question_2': {
            'title': 'Фото объекта',
            'description': 'Сделайте минимум 2 фото объекта с разных сторон так, чтобы был полностью виден объект, его атрибуты, наполнение (если есть) и местоположение внутри организации.',
            'example_link_1': 'https://mt-content-public.s3.yandex.net/instructions/toloka_field_templates/obj_org_2-min.png'
        },

        // кастомизация начало фрагмента
        'question_new_imgs': {
          'title': 'Фото',
          'description': 'Сделайте хотя бы 2 фотографии',
          'example_link_1': 'ссылка на пример фото'
        },
        'question_new_video': {
          'title': 'Видео',
          'description': 'Запишите видео'
        },
        'question_new_audio': {
          'title': 'Аудио',
          'description': 'Запишите аудио'
        }
        // кастомизация конец фрагмента

    },
    'btn_no_obj': {
        'title': 'Я в организации, но объекта нет',
        'question_1': {
            'title': 'Фото фасада организации',
            'description': 'Сделайте 2 фото фасада организации с разных сторон так, чтобы на фото была видна вывеска и название хорошо читалось.',
            'example_link_1': 'https://mt-content-public.s3.yandex.net/instructions/toloka_field_templates/obj_org_1_3-min.png'
        },
        'question_2': {
            'title': 'Фото таблички организации',
            'description': 'Сфотографируйте табличку искомого адреса или информационный лист с адресом организации на входе.',
            'example_link_1': 'https://mt-content-public.s3.yandex.net/instructions/toloka_field_templates/obj_org_4_5-min.png'
        },
        'question_3': {
            'title': 'Фото окружения',
            'description': 'Сфотографируйте место, где должен находиться объект, со всех сторон так, чтобы можно было сделать однозначный вывод об отсутствии объекта.',
            'example_link_1': 'https://mt-content-public.s3.yandex.net/instructions/toloka_field_templates/obj_org_6-min.png'
        },
        'question_4': {
            'title': 'Комментарий',
            'description': 'Попробуйте узнать причину отсутствия объекта в организации и укажите в комментарии (например: не выставили, не привезли, стоит на складе и т.п.).'
        }
    },
    'btn_no_org': {
        'title': 'Организация закрыта или отсутствует',
        'question_1': {
            'title': 'Фото здания со всех сторон',
            'description': 'Сфотографируйте со всех сторон здание или место, где должна находиться организация, так, чтобы можно было убедиться, что нужной организации нет. '
        },
        'question_2': {
            'title': 'Фото таблички организации',
            'description': 'Сфотографируйте табличку искомого адреса или информационный лист с адресом организации на входе.',
            'example_link_1': 'https://mt-content-public.s3.yandex.net/instructions/toloka_field_templates/obj_org_4_5-min.png'
        },
        'question_3': {
            'title': 'Обязательный комментарий',
            'description': 'Напишите причину отсутствия или закрытия организации (например: на ремонте, закрыт, не удалось найти и пр.).'
        }
    }
};

// Максимальная удаленность исполнителя от магазина в километрах.
var MAX_DISTANCE = 1;

var verdictsOut = ['ok', 'no_obj', 'no_org'];

var injectMaps = function(locale) {
    return new Promise(function(resolve, reject) {
        var script = document.createElement('script');

        script.async = true;
        script.src = "https://api-maps.yandex.ru/2.1/?load=package.full,vectorEngine.preload&lang=" + locale + "_" + locale.toUpperCase() + "&csp=true";
        script.addEventListener('load', resolve);
        script.addEventListener('error', reject);
        script.addEventListener('abort', reject);

        document.head.appendChild(script);
    });
};

var map;

exports.Assignment = extend(TolokaAssignment, function (options) {
    TolokaAssignment.call(this, options);

    var workspaceOptions = this.getWorkspaceOptions();

    if (workspaceOptions.isReviewMode) {
        map = injectMaps('ru');
    }
}, {});

exports.Task = extend(TolokaHandlebarsTask, function (options) {
    TolokaHandlebarsTask.call(this, options);
}, {
    getTemplateData: function() {
        var data = TolokaHandlebarsTask.prototype.getTemplateData.apply(this, arguments);
        var workspaceOptions = this.getWorkspaceOptions();
        var outputValues = this.getSolution().output_values;

        this.setSolutionOutputValue('coordinates', data.coordinates);
        this.setSolutionOutputValue('address', data.address);

        data.id = this.getTask().id;
        data.texts = texts;

        if (workspaceOptions.isReviewMode) {
            data.reviewMode = true;

            if (outputValues.imgs_facade && outputValues.imgs_facade.length > 0) {
                data.imgs_facade = [];
                for (var i = 0; i < outputValues.imgs_facade.length; i++) {
                    data.imgs_facade.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.imgs_facade[i] + '/preview');
                }
            }

            if (outputValues.imgs_obj && outputValues.imgs_obj.length > 0) {
                data.imgs_obj = [];
                for (var i = 0; i < outputValues.imgs_obj.length; i++) {
                    data.imgs_obj.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.imgs_obj[i] + '/preview');
                }
            }

            if (outputValues.imgs_plate_or_address && outputValues.imgs_plate_or_address.length > 0) {
                data.imgs_plate_or_address = [];
                for (var i = 0; i < outputValues.imgs_plate_or_address.length; i++) {
                    data.imgs_plate_or_address.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.imgs_plate_or_address[i] + '/preview');
                }
            }

            if (outputValues.imgs_around_obj && outputValues.imgs_around_obj.length > 0) {
                data.imgs_around_obj = [];
                for (var i = 0; i < outputValues.imgs_around_obj.length; i++) {
                    data.imgs_around_obj.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.imgs_around_obj[i] + '/preview');
                }
            }

            if (outputValues.imgs_around_org && outputValues.imgs_around_org.length > 0) {
                data.imgs_around_org = [];
                for (var i = 0; i < outputValues.imgs_around_org.length; i++) {
                    data.imgs_around_org.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.imgs_around_org[i] + '/preview');
                }
            }

            if (outputValues.comment) {
                data.comment = outputValues.comment;
            }

            if (outputValues.verdict) {
                data.verdict = outputValues.verdict;
            }

            // кастомизация начало фрагмента
            // фото
            if (outputValues.new_imgs && outputValues.new_imgs.length > 0) {
              data.new_imgs = [];
              for (var i = 0; i < outputValues.new_imgs.length; i++) {
                data.new_imgs.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.new_imgs[i] + '/preview');
              }
            }

            // видео
            if (outputValues.new_video && outputValues.new_video.length > 0) {
              data.new_video = [];
              for (var i = 0; i < outputValues.new_video.length; i++) {
                data.new_video.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.new_video[i] + '/preview');
              }
            }

            // аудио
            if (outputValues.new_audio && outputValues.new_audio.length > 0) {
              data.new_audio = [];
              for (var i = 0; i < outputValues.new_audio.length; i++) {
                data.new_audio.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.new_audio[i] + '/preview');
              }
            }
            // кастомизация конец фрагмента

        } else {
            data.reviewMode = false;

            var assId = this.getOptions().assignment._options.assignment.id;
            var taskID = this.getTask().id;
            var solutionStorage = this.storage.getItem('solution_' + assId);

            if (solutionStorage && solutionStorage[taskID]) {
                this.setSolutionOutputValue('verdict', solutionStorage[taskID].verdict);
            }
        }

        return data;
    },
    initFastFileSelector: function() {
        var $el = $(this.getDOMElement()),
            sources = [],
            type = '',
            audioRecorder = $el.find('.audioRecorder');

        $el.find('.field_file-img__upload_camera').each(function (i,el) {
            $(el).on('click',function () {
                sources = ['CAMERA'];
                type = 'IMAGE';
            })
        });

        $el.find(".field_file-img__label").prepend($("<span/>", {class: "field_file-img__upload gallery"}));

        $el.find('.gallery').on('click',function () {
            sources = ['GALLERY'];
            type = 'IMAGE';
        });

        var baseGetFile = this.file.getFile.bind(this.file);
        this.file.getFile = function(options) {
            var promise = baseGetFile(
                _.extend(options, {
                    sources: _.isEmpty(sources) ? ["GALLERY", "CAMERA"] : sources
                })
            );
            sources = ["GALLERY", "CAMERA"];

            return promise;
        };
    },
    onRender: function() {
        this.rendered = true;

        var task = this.getDOMElement();
        var that = this;
        var workspaceOptions = this.getWorkspaceOptions();
        var outputValues = this.getSolution().output_values;

        if (workspaceOptions.isReviewMode) {
            var reviewImgs = task.querySelectorAll('.review__grid-item');
            var initMap = this.initMap.bind(this);

            for (var i = 0; i < reviewImgs.length; i++) {
                reviewImgs[i].addEventListener('click', this.handleImg);
            }

            map.then(function() {
                ymaps.ready(initMap);
            });
        } else if (workspaceOptions.isReadOnly) {
            var mainBlocks = task.querySelectorAll('.main__block');
            var selectedBtnId = verdictsOut.indexOf(outputValues.verdict);

            for (var f = 0; f < mainBlocks.length; f++) {
                if (f === selectedBtnId) {
                    mainBlocks[f].querySelector('.main__content').classList.add('main__content_active');
                    mainBlocks[f].querySelector('.main__btn').classList.add('main__btn_active');
                } else {
                    mainBlocks[f].classList.add('main__block_hidden');
                }
            }

            this.initFastFileSelector();

        } else {
            var btns = task.querySelectorAll('.main__btn');
            var mainBlocks = task.querySelectorAll('.main__block');
            var assId = this.getOptions().assignment._options.assignment.id;
            var taskID = this.getTask().id;
            var solutionStorage = this.storage.getItem('solution_' + assId);

            if (solutionStorage && solutionStorage[taskID]) {
                if (solutionStorage[taskID].selectedBtnId >= 0) {
                    var selectedBtnId = solutionStorage[taskID].selectedBtnId;

                    for (var f = 0; f < mainBlocks.length; f++) {
                        if (f === selectedBtnId) {
                            mainBlocks[f].querySelector('.main__content').classList.add('main__content_active');
                            mainBlocks[f].querySelector('.main__btn').classList.add('main__btn_active');
                        } else {
                            mainBlocks[f].classList.add('main__block_hidden');
                        }
                    }
                }
            }

            for (var i = 0; i < btns.length; i++) {
                btns[i].addEventListener('click', this.handleBtn.bind(this, i, mainBlocks));
            }

            this.initFastFileSelector();

            task.querySelector('.main__popup').addEventListener('click', this.handleMainPopup);
        }
    },
    initMap: function() {
        var inputValues = this.getTask().input_values;
        var outputValues = this.getSolution().output_values;

        if (!inputValues.coordinates || inputValues.coordinates === '') {
            return;
        }

        var coordinates = inputValues.coordinates.split(',');

        var myMap = new ymaps.Map('map_' + inputValues.id, {
            center: coordinates,
            zoom: 15
        });

        var shop = new ymaps.GeoObject({
            geometry: {
                type: "Point",
                coordinates: coordinates
            },
            properties: {
                iconContent: 'Организация'
            }
        }, {
            preset: 'islands#greenStretchyIcon'
        });

        myMap.geoObjects.add(shop);

        if (outputValues.worker_coordinates) {
            var workerCoordinates = outputValues.worker_coordinates.split(',');

            var worker = new ymaps.GeoObject({
                geometry: {
                    type: "Point",
                    coordinates: workerCoordinates
                },
                properties: {
                    iconContent: 'Исполнитель'
                }
            }, {
                preset: 'islands#blueStretchyIcon'
            });

            myMap.geoObjects.add(worker);
        }
    },
    handleImg: function(e) {
        var img = e.currentTarget.querySelector('.review__img');

        if (e.target.classList.contains('review__rotate_left')) {
            img.dataset.rotationdeg = parseInt(img.dataset.rotationdeg, 10) - 90;
            img.style.transform = 'rotate(' + img.dataset.rotationdeg + 'deg)';
        } else if (e.target.classList.contains('review__rotate_right')) {
            img.dataset.rotationdeg = parseInt(img.dataset.rotationdeg, 10) + 90;
            img.style.transform = 'rotate(' + img.dataset.rotationdeg + 'deg)';
        }

        if (e.target.classList.contains('review__img') || e.target.classList.contains('review__grid-inner')) {
            e.currentTarget.querySelector('.review__grid-inner').classList.toggle('review__grid-inner_zoomed');
        }
    },
    handleBtn: function(i, mainBlocks, e) {
        var mainContent = e.currentTarget.parentNode.querySelector('.main__content');
        var outputValues = this.getSolution().output_values;
        var task = this.getDOMElement();
        var assId = this.getOptions().assignment._options.assignment.id;
        var taskID = this.getTask().id;
        var solutionStorage = this.storage.getItem('solution_' + assId);
        var newSolution = {};
        newSolution[taskID] = {};

        if (!e.currentTarget.classList.contains('main__btn_active')) {
            task.querySelector('.main__popup').classList.add('main__popup_hidden');

            this.setSolutionOutputValue('verdict', verdictsOut[i]);

            e.currentTarget.classList.add('main__btn_active');
            mainContent.classList.add('main__content_active');
            for (var j = 0; j < mainBlocks.length; j++) {
                if (j !== i) {
                    mainBlocks[j].classList.add('main__block_hidden');
                }
            }

            if (assId) {
                if (!solutionStorage) {
                    newSolution[taskID].selectedBtnId = i;
                    newSolution[taskID].verdict = verdictsOut[i];
                    if (i === 0) {
                        newSolution[taskID].comment = '';
                    }

                    this.storage.setItem('solution_' + assId, newSolution, new Date().getTime() + 21600000);
                } else {
                    if (!solutionStorage[taskID]) {
                        solutionStorage[taskID] = {};
                    }

                    solutionStorage[taskID].selectedBtnId = i;
                    solutionStorage[taskID].verdict = verdictsOut[i];

                    this.storage.setItem('solution_' + assId, solutionStorage, new Date().getTime() + 21600000);
                }
            }
        } else {
            var fields = this._fields;
            var deleteBtnsLength = task.querySelectorAll('.main .file__delete').length;

            for (var h = 0; h < deleteBtnsLength; h++) {
                $(task).find('.main .file__delete').first().trigger('click');
            }

            this.setSolutionOutputValue('verdict', '');
            this.setSolutionOutputValue('comment', '');

            e.currentTarget.classList.remove('main__btn_active');
            mainContent.classList.remove('main__content_active');

            for (var key in fields) {
                if (fields.hasOwnProperty(key)) {
                    for (var p = 0; p < fields[key].length; p++) {
                        fields[key][p].hideError();
                    }
                }
            }

            for (var j = 0; j < mainBlocks.length; j++) {
                if (j !== i) {
                    mainBlocks[j].classList.remove('main__block_hidden');
                }
            }

            if (assId) {
                if (!solutionStorage) {
                    newSolution[taskID].selectedBtnId = -1;
                    newSolution[taskID].verdict = '';
                    this.storage.setItem('solution_' + assId, newSolution, new Date().getTime() + 21600000);
                } else {
                    if (!solutionStorage[taskID]) {
                        solutionStorage[taskID] = {};
                    }

                    solutionStorage[taskID].selectedBtnId = -1;
                    solutionStorage[taskID].verdict = '';
                    this.storage.setItem('solution_' + assId, solutionStorage, new Date().getTime() + 21600000);
                }
            }
        }
    },
    // Функция определения расстояния между точками по их широте и долготе.
    _getDistanceBetweenCoords: function(lat1, lon1, lat2, lon2) {
        var Earth = 6371; // Radius of the Earth in km
        var x =
            (((lon2 - lon1) * Math.PI) / 180) *
            Math.cos((((lat1 + lat2) / 2) * Math.PI) / 180);
        var y = ((lat2 - lat1) * Math.PI) / 180;
        return Earth * Math.sqrt(x * x + y * y);
    },
    // Функция определения расстояния между двумя точками.
    _getDistance: function(coords1, coords2) {
        var coordFirst = {
            lat: parseFloat(coords1.split(",")[0]),
            lon: parseFloat(coords1.split(",")[1])
        };
        var coordSecond = {
            lat: parseFloat(coords2.split(",")[0]),
            lon: parseFloat(coords2.split(",")[1])
        };

        var dist = this._getDistanceBetweenCoords(
            coordFirst.lat,
            coordFirst.lon,
            coordSecond.lat,
            coordSecond.lon
        );
        return dist;
    },
    checkUserPosition: function(inputValues, outputValues) {
        if (outputValues["worker_coordinates"] &&
            inputValues["coordinates"] && this._getDistance(outputValues["worker_coordinates"], inputValues["coordinates"]) > MAX_DISTANCE) {
            return true;
        } else {
            return false;
        }
    },
    addError: function (message, field, errors) {
        errors || (errors = {
            task_id: this.getOptions().task.id,
            errors: {}
        });
        errors.errors[field] = {
            message: message
        };

        return errors;
    },
    onValidationFail: function (errors) {
        TolokaTask.prototype.onValidationFail.call(this, errors);

        var task = this.getDOMElement();

        _.each(errors.errors, function (error, fieldName) {
            if (fieldName === '__TASK__') {
                this.showTaskError(error.message);
            } else if (fieldName === 'verdict') {
                task.querySelector('.main__popup').classList.remove('main__popup_hidden');
            } else {
                var fields = this._fields[fieldName];

                if (fields) {
                    for (var i = 0; i < fields.length; i++) {
                        fields[i].showError(error);
                    }
                }
            }
        }.bind(this));
    },
    handleMainPopup: function(e) {
        e.currentTarget.classList.add('main__popup_hidden');
    },
    validate: function (solution) {
        this.errors = null;
        var task = this.getDOMElement();
        var input_values = this.getTask().input_values;

        if (!solution.output_values.verdict || solution.output_values.verdict === '') {
            this.errors = this.addError('Не выбран ни один вариант ответа', "verdict", this.errors);
        } else if (solution.output_values.verdict === 'ok') {
            if (!solution.output_values.imgs_facade || solution.output_values.imgs_facade.length === 0) {
                this.errors = this.addError('Нужно приложить фото организации', "imgs_facade", this.errors);
            } else if (solution.output_values.imgs_facade.length < 2) {
                this.errors = this.addError('Должно быть хотя бы 2 фото организации', "imgs_facade", this.errors);
            }

            if (!solution.output_values.imgs_obj || solution.output_values.imgs_obj.length === 0) {
                this.errors = this.addError('Нужно приложить фото объекта', "imgs_obj", this.errors);
            } else if (solution.output_values.imgs_obj.length < 2) {
                this.errors = this.addError('Должны быть хотя бы 2 фотографии объекта', "imgs_obj", this.errors);
            }

            // кастомизация начало фрагмента
            if (!solution.output_values.new_imgs || solution.output_values.new_imgs.length === 0) {
              this.errors = this.addError('Нужно приложить фото', "new_imgs", this.errors);
            } else if (solution.output_values.new_imgs.length < 2) {
              this.errors = this.addError('Должно быть хотя бы 2 фото', "new_imgs", this.errors);
            }

            if (!solution.output_values.new_video || solution.output_values.new_video.length < 1) {
              this.errors = this.addError('Нужно приложить видео', 'new_video', this.errors);
            }

            if (!solution.output_values.new_audio || solution.output_values.new_audio.length < 1) {
              this.errors = this.addError('Нужно приложить аудио', 'new_audio', this.errors);
            }
            // кастомизация конец фрагмента

        } else if (solution.output_values.verdict === 'no_obj') {
            if (!solution.output_values.imgs_facade || solution.output_values.imgs_facade.length === 0) {
                this.errors = this.addError('Нужно приложить фото организации', "imgs_facade", this.errors);
            } else if (solution.output_values.imgs_facade.length < 2) {
                this.errors = this.addError('Должно быть хотя бы 2 фото организации', "imgs_facade", this.errors);
            }

            if (!solution.output_values.imgs_plate_or_address || solution.output_values.imgs_plate_or_address.length === 0) {
                this.errors = this.addError('Нужно приложить фото таблички организации', "imgs_plate_or_address", this.errors);
            }

            if (!solution.output_values.imgs_around_obj || solution.output_values.imgs_around_obj.length === 0) {
                this.errors = this.addError('Нужно приложить фотографии окружения', "imgs_around_obj", this.errors);
            } else if (solution.output_values.imgs_around_obj.length < 4) {
                this.errors = this.addError('Должно быть хотя бы 4 фотографии окружения', "imgs_around_obj", this.errors);
            }
        } else if (solution.output_values.verdict === 'no_org') {
            if (!solution.output_values.imgs_around_org || solution.output_values.imgs_around_org.length === 0) {
                this.errors = this.addError('Нужно приложить фото здания', "imgs_around_org", this.errors);
            } else if (solution.output_values.imgs_around_org.length < 4) {
                this.errors = this.addError('Должно быть хотя бы 4 фотографии здания', "imgs_around_org", this.errors);
            }

            if (!solution.output_values.imgs_plate_or_address || solution.output_values.imgs_plate_or_address.length === 0) {
                this.errors = this.addError('Нужно приложить фото таблички организации', "imgs_plate_or_address", this.errors);
            }

            if (!solution.output_values.comment || solution.output_values.comment.trim() === '') {
                this.errors = this.addError('Нужно написать комментарий', "comment", this.errors);
            }
        }

        if (this.checkUserPosition.call(this, input_values, solution.output_values)) {
            this.errors = this.addError('Вы находитесь слишком далеко от организации', "__TASK__", this.errors);
        }

        if (!solution.output_values.worker_coordinates) {
            this.errors = this.addError('Не удалось получить ваши координаты. Пожалуйста, включите геолокацию.', "__TASK__", this.errors);
        }

        return this.errors || TolokaHandlebarsTask.prototype.validate.call(this, solution);
    },
    onDestroy: function() {

    }
});

exports.TaskSuite = extend(TolokaHandlebarsTaskSuite, function (options) {
    TolokaHandlebarsTaskSuite.call(this, options);
}, {
    onValidationFail: function (errors) {
        TolokaTaskSuite.prototype.onValidationFail.call(this, errors);

        var tasks = this.getDOMElement().querySelectorAll('.task');

        if (errors && errors.length > 0) {
            var firstError;

            for (var i = 0; i < errors.length; i++) {
                if (errors[i]) {
                    firstError = errors[i];
                    break;
                }
            }

            var firstTaskWithError = tasks[parseInt(firstError.task_id, 10)];
            this.focusTask(parseInt(firstError.task_id, 10));

            _.each(firstError.errors, function (error, fieldName) {
                if (fieldName === '__TASK__') {
                    firstTaskWithError.querySelector('.task__error').scrollIntoView();
                } else if (fieldName === 'verdict') {
                    firstTaskWithError.querySelector('.main__popup').scrollIntoView();
                } else {
                    firstTaskWithError.querySelector('.main__btn_active').parentNode.querySelector('.popup_visible').scrollIntoView();
                }
            }.bind(this));
        }
    },
    focusTask: function(index) {
        TolokaTaskSuite.prototype.focusTask.call(this, index, 'withoutScroll');
    }
});

function extend(ParentClass, constructorFunction, prototypeHash) {
    constructorFunction = constructorFunction || function () {};
    prototypeHash = prototypeHash || {};
    if (ParentClass) {
        constructorFunction.prototype = Object.create(ParentClass.prototype);
    }
    for (var i in prototypeHash) {
        constructorFunction.prototype[i] = prototypeHash[i];
    }
    return constructorFunction;
}
Блок CSS
.task {
    display: block;
    max-width: 1200px;
    margin: 20px auto;
    padding: 0;
    border: none;
}

.task-suite {
    height: 100%;
}

.header {
    font-weight: 700;
    background-color: #c0d1db;
    padding: 15px;
}

.header-review {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -webkit-box-pack: justify;
    -ms-flex-pack: justify;
    justify-content: space-between;
    -webkit-box-align: center;
    -ms-flex-align: center;
    align-items: center;
    padding-left: 10px;
    border-bottom: 1px solid #eaeaea;
}

.header-review__title {
    font-weight: 700;
}

.header-review__btn {
    padding: 10px;
    text-align: center;
}

.header-review__btn_green {
    background-color: #87da84;
}

.header-review__btn_red {
    background-color: #ef5f56;
}

.info__review {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    border-bottom: 1px solid #eaeaea;
}

.info__review-block {
    padding: 10px 0px;
}

.info__review-block:first-child {
    width: 35%;
    -ms-flex-negative: 0;
    flex-shrink: 0;
    margin-right: 15px;
    border-right: 1px solid #eaeaea;
}

.info {
    padding: 0px 10px;
    margin-bottom: 20px;
}

.info__block {
    padding: 10px 0px;
    border-bottom: 1px solid #eaeaea;
}

.info__title {
    padding-bottom: 10px;
}

.info__content {
    font-weight: 700;
}

.info__link {
    color: #314beb;
    text-decoration: none;
}

.main {
    padding-bottom: 15px;
    padding-right: 15px;
    padding-left: 15px;
}

.main__popup {
    position: absolute;
    bottom: 101%;
    left: 50%;
    -webkit-transform: translateX(-50%);
    -ms-transform: translateX(-50%);
    transform: translateX(-50%);
    margin: 0;
    width: 250px;
    max-width: 100%;
    white-space: normal;
    text-align: center;
    font-size: 13px;
    line-height: 20px;
    padding: 2px 8px;
    background: hsla(0,100%,70%,.9);
    color: #fff;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    cursor: pointer;
    z-index: 98;
}

.main__popup_hidden {
    display: none;
}

.main__title {
    margin-bottom: 20px;
}

.main__container {
    padding: 0px 10px;
    position: relative;
}

.main__block {
    margin-bottom: 10px;
}

.main__block:last-child {
    margin-bottom: 0px;
}

.main__block_hidden {
    display: none;
}

.main__btn {
    padding: 20px;
    font-size: 18px;
    border: 1px solid #afc0cb;
    background-color: #c0d1db;
    text-align: center;
}

.main__btn_green.main__btn_active {
    border: 1px solid #84fe81;
    background-color: #87da84;
}

.main__btn_red.main__btn_active {
    background-color: #ef5f56;
}

.main__btn:hover {
    cursor: pointer;
}

.main__content {
    padding: 0px 5px;
    display: none;
}

.main__content_active {
    display: block;
}

.main__content-block {
    padding: 15px 0px;
}

.main__content-title {
    font-weight: 700;
    margin-bottom: 15px;
}

.main__text {
    font-style: italic;
    margin-bottom: 10px;
}

.main__ex {
    margin-bottom: 10px;
}

.main__ex-link {
    color: #314beb;
    text-decoration: none;
}

.main__ex-link:first-child {
    margin-right: 5px;
}

.task__error {
    left: 0;
    right: 0;
    top: 0;
    width: auto;
}

.review__block {
    padding: 15px;
}

.review__title {
    padding-bottom: 5px;
    font-weight: 700;
}

.review__comment {
    padding-top: 10px;
}

.review__imgs-grid {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    margin: 0px -10px;
}

.review__grid-item {
    margin: 10px;
    width: 350px;
    height: 350px;
    position: relative;
    border: 1px solid #E5E5E5;
    border-radius: 5px;
    overflow: hidden;
}

.review__grid-inner_zoomed {
    position: fixed;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.65);
    z-index: 1000;
    cursor: -webkit-zoom-out;
    cursor: zoom-out;
}

.review__img {
    position: absolute;
    margin: auto;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    cursor: -webkit-zoom-in;
    cursor: zoom-in;
    max-width: 100%;
    max-height: 100%;
}

.review__rotate-panel {
    position: absolute;
    bottom: 0;
    right: 0;
    background-color: rgba(0, 0, 0, 0.65);
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
}

.review__rotate {
    cursor: pointer;
    padding: 10px;
    color: #fff;
    font-size: 20px;
}

.review__map {
    padding: 15px;
}

.review__video-grid {
  margin: 0px -10px;
}

.review__audio-grid {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -ms-flex-wrap: wrap;
      flex-wrap: wrap;
  margin: 0px -10px;
}

.review__audio-grid-item {
  margin: 10px;
}

.review__video-grid-item {
  margin: 10px;
}

.main__video .field,
.main__audio .field {
  white-space: normal;
}

.main__video .field_file__files__file,
.main__audio .field_file__files__file {
  margin-bottom: 10px;
}

.field_file-img__uploader {
    width: 100%;
    display: block;
    float: none;
    margin-right: 0;
}

.field_file-img__upload {
    display: inline-block;
    width: 50%;
    float: left;
}

.field_file-img_with-preview .field_file-img__label {
    width: 100%;
}

.field_file-img__files__file_with-preview {
    width: 100%;
    margin-right: 0px;
    height: 200px;
}

.field_type_file-img {
    margin-right: 0px;
}

.field_file-img__files {
    white-space: normal;
}

.field_file-img__files__file_with-preview .file__name {
    white-space: normal;
}

.popup__text {
    width: 230px;
}

.field_file-img__files__file_with-preview .file__delete {
    width: 35px;
    height: 35px;
    line-height: 35px;
}

.field_file__files__file {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
}

.icon_clear {
    -ms-flex-negative: 0;
    flex-shrink: 0;
    background-size: 40%;
    background: no-repeat 50% 0;
}

.main__content-title_req:after {
    display: inline-block;
    color: red;
    content: '*';
}

.main__text_req:after {
    display: inline-block;
    color: red;
    content: '*';
}

.task:not(.task_focused) .popup_visible.popup_type_error, .task:not(.task_focused) .task__error {
    opacity: 1;
}

@media screen and (max-width: 767px) {
    .header-review__title {
        margin-right: 10px;
        padding: 10px 0px;
    }
    .info__review {
        -ms-flex-wrap: wrap;
        flex-wrap: wrap;
    }
    .info__review-block:first-child {
        width: 100%;
        -ms-flex-negative: 1;
        flex-shrink: 1;
        margin-right: 0px;
        border-right: none;
        border-bottom: 1px solid #eaeaea;
    }
}

Редактирование выходной спецификации

Добавьте новые переменные с типом «файл» в выходную спецификацию. В них будут записываться загружаемые исполнителем файлы. Если необходимо, чтобы исполнитель имел возможность загрузить больше одного файла, то отметьте специальный флажок «массив» для переменной.

new_imgs — массив файлов для фотографий;

new_video — массив файлов для видео;

new_audio — массив файлов для аудио.

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

Редактирование HTML

  1. Код HTML состоит из блоков, описывающих различные элементы интерфейса. Каждый блок может содержать внутри себя другие блоки. Таких уровней вложенности может быть несколько. Например, блок с описанием кнопки ответа содержит в себе блоки с полями для заполнения. Каждое поле тоже содержит в себе другие элементы, например, заголовок и поле для комментария.

    Каждый блок оформляется так:

    `<div class="наименование_блока">`
    <!-- код блока, может содержать вложенные блоки -->
    ...
    </div>
    
  2. Найдите блок main (он начинается со строки <div class="main">). Внутри него расположены блоки main__block, каждый из которых описывает одну из кнопок. Например, в шаблоне «Мониторинг объектов в организациях» есть 3 кнопки для ответа, значит, в блоке main у этого шаблона будет 3 блока main__block для каждой из кнопок.

    У каждой из кнопок есть наименование для обращения к ее свойствам. Например, в шаблоне «Мониторинг объектов в организациях» 3 кнопки называются btn_ok, btn_no_obj и btn_no_org. Запомните наименование той кнопки, в код которой добавляете новые поля.

    Внутри блока main__block расположен блок main__content, который содержит все поля для выбранной кнопки. Описание каждого отдельного поля расположено в блоках main__content-block.

    Найдите нужную кнопку main__block, в ней найдите поле main__content-block, после которого вы хотите добавить новое поле и вставьте после него следующий код:

    <!-- фото -->
    <div class="main__content-block">
    <div class="main__content-title main__content-title_req">
    not_var{{texts.btn_ok.question_new_imgs.title}}
    </div>
    <div class="main__text">
    not_var{{texts.btn_ok.question_new_imgs.description}}
    </div>
    <div class="main__ex">
    <a href="not_var{{texts.btn_ok.question_new_imgs.example_link_1}}" target="_blank" class="main__ex-link">Пример</a>
    </div>
    <div class="main__imgs">
    {{field type="file-img" name="new_imgs" camera=true validation-show="top-left"}}
    </div>
    </div>
    
    <!-- видео -->
    <div class="main__content-block">
    <div class="main__content-title main__content-title_req">
    not_var{{texts.btn_ok.question_new_video.title}}
    </div>
    <div class="main__text">
    not_var{{texts.btn_ok.question_new_video.description}}
    </div>
    <div class="main__video">
    {{field type="file" name="new_video" sources="CAMERA GALLERY" fileType="VIDEO" camera=true validation-show="top-left"}}
    </div>
    </div>
    
    <!-- аудио -->
    <div class="main__content-block">
    <div class="main__content-title main__content-title_req">
    not_var{{texts.btn_ok.question_new_audio.title}}
    </div>
    <div class="main__text">
    not_var{{texts.btn_ok.question_new_audio.description}}
    </div>
    <div class="main__audio">
    {{field type="file" name="new_audio" sources="RECORDER FILE_MANAGER" fileType="AUDIO" validation-show="top-left"}}
    </div>
    </div>
    

    В этом коде новые поля добавляются для первой кнопки с наименованием btn_ok. Если вы добавили поля для другой кнопки, измените наименование btn_ok на нужное.

  3. Обновите режим приемки.

    Блок review содержит в себе код для каждой кнопки в режиме приемки. Этот код расположен в таких блоках:

    {{#if (equal verdict "ok")}}
    <!-- код для кнопки "ok" в режиме приемки -->
    <div class="review__block">
    <!-- код для поля внутри кнопки "ok" в режиме приемки -->
    ...
    </div>
    ...
    {{/if}}
    

    Переменная verdict указана в выходной спецификации, в нее будет передаваться значение ответа для той кнопки, которую нажал исполнитель.

    Например, в шаблоне «Мониторинг объектов в организациях» для трех кнопок описаны три выходных значения: ok, no_obj и no_org.

    Блоки review__block содержат описание каждого из полей для данной кнопки.

    Найдите нужную кнопку по строке {{#if (equal verdict "значение_ответа_кнопки")}}, в ней найдите поле review__block, после которого вы хотите добавить новое поле, и вставьте после него следующий код:

    <!-- фото -->
    <div class="review__block">
    <div class="review__title">
    not_var{{texts.btn_ok.question_new_imgs.title}}
    </div>
    <div class="review__imgs-grid">
    {{#each new_imgs}}
    <div class="review__grid-item">
    <div class="review__grid-inner">
    <img src="not_var{{this}}" class="review__img" data-rotationdeg="0">
    <div class="review__rotate-panel">
    <span class="review__rotate review__rotate_left">&larr;</span>
    <span class="review__rotate review__rotate_right">&rarr;</span>
    </div>
    </div>
    </div>
    {{/each}}
    </div>
    </div>
    
    <!-- видео -->
    <div class="review__block">
    <div class="review__title">
    not_var{{texts.btn_ok.question_new_video.title}}
    </div>
    <div class="review__video-grid">
    {{#each new_video}}
    <div class="review__video-grid-item">
    <video width="50%" controls>
    <source src=not_var{{this}} type="video/mp4">
    </video>
    </div>
    {{/each}}
    </div>
    </div>
    
    <!-- аудио -->
    <div class="review__block">
    <div class="review__title">
    not_var{{texts.btn_ok.question_new_audio.title}}
    </div>
    <div class="review__audio-grid">
    {{#each new_audio}}
    <div class="review__audio-grid-item">
    <audio src=not_var{{this}} controls type="audio/wav">Невозможно воспроизвести</audio>
    </div>
    {{/each}}
    </div>
    </div>
    

    В этом коде новые поля добавляются для первой кнопки с наименованием btn_ok. Если вы добавили поля для другой кнопки, измените наименование btn_ok на нужное.

Редактирование JS

  1. Код JS состоит из блоков, описывающих различные элементы интерфейса. Эти блоки могут быть вложенными (кнопки содержат набор полей, поля содержат набор элементов и т. д.). Каждый блок заключен в фигурные скобки.

    В общем виде элементы описываются так:

    'свойство': 'значение'
    

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

  2. В самом начале файла находится константа texts, в которой хранятся все необходимые для интерфейса тексты для каждой кнопки.

    У каждой из кнопок есть наименование для обращения к ее свойствам. Например, в шаблоне «Мониторинг объектов в организациях» 3 кнопки называются btn_ok, btn_no_obj и btn_no_org. Запомните наименование той кнопки, в код которой добавляете новый текст.

    Например, в шаблоне «Мониторинг объектов в организациях» тексты для кнопки btn_ok расположены в следующем блоке кода:

    var texts = {
    //<общий текст для заголовков>
    'btn_ok': {
    'title': 'Я нашел объект',
    'question_1': {
    //<тексты для первого поля (фото фасада организации)>
    },
    'question_2': {
    //<тексты для второго поля (фото объекта)>
    }
    },
    
  3. Чтобы добавить нужные тексты, поставьте запятую после закрывающей фигурной скобки последнего поля и вставьте следующий код:

    'question_new_imgs': {
    'title': 'Фото',
    'description': 'Сделайте хотя бы 2 фотографии',
    'example_link_1': 'ссылка на пример фото'
    },
    'question_new_video': {
    'title': 'Видео',
    'description': 'Запишите видео'
    },
    'question_new_audio': {
    'title': 'Аудио',
    'description': 'Запишите аудио'
    }
    
  4. Найдите функцию getTemplateData. Она содержит несколько блоков следующего вида:

    if (<условие проверки заполнения поля>) {
    ...
    <код для отображения загруженных данных>
    ...
    }
    

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

    // фото
    if (outputValues.new_imgs && outputValues.new_imgs.length > 0) {
    data.new_imgs = [];
    for (var i = 0; i < outputValues.new_imgs.length; i++) {
    data.new_imgs.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.new_imgs[i] + '/preview');
    }
    }
    
    // видео
    if (outputValues.new_video && outputValues.new_video.length > 0) {
    data.new_video = [];
    for (var i = 0; i < outputValues.new_video.length; i++) {
    data.new_video.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.new_video[i] + '/preview');
    }
    }
    
    // аудио
    if (outputValues.new_audio && outputValues.new_audio.length > 0) {
    data.new_audio = [];
    for (var i = 0; i < outputValues.new_audio.length; i++) {
    data.new_audio.push(workspaceOptions.apiOrigin + '/api/attachments/' + outputValues.new_audio[i] + '/preview');
    }
    }
    
  5. Добавьте валидацию.

    Найдите функцию validate. В ней находится код для проверки заполнения полей для каждой из кнопок. Например, в шаблоне «Мониторинг объектов в организациях» этот код выглядит так:

    if (!solution.output_values.verdict || solution.output_values.verdict === '') {
    this.errors = this.addError('Не выбран ни один вариант ответа', "verdict", this.errors);
    } else if (solution.output_values.verdict === 'ok') {
    // код проверки полей кнопки ok
    if (!solution.output_values.imgs_facade || solution.output_values.imgs_facade.length === 0) {
    // код проверки поля imgs_facade
    }
    
    if (!solution.output_values.imgs_obj || solution.output_values.imgs_obj.length === 0) {
    // код проверки поля imgs_obj
    }
    
    } else if (solution.output_values.verdict === 'no_obj') {
    // код проверки полей кнопки no_obj
    } else if (solution.output_values.verdict === 'no_org') {
    // код проверки полей кнопки no_org
    }
    

    Значения ответа для кнопок в этом примере, которые передаются в выходную переменную verdict, называются так же, как на шаге обновления режима приемки: ok, no_obj и no_org.

    Найдите блок проверки нужной кнопки и внутри него после любого блока проверки поля вида

    if (!solution... ) {
    // код проверки поля
    }
    

    добавьте следующий код:

    if (!solution.output_values.new_imgs || solution.output_values.new_imgs.length === 0) {
    this.errors = this.addError('Нужно приложить фото', "new_imgs", this.errors);
    } else if (solution.output_values.new_imgs.length < 2) {
    this.errors = this.addError('Должно быть хотя бы 2 фото', "new_imgs", this.errors);
    }
    
    if (!solution.output_values.new_video || solution.output_values.new_video.length < 1) {
    this.errors = this.addError('Нужно приложить видео', 'new_video', this.errors);
    }
    
    if (!solution.output_values.new_audio || solution.output_values.new_audio.length < 1) {
    this.errors = this.addError('Нужно приложить аудио', 'new_audio', this.errors);
    }
    

Редактирование CSS

В конце блока с кодом CSS вставьте стили для новых полей:

.review__video-grid {
  margin: 0px -10px;
}

.review__audio-grid {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -ms-flex-wrap: wrap;
      flex-wrap: wrap;
  margin: 0px -10px;
}

.review__audio-grid-item {
  margin: 10px;
}

.review__video-grid-item {
  margin: 10px;
}

.main__video .field,
.main__audio .field {
  white-space: normal;
}

.main__video .field_file__files__file,
.main__audio .field_file__files__file {
  margin-bottom: 10px;
}

Написать в службу поддержки