WorkmateOS Frontend-Architektur - Visuelle Übersicht

Anwendungsstruktur-Hierarchie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
┌─────────────────────────────────────────────────────────────────┐
│                     main.ts (Entry Point)                       │
│                   Erstellt App + Pinia + Router                 │
└──────────────────────────┬──────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│                        App.vue (Root)                           │
│                 Einfacher RouterView-Delegat                    │
└──────────────────────────┬──────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│                 Vue Router (router/index.ts)                    │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ Route: "/" → umleiten zu "/under-construction"          │  │
│  ├──────────────────────────────────────────────────────────┤  │
│  │ Route: "/app" → AppLayout (Hauptanwendung)              │  │
│  │  Children:                                              │  │
│  │    - /dashboard → DashboardPage                         │  │
│  │    - /crm → CrmApp                                      │  │
│  ├──────────────────────────────────────────────────────────┤  │
│  │ Route: "/under-construction"                            │  │
│  │ Route: "/linktree"                                      │  │
│  └──────────────────────────────────────────────────────────┘  │
└──────────────────────────┬──────────────────────────────────────┘
                           │
                           ▼
        ┌──────────────────────────────────────┐
        │        AppLayout.vue                 │
        │  (Hauptcontainer mit 3 Teilen)       │
        └──────────────────────────────────────┘
                  │         │         │
        ┌─────────┴────┐    │    ┌────┴─────────┐
        ▼              ▼    ▼    ▼              ▼
    ┌────────┐   ┌───────────────┐   ┌────────┐
    │ Topbar │   │ WindowHost    │   │  Dock  │
    │(Fixed) │   │(App-Fenster)  │   │(Fixed) │
    └────────┘   └───────────────┘   └────────┘
                        │
             ┌──────────┴──────────┐
             ▼                     ▼
        ┌──────────┐          ┌──────────┐
        │WindowFrame│          │WindowFrame│
        │(CRM-App) │          │(Projekte)│
        └──────────┘          └──────────┘
             │                     │
             ▼                     ▼
        ┌──────────┐          ┌──────────┐
        │ CrmApp   │          │ProjectsApp
        │(Modul)   │          │(Modul)   │
        └──────────┘          └──────────┘

Modulinterne Architektur (CRM-Beispiel)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
┌───────────────────────────────────────────────────────────┐
│              CrmApp.vue (Modul-Entry)                     │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ Verwendet: useCrmNavigation()                       │ │
│  │   - view: "dashboard" | "customers" | ...          │ │
│  │   - activeCustomerId, activeContactId              │ │
│  │   - Navigationsfunktionen: goCustomers, etc        │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                           │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ Bedingtes Rendering basierend auf view-State       │ │
│  │                                                     │ │
│  │ ┌────────────────────────────────────────────────┐ │ │
│  │ │ <CrmDashboardPage v-if="view === 'dashboard"" │ │ │
│  │ │   @openCustomers="goCustomers" />             │ │ │
│  │ ├────────────────────────────────────────────────┤ │ │
│  │ │ <CustomersListPage v-if="view === 'customers'" │ │ │
│  │ │   @openCustomer="goCustomerDetail" />         │ │ │
│  │ ├────────────────────────────────────────────────┤ │ │
│  │ │ <CustomerDetailPage v-if="view === ..."       │ │ │
│  │ │   :customerId="activeCustomerId" />           │ │ │
│  │ ├────────────────────────────────────────────────┤ │ │
│  │ │ <CustomerForm v-if="view === 'customer-create'"│ │ │
│  │ │   @saved="goCustomers" />                     │ │ │
│  │ └────────────────────────────────────────────────┘ │ │
│  └─────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
                         │
        ┌────────────────┴────────────────┐
        ▼                                 ▼
┌──────────────────────────────┐  ┌──────────────────────────────┐
│     Pages-Layer              │  │   Components-Layer           │
├──────────────────────────────┤  ├──────────────────────────────┤
│ - CrmDashboardPage.vue       │  │ - CustomerCard.vue           │
│ - CustomersListPage.vue      │  │ - CustomerForm.vue           │
│ - CustomerDetailPage.vue     │  │ - ContactCard.vue            │
│ - ContactsListPage.vue       │  │ - ContactForm.vue            │
│ - ContactDetailPage.vue      │  │ - CrmKpiCustomers.vue        │
│                              │  │ - CrmRecentActivity.vue      │
│ (Emit Events, kein Routing) │  │ - CrmShortcuts.vue           │
└──────────────────────────────┘  └──────────────────────────────┘
        │                                 │
        └────────────────┬────────────────┘
                         ▼
        ┌────────────────────────────────┐
        │   Services/Composables-Layer   │
        ├────────────────────────────────┤
        │ • crmService (HTTP-Aufrufe)    │
        │ • useCrmNavigation (State)     │
        │ • useCrmStats (fetch + State)  │
        │ • useCrmActivity (fetch State) │
        └────────────────────────────────┘
                         │
                         ▼
        ┌────────────────────────────────┐
        │     Typen / Interfaces         │
        ├────────────────────────────────┤
        │ - Customer                     │
        │ - Contact                      │
        │ - CrmActivity                  │
        │ - CrmStats                     │
        └────────────────────────────────┘
                         │
                         ▼
        ┌────────────────────────────────┐
        │    Gemeinsamer API-Client      │
        │  (services/api/client.ts)      │
        │  Axios-Instanz mit             │
        │  Interceptors                  │
        └────────────────────────────────┘

