Данная статья предназначена в первую очередь программистам, но так же и их друзьям и знакомым.

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

Если речь идет о состоявшемся бизнесе, который может позволить себе нанять специалистов и потратить 100т.р. то существует большой выбор CMS (Джумла, Модекс, Друпал...) и можно найти нужные компетенции.

Но если речь идет о стартующем бизнесе, либо семейном, либо просто о желающих начать работать самостоятельно и нужен «простой сайт», то такой бюджет будет велик, не хочется тратить деньги непонятно на что. Почему «непонятно на что»? Потому что MODX, Worldpress, Drupal и так далее «невероятно сложны» в управлении для непосвященных.
Если Вы опытный специалист, то скорее скажете — ерунда, там все элементарно! Но это вряд ли Вы так скажете: обычный пользователь, получив сайт на MODX, впадет в ступор от его Админки, впрочем как от всех других...И Тильда тоже требует обучения и понимания html сущностей, а времени на обучение нет. Нужно работающее и желательно бесплатное решение, ну или «почти бесплатное».

Раньше я отказывался от таких «бесплатных» заказов — быстро сделать простой сайт, так чтобы можно было что‑то продавать не получается — на работу уходит неделя а то и три. Но сейчас ситуация в корне поменялась. ИИ при правильном обращении генерирует сайт по любой тематике. Остается только сделать самую простую админку и передать готовый продукт клиенту буквально задаром.

И вот у меня появилась идея, на поверхности лежащая, сделать такую админку из Гугл‑таблицы. Учить ничему и никого не надо — с таблицами все работать умеют, по крайней мере ткнуть в нужную ячеку и скорректировать цену товара — это даже объяснять не надо.

Преимущества гугл таблиц:

  • элементарная простота, проще некуда;

  • позволяет работать одновременно всем интересантам;

  • позволяет оставлять коментарии и инструкции прямо в листе;

  • позволяет привлекать для консультаций разработчика, легко предоставлять и убирать доступ посторонним;

  • реализует все "хотелки" в плане администрирования данных;

  • загрузка фотографий любого вида, из папки, из интернета, из поиска и прямо с камеры;

  • админка имеет готовое мобильное приложение от Гугл :-)

Недостатки:

  • ограниченный набор записей, больше 1 000 товаров - будет сложно работать

  • минималистичность дизайна (для меня это даже плюс)

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

Краткий план:

  1. Берем хостинг, самый бюджетный, регистрируем домен.

  2. Создаем учетную запись гугл, и открываем новую гугл-таблицу.

  3. С помощью ИИ генерируем одностраничный сайт.

  4. Шаблонизируем заготовку, пишем код, который скачивает данные из гугл таблицы, пишем их в базу данных.

Выполнение:

  1. С этим пунктом все понятно, я нашел хостинг за 1400 рублей в год и оплатил сразу весь 2026. Домен в зоне ru обошелся в 200 рублей (была скидка).

  2. У ребят учетной записи в гугл не было, но это и к лучшему для начала, у меня была своя учетка, и я все сделал на ней. Самым сложным во всей этой истории было разобраться в регистрации приложения в гугл, получении сервисной учетной записи, выдаче прав... но в итоге я получил от гугла заветный файл: service_key.json.Об этом челенже можно снимать фильмы, всегда будут свежие и забавные сюжеты. Идем дальше, а с этим вопросом ИИ в помощь.

  3. Промт такой: "сгенерируй мне одностраничный сайт в одном файле на чистом html, css, javascript, посвященной нужной тематике. Сайт должен продавать небольшой ассортимент товаров, быть негромоздким и хорошо смотреться на любых устройствах"

    В ответ вы получите файл либо небольшую простынку кода.

  4. Здесь самая суть.

Для бэкэнда я выбрал laravel, поскольку часто им пользуюсь, но можно писать в чистом php, вообще на чем угодно, где есть библиотека Google apiclient. Если вы пишете на Питоне или nodejs, то рассматривайте текст ниже, как псевдокод - методы google apiclient не поменяются.

