Vue3 多功能表单

自用存档,如需要依赖和使用说明可以私信

完整项目地址

Gitee
GitHub

功能描述

1、表格支持自适应页面的高度、宽度
2、支持行筛选、排序(服务端筛选排序)、支持前端底部汇总sum、avg
3、支持列宽度调整、顺序调整、字段隐藏/显示、固定左侧/右侧的配置,且可通过id保存到服务端、下次打开页面时自动从服务端加载配置
4、支持通过配置生成表格上的筛选表单,作为表格筛选条件的一部分传入服务端筛选
5、支持动态修改Locale切换国际化,支持动态切换light/dark两种色彩模式
6、支持行内编辑,行内编辑时可通过键盘快捷键切换下一个框,到尾部后可自动新增行
7、表头筛选支持按配置数据源的方式生成element的输入框、下拉框、日期选择控件来筛选
8、支持主表明细
9、支持弹窗高级筛选,筛选条件可通过接口保存为常用筛选
10、支持配置导出列后进行excel导出

在这里插入图片描述

组件代码

<template>
  <div :style="isDark ? 'background-color: #000;' : 'background-color: #fff;'">
    <vxe-pulldown v-model="showPull" style="position: fixed;top: 10px;right: 10px;" mode="text">
      <template #default>
        <vxe-button icon="vxe-icon-arrow-down" @click="toggleEvent">Language</vxe-button>
      </template>
      <template #dropdown>
        <div>
          <div class="my-dropdown-link" @click="clickEvent('zh-CN')">中文(简体)</div>
          <div class="my-dropdown-link" @click="clickEvent('zh-HK')">中文(香港)</div>
          <div class="my-dropdown-link" @click="clickEvent('zh-TW')">中文(繁体)</div>
          <div class="my-dropdown-link" @click="clickEvent('zh-MO')">中文(澳门)</div>
          <div class="my-dropdown-link" @click="clickEvent('en-US')">英语(美国)</div>
          <div class="my-dropdown-link" @click="clickEvent('es-ES')">西班牙语(国际)</div>
          <div class="my-dropdown-link" @click="clickEvent('hu-HU')">匈牙利语</div>
          <div class="my-dropdown-link" @click="clickEvent('ja-JP')">日语</div>
          <div class="my-dropdown-link" @click="clickEvent('ko-KR')">朝鲜语</div>
          <div class="my-dropdown-link" @click="clickEvent('pt-BR')">葡萄牙语</div>
          <div class="my-dropdown-link" @click="clickEvent('ru-RU')">俄语</div>
          <div class="my-dropdown-link" @click="clickEvent('uk-UA')">乌克兰语</div>
        </div>
      </template>
    </vxe-pulldown>
    <div class="toolbar">
      <vxe-button status="primary" icon="vxe-icon-save" circle @click="exportEvent"></vxe-button>
      <vxe-button status="primary" icon="vxe-icon-setting" circle @click="openToolEvent"></vxe-button>
      <vxe-button icon="vxe-icon-sunny" circle @click="toggleTheme"></vxe-button>
      <vxe-button status="primary" icon="vxe-icon-search" circle @click="showFilter = true" />
    </div>
    <vxe-grid ref="gridRef" v-bind="gridOptions" v-on="gridEvents">
      <template #expand_content="{ row }">
        <div>Name:{{ row.name }}</div>
        <div>Age:{{ row.age }}</div>
      </template>
    </vxe-grid>

    <!-- 列配置弹窗 -->
    <vxe-modal v-model="showTool" type="alert" title="列配置" showFooter width="1200px" @confirm="ToolClick">
      <template #default>
        <vxe-table :data="gridOptions.columns" show-overflow :edit-config="editToolConfig">
          <vxe-column field="title" title="标题" :edit-render="{ name: 'input' }"></vxe-column>
          <vxe-column field="width" title="宽度" :edit-render="{ name: 'input' }"></vxe-column>
          <vxe-column field="align" title="对齐方式" :edit-render="{
            name: 'select',
            options: [{ label: '', value: '' }, { label: '左侧', value: 'left' },
            { label: '居中', value: 'center' },
            { label: '右侧', value: 'right' }]
          }"></vxe-column>
          <vxe-column field="headerAlign" title="表头对齐方式" :edit-render="{
            name: 'select',
            options: [{ label: '', value: '' }, { label: '左侧', value: 'left' },
            { label: '居中', value: 'center' },
            { label: '右侧', value: 'right' }]
          }"></vxe-column>
          <vxe-column field="footerAlign" title="表尾对齐方式" :edit-render="{
            name: 'select',
            options: [{ label: '', value: '' }, { label: '左侧', value: 'left' },
            { label: '居中', value: 'center' },
            { label: '右侧', value: 'right' }]
          }"></vxe-column>
          <vxe-column field="fixed" title="冻结列" :edit-render="{
            name: 'select',
            options: [{ label: '', value: '' }, { label: '左侧', value: 'left' },
            { label: '右侧', value: 'right' }]
          }"></vxe-column>
        </vxe-table>
      </template>
    </vxe-modal>
    <!-- 操作提示 -->
    <div class="message" v-if="false">
      <p style="min-width: 100%; text-indent: 2em;">" | Arrow Up ↑ | 移动到当前活动单元格上面的单元格 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Arrow Down ↓ | 移动到当前活动单元格下面的单元格 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Arrow Left ← | 移动到当前活动单元格左边的单元格 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Arrow Right → | 移动到当前活动单元格右边的单元格 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Tab | 移动到当前选中或活动单元格的右侧单元格,如果到最后一列且存在下一行,则从下一行开始移动 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Tab + Shift | 移动到当前选中或活动单元格的左侧单元格,如果到第一列且存在上一行,则从上一行开始移动 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Spacebar | 如果单元格是复选框或单选框则切换勾选状态 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Enter | 取消编辑并移动到当前活动单元格下面的单元格 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Delete | 清空内容 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Backspace | 清空内容并激活选中单元格为编辑状态 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | F2 | 激活单元格编辑 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Esc | 取消单元格编辑 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Page Up | 向上翻页滚动 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Page Down | 向下翻页滚动 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | Home | 滚动到顶部 |。"</p>
      <p style="min-width: 100%; text-indent: 2em;">" | End | 滚动到底部 |。"</p>
    </div>

    <!-- 高级搜索弹窗 -->
    <vxe-modal v-model="showFilter" type="alert" title="高级筛选" showFooter width="1200px" @confirm="filterConfirm">
      <template #default>
        <div class="dialog-header">
          <div>
            <vxe-button-group>
              <vxe-button status="primary" @click="addFilterRule">新增筛选</vxe-button>
              <vxe-button status="primary" @click="resetFilter">重置条件</vxe-button>
            </vxe-button-group>
          </div>
          <div>
            <vxe-button-group>
              <vxe-button status="primary" @click="saveFilter(0)">保存方案</vxe-button>
              <vxe-button status="primary" @click="saveFilter(2)">另存方案</vxe-button>
            </vxe-button-group>

            <vxe-select style="margin-left: 10px;" v-model="selectedFilterIndex" @change="selectFilter"
              placeholder="选择方案">
              <vxe-option v-for="(item, index) in filterConfigurations" :key="index" :label="item.name"
                :value="index" />
            </vxe-select>
          </div>
        </div>

        <vxe-table ref="filterTableRef" :data="filterRules" style="width: 100%" :row-config="{ keyField: 'fieldCount' }"
          :tree-config="treeConfig" :column-config="columnConfig">
          <vxe-column field="field" title="列名" tree-node>
            <template #default="{ row }">
              <vxe-select v-model="row.field" @change="handleFieldChange(row)">
                <vxe-option v-for="col in filteredColumns" :key="col.field" :label="col.title" :value="col.field" />
              </vxe-select>
            </template>
          </vxe-column>
          <vxe-column field="operator" title="比较运算符">
            <template #default="{ row }">
              <vxe-select v-model="row.operator" :disabled="!row.field" @change="handleOperatorChange(row)">
                <vxe-option label="等于" value="eq" v-if="['number', 'string', 'date'].includes(row.type)" />
                <vxe-option label="不等于" value="ne" v-if="['number', 'string', 'date'].includes(row.type)" />
                <vxe-option label="大于" value="gt" v-if="['number', 'date'].includes(row.type)" />
                <vxe-option label="大于或等于" value="ge" v-if="['number', 'date'].includes(row.type)" />
                <vxe-option label="小于" value="lt" v-if="['number', 'date'].includes(row.type)" />
                <vxe-option label="小于或等于" value="le" v-if="['number', 'date'].includes(row.type)" />
                <vxe-option label="介于" value="between" v-if="row.type === 'date'" />
                <vxe-option label="不介于" value="notBetween" v-if="row.type === 'date'" />
                <vxe-option label="包含" value="like" v-if="row.type === 'string'" />
                <vxe-option label="不包含" value="notLike" v-if="row.type === 'string'" />
                <vxe-option label="以…开始" value="likeLeft" v-if="row.type === 'string'" />
                <vxe-option label="以…结束" value="likeRight" v-if="row.type === 'string'" />
                <vxe-option label="空" value="isNull" v-if="row.type === 'string'" />
                <vxe-option label="非空" value="isNotNUll" v-if="row.type === 'string'" />
                <vxe-option label="在列表之中" value="in" v-if="['number', 'string'].includes(row.type)" />
                <vxe-option label="不在列表之中" value="notIn" v-if="['number', 'string'].includes(row.type)" />
              </vxe-select>
            </template>
          </vxe-column>
          <vxe-column field="value" title="值">
            <template #default="{ row }">
              <template v-if="!['in', 'notIn'].includes(row.operator)">
                <vxe-input v-if="row.type === 'string'" v-model="row.value" :disabled="!row.field" />
                <vxe-input v-if="row.type === 'number'" v-model="row.value" type="number" :disabled="!row.field" />
                <vxe-date-range-picker v-if="row.type === 'date' && ['between', 'notBetween'].includes(row.operator)"
                  v-model="row.value" type="date" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
                <vxe-date-picker v-if="row.type === 'date' && !['between', 'notBetween'].includes(row.operator)"
                  v-model="row.value" type="date" />
              </template>
              <vxe-select v-else v-model="row.value" multiple :options="row.inList || []" />
            </template>
          </vxe-column>
          <vxe-column field="logic" title="逻辑运算符">
            <template #default="{ row }">
              <vxe-select v-model="row.logic" :disabled="!row.field">
                <vxe-option label="并且" value="and" />
                <vxe-option label="或者" value="or" />
              </vxe-select>
            </template>
          </vxe-column>
          <vxe-column title="操作" width="160">
            <template #default="{ row }">
              <vxe-button status="primary" icon="vxe-icon-add" @click="addChildFilter(row)" />
              <vxe-button status="primary" icon="vxe-icon-minus" @click="removeFilter(row)" />
            </template>
          </vxe-column>
        </vxe-table>
      </template>
    </vxe-modal>

    <!-- 保存筛选方案弹窗 -->
    <vxe-modal v-model="showSaveFilter" type="alert" title="请为该规则添加一个名字" showFooter @confirm="confirmSaveFilter">
      <template #default>
        <vxe-input v-model="filterName" placeholder="请输入方案名称" />
      </template>
    </vxe-modal>
  </div>
