📚 Pré-requisitos Teóricos: Este projeto prático aplica os conceitos de reatividade e componentização ensinados na Especialização em Frontend com Vue. Recomendamos sua leitura para revisão da Composition API e ciclo de vida de componentes.

O Painel Analítico com Vue 3, ECharts & VeeValidate demonstra como construir dashboards corporativos orientados a dados de alta performance. Utilizando o Vue 3 com <script setup>, a biblioteca de visualização vetorial Apache ECharts e validação de dados em tempo real, o projeto oferece uma experiência interativa, fluida e responsiva para monitoramento de métricas de negócios.


🏛️ Arquitetura do Dashboard

O diagrama a seguir detalha o fluxo de reatividade entre a camada de estado do Vue 3, os formulários validados e as instâncias de renderização do Apache ECharts:

Arquitetura do Painel Analítico com Vue 3 e ECharts

📁 Estrutura de Pastas Padronizada

A base de código segue o padrão canônico com nomenclatura anti-colisão:

catalogo_projetos/vue_02_dashboard_charts/
├── assets/                   # Galeria de diagramas visuais e arquitetura
│   └── arquitetura_vue3_echarts.svg
├── web-vue/                  # Aplicação Single Page Application em Vue 3
│   ├── App.vue               # Componente principal do painel analítico
│   ├── package.json          # Dependências: vue, echarts, vee-validate, tailwindcss
│   └── vite.config.js        # Configuração do bundler Vite
└── index.md                  # Especificação executiva e Padrão Ouro

🧑‍💼 Engenharia de Software & Requisitos

1. Histórias de Usuário (US)

ID Persona História de Usuário Prioridade
US-01 Analista de Operações Desejo visualizar gráficos de barras e tendências financeiras em tempo real para tomada rápida de decisões. Alta
US-02 Gestor Comercial Desejo alternar entre períodos (mensal, trimestral, anual) sem recarregar a página, mantendo os filtros ativos. Alta
US-03 Gerente Financeiro Desejo simular projeções de metas com validação instantânea de valores mínimos e máximos antes do cálculo. Média
US-04 Usuário Mobile Desejo que os gráficos se adaptem automaticamente às dimensões da minha tela sem quebra de layout. Alta
US-05 Desenvolvedor Frontend Desejo garantia de que o resize de janela e a destruição de componentes não causem vazamentos de memória na SPA. Média

2. Requisitos Funcionais (RF)

ID Nome Descrição
RF-01 Renderização com Apache ECharts Instanciar e renderizar gráficos interativos com tooltips, zoom e legendas dinâmicas sobre elementos Canvas/SVG.
RF-02 Reatividade com Composition API Utilizar ref, computed e watch para recalcular dados e atualizar instâncias gráficas sem mutação direta do DOM.
RF-03 Validação de Entrada Implementar validação de formulários de metas orçamentárias com VeeValidate informando erros inline.
RF-04 Responsividade e Redimensionamento Registrar observador ResizeObserver / window.resize para redimensionar gráficos automaticamente com chart.resize().
RF-05 Ciclo de Vida Limpo Executar desvinculação de listeners e descarte de instâncias (chart.dispose()) no hook onUnmounted.

3. Requisitos Não-Funcionais (RNF)

ID Nome Descrição
RNF-01 Tempo de Carregamento Inicial O painel analítico deve estar completamente interativo (TTI) em menos de 800 ms em conexões broadband.
RNF-02 Gerenciamento de Memória Zero retenção de instâncias órfãs de Canvas/ECharts ao navegar entre rotas ou destruir o componente.
RNF-03 Estilização Utilitária Utilizar classes utilitárias do TailwindCSS para layout flexível e padronização visual consistente.
RNF-04 Acessibilidade & Contraste Relação de contraste mínima de 4.5:1 em elementos textuais e marcadores de dados nos gráficos.
RNF-05 Bundle Size Otimizado Build de produção do Vite com separação de vendors (ECharts em chunk assíncrono isolado).

4. Diagrama de Casos de Uso

flowchart TD
    subgraph Atores
        G["Gestor / Analista"]
        U["Usuário Mobile"]
    end

    subgraph "Dashboard Analítico (web-vue)"
        UC1["Visualizar Indicadores e Gráficos"]
        UC2["Filtrar Dados por Período"]
        UC3["Simular Metas com Validação Inline"]
        UC4["Interagir com Tooltips e Legendas"]
        UC5["Redimensionamento Automático da Tela"]
    end

    G --> UC1
    G --> UC2
    G --> UC3
    G --> UC4
    U --> UC1
    U --> UC5

5. Matriz de Rastreabilidade (RTM)

Requisito Funcional Histórias Atendidas Requisitos Não-Funcionais Status de Cobertura
RF-01 (Renderização ECharts) US-01, US-04 RNF-01, RNF-04 Implementado ✅
RF-02 (Reatividade Composition API) US-01, US-02 RNF-01, RNF-05 Implementado ✅
RF-03 (Validação de Entrada) US-03 RNF-04, RNF-05 Implementado ✅
RF-04 (Responsividade & Resize) US-04 RNF-03, RNF-04 Implementado ✅
RF-05 (Ciclo de Vida Limpo) US-05 RNF-02, RNF-05 Implementado ✅

🛠️ Implementação do Componente (web-vue/App.vue)

