Skip to content

CAppLayout

Khung shell của ứng dụng: dải logo, sidebar, header, vùng nội dung cuộn và chân trang.

Dùng khi: dự án cần layout khác hẳn mặc định và muốn dựng lại từ các mảnh có sẵn.

Không dùng khi: layout mặc định đủ dùng — layer đã lắp sẵn CAppLayout + CSideNav + CTopNav, dự án chỉ khai appTitle, nav, topNav trong app.config.ts. Xem Khung giao diện.

Các slot

┌──────────────┬────────────────────────────────────┐
│   #logo      │  #header            #actions       │
├──────────────┼────────────────────────────────────┤
│   #nav       │  (slot mặc định — nội dung trang)  │
├──────────────┴────────────────────────────────────┤
│  #footer                                          │
└───────────────────────────────────────────────────┘
SlotNội dung thường đặt
#logoLogo dự án; bỏ trống thì hiện app-title
#navCSideNav
#headerCTopNav hoặc breadcrumb toàn cục
#actionsChuông thông báo, nút đổi sáng/tối, menu tài khoản
mặc địnhNội dung trang
#footerBỏ trống thì hiện footer-text

Trạng thái thu gọn

vue
<CAppLayout v-model:collapsed="collapsed" />

Sider rộng sider-width (mặc định 248px) khi mở và luôn 64px khi thu gọn. Trong app, layer giữ trạng thái ở useState('tasco:layout:collapsed') nên nó không bị mất khi chuyển trang.

Bỏ nút thu gọn ở dải logo bằng :collapsible="false".

Tự dựng layout cho dự án

vue
<!-- app/layouts/default.vue -->
<script setup lang="ts">
const appConfig = useAppConfig()
const route = useRoute()
const { filterByPermission } = usePermission()
const { isDark, toggle } = useThemeMode()
const collapsed = useState('tasco:layout:collapsed', () => false)

const nav = computed(() => filterByPermission(appConfig.tasco?.nav ?? []))
</script>

<template>
  <CAppLayout
    v-model:collapsed="collapsed"
    :app-title="appConfig.tasco?.appTitle"
    :footer-text="appConfig.tasco?.footerText"
  >
    <template #logo><img src="/logo.svg" alt="" height="28"></template>

    <template #nav>
      <CSideNav :items="nav" :active-path="route.path" :collapsed="collapsed" @navigate="navigateTo" />
    </template>

    <template #actions>
      <CButton variant="text" size="sm" @click="toggle">
        {{ isDark ? 'Sáng' : 'Tối' }}
      </CButton>
    </template>

    <NuxtErrorBoundary>
      <slot />
      <template #error="{ error }">
        <CEmpty :description="`Đã xảy ra lỗi: ${error?.message}`" bordered />
      </template>
    </NuxtErrorBoundary>
  </CAppLayout>
</template>

Ghi đè layout là nhận luôn trách nhiệm: lọc menu theo quyền, nút đổi sáng/tối, menu tài khoản và đăng xuất, và <NuxtErrorBoundary> quanh nội dung.

API

Props

TênKiểuMặc địnhMô tả
collapsedbooleanfalsev-model:collapsed — sider đang thu gọn hay không.
appTitlestring'Tasco'Tên ứng dụng hiện ở dải logo (khi không dùng slot #logo).
footerTextstring''Chữ chân trang; rỗng và không có slot #footer thì ẩn chân trang.
collapsiblebooleantrueHiện nút thu gọn/mở sider ở dải logo.
siderWidthnumber248Bề rộng sider khi mở (px); lúc thu gọn luôn là 64px.

Sự kiện

TênPayloadMô tả
update:collapsed[v: boolean]v-model:collapsed — phát khi bấm nút thu gọn/mở sider.

Slot

TênProps của slotMô tả
#logoanyLogo ở dải navy góc trên trái; mặc định hiện `appTitle`.
#headeranyNội dung bên trái header, vd menu module ngang (CTopNav).
#actionsanyKhu vực bên phải header: thông báo, tài khoản.
#navanyMenu sider — thường là CSideNav.
#defaultanyNội dung trang.
#footeranyChân trang; mặc định hiện `footerText`.

Khả năng tiếp cận

  • Nút thu gọn có nhãn đọc màn hình sẵn; nút tự thêm vào #actions thì phải tự đặt aria-label.
  • Vùng nội dung cuộn riêng, header và sider đứng yên — người dùng bàn phím Tab từ header xuống nội dung theo thứ tự tự nhiên.
  • Giữ một <h1> duy nhất cho mỗi trang, thường là CPageHeader trong vùng nội dung.

Lưu ý

  • Layout là một cho cả app: đừng lồng CAppLayout trong trang.
  • Nội dung trang tự lo khoảng đệm — layout không thêm padding vào slot mặc định.
  • Màn hình hẹp: sider thu về dải icon, nội dung vẫn cuộn bình thường; bảng rộng thì cho cuộn ngang thay vì ép co.

Liên quan