</template>

<script lang="ts" setup>
import { defineProps, ref, reactive, computed, nextTick } from 'vue'
import { VxeUI } from 'vxe-table'
import type { VxeGridProps } from 'vxe-table'

// 定义组件属性
const props = defineProps({
  tableData: {
    type: Array,
    required: true
  },
  tableConfig: {
    type: Object,
    required: true
  },
  ifForm: {
    type: Boolean,
    default: false
  }
})

// 创建过滤后的列数组
const filteredColumns = computed(() => {
  return props.tableConfig.columns.filter((column: { type: string }) => 
    !['seq', 'checkbox', 'expand'].includes(column.type)
  )
})

// 定义组件事件
const emit = defineEmits(['filter', 'search', 'reset'])

const filterTableRef = ref()

interface FilterRule {
  id: number
  parentId: number | null
  logic: string
  field: string
  operator: string
  value: string | number | any[]
  fieldCount: number
  type: string
  filters?: FilterRule[]
  hasChildren?: number
}

const gridRef = ref()
const gridOptions = reactive<VxeGridProps>({
  border: true,
  showFooter: true,
  showOverflow: true,
  keepSource: true,
  mouseConfig: {
    selected: true
  },
  keyboardConfig: {
    isEdit: true,
    isArrow: true,
    isEnter: true,
    isTab: true,
    isDel: true,
    isBack: true,
    isEsc: true,
    editMode: 'insert',
    isLastEnterAppendRow: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'cell',
    showStatus: true
  },
  exportConfig: {
    filename() {
      return `导出文件名${Date.now()}`
    },
    sheetName() {
      return `导出标题${Date.now()}`
    },
    excludeFields: [
      'seq',
      'checkbox',
      'expand'
    ]
  },
  // 搜索配置
  formConfig: props.ifForm ? {
    data: props.tableConfig.columns.reduce((acc:any, col:any) => {
      if (col.field) {
        acc[col.field] = ''
      }
      return acc
    }, {}),
    items: [
      ...props.tableConfig.columns
        .filter((col: { field: any; title: any }) => col.field && col.title)
        .map((col: { field: any; title: any; editRender: any }) => ({
          field: col.field,
          title: col.title,
          itemRender: col.editRender || { name: 'VxeInput' }
        })),
      {
        itemRender: {
          name: 'VxeButtonGroup',
          options: [
            { type: 'submit', content: '搜索', status: 'primary' },
            { type: 'reset', content: '重置' }
          ]
        }
      }
    ]
  } : undefined,
  columns: props.tableConfig.columns,
  data: props.tableData,
  footerData: props.tableConfig.footerData
})
const exportEvent = () => {
  gridOptions.columns = filteredColumns.value
  ToolClick()
  const $grid = gridRef.value
  if ($grid) {
    $grid.openExport()
  }
  // 屏蔽然后展示,绕过导出不能设置字段的缺陷
  nextTick(() => {
    gridOptions.columns = props.tableConfig.columns
    ToolClick()
  })
}

