table实现多行...以及浮窗提示宽度与元素保持一致组件

实现效果

在这里插入图片描述
两行…浮窗提示,多行提示要考虑到低版本火狐以及ie浏览的器的兼容性问题,因此需要引用clamp.js。多行提示封装的是全局组件可保持统一,文件路径components文件夹下。
在这里插入图片描述

兼容ie浏览器实现方式

一、clamp.js


export default {
  name: 'vue-clamp',
  props: {
    tag: {
      type: String,
      default: 'div'
    },
    autoresize: {
      type: Boolean,
      default: false
    },
    maxLines: Number,
    maxHeight: [String, Number],
    ellipsis: {
      type: String,
      default: '…'
    },
    location: {
      type: String,
      default: 'end',
      validator (value) {
        return ['start', 'middle', 'end'].indexOf(value) !== -1
      }
    },
    expanded: Boolean
  },
  data () {
    return {
      offset: null,
      text: this.getText(),
      localExpanded: !!this.expanded
    }
  },
  computed: {
    clampedText () {
      if (this.location === 'start') {
        return this.ellipsis + (this.text.slice(0, this.offset) || '').trim()
      } else if (this.location === 'middle') {
        const split = Math.floor(this.offset / 2)
        return (this.text.slice(0, split) || '').trim() + this.ellipsis + (this.text.slice(-split) || '').trim()
      }

      return (this.text.slice(0, this.offset) || '').trim() + this.ellipsis
    },
    isClamped () {
      if (!this.text) {
        return false
      }
      return this.offset !== this.text.length
    },
    realText () {
      return this.isClamped ? this.clampedText : this.text
    },
    realMaxHeight () {
      if (this.localExpanded) {
        return null
      }
      const { maxHeight } = this
      if (!maxHeight) {
        return null
      }
      return typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight
    }
  },
  watch: {
    expanded (val) {
      this.localExpanded = val
    },
    localExpanded (val) {
      if (val) {
        this.clampAt(this.text.length)
      } else {
        this.update()
      }
      if (this.expanded !== val) {
        this.$emit('update:expanded', val)
      }
    },
    isClamped: {
      handler (val) {
        this.$nextTick(() => this.$emit('clampchange', val))
      },
      immediate: true
    }
  },
  mounted () {
    this.init()
    
    this.$watch(
      (vm) => [vm.maxLines, vm.maxHeight, vm.ellipsis, vm.isClamped].join(),
      this.update
    )
    this.$watch((vm) => [vm.tag, vm.text, vm.autoresize].join(), this.init)
  },
  updated () {
    this.text = this.getText()
    this.applyChange()
  },
  beforeDestroy () {
    this.cleanUp()
  },
  methods: {
    init () {
      const contents = this.$slots.default
      if (!contents) {
        return
      }
      this.offset = this.text.length

      this.cleanUp()
      if (this.autoresize) {
        setTimeout(() => {
          this.update()
          window.addEventListener("resize", this.update) }, 10)
        this.unregisterResizeCallback = () => {
          window.removeEventListener("resize",this.update);
        }
      }
      this.update()
    },
    update () {
      if (this.localExpanded) {
        return
      }
      this.applyChange()
      if (this.isOverflow() || this.isClamped) {
        this.search()
      }
    },
    expand () {
      this.localExpanded = true
    },
    collapse () {
      this.localExpanded = false
    },
    toggle () {
      this.localExpanded = !this.localExpanded
    },
    getLines () {
      return Object.keys(
        Array.prototype.slice.call(this.$refs.content.getClientRects()).reduce(
          (prev, { top, bottom }) => {
            const key = `${top}/${bottom}`
            if (!prev[key]) {
              prev[key] = true
            }
            return prev
          },
          {}
        )
      ).length
    },
    isOverflow () {
      if (!this.maxLines && !this.maxHeight) {
        return false
      }

      if (this.maxLines) {
        if (this.getLines() > this.maxLines) {
          return true
        }
      }

      if (this.maxHeight) {
        if (this.$el.scrollHeight > this.$el.offsetHeight) {
          return true
        }
      }
      return false
    },
    getText () {
      // Look for the first non-empty text node
      const [content] = (this.$slots.default || []).filter(
        (node) => !node.tag && !node.isComment
      )
      return content ? content.text : ''
    },
    moveEdge (steps) {
      this.clampAt(this.offset + steps)
    },
    clampAt (offset) {
      this.offset = offset
      this.applyChange()
    },
    applyChange () {
      this.$refs.text.textContent = this.realText
    },
    stepToFit () {
      this.fill()
      this.clamp()
    },
    fill () {
      while (
        (!this.isOverflow() || this.getLines() < 2) &&
        this.offset < this.text.length
      ) {
        this.moveEdge(1)
      }
    },
    clamp () {
      while (this.isOverflow() && this.getLines() > 1 && this.offset > 0) {
        this.moveEdge(-1)
      }
    },
    search (...range) {
      const [from = 0, to = this.offset] = range
      if (to - from <= 3) {
        this.stepToFit()
        return
      }
      const target = Math.floor((to + from) / 2)
      this.clampAt(target)
      if (this.isOverflow()) {
        this.search(from, target)
      } else {
        this.search(target, to)
      }
    },
    cleanUp () {
      if (this.unregisterResizeCallback) {
        this.unregisterResizeCallback()
      }
    }
  },
  render (h) {
    const contents = [
      h(
        'span',
        this.$isServer
          ? {}
          : {
            ref: 'text',
            attrs: {
              'aria-label': this.text.trim()
            }
          },
        this.$isServer ? this.text : this.realText
      )
    ]

    const { expand, collapse, toggle } = this
    const scope = {
      expand,
      collapse,
      toggle,
      clamped: this.isClamped,
      expanded: this.localExpanded
    }
    const before = this.$scopedSlots.before
      ? this.$scopedSlots.before(scope)
      : this.$slots.before
    if (before) {
      contents.unshift(...(Array.isArray(before) ? before : [before]))
    }
    const after = this.$scopedSlots.after
      ? this.$scopedSlots.after(scope)
      : this.$slots.after
    if (after) {
      contents.push(...(Array.isArray(after) ? after : [after]))
    }
    const lines = [
      h(
        'span',
        {
          style: {
            boxShadow: 'transparent 0 0'
          },
          ref: 'content'
        },
        contents
      )
    ]
    return h(
      this.tag,
      {
        style: {
          maxHeight: this.realMaxHeight,
          overflow: 'hidden'
        }
      },
      lines
    )
  }
}

