@newlifex/cube-vue 页面覆盖 (Section)
什么时候用
当用户需要修改/覆盖框架默认组件(如搜索栏、表格、分页),又不希望修改框架源码时使用。
Section 机制原理
┌─────────────────────────────────────────────────┐
│ 框架组件 │
│ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │
│ │ Header │ │ Content │ │ Footer │ │
│ └─────────┘ └─────────┘ └─────────────────┘ │
│ ↑ ↑ ↑ │
│ │ │ │ │
│ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ │
│ │Section│ │Section│ │Section│ │
│ │覆盖 │ │覆盖 │ │覆盖 │ │
│ └───────┘ └───────┘ └───────┘ │
└─────────────────────────────────────────────────┘
命名规则
| 规则 | 说明 |
|---|---|
| 大写开头 | 文件名必须以大写字母开头,如 ListSearchBar.vue |
| PascalCase | 多个单词时首字母都大写,如 UserDetailHeader.vue |
| 禁止小写 | index.vue、form.vue 等小写开头的文件不会被扫描 |
常用 Section 覆盖点
| Section 名称 | 覆盖位置 | 用途 |
|---|---|---|
ListSearchBar.vue |
列表页面搜索栏 | 自定义搜索条件 |
ListToolbar.vue |
列表工具栏 | 添加自定义按钮 |
TableColumns.vue |
表格列配置 | 修改列定义 |
FormFields.vue |
表单字段 | 自定义表单控件 |
DetailHeader.vue |
详情页头部 | 自定义详情头部 |
Pagination.vue |
分页组件 | 自定义分页样式 |
创建 Section 组件
示例:覆盖列表页搜索栏
1. 在应用的 views 目录下创建
apps/<app-name>/src/views/
└── User/
├── index.vue # 列表主页面
└── ListSearchBar.vue # 搜索栏覆盖组件
2. 编写 Section 组件
<template>
<div class="custom-search-bar">
<el-row :gutter="16">
<el-col :span="8">
<el-input
v-model="searchForm.name"
placeholder="请输入用户名"
clearable
/>
</el-col>
<el-col :span="8">
<el-select v-model="searchForm.status" placeholder="请选择状态">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-col>
<el-col :span="8">
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置</el-button>
<el-button type="success" @click="handleExport">导出</el-button>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
// 搜索表单
const searchForm = reactive({
name: '',
status: undefined as number | undefined,
});
// 搜索事件 - 触发父组件刷新
const emit = defineEmits<{
search: [form: typeof searchForm];
reset: [];
}>();
function handleSearch() {
emit('search', searchForm);
}
function handleReset() {
searchForm.name = '';
searchForm.status = undefined;
emit('reset');
}
function handleExport() {
// 自定义导出逻辑
console.log('导出数据');
}
</script>
<style scoped>
.custom-search-bar {
padding: 16px;
background: var(--el-bg-color);
border-radius: var(--el-border-radius-base);
}
</style>
示例:覆盖工具栏
<template>
<div class="custom-toolbar">
<el-space>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增用户
</el-button>
<el-button @click="handleBatchImport">
<el-icon><Upload /></el-icon>
批量导入
</el-button>
<el-button type="danger" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>
批量删除
</el-button>
</el-space>
</div>
</template>
<script setup lang="ts">
import { Plus, Upload, Delete } from '@element-plus/icons-vue';
function handleAdd() {
// 新增逻辑
}
function handleBatchImport() {
// 批量导入逻辑
}
function handleBatchDelete() {
// 批量删除逻辑
}
</script>
<style scoped>
.custom-toolbar {
padding: 12px 16px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
</style>
红线 / 禁止自行发挥
以下为历史踩坑固化的强制约束,落实时严格照办,禁止凭记忆或"想当然"自行发挥:
- Section 文件名必须 PascalCase 大写开头:
ListSearchBar.vue/ListToolbar.vue合法;index.vue/form.vue/listSearchBar.vue不被扫描(Vite 插件只收大写开头文件,且排除index.vue)。命名错 = 覆盖不生效且无明显报错。 - Section 只能覆盖框架预设的覆盖点:先用「常用 Section 覆盖点」表确认框架有对应槽位,再写组件;不要臆造不存在的 Section 名。
- 样式规范(硬约束):Section 组件里也禁止硬编码色值、禁止私占
--cube-layout-*、禁止在scoped style覆盖--el-*、禁止定义任何自定义 CSS 变量;统一用--el-*token(如本技能示例中的.custom-search-bar/.custom-toolbar已改为var(--el-bg-color)、var(--el-border-color-lighter)等)。 - 不要手写路由注册 Section:Section 由
initApp()的registerPageSections()自动注册(虚拟模块virtual:@newlifex/cube-vue-sections),不要在routes.ts/main.ts里手动引入。 - 与父组件通信只允许
defineEmits约定事件:不要直接改父组件内部状态,按示例用search/reset/refresh/open等事件回传。
与父组件通信
Section 组件通过 defineEmits 与父组件通信:
// 定义事件
const emit = defineEmits<{
search: [form: object];
reset: [];
refresh: [];
open: [id: number];
}>();
// 触发事件
emit('search', searchForm);
emit('reset');
emit('refresh');
emit('open', 123);
自动注册
Vite 插件会自动扫描并注册 Section 组件:
- 插件在构建时扫描
views/目录 - 收集所有以大写字母开头的
.vue文件 - 通过
virtual:@newlifex/cube-vue-sections虚拟模块导出 initApp()时自动调用registerPageSections()注册
调试 Section
确认 Section 是否生效:
- 打开浏览器开发者工具
- 在 Vue Devtools 中查看
pageSections状态 - 或检查控制台输出的 Section 注册信息
限制
- Section 组件只能覆盖框架预设的覆盖点
- 需要先确认框架是否有对应的覆盖点
- 组件名称必须严格遵守大写开头规则
示例:完整覆盖用户列表页
apps/<app-name>/src/views/User/
├── index.vue # 主页面(由后端路由提供)
├── ListSearchBar.vue # 覆盖搜索栏
├── ListToolbar.vue # 覆盖工具栏
└── TableColumns.vue # 覆盖表格列(如果支持)