// 表头的返回
const gridEvents = {
  formSubmit(params: any) {
    emit('search', params)
  },
  formReset(params: any) {
    emit('reset', params)
  }
}

// 打开列布局
const showTool = ref(false)
const openToolEvent = () => {
  showTool.value = true
}
const editToolConfig = ref({
  trigger: 'click' as 'click' | 'manual' | 'dblclick',
  mode: 'cell' as 'cell' | 'row'
})

// 触发列配置修改
const ToolClick = () => {
  const $grid = gridRef.value
  if ($grid) {
    $grid.loadColumn(gridOptions.columns)
  }
}

// 语言配置
import zhCN from 'vxe-table/lib/locale/lang/zh-CN' // 中文(简体)
import zhHK from 'vxe-table/lib/locale/lang/zh-HK' // 中文(香港)
import zhTW from 'vxe-table/lib/locale/lang/zh-TW' // 中文(繁体)
import zhMO from 'vxe-table/lib/locale/lang/zh-MO' // 中文(澳门)
import enUS from 'vxe-table/lib/locale/lang/en-US' // 英语(美国)
import esES from 'vxe-table/lib/locale/lang/es-ES' // 西班牙语(国际)
import huHU from 'vxe-table/lib/locale/lang/hu-HU' // 匈牙利语
import jaJP from 'vxe-table/lib/locale/lang/ja-JP' // 日语
import koKR from 'vxe-table/lib/locale/lang/ko-KR' // 朝鲜语
import ptBR from 'vxe-table/lib/locale/lang/pt-BR' // 葡萄牙语
import ruRU from 'vxe-table/lib/locale/lang/ru-RU' // 俄语
import ukUA from 'vxe-table/lib/locale/lang/uk-UA' // 乌克兰语

