Live2d Test Env

【论术】基于t-table的表格拖拽

在老之将至的这几年,监听员千万遍问自己:这就是我的一生吗?他又千万次回答:是的,这就是你的一生。 - 刘慈欣《三体》

项目需要:表格中须支持拖拽功能,如图:

此功能参照了fastgpt的知识库文件对象管理,以公司主流的t-design为基础进行开发,t-table的api仅提供了dragSortAPI,即拖拽排序,无法分别切割拖拽功能与排序功能。前后思索了下成本后决定自己手写一个。

PS: 下方为t-table的拖拽排序:

准备工作

为了完善主功能,因而也给自己提了几点需求:

1.鼠标经过表格行时展示为移动图标;

2.移动时目录须展示不同底色

3.仅允许文件向目录移动,不允许目录向目录或者文件向文件或者目录向文件移动。

4.按住移动时手标为移动图标(另一种)

看了t-table的dom结构后发现表格的表格行的父单位是t-table__body,每一个tr即为一行表格:

我们的需求是拖动行元素,因而可以获取到所有的tr,为了防止未来此页面可能有多个表格元素,可以在t-table中给每个行元素添加自定义类元素:

由于项目的table是基于t-table的二开,写法与原生t-table略有不同,但是大同小异,只要f12 dom元素的tr元素有自定义class即可

t-table开放了行class设置函数rowClassName定义 :


  rowClassName: ({ row: { knowledgeType } }) => { 
    if (knowledgeType === 1) return 'custom-class-name  custom-class-move'  // 如果knowledgeType为1,则此元素是文件元素,赋予其经过时cursor为move
    return 'custom-class-name'
  },

因而我们可以直接在style中去书写对应的class,其实给空也可以,这里做的目的仅仅是为了标识它就是我们要找的row元素。

注意:必须要在全局style中写,scope下不生效

tip:为了防止全局class下的样式被污染,我们可以使用当前页面+当前模块+当前元素作为class名,这是一个不成熟的做法


<style>
// 标识row元素
.custom-class-name {
}
// 标识row元素里的文件元素
.custom-class-move {
  cursor: move;
}
<style>

实现

我们是基于vue3写的,为了确保有row元素,则须使dom加载完毕后开始写实现过程,不过不知道是不是记忆错乱了,依稀记得mounted并不保证所有dom都加载完毕,在实现过程中也有某些row找不到的情况,无奈就又加了nextTick+setTimeOut:


