Vue 实现一个操作组件

170 阅读1分钟

查看效果

image.png

说说实现与坑

预想的dom结构

<el-pro-operation style="width: 60px;">
  <el-pro-button type="primary" link @click="console.log('edit')">编辑</el-pro-button>
  <el-pro-button type="default" link disabled>禁用</el-pro-button>
  <el-pro-button type="primary" link confirm="你确定删除吗" @confirm="console.log('delete')">删除</el-pro-button>
</el-pro-operation>

好处的使用方简单,不用关心我应该放左边还是右边,并且子元素有可能if判断 那这样实现第一考虑是拦截slots.default()二次加工,可以参考

之前写Vue-通过space了解jsx写法好处说明了tsx的好处可以拦截插槽结构

代码

import { ref, defineComponent, Slots, onMounted, Ref, VNode, nextTick } from "vue";
import { ElDropdown, ElIcon, ElDropdownMenu, ElDropdownItem } from "element-plus";
import {
  ArrowDown
} from "@element-plus/icons-vue";

import './index.css';

function useChildren(slots: Slots, elProOperation: Ref) {
  let width = 0
  const parentWidth = ref(Infinity)
  const showRight = ref(false)
  const rightNode: VNode[] = []
  // 没有找打如何watch slots.default()改变事件,写到tsx中,重新得到 rightNode
  const node = () => slots.default && slots.default().map((x:any, xIndex) => {
    if (xIndex === 0) {
      width = 0
      rightNode.length = 0
      // 重新获取右边信息
      nextTick(() => {
        showRight.value = rightNode.length > 0
      })
    }
    const textLen = x.children.default && x.children.default()[0].children.length || 0
    const marginLeft = width === 0 ? 0 : 12
    const paddingLeft = 2;
    const textWidth = textLen * 15;
    const paddingRight = 2;
    if (textLen > 0) {
      width += marginLeft + paddingLeft + textWidth + paddingRight
    }

    x.left = width < parentWidth.value

    if (x.left) {
      return x
    }

    // 这个不能设置响应式数据,否则会死循环
    rightNode.push(x)
  })

  onMounted(() => {
    parentWidth.value = elProOperation.value.clientWidth
  })

  return {
    node,
    showRight,
    rightNode
  }

}

export default defineComponent({
  name: 'ElProOperation',
  props: {
  },
  setup(props, { slots, attrs }) {
    const elProOperation = ref()
    const { node, showRight, rightNode } = useChildren(slots, elProOperation)

    return () => (
      <div ref={elProOperation} class="el-pro-operation">
        {node()}
        {showRight.value && (
          <ElDropdown trigger="click" hideOnClick={false}>
            {{
              default: () => (<div class="el-pro-operation__arrow">
                <ElIcon>
                  <ArrowDown />
                </ElIcon>
              </div>),
              dropdown: () => (<ElDropdownMenu>
                {rightNode.map(x => <ElDropdownItem>{x}</ElDropdownItem>)}
              </ElDropdownMenu>)
            }}
          </ElDropdown>
        )}
      </div>
    );
  },
});

在线地址

http://localhost:5173/components/operation