Fensterverwaltungssystem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
┌──────────────────────────────────────────────────────────────────┐
│                  useAppManager() Composable                       │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ State:                                                     │ │
│  │  - windows: WindowApp[]    (Reaktives Array)              │ │
│  │  - activeWindow: Ref<string|null>  (Aktuell fokussiert)   │ │
│  │  - zCounter: number        (Für Layering)                 │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ WindowApp Interface:                                       │ │
│  │  {                                                         │ │
│  │    id: string;              // Eindeutige Fensterinstanz  │ │
│  │    appId: string;           // Referenz zur App-Registry  │ │
│  │    title: string;                                         │ │
│  │    component: Component;                                  │ │
│  │    props?: Record<string, any>;                           │ │
│  │    x, y, width, height: number;  // Position/Größe       │ │
│  │    z: number;               // Layer-Tiefe               │ │
│  │  }                                                         │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ Methoden:                                                  │ │
│  │  • openWindow(appId)        → Öffnet/fokussiert App       │ │
│  │  • closeWindow(id)          → Schließt Fenster            │ │
│  │  • focusWindow(id)          → Bringt in Vordergrund       │ │
│  │  • startDragFor(id, e)      → Initiiert Drag              │ │
│  │  • startResizeFor(id, e)    → Initiiert Resize            │ │
│  └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
                             │
                             ▼
            ┌────────────────────────────────┐
            │   appRegistry.ts               │
            │  (Zentrale App-Registrierung)  │
            ├────────────────────────────────┤
            │ const apps = [                 │
            │   {                            │
            │     id: "crm",                 │
            │     title: "CRM",              │
            │     icon: markRaw(icons.Users),│
            │     component: markRaw(CrmApp),│
            │     window: {                  │
            │       width: 1100,             │
            │       height: 700              │
            │     }                          │
            │   },                           │
            │   // Weitere Apps...           │
            │ ]                              │
            └────────────────────────────────┘
                             │
                             ▼
            ┌────────────────────────────────┐
            │   WindowHost.vue               │
            │  (Rendert alle Fenster)        │
            └────────────────────────────────┘
                             │
        ┌────────────────────┴────────────────┐
        ▼                                     ▼
┌─────────────────────┐              ┌─────────────────────┐
│  WindowFrame.vue    │              │  WindowFrame.vue    │
│  (CRM-Instanz)      │              │  (Projekt-Instanz)  │
│                     │              │                     │
│ Titelleiste (Ziehbar)│             │ Titelleiste (Ziehbar)│
│ Inhaltsbereich      │              │ Inhaltsbereich      │
│ Größenänderungs-    │              │ Größenänderungs-    │
│ Handle              │              │ Handle              │
│ Schließen-Button    │              │ Schließen-Button    │
└─────────────────────┘              └─────────────────────┘
        │                                     │
        ▼                                     ▼
┌─────────────────────┐              ┌─────────────────────┐
│   CrmApp.vue        │              │  ProjectsApp.vue    │
│   (Aktiv)           │              │   (Hintergrund)     │
└─────────────────────┘              └─────────────────────┘

Datenfluss: Datenabruf-Muster

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Komponente (Page)
    │
    ├─ onMounted()
    │   └─→ Ruft composable.fetchData() auf
    │
    ▼
Composable (useCrmStats, etc)
    │
    ├─ loading = true
    ├─ error = null
    │
    ▼
Service (crmService)
    │
    ├─ Ruft api.get("/api/...") auf
    │
    ▼
API-Client (Axios-Instanz)
    │
    ├─ Request-Interceptor (JWT-Token hinzufügen)
    ├─ HTTP-Request durchführen
    ├─ Response-Interceptor (Fehler behandeln)
    │
    ▼
Backend-API (z.B. FastAPI)
    │
    ▼
API-Client (gibt Antwort zurück)
    │
    ▼
Service (extrahiert Daten)
    │
    │ return data
    ▼
Composable
    │
    ├─ stats.value = data
    ├─ loading = false
    ├─ error = null
    │
    ▼
Komponente (Template)
    │
    ├─ v-if="loading" → Spinner anzeigen
    ├─ v-else-if="error" → Fehler anzeigen
    ├─ v-else → Daten anzeigen (stats.value)
    │
    └─ UI aktualisiert sich automatisch (reaktiv)

Styling-Architektur

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
┌─────────────────────────────────────────────────┐
│        Design Tokens (tokens.css)               │
│  ┌──────────────────────────────────────────┐  │
│  │ CSS Custom Properties:                   │  │
│  │  --color-bg-primary: #232223             │  │
│  │  --color-accent-primary: #ff9100         │  │
│  │  --color-text-primary: #ffffff           │  │
│  │  --space-md: 16px                        │  │
│  │  --os-dock-height: 80px                  │  │
│  │  ... etc ...                             │  │
│  └──────────────────────────────────────────┘  │
└─────────────────────────────────────────────────┘
                      │
        ┌─────────────┴──────────────┐
        ▼                            ▼