Но сначала на листе Гугл таблицы, который назовем Price, с некоторым пропуском верхних строк, наберем несколько товарных позиций, например



Артикул	Название	Цена	Описание	Фото	Активно
art1	...
art2	...

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

=ГИПЕРССЫЛКА(СЦЕПИТЬ("https://mysite.ru/google-price?"; "range=A4:F"); "Выгрузить все данные (кроме фото)")

mysite.ru это и есть наш сайт, в ячейке будет показана Гиперссылка, при клике по которой браузер откроет соседнюю вкладку, и мы сможем показать в этой вкладке лог выгрузки данных, вывести информацию об ошибках, то есть дать обратную связь, что крайне важно для пользователя.

Если решитесь повторить, рекомендую заменить читаемый адрес на сложноподбираемый, например mysite.ru/google-hjhgs4543nnsds, для безопасности.

Со стороны нашего сайта пишем роут, который принимает обработку запроса https://mysite.ru/google-price?range=A4:F
Мои данные начинались с четвертой стрки поэтому именно такой диапазон я указываю в ссылке.

На стороне сайта через composer подключаем библиотеку google/apiclient

composer require google/apiclient

Напишем сервис-класс, который будет прятать в себе взаимодействие сайта с гуглом

namespace App\GoogleApi;
use Google\Client as GooClient;
use Google\Service\Sheets;

// из адреса url гугл-таблицы берем ее ID, то что после spreadsheets/d/
define('SPREADSHEET_ID', '1QBLAblaBlajsdfsdflsdlkjlkjlkjlk_yE4');

class GoogleService {    
  protected $service;    
  public function __construct() {        
    $client = new GooClient();        
    $googleAccountKeyFilePath = app()->basePath('app/GoogleApi/service_key.json');        
    putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $googleAccountKeyFilePath);        
    $client->useApplicationDefaultCredentials();        
    $client->addScope('https://www.googleapis.com/auth/spreadsheets');        
    $client->addScope('https://www.googleapis.com/auth/drive');       

    // если вы не пользуйтесь сервис аккаунтом гугла, а будете от имени пользователя
    // вызывать апи, то также потребуется настройка редиректа для подтверждения 
    // пользователем прав доступа к данным. В моем случае я использую service account
    // и следующие две строчки не нужны
    //$redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];        
    //$client->setRedirectUri($redirect_uri);        
    
    $this->service = new Sheets($client);    
  }		
  
  function loadprice($range) {		
    $responseData = $this->service->spreadsheets_values->get(SPREADSHEET_ID, $range);
    $rows = $responseData->getValues();
    $products = [];
    foreach ($rows as $index => $row) {
      $products[] = [
        'sku' => $row[0],
        'name' => $row[1],
        'price' => str_replace(',', '.', $row[2]),
        'description' => $row[3],
        'active' => filter_var($row[5], FILTER_VALIDATE_BOOLEAN)
        ];
    }        
    return $products;	
  }
}


// и теперь где-то в другом месте, в коде контроллера, 
// обрабатывающего запрос на выгрузку данных можно написать

use App\GoogleApi\GoogleService;
// ...		
  // создаем экземпляр нашего сервис класса.		
  $service = new GoogleService();

  // получаем массив строк данных c листа 'Price' нашей Гугл таблицы.		
  // Очень важно указать корректный и полный диапазон данных с указанием листа		
  // $request - это объект запроса, в laravel попадает в контроллер как параметр 
  // функции контроллера, здесь имеется в виду $range = $_GET['range']
  $range = $request->string('range');

  // загружаем данные :)
  $prods = $service->loadprice("Price!" . $range);

  // у меня есть модель Product c настроенными кастингами, суть такая, что
  // при массовом обновлении провести необходимые преобразования:
  // даты в строки нужного формата, для float ',' меняем на '.'
  // или чекбоксы в 0/1 или список строк сначала в массивы а потом в json
  // потому что в Mysql и Google таблицах форматы на совпадают.
  // В этом месте мы обновляем или создаем новые товары в базе данных.
  $updata = collect($prods)->mapinto(Product::class);

  $res = Product::upsert($updata->toArray(), uniqueBy:['sku'],
                         update:['name',	'price', 'description', 'active']);

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

