vue项目element plus table表格和form搜索条件的二次封装

3 篇文章 0 订阅
2 篇文章 0 订阅

欢迎点击领取 -《前端开发面试题进阶秘籍》:前端登顶之巅-最全面的前端知识点梳理总结

*分享一个使用比较久的🪜

在这里插入图片描述

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>
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunnyRun!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值