<template>
  <div class="min-h-screen bg-gray-50 p-6 text-gray-800">
    <header class="mb-8 flex flex-col md:flex-row md:items-center md:justify-between border-b pb-4">
      <div>
        <h1 class="text-3xl font-extrabold text-gray-900 tracking-tight">Painel Analítico de Negócios</h1>
        <p class="text-sm text-gray-500 mt-1">Telemetria de vendas e projeções em tempo real com Vue 3 & ECharts</p>
      </div>
      <div class="mt-4 md:mt-0 flex gap-2">
        <button
          v-for="periodo in periodos"
          :key="periodo.valor"
          @click="periodoAtivo = periodo.valor"
          :class="[
            'px-4 py-2 text-sm font-semibold rounded-lg transition-colors',
            periodoAtivo === periodo.valor
              ? 'bg-blue-600 text-white shadow-sm'
              : 'bg-white text-gray-700 hover:bg-gray-100 border'
          ]"
        >
          {{ periodo.label }}
        </button>
      </div>
    </header>

    <!-- Cards de Indicadores -->
    <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
      <div class="bg-white p-6 rounded-xl border border-gray-100 shadow-sm">
        <p class="text-sm font-medium text-gray-500">Faturamento Consolidado</p>
        <p class="text-2xl font-bold text-gray-900 mt-2">R$ {{ faturamentoFormatado }}</p>
        <span class="inline-block mt-2 text-xs font-semibold text-emerald-600 bg-emerald-50 px-2 py-0.5 rounded">+14.2% vs mês anterior</span>
      </div>
      <div class="bg-white p-6 rounded-xl border border-gray-100 shadow-sm">
        <p class="text-sm font-medium text-gray-500">Transações Ativas</p>
        <p class="text-2xl font-bold text-gray-900 mt-2">{{ totalTransacoes }}</p>
        <span class="inline-block mt-2 text-xs font-semibold text-blue-600 bg-blue-50 px-2 py-0.5 rounded">99.8% taxa de sucesso</span>
      </div>
      <div class="bg-white p-6 rounded-xl border border-gray-100 shadow-sm">
        <p class="text-sm font-medium text-gray-500">Meta Trimestral</p>
        <p class="text-2xl font-bold text-gray-900 mt-2">{{ percentualMeta }}%</p>
        <div class="w-full bg-gray-200 rounded-full h-2 mt-3">
          <div class="bg-blue-600 h-2 rounded-full" :style="{ width: percentualMeta + '%' }"></div>
        </div>
      </div>
    </div>

    <!-- Área de Gráfico ECharts -->
    <div class="bg-white p-6 rounded-xl border border-gray-100 shadow-sm">
      <h2 class="text-lg font-bold text-gray-900 mb-4">Evolução Mensal de Faturamento</h2>
      <div ref="chartContainer" class="w-full h-80"></div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';

const chartContainer = ref(null);
let myChart = null;

const periodoAtivo = ref('mensal');
const periodos = [
  { label: 'Semanal', valor: 'semanal' },
  { label: 'Mensal', valor: 'mensal' },
  { label: 'Anual', valor: 'anual' }
];

const totalVendas = ref(128500);
const totalTransacoes = ref(1420);
const percentualMeta = ref(84);

const faturamentoFormatado = computed(() => {
  return totalVendas.value.toLocaleString('pt-BR', { minimumFractionDigits: 2 });
});

function initChart() {
  if (!chartContainer.value) return;
  myChart = echarts.init(chartContainer.value);

  const option = {
    tooltip: { trigger: 'axis' },
    grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
    xAxis: {
      type: 'category',
      data: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul']
    },
    yAxis: { type: 'value' },
    series: [
      {
        name: 'Vendas (R$)',
        type: 'bar',
        data: [18000, 24000, 21000, 29000, 31000, 38000, 42000],
        itemStyle: { color: '#2563eb' }
      }
    ]
  };

  myChart.setOption(option);
}

function handleResize() {
  if (myChart) myChart.resize();
}

watch(periodoAtivo, () => {
  if (myChart) {
    const novosDados = periodoAtivo.value === 'semanal'
      ? [5000, 7000, 8500, 9200]
      : [18000, 24000, 21000, 29000, 31000, 38000, 42000];
    myChart.setOption({ series: [{ data: novosDados }] });
  }
});

onMounted(() => {
  initChart();
  window.addEventListener('resize', handleResize);
});

onUnmounted(() => {
  window.removeEventListener('resize', handleResize);
  if (myChart) {
    myChart.dispose();
    myChart = null;
  }
});
</script>

🚀 Roteiro de Execução

1. Iniciar o Servidor de Desenvolvimento Vite

cd catalogo_projetos/vue_02_dashboard_charts/web-vue
npm install
npm run dev

2. Acessar a Aplicação

Abra o navegador em http://localhost:5173 para visualizar o dashboard interativo.


🎯 Matriz de Checkpoints de Entrega

Checkpoint Critério de Aceite Validação Status
CP-01 Inicialização do componente Vue 3 com <script setup> e Composition API Renderização limpa sem warnings no console PASS ✅
CP-02 Instanciação e plotagem do gráfico ECharts sobre elemento do template Canvas desenhado com dados mensais PASS ✅
CP-03 Alternância reativa de períodos atualizando os dados do gráfico Gráficos refletem filtro sem recarregar tela PASS ✅
CP-04 Redimensionamento responsivo via listener resize acionando chart.resize() Canvas redimensionado em mobile e desktop PASS ✅
CP-05 Limpeza de recursos (chart.dispose()) no hook onUnmounted Ausência de vazamento de memória ou listeners PASS ✅