Vue.js: Projeto Final: Admin E-commerce

Última atualização: 2026-10-20

Este é o projeto final do tutorial do Vue 3 — aplicar tudo o que você aprendeu na Lição 29 a um back-end real de comércio eletrônico SaaS. Vamos basear nossa implementação na arquitetura PanJiaChen/vue-element-admin, que tem 88 mil⭐ no GitHub, para construir um backend de comércio eletrônico capaz de suportar 5.000 lojistas e 30.000 SKUs.

O projeto completo é composto por 8 módulos principais e requer de 10 a 12 horas de trabalho. Ao concluir este projeto, você será capaz de desenvolver de forma independente aplicativos Vue de nível empresarial.

1. O que você vai aprender


2. “Requisitos de Full-Stack” para um gerente de produto em uma empresa de comércio eletrônico SaaS

(1) Desafio: Como desenvolver um backend de comércio eletrônico para 5.000 lojistas e 30.000 SKUs

Alice foi promovida para liderar o projeto do painel de controle administrativo:

TEXT
Business Scenarios:
  - 5,000 Small and Medium-Sized Business Owners(Per household 30-300 SKU)
  - 30,000 Items SKU
  - Daily 10,000 Order
  - 5 Role Permissions(admin / manager / editor / viewer / guest)
  - Real-time Data(Dashboard,Order,Notice)
  - Multilingual(5 Language)

O gerente de produto Charlie redigiu as especificações:

“Alice, nossa plataforma SaaS precisa de um painel de administração completo. Precisamos de 8 módulos: painel de controle, produtos, pedidos, usuários, marketing, configurações, login e implantação. Cada módulo é essencial. Desenvolva-o com o Vue 3, em 10 a 12 horas, utilizando todas as 29 lições que você aprendeu.”

(2) Projeto abrangente do Vue 3: arquitetura de 8 módulos

TEXT
src/
├-- api/                   # API Packaging
│   ├-- auth.ts
│   ├-- product.ts
│   ├-- order.ts
│   └-- user.ts
├-- assets/                # Static Resources
├-- components/            # Public Components
│   ├-- BaseButton.vue
│   ├-- BaseTable.vue       # Secondary Packaging Element Plus
│   ├-- BasePagination.vue
│   ├-- BaseDialog.vue
│   └-- charts/             # Charts
│       ├-- SalesChart.vue
│       ├-- UserChart.vue
│       └-- OrderChart.vue
├-- composables/           # Composite Functions
│   ├-- useAuth.ts
│   ├-- usePermission.ts
│   ├-- useTable.ts
│   └-- usePagination.ts
├-- layouts/                # Layout
│   ├-- DefaultLayout.vue   # After logging in
│   └-- AuthLayout.vue      # Login Page
├-- router/                # Routing
│   └-- index.ts
├-- stores/                 # Pinia
│   ├-- auth.ts
│   ├-- product.ts
│   ├-- order.ts
│   └-- app.ts              # Global(Topic / Sidebar)
├-- types/                  # TS Type
│   ├-- user.ts
│   ├-- product.ts
│   └-- api.ts
├-- utils/                  # Tools
│   ├-- request.ts          # axios Packaging
│   ├-- auth.ts
│   └-- format.ts
├-- views/                  # Page
│   ├-- dashboard/          # Dashboard
│   ├-- login/              # Log In
│   ├-- product/            # Products
│   ├-- order/              # Order
│   ├-- user/               # User
│   ├-- marketing/          # Marketing
│   └-- error/              # 403 / 404
├-- App.vue
└-- main.ts

(3) Receita

Após compilar este projeto:


3. Módulo 1: Inicialização do projeto (1,5 horas)

(1) Preencha o arquivo package.json

▶ Exemplo 1: Configuração completa da inicialização do projeto (Dificuldade: ⭐⭐)

Como configurar um projeto completo do zero usando Vue 3 + Vite + Pinia + Element Plus + ECharts + TypeScript:

JSON
{
  "name": "mercury-admin",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview",
    "test": "vitest",
    "test:e2e": "playwright test",
    "lint": "eslint . --ext .vue,.ts,.tsx"
  },
  "dependencies": {
    "vue": "^3.4.0",
    "vue-router": "^4.3.0",
    "pinia": "^2.1.0",
    "element-plus": "^2.5.0",
    "@element-plus/icons-vue": "^2.3.0",
    "echarts": "^5.4.0",
    "vue-echarts": "^7.0.0",
    "axios": "^1.6.0",
    "dayjs": "^1.11.0",
    "vueuse": "^10.7.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.0",
    "vite": "^5.0.0",
    "vue-tsc": "^1.8.0",
    "typescript": "^5.3.0",
    "unplugin-vue-components": "^0.26.0",
    "unplugin-auto-import": "^0.17.0",
    "unplugin-icons": "^0.17.0",
    "@iconify-json/carbon": "^1.1.0",
    "sass": "^1.69.0",
    "vitest": "^1.0.0",
    "@vue/test-utils": "^2.4.0",
    "@playwright/test": "^1.40.0"
  }
}

