最近个人学习中涉及到一些简单组件封装,在此记录一下
环境和依赖
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>