• vue管理系统列表行按钮过多, 封装更多组件


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

    MoreOperation/index.vue
    menu组件我这是ant的, 可以自行替换为其他框架的

    1. <script>
    2. /**
    3. * MoreOperation 组件
    4. * 1. 配置模式
    5. * 2. slot模式
    6. * @author chengxg
    7. * @since 2023-09-15
    8. */
    9. let weekTableMap = new WeakMap()
    10. import OperationItem, { MoreOperationItemProp } from "./OperationItem.vue"
    11. // 根据模版创建对象
    12. function createObjByTpl(obj, tpl) {
    13. let newObj = {}
    14. for (let f in tpl) {
    15. if (typeof obj[f] === 'undefined') {
    16. newObj[f] = tpl[f]
    17. } else {
    18. newObj[f] = obj[f]
    19. }
    20. }
    21. return newObj
    22. }
    23. export { OperationItem }
    24. export default {
    25. name: 'MoreOperation',
    26. comments: {},
    27. props: {
    28. // 表格所有数据
    29. tableData: {
    30. type: Array,
    31. default: null
    32. },
    33. // 表格行数据
    34. row: {
    35. type: Object,
    36. required: true,
    37. default: null
    38. },
    39. // 配置模式 所有的按钮
    40. btns: {
    41. type: Array,
    42. // [MoreOperationItemProp]
    43. default: null
    44. },
    45. // 外边的按钮数量
    46. outerBtnNum: {
    47. type: Number,
    48. default: 2
    49. }
    50. },
    51. provide() {
    52. return {
    53. 'moreOperations': this
    54. };
    55. },
    56. data() {
    57. return {
    58. items: []
    59. }
    60. },
    61. computed: {
    62. activeBtns() {
    63. let btns = this.btns || this.items || []
    64. return btns.filter((item) => {
    65. if (typeof item.disabled === 'function') {
    66. item.disabled = item.disabled(this.row, item.params)
    67. }
    68. if (typeof item.show === 'function') {
    69. return item.show(this.row, item.params)
    70. }
    71. return true
    72. })
    73. },
    74. outerBtns() {
    75. if (this.activeBtns.length <= this.outerBtnNum + 1) {
    76. return this.activeBtns
    77. }
    78. return this.activeBtns.slice(0, this.outerBtnNum)
    79. },
    80. moreBtns() {
    81. if (this.activeBtns.length <= this.outerBtnNum + 1) {
    82. return []
    83. }
    84. return this.activeBtns.slice(this.outerBtnNum)
    85. }
    86. },
    87. watch: {
    88. },
    89. created() {
    90. this.updateSlotBtns()
    91. this.autoCalcMaxWidth()
    92. this.$watch(() => {
    93. return this.row
    94. }, (newVal, oldVal) => {
    95. this.$nextTick(() => {
    96. this.updateSlotBtns()
    97. this.$nextTick(() => {
    98. this.autoCalcMaxWidth()
    99. })
    100. })
    101. })
    102. },
    103. methods: {
    104. // 计算当前列最大宽度
    105. autoCalcMaxWidth() {
    106. if (!this.row) {
    107. return
    108. }
    109. let width = 0
    110. for (let item of this.outerBtns) {
    111. width += item.width
    112. }
    113. // more 宽度60
    114. if (this.moreBtns.length > 1) {
    115. width += 60
    116. }
    117. this.row.btnsMaxWidth = width
    118. this.calcOperationWidth()
    119. },
    120. // 计算表格操作列的最大宽度
    121. calcOperationWidth() {
    122. let tableData = this.tableData || this.$parent.tableData
    123. if (!tableData) {
    124. return
    125. }
    126. if (weekTableMap[tableData]) {
    127. clearTimeout(weekTableMap[tableData])
    128. }
    129. weekTableMap[tableData] = setTimeout(() => {
    130. let maxWidth = 120;
    131. for (const row of tableData) {
    132. if (row.btnsMaxWidth > maxWidth) {
    133. maxWidth = row.btnsMaxWidth;
    134. }
    135. }
    136. let operatorColumnWidth = maxWidth + 10;
    137. this.$emit("update:operatorWidth", operatorColumnWidth)
    138. // 使用vxeTable组件库 自动调整最大操作列 列宽
    139. if (this.$parent.$parent.recalculate) {
    140. this.$parent.$parent.recalculate(true)
    141. }
    142. }, 10)
    143. },
    144. clickBtn(item) {
    145. item.click && item.click(this.row, item.params)
    146. },
    147. dropdownMenu(command) {
    148. let btnItem = this.moreBtns[command.key]
    149. if (btnItem && typeof btnItem.click === 'function') {
    150. btnItem.click(this.row, btnItem.params)
    151. }
    152. },
    153. updateSlotBtns() {
    154. if (!this.btns) {
    155. if (this.$slots.default) {
    156. this.items = this.$slots.default.filter((item) => {
    157. if (item && item.componentOptions && item.componentOptions.tag == 'OperationItem') {
    158. return true
    159. }
    160. return false
    161. }).map((item) => {
    162. let obj = createObjByTpl(item.componentOptions.propsData, MoreOperationItemProp)
    163. if (item.componentOptions.propsData.item) {
    164. Object.assign(obj, item.componentOptions.propsData.item)
    165. }
    166. obj.vnode = item
    167. return obj
    168. })
    169. } else {
    170. this.items = []
    171. }
    172. }
    173. },
    174. slotNode(vnode) {
    175. return {
    176. render(h) {
    177. return vnode;
    178. }
    179. }
    180. },
    181. },
    182. mounted() {
    183. }
    184. }
    185. script>
    186. <style lang="scss">
    187. .table-operations-group {
    188. display: flex;
    189. justify-content: flex-start;
    190. align-items: center;
    191. .option-btn-wrap {
    192. display: flex;
    193. justify-content: flex-start;
    194. align-items: center;
    195. cursor: pointer;
    196. user-select: none;
    197. &::after {
    198. display: block;
    199. content: ' ';
    200. width: 0;
    201. height: 14px;
    202. border-left: 1px solid #eeeeee;
    203. margin: 0 4px;
    204. }
    205. &:last-child {
    206. &::after {
    207. display: none;
    208. }
    209. }
    210. &.disabled {
    211. pointer-events: none;
    212. .my-text-btn {
    213. cursor: no-drop;
    214. color: #bfc2cc;
    215. &:active {
    216. background: none;
    217. }
    218. }
    219. }
    220. }
    221. .my-text-btn {
    222. display: inline-flex;
    223. justify-content: center;
    224. align-items: center;
    225. cursor: pointer;
    226. user-select: none;
    227. border-radius: 1px;
    228. padding: 3px 5px;
    229. height: 30px;
    230. vertical-align: middle;
    231. font-size: 14px;
    232. font-weight: 400;
    233. color: #3471ff;
    234. &:active {
    235. background: rgba(29, 111, 255, 0.06);
    236. }
    237. &.disabled {
    238. cursor: no-drop;
    239. color: #bfc2cc;
    240. &:active {
    241. background: none;
    242. }
    243. }
    244. .btn-icon {
    245. i,
    246. &.el-icon,
    247. &.icon-svg,
    248. &.svg-icon {
    249. display: inline-flex;
    250. justify-content: center;
    251. align-items: center;
    252. margin-right: 4px;
    253. width: 12px;
    254. height: 12px;
    255. }
    256. }
    257. .btn-icon-right {
    258. i,
    259. &.el-icon,
    260. &.icon-svg,
    261. &.svg-icon {
    262. display: inline-flex;
    263. justify-content: center;
    264. align-items: center;
    265. margin-right: 4px;
    266. width: 12px;
    267. height: 12px;
    268. }
    269. }
    270. }
    271. }
    272. style>

    MoreOperation/OperationItem.vue

    使用方法:

    实现了配置模式和slot模式, slot模式支持v-if来控制按钮显示隐藏

    1. <MoreOperation :btns="operationBtns" :row="row" :operatorWidth.sync="operatorColumnWidth">
    2. <template #del="item">
    3. <div class="my-text-btn" style="color:red;">{{ item.name }}div>
    4. template>
    5. MoreOperation>
    6. <MoreOperation :row="row" :operatorWidth.sync="operatorColumnWidth">
    7. <OperationItem name="详情" :click="operationBtns[0].click">OperationItem>
    8. <OperationItem v-if="operationBtns[1].show(row)" :width="80" name="历史记录" :click="operationBtns[1].click">OperationItem>
    9. <OperationItem v-if="operationBtns[2].show(row)" :width="50" name="删除" :click="operationBtns[2].click">
    10. <div class="my-text-btn" style="color:red;">删除div>
    11. OperationItem>
    12. <OperationItem :item="operationBtns[3]">OperationItem>
    13. <OperationItem :item="operationBtns[4]">OperationItem>
    14. <OperationItem v-if="operationBtns[5].show(row)" :width="50" name="禁用" :click="operationBtns[5].click">OperationItem>
    15. <OperationItem :item="operationBtns[6]">OperationItem>
    16. MoreOperation>
    1. import MoreOperation, { OperationItem } from "@/components/MoreOperation/index.vue"
    2. export default {
    3. name: "example",
    4. components: { MoreOperation, OperationItem },
    5. data() {
    6. return {
    7. loading: false,
    8. searchForm: {
    9. name: "",
    10. },
    11. page: {
    12. pageNum: 1,
    13. pageSize: 20,
    14. total: 0,
    15. }, // 分页信息
    16. tableData: [],
    17. operatorColumnWidth: 120,
    18. operationBtns: [{
    19. name: "详情",
    20. width: 45,
    21. params: null,
    22. show: (row, params) => {
    23. return Math.random() > 0.1
    24. },
    25. click: (row, params) => {
    26. console.log("click 详情")
    27. },
    28. disabled: false,
    29. customRender: ""
    30. }, {
    31. name: "删除",
    32. width: 45,
    33. params: null,
    34. show: (row, params) => {
    35. return Math.random() > 0.5
    36. },
    37. click: (row, params) => {
    38. console.log("click 删除")
    39. },
    40. disabled: false,
    41. customRender: "del",
    42. }, {
    43. name: "历史记录",
    44. width: 80,
    45. show: (row) => {
    46. return Math.random() > 0.8
    47. },
    48. click: (row) => {
    49. console.log("click 历史记录")
    50. },
    51. }, {
    52. name: "修改",
    53. width: 45,
    54. show: (row) => {
    55. return Math.random() > 0.5
    56. },
    57. click: (row) => {
    58. console.log("click 修改")
    59. },
    60. disabled: true
    61. }, {
    62. name: "撤回",
    63. width: 45,
    64. show: (row) => {
    65. return Math.random() > 0.5
    66. },
    67. click: (row) => {
    68. console.log("click 撤回")
    69. }
    70. }, {
    71. name: "禁用",
    72. width: 45,
    73. show: (row) => {
    74. return Math.random() > 0.5
    75. },
    76. click: (row) => {
    77. console.log("click 禁用")
    78. }
    79. }, {
    80. name: "流程跟踪",
    81. width: 80,
    82. show: (row) => {
    83. return Math.random() > 0.8
    84. },
    85. click: (row) => {
    86. console.log("click 流程跟踪")
    87. },
    88. disabled: true
    89. }],
    90. };
    91. },
    92. created() {
    93. },
    94. mounted() {
    95. this.onSearch()
    96. },
    97. methods: {
    98. onSearch(page = {}) {
    99. if (this.loading) {
    100. return
    101. }
    102. let params = {
    103. ...this.searchForm,
    104. ...this.page,
    105. ...page,
    106. }
    107. this.loading = true
    108. this.fetchData(params).then((data) => {
    109. this.loading = false
    110. this.tableData = data.list
    111. updatePageInfo(this.page, data)
    112. this.showBtn = true
    113. setTimeout(() => {
    114. // this.showBtn = false
    115. }, 2000)
    116. }).catch((err) => {
    117. this.loading = false
    118. this.tableData = []
    119. })
    120. },
    121. fetchData(params) {
    122. return new Promise((resolve, reject) => {
    123. let data = dataListPage({
    124. name: "@cname",
    125. "sex|1": ["男", "女"],
    126. age: "@natural(1, 100)",
    127. city: "@city",
    128. hobby: "@csentence(2, 10)",
    129. createName: "@cname",
    130. createTime: "@date",
    131. }, params).data
    132. console.log(data)
    133. resolve(data)
    134. })
    135. },
    136. }
    137. }

    效果图

  • 相关阅读:
    A133P EC200M模块调试
    【DevPress】V2.0.0版本发布,增加直播功能
    【杂记】Windows首页挟持病毒查杀过程记录
    [附源码]Java计算机毕业设计SSM房车营地在线管理系统
    暑假超越计划练习题(6)
    Javascript分类面试题
    Spring Ioc 容器启动流程
    Rust 从入门到精通07-函数
    Speedoffice(word)中如何添加批注内容
    ROS学习——利用电脑相机标定
  • 原文地址:https://blog.csdn.net/cxgasd/article/details/132968700