Skip to content

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 ​

TypeConventionExample
View{Feature}View.vueStudentListView.vue
Component{Feature}.vueStudentCard.vue
Composableuse{Feature}.tsuseStudentSearch.ts

Component Organization ​

The project has 35 reusable components organized by domain:

DirectoryComponents
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')
)

Student Information System Documentation