前言
先上图看右侧列 action 的 UI 效果:
正常来说,如果一个表格的附带 action 操作,我们一般会放在最右侧的列里面实现,这个时候有些UI 框架支持在 SFC 模板里面定义额外的 solt,当然如果不支持,更通用的做法是通过 vue 的 h 函数来实现,纯粹用 js 或 ts 组装组件方式实现,这种方式很灵活,但有一个弊端,当定义的组件很多的时候,比如上图有4 个Button,还得定义按钮样式和点击事件,代码就显的非常乱。
更为优雅的做法是,把这一小块的功能给单独定义成一个 vue 组件,可以用 SFC 也可以用 JSX/TSX 实现,然后事件通过回调完成,代码就显得非常整洁了。
定义 Action 组件
这里用 tsx 实现:
import { defineComponent, PropType, toRefs } from 'vue'
import { NSpace, NTooltip, NButton, NIcon, NPopconfirm } from 'naive-ui'
import {AreaChartOutlined, DeleteOutlined, EditOutlined,
PlayCircleOutlined,PauseCircleOutlined,RedoOutlined,AlignCenterOutlined} from "@vicons/antd";
import {SolrListActionModel} from "@/service/middleware/types";
const props = {
row: {
type: SolrListActionModel as PropType<SolrListActionModel>
}
}
export default defineComponent({
name: 'SolrTableAction',
props,
emits: [
'start',
'stop',
'restart',
'showLog',
],
setup(props, ctx) {
const handleStartAction = () => {
ctx.emit('start')
}
const handleStopAction = () => {
ctx.emit('stop')
}
const handleRestartAction = () => {
ctx.emit('restart')
}
const handleShowLogAction = () => {
ctx.emit('showLog')
}
return {
handleStartAction,
handleStopAction,
handleRestartAction,
handleShowLogAction,
...toRefs(props)
}
},
render() {
return (
<NSpace>
{this.row?.start ? <NTooltip trigger={'hover'}>
{{
default: () => "启动",
trigger: () => (
<NButton
size='small'
type='info'
tag='div'
circle
onClick={this.handleStartAction}
class='btn-edit'
>
<NIcon>
<PlayCircleOutlined></PlayCircleOutlined>
</NIcon>
</NButton>
)
}}
</NTooltip>
:""
}
{
this.row?.stop?<NTooltip trigger={'hover'}>
{{
default: () => "停止",
trigger: () => (
<NButton
size='small'
type='info'
tag='div'
circle
onClick={this.handleStopAction}
class='btn-edit'
>
<NIcon>
<PauseCircleOutlined></PauseCircleOutlined>
</NIcon>
</NButton>
)
}}
</NTooltip>:
""
}
{
this.row?.restart?<NTooltip trigger={'hover'}>
{{
default: () => "重启",
trigger: () => (
<NButton
size='small'
type='info'
tag='div'
circle
onClick={this.handleRestartAction}
class='btn-edit'
>
<NIcon>
<RedoOutlined></RedoOutlined>
</NIcon>
</NButton>
)
}}
</NTooltip>:
""
}
{
this.row?.showLog?<NTooltip trigger={'hover'}>
{{
default: () => "日志",
trigger: () => (
<NButton
size='small'
type='info'
tag='div'
circle
onClick={this.handleShowLogAction}
class='btn-edit'
>
<NIcon>
<AlignCenterOutlined></AlignCenterOutlined>
</NIcon>
</NButton>
)
}}
</NTooltip>:
""
}
{this.$slots.extraAction?.()}
</NSpace>
)
}
})
在 Table 列里面引用
定义好之后,在 table 里面引用就非常简单了,只需要把 props 属性传递一下,然后定义回调处理事件即可:
const columns = [
{
title: '服务名',
key: 'server_name',
align: 'center'
},
{
title: '运行状态',
key: 'process_state',
align: 'center'
},
{
title: '主机IP',
key: 'process_ip',
align: 'center'
},
{
title: '启动信息',
key: 'process_description',
align: 'center'
},
{
title: '操作',
key: 'operation',
//列宽自适应
...COLUMN_WIDTH_CONFIG['operation'](4),
align: 'center',
render: (row: SolrList)=>{
let model=new SolrListActionModel(row)
return h(SolrTableAction, {
row: model,
onStart: () => {
},
onStop:()=>{
},
onRestart:()=>{
},
onShowLog:()=>{
}
})
}
}
] as TableColumns<any>
列宽自适应工具类
这个是为了该列的宽度,根据实际情况重新分布:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isNumber, sumBy } from 'lodash'
import type {
TableColumns,
CommonColumnInfo
} from 'naive-ui/es/data-table/src/interface'
export const COLUMN_WIDTH_CONFIG = {
selection: {
width: 50
},
index: {
width: 50
},
linkName: {
width: 200
},
linkEllipsis: {
style: 'max-width: 180px;line-height: 1.5'
},
name: {
width: 200,
ellipsis: {
tooltip: true
}
},
state: {
width: 120
},
type: {
width: 130
},
version: {
width: 80
},
time: {
width: 180
},
timeZone: {
width: 220
},
operation: (number: number): CommonColumnInfo => ({
fixed: 'right',
width: Math.max(30 * number + 12 * (number - 1) + 24, 100)
}),
userName: {
width: 120,
ellipsis: {
tooltip: true
}
},
ruleType: {
width: 120
},
note: {
width: 180,
ellipsis: {
tooltip: true
}
},
dryRun: {
width: 140
},
times: {
width: 120
},
duration: {
width: 120
},
yesOrNo: {
width: 100,
ellipsis: {
tooltip: true
}
},
size: {
width: 100
},
tag: {
width: 160
},
copy: {
width: 50
}
}
export const calculateTableWidth = (columns: TableColumns) =>
sumBy(columns, (column) => (isNumber(column.width) ? column.width : 0))
export const DefaultTableWidth = 1800
总结
通用这种单独定义组件的方式,大大提高了我们代码的可读性,并且后续这个组件可以单独扩展和改变样式而不影响主列表页的迭代,最后我们要注意给 h 组件的传递 props 的方式,直接使用 props name:value 的形式即可。