// 注册语言
VxeUI.setI18n('zh-CN', zhCN)
VxeUI.setI18n('zh-HK', zhHK)
VxeUI.setI18n('zh-TW', zhTW)
VxeUI.setI18n('zh-MO', zhMO)
VxeUI.setI18n('en-US', enUS)
VxeUI.setI18n('es-ES', esES)
VxeUI.setI18n('hu-HU', huHU)
VxeUI.setI18n('ja-JP', jaJP)
VxeUI.setI18n('ko-KR', koKR)
VxeUI.setI18n('pt-BR', ptBR)
VxeUI.setI18n('ru-RU', ruRU)
VxeUI.setI18n('uk-UA', ukUA)
// 下拉切换语言
const showPull = ref(false)
const i18Lang = ref('zhCN')
const toggleEvent = () => {
  showPull.value = !showPull.value
}
const clickEvent = (num: any) => {
  showPull.value = false
  i18Lang.value = num
  // 切换指定语言
  VxeUI.setLanguage(num)
}

// 主题切换相关
const isDark = ref(false)
const toggleTheme = () => {
  isDark.value = !isDark.value
  VxeUI.setTheme(isDark.value ? 'dark' : 'light')
}

// 高级筛选相关
const showFilter = ref(false)
const showSaveFilter = ref(false)
const filterName = ref('')
const selectedFilterIndex = ref()
const filterRules = ref<FilterRule[]>([])
interface FilterConfiguration {
  name: string
  rules: FilterRule[]
}

