# Cube Page Override

> 使用 @newlifex/cube-vue 的 Section 机制覆盖/扩展页面组件。 当用户说"覆盖页面"、"扩展页面"、"自定义组件"、"修改框架组件"时使用。 Section 机制允许在不修改框架源码的情况下，覆盖搜索栏、表格、操作按钮等页面区块。

- Skill: `newlifex/cube-page-override` (Agent Skill)
- Install (CLI): `npx skillmds@latest add newlifex/cube-page-override`
- Raw SKILL.md: https://api.skillmd.com/api/skills/newlifex/cube-page-override/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: newlifex (https://skillmd.com/u/newlifex)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/newlifex/cube-page-override

---


# @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 组件**

```vue
<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>
```

### 示例：覆盖工具栏

```vue
<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>
```

## 红线 / 禁止自行发挥

> 以下为历史踩坑固化的强制约束，**落实时严格照办，禁止凭记忆或"想当然"自行发挥**：

1. **Section 文件名必须 PascalCase 大写开头**：`ListSearchBar.vue`/`ListToolbar.vue` 合法；`index.vue`/`form.vue`/`listSearchBar.vue` 不被扫描（Vite 插件只收大写开头文件，且排除 `index.vue`）。命名错 = 覆盖不生效且无明显报错。
2. **Section 只能覆盖框架预设的覆盖点**：先用「常用 Section 覆盖点」表确认框架有对应槽位，再写组件；不要臆造不存在的 Section 名。
3. **样式规范（硬约束）**：Section 组件里也禁止硬编码色值、禁止私占 `--cube-layout-*`、禁止在 `scoped style` 覆盖 `--el-*`、禁止定义任何自定义 CSS 变量；统一用 `--el-*` token（如本技能示例中的 `.custom-search-bar`/`.custom-toolbar` 已改为 `var(--el-bg-color)`、`var(--el-border-color-lighter)` 等）。
4. **不要手写路由注册 Section**：Section 由 `initApp()` 的 `registerPageSections()` 自动注册（虚拟模块 `virtual:@newlifex/cube-vue-sections`），不要在 `routes.ts`/`main.ts` 里手动引入。
5. **与父组件通信只允许 `defineEmits` 约定事件**：不要直接改父组件内部状态，按示例用 `search`/`reset`/`refresh`/`open` 等事件回传。

## 与父组件通信

Section 组件通过 `defineEmits` 与父组件通信：

```typescript
// 定义事件
const emit = defineEmits<{
  search: [form: object];
  reset: [];
  refresh: [];
  open: [id: number];
}>();

// 触发事件
emit('search', searchForm);
emit('reset');
emit('refresh');
emit('open', 123);
```

## 自动注册

Vite 插件会自动扫描并注册 Section 组件：

1. 插件在构建时扫描 `views/` 目录
2. 收集所有以大写字母开头的 `.vue` 文件
3. 通过 `virtual:@newlifex/cube-vue-sections` 虚拟模块导出
4. `initApp()` 时自动调用 `registerPageSections()` 注册

## 调试 Section

确认 Section 是否生效：

1. 打开浏览器开发者工具
2. 在 Vue Devtools 中查看 `pageSections` 状态
3. 或检查控制台输出的 Section 注册信息

## 限制

- Section 组件只能覆盖框架预设的覆盖点
- 需要先确认框架是否有对应的覆盖点
- 组件名称必须严格遵守大写开头规则

## 示例：完整覆盖用户列表页

```
apps/<app-name>/src/views/User/
├── index.vue              # 主页面（由后端路由提供）
├── ListSearchBar.vue      # 覆盖搜索栏
├── ListToolbar.vue        # 覆盖工具栏
└── TableColumns.vue       # 覆盖表格列（如果支持）
```