Saída:

TEXT
npm run dev sobe o painel em http://localhost:5173 com HMR.
npm run build roda vue-tsc antes do Vite e falha se houver erro de tipo.
Componentes do Element Plus e APIs do Vue entram sob demanda, sem import manual.

(2) vite.config.ts

TS
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import'
import Components from 'unplugin-vue-components'
import Icons from 'unplugin-icons/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import path from 'path'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia', 'vueuse'],
      resolvers: [ElementPlusResolver()],
      dts: 'src/auto-imports.d.ts'
    }),
    Components({
      resolvers: [ElementPlusResolver()],
      dts: 'src/components.d.ts'
    }),
    Icons({ autoInstall: true })
  ],
  resolve: {
    alias: { '@': path.resolve(__dirname, 'src') }
  },
  server: {
    port: 5173,
    proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } }
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'echarts-vendor': ['echarts', 'vue-echarts']
        }
      }
    }
  }
})

4. Módulo 2: Login + Autenticação JWT + Roteamento baseado em permissões (2h)

(1) Loja oficial da Pinia

▶ Exemplo 2: Login com JWT + Banco de permissões com 5 funções (Dificuldade: ⭐⭐)

Use a loja de configuração do Pinia para gerenciar tokens, usuários, funções e permissões:

TS
// stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { loginApi, getUserInfoApi } from '@/api/auth'

export const useAuthStore = defineStore('auth', () => {
  const token = ref<string>(localStorage.getItem('token') || '')
  const user = ref<User | null>(null)
  const roles = ref<string[]>([])
  const permissions = ref<string[]>([])
  
  const isLoggedIn = computed(() => !!token.value)
  const isAdmin = computed(() => roles.value.includes('admin'))
  
  async function login(credentials: { username: string; password: string }) {
    const { data } = await loginApi(credentials)
    token.value = data.token
    user.value = data.user
    roles.value = data.roles
    permissions.value = data.permissions
    localStorage.setItem('token', data.token)
  }
  
  async function fetchUserInfo() {
    const { data } = await getUserInfoApi()
    user.value = data.user
    roles.value = data.roles
    permissions.value = data.permissions
  }
  
  function logout() {
    token.value = ''
    user.value = null
    roles.value = []
    permissions.value = []
    localStorage.removeItem('token')
  }
  
  return { token, user, roles, permissions, isLoggedIn, isAdmin, login, fetchUserInfo, logout }
}, {
  persist: {
    key: 'mercury-auth',
    paths: ['token', 'user', 'roles', 'permissions']
  }
})
▶ Experimente

Saída:

TEXT
Acessar /dashboard sem token redireciona para /login?redirect=/dashboard.
Após o login, token, usuário, papéis e permissões ficam salvos na chave mercury-auth.
Logout limpa a store e volta ao login; v-permission remove os botões sem permissão.

(2) Route Guard

TS
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/login', component: () => import('@/views/login/LoginPage.vue') },
    {
      path: '/',
      component: () => import('@/layouts/DefaultLayout.vue'),
      meta: { requiresAuth: true },
      children: [
        { path: '', redirect: '/dashboard' },
        { path: 'dashboard', component: () => import('@/views/dashboard/DashboardPage.vue') },
        { path: 'products', component: () => import('@/views/product/ProductListPage.vue') }
      ]
    }
  ]
})

router.beforeEach((to, from, next) => {
  const auth = useAuthStore()
  
  if (to.meta.requiresAuth && !auth.isLoggedIn) {
    return next({ name: 'login', query: { redirect: to.fullPath } })
  }
  
  next()
})

export default router

(3) Diretivas de permissão personalizadas

TS
// directives/permission.ts
import { useAuthStore } from '@/stores/auth'

export const permission = {
  mounted(el: HTMLElement, binding: { value: string }) {
    const auth = useAuthStore()
    if (!auth.permissions.includes(binding.value)) {
      el.parentNode?.removeChild(el)
    }
  }
}
VUE
<button v-permission="'product.create'">Create Product</button>

