基于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>
相关推荐
进阶的小木桩10 分钟前
Vue 3 + Elementui + TypeScript 实现左侧菜单定位右侧内容
vue.js·elementui·typescript
whysqwhw13 分钟前
js之Promise
前端
恋猫de小郭4 小时前
Flutter 3.35 发布,快来看看有什么更新吧
android·前端·flutter
chinahcp20085 小时前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
暖木生晖5 小时前
flex-wrap子元素是否换行
javascript·css·css3·flex
gnip6 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip6 小时前
运行时模块批量导入
前端·javascript
hyy27952276847 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
逆风优雅7 小时前
vue实现模拟 ai 对话功能
前端·javascript·html
若梦plus7 小时前
http基于websocket协议通信分析
前端·网络协议