┌──────────────────┐        ┌──────────────────┐
│  Tailwind CSS    │        │  Komponenten-CSS │
│  (base.css)      │        │  (button.css)    │
├──────────────────┤        ├──────────────────┤
│ @layer base      │        │ .kit-btn-primary │
│ @layer components│        │ .kit-input       │
│ @layer utilities │        │ .kit-label       │
│                  │        │ ... etc ...      │
│ Tailwind-Klassen │        │                  │
│ px-4, py-2, etc  │        │ Scoped in .vue   │
└──────────────────┘        └──────────────────┘
        │                            │
        └─────────────┬──────────────┘
                      ▼
        ┌──────────────────────────┐
        │   Komponenten-Styling    │
        │   (Template + <style>)   │
        │                          │
        │ <div class="px-4 py-2    │
        │   bg-white/5 ...">       │
        │                          │
        │ <style scoped>           │
        │   .window-frame { ... }  │
        │ </style>                 │
        └──────────────────────────┘

Datentypen-Organisation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Modul-Root: src/modules/crm/

types/
├── customer.ts
│   └─ export interface Customer { ... }
├── contact.ts
│   └─ export interface Contact { ... }
├── activity.ts
│   └─ export interface CrmActivity { ... }
│   └─ export interface CreateCrmActivity { ... }
└── stats.ts
    └─ export interface CrmStats { ... }

services/
└── crm.service.ts
    └─ export const crmService = {
         async getCustomers(): Promise<Customer[]> { ... }
         async getContact(id): Promise<Contact> { ... }
         async getCrmStats(): Promise<CrmStats> { ... }
       }

composables/
├── useCrmNavigation.ts
│   └─ Navigations-State (view, activeId)
├── useCrmStats.ts
│   └─ Datenabruf + Loading-State
└── useCrmActivity.ts
    └─ Activity-Management

pages/
├── CustomersListPage.vue
├── CustomerDetailPage.vue
├── ContactsListPage.vue
├── ContactDetailPage.vue
└── CrmDashboardPage.vue

components/
├── customer/
│   ├── CustomerCard.vue
│   └── CustomerForm.vue
├── contacts/
│   ├── ContactCard.vue
│   └── ContactForm.vue
└── widgets/
    ├── CrmKpiCustomers.vue
    ├── CrmRecentActivity.vue
    └── CrmShortcuts.vue

Komponentenkommunikations-Muster

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌─────────────────────────────┐
│      CrmApp.vue             │
│   (Parent/Router)           │
│                             │
│ const { view,              │
│         activeCustomerId,  │
│         goCustomerDetail   │
│       } = useCrmNavigation()│
└─────────────────────────────┘
        │
        ├─ Event: @openCustomer
        │           ↓
        │  Ruft auf: goCustomerDetail(id)
        │           ↓
        │  Aktualisiert: activeCustomerId = id
        │           ↓
        │  Aktualisiert: view = "customer-detail"
        │
        ▼
┌─────────────────────────────┐
│ CustomersListPage.vue       │
│                             │
│ emit("openCustomer", id)    │
│                             │
│ @openCustomer="             │
│   goCustomerDetail"         │
└─────────────────────────────┘
        │
        ├─ Props: keine
        │         (State über Composable übergeben)
        ├─ Emits: openCustomer(id), openDashboard()
        └─ State: search, page (lokal)
                  customers (vom Service)
                  loading (vom Composable)

Props nach unten → Events nach oben → Composables für gemeinsamen State

Neues Modul hinzufügen: Dateierstellungs-Checkliste

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
✓ Schritt 1: Verzeichnisstruktur erstellen
  └─ ui/src/modules/mymodule/

✓ Schritt 2: Typdefinitionen erstellen
  └─ types/mymodule.ts
     export interface MyResource { ... }

✓ Schritt 3: Service-Layer erstellen
  └─ services/mymodule.service.ts
     export const mymoduleService = { ... }

✓ Schritt 4: Navigations-Composable erstellen
  └─ composables/useMyModuleNav.ts
     export function useMyModuleNav() { ... }

✓ Schritt 5: Pages erstellen
  └─ pages/MyPage.vue
     pages/index.ts

✓ Schritt 6: Komponenten erstellen
  └─ components/MyComponent.vue
     components/index.ts

✓ Schritt 7: Modul-Entry-Point erstellen
  └─ MyModuleApp.vue
     (Verwendet useMyModuleNav, rendert Pages bedingt)

✓ Schritt 8: Modul registrieren
  └─ layouts/app-manager/appRegistry.ts
     Zum apps[]-Array hinzufügen

✓ Schritt 9: Dock-Item hinzufügen
  └─ layouts/components/Dock.vue
     Zum dockItems[]-Array hinzufügen

✓ Schritt 10 (Optional): Route hinzufügen
  └─ router/index.ts
     Child-Route unter /app hinzufügen