Appearance
Code Style - Padrões de Código
Estrutura de Componentes Vue
OBRIGATÓRIO: Seguir a ordem <template>, <script setup>, <style>
vue
<template>
<!-- Template HTML -->
</template>
<script setup lang="ts">
// Lógica do componente
</script>
<style lang="scss" scoped>
// Estilos SCSS
</style>Regras de Código
| Regra | Descrição |
|---|---|
| Sem comentários | Não adicionar comentários no código. O código deve ser autoexplicativo |
| Script Setup | Usar sempre <script setup lang="ts"> |
| TypeScript | Todo código deve ser tipado |
| Sem tipar retorno | NÃO tipar retorno de funções. O TypeScript infere automaticamente |
| Sem if inline | PROIBIDO if em uma linha. Sempre usar bloco { } com quebra de linha |
| Sem return inline | PROIBIDO return na mesma linha do if. Sempre dentro do bloco { } |
| Linha vazia após if | Sempre deixar uma linha vazia após o fechamento } de um bloco if |
| Linha vazia após const/let | Sempre deixar uma linha vazia após cada declaração const ou let |
| SCSS | Usar <style lang="scss" scoped> para estilos |
| Composables | Extrair lógica reutilizável em composables |
| Sem aninhamento | PROIBIDO aninhar if ou for. Usar early returns e métodos auxiliares |
| Responsabilidade única | Cada método deve ter apenas uma responsabilidade. Extrair lógica em funções menores |
| Sem lógica em componentes | Componentes são pura view. Toda lógica de negócio, filtros, computeds e ações deve ficar em stores ou composables |
| Sem try/catch em componentes | Tratamento de erros deve ficar na store ou composable. Componentes apenas chamam métodos |
| storeToRefs | Usar storeToRefs(store) para expor variáveis reativas. Desestruturar ações diretamente da store |
| BEM no SCSS | Usar convenção BEM (block__element--modifier) com @apply Tailwind nos estilos SCSS |
| Sem cores hardcoded | Não usar cores fixas (rgba, #hex). Usar classes Tailwind com variantes dark: ou variáveis CSS do tema para suportar tema claro/escuro e customização futura |
| Sem props/emits em nested | Componentes nested dentro do mesmo módulo devem acessar a store diretamente via storeToRefs() em vez de receber dados por props/emits. Props/emits são para componentes genéricos reutilizáveis entre módulos |
| router.push pelo name | PROIBIDO usar router.push('/path') com path string. Sempre usar router.push({ name: 'route-name' }) pois o path pode mudar |
| Erros do backend no catch | No .catch() de chamadas de serviço, sempre exibir a mensagem vinda do backend: catch((e) => { message.error(e?.response?.data?.message) }). Não usar mensagens genéricas de i18n no catch, o backend já retorna mensagens assertivas |
Separação de responsabilidades: componentes vs stores
Componentes são pura view
Componentes não devem conter lógica de negócio. Toda lógica deve estar em stores ou composables.
typescript
// ❌ ERRADO - lógica no componente
const search = ref('')
const debouncedSearch = ref('')
const updateDebounced = debounce((v: string) => { debouncedSearch.value = v }, 500)
watch(search, (v) => updateDebounced(v))
const filtered = computed(() => items.filter(...))
const handleDelete = (id: string) => { dialog.warning({ onPositiveClick: () => store.delete(id) }) }
// ✅ CORRETO - tudo na store, componente só consome
const store = useItemsStore()
const { search, filteredItems, loading } = storeToRefs(store)
const { init, handleDelete } = storestoreToRefs para variáveis, desestruturação para ações
typescript
// ❌ ERRADO - acessar tudo via store.xxx
const store = useStore()
// template: store.loading, store.items, store.deleteItem()
// ✅ CORRETO - separar refs de ações
const store = useStore()
const { items, loading, search } = storeToRefs(store)
const { deleteItem, loadItems, init } = storeSem try/catch em componentes
typescript
// ❌ ERRADO - try/catch no componente
const handleSave = async () => {
saving.value = true
try {
await api.save(data)
message.success('Salvo')
} catch {
message.error('Erro')
} finally {
saving.value = false
}
}
// ✅ CORRETO - componente só chama a store
const handleSave = async () => {
await saveProfile()
}Princípios de Clean Code
Sem if inline e sem return inline
typescript
// ❌ ERRADO - if inline com return
if (!user) return null
if (items.length === 0) return
// ✅ CORRETO - bloco com chaves e quebra de linha
if (!user) {
return null
}
if (items.length === 0) {
return
}Linha vazia após if, const e let
typescript
// ❌ ERRADO - sem espaçamento
const user = getUser()
const name = user.name
if (!name) {
return
}
const formatted = formatName(name)
// ✅ CORRETO - linha vazia após cada const/let e após cada bloco if
const user = getUser()
const name = user.name
if (!name) {
return
}
const formatted = formatName(name)Não tipar retorno de funções
typescript
// ❌ ERRADO - retorno tipado manualmente
const getUser = (): User => { ... }
const isValid = (value: string): boolean => { ... }
// ✅ CORRETO - TypeScript infere o retorno
const getUser = () => { ... }
const isValid = (value: string) => { ... }PROIBIDO aninhamento de estruturas de controle
typescript
// ❌ ERRADO - if aninhado
const processItems = (items: Item[]) => {
if (items.length > 0) {
for (const item of items) {
if (item.active) {
// lógica
}
}
}
}
// ✅ CORRETO - early return + método auxiliar
const processItems = (items: Item[]) => {
if (items.length === 0) {
return
}
const activeItems = items.filter((item) => item.active)
activeItems.forEach(processItem)
}
const processItem = (item: Item) => {
// lógica isolada
}Responsabilidade única por método
typescript
// ❌ ERRADO - múltiplas responsabilidades
const handleSubmit = async () => {
const isValid = validateForm()
if (!isValid) {
showError('Formulário inválido')
return
}
const data = transformData(formData)
await api.save(data)
showSuccess('Salvo!')
router.push('/list')
}
// ✅ CORRETO - responsabilidades separadas
const handleSubmit = async () => {
if (!validateForm()) {
return showValidationError()
}
await saveData()
navigateToList()
}
const showValidationError = () => showError('Formulário inválido')
const saveData = async () => {
const data = transformData(formData)
await api.save(data)
showSuccess('Salvo!')
}
const navigateToList = () => router.push('/list')Nomenclatura
| Tipo | Convenção | Exemplo |
|---|---|---|
| Componentes | PascalCase | ContractList.vue |
| Composables | camelCase com prefixo use | useContracts.ts |
| Services | camelCase com sufixo Service | getContractsService |
| Stores | camelCase com prefixo use | useContractsStore |
| Types/Interfaces | PascalCase | Contract, ContractPart |
| Arquivos de página | PascalCase | ListContracts.vue |
Padrão de Props e Emits
typescript
const props = defineProps<{
currentStep: number
title: string
}>()
const emit = defineEmits<{
(e: 'update', value: string): void
(e: 'setStep', index: number): void
}>()Padrão de Composables
typescript
export const useContracts = () => {
const items = ref<Contract[]>([])
const loading = ref(false)
const loadContracts = async () => {
loading.value = true
const { data } = await getContractsService()
items.value = data
loading.value = false
}
return {
items,
loading,
loadContracts,
}
}Regras de i18n (Traduções)
| Regra | Descrição |
|---|---|
| Sentence case | Usar letra maiúscula apenas no início da frase e em nomes próprios. Nunca usar Title Case (maiúscula em todas as palavras) |
| Nomes próprios | Manter maiúsculas apenas em: siglas (CPF, CNPJ, FAQ), marcas (WhatsApp), títulos de documentos legais (Termos de Serviço, Política de Privacidade), categorias jurídicas (Pessoa Jurídica, Pessoa Física) |
Sentence case em traduções
❌ ERRADO - Title Case
"Novo Contrato"
"Total de Horas"
"Relatório Mensal"
"Histórico de Contratos"
✅ CORRETO - Sentence case
"Novo contrato"
"Total de horas"
"Relatório mensal"
"Histórico de contratos"
✅ CORRETO - Exceções (nomes próprios, siglas, títulos legais)
"Termos de Serviço"
"Política de Privacidade"
"Pessoa Jurídica"
"CPF do responsável"Checklist para Novos Componentes
- [ ] Usar
<script setup lang="ts"> - [ ] Não adicionar comentários no código
- [ ] Seguir ordem: template → script → style
- [ ] Usar
<style lang="scss" scoped> - [ ] Tipar todas as props e emits
- [ ] Usar composables para lógica reutilizável
- [ ] Seguir convenções de nomenclatura
- [ ] Usar classes Tailwind para layout básico
- [ ] Usar i18n para textos estáticos