const filterConfigurations = ref<FilterConfiguration[]>([])
// 从本地存储加载筛选配置
const loadFilterConfigurations = () => {
  const storedConfig = localStorage.getItem('tableFilters')
  if (storedConfig) {
    filterConfigurations.value = JSON.parse(storedConfig)
  }
}

const columnConfig = reactive({
  resizable: true
})
const treeConfig = reactive({
  transform: true,
  rowField: 'id',
  parentField: 'parentId',
})

// 添加筛选规则
const addFilterRule = () => {
  filterRules.value.push({
    id: Date.now(),
    parentId: null,
    logic: 'and',
    field: '',
    operator: '',
    value: '',
    fieldCount: Date.now(),
    type: 'string'
  })
}

// 重置筛选条件
const resetFilter = () => {
  filterRules.value = []
}

// 处理字段变更
const handleFieldChange = (row: { field: string | undefined; type: string; value: string; operator: string }) => {
  const column = gridOptions.columns?.find(col => col.field === row.field)
  if (column) {
    row.type = column.type || 'string'
    row.value = ''
    row.operator = ''
  }
}

// 处理运算符变更
const handleOperatorChange = (row: { operator: string; value: string | never[] | null; type: string }) => {
  if (['between', 'notBetween'].includes(row.operator)) {
    row.value = []
  } else if (row.type === 'string') {
    row.value = ''
  } else if (row.type === 'number') {
    row.value = null
  }
}

// 添加子筛选规则
const addChildFilter = (row: any) => {
  const record = {
    id: Date.now(),
    parentId: row.id,
    logic: 'and',
    field: '',
    operator: '',
    value: '',
    fieldCount: Date.now(),
    type: 'string'
  }
  filterRules.value.push(record)
  // filterTableRef.value.insert(record)
  console.log(row, filterRules.value)
}

