管理系统table列表操作列, 随着按钮的数量越来越多会不断加宽操作列, 感觉很不好, 对此我封装了这个自动把多余的按钮放到更多菜单下
MoreOperation/index.vue
menu组件我这是ant的, 可以自行替换为其他框架的<template> <div class="table-operations-group"> <template v-if="btns"> <div class="option-btn-wrap" v-for="(item, index) in outerBtns" :key="index" @click="clickBtn(item)" :class="{ 'disabled': item.disabled === true }"> <slot v-if="item.customRender" :name="item.customRender" v-bind="item"> <div class="my-text-btn">{{ item.name }}</div> </slot> <div v-else class="my-text-btn"> {{ item.name }} </div> </div> </template> <template v-if="!btns"> <div class="option-btn-wrap" v-for="(item, index) in outerBtns" :key="index" @click="clickBtn(item)" :class="{ 'disabled': item.disabled === true }"> <component :is="slotNode(item.vnode)" /> </div> </template> <div class="option-btn-wrap" v-if="moreBtns.length > 0"> <a-dropdown> <div class="my-text-btn"> 更多 <a-icon type="down" /> </div> <a-menu slot="overlay" @click="dropdownMenu"> <a-menu-item v-for="(item, index) in moreBtns" :key="index" :disabled="item.disabled"> {{ item.name }} </a-menu-item> </a-menu> </a-dropdown> </div> </div> </template> <script> /** * MoreOperation 组件 * 1. 配置模式 * 2. slot模式 * @author chengxg * @since 2023-09-15 */ let weekTableMap = new WeakMap() import OperationItem, { MoreOperationItemProp } from "./OperationItem.vue" // 根据模版创建对象 function createObjByTpl(obj, tpl) { let newObj = {} for (let f in tpl) { if (typeof obj[f] === 'undefined') { newObj[f] = tpl[f] } else { newObj[f] = obj[f] } } return newObj } export { OperationItem } export default { name: 'MoreOperation', comments: {}, props: { // 表格所有数据 tableData: { type: Array, default: null }, // 表格行数据 row: { type: Object, required: true, default: null }, // 配置模式 所有的按钮 btns: { type: Array, // [MoreOperationItemProp] default: null }, // 外边的按钮数量 outerBtnNum: { type: Number, default: 2 } }, provide() { return { 'moreOperations': this }; }, data() { return { items: [] } }, computed: { activeBtns() { let btns = this.btns || this.items || [] return btns.filter((item) => { if (typeof item.disabled === 'function') { item.disabled = item.disabled(this.row, item.params) } if (typeof item.show === 'function') { return item.show(this.row, item.params) } return true }) }, outerBtns() { if (this.activeBtns.length <= this.outerBtnNum + 1) { return this.activeBtns } return this.activeBtns.slice(0, this.outerBtnNum) }, moreBtns() { if (this.activeBtns.length <= this.outerBtnNum + 1) { return [] } return this.activeBtns.slice(this.outerBtnNum) } }, watch: { }, created() { this.updateSlotBtns() this.autoCalcMaxWidth() this.$watch(() => { return this.row }, (newVal, oldVal) => { this.$nextTick(() => { this.updateSlotBtns() this.$nextTick(() => { this.autoCalcMaxWidth() }) }) }) }, methods: { // 计算当前列最大宽度 autoCalcMaxWidth() { if (!this.row) { return } let width = 0 for (let item of this.outerBtns) { width += item.width } // more 宽度60 if (this.moreBtns.length > 1) { width += 60 } this.row.btnsMaxWidth = width this.calcOperationWidth() }, // 计算表格操作列的最大宽度 calcOperationWidth() { let tableData = this.tableData || this.$parent.tableData if (!tableData) { return } if (weekTableMap[tableData]) { clearTimeout(weekTableMap[tableData]) } weekTableMap[tableData] = setTimeout(() => { let maxWidth = 120; for (const row of tableData) { if (row.btnsMaxWidth > maxWidth) { maxWidth = row.btnsMaxWidth; } } let operatorColumnWidth = maxWidth + 10; this.$emit("update:operatorWidth", operatorColumnWidth) // 使用vxeTable组件库 自动调整最大操作列 列宽 if (this.$parent.$parent.recalculate) { this.$parent.$parent.recalculate(true) } }, 10) }, clickBtn(item) { item.click && item.click(this.row, item.params) }, dropdownMenu(command) { let btnItem = this.moreBtns[command.key] if (btnItem && typeof btnItem.click === 'function') { btnItem.click(this.row, btnItem.params) } }, updateSlotBtns() { if (!this.btns) { if (this.$slots.default) { this.items = this.$slots.default.filter((item) => { if (item && item.componentOptions && item.componentOptions.tag == 'OperationItem') { return true } return false }).map((item) => { let obj = createObjByTpl(item.componentOptions.propsData, MoreOperationItemProp) if (item.componentOptions.propsData.item) { Object.assign(obj, item.componentOptions.propsData.item) } obj.vnode = item return obj }) } else { this.items = [] } } }, slotNode(vnode) { return { render(h) { return vnode; } } }, }, mounted() { } } </script> <style lang="scss"> .table-operations-group { display: flex; justify-content: flex-start; align-items: center; .option-btn-wrap { display: flex; justify-content: flex-start; align-items: center; cursor: pointer; user-select: none; &::after { display: block; content: ' '; width: 0; height: 14px; border-left: 1px solid #eeeeee; margin: 0 4px; } &:last-child { &::after { display: none; } } &.disabled { pointer-events: none; .my-text-btn { cursor: no-drop; color: #bfc2cc; &:active { background: none; } } } } .my-text-btn { display: inline-flex; justify-content: center; align-items: center; cursor: pointer; user-select: none; border-radius: 1px; padding: 3px 5px; height: 30px; vertical-align: middle; font-size: 14px; font-weight: 400; color: #3471ff; &:active { background: rgba(29, 111, 255, 0.06); } &.disabled { cursor: no-drop; color: #bfc2cc; &:active { background: none; } } .btn-icon { i, &.el-icon, &.icon-svg, &.svg-icon { display: inline-flex; justify-content: center; align-items: center; margin-right: 4px; width: 12px; height: 12px; } } .btn-icon-right { i, &.el-icon, &.icon-svg, &.svg-icon { display: inline-flex; justify-content: center; align-items: center; margin-right: 4px; width: 12px; height: 12px; } } } } </style>
MoreOperation/OperationItem.vue
<script> export const MoreOperationItemProp = { name: "", // 按钮名 width: 50, // 按钮所占的宽度 params: null, // show, click, disabled 传入的第二个参数 // 是否显示回调 show: (row, params) => { return true }, // 点击回调 click: (row, params) => { }, // 是否禁用, 可以为函数 disabled: false, // 配置模式下的自定义渲染slot customRender: "" } export default { name: 'OperationItem', props: { name: { type: String, default: "" }, width: { type: Number, default: 50 }, params: { default: null }, show: { type: Function, default: null }, click: { type: Function, default: null }, disabled: { type: [Boolean, Function], default: false }, // 对象形式赋值 item: { type: Object, default: null // MoreOperationItemProp } }, data() { return { } }, computed: { }, watch: { }, created() { }, render(h) { if (!this.$slots.default) { let btnName = this.name if (this.item && this.item.name) { btnName = this.item.name } return h('div', { class: 'my-text-btn' }, btnName) } return this.$slots.default }, methods: { }, beforeDestroy() { } } </script>
使用方法:
实现了配置模式和slot模式, slot模式支持v-if来控制按钮显示隐藏
<!-- 1. 配置模式 -->
<MoreOperation :btns="operationBtns" :row="row" :operatorWidth.sync="operatorColumnWidth">
<template #del="item">
<div class="my-text-btn" style="color:red;">{{ item.name }}</div>
</template>
</MoreOperation>
<!-- 2. slot模式 -->
<MoreOperation :row="row" :operatorWidth.sync="operatorColumnWidth">
<OperationItem name="详情" :click="operationBtns[0].click"></OperationItem>
<OperationItem v-if="operationBtns[1].show(row)" :width="80" name="历史记录" :click="operationBtns[1].click"></OperationItem>
<OperationItem v-if="operationBtns[2].show(row)" :width="50" name="删除" :click="operationBtns[2].click">
<div class="my-text-btn" style="color:red;">删除</div>
</OperationItem>
<OperationItem :item="operationBtns[3]"></OperationItem>
<OperationItem :item="operationBtns[4]"></OperationItem>
<OperationItem v-if="operationBtns[5].show(row)" :width="50" name="禁用" :click="operationBtns[5].click"></OperationItem>
<OperationItem :item="operationBtns[6]"></OperationItem>
</MoreOperation>
import MoreOperation, { OperationItem } from "@/components/MoreOperation/index.vue"
export default {
name: "example",
components: { MoreOperation, OperationItem },
data() {
return {
loading: false,
searchForm: {
name: "",
},
page: {
pageNum: 1,
pageSize: 20,
total: 0,
}, // 分页信息
tableData: [],
operatorColumnWidth: 120,
operationBtns: [{
name: "详情",
width: 45,
params: null,
show: (row, params) => {
return Math.random() > 0.1
},
click: (row, params) => {
console.log("click 详情")
},
disabled: false,
customRender: ""
}, {
name: "删除",
width: 45,
params: null,
show: (row, params) => {
return Math.random() > 0.5
},
click: (row, params) => {
console.log("click 删除")
},
disabled: false,
customRender: "del",
}, {
name: "历史记录",
width: 80,
show: (row) => {
return Math.random() > 0.8
},
click: (row) => {
console.log("click 历史记录")
},
}, {
name: "修改",
width: 45,
show: (row) => {
return Math.random() > 0.5
},
click: (row) => {
console.log("click 修改")
},
disabled: true
}, {
name: "撤回",
width: 45,
show: (row) => {
return Math.random() > 0.5
},
click: (row) => {
console.log("click 撤回")
}
}, {
name: "禁用",
width: 45,
show: (row) => {
return Math.random() > 0.5
},
click: (row) => {
console.log("click 禁用")
}
}, {
name: "流程跟踪",
width: 80,
show: (row) => {
return Math.random() > 0.8
},
click: (row) => {
console.log("click 流程跟踪")
},
disabled: true
}],
};
},
created() {
},
mounted() {
this.onSearch()
},
methods: {
onSearch(page = {}) {
if (this.loading) {
return
}
let params = {
...this.searchForm,
...this.page,
...page,
}
this.loading = true
this.fetchData(params).then((data) => {
this.loading = false
this.tableData = data.list
updatePageInfo(this.page, data)
this.showBtn = true
setTimeout(() => {
// this.showBtn = false
}, 2000)
}).catch((err) => {
this.loading = false
this.tableData = []
})
},
fetchData(params) {
return new Promise((resolve, reject) => {
let data = dataListPage({
name: "@cname",
"sex|1": ["男", "女"],
age: "@natural(1, 100)",
city: "@city",
hobby: "@csentence(2, 10)",
createName: "@cname",
createTime: "@date",
}, params).data
console.log(data)
resolve(data)
})
},
}
}
效果图