5. Módulo 3: Painel de controle (2 horas)

(1) 5 gráficos do ECharts

▶ Exemplo 3: Painel com 5 gráficos do ECharts (Dificuldade: ⭐⭐)

Código principal do painel: 5 gráficos + 4 KPIs + layout em grade do Element Plus:

VUE
<!-- views/dashboard/DashboardPage.vue -->
<template>
  <div class="dashboard">
    <!-- 1. KPI Card -->
    <div class="kpi-grid">
      <KpiCard title="Today's Sales" :value="stats.todaySales" :change="+12%" />
      <KpiCard title="Orders" :value="stats.todayOrders" :change="+5%" />
      <KpiCard title="Active Users" :value="stats.activeUsers" :change="+8%" />
      <KpiCard title="Conversion" :value="stats.conversion" :change="-2%" />
    </div>
    
    <!-- 2. 5 View Chart -->
    <el-row :gutter="20">
      <el-col :span="12">
        <SalesChart :data="salesData" />
      </el-col>
      <el-col :span="12">
        <UserChart :data="userData" />
      </el-col>
      <el-col :span="24">
        <OrderChart :data="orderData" />
      </el-col>
      <el-col :span="12">
        <CategoryChart :data="categoryData" />
      </el-col>
      <el-col :span="12">
        <TopProductsTable :data="topProducts" />
      </el-col>
    </el-row>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getDashboardStatsApi } from '@/api/dashboard'

const stats = ref({})
const salesData = ref([])
const userData = ref([])
const orderData = ref([])
const categoryData = ref([])
const topProducts = ref([])

onMounted(async () => {
  const { data } = await getDashboardStatsApi()
  Object.assign(stats.value, data.stats)
  salesData.value = data.sales
  userData.value = data.users
  orderData.value = data.orders
  categoryData.value = data.categories
  topProducts.value = data.topProducts
})
</script>
▶ Experimente

Saída:

TEXT
Topo com 4 cartões de KPI (vendas do dia, pedidos, usuários ativos, conversão).
Abaixo, 5 gráficos: vendas, usuários, pedidos, categorias e top produtos.
Ao redimensionar a janela os gráficos se ajustam (autoresize); o tooltip segue o eixo.

(2) Encapsulamento do componente ECharts

VUE
<!-- components/charts/SalesChart.vue -->
<template>
  <el-card title="Sales Trend">
    <v-chart :option="chartOption" autoresize style="height: 300px" />
  </el-card>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart } from 'echarts/charts'
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'

use([CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent])

const props = defineProps<{
  data: Array<{ date: string; sales: number }>
}>()

const chartOption = computed(() => ({
  tooltip: { trigger: 'axis' },
  xAxis: { type: 'category', data: props.data.map(d => d.date) },
  yAxis: { type: 'value' },
  series: [{
    name: 'Sales',
    type: 'line',
    data: props.data.map(d => d.sales),
    smooth: true
  }]
}))
</script>

6. Módulo 4: Gestão de Produtos (1,5 horas)

(1) Lista de produtos + CRUD

▶ Exemplo 4: CRUD de produtos + controle de acesso (Dificuldade: ⭐⭐)

Essência da gestão de produtos: reencapsulamento da BaseTable + exibição em 6 colunas + diretrizes de permissão + confirmação de exclusão:

VUE
<!-- views/product/ProductListPage.vue -->
<template>
  <BaseTable
    :data="products"
    :columns="columns"
    :total="total"
    :loading="loading"
    @search="onSearch"
    @page="onPage"
  >
    <template #toolbar>
      <el-button type="primary" v-permission="'product.create'" @click="onCreate">
        Create Product
      </el-button>
    </template>
    
    <template #column-status="{ row }">
      <el-tag :type="statusType(row.status)">{{ row.status }}</el-tag>
    </template>
    
    <template #column-actions="{ row }">
      <el-button v-permission="'product.edit'" @click="onEdit(row)">Edit</el-button>
      <el-button v-permission="'product.delete'" type="danger" @click="onDelete(row)">Delete</el-button>
    </template>
  </BaseTable>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getProductsApi, deleteProductApi } from '@/api/product'

const products = ref<Product[]>([])
const total = ref(0)
const loading = ref(false)
const search = ref({ name: '', category: '', status: '' })
const page = ref(1)
const size = ref(20)

const columns = [
  { key: 'id', label: 'ID', width: 80 },
  { key: 'name', label: 'Name' },
  { key: 'category', label: 'Category' },
  { key: 'price', label: 'Price', formatter: (row) => `$${row.price}` },
  { key: 'stock', label: 'Stock' },
  { key: 'status', label: 'Status' }
]

