欢迎点击领取 -《前端开发面试题进阶秘籍》:前端登顶之巅-最全面的前端知识点梳理总结
vue3.x + element plus table表格和form搜索条件的二次封装
table表格的二次封装和Search的二次封装。使用方法大同小异;表格根据自己喜好把请求层拆出来,引用上面;直接上代码
trap-ui基础ui使用
https://www.npmjs.com/package/trap-ui
// table表格的二次封装
<template>
<div class="table_list_contaniner">
<el-form @submit.prevent ref="formFeildValRef" :model="formFeildVal" :rules="rules" label-width="0px">
<el-table
:size="size"
v-bind="options"
:border="border"
:data="formFeildVal.dataSource"
v-loading="loading"
style="width: 100%"
ref="multipleTableRefs"
:header-cell-style="{ background: '#F5F7FA' }"
@select-all="handleSelectAll"
@sort-change="handleSortChange"
@expand-change="handleExpandchange"
@selection-change="handleSelectionChange"
>
<!-- 单元格数据 -->
<template v-for="(column, index) in columns">
<!---复选框, 序号 (START)-->
<el-table-column
:key="index"
:prop="column.prop"
:align="column.align"
:label="column.label"
:type="column.type"
:width="column.width"
:max-width="column.maxWidth"
:min-width="column.minWidth"
:sortable="column.sortable"
v-bind="column.props"
v-if="column.type === 'index' || column.type === 'selection' || column.type === 'expand'"
/>
<el-table-column
:key="index + 1"
:prop="column.prop"
:label="column.label"
:align="column.align"
:width="column.width"
:sortable="column.sortable"
:max-width="column.maxWidth"
:min-width="column.minWidth"
v-bind="column.props"
v-else-if="!column.isShow || (column.isShow && column.isShow())"
:show-overflow-tooltip="!column.render && !column.slot && !column.children && !column.formatter && !column.newjump"
>
<template #default="scope">
<!-- render渲染 -->
<template v-if="column.render">
<component :is="column.render" :row="scope.row" :index="index" />
</template>
<!-- 表单信息 -->
<el-form-item
:rules="rules?.[column.prop]"
v-else-if="
scope.row.isShowEdit &&
!column.children &&
(!column.closeEdit || (column.closeEdit && column.closeEdit(scope.row, scope.$index)))
"
:prop="'dataSource.' + scope.$index + '.' + column.prop"
>
<el-select
clearable
style="width: 100%"
v-bind="column.form"
v-if="column.formType === 'select'"
v-model="scope.row[column.prop]"
:placeholder="column.placeholder || '请选择'"
>
<el-option v-for="option in selectDataList" :key="option.value" :value="option.value" :label="option.label" />
</el-select>
<el-input
v-else
clearable
v-bind="column.form"
v-model.trim="scope.row[column.prop]"
:placeholder="column.placeholder || '请输入'"
/>
<div
class="tips"
v-if="scope.row.isShowEdit"
v-html="(column.tips && column.tips(scope.row, column, scope.$index)) || ''"
/>
</el-form-item>
<!-- slot插槽 -->
<template v-else-if="column.slot">
<slot :slotName="column.slot" :row="scope.row" :index="index" />
</template>
<!-- 操作按钮 -->
<template v-else-if="column.children">
<template v-for="(btn, key) in column.children">
<span :key="key" v-if="!btn.isShow || (btn.isShow && btn.isShow(scope.row, scope.$index))">
<el-button
:icon="btn.icon"
:plain="btn.plain"
style="padding: 6px"
:loading="scope.row?.loading"
:size="btn.size || 'small'"
:text="btn.text ? false : true"
:type="btn.type ? btn.type : 'primary'"
:disabled="btn.disabled && btn.disabled(scope.row, scope.$index)"
@click="btn.method(scope.row, scope.$index)"
>{{ btn.label }}</el-button
>
</span>
</template>
</template>
<!-- 单元格文本 -->
<template v-else>
<template v-if="column.formatter">
<span
v-html="column.formatter(scope.row, column, scope.$index)"
@click="column?.click && column?.click(scope.row, scope.$index)"
/>
</template>
<template v-else-if="column.newjump">
<router-link
class="newjump"
v-bind="{ target: '_blank', ...column.target }"
:to="column.newjump(scope.row, column, scope.$index)"
>{{ scope.row[column.prop] || column.content }}
</router-link>
</template>
<template v-else>
<span
:style="column.click ? 'color: #409EFF; cursor: pointer;' : null"
@click="column?.click && column?.click(scope.row, scope.$index)"
>
{{ scope.row[column.prop] || column.content || '--' }}
{{ `${scope.row[column.prop] && column.unit ? column.unit : ''}` }}
</span>
</template>
</template>
</template>
<!-- 自定义表头 -->
<template #header="scope">
<component v-if="column.headerRender" :is="column.headerRender" :column="scope.column" :index="scope.$index" />
<slot
name="customHeader"
:column="column"
v-else-if="column.headerSlot"
:slotName="column.headerSlot"
:index="scope.$index"
/>
<span v-else>{{ column.label }}</span>
</template>
</el-table-column>
</template>
</el-table>
<!-- 分页部分 -->
<div class="footer_box">
<div style="display: flex; align-items: center">
<template v-if="isFooterExtend">
<el-checkbox style="margin: 0 20px 0 1.2em" v-model="checkedAllSelect" @change="handleChangeSelected" />
<el-button
size="small"
@click="handleDeactivate"
:plain="!multipleSelection.length"
:disabled="!multipleSelection.length"
:type="multipleSelection.length ? '' : 'info'"
>停用</el-button
>
<el-button
size="small"
@click="handleEnable"
:plain="!multipleSelection.length"
:disabled="!multipleSelection.length"
:type="multipleSelection.length ? '' : 'info'"
>启用</el-button
>
</template>
<slot v-else name="customFooter" />
</div>
<el-pagination
small
background
:total="dataAllTotal"
v-if="pagination"
@size-change="handleSizeChange"
:page-sizes="[5, 10, 20, 30, 50, 100]"
:current-page="paginationInfo.curPage"
:page-size="paginationInfo.pageSize"
@current-change="handleChangePage"
:layout="options?.pageExtendLayout || 'total, sizes, prev, pager, next, jumper'"
/>
</div>
</el-form>
</div>
</template>
<script lang="ts" , setup>
import axios from 'axios'
import { ref, reactive, watch, toRaw, onBeforeMount } from 'vue'
import { type FormInstance, type FormRules, ElTable, ElMessage } from 'element-plus'
const getToken = document.cookie.split('=')
axios.defaults.headers['Authorization'] = 'Bearer ' + getToken[getToken.length - 1]
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
interface httpRequest {
httpApi?: string
method?: string
params?: object
response?: any
ajax?: Function
curPage?: number
pageSize?: number
}
const props = withDefaults(
defineProps<{
size?: string
columns: Record<string, any>[]
border?: boolean
options?: any
httpRequest?: httpRequest
pagination?: boolean
isFooterExtend?: boolean
modelValue?: object[]
dataTotal?: number
customTotal?: string
rules?: FormRules
selectDataList?: Record<string, any>[]
}>(),
{
border: false,
pagination: true,
isFooterExtend: true,
dataTotal: 0
}
)
const emit = defineEmits([
'selection-change',
'current-change',
'size-change',
'sort-change',
'expand-change',
'on-deactivate',
'on-enable',
'update:modelValue'
])
const errorMessage = {
400: '请求错误',
401: '无权限访问',
403: '拒绝访问',
404: '请求地址出错',
408: '请求超时',
500: '服务器内部错误',
501: '服务未实现',
502: '网关错误',
503: '服务不可用',
504: '网关超时',
505: 'HTTP 版本不受支持'
}
const multipleSelection = ref<object[]>([])
const multipleTableRefs = ref<InstanceType<typeof ElTable>>()
const loading = ref<boolean>(false)
const dataAllTotal = ref<number>(0)
const paginationInfo = reactive({
curPage: 1,
pageSize: 10
})
const checkedAllSelect = ref<boolean>(false)
const formFeildValRef = ref<FormInstance>(null)
const formFeildVal = reactive<Record<string, any>>({ dataSource: [] })
const convertParams = (props) => {
const newParams = {}
for (const index in props) {
const item = props[index]
const type = typeof item
if (item || item === 0) {
if (item && type === 'string') {
newParams[index] = item.replace(/(^\s+)|(\s+$)/g, '')
} else if (Object.prototype.toString.call(item) === '[object Object]') {
newParams[index] = convertParams(item)
} else {
newParams[index] = item
}
}
}
return newParams
}
// 复选框全选和全不选
const handleSelectAll = (val) => {
if (JSON.stringify(val) === '[]') {
checkedAllSelect.value = false
} else {
checkedAllSelect.value = true
}
}
// 复选框选中项
const handleSelectionChange = (val) => {
multipleSelection.value = val
checkedAllSelect.value = false
emit('selection-change', Array.from(multipleSelection.value))
}
// 底部复选框全选中
const handleChangeSelected = (val) => {
if (val) {
multipleTableRefs.value.toggleAllSelection()
} else {
multipleTableRefs.value.clearSelection()
}
}
// 列表排序
const handleSortChange = (row) => {
emit('sort-change', row)
}
// 某行的折叠事件触发
const handleExpandchange = (row, expandedRows) => {
emit('expand-change', row, expandedRows)
}
// 改变分页触发事件
const handleChangePage = (current = 1) => {
paginationInfo.curPage = current
emit('current-change', current)
getDataList()
}
// 改变分页条数
const handleSizeChange = (page = 10) => {
paginationInfo.pageSize = page
emit('size-change', page)
getDataList()
}
// 停用
const handleDeactivate = () => {
emit('on-deactivate', Array.from(multipleSelection.value))
}
// 启用
const handleEnable = () => {
emit('on-enable', Array.from(multipleSelection.value))
}
// 获取列表数据
const getDataList = async () => {
if (props.modelValue) {
formFeildVal.dataSource = toRaw(props.modelValue)
if (props.dataTotal) {
dataAllTotal.value = props.dataTotal
}
return emit('update:modelValue', formFeildVal.dataSource)
}
const { httpApi, method, params, response, ajax } = props.httpRequest || {}
const { classA, classB, customTotal, customPage, customPageSize } = response || {}
if (!httpApi) return false
loading.value = true
const pageInfo = customPage
? {
[customPage || 'curPage']: paginationInfo.curPage,
[customPageSize || 'pageSize']: paginationInfo.pageSize
}
: { ...paginationInfo }
const methodParmas =
(method || 'get').toLocaleLowerCase() === 'get'
? { params: { ...pageInfo, ...(params || {}) } }
: { ...pageInfo, ...(params || {}) }
try {
const response: any = ajax
? await ajax(httpApi, { ...pageInfo, ...(params || {}) }, method || 'get')
: await axios[method || 'get'](httpApi, convertParams(methodParmas))
const res: any = ajax ? response : response.data || {}
loading.value = false
if (res?.code === 0) {
formFeildVal.dataSource = classA && classB ? res?.data[classA][classB] : classA ? res?.data[classA] : res?.data?.data || []
dataAllTotal.value = customTotal ? res?.data[customTotal] : res?.data?.totalCnt || 0
emit('update:modelValue', formFeildVal.dataSource)
if (checkedAllSelect.value === true) {
checkedAllSelect.value = false
}
} else {
!ajax ? ElMessage.error(res.message) : void null
}
} catch (err) {
loading.value = false
formFeildVal.dataSource = []
dataAllTotal.value = 0
if (!ajax) {
const {
response: { status }
} = err
errorMessage[status] ? ElMessage.error(errorMessage[status]) : void null
}
throw new Error(err)
}
}
// 提交form列表
const handleSubmit = () => {
formFeildValRef.value.validate(async (valid) => {
if (!valid) return
emit('update:modelValue', toRaw(formFeildVal.dataSource || []))
})
}
const handlePagination = () => {
if (!props.httpRequest?.curPage && !props.httpRequest?.pageSize) return
const curPage = props.httpRequest?.curPage || 1
const pageSize = props.httpRequest?.pageSize || 10
paginationInfo.curPage = curPage
paginationInfo.pageSize = pageSize
}
onBeforeMount(() => {
if (props.modelValue) return getDataList()
if (props.httpRequest && JSON.stringify(props.httpRequest) !== '{}') {
handlePagination()
getDataList()
}
})
watch(
() => props.httpRequest?.params,
() => {
paginationInfo.curPage = 1
getDataList()
}
)
watch(
() => props.modelValue,
(newVal) => {
if (newVal === undefined) return
getDataList()
}
)
defineExpose({
fetch: getDataList,
submit: handleSubmit,
formEl: formFeildValRef,
element: multipleTableRefs,
checkAll: handleChangeSelected
})
</script>
<script lang="ts">
export default { name: 'TableList' }
</script>
<style lang="scss" scoped>
:deep(.el-button--small) {
font-size: 13px;
}
:deep(.el-form-item) {
margin-bottom: 0px;
}
.table_list_contaniner {
overflow: hidden;
}
.table-header {
padding-top: 10px;
}
.table-header .table-header_button {
text-align: right;
float: right;
margin-bottom: 12px;
line-height: 40px;
}
.table_list_contaniner .newjump {
text-decoration: none;
color: dodgerblue;
}
.table_list_contaniner .footer_box {
display: flex;
flex-wrap: wrap;
margin-top: 10px;
align-items: center;
justify-content: space-between;
}
.table_list_contaniner .pagination {
float: right;
}
</style>
form 表单,搜索条件的二次封装,常用标签;按需添加!
<template>
<div :style="{ display: 'flex', 'overflow-x': isMobile ? 'scroll' : void null }">
<!-- 扩展性内容 -->
<slot name="pre_form_content" />
<el-form
:inline="true"
:rules="rules"
:model="formSearch"
ref="formSearchRef"
@submit.prevent
v-if="search && search.length > 0"
v-bind="{ 'label-width': '110px', ...options?.formProps }"
>
<template :key="index" v-for="(item, index) in search">
<el-form-item :prop="item.value" v-bind="item.labelProps" :label="item.label ? item.label + ':' : void null">
<el-select
clearable
style="width: 100%"
v-bind="item.props"
v-if="item.type === 'select'"
v-model="formSearch[item.value]"
:placeholder="`请选择${item.placeholder || item.label}`"
>
<el-option v-for="option in item.children" :key="index" :value="option.value" :label="option.label" />
</el-select>
<el-select-v2
clearable
style="width: 100%"
v-bind="item.props"
:options="item.children"
v-else-if="item.type === 'select-v2'"
v-model="formSearch[item.value]"
:placeholder="`请选择${item.placeholder || item.label}`"
/>
<el-date-picker
clearable
placeholder="选择日期"
v-bind="item.props || { type: 'date' }"
v-else-if="item.type === 'picker'"
v-model="formSearch[item.value]"
/>
<el-row type="flex" :gutter="5" v-else-if="item.type === 'datePicker'">
<el-col :span="12">
<el-date-picker
clearable
:editable="false"
placeholder="选择开始日期"
v-bind="item.props || { type: 'date' }"
v-model="formSearch[item.startDate]"
:disabled-date="(time) => startPickerOptions(time, formSearch[item.endDate])"
/>
</el-col>
<el-col :span="12">
<el-date-picker
clearable
:editable="false"
placeholder="选择结束日期"
v-bind="item.props || { type: 'date' }"
v-model="formSearch[item.endDate]"
:disabled-date="(time) => endPickerOptions(time, formSearch[item.startDate])"
/>
</el-col>
</el-row>
<el-input
v-else
clearable
v-bind="item.props"
:type="item.inputType || 'text'"
v-model.trim="formSearch[item.value]"
:placeholder="`请输入${item.placeholder || item.label}`"
:maxlength="item.maxlength"
@keyup.enter="handleSearch"
:oninput="handleChangeInput(item)"
>
<template #append v-if="item.vSlot">
<el-button icon="Search" @click="handleSearch" />
</template>
</el-input>
</el-form-item>
</template>
<el-form-item v-if="isShowSearch" label-width="0">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-if="isShowReset" @click="handleReset(formSearchRef)">重置</el-button>
</el-form-item>
</el-form>
<slot name="next_form_content" />
</div>
</template>
<script lang="ts" setup>
import type { FormInstance, FormRules } from 'element-plus'
import { reactive, ref, onMounted, watch, computed, onBeforeMount, onBeforeUnmount } from 'vue'
const WIDTH = 992
const props = withDefaults(
defineProps<{
search: Record<string, any>[]
rules?: FormRules
options?: any
reset?: boolean
isShowSearch?: boolean
isShowReset?: boolean
value?: any
}>(),
{
isShowReset: true
}
)
const isMobile = ref<boolean>(false)
const formSearch = reactive({})
const formSearchRef = ref<FormInstance>()
const emit = defineEmits(['handleSearch', 'handleReset'])
// 搜索查询按钮
const handleSearch = () => {
if (props.rules) {
return formSearchRef.value.validate((valid) => {
if (!valid) return false
emit('handleSearch', Object.assign({}, formSearch))
})
}
emit('handleSearch', Object.assign({}, formSearch))
}
// 搜索重置按钮
const handleReset = (formName: FormInstance | undefined) => {
formName.resetFields()
props.reset ? Object.assign(formSearch, props.value) : {}
emit('handleReset')
if (props.reset) return false
handleSearch()
}
// input为number校验
const handleChangeInput = (item) => {
return item.inputType === 'number' ? handleOnInput(formSearch[item.value], item.value, item.maxlength) : null
}
// input渲染长度校验
const handleOnInput = (val, label, maxlength) => {
if (val && Number(val) <= 0) {
formSearch[label] = undefined
}
if (maxlength && val && val.length > maxlength) {
formSearch[label] = formSearch[label].slice(0, maxlength)
}
}
const startPickerOptions = computed(() => (time: any, value: any) => {
const endDateVal = new Date(value).getTime()
if (endDateVal) {
return time.getTime() > endDateVal - 0
}
})
const endPickerOptions = computed(() => (time: any, value: any) => {
const startDateVal = new Date(value).getTime()
if (startDateVal) {
return time.getTime() < startDateVal - 8.64e7 - 1
}
})
const _isMobile = () => {
const rect = document.body.getBoundingClientRect()
return rect.width - 1 < WIDTH
}
const toggleDevice = (value) => {
isMobile.value = value
}
const _resizeHandler = () => {
if (!document.hidden) {
const isMobile = _isMobile()
toggleDevice(isMobile ? true : false)
}
}
onBeforeMount(() => {
window.addEventListener('resize', _resizeHandler)
})
onMounted(() => {
if (props.value) {
Object.assign(formSearch, props.value)
}
if (_isMobile()) {
toggleDevice(isMobile.value)
}
})
onBeforeUnmount(() => {
window.removeEventListener('resize', _resizeHandler)
})
watch(
() => props.value,
(newval) => {
if (newval) return Object.assign(formSearch, props.value)
}
)
</script>
<script lang="ts">
export default { name: 'SearchForm' }
</script>
<style lang="scss" scoped>
:deep(.el-form--inline .el-form-item) {
margin-right: 12px;
}
</style>