二、在封装的tooltip组件中引用。

<template>
  <div ref="content" class="text-tooltip" :style="boxStyle">
    <el-tooltip v-if="content || content === 0" class="item" effect="dark" :disabled="!isShowTooltip" :content="contentText" :placement="placement">
      <div class="text-content">
        <v-clamp ref="spanClamp" :style="pStyle" autoresize :max-lines="2" @clampchange="clampchange">{{ content }}</v-clamp>
        <!--<span v-else ref="span" :style="pStyle" class="text-value" v-html="content" />-->
      </div>
      <div slot="content" :style="tooltipView" class="mult-text-tooltip-popper" v-html="contentText" />
    </el-tooltip>
    <!--<span v-if="isShowTooltip" :style="{lineHeight: lineHeight + 'px', background: backgroundColor, bottom: paddingHeight + 'px'}" class="over-i">...</span>-->
  </div>
</template>

<script>
    import VClamp from './components/Clamp'
    export default {
        name: "MultiLineTooltip",
        components: {
            VClamp
        },
        props: {
            content: {
                type: [Number, String],
                default: ""
            },
            line: {
                type: Number,
                default: 2
            },
            lineHeight: {
                type: Number,
                default: 24
            },
            paddingHeight: {
                type: Number,
                default: 8
            },
            backgroundColor: {
                type: String,
                default: ""
            },
            placement: {
                type: String,
                default: "top-start"
            }
        },
        data() {
            return {
                contentText: '',
                isShowTooltip: false,
                boxStyle: {
                    paddingTop: this.paddingHeight + "px",
                    paddingBottom: this.paddingHeight + "px"
                },
                styleObject: {
                    maxHeight: this.lineHeight * this.line + "px"
                },
                pStyle: {
                    lineHeight: this.lineHeight + "px"
                },
                tooltipView: {
                    width: '400px'
                },
                lineNum: 2,
                lineHeightNum: 80
            }
        },
        watch: {
            content() {
                this.$nextTick(() => {
                    // this.detectionHeight()
                })
            }  
        },
        mounted() {
            this.detectionHeight()
        },
        methods: {
            clampchange($event) {
                this.isShowTooltip = $event
                if ($event) {
                    this.detectionHeight()
                }
            },
            detectionHeight() {
                if (!this.content && this.content !== 0) {
                    return
                }
                this.contentText = this.$refs["spanClamp"].text
                // this.isShowTooltip = this.$refs["spanClamp"].isClamped
                const contentWidth = this.$refs["content"].offsetWidth
                this.tooltipView.width = contentWidth + 'px'
            }
        }

    }
