1242 lines
40 KiB
Markdown
1242 lines
40 KiB
Markdown
|
|
# sct-base-table 组件架构重构方案(方案 B)
|
|||
|
|
|
|||
|
|
> 设计人:前端团队
|
|||
|
|
> 日期:2026-05-26
|
|||
|
|
> 状态:待评审(RFC)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
|
|||
|
|
1. [背景与现状](#1-背景与现状)
|
|||
|
|
2. [方案总览](#2-方案总览)
|
|||
|
|
3. [架构对照:旧 vs 新](#3-架构对照旧-vs-新)
|
|||
|
|
4. [新组件详细设计](#4-新组件详细设计)
|
|||
|
|
5. [页面迁移对照示例](#5-页面迁移对照示例)
|
|||
|
|
6. [文件清单](#6-文件清单)
|
|||
|
|
7. [风险与回滚策略](#7-风险与回滚策略)
|
|||
|
|
8. [工时估算](#8-工时估算)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 背景与现状
|
|||
|
|
|
|||
|
|
### 1.1 当前使用规模
|
|||
|
|
|
|||
|
|
`src/components/sct-base-table/index.vue` 是一个基于 `el-table` 的二次封装组件,在旧项目中**被 100+ 个页面引用**,是使用频率最高的公共组件。
|
|||
|
|
|
|||
|
|
### 1.2 现有问题
|
|||
|
|
|
|||
|
|
| # | 问题 | 严重程度 | 说明 |
|
|||
|
|
|---|------|:---:|------|
|
|||
|
|
| 1 | 引入了 `yargs`(Node.js CLI 库)到浏览器代码 | 🔴 致命 | `import { boolean } from 'yargs'` 会导致打包体积膨胀,且 yargs 依赖 Node API |
|
|||
|
|
| 2 | 60% CSS 为注释掉的死代码 | 🔴 | 100+ 行 `/* ... */` 注释占据组件内容 |
|
|||
|
|
| 3 | 包含备份文件 `index - 副本.vue` | 🔴 | 源码应无备份文件 |
|
|||
|
|
| 4 | `v-bind="$attrs"` 无白名单过滤 | 🟡 | 任何父组件属性都会污染 el-table DOM |
|
|||
|
|
| 5 | 按钮栏与表格强耦合 | 🟡 | 无法单独复用表格或按钮栏 |
|
|||
|
|
| 6 | 每个页面手动分配 `idx` 序号 | 🟡 | 100+ 个页面都在重复 `{ idx: 0, attrs: {...} }` |
|
|||
|
|
| 7 | 每个页面重复定义 `buttonList` / `tableButtonList` | 🟡 | 结构完全相同,仅在权限和回调上有差异 |
|
|||
|
|
| 8 | `__judge` 命名不规范 | 🟢 | 双下划线命名、语义不清 |
|
|||
|
|
| 9 | 无分页能力 | 🟡 | 100+ 页面需要额外包装 `<el-pagination>` |
|
|||
|
|
| 10 | 与 `sct-base-dialog`、`sct-base-form`、`sct-back-to-top` 四件套重复组合 | 🟡 | 每个页面都要手动组装这四个组件 |
|
|||
|
|
|
|||
|
|
### 1.3 当前使用模式
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
每个页面的 PageMain 组件:
|
|||
|
|
├── <sct-base-table> ← 表格(含顶部按钮栏)
|
|||
|
|
├── <sct-base-dialog> ← 新增/编辑弹框
|
|||
|
|
│ └── <SctBaseForm> ← 表单
|
|||
|
|
└── <sct-back-to-top> ← 回到顶部
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 方案总览
|
|||
|
|
|
|||
|
|
### 2.1 核心理念
|
|||
|
|
|
|||
|
|
**从「一个万能组件」拆分为「组合式工具函数 + 职责单一的组件」。**
|
|||
|
|
|
|||
|
|
### 2.2 三层架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 组合层:各页面 PageMain 组件 │
|
|||
|
|
│ 通过 <page-table> 或 <sct-table> + <sct-toolbar> 自由组合 │
|
|||
|
|
├─────────────────────────────────────────────────────────────┤
|
|||
|
|
│ 组件层:8 个职责单一的 UI 组件 │
|
|||
|
|
│ sct-table / sct-toolbar / sct-action-buttons / sct-expand │
|
|||
|
|
│ sct-batch-actions / page-dialog-form / page-search / │
|
|||
|
|
│ page-table(便捷组合体) │
|
|||
|
|
├─────────────────────────────────────────────────────────────┤
|
|||
|
|
│ 逻辑层:5 个可复用的 composable 函数 │
|
|||
|
|
│ useTableColumns / useTableButtons / useTableSelection / │
|
|||
|
|
│ usePagination / usePageTable │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 改造范围
|
|||
|
|
|
|||
|
|
| 不改造 | 改造 |
|
|||
|
|
|--------|------|
|
|||
|
|
| API 调用的业务逻辑 (methods) | 表格/按钮/列的组装方式 |
|
|||
|
|
| sct-base-form(表单组件) | sct-base-table → 拆为多个小组件 |
|
|||
|
|
| sct-base-dialog(弹框组件) | 页面模板结构 |
|
|||
|
|
| sct-back-to-top | columns/buttonList 的定义方式 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 架构对照:旧 vs 新
|
|||
|
|
|
|||
|
|
### 3.1 组件拆分对照
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
旧架构(一个组件包揽一切):
|
|||
|
|
sct-base-table/index.vue
|
|||
|
|
├── 模板:按钮栏+表格(215 行)
|
|||
|
|
├── 逻辑:权限校验+按钮显隐+表头样式
|
|||
|
|
└── 样式:60% 注释掉
|
|||
|
|
|
|||
|
|
新架构(按职责拆分):
|
|||
|
|
src/
|
|||
|
|
├── components/
|
|||
|
|
│ ├── sct-table/index.vue 纯表格封装(~40 行)
|
|||
|
|
│ ├── sct-toolbar/index.vue 顶部工具栏(按钮+筛选,~60 行)
|
|||
|
|
│ ├── sct-action-buttons/index.vue 行内操作按钮(~30 行)
|
|||
|
|
│ ├── sct-expand/index.vue 展开行内容(~40 行)
|
|||
|
|
│ ├── sct-batch-actions/index.vue 批量操作栏(~30 行)
|
|||
|
|
│ ├── page-dialog-form/index.vue 增删改查弹框一体(~80 行)
|
|||
|
|
│ └── page-table/index.vue 便捷组合体(表格+工具栏+分页,~80 行)
|
|||
|
|
└── composables/
|
|||
|
|
├── useTableColumns.js 列定义工厂(~30 行)
|
|||
|
|
├── useTableButtons.js 按钮定义工厂(~50 行)
|
|||
|
|
├── useTableSelection.js 选中行管理(~30 行)
|
|||
|
|
├── usePagination.js 分页逻辑(~40 行)
|
|||
|
|
└── usePageTable.js 表格数据+分页+刷新(~60 行)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 数据流对照
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
旧模式(单向强耦合):
|
|||
|
|
data() 手写 { idx, attrs: { prop, label, ... } } → columns prop → el-table-column 遍历
|
|||
|
|
|
|||
|
|
新模式(声明式 + 自动推导):
|
|||
|
|
columns: [{ prop: 'code', label: '编码' }] → composable 自动补全 idx → sct-table
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 新组件详细设计
|
|||
|
|
|
|||
|
|
### 4.1 `sct-table` — 纯表格封装
|
|||
|
|
|
|||
|
|
**定位**:最底层的表格组件,只负责渲染列和数据,没有任何业务逻辑。
|
|||
|
|
|
|||
|
|
**Props:**
|
|||
|
|
|
|||
|
|
| Prop | 类型 | 默认值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| columns | Array | `[]` | 列定义列表,直接透传 el-table-column 属性 |
|
|||
|
|
| data | Array | `[]` | 表格数据 |
|
|||
|
|
| loading | Boolean | false | 加载状态 |
|
|||
|
|
| height | String/Number | — | 表格高度 |
|
|||
|
|
| border | Boolean | true | 是否带边框 |
|
|||
|
|
| rowKey | String | `'id'` | 行数据的 Key |
|
|||
|
|
| expandable | Boolean | false | 是否启用展开行 |
|
|||
|
|
|
|||
|
|
**Slots:**
|
|||
|
|
|
|||
|
|
| 插槽名 | 作用 | 作用域 |
|
|||
|
|
|--------|------|--------|
|
|||
|
|
| `#col-{prop}` | 自定义列的默认插槽 | `{ row, $index }` |
|
|||
|
|
| `#col-{prop}-header` | 自定义列表头 | `{ column }` |
|
|||
|
|
| `#expand` | 展开行内容 | `{ row, $index }` |
|
|||
|
|
| `#empty` | 空数据占位 | — |
|
|||
|
|
| `#append` | 表格最后一行追加内容 | — |
|
|||
|
|
|
|||
|
|
**事件:** 与 `el-table` 一致,透传 `@selection-change`、`@sort-change` 等。
|
|||
|
|
|
|||
|
|
**示例:**
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<sct-table
|
|||
|
|
:columns="columns"
|
|||
|
|
:data="tableData"
|
|||
|
|
:loading="loading"
|
|||
|
|
expandable
|
|||
|
|
@selection-change="onSelect"
|
|||
|
|
>
|
|||
|
|
<template #col-status="{ row }">
|
|||
|
|
<el-tag :type="row.status === 1 ? 'success' : 'info'">
|
|||
|
|
{{ row.status === 1 ? '启用' : '禁用' }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
<template #col-actions="{ row }">
|
|||
|
|
<sct-action-buttons :row="row" :buttons="rowButtons" />
|
|||
|
|
</template>
|
|||
|
|
<template #expand="{ row }">
|
|||
|
|
<sct-expand :row="row" :fields="expandFields" />
|
|||
|
|
</template>
|
|||
|
|
</sct-table>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.2 `sct-toolbar` — 顶部工具栏
|
|||
|
|
|
|||
|
|
**定位**:表格上方的操作按钮区域,支持权限控制、loading、下拉菜单分组。
|
|||
|
|
|
|||
|
|
**Props:**
|
|||
|
|
|
|||
|
|
| Prop | 类型 | 默认值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| buttons | Array | `[]` | 按钮列表(定义见下方) |
|
|||
|
|
| selectedCount | Number | 0 | 当前选中行数 |
|
|||
|
|
| showSelectedHint | Boolean | true | 是否显示"已选 N 项"文字 |
|
|||
|
|
|
|||
|
|
**按钮定义结构:**
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
const toolbarButtons = [
|
|||
|
|
{
|
|||
|
|
key: 'add',
|
|||
|
|
label: '新增',
|
|||
|
|
icon: 'el-icon-plus',
|
|||
|
|
type: 'primary',
|
|||
|
|
auth: '/xxx/create',
|
|||
|
|
onClick: handleAdd,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: 'delete',
|
|||
|
|
label: '批量删除',
|
|||
|
|
icon: 'el-icon-delete',
|
|||
|
|
type: 'danger',
|
|||
|
|
auth: '/xxx/batch-delete',
|
|||
|
|
needSelection: true, // 需要选中行才能点击
|
|||
|
|
confirmMsg: '确认删除选中项?',
|
|||
|
|
onClick: handleBatchDelete,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: 'export',
|
|||
|
|
label: '导出',
|
|||
|
|
icon: 'el-icon-download',
|
|||
|
|
type: 'success',
|
|||
|
|
auth: '/xxx/export',
|
|||
|
|
onClick: handleExport,
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**示例:**
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<sct-toolbar
|
|||
|
|
:buttons="toolbarButtons"
|
|||
|
|
:selected-count="selectedRows.length"
|
|||
|
|
/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.3 `sct-action-buttons` — 行内操作按钮
|
|||
|
|
|
|||
|
|
**定位**:表格每行末尾的"编辑/删除/查看"等操作按钮。
|
|||
|
|
|
|||
|
|
**Props:**
|
|||
|
|
|
|||
|
|
| Prop | 类型 | 默认值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| row | Object | — | 当前行数据 |
|
|||
|
|
| buttons | Array | `[]` | 按钮列表 |
|
|||
|
|
| maxVisible | Number | 3 | 超过此数量折叠到"更多"下拉菜单 |
|
|||
|
|
|
|||
|
|
**按钮定义结构:**
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
const rowButtons = [
|
|||
|
|
{ key: 'edit', label: '编辑', icon: 'el-icon-edit', auth: '/xxx/edit', onClick: handleEdit },
|
|||
|
|
{ key: 'delete', label: '删除', icon: 'el-icon-delete', color: 'danger', auth: '/xxx/delete', confirm: true, onClick: handleDelete },
|
|||
|
|
{ key: 'print', label: '打印', icon: 'el-icon-printer', auth: '/xxx/print', onClick: handlePrint },
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.4 `sct-expand` — 展开行内容
|
|||
|
|
|
|||
|
|
**定位**:表格展开行中以 form 布局展示的详情信息。
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<sct-expand :row="row" :fields="[
|
|||
|
|
{ label: '长度(m)', prop: 'length' },
|
|||
|
|
{ label: '宽度(m)', prop: 'width' },
|
|||
|
|
{ label: '高度(m)', prop: 'height' },
|
|||
|
|
{ label: '容积(m³)', prop: 'volume' },
|
|||
|
|
]" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.5 `page-table` — 便捷组合体(向下兼容)
|
|||
|
|
|
|||
|
|
**定位**:为快速迁移提供一个与旧组件心智模型接近的便捷组合体。在 `sct-table` 基础上内置了工具栏和分页。
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<page-table
|
|||
|
|
:columns="columns"
|
|||
|
|
:data="tableData"
|
|||
|
|
:loading="loading"
|
|||
|
|
:toolbar-buttons="toolbarButtons"
|
|||
|
|
:pagination="pagination"
|
|||
|
|
:row-buttons="rowButtons"
|
|||
|
|
@action="onToolbarAction"
|
|||
|
|
@row-action="onRowAction"
|
|||
|
|
@page-change="onPageChange"
|
|||
|
|
@selection-change="onSelect"
|
|||
|
|
>
|
|||
|
|
<!-- 自定义列插槽透传至 sct-table -->
|
|||
|
|
<template #col-status="{ row }">
|
|||
|
|
<el-tag>...</el-tag>
|
|||
|
|
</template>
|
|||
|
|
<template #expand="{ row }">
|
|||
|
|
<sct-expand ... />
|
|||
|
|
</template>
|
|||
|
|
</page-table>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`page-table` 只服务于"标准 CRUD 页面"场景(占比 ~80% 的页面)。特殊页面(如检验单管理那种自定义工具栏)直接使用 `sct-table` + `sct-toolbar` 组合。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.6 Composable 层:消除重复代码
|
|||
|
|
|
|||
|
|
#### `useTableColumns(columns)`
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 旧方式:每个页面手写
|
|||
|
|
this.columns = [
|
|||
|
|
{ idx: 0, attrs: { prop: 'sort', label: '序号' } },
|
|||
|
|
{ idx: 1, attrs: { prop: 'code', label: '编码' } },
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 新方式:声明列定义,idx 自动生成
|
|||
|
|
import { useTableColumns } from '@/composables/useTableColumns'
|
|||
|
|
|
|||
|
|
const columns = useTableColumns([
|
|||
|
|
{ prop: 'sort', label: '序号', width: 80 },
|
|||
|
|
{ prop: 'code', label: '编码', minWidth: 120 },
|
|||
|
|
{ prop: 'name', label: '名称' },
|
|||
|
|
{ prop: '_actions', label: '操作', width: 200, fixed: 'right' },
|
|||
|
|
])
|
|||
|
|
// 自动补齐 idx,_actions 约定为操作列插槽
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### `useTableButtons(...)`
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 生成 toolbar 按钮 + row 按钮,自动注入权限判断
|
|||
|
|
const { toolbarButtons, rowButtons } = useTableButtons({
|
|||
|
|
toolbar: [
|
|||
|
|
{ key: 'add', label: '新增', ... },
|
|||
|
|
],
|
|||
|
|
row: [
|
|||
|
|
{ key: 'edit', label: '编辑', ... },
|
|||
|
|
],
|
|||
|
|
}, permissionCheck)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### `usePagination()`
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
const { pagination, onPageChange, resetPage } = usePagination({
|
|||
|
|
defaultPageSize: 20,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### `usePageTable(fetchFn)`
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 封装表格数据 + 分页 + loading + 刷新
|
|||
|
|
const { data, loading, pagination, refresh, search } = usePageTable(fetchList)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 页面迁移对照示例
|
|||
|
|
|
|||
|
|
### 5.1 标准 CRUD 页面(产线管理)
|
|||
|
|
|
|||
|
|
> 源文件:`views/production_configuration/factory_model/factory_line/components/PageMain/index.vue`
|
|||
|
|
|
|||
|
|
#### 旧代码(简化)
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div v-loading="loading">
|
|||
|
|
<sct-base-table
|
|||
|
|
ref="sctBaseTable"
|
|||
|
|
:columns="columns"
|
|||
|
|
:data="currentTableData"
|
|||
|
|
:button-list="buttonList"
|
|||
|
|
@selection-change="handleSelectionChange">
|
|||
|
|
<template #handle="{ scope: { row, $index } }">
|
|||
|
|
<strong v-for="(item, index) in tableButtonList" :key="index">
|
|||
|
|
<i :class="item.icon" :style="item.cssStyle"
|
|||
|
|
v-if="$permission(item.auth)"
|
|||
|
|
@click="item.handle(row, $index)">{{ $t(item.label) }}</i>
|
|||
|
|
</strong>
|
|||
|
|
</template>
|
|||
|
|
</sct-base-table>
|
|||
|
|
<sct-base-dialog ...>
|
|||
|
|
<SctBaseForm ... />
|
|||
|
|
</sct-base-dialog>
|
|||
|
|
<sct-back-to-top />
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
export default {
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
currentTableData: [],
|
|||
|
|
columns: [], // ← 手动构建
|
|||
|
|
buttonList: [...], // ← 重复定义
|
|||
|
|
tableButtonList: [...],
|
|||
|
|
// ... 弹框相关 fields
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
mounted() {
|
|||
|
|
this.getColumns()
|
|||
|
|
this.getFormData()
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
getColumns() {
|
|||
|
|
this.columns = [
|
|||
|
|
{ idx: 0, attrs: { prop: 'sort', label: '排序' } },
|
|||
|
|
{ idx: 1, attrs: { prop: 'code', label: '编码' } },
|
|||
|
|
{ idx: 2, attrs: { prop: 'name', label: '名称' } },
|
|||
|
|
{ idx: 3, slot: 'handle', attrs: { label: '操作' } },
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
// ... 20+ 个方法
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 新代码(使用 `page-table` 便捷组合体)
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<page-table
|
|||
|
|
:columns="columns"
|
|||
|
|
:data="tableData"
|
|||
|
|
:loading="loading"
|
|||
|
|
:toolbar-buttons="toolbarButtons"
|
|||
|
|
:row-buttons="rowButtons"
|
|||
|
|
:pagination="pagination"
|
|||
|
|
@action="onToolbarAction"
|
|||
|
|
@row-action="onRowAction"
|
|||
|
|
@page-change="onPageChange"
|
|||
|
|
@selection-change="onSelect"
|
|||
|
|
/>
|
|||
|
|
<page-dialog-form
|
|||
|
|
v-model="dialogVisible"
|
|||
|
|
:title="dialogTitle"
|
|||
|
|
:form-cols="formCols"
|
|||
|
|
:form-data="formData"
|
|||
|
|
:rules="rules"
|
|||
|
|
@submit="handleSubmit"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import { useTableColumns } from '@/composables/useTableColumns'
|
|||
|
|
import { useTableButtons } from '@/composables/useTableButtons'
|
|||
|
|
import { usePageTable } from '@/composables/usePageTable'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
setup() {
|
|||
|
|
// 列定义 —— 一次声明,永不手动分配 idx
|
|||
|
|
const columns = useTableColumns([
|
|||
|
|
{ prop: 'sort', label: '排序', width: 80 },
|
|||
|
|
{ prop: 'code', label: '产线编码', minWidth: 120 },
|
|||
|
|
{ prop: 'name', label: '产线名称' },
|
|||
|
|
{ prop: 'area_name', label: '所属区域' },
|
|||
|
|
{ prop: 'remark', label: '备注' },
|
|||
|
|
{ prop: '_actions', label: '操作', width: 180, fixed: 'right' },
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
// 按钮定义 —— 不用再写 tableButtonList 和 buttonList 两套了
|
|||
|
|
const { toolbarButtons, rowButtons } = useTableButtons({
|
|||
|
|
toolbar: [
|
|||
|
|
{ key: 'add', label: '新增', type: 'primary', icon: 'el-icon-plus', auth: 'create', onClick: openDialog },
|
|||
|
|
],
|
|||
|
|
row: [
|
|||
|
|
{ key: 'edit', label: '编辑', icon: 'el-icon-edit', auth: 'edit', onClick: handleEdit },
|
|||
|
|
{ key: 'delete', label: '删除', icon: 'el-icon-delete', color: 'danger', auth: 'delete', confirm: true, onClick: handleDelete },
|
|||
|
|
],
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 表格数据 + 分页
|
|||
|
|
const { data: tableData, loading, pagination, refresh } = usePageTable(fetchLineList)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
columns,
|
|||
|
|
toolbarButtons,
|
|||
|
|
rowButtons,
|
|||
|
|
tableData,
|
|||
|
|
loading,
|
|||
|
|
pagination,
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 复杂自定义页面(检验单管理)
|
|||
|
|
|
|||
|
|
> 源文件:`views/quality_control/xqc/inspection_order_manage/components/PageMain/index.vue`
|
|||
|
|
|
|||
|
|
这个页面工具栏有 10 个自定义按钮 + 列筛选器 + 多处自定义事件,旧代码直接用了 `vxe-table` 绕过了 `sct-base-table`。
|
|||
|
|
|
|||
|
|
#### 新代码(使用底层组件自由组合)
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div>
|
|||
|
|
<!-- 自定义工具栏 -->
|
|||
|
|
<sct-toolbar :buttons="toolbarButtons" :selected-count="selectedCount">
|
|||
|
|
<template #extra>
|
|||
|
|
<el-popover placement="bottom-start" width="280" trigger="click">
|
|||
|
|
<div v-for="item in columnPickerItems" :key="item.key" class="col-picker-row">
|
|||
|
|
<el-checkbox v-model="columnVisible[item.key]">{{ item.label }}</el-checkbox>
|
|||
|
|
</div>
|
|||
|
|
<el-button slot="reference" size="mini" icon="el-icon-s-operation">列显示</el-button>
|
|||
|
|
</el-popover>
|
|||
|
|
</template>
|
|||
|
|
</sct-toolbar>
|
|||
|
|
|
|||
|
|
<!-- 纯表格(vxe-table 或 el-table) -->
|
|||
|
|
<vxe-table
|
|||
|
|
ref="xTable"
|
|||
|
|
border resizable show-header-overflow
|
|||
|
|
:data="tableData"
|
|||
|
|
:height="tableHeight"
|
|||
|
|
@checkbox-change="syncCheckbox"
|
|||
|
|
>
|
|||
|
|
<vxe-column type="checkbox" width="55" fixed="left" />
|
|||
|
|
<vxe-column
|
|||
|
|
v-for="col in visibleColumns"
|
|||
|
|
:key="col.field"
|
|||
|
|
v-bind="col"
|
|||
|
|
/>
|
|||
|
|
</vxe-table>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 带展开行的页面(库位管理)
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<page-table
|
|||
|
|
:columns="columns"
|
|||
|
|
:data="tableData"
|
|||
|
|
:toolbar-buttons="toolbarButtons"
|
|||
|
|
:row-buttons="rowButtons"
|
|||
|
|
:pagination="pagination"
|
|||
|
|
>
|
|||
|
|
<template #col-status="{ row }">
|
|||
|
|
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
|||
|
|
{{ row.status === 1 ? '启用' : '禁用' }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
<template #expand="{ row }">
|
|||
|
|
<sct-expand :row="row" :fields="expandFields" />
|
|||
|
|
</template>
|
|||
|
|
</page-table>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 文件清单
|
|||
|
|
|
|||
|
|
### 6.1 新增文件
|
|||
|
|
|
|||
|
|
| 文件 | 类型 | 行数估计 | 说明 |
|
|||
|
|
|------|:---:|:---:|------|
|
|||
|
|
| `src/composables/useTableColumns.js` | 逻辑 | ~40 | 列定义工厂,自动补 idx |
|
|||
|
|
| `src/composables/useTableButtons.js` | 逻辑 | ~60 | 按钮定义工厂,自动权限过滤 |
|
|||
|
|
| `src/composables/useTableSelection.js` | 逻辑 | ~35 | 选中行管理 |
|
|||
|
|
| `src/composables/usePagination.js` | 逻辑 | ~50 | 分页逻辑 |
|
|||
|
|
| `src/composables/usePageTable.js` | 逻辑 | ~70 | 表格数据+分页+刷新一站式 |
|
|||
|
|
| `src/composables/index.js` | 逻辑 | ~10 | 统一导出 |
|
|||
|
|
| `src/components/sct-table/index.vue` | 组件 | ~60 | 纯表格 |
|
|||
|
|
| `src/components/sct-toolbar/index.vue` | 组件 | ~80 | 工具栏 |
|
|||
|
|
| `src/components/sct-action-buttons/index.vue` | 组件 | ~50 | 行内操作按钮 |
|
|||
|
|
| `src/components/sct-expand/index.vue` | 组件 | ~50 | 展开行详情 |
|
|||
|
|
| `src/components/sct-batch-actions/index.vue` | 组件 | ~40 | 批量操作栏 |
|
|||
|
|
| `src/components/page-dialog-form/index.vue` | 组件 | ~100 | 增删改查弹框一体 |
|
|||
|
|
| `src/components/page-table/index.vue` | 组件 | ~100 | 便捷组合体(80% 页面用这个) |
|
|||
|
|
|
|||
|
|
**总计:约 745 行新代码**(旧 sct-base-table 215 行,删除 ~100 行死代码后实际上只有 ~100 行有效代码)
|
|||
|
|
|
|||
|
|
### 6.2 修改文件
|
|||
|
|
|
|||
|
|
| 文件 | 修改内容 |
|
|||
|
|
|------|---------|
|
|||
|
|
| `src/components/sct-base-table/index.vue` | 删除或标记 `@deprecated`,重定向到新组件 |
|
|||
|
|
| 100+ 个 PageMain/index.vue | 按页面类型批量替换为 page-table / sct-table+toolbar |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 风险与回滚策略
|
|||
|
|
|
|||
|
|
### 7.1 风险
|
|||
|
|
|
|||
|
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
|||
|
|
|------|:---:|:---:|------|
|
|||
|
|
| 新组件 bug 影响线上 | 低 | 高 | 分模块灰度迁移,先迁移"系统设置"模块验证 |
|
|||
|
|
| 性能回退(多一层组件) | 极低 | 低 | 新组件均为无渲染函数式组件,零额外开销 |
|
|||
|
|
| 开发者学习成本 | 中 | 中 | `page-table` API 刻意靠近旧组件,降低迁移心智负担 |
|
|||
|
|
|
|||
|
|
### 7.2 回滚策略
|
|||
|
|
|
|||
|
|
- 旧 `sct-base-table` 保留不删,标记 `@deprecated`
|
|||
|
|
- 每个模块迁移后在路由配置中可独立切回旧组件
|
|||
|
|
- 使用 Vue 的 `defineAsyncComponent` 做新旧切换的 feature flag
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 工时估算
|
|||
|
|
|
|||
|
|
| 阶段 | 内容 | 预估时间 |
|
|||
|
|
|------|------|:---:|
|
|||
|
|
| 阶段 0 | 创建 composables + 核心组件 | 0.5 天 |
|
|||
|
|
| 阶段 1 | 创建 page-table 便捷组合体 | 0.5 天 |
|
|||
|
|
| 阶段 2 | 迁移「系统设置」模块(6 页)验证 | 0.5 天 |
|
|||
|
|
| 阶段 3 | 迁移「生产配置」(15 页) | 1 天 |
|
|||
|
|
| 阶段 4 | 迁移「设备模型」(14 页) | 1 天 |
|
|||
|
|
| 阶段 5 | 迁移「计划与生产」(10 页) | 1 天 |
|
|||
|
|
| 阶段 6 | 迁移「质量管理」(26 页) | 2 天 |
|
|||
|
|
| 阶段 7 | 迁移「数据中台」(8 页) | 0.5 天 |
|
|||
|
|
| 阶段 8 | 迁移「仓储管理」(25 页) | 2 天 |
|
|||
|
|
| 阶段 9 | 迁移「SCADA 管理」(10 页) | 1 天 |
|
|||
|
|
| 阶段 10 | 迁移特殊页面(检验单、鹰眼等) | 1 天 |
|
|||
|
|
| **合计** | | **约 11 天** |
|
|||
|
|
|
|||
|
|
> 注:与搬迁工作并行进行,每个模块搬迁时顺便替换。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附录 A:完整的 buttonList 到 toolbarButtons 映射
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 旧 buttonList 结构
|
|||
|
|
buttonList: [
|
|||
|
|
{
|
|||
|
|
label: '新增',
|
|||
|
|
size: 'mini',
|
|||
|
|
icon: 'el-icon-plus',
|
|||
|
|
type: 'primary',
|
|||
|
|
auth: '/xxx/create',
|
|||
|
|
handle: this.openDialog,
|
|||
|
|
cssStyle: { background: '#3CBA92' },
|
|||
|
|
noShow: this.someCondition,
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
// 新 toolbarButtons 结构
|
|||
|
|
toolbarButtons: [
|
|||
|
|
{
|
|||
|
|
key: 'add',
|
|||
|
|
label: this.$t('新增'),
|
|||
|
|
icon: 'el-icon-plus',
|
|||
|
|
type: 'primary',
|
|||
|
|
auth: '/xxx/create',
|
|||
|
|
onClick: openDialog,
|
|||
|
|
// cssStyle 通过 type 预设色系,自定义色系用 color prop
|
|||
|
|
visible: computed(() => someCondition),
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 附录 B:完整的 columns 结构变化
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 旧 —— 命令式
|
|||
|
|
{ idx: 0, attrs: { prop: 'sort', label: '排序' } }
|
|||
|
|
{ idx: 5, slot: 'handle', attrs: { label: '操作' } }
|
|||
|
|
{ idx: 3, slot: 'status', headerSlot: 'status_header', attrs: { prop: 'status', label: '状态' } }
|
|||
|
|
|
|||
|
|
// 新 —— 声明式
|
|||
|
|
{ prop: 'sort', label: '排序' }
|
|||
|
|
{ prop: '_actions', label: '操作' } // _actions 约定为操作列
|
|||
|
|
{ prop: 'status', label: '状态', slot: true, headerSlot: 'status_header' } // slot: true 启用插槽
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📖 使用指南
|
|||
|
|
|
|||
|
|
> 本节基于已落地的实际代码编写。源码位置:`src/components/page-table/`、`src/components/page-dialog-form/`、`src/composables/`。
|
|||
|
|
>
|
|||
|
|
> 完整可运行示例参考:`src/views/production-configuration/factory-model/factory-area/index.vue`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 快速开始(三步走)
|
|||
|
|
|
|||
|
|
**第一步**:在 `created()` 中用 `useTableColumns` 声明列、`useTableButtons` 声明按钮:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import { useTableColumns } from '@/composables/useTableColumns'
|
|||
|
|
import { useTableButtons } from '@/composables/useTableButtons'
|
|||
|
|
|
|||
|
|
created () {
|
|||
|
|
// 列定义 —— 不再手动分配 idx
|
|||
|
|
this.columns = useTableColumns([
|
|||
|
|
{ prop: 'sort', label: '排序', width: 80 },
|
|||
|
|
{ prop: 'code', label: '编码', minWidth: 120 },
|
|||
|
|
{ prop: 'name', label: '名称', minWidth: 120 },
|
|||
|
|
{ prop: 'remark', label: '备注' },
|
|||
|
|
{ prop: '_actions', label: '操作', width: 160, fixed: 'right' } // ← 操作列
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
// 按钮定义 —— 不再分开写 buttonList / tableButtonList
|
|||
|
|
const btns = useTableButtons({
|
|||
|
|
toolbar: [
|
|||
|
|
{ key: 'add', label: '新 增', icon: 'el-icon-plus', type: 'primary',
|
|||
|
|
auth: '/xxx/create', onClick: this.openAdd }
|
|||
|
|
],
|
|||
|
|
row: [
|
|||
|
|
{ key: 'edit', label: '编辑', icon: 'el-icon-edit',
|
|||
|
|
auth: '/xxx/edit', onClick: this.openEdit },
|
|||
|
|
{ key: 'delete', label: '删除', icon: 'el-icon-delete', color: 'danger',
|
|||
|
|
auth: '/xxx/delete', onClick: this.handleDelete }
|
|||
|
|
]
|
|||
|
|
}, this.$permission)
|
|||
|
|
this.toolbarButtons = btns.toolbarButtons
|
|||
|
|
this.rowButtons = btns.rowButtons
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**第二步**:模板中直接用 `<page-table>` + `<page-dialog-form>`:
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<d2-container>
|
|||
|
|
<template #header>
|
|||
|
|
<!-- 搜索区:直接用 el-form 内联表单 -->
|
|||
|
|
<el-form :inline="true" size="mini">
|
|||
|
|
<el-form-item label="编码">
|
|||
|
|
<el-input v-model="search.code" placeholder="输入编码" clearable
|
|||
|
|
style="width:200px" @keyup.enter.native="onSearch" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="名称">
|
|||
|
|
<el-input v-model="search.name" placeholder="输入名称" clearable
|
|||
|
|
style="width:200px" @keyup.enter.native="onSearch" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item>
|
|||
|
|
<el-button type="primary" icon="el-icon-search" @click="onSearch">查询</el-button>
|
|||
|
|
<el-button icon="el-icon-refresh" @click="onReset">重置</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- 表格 + 按钮栏 + 分页 -->
|
|||
|
|
<page-table
|
|||
|
|
:columns="columns"
|
|||
|
|
:data="tableData"
|
|||
|
|
:loading="loading"
|
|||
|
|
:toolbar-buttons="toolbarButtons"
|
|||
|
|
:row-buttons="rowButtons"
|
|||
|
|
:pagination="pagination"
|
|||
|
|
@page-change="onPageChange"
|
|||
|
|
@selection-change="onSelect"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- 新增/编辑弹框 -->
|
|||
|
|
<page-dialog-form
|
|||
|
|
ref="dialogForm"
|
|||
|
|
:visible.sync="dialogVisible"
|
|||
|
|
:title="dialogTitle"
|
|||
|
|
width="35%"
|
|||
|
|
:form-cols="formCols"
|
|||
|
|
:form-data="formData"
|
|||
|
|
:rules="rules"
|
|||
|
|
:submitting="submitting"
|
|||
|
|
@submit="onDialogSubmit"
|
|||
|
|
@close="onDialogClose"
|
|||
|
|
/>
|
|||
|
|
</d2-container>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**第三步**:写业务方法(fetchData / 增删改),和旧代码完全一致:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
methods: {
|
|||
|
|
async fetchData () {
|
|||
|
|
this.loading = true
|
|||
|
|
try {
|
|||
|
|
const res = await getList({ ...this.search, page_no: this.pagination.current, page_size: this.pagination.size })
|
|||
|
|
this.tableData = res.data || []
|
|||
|
|
this.pagination.total = res.count || 0
|
|||
|
|
} finally { this.loading = false }
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onPageChange (page) {
|
|||
|
|
this.pagination.current = page.current
|
|||
|
|
this.pagination.size = page.size
|
|||
|
|
this.fetchData()
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 新增
|
|||
|
|
openAdd () {
|
|||
|
|
this.handleType = 'create'
|
|||
|
|
this.dialogTitle = '新 增'
|
|||
|
|
this.$nextTick(() => {
|
|||
|
|
this.$refs.dialogForm.reset()
|
|||
|
|
this.formData = { code: '', name: '' }
|
|||
|
|
this.dialogVisible = true
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 编辑
|
|||
|
|
openEdit (row) {
|
|||
|
|
this.handleType = 'edit'
|
|||
|
|
this.dialogTitle = '编 辑'
|
|||
|
|
this.editId = row.id
|
|||
|
|
this.formData = { code: row.code, name: row.name }
|
|||
|
|
this.dialogVisible = true
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 提交
|
|||
|
|
async onDialogSubmit () {
|
|||
|
|
this.submitting = true
|
|||
|
|
try {
|
|||
|
|
if (this.handleType === 'create') {
|
|||
|
|
await create(this.formData)
|
|||
|
|
} else {
|
|||
|
|
await edit({ ...this.formData, id: this.editId })
|
|||
|
|
}
|
|||
|
|
this.$message.success('操作成功')
|
|||
|
|
this.dialogVisible = false
|
|||
|
|
this.fetchData()
|
|||
|
|
} finally { this.submitting = false }
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 删除
|
|||
|
|
async handleDelete (row) {
|
|||
|
|
try {
|
|||
|
|
await this.$confirm('确定要执行该操作吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
|||
|
|
await del({ id: [row.id] })
|
|||
|
|
this.$message.success('操作成功')
|
|||
|
|
this.fetchData()
|
|||
|
|
} catch (e) { /* 用户取消不处理 */ }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 完整可运行示例
|
|||
|
|
|
|||
|
|
> 📁 `src/views/production-configuration/factory-model/factory-area/index.vue`
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<d2-container>
|
|||
|
|
<template #header>
|
|||
|
|
<div class="search-bar">
|
|||
|
|
<el-form :inline="true" size="mini">
|
|||
|
|
<el-form-item label="编码">
|
|||
|
|
<el-input v-model="search.code" placeholder="输入编码" clearable
|
|||
|
|
style="width:200px" @keyup.enter.native="onSearch" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="名称">
|
|||
|
|
<el-input v-model="search.name" placeholder="输入名称" clearable
|
|||
|
|
style="width:200px" @keyup.enter.native="onSearch" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item>
|
|||
|
|
<el-button type="primary" icon="el-icon-search" @click="onSearch">查询</el-button>
|
|||
|
|
<el-button icon="el-icon-refresh" @click="onReset">重置</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<page-table
|
|||
|
|
ref="pageTable"
|
|||
|
|
:columns="columns"
|
|||
|
|
:data="tableData"
|
|||
|
|
:loading="loading"
|
|||
|
|
:toolbar-buttons="toolbarButtons"
|
|||
|
|
:row-buttons="rowButtons"
|
|||
|
|
:pagination="pagination"
|
|||
|
|
@page-change="onPageChange"
|
|||
|
|
@selection-change="onSelect"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<page-dialog-form
|
|||
|
|
ref="dialogForm"
|
|||
|
|
:visible.sync="dialogVisible"
|
|||
|
|
:title="dialogTitle"
|
|||
|
|
width="35%"
|
|||
|
|
:form-cols="formCols"
|
|||
|
|
:form-data="formData"
|
|||
|
|
:rules="rules"
|
|||
|
|
:submitting="submitting"
|
|||
|
|
@submit="onDialogSubmit"
|
|||
|
|
@close="onDialogClose"
|
|||
|
|
/>
|
|||
|
|
</d2-container>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import { useTableColumns } from '@/composables/useTableColumns'
|
|||
|
|
import { useTableButtons } from '@/composables/useTableButtons'
|
|||
|
|
import { getFactoryAreaList, createFactoryArea, editFactoryArea, deleteFactoryArea } from '@/api/production-configuration/factory-area'
|
|||
|
|
import PageTable from '@/components/page-table'
|
|||
|
|
import PageDialogForm from '@/components/page-dialog-form'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: 'factory-area',
|
|||
|
|
components: { PageTable, PageDialogForm },
|
|||
|
|
data () {
|
|||
|
|
return {
|
|||
|
|
loading: false,
|
|||
|
|
submitting: false,
|
|||
|
|
tableData: [],
|
|||
|
|
selectedRows: [],
|
|||
|
|
dialogVisible: false,
|
|||
|
|
dialogTitle: '',
|
|||
|
|
editId: '',
|
|||
|
|
handleType: 'create',
|
|||
|
|
search: { code: '', name: '' },
|
|||
|
|
pagination: { current: 1, size: 10, total: 0 },
|
|||
|
|
formData: { code: '', name: '', remark: '' },
|
|||
|
|
rules: {
|
|||
|
|
code: [{ required: true, message: '请输入编码', trigger: 'blur' }],
|
|||
|
|
name: [{ required: true, message: '请输入名称', trigger: 'blur' }]
|
|||
|
|
},
|
|||
|
|
columns: [],
|
|||
|
|
toolbarButtons: [],
|
|||
|
|
rowButtons: [],
|
|||
|
|
formCols: [
|
|||
|
|
[{ type: 'input', prop: 'code', label: '编码', placeholder: '请输入编码', clearable: true, style: { width: '90%' } }],
|
|||
|
|
[{ type: 'input', prop: 'name', label: '名称', placeholder: '请输入名称', clearable: true, style: { width: '90%' } }],
|
|||
|
|
[{ type: 'input', prop: 'remark', label: '备注', inputType: 'textarea', autosize: { minRows: 2, maxRows: 6 }, placeholder: '请输入备注', clearable: true, style: { width: '90%' } }]
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
created () {
|
|||
|
|
this.columns = useTableColumns([
|
|||
|
|
{ prop: 'sort', label: '排序', width: 80 },
|
|||
|
|
{ prop: 'code', label: '编码', minWidth: 120 },
|
|||
|
|
{ prop: 'name', label: '名称', minWidth: 120 },
|
|||
|
|
{ prop: 'remark', label: '备注' },
|
|||
|
|
{ prop: '_actions', label: '操作', width: 160, fixed: 'right' }
|
|||
|
|
])
|
|||
|
|
const btns = useTableButtons({
|
|||
|
|
toolbar: [{ key: 'add', label: '新 增', icon: 'el-icon-plus', type: 'primary', auth: '/production_configuration/factory_model/factory_area/create', onClick: this.openAdd }],
|
|||
|
|
row: [
|
|||
|
|
{ key: 'edit', label: '编辑', icon: 'el-icon-edit', auth: '/production_configuration/factory_model/factory_area/edit', onClick: this.openEdit },
|
|||
|
|
{ key: 'delete', label: '删除', icon: 'el-icon-delete', color: 'danger', auth: '/production_configuration/factory_model/factory_area/delete', onClick: this.handleDelete }
|
|||
|
|
]
|
|||
|
|
}, this.$permission)
|
|||
|
|
this.toolbarButtons = btns.toolbarButtons
|
|||
|
|
this.rowButtons = btns.rowButtons
|
|||
|
|
this.fetchData()
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
async fetchData () {
|
|||
|
|
this.loading = true
|
|||
|
|
try {
|
|||
|
|
const res = await getFactoryAreaList({ ...this.search, page_no: this.pagination.current, page_size: this.pagination.size })
|
|||
|
|
this.tableData = res.data || []
|
|||
|
|
this.pagination.total = res.count || 0
|
|||
|
|
} finally { this.loading = false }
|
|||
|
|
},
|
|||
|
|
onSearch () { this.pagination.current = 1; this.fetchData() },
|
|||
|
|
onReset () { this.search = { code: '', name: '' }; this.pagination.current = 1; this.fetchData() },
|
|||
|
|
onPageChange (page) { this.pagination.current = page.current; this.pagination.size = page.size; this.fetchData() },
|
|||
|
|
onSelect (rows) { this.selectedRows = rows },
|
|||
|
|
openAdd () {
|
|||
|
|
this.handleType = 'create'; this.dialogTitle = '新 增'
|
|||
|
|
this.$nextTick(() => { this.$refs.dialogForm.reset(); this.formData = { code: '', name: '', remark: '' }; this.editId = ''; this.dialogVisible = true })
|
|||
|
|
},
|
|||
|
|
openEdit (row) {
|
|||
|
|
this.handleType = 'edit'; this.dialogTitle = '编 辑'; this.editId = row.id
|
|||
|
|
this.formData = { code: row.code, name: row.name, remark: row.remark || '' }
|
|||
|
|
this.dialogVisible = true
|
|||
|
|
},
|
|||
|
|
async onDialogSubmit () {
|
|||
|
|
this.submitting = true
|
|||
|
|
try {
|
|||
|
|
if (this.handleType === 'create') { await createFactoryArea(this.formData) }
|
|||
|
|
else { await editFactoryArea({ ...this.formData, id: this.editId }) }
|
|||
|
|
this.$message.success('操作成功'); this.dialogVisible = false; this.fetchData()
|
|||
|
|
} finally { this.submitting = false }
|
|||
|
|
},
|
|||
|
|
onDialogClose () { this.formData = { code: '', name: '', remark: '' }; this.editId = '' },
|
|||
|
|
async handleDelete (row) {
|
|||
|
|
try {
|
|||
|
|
await this.$confirm('确定要执行该操作吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
|||
|
|
await deleteFactoryArea({ id: [row.id] })
|
|||
|
|
this.$message.success('操作成功')
|
|||
|
|
this.pagination.current = Math.min(this.pagination.current, Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1)
|
|||
|
|
this.fetchData()
|
|||
|
|
} catch (e) { /* 取消删除不处理 */ }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### API 速查
|
|||
|
|
|
|||
|
|
#### `useTableColumns(rawColumns, options?)`
|
|||
|
|
|
|||
|
|
| 参数 | 类型 | 默认值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| rawColumns | Array | — | 列定义,字段同 `el-table-column` 属性 |
|
|||
|
|
| options.selectionWidth | number | 55 | 复选框列宽,传 0 不显示 |
|
|||
|
|
| options.indexWidth | number | 0 | 序号列宽,传 0 不显示 |
|
|||
|
|
|
|||
|
|
**约定**:`prop: '_actions'` 自动映射为操作列插槽,由 `page-table` 自动渲染 `rowButtons`。
|
|||
|
|
|
|||
|
|
#### `useTableButtons(options, permissionCheck?)`
|
|||
|
|
|
|||
|
|
| 参数 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| options.toolbar | Array | 顶部工具栏按钮 |
|
|||
|
|
| options.row | Array | 行内操作按钮 |
|
|||
|
|
| permissionCheck | Function | 权限函数,默认 `() => true`,通常传 `this.$permission` |
|
|||
|
|
|
|||
|
|
返回值 `{ toolbarButtons, rowButtons }`。
|
|||
|
|
|
|||
|
|
**按钮字段**:
|
|||
|
|
|
|||
|
|
| 字段 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| key | 唯一标识 |
|
|||
|
|
| label | 显示文本 |
|
|||
|
|
| icon | Element UI 图标名 |
|
|||
|
|
| type | `primary` / `success` / `warning` / `danger` |
|
|||
|
|
| color | 行内按钮专用:`'danger'` 显示红色 |
|
|||
|
|
| auth | 权限 key(传给 `$permission` 检查) |
|
|||
|
|
| onClick | 点击回调 |
|
|||
|
|
| hasPermission | **自动注入**,由 `permissionCheck(auth)` 计算 |
|
|||
|
|
|
|||
|
|
#### `page-table` — 组件 Props
|
|||
|
|
|
|||
|
|
| Prop | 类型 | 默认值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| columns | Array | `[]` | 列定义(由 useTableColumns 生成) |
|
|||
|
|
| data | Array | `[]` | 表格数据 |
|
|||
|
|
| loading | Boolean | false | loading 遮罩 |
|
|||
|
|
| height | String/Number | — | 表格高度 |
|
|||
|
|
| toolbarButtons | Array | `[]` | 顶部按钮(由 useTableButtons 生成) |
|
|||
|
|
| rowButtons | Array | `[]` | 行内按钮(由 useTableButtons 生成) |
|
|||
|
|
| pagination | Object | null | 传 `{ current, size, total }` 才显示分页 |
|
|||
|
|
| border | Boolean | true | 是否带边框 |
|
|||
|
|
| rowKey | String | `'id'` | 行 key |
|
|||
|
|
|
|||
|
|
**事件**:`@page-change`、`@selection-change`,同时透传 `el-table` 原生事件。
|
|||
|
|
|
|||
|
|
**插槽**:`#col-{prop}` 自定义列渲染、`#toolbar-extra` 工具栏追加、`#empty` / `#append` / `#extra`。
|
|||
|
|
|
|||
|
|
#### `page-dialog-form` — 组件 Props
|
|||
|
|
|
|||
|
|
| Prop | 类型 | 默认值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| visible | Boolean | false | `.sync` 绑定 |
|
|||
|
|
| title | String | `''` | 弹框标题 |
|
|||
|
|
| width | String | `'35%'` | 弹框宽度 |
|
|||
|
|
| formCols | Array | `[]` | 表单字段(二维数组,每行一个子数组) |
|
|||
|
|
| formData | Object | `{}` | 表单数据 |
|
|||
|
|
| rules | Object | `{}` | 校验规则(el-form rules) |
|
|||
|
|
| labelWidth | String | `'100px'` | label 宽度 |
|
|||
|
|
| submitting | Boolean | false | 提交中 loading |
|
|||
|
|
| confirmText | String | `'确定'` | 确认按钮文字 |
|
|||
|
|
| cancelText | String | `'取消'` | 取消按钮文字 |
|
|||
|
|
|
|||
|
|
**方法**(通过 `ref` 调用):`reset()` 重置表单、`validate()` 返回 Promise。
|
|||
|
|
|
|||
|
|
**formCols 字段项**:
|
|||
|
|
|
|||
|
|
| 字段 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| type | `'input'`(默认 input)/ `'select'` |
|
|||
|
|
| prop | 绑定 `formData.prop` 的 key |
|
|||
|
|
| label | 表单项标签 |
|
|||
|
|
| placeholder | 占位文本 |
|
|||
|
|
| inputType | `textarea` 时启用多行输入 |
|
|||
|
|
| autosize | textarea 的 `{ minRows, maxRows }` |
|
|||
|
|
| style | 样式对象,如 `{ width: '90%' }` |
|
|||
|
|
| options | type 为 `'select'` 时的选项数组 `[{ label, value }]` |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 常用场景速查
|
|||
|
|
|
|||
|
|
**场景 1:自定义列渲染(如状态显示 tag)**
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<page-table :columns="columns" :data="tableData">
|
|||
|
|
<template #col-status="{ row }">
|
|||
|
|
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
|||
|
|
{{ row.status === 1 ? '启用' : '禁用' }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
</page-table>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
列定义中加入 `slot: true`:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
useTableColumns([
|
|||
|
|
{ prop: 'status', label: '状态', slot: true, width: 100 },
|
|||
|
|
])
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**场景 2:工具栏追加自定义内容**
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<page-table ...>
|
|||
|
|
<template #toolbar-extra>
|
|||
|
|
<el-button size="mini" icon="el-icon-printer" @click="print">打印</el-button>
|
|||
|
|
</template>
|
|||
|
|
</page-table>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**场景 3:序号列**
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
useTableColumns(
|
|||
|
|
[{ prop: 'code', label: '编码' }],
|
|||
|
|
{ selectionWidth: 55, indexWidth: 60 } // 开启复选框 + 序号列
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 路由配置
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// src/router/modules/production-configuration.js
|
|||
|
|
import layoutHeaderAside from '@/layout/header-aside'
|
|||
|
|
const meta = { auth: true }
|
|||
|
|
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
path: '/production_configuration',
|
|||
|
|
component: layoutHeaderAside,
|
|||
|
|
children: (pre => [
|
|||
|
|
{
|
|||
|
|
path: 'factory_model/factory_area',
|
|||
|
|
name: `${pre}factory_model-factory_area`,
|
|||
|
|
meta: { ...meta, cache: true, title: '工厂区域' },
|
|||
|
|
component: _import('production-configuration/factory-model/factory-area')
|
|||
|
|
}
|
|||
|
|
])('production_configuration-')
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 旧代码迁移对照
|
|||
|
|
|
|||
|
|
| 旧写法 | 新写法 |
|
|||
|
|
|--------|--------|
|
|||
|
|
| 手动 `columns: [{ idx: 0, attrs: { prop, label } }]` | `useTableColumns([{ prop, label }])` |
|
|||
|
|
| `buttonList: [...]` + `tableButtonList: [...]` 分两套 | `useTableButtons({ toolbar: [...], row: [...] })` |
|
|||
|
|
| `sct-base-table` + `sct-base-dialog` + `SctBaseForm` 三层 | `page-table` + `page-dialog-form` 两层 |
|
|||
|
|
| `sct-form-search` 组件 | 原生 `el-form :inline` |
|
|||
|
|
| `sct-back-to-top` 组件 | 需要时自行添加 |
|
|||
|
|
| 分页需额外 `page-footer` 组件 | `page-table` 内置分页 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 国际化 (i18n) 使用说明
|
|||
|
|
|
|||
|
|
**组件层自动翻译**:`page-table` 和 `page-dialog-form` 对所有 `label`、`placeholder`、`title` 等文本属性自动调用 `$t()` 翻译。因此页面只需传入 i18n key,组件会自动渲染翻译后的文本。如果传入的是普通字符串(非 i18n key),`$t()` 会原样返回,不会报错。
|
|||
|
|
|
|||
|
|
**页面层用法**:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 约定:每个页面定义一个 T 常量作为该页面的 i18n key 前缀
|
|||
|
|
const T = 'page.production_configuration.factory_model.factory_area'
|
|||
|
|
|
|||
|
|
// 列定义 —— 传 i18n key,组件自动翻译
|
|||
|
|
columns = useTableColumns([
|
|||
|
|
{ prop: 'sort', label: T + '.sort', width: 80 },
|
|||
|
|
{ prop: 'code', label: T + '.code', minWidth: 120 },
|
|||
|
|
{ prop: '_actions', label: T + '.operation', width: 160, fixed: 'right' }
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
// 按钮定义 —— 传 i18n key
|
|||
|
|
const btns = useTableButtons({
|
|||
|
|
toolbar: [{ key: 'add', label: T + '.add', icon: 'el-icon-plus', ... }],
|
|||
|
|
row: [{ key: 'edit', label: T + '.edit', ... }]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 表单定义 —— 传 i18n key
|
|||
|
|
formCols: [
|
|||
|
|
[{ type: 'input', prop: 'code', label: T + '.code', placeholder: T + '.enter_code' }]
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
// 模板中直接用 $t() 或 $t(tkey('key'))
|
|||
|
|
methods: {
|
|||
|
|
tkey (key) { return T + '.' + key }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<el-form-item :label="$t(tkey('code'))">
|
|||
|
|
<el-input :placeholder="$t(tkey('enter_code'))" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-button>{{ $t(tkey('search')) }}</el-button>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**语言包文件**:在 `src/locales/zh-chs.json` 和 `src/locales/en.json` 中按页面维护:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"page": {
|
|||
|
|
"production_configuration": {
|
|||
|
|
"factory_model": {
|
|||
|
|
"factory_area": {
|
|||
|
|
"search": "查询",
|
|||
|
|
"code": "所区编码",
|
|||
|
|
"name": "所区名称",
|
|||
|
|
"add": "新 增",
|
|||
|
|
"edit": "编 辑",
|
|||
|
|
"delete": "删 除",
|
|||
|
|
"operation_success": "操作成功"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**语言包目录命名规则**:
|
|||
|
|
|
|||
|
|
| 语言 | 文件名 |
|
|||
|
|
|------|--------|
|
|||
|
|
| 简体中文 | `src/locales/zh-chs.json` |
|
|||
|
|
| 繁体中文 | `src/locales/zh-cht.json` |
|
|||
|
|
| 英文 | `src/locales/en.json` |
|
|||
|
|
| 日文 | `src/locales/ja.json` |
|
|||
|
|
|
|||
|
|
> 注意:`page-table` 和 `page-dialog-form` 已内置 `$t()` 调用,页面传 i18n key 即可自动翻译。搜索区等其他 UI 文字需要用 `$t(tkey('...'))` 手动处理。
|