vue3的 三种插槽 匿名插槽,具名插槽,作用域插槽

发布于:2025-09-07 ⋅ 阅读:(22) ⋅ 点赞:(0)

背景:

在vue3项目中,常用的三种插槽。

关于插槽的理论知识如下:

插槽是指可以在父组件内自定义模板片段,在子组件中可以将定义的模板片段插入到子组件的特定位置。常用:匿名插槽、具名插槽

作用域插槽:子组件向父组件传递数据,并在父组件定义的模板中渲染。常用:作用域插槽

匿名插槽和具名插槽:

插槽是指可以在父组件内自定义模板片段,在子组件中可以将定义的模板片段插入到子组件的特定位置。常用:匿名插槽、具名插槽

代码截图:

父组件:

子组件:

作用域插槽:

作用域插槽:子组件向父组件传递数据,并在父组件定义的模板中渲染。

代码截图:

实际代码如下:

父组件:

<div class="container">
    <Table :columns="columns" :current-page="currentPage" :data="tableData" :page-size="pageSize" :total="total"
        @update:currentPage="val => currentPage = val">
        <template #operation="{ row, $index }">
            <el-button text @click="ClickDetail(row, $index)">
                <template #icon>
                    <img :src="detailIcon" alt="">
                </template>
                详情
            </el-button>
            <el-button text @click="ClickEdit(row, $index)">
                <template #icon>
                    <img :src="editIcon" alt="">
                </template>
                编辑
            </el-button>
            <el-button text @click="ClickForward(row, $index)">
                <template #icon>
                    <img :src="forwardIcon" alt="">
                </template>
                预/决算
            </el-button>
            <el-button text @click="ClickDelete(row, $index)">
                <template #icon>
                    <img :src="deleteIcon" alt="">
                </template>
                删除
            </el-button>
        </template>
    </Table>
</div>

子组件:

<template>
  <div class="table-container">
    <el-table :data="data"
      :style="`width: 100%; flex: 1 1 auto;opacity: ${!enableLoading || !isloading || isempty ? 1 : 0};`"
      :height="tableBodyHeight" :header-cell-style="{ background: '#F5F9FF', color: '#333333', fontWeight: '550' }"
      :cell-style="cellStyle" max-height="100%" :border="showBorder" empty-text="">
      <el-table-column type="index" label="序号" width="60" align="center" :index="indexMethod" fixed="left"
        v-if="showIndex" />
      <!-- 操作列插槽 -->
      <el-table-column label="操作" align="center" fixed="right" v-if="$slots.operation" :width="400">
        <template #default="scope">
          <slot name="operation" :row="scope.row" :$index="scope.$index"></slot>
        </template>
      </el-table-column>
    </el-table>
    <div class="custom-pagination-wrapper">
      <span class="custom-pagination-total">共 {{ total }} 个</span>
      <el-pagination background layout="prev, pager, next" :total="total" :page-size="pageSize"
        :current-page="currentPage" @current-change="handlePageChange" class="custom-pagination" />
    </div>
  </div>
</template>

<script setup>
import { defineProps, defineEmits, onMounted, ref, nextTick, reactive, watch, computed } from 'vue'

const props = defineProps({
  columns: {
    type: Array,
    required: true
  },
  data: {
    type: Array,
    required: true
  },
  total: {
    type: Number,
    default: 0
  },
  pageSize: {
    type: Number,
    default: 10
  },
  currentPage: {
    type: Number,
    default: 1
  },
  // 是否默认显示序号列
  showIndex: {
    type: Boolean,
    default: true
  },
  // 是否默认显示表格边框
  showBorder: {
    type: Boolean,
    default: false
  },
  // 是否默认开启加载动画
  enableLoading: {
    type: Boolean,
    default: false
  }
});

const isloading = ref(true), isempty = ref(false);
watch(() => props.data, (newValue, oldValue) => {
  nextTick(() => {
    // console.log('table');
    isempty.value = props.data.length == 0;
    isloading.value = false;
  })
});

const emit = defineEmits(['update:currentPage', 'customCellStyle'])