</script>

<style lang="scss" scoped>
    .text-tooltip {
        display: flex;
        position: relative;
    }
    .text-content {
        overflow: hidden;
    }
    .over-i {
        content: "...";
        position: absolute;
        bottom: 0;
        right: 0;
        padding-left: 20px;
        user-select: none;
        pointer-events: none;
        /*background: linear-gradient(to right, transparent, #fff 55%);*/
    }
    .text-value {
        word-break: break-all;
        overflow: hidden;
        white-space: initial;
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
    }
</style>

三、全局注册组件

import MultiLineTooltip from '@/components/MultiLineTooltip'
Vue.component('MultiLineTooltip', MultiLineTooltip)

四、在局部模块中使用

<table-comment
        :table_getdata.sync="table"
        class="table_comment"
        @handleSizeChange="handleSizeChange"
        @handleCurrentChange="handleCurrentChange"
      >
        <template v-slot:title="slotProps">
          <div @click="handleDetail(slotProps.row)">
          //使用全局封装的提示组件
            <multi-line-tooltip
            :content="slotProps.row.title"
            class="title-view"
            >
                <!-- <span class="data-title" >{{ slotProps.row.innnerTitle }}</span> -->
            </multi-line-tooltip>
          </div>
        </template>
      </table-comment>

二、不兼容ie浏览器多行提示

  1. 实现多行…效果 .text-value { word-break: break-all; overflow: hidden; white-space: initial; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }

2.去掉clamp其他是实现方式一样

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要在React组件中引入Fusion Table库。在组件中创建一个状态(state)来存储选中的列的索引。然后,在render函数中,通过遍历表格的每一列并为其添加onClick事件来实现高亮。 代码示例如下: ```jsx import React, { useState } from "react"; import { Table } from "@devexpress/dx-react-grid-material-ui"; import FusionTable from "fusion-table"; const HighlightableTable = (props) => { const [highlightedColumnIndex, setHighlightedColumnIndex] = useState(-1); const handleColumnClick = (columnIndex) => { setHighlightedColumnIndex(columnIndex); }; const renderTableColumn = ({ column, children }) => { const columnIndex = props.columns.findIndex( (c) => c.name === column.name ); const isHighlighted = columnIndex === highlightedColumnIndex; return ( <Table.Column {...column} onClick={() => handleColumnClick(columnIndex)} style={{ background: isHighlighted ? "#efefef" : "transparent" }} > {children} </Table.Column> ); }; return ( <FusionTable {...props}> <Table {...props} columnExtensions={props.columns.map((c) => ({ columnName: c.name, wordWrapEnabled: true }))}> {props.children.map((child) => React.cloneElement(child, { render: renderTableColumn }))} </Table> </FusionTable> ); }; export default HighlightableTable; ``` 在这个示例中,我们将一个名为`highlightedColumnIndex`的状态用于跟踪选中的列的索引。当用户单击列时,我们通过调用`handleColumnClick`函数将选中的列的索引设置为`highlightedColumnIndex`状态。在渲染每一列时,我们检查当前列是否与选中的列匹配,并根据需要设置背景色。 使用示例: ```jsx import React from "react"; import HighlightableTable from "./HighlightableTable"; const columns = [ { name: "id", title: "ID" }, { name: "name", title: "Name" }, { name: "age", title: "Age" }, ]; const rows = [ { id: 1, name: "John Doe", age: 30 }, { id: 2, name: "Jane Smith", age: 25 }, { id: 3, name: "Bob Johnson", age: 40 }, ]; const App = () => { return ( <HighlightableTable columns={columns} rows={rows}> <Table.HeaderRow /> <Table.Row /> </HighlightableTable> ); }; export default App; ``` 这将创建一个具有选中列高亮的表格。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值