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>