function handlePageChange(page) {
  // if (props.data && props.data.length !== 0) {
  //   state.tableData = props.data.slice((page - 1) * 10, 10 * page);
  //   emit('update:currentPage', page)
  // }
  emit('update:currentPage', page)
}

// 自动序号方法,分页自增
const indexMethod = (index) => {
  return (props.currentPage - 1) * props.pageSize + index + 1
}

// 计算表格可视区高度,减去分页区高度
const tableBodyHeight = ref('100%')

// 自定义每个单元格的样式
const cellStyle = (target) => {
  let style = { color: '#363636' };
  // 子组件可自己实现自定义样式
  emit('customCellStyle', target, data => {
    style = data;
  });
  return style;
}

onMounted(() => {
  nextTick(() => {
    const container = document.querySelector('.table-container')
    const pagination = document.querySelector('.custom-pagination-wrapper')
    if (container && pagination) {
      const containerHeight = container.clientHeight
      const paginationHeight = pagination.clientHeight
      tableBodyHeight.value = `${containerHeight - paginationHeight}px`
    }
    // handlePageChange(props.currentPage)
  })
})
</script>

<style scoped>
.table-container {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;

  .loading {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    z-index: 9;


    span {
      color: #3A5269;
      text-align: center;
      font-family: "Alibaba PuHuiTi";
      font-size: 18px;
      font-style: normal;
      line-height: 22px;
      /* 122.222% */
      font-weight: 400;
      /* margin-top: 10px; */
      margin-left: 20px;
    }
  }

  .isempty {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    z-index: 9;

    img {
      width: 140px;
      height: 140px;
    }

    span {
      color: #3A5269;
      text-align: center;
      font-family: "Alibaba PuHuiTi";
      font-size: 18px;
      font-style: normal;
      line-height: 22px;
      /* 122.222% */
      /* margin-top: 16px; */
      margin-left: 20px;
    }
  }
}

.el-table {
  flex: 1 1 auto;
  min-height: 0;
}

.el-table__body-wrapper {
  overflow-y: auto !important;
}

:deep(.el-table__row) {
  height: 40px;
}

.custom-pagination-wrapper {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 30px;
}

.custom-pagination-total {
  color: #86909C;
  font-size: 14px;
  margin-right: 16px;
}

.custom-pagination {
  margin-left: auto;
}

.custom-pagination .el-pager li {
  border-radius: 4px;
  min-width: 32px;
  height: 32px;
  line-height: 32px;
  margin: 0 2px;
  color: #4E5A6A;
  font-size: 14px;
  background: transparent;
  border: none;
  transition: background 0.2s, color 0.2s, border 0.2s;
}

.custom-pagination .el-pager li.active {
  background: transparent;
  color: #28ABF5;
  border: 1px solid #28ABF5;
  border-radius: 4px;
}

.custom-pagination .el-pager li:hover {
  background: #eaf4ff;
  color: #28ABF5;
}

.custom-pagination .el-pagination__prev,
.custom-pagination .el-pagination__next {
  min-width: 32px;
  height: 32px;
  line-height: 32px;
  border-radius: 4px;
  color: #4E5A6A;
  background: transparent;
  border: none;
  margin: 0 2px;
}

.custom-pagination .el-pagination__prev.is-active,
.custom-pagination .el-pagination__next.is-active {
  background: transparent;
  color: #28ABF5;
  border: 1px solid #28ABF5;
  border-radius: 4px;
}

.custom-pagination .el-pagination__prev:hover,
.custom-pagination .el-pagination__next:hover {
  background: #eaf4ff;
  color: #28ABF5;
}

:deep(.el-pagination.is-background .btn-next),
:deep(.el-pagination.is-background .el-pager li),
:deep(.el-pagination.is-background .btn-prev:disabled),
:deep(.el-pagination.is-background .btn-prev) {
  background: transparent;
  color: #606266;
}

:deep(.el-pagination.is-background .el-pager li.is-active) {
  min-width: 25px;
  height: 25px;
  color: #28ABF5;
  border-radius: 4px;
  border: 1px solid #28ABF5;
}
</style>

为什么这么封装,需要了解el-table组件,点击跳转


网站公告

今日签到

点亮在社区的每一天
去签到