async function fetchData() {
  loading.value = true
  const { data } = await getProductsApi({ ...search.value, page: page.value, size: size.value })
  products.value = data.items
  total.value = data.total
  loading.value = false
}

function onSearch() { page.value = 1; fetchData() }
function onPage(p) { page.value = p; fetchData() }

async function onDelete(row) {
  await ElMessageBox.confirm(`Delete ${row.name}?`, 'Confirm')
  await deleteProductApi(row.id)
  ElMessage.success('Deleted')
  fetchData()
}

onMounted(fetchData)
</script>
▶ Experimente

Saída:

TEXT
Tabela com 6 colunas e 20 itens por página; buscar reinicia para a página 1.
Delete pede confirmação, apaga no servidor e mostra a mensagem Deleted.
Sem permissão, os botões de criar/editar/excluir não aparecem; o diálogo valida nome, preço e estoque.

(2) Caixa de diálogo “Editar produto”


7. Módulo 5: Gestão de pedidos (1 hora)

(1) Lista de pedidos + Detalhes

▶ Exemplo 5: Lista de pedidos + caixa de diálogo de detalhes (Dificuldade: ⭐)

Núcleo do pedido: Lista BaseTable + Gaveta de detalhes + Transições de status:

▶ Experimente

Saída:

TEXT
Lista com status em tag colorida (pending em amarelo, etc.).
View abre o diálogo com usuário, total e a lista de itens calculada por preço × quantidade.
O botão Confirm só aparece nos pedidos com status pending.

8. Módulo 6: Gerenciamento de usuários (1 hora)

(1) Lista de usuários + Gerenciamento de funções

▶ Exemplo 6: Gerenciamento de funções de usuário + Edição de RBAC (Dificuldade: ⭐)

Núcleo de gerenciamento de usuários: Lista + Funções de tags + Funções de edição de CheckboxGroup:

▶ Experimente

Saída:

TEXT
A lista mostra as funções de cada usuário como tags.
Edit Roles abre o checkbox com as 5 funções (admin, manager, editor, viewer, guest).
Ao salvar, a linha é atualizada com as novas tags.

(2) 5 Permissões de funções

TS
// utils/roles.ts
export const ROLES = {
  admin: {
    label: 'Admin',
    permissions: ['*']  // Full Access
  },
  manager: {
    label: 'Manager',
    permissions: ['product.*', 'order.*', 'user.read']
  },
  editor: {
    label: 'Editor',
    permissions: ['product.read', 'product.edit', 'order.read']
  },
  viewer: {
    label: 'Viewer',
    permissions: ['*.read']
  },
  guest: {
    label: 'Guest',
    permissions: ['product.read']
  }
}

9. Módulo 7: Campanhas de marketing (1 hora)

(1) Gestão de cupons

▶ Exemplo 7: Lista de cupons + intervalo de tempo (Dificuldade ⭐)

Módulo de Marketing – Núcleo: el-table + Status da tag + Formato de hora:

▶ Experimente

Saída:

TEXT
Tabela listrada com código, tipo, desconto, status e período de validade.
As datas aparecem formatadas (dd/mm/aaaa hh:mm) e o status vem em tag colorida.
Trocar o intervalo de datas refaz a consulta e atualiza a lista.

10. Módulo 8: Implantação (1 hora)

(1) Otimização da compilação

▶ Exemplo 8: Otimização manual da divisão em blocos no Vite (Dificuldade: ⭐⭐)

Implantação do Módulo Core: divisão manual em blocos (vue / echarts / ui / utils):

TS
// vite.config.ts(Production Environment Optimization)
build: {
  outDir: 'dist',
  sourcemap: true,  // Used for Sentry
  minify: 'esbuild',
  cssMinify: 'lightningcss',
  rollupOptions: {
    output: {
      manualChunks: {
        'vue-vendor': ['vue', 'vue-router', 'pinia'],
        'echarts-vendor': ['echarts', 'vue-echarts'],
        'ui-vendor': ['element-plus'],
        'utils': ['axios', 'dayjs']
      }
    }
  }
}
▶ Experimente

Saída:

TEXT
O build gera 4 arquivos: vue-vendor, echarts-vendor, ui-vendor e utils.
A primeira tela baixa apenas vue-vendor e o chunk da rota atual.
Com sourcemap ativo o Sentry consegue mapear o erro para o código original.

(2) Implantação no Vercel

