perf(ops): lazy-load heavy libs & shell components — chunks route/shell bien plus légers
Batch 1 de l'audit d'optimisation. Les libs lourdes étaient statiquement importées donc livrées même inutilisées → passées en import dynamique / defineAsyncComponent : - MainLayout : ConversationPanel/PhoneModal/FlowEditor/NewTicket/Orchestrator/ ServiceStatus/OutboxPanel → async (chunk shell 312 KB → 56 KB, chargé sur CHAQUE page) - TaskGraphPage : TaskGantt (hy-vue-gantt) → async (2.6 MB → 20 KB ; le Gantt ne charge que dans la vue Gantt) - NetworkPage : cytoscape → import() dans loadNetworkMap (484 KB → 52 KB) - ReportAR/Revenu/Taxes : chart.js/auto → import() dans renderChart (async) (~150→~10 KB/page) Total dist inchangé (~8 M) — le code est DÉFÉRÉ en chunks à la demande, pas supprimé. Gain réel = ce que l'utilisateur télécharge pour VOIR une page. Build OK, leak-check propre, shell + route rapport vérifiés (aucune erreur d'import). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1af8c62142
commit
4a641ac802
|
|
@ -168,7 +168,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, nextTick, watch } from 'vue'
|
import { ref, computed, nextTick, watch, defineAsyncComponent } from 'vue'
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from 'src/stores/auth'
|
import { useAuthStore } from 'src/stores/auth'
|
||||||
|
|
@ -180,16 +180,18 @@ import {
|
||||||
Gift, Settings, LogOut, PanelLeftOpen, PanelLeftClose, Mail,
|
Gift, Settings, LogOut, PanelLeftOpen, PanelLeftClose, Mail,
|
||||||
CalendarRange, CalendarClock, Sparkles, MapPinned, Workflow, RefreshCw, ReceiptText, MessagesSquare, History, Award, Star, ClipboardCheck, HardHat,
|
CalendarRange, CalendarClock, Sparkles, MapPinned, Workflow, RefreshCw, ReceiptText, MessagesSquare, History, Award, Star, ClipboardCheck, HardHat,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import ConversationPanel from 'src/components/shared/ConversationPanel.vue'
|
// Composants lourds montés conditionnellement (v-if) → chargés à la demande (defineAsyncComponent) au lieu d'alourdir
|
||||||
import PhoneModal from 'src/components/customer/PhoneModal.vue'
|
// le chunk MainLayout livré sur CHAQUE page. Aucun accès par ref → sûr. NotificationBell reste synchrone (barre du haut, toujours visible).
|
||||||
|
const ConversationPanel = defineAsyncComponent(() => import('src/components/shared/ConversationPanel.vue'))
|
||||||
|
const PhoneModal = defineAsyncComponent(() => import('src/components/customer/PhoneModal.vue'))
|
||||||
import { useSoftphone } from 'src/composables/useSoftphone'
|
import { useSoftphone } from 'src/composables/useSoftphone'
|
||||||
import NotificationBell from 'src/components/shared/NotificationBell.vue'
|
import NotificationBell from 'src/components/shared/NotificationBell.vue'
|
||||||
import { useConversations } from 'src/composables/useConversations'
|
import { useConversations } from 'src/composables/useConversations'
|
||||||
import FlowEditorDialog from 'src/components/flow-editor/FlowEditorDialog.vue'
|
const FlowEditorDialog = defineAsyncComponent(() => import('src/components/flow-editor/FlowEditorDialog.vue'))
|
||||||
import NewTicketDialog from 'src/components/shared/NewTicketDialog.vue'
|
const NewTicketDialog = defineAsyncComponent(() => import('src/components/shared/NewTicketDialog.vue'))
|
||||||
import OrchestratorDialog from 'src/components/shared/OrchestratorDialog.vue'
|
const OrchestratorDialog = defineAsyncComponent(() => import('src/components/shared/OrchestratorDialog.vue'))
|
||||||
import ServiceStatusDialog from 'src/components/shared/ServiceStatusDialog.vue'
|
const ServiceStatusDialog = defineAsyncComponent(() => import('src/components/shared/ServiceStatusDialog.vue'))
|
||||||
import OutboxPanel from 'src/components/shared/OutboxPanel.vue'
|
const OutboxPanel = defineAsyncComponent(() => import('src/components/shared/OutboxPanel.vue'))
|
||||||
|
|
||||||
const icons = { LayoutDashboard, Users, Truck, Ticket, UsersRound, BarChart3, Gift, Settings, LogOut, PanelLeftOpen, PanelLeftClose, Mail, CalendarRange, CalendarClock, Sparkles, MapPinned, Workflow, RefreshCw, ReceiptText, MessagesSquare, History, Award, Star, ClipboardCheck, HardHat }
|
const icons = { LayoutDashboard, Users, Truck, Ticket, UsersRound, BarChart3, Gift, Settings, LogOut, PanelLeftOpen, PanelLeftClose, Mail, CalendarRange, CalendarClock, Sparkles, MapPinned, Workflow, RefreshCw, ReceiptText, MessagesSquare, History, Award, Star, ClipboardCheck, HardHat }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -696,7 +696,6 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, watch, nextTick, reactive } from 'vue'
|
import { ref, computed, onMounted, watch, nextTick, reactive } from 'vue'
|
||||||
import cytoscape from 'cytoscape'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { listDocs, updateDoc, createDoc } from 'src/api/erp'
|
import { listDocs, updateDoc, createDoc } from 'src/api/erp'
|
||||||
import { Notify } from 'quasar'
|
import { Notify } from 'quasar'
|
||||||
|
|
@ -1172,6 +1171,7 @@ const mapSelectedSite = ref(null)
|
||||||
const mapSelectedSiteId = ref(null)
|
const mapSelectedSiteId = ref(null)
|
||||||
const cyContainer = ref(null)
|
const cyContainer = ref(null)
|
||||||
let cyInstance = null
|
let cyInstance = null
|
||||||
|
let cytoscape = null // chargé à la demande (lib ~350 KB) — seulement quand on ouvre l'onglet Carte
|
||||||
|
|
||||||
const mapSelectedSiteLinks = computed(() => {
|
const mapSelectedSiteLinks = computed(() => {
|
||||||
if (!mapData.value || !mapSelectedSiteId.value) return []
|
if (!mapData.value || !mapSelectedSiteId.value) return []
|
||||||
|
|
@ -1183,6 +1183,7 @@ async function loadNetworkMap () {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${hubUrl}/network/map`)
|
const resp = await fetch(`${hubUrl}/network/map`)
|
||||||
mapData.value = await resp.json()
|
mapData.value = await resp.json()
|
||||||
|
if (!cytoscape) cytoscape = (await import('cytoscape')).default // import dynamique : sort la lib du chunk NetworkPage
|
||||||
await nextTick()
|
await nextTick()
|
||||||
renderCytoscape()
|
renderCytoscape()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import { fetchARReport } from 'src/api/reports'
|
||||||
import { formatMoney } from 'src/composables/useFormatters'
|
import { formatMoney } from 'src/composables/useFormatters'
|
||||||
import { exportCsv } from 'src/composables/useCsvExport'
|
import { exportCsv } from 'src/composables/useCsvExport'
|
||||||
import ReportScaffold from 'src/components/shared/ReportScaffold.vue'
|
import ReportScaffold from 'src/components/shared/ReportScaffold.vue'
|
||||||
import Chart from 'chart.js/auto'
|
let Chart = null // chart.js ~90 KB → chargé à la demande au 1er rendu de graphique (hors du chunk de la page)
|
||||||
|
|
||||||
const asOfDate = ref(new Date().toISOString().slice(0, 10))
|
const asOfDate = ref(new Date().toISOString().slice(0, 10))
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
@ -83,9 +83,10 @@ async function loadReport () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderChart () {
|
async function renderChart () {
|
||||||
if (chartInstance) chartInstance.destroy()
|
if (chartInstance) chartInstance.destroy()
|
||||||
if (!chartCanvas.value || !summary.value) return
|
if (!chartCanvas.value || !summary.value) return
|
||||||
|
if (!Chart) Chart = (await import('chart.js/auto')).default
|
||||||
const s = summary.value
|
const s = summary.value
|
||||||
chartInstance = new Chart(chartCanvas.value, {
|
chartInstance = new Chart(chartCanvas.value, {
|
||||||
type: 'doughnut',
|
type: 'doughnut',
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ import { fetchRevenueReport } from 'src/api/reports'
|
||||||
import { formatMoney } from 'src/composables/useFormatters'
|
import { formatMoney } from 'src/composables/useFormatters'
|
||||||
import StatCard from 'src/components/shared/StatCard.vue'
|
import StatCard from 'src/components/shared/StatCard.vue'
|
||||||
import DataTable from 'src/components/shared/DataTable.vue'
|
import DataTable from 'src/components/shared/DataTable.vue'
|
||||||
import Chart from 'chart.js/auto'
|
let Chart = null // chart.js ~90 KB → chargé à la demande au 1er rendu de graphique (hors du chunk de la page)
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const startDate = ref(new Date(now.getFullYear(), 0, 1).toISOString().slice(0, 10))
|
const startDate = ref(new Date(now.getFullYear(), 0, 1).toISOString().slice(0, 10))
|
||||||
|
|
@ -197,9 +197,10 @@ const CHART_COLORS = [
|
||||||
'#a855f7', '#3b82f6', '#d946ef', '#0ea5e9', '#f43f5e',
|
'#a855f7', '#3b82f6', '#d946ef', '#0ea5e9', '#f43f5e',
|
||||||
]
|
]
|
||||||
|
|
||||||
function renderChart () {
|
async function renderChart () {
|
||||||
if (chartInstance) chartInstance.destroy()
|
if (chartInstance) chartInstance.destroy()
|
||||||
if (!chartCanvas.value || !chartAccounts.value.length) return
|
if (!chartCanvas.value || !chartAccounts.value.length) return
|
||||||
|
if (!Chart) Chart = (await import('chart.js/auto')).default
|
||||||
|
|
||||||
const months = data.value.months || []
|
const months = data.value.months || []
|
||||||
const labels = months.map(m => {
|
const labels = months.map(m => {
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ import { fetchTaxReport } from 'src/api/reports'
|
||||||
import { formatMoney } from 'src/composables/useFormatters'
|
import { formatMoney } from 'src/composables/useFormatters'
|
||||||
import { exportCsv } from 'src/composables/useCsvExport'
|
import { exportCsv } from 'src/composables/useCsvExport'
|
||||||
import ReportScaffold from 'src/components/shared/ReportScaffold.vue'
|
import ReportScaffold from 'src/components/shared/ReportScaffold.vue'
|
||||||
import Chart from 'chart.js/auto'
|
let Chart = null // chart.js ~90 KB → chargé à la demande au 1er rendu de graphique (hors du chunk de la page)
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const startDate = ref(new Date(now.getFullYear(), 0, 1).toISOString().slice(0, 10))
|
const startDate = ref(new Date(now.getFullYear(), 0, 1).toISOString().slice(0, 10))
|
||||||
|
|
@ -136,9 +136,10 @@ async function loadReport () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderChart () {
|
async function renderChart () {
|
||||||
if (chartInstance) chartInstance.destroy()
|
if (chartInstance) chartInstance.destroy()
|
||||||
if (!chartCanvas.value || !periods.value.length) return
|
if (!chartCanvas.value || !periods.value.length) return
|
||||||
|
if (!Chart) Chart = (await import('chart.js/auto')).default
|
||||||
const labels = periods.value.map(p => p.label)
|
const labels = periods.value.map(p => p.label)
|
||||||
chartInstance = new Chart(chartCanvas.value, {
|
chartInstance = new Chart(chartCanvas.value, {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
|
|
|
||||||
|
|
@ -73,12 +73,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ref, reactive, computed, onMounted, defineAsyncComponent } from 'vue'
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { listFlowTemplates, getFlowTemplate, updateFlowTemplate } from 'src/api/flow-templates'
|
import { listFlowTemplates, getFlowTemplate, updateFlowTemplate } from 'src/api/flow-templates'
|
||||||
import { HUB_URL } from 'src/config/hub'
|
import { HUB_URL } from 'src/config/hub'
|
||||||
import TaskDependencyGraph from 'src/components/flow-editor/TaskDependencyGraph.vue'
|
import TaskDependencyGraph from 'src/components/flow-editor/TaskDependencyGraph.vue'
|
||||||
import TaskGantt from 'src/components/flow-editor/TaskGantt.vue'
|
// Vue Gantt (hy-vue-gantt, lourd) rendu seulement dans la vue « gantt » → chargé à la demande.
|
||||||
|
const TaskGantt = defineAsyncComponent(() => import('src/components/flow-editor/TaskGantt.vue'))
|
||||||
|
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
const view = ref('dag')
|
const view = ref('dag')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user