// 移除筛选规则
const removeFilter = (row: any) => {
  if (row.hasChildren) {
    const parent = filterRules.value.find(r => r.fieldCount === row.hasChildren)
    if (parent && parent.filters) {
      parent.filters = parent.filters.filter(f => f.fieldCount !== row.fieldCount)
    }
  } else {
    filterRules.value = filterRules.value.filter(r => r.fieldCount !== row.fieldCount)
  }
}

// 选择筛选方案
const selectFilter = () => {
  if (selectedFilterIndex.value !== undefined) {
    filterRules.value = JSON.parse(JSON.stringify(filterConfigurations.value[selectedFilterIndex.value].rules))
  }
}

// 保存筛选方案
const saveFilter = (type: number) => {
  if (type === 2) {
    showSaveFilter.value = true
  } else if (type === 0) {
    if (selectedFilterIndex.value !== undefined) {
      filterConfigurations.value[selectedFilterIndex.value].rules = JSON.parse(JSON.stringify(filterRules.value))
      localStorage.setItem('tableFilters', JSON.stringify(filterConfigurations.value))
    } else {
      showSaveFilter.value = true
    }
  }
}

// 确认保存筛选方案
const confirmSaveFilter = () => {
  if (filterName.value) {
    const newConfig = {
      name: filterName.value,
      rules: JSON.parse(JSON.stringify(filterRules.value))
    }
    filterConfigurations.value.push(newConfig)
    localStorage.setItem('tableFilters', JSON.stringify(filterConfigurations.value))
    showSaveFilter.value = false
    filterName.value = ''
  }
}

// 确认筛选
const filterConfirm = () => {
  const filterConfig = {
    logic: 'and',
    field: '',
    operator: '',
    value: '',
    filters: filterRules.value
  }
  // 触发筛选事件
  emit('filter', filterConfig)
  showFilter.value = false
}

// 初始化时加载筛选配置
loadFilterConfigurations()
</script>

<style scoped>
.toolbar {
  display: flex;
  align-items: center;
  justify-content: end;
  margin-bottom: 6px;
}

.my-dropdown-link {
  min-width: 100%;
  cursor: pointer;
}

.message {
  color: #999;
  font-size: 14px;
  text-align: left;
}

.dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.vxe-select {
  width: 120px;
}

.vxe-input {
  width: 200px;
}
</style>

组件引用

<script setup lang="ts">
import tableGrid from './components/tableGrid.vue';
import { ref, reactive } from 'vue';