При получении данных с листа как указано выше, применяется объект spreadsheets_values, который отправляет в нашу сторону только данные в виде двумерного массива. Это очень компактный способ обмена, позволяющий выгружать сразу все записи. Содержимое ячеек с изображением не передается, мы получим пустую строку. Но нам и не требуется выгружать все изображения. Пользователь будет выгружать изображения по мере их добавления в ячейки столбца "Фото".

Важно (!), чтобы изображения были вставлены, как Image In Cell (Изображение в ячейке).

Механизм отправки тот же, через ссылку, но этого будет мало. В Google Spreadshet api нет методов получения каких-либо данных об изображении, потому что они хранятся отдельно от данных листа. Проверив разные варианты, я остановился на самом локаничном способе - через App Script. App Script это привязанное к таблице Приложение на javascript, имеющее доступ к ячейкам листа и неограниченный функционал работы с данными.

Итак, в Гугл Таблице кликаем в меню Расширения | App Script, в соседней вкладке откроется окно приложения. На вертикально вкладке Код js набираем 2 функции:

function getCellImageData(CellAdress) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var range = sheet.getRange(CellAdress);
  var value = range.getValue();
  if (value && typeof value === 'object' && value.toString() === "CellImage") {
    return value.getContentUrl();
  }
}


function doGet(e) {
  const cell = e.parameter.cell; // Получаем параметр ?cell=E4 из URL
  const result = getCellImageData(cell);
  return ContentService.createTextOutput(result);
}

Функция getCellImageData принимает параметром адрес ячейки с изображением и возвращает URL для его скачивания. Важная деталь - getActiveSpreadsheet().getActiveSheet() вернет автивную книгу и активную вкладку открытого в браузере листа, а нам это и надо. Поскольку инициатором обмена является пользователь, кликающий по ссылке, то активной вкладкой будет именно Price. Это не баг, это фича - элемент защиты от ботов. Нельзя инициировать обмен фото, не имея открытой таблицы.

Вторая функция doGet выполнит обработку исходящего от нас GET запроса с параметрами. Но для того, чтобы такой запрос принимался, App Script надо опубликовать!

Для этого справа вверху на странице App Script Приложения есть внопка "Начать развертывание". Интуиция подскажет что жать, но для начала:

  • выберите вариант "Новое развертывание",

  • далее "выберите тип" Web Приложение,

  • Заполните поля, доступ нужно дать "всем",

  • завершите развертывание кнопкой "Начать развертывание".

    Разрешение нужно дать "всем" - не бойтесь, ссылка на приложение будет только у вас и ее невозможно подобрать.

    Развертывание в результатах своей работы покажет вам ссылку на Веб-приложение, завершающуюся на /exec.

Копируем эту ссылку и возвращаемся в наш код класса GoogleService.

Определим новую константу, в которую вставляем полученный URL.

define('IMAGE_EXEC', 'https://script.google.com/macros/s/<id_вашего_app_script>/exec');

// и добавим еще один метод класса
function loadphoto($cell) {
  // я использую встроенный http-client laravel но можно и через curl
  $response = Http::get(IMAGE_EXEC . "?cell=$cell");
  
  // получаем от app script временный url изображения для скачивания
  $imageurl = $response->body();
  
  // скачиваем изображение по URL (downloadImageFromUrl пишем самостоятельно)
  $imagepath = $this->downloadImageFromUrl($imageurl);
  return $imagepath;    
}

Что здесь происходит: функция принимает параметром адрес ячейки, добавляет ее как параметр к запросу в адрес app script приложения и в ответ получает url изображения для скачивания.