interface KnowledgeItem {
  createName: string
  createTime: string
  fileId: string
  id: string
  knowledgeDesc: string
  knowledgeName: string
  knowledgeStatus: number
  knowledgeType: number
  parentId: string
  permission: string
  rootId: string
  textNum: number
}
const tableData = ref<KnowledgeItem[]>([])
//加载dom
onMounted(() => {
  //   
nextTick(() => {
    setTimeout(() => {
     // 在这里其实可以做一步优化,如果tableData没有值或者没有目录类row则可以终止接下来的动作

//获取所有row元素
    const rows: NodeListOf<HTMLElement> = document.querySelectorAll('.custom-class-name')
       for (let index = 0; index < rows.length; index++) {
        const row = rows[index] as HTMLDivElement
        // 如果 knowledgeType 不等于 0则为文件格式,则设置为可拖拽
        if (tableData.value[index].knowledgeType !== 0) {
          row.draggable = true
          row.ondragstart = (event: DragEvent) => handleDragStart(event, index)
          row.ondragover = (event: DragEvent) => handleDragOver(event, index)
        } else {
          // 如果 knowledgeType 等于 0则为目录,则设置为不可拖拽
          row.draggable = false
          // 移除原有的拖拽开始和经过的事件监听器
          row.ondragstart = null
          row.ondragover = (event: DragEvent) => {
            // 阻止默认行为以允许放置
            event.preventDefault()
          }
        }

        //  设置ondrop 事件监听器
        row.ondrop = (event: DragEvent) => handleDrop(event, index)
      }   
})
})
//
})
// 设置移动时其在tableData中的元素
const metaDropIndex = ref(-1)
//鼠标长按移动时
const handleDragStart = (event: DragEvent, index:number) => {
  event.stopPropagation() //阻止默认事件
  metaDropIndex.value = index
  event.dataTransfer.effectAllowed = 'move' // 设置拖拽效果为移动
}
//按住不丢经过row元素时
const handleDragOver = (event: DragEvent, index: number) => {
  event.preventDefault() // 允许放置
  const rows: NodeListOf<HTMLElement> = document.querySelectorAll('.custom-class-name')
// 将所有目录着重展示
  for (let index = 0; index < rows.length; index++) {
    const element = rows[index]
    if (tableData.value[index].knowledgeType === 0) {
      // 此方式在t-table中无效
      // element.classList.add('custom-class-name-body-bd')
      element.style.background = '#f2f3ff'
      element.style.fontWeight = 'bold'
    }
  }
  if (tableData.value[index].knowledgeType === 1) event.dataTransfer.effectAllowed = 'none'
}
//鼠标放下时
const handleDrop = async (event: DragEvent, targetIndex: number) => {
  event.preventDefault()
// 如果目标索引与拖动索引一致则return
  if (metaDropIndex.value === targetIndex) return
  // 如果目标dom不是目录 上同
  if (tableData.value[targetIndex].knowledgeType !== 0) return
  const rows: NodeListOf<HTMLElement> = document.querySelectorAll('.custom-class-name')
  for (let index = 0; index < rows.length; index++) {
    const element = rows[index]
    if (tableData.value[index].knowledgeType === 0) {
     // 取消将所有目录着重展示
     element.style.background = '#ffffff'
      element.style.fontWeight = 'normal'
    }
  }
  console.log('metaDropIndex.value[targetIndex] :>> ', tableData.value[metaDropIndex.value])
  console.log('targetIndex.value[targetIndex] :>> ', tableData.value[targetIndex])
  const params = {
    id: tableData.value[metaDropIndex.value].id,
    targetId: tableData.value[targetIndex].id
  }
  const res: boolean = await moveFileApi(params)
  // 移动成功后将源下标赋为非法值
  !!res && (metaDropIndex.value = -1)
 // 重新刷新当前列表
  refreshTable(true)
}

tip: 个人感觉metaDropIndex似乎没有必要,可以使用 event.dataTransfer.setData(type,value)来设置值,使用event.dataTransfer.getData(type)来获取值。

为了使设置row函数更具备适用性,我们也可以将其封装成通用函数,vue与jq的区别在此也体现的淋漓尽致:

请看vcr:


//公有拖拽函数
const setDomDraggable (){
  //   
nextTick(() => {
    setTimeout(() => {
     // 在这里其实可以做一步优化,如果tableData没有值或者没有目录类row则可以终止接下来的动作

//获取所有row元素
    const rows: NodeListOf<HTMLElement> = document.querySelectorAll('.custom-class-name')
       for (let index = 0; index < rows.length; index++) {
        const row = rows[index] as HTMLDivElement
        // 如果 knowledgeType 不等于 0则为文件格式,则设置为可拖拽
        if (tableData.value[index].knowledgeType !== 0) {
          row.draggable = true
          row.ondragstart = (event: DragEvent) => handleDragStart(event, index)
          row.ondragover = (event: DragEvent) => handleDragOver(event, index)
        } else {
          // 如果 knowledgeType 等于 0则为目录,则设置为不可拖拽
          row.draggable = false
          // 移除原有的拖拽开始和经过的事件监听器
          row.ondragstart = null
          row.ondragover = (event: DragEvent) => {
            // 阻止默认行为以允许放置
            event.preventDefault()
          }
        }

        //  设置ondrop 事件监听器
        row.ondrop = (event: DragEvent) => handleDrop(event, index)
      }   
})
})

}


onMounted(() => {
  setDomDraggable()
})
watch(
  () => tableData.value,
  (n) => {
   n.length > 0 && setDomDraggable()
  },
  { deep: true }
)

tip:其实依然还有许多可增补的点,比如可以直接获取tbody的dom然后直接获取它的子项,比如在每次拖拽时遍历的优化,比如当在非目录下松手时在return前应重置目录样式...

发散思维下其实这种方式不仅限于t-table,因为道理都是共通的,放到antd或者element也可使用,只需获取到tr上一级的父元素或者表格给与行class的api即可,不过尽管如此,依然不建议在响应式框架下操作dom(具体原因我不说)。

这样就实现了本次需求,

以上。

posted @ 2024-11-26 17:37  致爱丽丝  阅读(11)  评论(0编辑  收藏  举报