Розробка дашбордів на Vue.js для Бітрікс24

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка дашбордів на Vue.js для Бітрікс24
Середня
~1-2 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Розробка дашбордів на Vue.js для Бітрікс24

Вбудована аналітика Бітрікс24 покриває стандартні сценарії: воронка продажів, звіт по менеджерах, план/факт. Щойно бізнес-аналітик приходить із запитом «покажи конверсію по джерелах у розрізі регіонів за довільний період із фільтром по відповідальному» — стандартні звіти закінчуються. Дашборд на Vue.js — це кастомний інтерфейс аналітики, вбудований у Бітрікс24 через iframe-застосунок із прямим доступом до REST API.

Архітектурна схема дашборду

Дашборд складається з трьох шарів:

  1. Шар даних — запити до Бітрікс24 REST API, кешування, агрегація
  2. Шар сховища — Pinia stores з реактивними обчислюваними властивостями
  3. Шар представлення — Vue-компоненти з Chart.js або ApexCharts

Для важких агрегацій — проміжний серверний шар (Node.js/PHP), який зводить дані з кількох REST-викликів і повертає готовий JSON.

Пакетне збирання даних

REST API Бітрікс24 має ліміт 50 елементів на запит. Для отримання всіх угод за період потрібна пагінація або використання callBatch:

// composables/useCrmData.js
export function useCrmData() {
  const { callMethod } = useBx24()

  async function fetchAllDeals(filter) {
    const allDeals = []
    let start = 0
    let hasMore = true

    while (hasMore) {
      const result = await callMethod('crm.deal.list', {
        select: ['ID', 'TITLE', 'STAGE_ID', 'OPPORTUNITY', 'SOURCE_ID',
                 'ASSIGNED_BY_ID', 'DATE_CREATE', 'UF_CRM_REGION'],
        filter,
        order: { ID: 'ASC' },
        start,
      })
      allDeals.push(...result)
      hasMore = result.length === 50
      start += 50
    }

    return allDeals
  }

  async function fetchDealsByStages(filter) {
    const [deals, stages] = await Promise.all([
      fetchAllDeals(filter),
      callMethod('crm.status.list', {
        filter: { ENTITY_ID: 'DEAL_STAGE' }
      })
    ])
    return { deals, stages }
  }

  return { fetchAllDeals, fetchDealsByStages }
}

Компонент фільтра періоду

<!-- components/DateRangeFilter.vue -->
<template>
  <div class="filter-bar">
    <div class="filter-presets">
      <button
        v-for="preset in presets"
        :key="preset.id"
        :class="{ active: activePreset === preset.id }"
        @click="applyPreset(preset)"
      >{{ preset.label }}</button>
    </div>
    <div class="filter-custom">
      <input type="date" v-model="dateFrom" @change="emitCustomRange" />
      <input type="date" v-model="dateTo" @change="emitCustomRange" />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { startOfMonth, endOfMonth, subMonths, format } from 'date-fns'

const emit = defineEmits(['change'])
const activePreset = ref('current_month')
const dateFrom = ref('')
const dateTo = ref('')

const presets = [
  { id: 'current_month', label: 'Поточний місяць' },
  { id: 'prev_month', label: 'Минулий місяць' },
  { id: 'quarter', label: 'Квартал' },
  { id: 'year', label: 'Рік' },
]

function applyPreset(preset) {
  activePreset.value = preset.id
  const now = new Date()
  let from, to

  switch (preset.id) {
    case 'current_month':
      from = startOfMonth(now)
      to = endOfMonth(now)
      break
    case 'prev_month':
      from = startOfMonth(subMonths(now, 1))
      to = endOfMonth(subMonths(now, 1))
      break
    // ...
  }

  emit('change', {
    from: format(from, 'yyyy-MM-dd'),
    to: format(to, 'yyyy-MM-dd'),
  })
}
</script>

Інтеграція Chart.js

// composables/useChart.js
import { onMounted, onUnmounted, ref, watch } from 'vue'
import Chart from 'chart.js/auto'

export function useChart(canvasRef, config) {
  let chart = null

  onMounted(() => {
    chart = new Chart(canvasRef.value, config.value)
  })

  watch(config, (newConfig) => {
    if (!chart) return
    chart.data = newConfig.data
    chart.update('active')
  }, { deep: true })

  onUnmounted(() => chart?.destroy())
}

Компонент воронки продажів:

<template>
  <div class="chart-card">
    <h3>Воронка продажів</h3>
    <canvas ref="canvasRef" height="300"></canvas>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { useDashboardStore } from '@/stores/dashboard'
import { useChart } from '@/composables/useChart'

const store = useDashboardStore()
const canvasRef = ref(null)

const chartConfig = computed(() => ({
  type: 'bar',
  data: {
    labels: store.stages.map(s => s.NAME),
    datasets: [{
      label: 'Кількість угод',
      data: store.stages.map(s => store.dealsByStage[s.STATUS_ID] || 0),
      backgroundColor: store.stages.map(s => s.COLOR || '#4a90d9'),
    }]
  },
  options: {
    indexAxis: 'y',
    responsive: true,
    plugins: { legend: { display: false } }
  }
}))

useChart(canvasRef, chartConfig)
</script>

KPI-картки

Агрегація метрик на клієнті:

// stores/dashboard.js
const kpis = computed(() => {
  const deals = filteredDeals.value
  const won = deals.filter(d => d.STAGE_ID === 'WON')
  const total = deals.length

  return {
    totalDeals: total,
    wonDeals: won.length,
    conversionRate: total ? Math.round((won.length / total) * 100) : 0,
    totalRevenue: won.reduce((sum, d) => sum + parseFloat(d.OPPORTUNITY || 0), 0),
    avgDealSize: won.length
      ? won.reduce((sum, d) => sum + parseFloat(d.OPPORTUNITY || 0), 0) / won.length
      : 0,
  }
})

Експорт даних

Вивантаження дашборду в Excel через xlsx:

import * as XLSX from 'xlsx'

function exportToExcel(data, filename) {
  const ws = XLSX.utils.json_to_sheet(data)
  const wb = XLSX.utils.book_new()
  XLSX.utils.book_append_sheet(wb, ws, 'Звіт')
  XLSX.writeFile(wb, `${filename}.xlsx`)
}

Продуктивність

Дашборди з великим обсягом даних потребують:

  • Серверна агрегація — не гоняйте 10 000 угод у браузер
  • shallowRef для великих масивів у Pinia — глибока реактивність на кожен об'єкт не потрібна
  • Віртуалізація таблиць — @tanstack/vue-virtual для списків понад 500 рядків
  • Дебаунс на фільтрах — 300–500 мс перед запитом

Терміни виконання

Дашборд із 3–5 віджетами (воронка, KPI-картки, динаміка по періодах) — 5–8 робочих днів. Аналітичний портал із десятком віджетів, серверною агрегацією, експортом та ролями доступу — 3–5 тижнів.