BASH
npm install -g vercel
vercel --prod
JSON
// vercel.json
{
  "build": { "command": "npm run build" },
  "outputDirectory": "dist",
  "framework": "vite",
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

(3) Monitoramento de desempenho

TS
// main.ts
import * as Sentry from '@sentry/vue'

Sentry.init({
  app,
  dsn: 'https://your-dsn@sentry.io/123',
  tracesSampleRate: 0.1
})

11. Estatísticas de volume de código para todos os 8 módulos

Módulo Linhas de código Principais recursos
1 Inicialização do projeto 300 Vite + Pinia + Router + Element Plus + ECharts
2 Autenticação 400 JWT + Route Guard + Diretiva de Permissão + 5 Funções
3 painéis 500 5 ECharts + 4 KPIs + dados em tempo real
4 Gestão de Produtos 600 CRUD + Visualização em lista + Edição + Upload de imagens + Permissões
5 Gerenciamento de pedidos 400 Lista + Detalhes + Alterações de status + Exportação
6 Gerenciamento de usuários 300 Lista + Gerenciamento de funções + Edição de permissões
7 campanhas de marketing 300 Cupons + Intervalo de tempo + Máquina de estados
8 Implantação e Lançamento 200 Otimização da compilação com Vite + Vercel + Sentry
Total 3.000 Back-end SaaS completo com 8 módulos

12. Referência rápida: 5 erros comuns

Erro Sintoma Solução
Tela em branco na primeira inicialização Erro na ordem de carregamento do Router / Pinia No arquivo main.ts, chame app.use(router) antes de app.use(pinia)
Permissões expiradas Função não atualizada permissões de observação
10.000 eventos de travamento Baixo desempenho da tabela Rolagem virtual el-table-v2
O ECharts não está sendo exibido Altura do contêiner: 0 Defina style="height: 300px"
Erro 404 após a implantação Problemas de roteamento em SPA A Vercel reescreve a configuração

❓ Perguntas Frequentes

P Quanto tempo leva para concluir o Módulo 8?
R 10 a 12 horas (seguindo o ritmo do curso). 4 a 6 horas para desenvolvedores experientes.
P Como faço para simular uma API de back-end?
R Use o MSW (Mock Service Worker) ou o Express + json-server. Este tutorial utiliza a primeira opção.
P O Element Plus oferece suporte total ao TypeScript?
R Sim. O Element Plus 2.x foi totalmente reescrito em TypeScript e oferece inferência de tipos completa.
P Como posso otimizar um formulário com 10.000 campos?
R Use o el-table-v2 (Versão Oficial do Element Plus com Rolagem Virtual) ou o vue-virtual-scroller. Aumento de desempenho de 100 vezes.
P Como faço para implantar em produção?
R Vercel (recomendado, gratuito) + domínio personalizado. vercel --prod Um único comando.
P O que devo aprender depois de concluir o curso de 30 aulas?
R (1) Nuxt 3 (SSR); (2) React (análise comparativa); (3) Micro-front-ends (qiankun); (4) Desenvolvimento para dispositivos móveis (uni-app).
P Posso usar este modelo de projeto tal como está?
R Sim. O código é totalmente de código aberto e pode ser usado diretamente como ponto de partida para projetos corporativos.

📖 Resumo


13. 🎉 Concluí a lição 30!

Parabéns por ter concluído o curso completo de Vue 3, com 30 aulas! Desde os conceitos básicos até projetos de nível empresarial, você agora domina:

Recomendações para as próximas etapas:


📝 Exercícios

  1. Questões básicas (Dificuldade: ⭐)

    Conclua o Módulo 1 (Inicialização do projeto) + Módulo 2 (Login e autenticação):

    1. Vite + Vue 3 + Pinia + Element Plus
    2. Página de login + Painel (espaço reservado) 3.5 Noções básicas sobre permissões de funções
  2. Problemas avançados (Dificuldade: ⭐⭐)

    Conclua os 4 módulos principais: Inicialização / Login / Painel / Produtos:

    1. Configuração completa do Vite + TypeScript
    2. 5 gráficos do ECharts
    3. Operações CRUD no produto (10 campos)
  3. Problemas desafiadores (Dificuldade: ⭐⭐⭐)

    Conclua todos os 8 módulos + implantação:

    1. Todos os 8 módulos (10–12 horas)
    2. Tipagem forte do TypeScript
    3. Otimização de desempenho (rolagem virtual / blocos manuais)
    4. Cobertura de testes (5 componentes + 3 testes E2E)
    5. Implantação no Vercel + Integração com o Sentry
    6. Concluí o tutorial do Vue com 30 lições — Me formei! 🎉
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%