Внимание, это временный url, его нельзя сохранять в базе данных. Поэтому мы вызываем функцию downloadImageFromUrl($imageurl) которая скачает изображение, уменьшит его если надо, создаст тамбнейлы, сделает resize и crop, все что вы сами захотите, поэтому я не буду приводить здесь ее текст.

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

// в нашем контроллере отклик на url 
//https://mysite.ru/google-photo?range=E4&sku=sku1 

public function photo(Request $request){
  $service = new GoogleService();
  $sku = $request->string('sku');
  $prod = Product::where('sku', $sku)->first();
  if (!$prod) {
    // выгружаем фото, а продукта еще нет?
    die "Перед выгрузкой фото, выгрузите данные";
  }		
  $range = $request->string('range');
  $imagepath = $service->loadphoto($range);
  
  //сохраняем путь к изображению в базе
  //код в стиле ORM, у меня Eloquent
  $prod->photo = $imagepath;
  $prod->save();

  //сообщаем пользователю об успехе
  echo "Картинка артикула $sku успешно выгружена";
}

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

=ГИПЕРССЫЛКА(СЦЕПИТЬ("mysite.ru/google-photo?"; "range=J"; СТРОКА(); "&sku="; A4); "Выгрузить фото")

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

Итоги:

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

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

В качетсве бартерного вознаграждения я попросил кусок премиального брискета весом в 4 кг. Меня устроило.

Развитие:

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

Рекомендации:

В целях безопасности сделайте эндпоинты случайным набором символов. Тогда ваша админка будет защищена так же, как вы защищаете контент этой гугл таблицы. Можно вместо этого добавить к ссылке token и проверять его на своей стороне.

