Components
Vue 3 component patterns.
Component Template
vue
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import type { Entity } from '@/api/types'
const { t } = useI18n()
// Props
const props = defineProps<{
entityId: number
readonly?: boolean
}>()
// Emits
const emit = defineEmits<{
saved: [entity: Entity]
cancelled: []
}>()
// State
const entity = ref<Entity | null>(null)
const loading = ref(false)
// Computed
const canSave = computed(() => {
return entity.value?.name && !props.readonly
})
// Methods
async function loadEntity() {
loading.value = true
try {
// Load data
} finally {
loading.value = false
}
}
function handleSave() {
if (entity.value) {
emit('saved', entity.value)
}
}
// Lifecycle
onMounted(() => loadEntity())
</script>
<template>
<div class="entity-component">
<!-- Template -->
</div>
</template>
<style scoped>
.entity-component {
/* Styles */
}
</style>Naming Conventions
| Type | Convention | Example |
|---|---|---|
| View | {Feature}View.vue | StudentListView.vue |
| Component | {Feature}.vue | StudentCard.vue |
| Composable | use{Feature}.ts | useStudentSearch.ts |
Component Organization
The project has 35 reusable components organized by domain:
| Directory | Components |
|---|---|
layout/ | App shell (sidebar, header, breadcrumbs) |
academic/ | Course and academic display components |
audit/ | Degree audit visualization |
common/ | Generic shared components |
formbuilder/ | Dynamic form rendering |
graduation/ | Graduation workflow steps |
people/ | Person detail panels |
testscores/ | Test score display |
Props and Emits
Typed Props
typescript
const props = defineProps<{
studentId: number
mode: 'view' | 'edit'
optional?: string
}>()With Defaults
typescript
const props = withDefaults(defineProps<{
pageSize?: number
showHeader?: boolean
}>(), {
pageSize: 20,
showHeader: true
})Typed Emits
typescript
const emit = defineEmits<{
selected: [item: Entity]
updated: [id: number, data: UpdateData]
cancelled: []
}>()Component Communication
Parent to Child (Props)
vue
<!-- Parent -->
<StudentCard :student="selectedStudent" mode="view" />Child to Parent (Emits)
vue
<!-- Child -->
<script setup>
const emit = defineEmits<{ selected: [student: Student] }>()
function handleClick() {
emit('selected', student)
}
</script>v-model
vue
<!-- Parent -->
<SearchInput v-model="searchTerm" />
<!-- Child -->
<script setup>
const model = defineModel<string>()
</script>
<template>
<input v-model="model" />
</template>Internationalization
Use Vue i18n for all user-facing text:
vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
<template>
<h1>{{ t('academic.courses.title') }}</h1>
<Button :label="t('common.save')" />
</template>Translation files are in src/locales/en.json.
Async Components
Lazy load components:
typescript
const HeavyComponent = defineAsyncComponent(() =>
import('@/components/HeavyComponent.vue')
)