@@ -1,6 +1,6 @@
# 表格组件使用说明
> 基于 `page-table` + `page-dialog-form` 的新一<EFBFBD> <EFBFBD> ? CRUD 表格方案<EFBFBD> <EFBFBD> ?
> 基于 `page-table` + `page-dialog-form` 的新一代 CRUD 表格方案。
> 源码位置:`src/components/page-table/`、`src/components/page-dialog-form/`、`src/composables/`
> 完整可运行示例:`src/views/production-master-data/factory-model/factory-area/index.vue`
@@ -10,13 +10,13 @@
1. [快速开始(三步走) ](#1-快速开始三步走 )
2. [完整示例代码 ](#2-完整示例代码 )
3. [组件 API 参考]( #3 -组件-api-参<EFBFBD> <EFBFBD> ?
4. [Composable API 参考]( #4 -composable-api-参<EFBFBD> <EFBFBD> ?
5. [i18n 国际化方案]( #5 -i18n-国际化方<EFBFBD> <EFBFBD> ?
3. [ 组件 API 参考 ]( #3-组件-api-参考 )
4. [ Composable API 参考 ]( #4-composable-api-参考 )
5. [ i18n 国际化方案 ]( #5-i18n-国际化方案 )
6. [常用场景速查 ](#6-常用场景速查 )
7. [路由配置 ](#7-路由配置 )
8. [API 文件写法 ](#8-api-文件写法 )
9. [旧代码迁移对照]( #9 -旧代码迁移对<EFBFBD> <EFBFBD> ?
9. [ 旧代码迁移对照 ]( #9-旧代码迁移对照 )
10. [常见问题排查 ](#10-常见问题排查 )
---
@@ -31,12 +31,12 @@ import { useTableButtons } from '@/composables/useTableButtons'
import { i18nMixin } from '@/composables/useI18n'
export default {
mixins : [ i18nMixin ( 'page.模块<EFBFBD> <EFBFBD> ? 二级模块.三级模块' )],
mixins : [ i18nMixin ( 'page.模块名. 二级模块.三级模块' )],
created () {
// --- 列定<EFBFBD> <EFBFBD> ? ---
// 只需声明 prop <EFBFBD> <EFBFBD> ? label, idx 自动补齐
// prop: '_actions' 约定为操作列,自动渲<EFBFBD> <EFBFBD> ? rowButtons
// --- 列定义 ---
// 只需声明 prop 和 label, idx 自动补齐
// prop: '_actions' 约定为操作列,自动渲染 rowButtons
this . columns = useTableColumns ([
{ prop : 'sort' , label : this . key ( 'sort' ), width : 80 },
{ prop : 'code' , label : this . key ( 'code' ), minWidth : 120 },
@@ -45,7 +45,7 @@ export default {
])
// --- 按钮定义 ---
// 不再分开<EFBFBD> <EFBFBD> ? buttonList / tableButtonList
// 不再分开写 buttonList / tableButtonList
const btns = useTableButtons ({
toolbar : [
{ key : 'add' , label : this . key ( 'add' ), icon : 'el-icon-plus' ,
@@ -57,7 +57,7 @@ export default {
{ key : 'delete' , label : this . key ( 'delete' ), icon : 'el-icon-delete' ,
color : 'danger' , auth : '/xxx/delete' , onClick : this . handleDelete }
]
}, this . $permission ) // <EFBFBD> <EFBFBD> ? 第二个参数传入权限校验函<EFBFBD> <EFBFBD> ?
}, this . $permission ) // 第二个参数传入权限校验函数
this . toolbarButtons = btns . toolbarButtons
this . rowButtons = btns . rowButtons
}
@@ -69,7 +69,7 @@ export default {
```vue
< template >
< d2-container >
<!-- 搜索<EFBFBD> <EFBFBD> ? -->
<!-- 搜索区 -->
< template # header >
< el-form :inline = "true" size = "mini" >
< el-form-item :label = "$t(key('code'))" >
@@ -87,7 +87,7 @@ export default {
</ el-form >
</ template >
<!-- 表格 + 按钮<EFBFBD> <EFBFBD> ? + 分页 -->
<!-- 表格 + 按钮栏 + 分页 -->
< page-table
:columns = "columns"
:data = "tableData"
@@ -96,6 +96,7 @@ export default {
:row-buttons = "rowButtons"
:pagination = "pagination"
help -url ="/ help / your -page "
:help-text = "$t(ckey('help'))"
auto -height
@ page -change =" onPageChange "
@ selection -change =" onSelect "
@@ -111,8 +112,8 @@ export default {
:form-data = "formData"
:rules = "rules"
:submitting = "submitting"
:confirm-text = "$t( key('confirm')) "
:cancel-text = "$t( key('cancel')) "
:confirm-text = "key('confirm')"
:cancel-text = "key('cancel')"
@submit ="onDialogSubmit"
@close ="onDialogClose"
/>
@@ -120,7 +121,8 @@ export default {
</ template >
```
### 第三步:写业务方法(增删改查<EFBFBD> <EFBFBD> ?
### 第三步:写业务方法(增删改查)
```js
methods : {
// 获取列表数据
@@ -159,7 +161,8 @@ methods: {
})
},
// 编辑:回填表<EFBFBD> <EFBFBD> ? openEdit (row) {
// 编辑:回填表单
openEdit ( row ) {
this . handleType = 'edit'
this . dialogTitle = this . key ( 'edit_title' )
this . editId = row . id
@@ -198,7 +201,7 @@ methods: {
await deleteApi ({ id : [ row . id ] })
this . $message . success ( this . $t ( this . key ( 'operation_success' )))
this . fetchData ()
} catch ( e ) { /* 取消或失<EFBFBD> <EFBFBD> ? */ }
} catch ( e ) { /* 取消或失败 */ }
}
}
```
@@ -208,7 +211,8 @@ methods: {
## 2. 完整示例代码
> 📁 `src/views/production-master-data/factory-model/factory-area/index.vue`
> 这是一<EFBFBD> <EFBFBD> ? *可直接运行的完整 CRUD 页面**,包含搜索栏、表格、分页、新<EFBFBD> <EFBFBD> ? 编辑弹框、删除确认<EFBFBD> <EFBFBD> ?
> 这是一个* *可直接运行的完整 CRUD 页面**,包含搜索栏、表格、分页、新增/ 编辑弹框、删除确认。
### 模板部分
```vue
@@ -246,6 +250,7 @@ methods: {
:row-buttons = "rowButtons"
:pagination = "pagination"
help -url ="/ help / factory -area "
:help-text = "$t(ckey('help'))"
auto -height
@ page -change =" onPageChange "
@ selection -change =" onSelect "
@@ -260,8 +265,8 @@ methods: {
:form-data = "formData"
:rules = "rules"
:submitting = "submitting"
:confirm-text = "$t( key('confirm')) "
:cancel-text = "$t( key('cancel')) "
:confirm-text = "key('confirm')"
:cancel-text = "key('cancel')"
@submit ="onDialogSubmit"
@close ="onDialogClose"
/>
@@ -286,8 +291,6 @@ export default {
mixins : [ i18nMixin ( 'page.production_master_data.factory_model.factory_area' )],
data () {
const t = this . $t . bind ( this )
const k = ( s ) => t ( this . key ( s ))
return {
loading : false ,
submitting : false ,
@@ -302,12 +305,12 @@ export default {
formData : { code : '' , name : '' , remark : '' },
rules : {
code : [
{ required : true , message : k ( 'enter_code' ), trigger : 'blur' },
{ min : 1 , max : 100 , message : k ( 'remark_length' ), trigger : 'blur' }
{ required : true , message : this . key ( 'enter_code' ), trigger : 'blur' },
{ min : 1 , max : 100 , message : this . key ( 'remark_length' ), trigger : 'blur' }
],
name : [
{ required : true , message : k ( 'enter_name' ), trigger : 'blur' },
{ min : 1 , max : 100 , message : k ( 'remark_length' ), trigger : 'blur' }
{ required : true , message : this . key ( 'enter_name' ), trigger : 'blur' },
{ min : 1 , max : 100 , message : this . key ( 'remark_length' ), trigger : 'blur' }
]
},
columns : [],
@@ -316,18 +319,18 @@ export default {
formCols : [
[
{ type : 'input' , prop : 'code' ,
label : k ( 'code' ), placeholder : k ( 'enter_code' ),
label : this . key ( 'code' ), placeholder : this . key ( 'enter_code' ),
clearable : true , style : { width : '90%' } }
],
[
{ type : 'input' , prop : 'name' ,
label : k ( 'name' ), placeholder : k ( 'enter_name' ),
label : this . key ( 'name' ), placeholder : this . key ( 'enter_name' ),
clearable : true , style : { width : '90%' } }
],
[
{ type : 'input' , prop : 'remark' , inputType : 'textarea' ,
autosize : { minRows : 2 , maxRows : 6 },
label : k ( 'remark' ), placeholder : k ( 'remark_required' ),
label : this . key ( 'remark' ), placeholder : this . key ( 'remark_required' ),
clearable : true , style : { width : '90%' } }
]
]
@@ -436,7 +439,7 @@ export default {
Math . ceil (( this . pagination . total - 1 ) / this . pagination . size ) || 1
)
this . fetchData ()
} catch ( e ) { /* 取消删除 / 请求失败不处<EFBFBD> <EFBFBD> ? */ }
} catch ( e ) { /* 取消删除 / 请求失败不处理 */ }
}
}
}
@@ -444,111 +447,115 @@ export default {
---
## 3. 组件 API 参<EFBFBD> <EFBFBD> ?
### 3.1 `page-table` <20> <> ?表格 + 按钮<E68C89> <E992AE> ?+ 分页
## 3. 组件 API 参考
### 3.1 `page-table` — 表格 + 按钮栏 + 分页
#### Props
| Prop | 类型 | 默认<EFBFBD> <EFBFBD> ? | 说明 |
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `columns` | `Array` | `[]` | 列定义,<EFBFBD> <EFBFBD> ?`useTableColumns()` 生成 |
| `data` | `Array` | `[]` | 表格行数<EFBFBD> <EFBFBD> ? |
| `columns` | `Array` | `[]` | 列定义,由 `useTableColumns()` 生成 |
| `data` | `Array` | `[]` | 表格行数据 |
| `loading` | `Boolean` | `false` | 是否显示 loading 遮罩 |
| `height` | `String/Number` | <EFBFBD> <EFBFBD> ? | 表格高度。不传则随内容撑开; 传具体数值固定高度; <EFBFBD> <EFBFBD> ? `'auto'` 启用自适应 |
| `auto-height` | `Boolean` | `false` | 启用高度自适应,表格自动 填满可用空<EFBFBD> <EFBFBD> ? |
| `border` | `Boolean` | `true` | 是否带边<EFBFBD> <EFBFBD> ? |
| `height` | `String/Number` | — | 表格高度,传 `'auto'` 启用自适应 |
| `auto-height` | `Boolean` | `false` | 启用高度自适应,填满可用空间 |
| `border` | `Boolean` | `true` | 是否带边框 |
| `row-key` | `String` | `'id'` | 行唯一 key |
| `toolbar-buttons` | `Array` | `[]` | 顶部工具栏按钮,<EFBFBD> <EFBFBD> ?`useTableButtons()` 生成 |
| `toolbar-buttons` | `Array` | `[]` | 顶部工具栏按钮,由 `useTableButtons()` 生成 |
| `row-buttons` | `Array` | `[]` | 行内操作按钮,由 `useTableButtons()` 生成 |
| `pagination` | `Object` | `null` | 分页参数 `{ current, size, total }` ,传了才显示分页 |
| `table-attrs` | `Object` | `{}` | 额外透传<EFBFBD> <EFBFBD> ?`el-table` 的属<EFBFBD> <EFBFBD> ? |
| `table-listeners` | `Object` | `{}` | 额外透传<EFBFBD> <EFBFBD> ?`el-table` 的事<EFBFBD> <EFBFBD> ? |
| `help-url` | `String` | `''` | 帮助文档跳转 URL。 传了才 显示工具栏右侧的问号按钮,点击新窗口打开 |
| `help-text` | `String` | `'帮助'` | 帮助按钮文字,支<EFBFBD> <EFBFBD> ? i18n key( 组件自<EFBFBD> <EFBFBD> ?`$t()` 翻译<E7BFBB> <E8AF91> ? |
| `pagination` | `Object` | `null` | 分页参数 `{ current, size, total }` |
| `table-attrs` | `Object` | `{}` | 额外透传给 `el-table` 的属性 |
| `table-listeners` | `Object` | `{}` | 额外透传给 `el-table` 的事件 |
| `help-url` | `String` | `''` | 帮助文档 URL, 传了显示问号按钮 |
| `help-text` | `String` | `'帮助'` | 帮助按钮文字,支持 i18n key |
#### 事件
| 事件<EFBFBD> <EFBFBD> ? | 参数 | 说明 |
| 事件名 | 参数 | 说明 |
|--------|------|------|
| `@page-change` | `{ current, size, total }` | 分页变化( 切换页<EFBFBD> <EFBFBD> ?条数<E69DA1> <E695B0> ? |
| `@selection-change` | `rows: Array` | 选中行变<EFBFBD> <EFBFBD> ? |
| `@sort-change` | 透传 el-table 原生事件 | 排序变化 |
| `@page-change` | `{ current, size, total }` | 分页变化 |
| `@selection-change` | `rows: Array` | 选中行变化 |
#### 插槽
| 插槽<EFBFBD> <EFBFBD> ? | 作用<EFBFBD> <EFBFBD> ? | 说明 |
| 插槽名 | 作用域 | 说明 |
|--------|--------|------|
| `#col-{prop}` | `{ row, index }` | 自定义列的 渲染(列定义中 prop 需<> <E99C80> ? `slot: true` <EFBFBD> <EFBFBD> ? |
| `#toolbar-extra` | <EFBFBD> <EFBFBD> ? | 工具栏区域追加自定义 内容 |
| `#empty` | <EFBFBD> <EFBFBD> ?| 表格空数据时的占<E79A84> <E58DA0> ? |
| `#append` | <EFBFBD> <EFBFBD> ? | 表格最后一行后 追加 |
| `#extra` | <EFBFBD> <EFBFBD> ? | 页面底部追加区域 |
| `#col-{prop}` | `{ row, index }` | 自定义列渲染(列需设 `slot: true` ) |
| `#toolbar-extra` | — | 工具栏追加 内容 |
| `#empty` | — | 空数据占位 |
| `#append` | — | 表格末尾 追加 |
| `#extra` | — | 页面底部追加 |
#### 列定义规范
#### 列定义规<E4B989> <E8A784> ?
```js
// 普通列
{ prop : 'code' , label : '编码' , minWidth : 120 }
// 操作列( 约定: prop === '_actions'<EFBFBD> <EFBFBD> ?{ prop: '_actions', label: '操作', width: 160, fixed: 'right' }
// 操作列( 约定: prop === '_actions')
{ prop : '_actions' , label : '操作' , width : 160 , fixed : 'right' }
// 自定义插槽列( slot: true<EFBFBD> <EFBFBD> ?{ prop: 'status', label: '状<> <E78AB6> ?, slot: true, width: 100 }
// 自定义插槽列( slot: true)
{ prop : 'status' , label : '状态' , slot : true , width : 100 }
// 复选框<EFBFBD> <EFBFBD> ? + 序号列(通过 useTableColumns 第二个参数)
// 复选框 + 序号列(通过 useTableColumns 第二个参数)
useTableColumns ([...], { selectionWidth : 55 , indexWidth : 60 })
```
---
### 3.2 `page-dialog-form` <EFBFBD> <EFBFBD> ? 增删改查弹框
### 3.2 `page-dialog-form` — 增删改查弹框
#### Props
| Prop | 类型 | 默认<EFBFBD> <EFBFBD> ? | 说明 |
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `visible` | `Boolean` | `false` | 弹框显隐,使<EFBFBD> <EFBFBD> ?`.sync` 修饰<EFBFBD> <EFBFBD> ? |
| `title` | `String` | `''` | 弹框标题,支<EFBFBD> <EFBFBD> ? i18n key |
| `visible` | `Boolean` | `false` | 弹框显隐,使用 `.sync` |
| `title` | `String` | `''` | 弹框标题,支持 i18n key |
| `width` | `String` | `'35%'` | 弹框宽度 |
| `form-cols` | `Array` | `[]` | 表单字段结构(二维数组,见下方 ) |
| `form-cols` | `Array` | `[]` | 表单字段结构(二维数组) |
| `form-data` | `Object` | `{}` | 表单数据对象 |
| `rules` | `Object` | `{}` | 校验规则,与 `el-form` rules 一<EFBFBD> <EFBFBD> ? |
| `rules` | `Object` | `{}` | 校验规则,与 `el-form` rules 一致 |
| `label-width` | `String` | `'100px'` | label 宽度 |
| `submitting` | `Boolean` | `false` | 提交 loading 状<EFBFBD> <EFBFBD> ? |
| `confirm-text` | `String` | `'确定'` | 确定按钮文字 |
| `cancel-text` | `String` | `'取消'` | 取消按钮文字 |
| `submitting` | `Boolean` | `false` | 提交 loading 状态 |
| `confirm-text` | `String` | `'确定'` | 确定按钮文字,支持 i18n key |
| `cancel-text` | `String` | `'取消'` | 取消按钮文字,支持 i18n key |
#### 事件
| 事件<EFBFBD> <EFBFBD> ? | 说明 |
| 事件名 | 说明 |
|--------|------|
| `@submit` | 表单验证通过后触发,父组件执行提交逻辑 |
| `@close` | 弹框关闭后触<EFBFBD> <EFBFBD> ? |
| `@submit` | 表单验证通过后触发 |
| `@close` | 弹框关闭后触发 |
#### 方法(通过 ref 调用)
#### 方法(通过 ref 调用<E8B083> <E794A8> ?
| 方法 | 说明 |
|------|------|
| `reset()` | 重置表单 |
| `validate()` | 手动验证,返<EFBFBD> <EFBFBD> ?`Promise<boolean>` |
| `validate()` | 手动验证,返回 `Promise<boolean>` |
#### formCols 数据结构
**注意: 需<EFBC9A> <E99C80> ?`data()` 中用 `k(prop)` 提前完成 i18n 翻译** ,不要传 raw key<65> <79> ?
传入 i18n key( 用 `this.key()` 拼接完整 key) , 组件内部自动翻译且跟随语言切换:
```js
// 例:两个普通输入框 + 一个多行文本框
formCols : [
[
{ type : 'input' , prop : 'code' ,
label : k ( 'code' ), placeholder : k ( 'enter_code' ),
label : this . key ( 'code' ), placeholder : this . key ( 'enter_code' ),
clearable : true , style : { width : '90%' } }
],
[
{ type : 'input' , prop : 'name' ,
label : k ( 'name' ), placeholder : k ( 'enter_name' ),
label : this . key ( 'name' ), placeholder : this . key ( 'enter_name' ),
clearable : true , style : { width : '90%' } }
],
[
{ type : 'input' , prop : 'remark' , inputType : 'textarea' ,
autosize : { minRows : 2 , maxRows : 6 },
label : k ( 'remark' ), placeholder : k ( 'remark_required' ),
label : this . key ( 'remark' ), placeholder : this . key ( 'remark_required' ),
clearable : true , style : { width : '90%' } }
]
]
@@ -556,14 +563,14 @@ formCols: [
**字段支持的属性:**
| 属<EFBFBD> <EFBFBD> ? | 适用类型 | 说明 |
| 属性 | 适用类型 | 说明 |
|------|---------|------|
| `type` | 全部 | `'input'` (文本输入 ) / `'select'` (下拉) |
| `type` | 全部 | `'input'` (文本) / `'select'` (下拉) |
| `prop` | 全部 | 绑定 `formData` 中的 key |
| `label` | 全部 | 表单项标<EFBFBD> <EFBFBD> ? |
| `placeholder` | 全部 | 占位提示 |
| `inputType` | `input` | `'textarea'` 多行 / 不传为普<EFBFBD> <EFBFBD> ? text |
| `autosize` | `input` | textarea <EFBFBD> <EFBFBD> ?`{ minRows, maxRows }` |
| `label` | 全部 | 表单项标签,支持 i18n key |
| `placeholder` | 全部 | 占位提示,支持 i18n key |
| `inputType` | `input` | `'textarea'` / 不传为普通 text |
| `autosize` | `input` | textarea 自适应: `{ minRows, maxRows }` |
| `clearable` | 全部 | 是否可清空,默认 `true` |
| `style` | 全部 | 样式对象 |
| `options` | `select` | 选项数组 `[{ label, value }]` |
@@ -571,13 +578,15 @@ formCols: [
---
## 4. Composable API 参<EFBFBD> <EFBFBD> ?
## 4. Composable API 参考
### 4.1 `useTableColumns(rawColumns, options?)`
**作用** :消除手动分<EFBFBD> <EFBFBD> ?`idx` 序号,自动识别操作列和插槽列<EFBFBD> <EFBFBD> ?
| 参数 | 类型 | 默认<E9BB98> <E8AEA4> ?| 说明 |
**作用** :消除手动分配 `idx` 序号,自动识别操作列和插槽列。
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `rawColumns` | `Array` | <EFBFBD> <EFBFBD> ? | 列定<EFBFBD> <EFBFBD> ? |
| `rawColumns` | `Array` | — | 列定义 |
| `options.selectionWidth` | `number` | `55` | 复选框列宽,传 `0` 隐藏 |
| `options.indexWidth` | `number` | `0` | 序号列宽,传 `0` 隐藏 |
@@ -585,37 +594,40 @@ formCols: [
const columns = useTableColumns ([
{ prop : 'code' , label : '编码' , width : 120 },
{ prop : 'name' , label : '名称' },
{ prop : '_actions' , label : '操作' , width : 160 , fixed : 'right' } // <EFBFBD> <EFBFBD> ?操作<E6938D> <E4BD9C> ?])
{ prop : '_actions' , label : '操作' , width : 160 , fixed : 'right' } // 操作列
])
```
**内部约定** : `prop === '_actions'` 自动标记<EFBFBD> <EFBFBD> ?`slot: '_actions'` ,由 `page-table` 渲染为操作列<EFBFBD> <EFBFBD> ?
**内部约定** : `prop === '_actions'` 自动标记为 `slot: '_actions'` ,由 `page-table` 渲染为操作列。
---
### 4.2 `useTableButtons(options, permissionCheck?)`
**作用** :统一生成工具栏按钮和行内操作按钮,自动注入权限校验结果<EFBFBD> <EFBFBD> ?
**作用** :统一生成工具栏按钮和行内操作按钮,自动注入权限校验结果。
| 参数 | 类型 | 说明 |
|------|------|------|
| `options.toolbar` | `Array` | 顶部按钮列表 |
| `options.row` | `Array` | 行内按钮列表 |
| `permissionCheck` | `Function` | 权限函数,通常<EFBFBD> <EFBFBD> ?`this.$permission` |
| `permissionCheck` | `Function` | 权限函数,通常为 `this.$permission` |
**返回<EFBFBD> <EFBFBD> ?*: `{ toolbarButtons, rowButtons }`
**返回值** : `{ toolbarButtons, rowButtons }`
**按钮字段<EFBFBD> <EFBFBD> ?*
**按钮字段: **
| 字段 | toolbar | row | 说明 |
|------|:---:|:---:|------|
| `key` | <EFBFBD> <EFBFBD> ?| <20> <> ? | 唯一标识 |
| `label` | <EFBFBD> <EFBFBD> ?| <20> <> ? | 显示文本 |
| `icon` | <EFBFBD> <EFBFBD> ?| <20> <> ? | Element UI 图标 |
| `type` | <EFBFBD> <EFBFBD> ?| <20> <> ? | `primary` /`success` /`warning` /`danger` |
| `color` | <EFBFBD> <EFBFBD> ?| <20> <> ? | `'danger'` 使文字变<EFBFBD> <EFBFBD> ? |
| `auth` | <EFBFBD> <EFBFBD> ?| <20> <> ? | 权限 key |
| `cssStyle` | <EFBFBD> <EFBFBD> ?| <20> <> ? | 自定义样<EFBFBD> <EFBFBD> ? |
| `onClick` | <EFBFBD> <EFBFBD> ?| <20> <> ? | 点击回调 |
| `hasPermission` | <EFBFBD> <EFBFBD> ?| <20> <> ? | **自动注入** ,由权限函数计算 |
| `needSelection` | <EFBFBD> <EFBFBD> ?| <20> <> ? | 需选中行才能点<EFBFBD> <EFBFBD> ? |
| `key` | √ | √ | 唯一标识 |
| `label` | √ | √ | 显示文本 |
| `icon` | √ | √ | Element UI 图标 |
| `type` | √ | — | `primary` /`success` /`warning` /`danger` |
| `color` | — | √ | `'danger'` 使文字变红 |
| `auth` | √ | √ | 权限 key |
| `cssStyle` | √ | √ | 自定义样式 |
| `onClick` | √ | √ | 点击回调 |
| `hasPermission` | √ | √ | **自动注入** ,由权限函数计算 |
| `needSelection` | √ | — | 需选中行才能点击 |
```js
const btns = useTableButtons ({
@@ -636,55 +648,63 @@ const btns = useTableButtons({
### 4.3 `i18nMixin(prefix)`
**作用** :注<EFBFBD> <EFBFBD> ?`key(suffix)` <EFBFBD> <EFBFBD> ?`ckey(suffix)` 两个方法,消除每个页面手<EFBFBD> <EFBFBD> ?`T` 常量<E5B8B8> <E9878F> ?`tkey` 方法<EFBFBD> <EFBFBD> ?
| 方法 | 返回<E8BF94> <E59B9E> ?| 说明 |
**作用** :注入 `key(suffix)` 和 `ckey(suffix)` 两个方法,消除每个页面手动定义常量和 `tkey` 方法。
| 方法 | 返回值 | 说明 |
|------|--------|------|
| `key(suffix)` | `prefix.suffix` | 当前页面<EFBFBD> <EFBFBD> ? i18n key |
| `ckey(suffix)` | `page.common.suffix` | 公共 i18n key(如"帮助"<22> <> ?确定"<22> <> ?取消"<22> <> ? |
| `key(suffix)` | `prefix.suffix` | 当前页面的 i18n key |
| `ckey(suffix)` | `page.common.suffix` | 公共 i18n key |
| 参数 | 类型 | 说明 |
|------|------|------|
| `prefix` | `String` | 该页面的 i18n key 前缀,如 `'page.production_master_data.factory_model.factory_area'` |
| `prefix` | `String` | 该页面的 i18n key 前缀 |
```js
import { i18nMixin } from '@/composables/useI18n'
export default {
mixins : [ i18nMixin ( 'page.production_master_data.factory_model.factory_area' )],
// 此后 模板中用 $t(key('code')) 访问当前页面的翻<EFBFBD> <EFBFBD> ? // <20> <> ?$t(ckey('help')) 访问公共翻译 'page.common.help'
// 模板中用 $t(key('code')) 访问当前页面翻译
// 模板中用 $t(ckey('help')) 访问公共翻译 'page.common.help'
// JS 中用 this.$t(this.key('code')) / this.$t(this.ckey('help'))
}
```
** `data()` 中翻译文本的技<EFBFBD> <EFBFBD> ?*<2A> <> ?
** `data()` 中 i18n key 传参技巧**:
```js
data () {
const t = this . $t . bind ( this ) // 绑定 i18n 翻译函数
const k = ( s ) => t ( this . key ( s )) // 当前页面翻译: key + translate
const ck = ( s ) => t ( this . ckey ( s )) // 公共翻译: ckey + translate
return {
// 传入完整 i18n key, 组件内部 $t() 翻译,切换语言自动响应
formCols : [
[{ label : k ( 'code' ), placeholder : k ( 'enter_code' ) }] // 页面<E9A1B5> <E99DA2> ?key
[{ label : this . key ( 'code' ), placeholder : this . key ( 'enter_code' ) }]
],
helpText : ck ( 'help' ) // 公共 key
rules : {
code : [{ required : true , message : this . key ( 'enter_code' ), trigger : 'blur' }]
}
}
}
```
> **原理**: `page-table` / `page-dialog-form` 内部使用 `$t()` 翻译传入的 key。`$t()` 是 Vue I18n 的响应式方法,切换语言时自动返回新的翻译结果,触发组件重新渲染。不要用 `k()` 等辅助函数提前翻译成静态字符串,否则语言切换后不会更新。
---
## 5. i18n 国际化方<EFBFBD> <EFBFBD> ?
### 5.1 语言包文<E58C85> <E69687> ?
## 5. i18n 国际化方案
### 5.1 语言包文件
| 语言 | 文件路径 |
|------|---------|
| 简体中<EFBFBD> <EFBFBD> ? | `src/locales/zh-chs.json` |
| 简体中文 | `src/locales/zh-chs.json` |
| 繁体中文 | `src/locales/zh-cht.json` |
| 英文 | `src/locales/en.json` |
| 日文 | `src/locales/ja.json` |
### 5.2 语言包结<EFBFBD> <EFBFBD> ?
<EFBFBD> <EFBFBD> ?`一级模<E7BAA7> <E6A8A1> ?<3F> <> ?二级模块 <20> <> ?三级模块` 三层嵌套<E5B58C> <E5A597> ?
### 5.2 语言包结构
按 `一级模块 > 二级模块 > 三级模块` 三层嵌套:
```json
{
"page" : {
@@ -693,20 +713,23 @@ data () {
"factory_area" : {
"search" : "查询" ,
"reset" : "重置" ,
"code" : "所区编<EFBFBD> <EFBFBD> ?,
" name": " 所区名<EFBFBD> <EFBFBD> ? ,
"add" : "<EFBFBD> <EFBFBD> ?<3F> <> ?,
" edit": " <EFBFBD> <EFBFBD> ?<3F> <> ?,
"delete" : "<EFBFBD> <EFBFBD> ?<3F> <> ?,
" enter_code": " 请输入所区编<EFBFBD> <EFBFBD> ?,
"enter_name" : "请输入所区名<EFBFBD> <EFBFBD> ?,
" add_title": " 新增所<EFBFBD> <EFBFBD> ? ,
"edit_title" : "编辑所<EFBFBD> <EFBFBD> ?,
"code" : "所区编码" ,
"name" : "所区名称" ,
"add" : "新 增" ,
"edit" : "编 辑" ,
"delete" : "删 除" ,
"enter_code" : " 请输入所区编码" ,
"enter_name" : "请输入所区名称" ,
"add_title" : "新增所区" ,
"edit_title" : "编辑所区" ,
"confirm" : "确定" ,
"cancel" : "取消" ,
"operation_success" : "操作成功"
}
}
},
"common" : {
"help" : "帮 助"
}
}
}
@@ -714,17 +737,22 @@ data () {
### 5.3 组件自动翻译
`page-table` <EFBFBD> <EFBFBD> ?`page-dialog-form` 内置 `$t()` <EFBFBD> <EFBFBD> ?
- **<2A> <> ?label** <20> <> ?自动翻译
- **按钮 label** <EFBFBD> <EFBFBD> ? 自动翻译
- **表单 label / placeholder** <20> <> ? 自动翻译
- **弹框 title / confirm / cancel** <20> <> ? 自动翻译
`page-table` 和 `page-dialog-form` 内部使用 `$t()` 翻译:
- **列 label** — 自动翻译
- **按钮 label** — 自动翻译
- **表单 label / placeholder** — 自动翻译
- **弹框 title / confirm / cancel** — 自动翻译
- **验证规则 message** — 自动翻译(`translatedRules` 计算属性)
页面只需传入 i18n key, 组件渲染时自动替换为当前语言的文本。
### 5.4 页面中手动翻译
搜索区、提示消息、`$confirm` 等 UI 文字需手动 `$t()` :
因此页面只需传入 i18n key 字符串, 组件渲染时自动替换为当前语言的文本<E69687> <E69CAC> ?
### 5.4 页面中手动翻<E58AA8> <E7BFBB> ?
搜索区、校验消息、`$confirm` <20> <> ?UI 文字需手动<E6898B> <E58AA8> ?`$t()` <EFBFBD> <EFBFBD> ?
```vue
<!-- 模板<EFBFBD> <EFBFBD> ? <EFBFBD> <EFBFBD> ? key () 返回 i18n key -->
<!-- 模板中 : key () 返回 i18n key -->
< el-form-item :label = "$t(key('code'))" >
< el-input :placeholder = "$t(key('enter_code'))" />
</ el-form-item >
@@ -732,7 +760,8 @@ data () {
```
```js
// JS <EFBFBD> <EFBFBD> ?this.$message.success(this.$t(this.key('operation_success')))
// JS 中
this . $message . success ( this . $t ( this . key ( 'operation_success' )))
this . $confirm ( this . $t ( this . key ( 'confirm_delete' )), this . $t ( this . key ( 'tip' )), { ... })
```
@@ -740,15 +769,18 @@ this.$confirm(this.$t(this.key('confirm_delete')), this.$t(this.key('tip')), { .
## 6. 常用场景速查
### 场景 1: 自定义列渲染( 状<EFBFBD> <EFBFBD> ?tag<61> <67> ?
列定义加<EFBFBD> <EFBFBD> ?`slot: true` <EFBFBD> <EFBFBD> ?
### 场景 1: 自定义列渲染( 状态 tag)
列定义加 `slot: true` :
```js
useTableColumns ([
{ prop : 'status' , label : ' 状 <EFBFBD> <EFBFBD> ? , slot : true , width : 100 }
{ prop : 'status' , label : '状态' , slot : true , width : 100 }
])
```
模板中用 `#col-status` 插槽<EFBFBD> <EFBFBD> ?
模板中用 `#col-status` 插槽:
```vue
< page-table :columns = "columns" :data = "tableData" >
< template # col -status =" { row } " >
@@ -759,7 +791,8 @@ useTableColumns([
</ page-table >
```
### 场景 2: 工具栏追加自定义按<EFBFBD> <EFBFBD> ?
### 场景 2: 工具栏追加自定义按钮
```vue
< page-table :columns = "columns" :toolbar-buttons = "toolbarButtons" >
< template # toolbar -extra >
@@ -768,7 +801,8 @@ useTableColumns([
</ page-table >
```
### 场景 3: 复选框<EFBFBD> <EFBFBD> ? + 序号<EFBFBD> <EFBFBD> ?
### 场景 3: 复选框 + 序号列
```js
useTableColumns (
[
@@ -779,47 +813,52 @@ useTableColumns(
)
```
### 场景 4: 带下拉选择的表<EFBFBD> <EFBFBD> ?
### 场景 4: 带下拉选择的表单
```js
formCols : [
[
{ type : 'select' , prop : 'area_id' ,
label : k ( 'area' ), placeholder : k ( 'select_area' ),
label : this . key ( 'area' ), placeholder : this . key ( 'select_area' ),
options : [{ label : 'A厂区' , value : 1 }, { label : 'B厂区' , value : 2 }],
clearable : true , style : { width : '90%' } }
]
]
```
### 场景 5: 批量删除( 工具<EFBFBD> <EFBFBD> ? + 需要选中行)
### 场景 5: 批量删除( 工具栏 + 需要选中行)
```js
useTableButtons ({
toolbar : [
{ key : 'batchDelete' , label : '批量删除' , icon : 'el-icon-delete' ,
type : 'danger' , auth : '/xxx/batch-delete' ,
needSelection : true , // <EFBFBD> <EFBFBD> ? 需要选中行才能点<EFBFBD> <EFBFBD> ? onClick: this.batchDelete }
needSelection : true , // 需要选中行才能点击
onClick : this . batchDelete }
]
})
```
### 场景 6: 表格自适应高度
只需<EFBFBD> <EFBFBD> ?`auto-height` 属性:
只需加 `auto-height` 属性:
```vue
< page-table ... auto -height />
```
表格高度会 自动填<EFBFBD> <EFBFBD> ?`d2-container` <EFBFBD> <EFBFBD> ? body 区域剩余空间,窗<EFBFBD> <EFBFBD> ? resize / 侧栏折叠时自动重算<EFBFBD> <EFBFBD> ?
### 场景 7: 帮助按<E58AA9> <E68C89> ?
<EFBFBD> <EFBFBD> ?`help-url` 传值即可在工具栏最右侧显示问号帮助按钮, 点击在新窗口打开帮助文档<E69687> <E6A1A3> ?
表格高度自动填满 `d2-container` 的 body 区域剩余空间,窗口 resize / 侧栏折叠时自动重算。
### 场景 7: 帮助按钮
传 `help-url` 即可在工具栏最右侧显示问号帮助按钮:
```vue
< page-table ... help -url ="/ help / factory -area " :help-text = "$t(ckey('help'))" />
```
按钮文字通过 `help-text` prop 支持 i18n。 推荐使用公<EFBFBD> <EFBFBD> ? key `page.common.help` (模板中<EFBFBD> <EFBFBD> ?`$t(ckey('help'))` ) , 所有页面共享, 无需每个模块重复定义<EFBFBD> <EFBFBD> ?
不传 `help-url` 或传空字符串则不显示帮助按钮<E68C89> <E992AE> ?
推荐使用公共 key `page.common.help` (模板中用 `$t(ckey('help'))` ) 。不传 `help-url` 则不显示。
---
## 7. 路由配置
@@ -845,7 +884,8 @@ export default {
}
```
之后<EFBFBD> <EFBFBD> ?`src/router/routes.js` 中引入该模块并加<EFBFBD> <EFBFBD> ?`frameIn` 数组<EFBFBD> <EFBFBD> ?
之后在 `src/router/routes.js` 中引入该模块并加入 `frameIn` 数组:
```js
import productionConfiguration from './modules/production-master-data'
@@ -908,46 +948,43 @@ export function deleteFactoryArea (data) {
---
## 9. 旧代码迁移对<EFBFBD> <EFBFBD> ?
| 旧写<E697A7> <E58699> ?| 新写<E696B0> <E58699> ?|
## 9. 旧代码迁移对照
| 旧写法 | 新写法 |
|--------|--------|
| 手动构建 `columns: [{ idx: 0, attrs: { prop, label } }]` | `useTableColumns([{ prop, label }])` |
| `buttonList: [...] ` + `tableButtonList: [...]` 分两<E58886> <E4B8A4> ? | `useTableButtons({ toolbar: [...], row: [...] })` |
| `<sct-base-table>` + `<sct-base-dialog>` + `<SctBaseForm>` 三层 | `<page-table>` + `<page-dialog-form>` 两层 |
| 手动构建 `columns` 带 `idx` 序号 | `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>` 组件 | 需要时自行添加 |
| 分页需要额<EFBFBD> <EFBFBD> ? `<page-footer>` | `page-table` 内置分页 |
| 每个页面定义 `T` 常量 + `tkey()` 方法 | `mixins: [i18nMixin(prefix)]` ,使<EFBFBD> <EFBFBD> ?`this.key('xxx')` |
| 表单字段<E5AD97> <E6AEB5> ?i18n raw key, 子组件翻译 | `data()` 中用 `k(prop )` 提前翻译 |
| 额外 `<page-footer>` 分页 | `page-table` 内置分页 |
| 每个页面定义 `T` 常量 + `tkey()` 方法 | `mixins: [i18nMixin(prefix)]` ,使用 `this.key('xxx')` |
| `data()` 中用 `k()` 提前翻译 | 用 `this.key()` 传 key, 组件内部 `$t()` 翻译 |
---
## 10. 常见问题排查
### Q1: 弹框打开后不显示内容<EFBFBD> <EFBFBD> ?
确认 `formCols` 中的 `label` <20> <> ?`placeholder` 是否<E698AF> <E590A6> ?`data()` 阶段已翻译。子组件 `page-dialog-form` 会调<E4BC9A> <E8B083> ?`$t()` 处理 label, 但建议<E5BBBA> <E8AEAE> ?`data()` 阶段就用 `k()` 提前翻译好,避免 webpack HMR 缓存问题<E997AE> <E9A298> ?
### Q1: 弹框打开后不显示内容?
确认 `formCols` 中的 `label` 和 `placeholder` 是否传入了正确的 i18n key。应该用 `this.key()` 拼接完整 key, 不要用 `k()` 提前翻译成静态字符串。
### Q2: 表格高度自适应不生效?
确认 `d2-container` <EFBFBD> <EFBFBD> ? body 区域高度被正确约束( flex 填充)。如<EFBFBD> <EFBFBD> ?`d2-container` 本身<E69CAC> <E8BAAB> ?`overflow: auto` 或其他高度约束问题,`page-table` <20> <> ?`height: 100%` 会失效。 给 `d2-container` <EFBFBD> <EFBFBD> ? body 部分<EFBFBD> <EFBFBD> ?`overflow: hidden` 通常可解决<EFBFBD> <EFBFBD> ?
确认 `d2-container` 的 body 区域高度被正确约束( flex 填充)。给 `d2-container` 的 body 部分设置 `overflow: hidden` 通常可解决。
### Q3: 权限校验有按钮不显示?
`useTableButtons` 的第二个参数必须<EFBFBD> <EFBFBD> ?`this.$permission` 。如果项目没有注册这个全局方法,可以在 `useTableButtons` 中传入一个返<E4B8AA> <E8BF94> ?`true` 的占位函数: `() => true` <EFBFBD> <EFBFBD> ?
`useTableButtons` 的第二个参数必须传入 `this.$permission` 。如果项目没有注册这个全局方法,可以传入 `() => true` 占位。
### Q4: 新增一条后页码不跳回第一页?
<EFBFBD> <EFBFBD> ?`onDialogSubmit` 成功后调<EFBFBD> <EFBFBD> ?`this.fetchData()` 前,如果删除了 当前页最后一行导<EFBFBD> <EFBFBD> ?`total - 1` 超出范围,需手动修正页码<EFBFBD> <EFBFBD> ?
```js
this . pagination . current = Math . min (
this . pagination . current ,
Math . ceil (( this . pagination . total - 1 ) / this . pagination . size ) || 1
)
```
在 `onDialogSubmit` 成功后调用 `this.fetchData()` 前,如果删除当前页最后一行导致 `total - 1` 超出范围,需手动修正页码。
### Q5: 表单验证不提示错误文字<EFBFBD> <EFBFBD> ?
确认 `page-dialog-form` <20> <> ?`<style>` 块存在(默认 22px `margin-bottom` + `position: absolute` 错误文字)。如果表单项之间被其他样式覆盖,检查是否有全局 CSS 重置<E9878D> <E7BDAE> ?`.el-form-item` <20> <> ?margin<69> <6E> ?
### Q6: 如何新增一<E5A29E> <E4B880> ?CRUD 页面最快?
### Q5: 切换语言后弹框/表格内容不更新?
1. 复制 `src/views/production-master-data/factory-model/factory-area/index.vue`
2. 替换 API 引用(`import { getList, create, edit, ... } from '@/api/xxx'` <EFBFBD> <EFBFBD> ?3. 修改 `i18nMixin` 参数、`columns` 、`formCols` 、`rules`
4. <20> <> ?`zh-chs.json` <20> <> ?`en.json` 中添加语言<E8AFAD> <E8A880> ?5. 添加路由配置
检查 `data()` 中是否使用了 `k()` 等辅助函数提前翻译。应该用 `this.key()` 传 i18n key, 由组件内部 `$t()` 处理翻译,这样切换语言时才能自动响应。
平均 10 分钟即可完成一个标<E4B8AA> <E6A087> ?CRUD 页面<E9A1B5> <E99DA2> ?
### Q6: 表单验证错误提示显示为原始 i18n key?
`page-dialog-form` 会通过 `translatedRules` 计算属性自动翻译验证规则的 `message` 字段。确认传入的 `rules.message` 使用了 `this.key()` 传入完整 key。