// 表格数据
const tableData = ref([
  { id: 10001, name: 'Test1', date: '', role: 'Develop', sex: 'Man', age: 28, address: 'test abc' },
  { id: 10002, name: 'Test2', date: '', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' },
  { id: 10003, name: 'Test3', date: '', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' },
  { id: 10004, name: 'Test4', date: '', role: 'Designer', sex: 'Women', age: 24, address: 'Shanghai' }
])

// 表格配置
const tableConfig = reactive({
  columns: [
    { field: 'seq', type: 'seq', width: 70, fixed: 'left' },
    { field: 'checkbox', type: 'checkbox', width: 70 },
    { type: 'expand', width: 80, slots: { content: 'expand_content' } },
    { field: 'name', title: 'Name', align: 'center', headerAlign: 'center', footerAlign: 'center', editRender: { name: 'input' } },
    { field: 'date', title: '日期', align: 'center', headerAlign: 'center', footerAlign: 'center', width: 180, editRender: { name: 'VxeDatePicker', props: { type: 'date' } } },
    {
      field: 'sex', title: 'Sex', align: 'center', headerAlign: 'center', footerAlign: 'center', editRender: {
        name: 'select',
        options: [{ label: '未知', value: '' },
        { label: '女', value: 'Women' },
        { label: '男', value: 'Man' }]
      }
    },
    { field: 'age', title: 'Age', align: 'center', headerAlign: 'center', footerAlign: 'center', editRender: { name: 'VxeNumberInput', props: { type: 'integer' } } }
  ],
  footerData: [
    { seq: '合计', sex: '666', age: '999' },
    { seq: '平均', sex: '888', age: '333' }
  ]
})

// 处理筛选事件
const handleFilter = (filterConfig: any) => {
  console.log('筛选配置:', filterConfig)
  // 这里可以根据筛选配置处理数据
}
// 处理查询事件
const handleSearch = (filterConfig: any) => {
  console.log('查询:', filterConfig)
  // 这里可以处理数据
}
// 处理重置事件
const handleResetr = (filterConfig: any) => {
  console.log('重置:', filterConfig)
  // 这里可以处理数据
}
</script>

<template>
  <div>
    <tableGrid 
      :table-data="tableData"
      :table-config="tableConfig"
      :if-form="true"
      @filter="handleFilter"
      @search="handleSearch"
      @reset="handleResetr"
    ></tableGrid>
  </div>
</template>

<style>
body {
  margin: 0;
  width: 100%;
  height: 100%;
}
</style>

readme.md

TableGrid 组件使用说明

TableGrid 是一个基于Vxe-Table的高级表格组件,支持数据展示、编辑、筛选、导出等功能。

Vxe-Table文档地址:https://vxetable.cn/#/demo/list

功能特性

  • 数据展示与编辑
  • 高级筛选功能
  • 列配置功能
  • 数据导出功能
  • 主题切换
  • 多语言支持
  • 搜索表单(可选)

依赖

npm install vxe-pc-ui@4.5.36 vxe-table@4.13.20
# 或者
yarn add vxe-pc-ui@4.5.36 vxe-table@4.13.20
# 或者
pnpm add vxe-pc-ui@4.5.36 vxe-table@4.13.20

基础用法

<template>
  <tableGrid
    :table-data="tableData"
    :table-config="tableConfig"
    :if-form="true"
    @filter="handleFilter"
  ></tableGrid>
</template>

<script setup lang="ts">
import { ref, reactive } from "vue";

// 表格数据
const tableData = ref([
  {
    id: 10001,
    name: "Test1",
    date: "",
    role: "Develop",
    sex: "Man",
    age: 28,
    address: "test abc",
  },
  {
    id: 10002,
    name: "Test2",
    date: "",
    role: "Test",
    sex: "Women",
    age: 22,
    address: "Guangzhou",
  },
]);

// 表格配置
const tableConfig = reactive({
  columns: [
    // 序号列配置
    {
      field: "seq", // 字段名称
      type: "seq", // 字段类型为序号列
      width: 70, // 列宽度70px
      fixed: "left", // 固定在表格左侧
    },
    // 复选框列配置
    {
      field: "checkbox", // 字段名称
      type: "checkbox", // 字段类型为复选框
      width: 70, // 列宽度70px
    },
    // 展开列配置
    {
      type: "expand", // 字段类型为展开列
      width: 80, // 列宽度80px
      slots: { content: "expand_content" }, // 展开内容插槽
    },
    // 姓名列配置
    {
      field: "name", // 字段名称
      title: "Name", // 列标题
      align: "center", // 内容居中
      headerAlign: "center", // 表头居中
      footerAlign: "center", // 表尾居中
      editRender: {
        // 编辑配置
        name: "input", // 使用input输入框进行编辑
      },
    },
    // 日期列配置
    {
      field: "date", // 字段名称
      title: "日期", // 列标题
      align: "center", // 内容居中
      headerAlign: "center", // 表头居中
      footerAlign: "center", // 表尾居中
      width: 180, // 列宽度180px
      editRender: {
        // 编辑配置
        name: "VxeDatePicker", // 使用日期选择器
        props: { type: "date" }, // 日期选择器类型为日期
      },
    },
    // 性别列配置
    {
      field: "sex", // 字段名称
      title: "Sex", // 列标题
      align: "center", // 内容居中
      headerAlign: "center", // 表头居中
      footerAlign: "center", // 表尾居中
      editRender: {
        // 编辑配置
        name: "select", // 使用下拉选择器
        options: [
          // 下拉选项配置
          { label: "", value: "" },
          { label: "女", value: "Women" },
          { label: "男", value: "Man" },
        ],
      },
    },
    // 年龄列配置
    {
      field: "age", // 字段名称
      title: "Age", // 列标题
      align: "center", // 内容居中
      headerAlign: "center", // 表头居中
      footerAlign: "center", // 表尾居中
      editRender: {
        // 编辑配置
        name: "VxeNumberInput", // 使用数字输入框
        props: { type: "integer" }, // 限制为整数类型
      },
    },
  ],
  // 表尾数据配置
  footerData: [
    { seq: "合计", sex: "666", age: "999" },
    { seq: "平均", sex: "888", age: "333" },
  ],
});

// 处理筛选事件
const handleFilter = (filterConfig: any) => {
  console.log("筛选配置:", filterConfig);
  // 这里可以根据筛选配置处理数据
};
</script>

Props 说明

属性名类型必填默认值说明
tableDataArray-表格数据源
tableConfigObject-表格配置对象
ifFormBooleanfalse是否显示搜索表单

tableConfig 配置说明

interface TableConfig {
  columns: Array<{
    field?: string; // 字段名
    title?: string; // 列标题
    type?: string; // 列类型
    width?: number; // 列宽度
    fixed?: "left" | "right"; // 固定列
    align?: string; // 对齐方式
    headerAlign?: string; // 表头对齐方式
    footerAlign?: string; // 表尾对齐方式
    editRender?: {
      // 编辑配置
      name: string; // 编辑组件名称
      props?: object; // 编辑组件属性
      options?: Array<{
        // 选项(用于下拉框等)
        label: string;
        value: any;
      }>;
    };
  }>;
  footerData?: Array<any>; // 表尾数据
}

事件说明

事件名参数说明
filterfilterConfig: object高级筛选事件,返回筛选配置
searchparams: object搜索表单提交事件,返回搜索参数
resetparams: object搜索表单重置事件,返回重置后的参数

功能说明

1. 搜索表单

  • ifFormtrue 时,会自动根据 tableConfig.columns 生成搜索表单
  • 搜索表单的字段类型会自动继承表格列的编辑类型
  • 支持搜索和重置功能

2. 高级筛选

  • 支持多条件组合筛选
  • 支持保存筛选方案
  • 支持筛选方案的导入导出

3. 列配置

  • 支持自定义列宽
  • 支持列对齐方式设置
  • 支持列固定

4. 数据导出

  • 支持导出为 Excel
  • 支持自定义导出文件名和表名

5. 主题切换

  • 支持明暗主题切换
  • 支持自定义主题

6. 多语言

  • 支持多种语言切换
  • 支持自定义语言包

注意事项

  1. 表格列配置中的 editRender 会同时影响编辑和搜索表单的输入组件
  2. 高级筛选功能需要自行实现数据过滤逻辑
  3. 导出功能需要配置相应的导出权限
  4. 多语言切换需要预先注册语言包

示例代码

基础表格

<tableGrid :table-data="tableData" :table-config="tableConfig"></tableGrid>

带搜索表单的表格

<tableGrid
  :table-data="tableData"
  :table-config="tableConfig"
  :if-form="true"
></tableGrid>

带筛选功能的表格

<tableGrid
  :table-data="tableData"
  :table-config="tableConfig"
  @filter="handleFilter"
></tableGrid>

最后:谴责深圳【普盛网络】有偿定制功能却要了源码就跑的行为

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值