Комментарии (20)


  1. crims0n_ru
    12.01.2026 13:25

    Интересная идея, но на хостинге я бы сэкономил в пользу GitHub Pages, используя таблицу как базу данных.


    1. Anrol Автор
      12.01.2026 13:25

      все же время отклика у гугл таблиц большое. тот же Гугл просит чтобы сайт отечал за 250 мсек (!), хотя лет 10 назад 1 секунда считалась нормой. сейчас нет, не норма. То есть данные надо "кэшировать" но можно в простом файле, не обязательно в БД. И про картинки - адрес фото - он временный, нельзя вставить картинку с адресом, как его показывает гугл. Надо скачать и отдавать от себя.


      1. BasiC2k
        12.01.2026 13:25

        присоединюсь к Вашему мнению.
        Да, вызов doGet (c дальнейшим обращением к данным в Spreadsheet) - это слишком долго.

        Кроме того, автор ещё не столкнулся с таким ограничением как Google Quotes - ограничение на количество запросов, времени выполнения (разового/суточного) google apps scripts и тд.


  1. alexhu
    12.01.2026 13:25

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

    В итоге нет возможности ничего поменять без программиста, это не масштабируется на большое количество товара без потери скорости; нельзя подключить дополнительных админов, что бы данные не утекли. Утечка данных - это основная боль мелкого бизнеса, у них текучка , они этого боятся. И допущенный работник получает доступ сразу ко всему - тут и ошибки сразу на всю базу и саботаж и шпионаж от конкурентов и все остальные риски.

    Где то у меня есть целые статьи, где я расписывал эти десятки возникающих проблем для сомневающихся клиентов, во времена когда я массово делал сайты. Проще научить админке или выбрать cms попроще для удобства, но в целом готовая цмс даёт не сопоставимый выигрыш по всем показателям в сравнении с ексель.


    1. Anrol Автор
      12.01.2026 13:25

      Что касается защиты данных - она ровно такая же как у любой CMS. А что собственно говоря вы хотите сохранить в секрете? Утечка чего вас пугает? Что такого есть в прейскуранте, чего вы публично не выкладываете на сайт. Не понимаю... Другое дело, если вы начинаете вместо сайта строить систему CRM хранить данные клиентов, телефонные номера и прочее.. Но статья о другом. О простом сайте с возможностью один раз сделать его и дать администратору работать с контентом.


      1. alexhu
        12.01.2026 13:25

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

        Ексель не даёт выигрыша как база данных, наоборот сильно проигрывает. Это не удивительно, поскольку ексель и базы данных это разные продукты для разных целей. Можно ли сделать ексель как админку - можно, у меня где то есть такой проект, сам делал лет 6 назад, для тестирования. Только у меня был локальный файл ексель, редактируем файл на компьютере, за несколько секунд заливаем новый файл на хостинг и всё работает из нового файла.

        Только если мы говорим про бизнес, то не нужно наступать на все грабли, уже всё сравнили и выводы сделали. Для бизнеса лучше цмс с базой данных и админкой из цмс. Ничего сложного в тех административных панелях нет, учатся за минуты.


        1. Anrol Автор
          12.01.2026 13:25

          Это решение для людей, которые хотят начать работать уже сейчас, а не отправляться на курсы администрирования MODX. А знаете сколько стоит обучение администрированию одной и отечественных систем сайтостроения? 60 тысяч рублей. Только для того чтобы научиться как заполнгить прейскурант.

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

          Что касается выводов про бизнес - вы эти выводы делаете как разработчик или как представитель бизнеса? Я никогда на скрывал перед своими клиентами, что есть много разных CMS. Называл цену внедрения и они делали свой выбор.


      1. alexhu
        12.01.2026 13:25

        Утечка чего вас пугает?

        Данные поставшиков хранятся в цмс. Смотрят в панель - закончился товар и сразу заказывают. Делать две базы - в одной продукция, в другой контакты очень неудобно.

        Количество продукции на складе, скорость продаж, поставщики. клиенты, цены закупки, себестоимость - при оглашении таких критических данных бизнес обанкротится за два дня.


        1. Anrol Автор
          12.01.2026 13:25

          вы куда то в другую сторону ушли. И вопросы которые сейчас поднимаете - это управленческие вопросы. Статья о другом. Статья о том, что есть вариант очень быстрого, понятного в работе и простого решения, не требующего обучения персонала. И этот вариант не отменяет других, тех, с которыми Вы привыкли работать.

          Что касается лично моего мнения по этой бизнес теме - я лично как разработчик и как человек, имеющий опыт собственного бизнеса в торговле, считаю, что не надо вести в одном месте ваш публичный сайт и вашу внутреннюю бухгалтерскую кухню. Это не для дискуссии, это просто мое мнение. И если сайт будет отдельно, то тогда вы будете стремиться распространить полезную для вашего бизнеса информацию, а не бояться ее потерять.


    1. Sntpatrizan
      12.01.2026 13:25

      Нет, этот подход к вопросам бизнеса неправильный.

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


  1. UniInter
    12.01.2026 13:25

    который скачивает данные из гугл таблицы, пишем их в базу данных

    Если все равно пишите в свою БД, то Гугл-таблица излишня. Например, для работы с БД есть отличная десктопная программа HeidiSQL - ее интерфейс - это та же Гугл или Excel таблица. Принцип работы такой же. Только надо прописать в настройках соединение с БД и всё. Данные хранятся в одном месте - в БД хоста, а не в двух местах как у вас.

    Ловил себя на мысли, что, действительно, админку заменяет, если весь сайт на БД завязан.


    1. Anrol Автор
      12.01.2026 13:25

      "Гугл-таблица излишня" да нет же!!! Общий доступ, возможность комментировать, приглашать отписывать, работа с сотового телефона!


      1. mefutu
        12.01.2026 13:25

        Я пожалуй тоже соглашусь, что раз есть своя бд и таблицы, то гугл таблица неуместна.

        В качестве альтернативы, посмотрите headless cms, мне очень понравилась Directus. Ставиться просто в докере, работает с вашей БД без добавления своих полей в ваши таблицы, имеет красивую и удобную админ панель. Может хранить файлы как в s3, так и в файлах сервера. Никаких кастомных админ панелей или платных решений на laravel


        1. Anrol Автор
          12.01.2026 13:25

          Все верно, Headless CMS. Именно это я и сделал - обратил внимание на тот факт, что для случаев табличных данных гугл таблица и есть готовая Headless CMS. Чем она хуже Chost-Strapi-Netlify? По моему ничем, оговорюсь, в рамках поставленной задачи.


  1. Dhwtj
    12.01.2026 13:25

    Безопасность плачет


    1. Anrol Автор
      12.01.2026 13:25

      Безопасность неудобная, но нормальная. Что может случиться плохого - утечет наш "вэб-хук". То есть та ссылка, по которой мы обращаемся к себе на сервер. Ссылка только в статье удобочитаемая но я обращаю внимание, что ее надо сгенерировать случайным способом и использовать https. Допустим, даже произошла утечка нашего вэбхука, но что злоумышленник может сделать? Задедосить только. Внести подложную информацию - нет, не получится, потому что вэб хук просто сообщает, что надо считать данные, а данные считываются с конкретной spreadsheet. Даже диапазон в принципе можно не передавать, это вариация - передавать диапазон. Можно читать данные из строго заданного диапазона.

      Другими словами: безопасность системы обеспечена ровно настолько, насколько ее обкспечил Гугл для своих таблиц. Если только сам пользователь не передаст злоумышленнику права на доступ к странице, ничего разместить на сайте будет нельзя.

      Приведите свой сценрий атаки и ее последствий, будет больше конкретики - будет пища к размышлению.


  1. copyhold
    12.01.2026 13:25

    а нельзя ли SSG - перегенерить весть сайт в HTML ? при максимум 1000 товаров их можно все загрузить в страницу индекса


    1. Anrol Автор
      12.01.2026 13:25

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


  1. Sntpatrizan
    12.01.2026 13:25

    Я бы не использовал Sheets API на сервере совсем, не вижу тут необходимости. Средствами Apps Script можно легко сформировать JSON вместе со ссылками на картинки и отправить все одним запросом.
    Ограничение в 1000 товаров, поверьте, у вас сильно занижено...
    Google Apps - мощнейшая штука, очень много чего можно реализовать. Так то там и к БД можно подключиться.


  1. Anrol Автор
    12.01.2026 13:25

    Надо будет проверить. Приведу конкретный пример: в таблице на 1000 строк мне надо было раскрасить текстовые фрагменты, которые совпадают в двух соседних столбцах, визуализация совпадений, скорость раскраски после запуска ~ 4 строки в секунду, то есть совсем не аховая скорость. Раскраска листа в 1000 строк - больше 2 минут.

    Функционал впечатлил, но осадочек остался.

    function highlightVisibleText(columnLiter='B', color="#FF0000") {
      const sheet = SpreadsheetApp.getActiveSheet();
      const src = `${columnLiter}1:${columnLiter}`
      // Если фильтр не применён, используем весь столбец A
      const range = sheet.getRange("A1:A" + sheet.getLastRow());
      const keywords = sheet.getRange(src + sheet.getLastRow()).getValues(); // Ключевое слово (регистронезависимо)
      
      // Перебираем все строки в диапазоне
      for (let i = 1; i <= range.getNumRows(); i++) {
        // Пропускаем скрытые ст
    
        const keyword = keywords[i-1][0].toString().toLowerCase();
           
        const cell = range.getCell(i, 1); // Берём ячейку (i, 1) в диапазоне
        const cellValue = cell.getValue().toString();
        const richText = SpreadsheetApp.newRichTextValue().setText(cellValue);
        
        // Ищем ключевое слово
        const startIndex = cellValue.toLowerCase().indexOf(keyword);
        if (startIndex !== -1) {
          const endIndex = startIndex + keyword.length;
          richText.setTextStyle(
            startIndex,
            endIndex,
            SpreadsheetApp.newTextStyle()
              .setForegroundColor(color) 
              .setBold(true)
              .build()
          );
          // Применяем форматирование сразу к ячейке
          cell.setRichTextValue(richText.build());
        } else {
          // Сбрасываем форматирование, если слово не найдено
          cell.setRichTextValue(richText.build());
        }
      }
    }