管理系统table列表操作列, 随着按钮的数量越来越多会不断加宽操作列, 感觉很不好, 对此我封装了这个自动把多余的按钮放到更多菜单下

MoreOperation/index.vue
menu组件我这是ant的, 可以自行替换为其他框架的
<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> <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
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() { } }
使用方法:
实现了配置模式和slot模式, slot模式支持v-if来控制按钮显示隐藏
-
- <MoreOperation :btns="operationBtns" :row="row" :operatorWidth.sync="operatorColumnWidth">
- <template #del="item">
- <div class="my-text-btn" style="color:red;">{{ item.name }}div>
- template>
- MoreOperation>
-
-
- <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)
- })
- },
-
- }
- }
效果图
