Frontend Architecture
Vue 3 + TypeScript frontend design.
Project Structure
sis_frontend/
├── src/
│ ├── api/ # API layer
│ │ ├── index.ts # Axios instance
│ │ ├── services/ # Domain API services
│ │ │ └── {domain}.api.ts
│ │ └── types/ # TypeScript interfaces
│ │ └── index.ts
│ ├── components/ # Reusable components
│ │ └── {Feature}.vue
│ ├── composables/ # Vue composables
│ │ └── use{Feature}.ts
│ ├── router/ # Vue Router
│ │ └── index.ts
│ ├── stores/ # Pinia stores
│ │ └── {domain}.ts
│ ├── views/ # Page components
│ │ └── {domain}/
│ │ └── {Feature}View.vue
│ ├── App.vue
│ └── main.ts
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.tsComponent Pattern
Vue 3 Composition API with <script setup>:
vue
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useToast } from 'primevue/usetoast'
import type { Entity } from '@/api/types'
import { entityApi } from '@/api/services/domain.api'
// Props and emits
const props = defineProps<{
entityId?: number
}>()
const emit = defineEmits<{
saved: [entity: Entity]
}>()
// State
const entity = ref<Entity | null>(null)
const loading = ref(false)
// Computed
const isNew = computed(() => !props.entityId)
// Methods
async function loadEntity() {
if (!props.entityId) return
loading.value = true
try {
const response = await entityApi.getById(props.entityId)
entity.value = response.data
} finally {
loading.value = false
}
}
async function save() {
// Save logic
emit('saved', entity.value!)
}
// Lifecycle
onMounted(() => loadEntity())
</script>
<template>
<div class="entity-form">
<!-- Template -->
</div>
</template>
<style scoped>
/* Component styles */
</style>API Service Pattern
typescript
// api/services/domain.api.ts
import api from '../index'
import type { Entity, EntityCreate, PaginatedResponse } from '../types'
export const entityApi = {
getAll: (params?: Record<string, any>) =>
api.get<PaginatedResponse<Entity>>('/domain/entities/', { params }),
getById: (id: number) =>
api.get<Entity>(`/domain/entities/${id}/`),
create: (data: EntityCreate) =>
api.post<Entity>('/domain/entities/', data),
update: (id: number, data: Partial<EntityCreate>) =>
api.patch<Entity>(`/domain/entities/${id}/`, data),
delete: (id: number) =>
api.delete(`/domain/entities/${id}/`),
// Custom action
customAction: (id: number) =>
api.post(`/domain/entities/${id}/custom_action/`)
}TypeScript Types
typescript
// api/types/index.ts
export interface Entity {
entity_id: number
name: string
status_code: string
status_display: string
created_at: string
}
export interface EntityCreate {
name: string
status_id: number
}
export interface PaginatedResponse<T> {
count: number
next: string | null
previous: string | null
results: T[]
}Pinia Store Pattern
typescript
// stores/entities.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Entity } from '@/api/types'
import { entityApi } from '@/api/services/domain.api'
export const useEntityStore = defineStore('entities', () => {
// State
const entities = ref<Entity[]>([])
const loading = ref(false)
// Getters
const activeEntities = computed(() =>
entities.value.filter(e => e.status_code === 'ACTIVE')
)
// Actions
async function fetchEntities() {
loading.value = true
try {
const response = await entityApi.getAll()
entities.value = response.data.results
} finally {
loading.value = false
}
}
return {
entities,
loading,
activeEntities,
fetchEntities
}
})Router Configuration
typescript
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/entities',
name: 'entities',
component: () => import('@/views/domain/EntityListView.vue'),
meta: { requiresAuth: true }
},
{
path: '/entities/:id',
name: 'entity-detail',
component: () => import('@/views/domain/EntityDetailView.vue'),
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})PrimeVue Components
Use PrimeVue for UI components:
vue
<template>
<DataTable :value="entities" :loading="loading">
<Column field="name" header="Name" sortable />
<Column field="status_display" header="Status" />
</DataTable>
</template>