基于ElementPlus组件的二次封装

CommonForm 组件

vue 复制代码
<template>
  <div class="form-container">
    <el-form ref="formRef" :model="modelValue" v-bind="$attrs">
      <el-row :gutter="gutter || 15">
        <el-col v-for="item in formItems" :key="item.prop" :span="item.span || 6">
          <el-form-item :prop="item.prop" :label="item.label" :rules="item.rules">
            <slot :name="item.prop">
              <component :is="getComponents(item)" v-bind="getProp(item)" v-model="modelValue[item.prop]">
                <template v-for="(slotFn, name) in item.slots" :key="name" #[name]="slotProps">
                  <component :is="slotFn()" v-bind="slotProps" />
                </template>
              </component>
            </slot>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  </div>
</template>

<script setup lang="ts">
import { ElForm, ElInput, ElInputNumber, ElDatePicker } from 'element-plus'
import type { FormInstance } from 'element-plus'
import Select from './select.vue'
import { omit } from 'lodash-es'

const props = defineProps<{
  formItems: any[]
  modelValue: any
  gutter?: number
}>()

interface ComponentsMap {
  select: typeof Select
  input: typeof ElInput
  inputNumber: typeof ElInputNumber
  datePicker: typeof ElDatePicker
}

const modelValue = useVModel(props, 'modelValue')

const componentsMap: ComponentsMap = {
  select: Select,
  input: ElInput,
  inputNumber: ElInputNumber,
  datePicker: ElDatePicker,
}

const getComponents = (item: any) => {
  const { type } = item
  if (type && typeof type !== 'string') {
    return type
  }

  return componentsMap[(type as keyof ComponentsMap) || 'input']
}

const rootProps = ['label', 'prop', 'type', 'rules']

// 如果有props属性 则取props属性 否则取除label, prop, type, rules之外的属性作为props
const getProp = (item: any) => {
  if (item.props) return item.props
  return omit(item, rootProps)
}

const formRef = ref<FormInstance>()
defineExpose({
  validate: (...args: any[]) => {
    formRef.value.validate(...args)
  },
})
</script>

<style lang="scss" scoped>
.form-container {
  padding: 20px 20px 0 20px;
  margin-bottom: 10px;
}

:deep(.el-select__wrapper) {
  box-shadow: none;
  background-color: #f2f2f7;
}

:deep(.el-input__wrapper) {
  box-shadow: none;
  background-color: #f2f2f7;
}
</style>

其中,用到了二次封装的 Select 组件,代码如下:

vue 复制代码
<template>
  <el-select ref="selectRef" v-bind="$attrs" v-model="modelValue">
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
  </el-select>
</template>

<script setup lang="ts">
import { ElSelect } from 'element-plus'

const props = defineProps<{
  options: { label: string; value: string }[]
  modelValue: any
}>()

const modelValue = useVModel(props, 'modelValue')

const selectRef = ref<InstanceType<typeof ElSelect>>()

defineExpose({
  selectRef,
})
</script>

CommonTable 组件

vue 复制代码
<template>
  <div class="common-table">
    <div class="operate-button">
      <slot name="operate-button"></slot>
    </div>

    <!-- 表格区域 -->
    <el-table
      v-loading="loading"
      class="table-container"
      v-bind="tableProps"
      :data="data"
      @selection-change="handleSelectionChange"
    >
      <el-table-column fixed type="selection" width="55" />

      <template v-for="(item, index) in columns" :key="item.prop">
        <el-table-column v-bind="item">
          <template #header="scope">
            <component
              :is="item.headerRender"
              v-if="item.headerRender"
              :column="scope.column"
              :index="scope.$index"
            ></component>
            <template v-else>{{ scope.column.label }}</template>
          </template>

          <template #default="scope">
            <slot :name="item.prop" :row="scope.row" :index="scope.$index" :column="scope.column">
              <component
                :is="item.render"
                v-if="item.render"
                :row="scope.row"
                :index="scope.$index"
                :column="scope.column"
              ></component>

              <template v-else>{{ scope.row[item.prop] }}</template>
            </slot>
          </template>
        </el-table-column>
      </template>
    </el-table>
    <!-- </div> -->

    <!-- 底部操作栏 -->
    <div class="pagination">
      <div class="right-pagination">
        <el-pagination
          v-model:current-page="pagination.pageNo"
          v-model:page-size="pagination.pageSize"
          :total="pagination.total"
          v-bind="pageProps"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import useTableColumns from '@/components/commonTable/columnSelector/useTableColumns'
import ColumnSelector from '@/components/commonTable/columnSelector/index.vue'
import type { Column, TableProps, PaginationProps } from 'element-plus'
import type { VNode } from 'vue'

interface CommonTableProps<T> {
  data: T[]
  columns: Array<Partial<Column> & { render?: (scope: any) => VNode; headerRender?: (scope: any) => VNode }>
  pagination: {
    pageNo: number
    pageSize: number
    total: number
  }
  loading?: boolean
  tableProps?: Partial<Omit<TableProps<any>, 'data'>>
  pageProps?: Partial<Omit<PaginationProps, 'current-page' | 'page-size' | 'total'>>
}

const props = withDefaults(defineProps<CommonTableProps<any>>(), {
  pagination: () => ({
    pageNo: 1,
    pageSize: 10,
    total: 0,
  }),
  pageProps: () => ({
    background: true,
    pageSizes: [10, 20, 50, 100],
    layout: 'total, sizes, prev, pager, next, jumper',
  }),
  loading: false,
})

const emits = defineEmits<{
  pageSizeChange: [number]
  currentPageChange: [number]
  selectChange: [any[]]
}>()

const pagination = useVModel(props, 'pagination')
const { columns } = toRefs(props)
const handleSizeChange = (size: number) => {
  emits('pageSizeChange', size)
}
const handleCurrentChange = (page: number) => {
  emits('currentPageChange', page)
}

const handleSelectionChange = (selection: any[]) => {
  emits('selectChange', selection)
}
</script>
<style lang="scss" scoped>
.common-table {
  .operate-button {
    margin-bottom: 20px;
  }

  .table-container {
    .operation-container {
      position: absolute;
      top: 50%;
      right: 0;
      transform: translateY(-50%);
      // display: flex;
      // justify-content: space-between;
      // align-items: center;
    }
  }

  .pagination {
    position: absolute;
    bottom: 0;
    right: 0;
    padding: 10px 20px;
    z-index: 100;
  }
}

:deep(.el-table.is-scrolling-left.el-table--border .el-table-fixed-column--left.is-last-column.el-table__cell) {
  border-right: none;
}
:deep(.el-table--border .el-table__cell) {
  border-right: none;
}
:deep(.el-table__border-left-patch) {
  width: 0;
}
:deep(.el-table--border::before) {
  width: 0;
}
:deep(.el-table--border::after) {
  width: 0;
}
.el-table {
  --el-table-header-bg-color: #ecf4ff;
  --el-table-header-text-color: #111a34;
}
</style>
相关推荐
2分钟速写快排1 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰3 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马6 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu6 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss
IT_陈寒6 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端