一些组件封装

226 阅读1分钟

最近个人学习中涉及到一些简单组件封装,在此记录一下

环境和依赖

vue3 + element-plus + sortablejs

右键菜单(单层)

context-menu.vue

<template>
  <teleport to="body">
    <ul
      v-if="modelValue"
      class="context-menu-container"
      :style="{
        left: pos.x + 'px',
        top: pos.y + 'px'
      }"
    >
      <template v-for="(item, index) in data" :key="index">
        <template v-if="canShow(item)">
          <li
            class="context-menu-item"
            v-if="!item.template"
            @click.stop="onItemClick(item)"
          >
            {{ item.title }}
          </li>
          <slot v-else :name="item.template"></slot>
        </template>
      </template>
    </ul>
  </teleport>
</template>

<script lang="ts">
import { ContextMenuData } from './contextMenu'
import { onMounted, onUnmounted, PropType } from 'vue'
export default {
  props: {
    modelValue: {
      type: Boolean as PropType<boolean>,
      required: true
    },
    data: {
      type: Array as PropType<Array<ContextMenuData>>,
      required: true
    },
    pos: {
      type: Object as PropType<{ x: number; y: number }>,
      required: true
    }
  },
  setup(props, { emit }) {
    function onItemClick(item: ContextMenuData) {
      typeof item.click === 'function' && item.click()
    }
    function hideContextMenu() {
      emit('update:modelValue', false)
    }
    onMounted(() => {
      document.body.addEventListener('click', hideContextMenu)
      document.body.addEventListener('contextmenu', hideContextMenu)
    })
    onUnmounted(() => {
      document.body.removeEventListener('click', hideContextMenu)
      document.body.removeEventListener('contextmenu', hideContextMenu)
    })
    function canShow(item: ContextMenuData) {
      const showType = typeof item.show
      if (showType === 'undefined') {
        return true
      }
      if (showType === 'boolean') {
        return item.show
      }
      return (item.show as () => void)()
    }
    return {
      onItemClick,
      canShow
    }
  }
}
</script>

<style lang="scss" scoped>
.context-menu-container {
  position: fixed;
  background: #fff;
  z-index: 3000;
  list-style-type: none;
  padding: 5px 0;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 400;
  color: #333;
  box-shadow: var(--el-box-shadow-light);
  li {
    font-size: var(--el-font-size-base);
    padding: 0 32px 0 20px;
    position: relative;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--el-text-color-regular);
    height: 34px;
    line-height: 34px;
    box-sizing: border-box;
    cursor: pointer;
    &:hover {
      background: var(--el-fill-color-light);
    }
  }
}
</style>

types.ts

export interface ContextMenuData {
  title: string
  click?: () => void
  template?: string
  show?: boolean | (() => boolean)
}

demo.vue

<template>
  <context-menu
    v-model="visible"
    :data="menuData"
    :pos="contextMenuPos"
  ></context-menu>
</template>

动态表格(简易带排序)

dynamic-table

<template>
  <el-table ref="tableRef" v-loading="loading" :data="data" :border="true">
    <el-table-column
      v-for="item in columns"
      :key="item.label"
      :prop="item.prop"
      :label="item.label"
      :show-overflow-tooltip="!!item.tip"
    >
      <template #default="{ row }" v-if="item.template">
        <slot :name="item.prop" :data="row"></slot>
      </template>
      <template #default="{ row }" v-else>
        {{ row[item.prop] }}
      </template>
    </el-table-column>
  </el-table>
  <div class="pagination-container" v-if="pagination">
    <el-pagination
      background
      class="pagination"
      @size-change="$emit('size-change', $event)"
      @current-change="$emit('current-change', $event)"
      :current-page="currentPage"
      :page-sizes="pageSizes"
      :page-size="pageSize"
      :layout="layout"
      :total="total"
    >
    </el-pagination>
  </div>
</template>

<script lang="ts">
import { computed, PropType, ref } from 'vue'
import { Column } from '@/types'
import { pageSizes, layout } from '@/config/pagination'
import useSortable from './useSortable'
import { SortableEvent, Options } from 'sortablejs'

export default {
  props: {
    data: {
      type: Array as PropType<Array>,
      default: () => []
    },
    columns: {
      type: Array as PropType<Array<Column>>,
      default: () => []
    },
    pagination: {
      type: Boolean as PropType<boolean>,
      default: true
    },
    loading: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    currentPage: {
      type: Number as PropType<number>,
      default: 1
    },
    pageSize: {
      type: Number as PropType<number>,
      default: 10
    },
    pageSizes: {
      type: Array as PropType<Array<number>>,
      default: pageSizes
    },
    layout: {
      type: String as PropType<string>,
      default: layout
    },
    total: {
      type: Number as PropType<number>,
      default: 0
    },
    sortable: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    sortOption: {
      type: Object as PropType<Options>,
      default: () => ({
        ghostClass: 'sortable-ghost'
      })
    }
  },
  emits: ['current-change', 'size-change', 'sort-start', 'sort-end'],
  setup(props, { emit }) {
    const tableRef = ref()
    const canSort = computed(() => props.sortable)
    const sortable = useSortable(tableRef, canSort, {
      ghostClass: props.sortOption.ghostClass,
      onStart: (e: SortableEvent) => {
        emit('sort-start', e)
      },
      onEnd: (e: SortableEvent) => {
        emit('sort-end', e)
      }
    })
    return {
      tableRef,
      sort: sortable
    }
  }
}
</script>

useSortable

import Sortable, { Options } from 'sortablejs'
import { onMounted, ref, Ref } from 'vue'
export default function useSortable(
  tableRef: Ref<any>,
  canSort: Ref<boolean>,
  config?: Options
) {
  const sortable = ref<Sortable>()
  onMounted(() => {
    if (canSort.value) {
      const sortParent = tableRef.value.$el.querySelector(
        '.el-table__body tbody'
      )
      if (sortParent) {
        config = config || {
          ghostClass: 'sortable-ghost'
        }
        sortable.value = Sortable.create(sortParent as HTMLElement, config)
      }
    }
  })
  return {
    sortable
  }
}


types.ts

export interface Column {
  label: string
  prop: string
  template?: boolean
  tip?: boolean
}

demo.vue

<dynamic-table
    :columns="tableColumns"
    :data="tableManage.list"
    :loading="loading"
    :current-page="requestParameter.page"
    :page-size="requestParameter.size"
    :total="tableManage.total"
    :sortable="true"
    @size-change="onSizeChange"
    @current-change="onCurrentPageChange"
    @sort-end="onSortEnd"
>
</dynamic-table>