5-xnPageLayout 基于:心念后台

141 阅读2分钟

5、页面布局 xnPageLayout

本文基于开源项目 心念后台(xn-admin):Apache License 2.0 可商用,后端 Java 21 + Spring Boot 4 + Spring Cloud(网关 + 系统 / 文件 / 日志 / 任务),前端提供 React 19、Vue3+TS、Vue3+JS、Vue3 Options API 四套管理端,共用同一套 REST API。前文已介绍整体架构、技术栈与启动方式;本文从内部组件切入,讲 xnPageLayout(列表页骨架) 如何把搜索、按钮、表格、左侧树拼成一页,以及用法。演示:React · Vue3 TS。源码:后端 · React · Vue3+TS。 在这里插入图片描述

组件作用

xnPageLayout 是后台 CRUD 列表页的整页骨架。它不管业务数据,只规定:左边可以放树、上面放搜索、工具栏一行按钮、下面放表格(或卡片)。xnSearch、xnButton、xnTable、xnTreePanel 四个组件都往它的插槽里塞,各列表页看起来才整齐。

它解决的是:没有统一壳时,每页自己调 padding、滚动、工具栏对齐,时间一长就会乱。有它之后,业务页只填槽,版式由布局组件保证。

功能:

  • 有左侧插槽时左右分栏,没有则主区铺满
  • 插槽:左侧树、搜索、工具栏、工具栏右侧扩展、表格、卡片、分页、页脚
  • 同时提供表格和卡片插槽时,可切换两种视图;偏好按路由写入 localStorage,刷新还在
  • 分页可以放在布局层,列表页更常见的是放在 xnTable 内部
  • 纯前端,不请求任何接口

用在哪: 用户、角色、字典、日志、文件等绝大多数列表页的最外层包裹。前面四个组件仍然能单独用,但组合出现时都应套本布局。

前端依赖(npm)

栈依赖用途
Vue3 TSvue、element-plus卡片容器、分页、视图切换按钮
React TSreact、antdLayout 结构、Pagination、Segmented

纯布局,无图表、无拖拽库。视图模式只写浏览器 localStorage。

后端依赖(Maven)

无独立后端。 不请求接口,也不对应 Java 类。

运行时会读前端 appConfig(系统配置里的字号、标签栏高度等),那是全局配置,不是本组件的 API。

源码位置

路径均相对 xn-admin/。

Vue3 TSReact TS
组件xn-admin-vue3-ts/src/components/xnPageLayout/xn-admin-react-ts/src/components/XnPageLayout/

用法

<xnPageLayout>
  <template #aside>
    <xnTreePanel ... />
  </template>
  <template #search>
    <xnSearch ... />
  </template>
  <template #toolbar>
    <xnButton ... />
  </template>
  <template #table>
    <xnTable ... />
  </template>
</xnPageLayout>

截图

用户管理整页就是 xnPageLayout 把左树、搜索、按钮、表格拼在一起:

  • 截图/react-users-list.png / vue3-ts-users-list.png
  • 截图/react-dashboard.png:工作台(对照:不是列表壳)

源码(可直接粘贴 CSDN)

本组件无后端。Vue 用插槽,React 用 props 传节点,结构一致。

Vue 插槽骨架

<template>
  <div class="page-layout" :class="{ 'is-with-aside': hasAside }">
    <aside v-if="hasAside" class="page-layout__aside">
      <slot name="aside" />
    </aside>
    <div class="page-layout__right">
      <div v-if="$slots.search" class="page-layout__search">
        <slot name="search" />
      </div>
      <div v-if="hasToolbar" class="page-layout__toolbar">
        <slot name="toolbar" />
        <!-- 同时有 table + card 插槽才显示切换 -->
        <el-radio-group v-if="showViewSwitchActive" v-model="currentViewMode">
          <el-radio-button value="table">表格</el-radio-button>
          <el-radio-button value="card">卡片</el-radio-button>
        </el-radio-group>
      </div>
      <div class="page-layout__main">
        <div v-show="currentViewMode === 'table'"><slot name="table" /></div>
        <div v-show="currentViewMode === 'card'"><slot name="card" /></div>
      </div>
    </div>
  </div>
</template>

视图模式写入 localStorage,键为 xn-view-mode: + route.path,刷新不丢。

React 对照

interface XnPageLayoutProps {
  aside?: ReactNode
  search?: ReactNode
  toolbar?: ReactNode
  table?: ReactNode
  card?: ReactNode
  showViewSwitch?: boolean
  loading?: boolean
}

export default function XnPageLayout({ aside, search, toolbar, table, card }: XnPageLayoutProps) {
  const hasAside = Boolean(aside)
  return (
    <div className={hasAside ? 'page-layout is-with-aside' : 'page-layout'}>
      {aside ? <aside className="page-layout__aside">{aside}</aside> : null}
      <div className="page-layout__right">
        {search}
        {toolbar}
        {table}
      </div>
    </div>
  )
}

业务页拼起来

<xnPageLayout>
  <template #aside><xnTreePanel :data="tree" @node-click="onNode" /></template>
  <template #search><xnSearch :search-item="pageUi.searchItems" @query-form="load" /></template>
  <template #toolbar><xnButton :list-item="pageUi.buttonItems" :selected="selected" /></template>
  <template #table><xnTable :data="rows" :columns="columns" :total="total" /></template>
</xnPageLayout>