用deepseek封装一个 vue2+elementui 表单搜索组件和表格组件

主要是自己保存使用,有错误欢迎提出!

表单搜索代码

<!-- SearchForm.vue -->
<template>
  <div class="search-container">
    <el-form
      ref="searchForm"
      :model="formData"
      :inline="inline"
      :label-width="labelWidth"
      size="small"
      @submit.native.prevent
    >
      <el-row :gutter="gutter">
        <!-- 可见表单项 -->
        <el-col
          v-for="(item, index) in visibleConfig"
          :key="index"
          :span="item.span || defaultSpan"
          :offset="item.offset || 0"
        >
          <form-item :config="item" :form-data="formData" />
        </el-col>
      </el-row>
      <!-- 展开收起按钮 -->
      <el-row type="flex" justify="center">
        <el-col :span="btnSpan" class="operation-col">
          <el-form-item>
            <el-button
              type="primary"
              icon="el-icon-search"
              @click="handleSearch"
            >
              搜索
            </el-button>
            <el-button icon="el-icon-refresh-right" @click="handleReset">
              重置
            </el-button>
            <el-button
              v-if="showCollapse"
              type="text"
              @click="isCollapsed = !isCollapsed"
            >
              {{ isCollapsed ? "展开" : "收起" }}
              <i
                :class="isCollapsed ? 'el-icon-arrow-down' : 'el-icon-arrow-up'"
              />
            </el-button>
            <slot name="extra-buttons" />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  </div>
</template>

<script>
import FormItem from "./FormItem.vue";

export default {
  name: "SearchForm",
  components: { FormItem },
  props: {
    config: {
      type: Array,
      required: true,
    },
    // 传入的查询参数
    value: {
      type: Object,
      required: true,
    },
    inline: {
      type: Boolean,
      default: true,
    },
    labelWidth: {
      type: String,
      default: "90px",
    },
    gutter: {
      type: Number,
      default: 16,
    },
    defaultSpan: {
      type: Number,
      default: 6,
    },
    btnSpan: {
      type: Number,
      default: 6,
    },
    // 显示折叠按钮的阈值
    collapseThreshold: {
      type: Number,
      default: 3,
    },
  },
  data() {
    return {
      formData: { ...this.value },
      isCollapsed: true,
    };
  },
  computed: {
    // 判断是否显示展开收起
    showCollapse() {
      return this.config.length > this.collapseThreshold;
    },
    visibleConfig() {
      return this.showCollapse && this.isCollapsed
        ? this.config.slice(0, this.collapseThreshold)
        : this.config;
    },
  },
  watch: {
    value: {
      handler(newVal) {
        this.formData = { ...newVal };
      },
      deep: true,
    },
  },
  methods: {
    handleSearch() {
      this.$emit("search", this.formData);
    },
    // 重置表单
    handleReset() {
      this.$refs.searchForm.resetFields();
      this.$emit("reset");
      this.$emit("input", this.formData);
    },
  },
};
</script>

<style scoped>
.search-container {
  padding: 16px;
  background: #fff;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
}

.operation-col {
  text-align: center;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 10px;
}

.el-form-item {
  margin-bottom: 16px;
}

.el-button--text {
  padding: 9px 5px;
}
</style>

搜索表单项组件

<!-- FormItem.vue -->
<template>
  <el-form-item :label="config.label" :prop="config.prop">
    <!-- 输入框 -->
    <el-input
      v-if="config.type === 'input'"
      v-model="formData[config.prop]"
      :placeholder="config.placeholder || '请输入'"
      clearable
      size="small"
    />

    <!-- 下拉选择 -->
    <el-select
      v-else-if="config.type === 'select'"
      v-model="formData[config.prop]"
      :placeholder="config.placeholder || '请选择'"
      clearable
      size="small"
    >
      <el-option
        v-for="opt in config.options"
        :key="opt.value"
        :label="opt.label"
        :value="opt.value"
      />
    </el-select>

    <!-- 日期选择 -->
    <el-date-picker
      v-else-if="config.type === 'date'"
      v-model="formData[config.prop]"
      :type="config.dateType || 'date'"
      :placeholder="config.placeholder || '选择日期'"
      value-format="yyyy-MM-dd"
      size="small"
    />

    <!-- 自定义插槽 -->
    <slot
      v-else-if="config.type === 'slot'"
      :name="config.slotName"
      :scope="formData"
    />
  </el-form-item>
</template>

<script>
export default {
  name: "FormItem",
  props: {
    config: {
      type: Object,
      required: true,
    },
    formData: {
      type: Object,
      required: true,
    },
  },
};
</script>

表单配置文件

// formConfig.js
export const SEARCH_CONFIG = [
  {
    type: "input",
    label: "订单",
    prop: "aaa",
    span: 8,
  },
  {
    type: "input",
    label: "订单编号",
    prop: "orderNo",
    span: 8,
  },
  {
    type: "input",
    label: "订单编号",
    prop: "orderNo",
    span: 8,
  },
  {
    type: "select",
    label: "订单状态",
    prop: "status",
    options: [
      { label: "待支付", value: 1 },
      { label: "已发货", value: 2 },
    ],
    span: 8,
  },
  // {
  //   type: "date",
  //   label: "创建时间",
  //   prop: "createTime",
  //   dateType: "daterange",
  //   span: 8,
  // },
  // 更多配置项...
];

表格组件

<!-- SmartTable.vue -->
<template>
  <div class="smart-table-container">
    <!-- 顶部工具栏插槽 -->
    <div v-if="$slots.toolbar" class="table-toolbar">
      <slot name="toolbar"></slot>
    </div>

    <el-table
      ref="elTable"
      v-loading="loading"
      :data="tableData"
      :border="border"
      :stripe="stripe"
      :row-key="rowKey"
      :height="height"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
    >
      <!-- 选择列 -->
      <el-table-column
        v-if="showSelection"
        type="selection"
        width="55"
        align="center"
      />

      <!-- 序号列 -->
      <el-table-column
        v-if="showIndex"
        label="序号"
        type="index"
        width="80"
        align="center"
      >
        <template slot-scope="scope">
          {{ (pagination.page - 1) * pagination.pageSize + scope.$index + 1 }}
        </template>
      </el-table-column>

      <!-- 动态列渲染 -->
      <el-table-column
        v-for="column in columns"
        :key="column.prop"
        v-bind="column"
        :align="column.align || 'center'"
        :show-overflow-tooltip="column.tooltip !== false"
      >
        <template slot-scope="scope">
          <!-- 自定义插槽 -->
          <slot v-if="column.slot" :name="column.slot" :row="scope.row" />

          <!-- 标签类型 -->
          <el-tag
            v-else-if="column.type === 'tag'"
            :type="column.tagType || getTagType(scope.row[column.prop], column)"
          >
            {{ getColumnText(scope.row, column) }}
          </el-tag>

          <!-- 按钮类型 -->
          <el-button
            v-else-if="column.type === 'button'"
            v-bind="column.buttonProps"
            @click="handleColumnButton(scope.row, column)"
          >
            {{ getColumnText(scope.row, column) }}
          </el-button>

          <!-- 图片类型 -->
          <el-image
            v-else-if="column.type === 'image'"
            :src="scope.row[column.prop]"
            :preview-src-list="[scope.row[column.prop]]"
            fit="cover"
            style="width: 60px; height: 60px"
          />

          <!-- 默认文本 -->
          <span v-else>{{ getColumnText(scope.row, column) }}</span>
        </template>
      </el-table-column>

      <!-- 操作列 -->
      <el-table-column
        v-if="actions.length"
        label="操作"
        align="center"
        :width="actionsWidth"
        fixed="right"
      >
        <template slot-scope="scope">
          <template v-for="(action, index) in actions">
            <el-button
              v-if="showAction(action, scope.row)"
              :key="index"
              v-bind="action.props"
              @click="handleAction(action, scope.row)"
            >
              <i v-if="action.icon" :class="action.icon"></i>
              {{ action.label }}
            </el-button>
          </template>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <div v-if="showPagination" class="pagination-container">
      <el-pagination
        :current-page="pagination.page"
        :page-sizes="pagination.sizes"
        :page-size="pagination.pageSize"
        :layout="pagination.layout"
        :total="pagination.total"
        @size-change="handleSizeChange"
        @current-change="handlePageChange"
      />
    </div>
  </div>
</template>

<script>
export default {
  name: "SmartTable",
  props: {
    columns: {
      type: Array,
      required: true,
    },
    data: {
      type: Array,
      default: () => [],
    },
    loading: Boolean,
    rowKey: String,
    height: [String, Number],
    border: {
      type: Boolean,
      default: true,
    },
    stripe: {
      type: Boolean,
      default: true,
    },
    showIndex: Boolean,
    showSelection: Boolean,
    actions: {
      type: Array,
      default: () => [],
    },
    actionsWidth: {
      type: [String, Number],
      default: "240",
    },
    pagination: {
      type: Object,
      default: () => ({
        page: 1,
        pageSize: 10,
        sizes: [10, 20, 50, 100],
        total: 0,
        layout: "total, sizes, prev, pager, next, jumper",
      }),
    },
  },
  data() {
    return {
      tableData: [...this.data],
      selectedRows: [],
    };
  },
  computed: {
    showPagination() {
      return this.pagination && this.pagination.total > 0;
    },
  },
  watch: {
    data: {
      handler(newVal) {
        this.tableData = [...newVal];
      },
      deep: true,
    },
  },
  methods: {
    // 获取单元格显示内容
    getColumnText(row, column) {
      if (column.formatter) {
        return column.formatter(row);
      }
      return column.prop ? row[column.prop] : "";
    },

    // 获取标签类型
    getTagType(value, column) {
      if (column.tagMap) {
        return column.tagMap[value] || "";
      }
      return "";
    },

    // 处理操作按钮
    handleAction(action, row) {
      if (action.handler) {
        action.handler(row);
      } else {
        this.$emit("action", { action: action.name, row });
      }
    },

    // 处理列按钮点击
    handleColumnButton(row, column) {
      this.$emit("column-action", { column, row });
    },

    // 显示操作按钮条件
    showAction(action, row) {
      return typeof action.show === "function"
        ? action.show(row)
        : action.show !== false;
    },

    // 分页事件
    handleSizeChange(size) {
      this.pagination.pageSize = size;
      this.$emit("pagination-change", { ...this.pagination });
    },

    handlePageChange(page) {
      this.pagination.page = page;
      this.$emit("pagination-change", { ...this.pagination });
    },

    // 选择变化
    handleSelectionChange(selection) {
      this.selectedRows = selection;
      this.$emit("selection-change", selection);
    },

    // 行点击事件
    handleRowClick(row) {
      this.$emit("row-click", row);
    },

    // 暴露Element Table方法
    clearSelection() {
      this.$refs.elTable.clearSelection();
    },
  },
};
</script>

<style scoped>
.smart-table-container {
  background: #fff;
  padding: 16px;
  border-radius: 4px;
}

.table-toolbar {
  margin-bottom: 16px;
}

.pagination-container {
  margin-top: 16px;
  text-align: right;
}

.el-button + .el-button {
  margin-left: 8px;
}
</style>

表格配置文件

export const TABLE_COLUMNS = [
  {
    prop: "name",
    label: "姓名",
    width: 120,
    searchable: true,
  },
  {
    prop: "avatar",
    label: "头像",
    type: "image",
  },
  {
    prop: "status",
    label: "状态",
    type: "tag",
    tagMap: {
      1: "success",
      0: "danger",
    },
    formatter: (row) => (row.status === 1 ? "启用" : "停用"),
  },
  {
    prop: "phone",
    label: "联系电话",
    slot: "phone", // 使用插槽
  },
];

export const TABLE_ACTIONS = [
  {
    label: "编辑",
    name: "edit",
    icon: "el-icon-edit",
    props: {
      type: "text",
    },
  },
  {
    label: "删除",
    name: "delete",
    icon: "el-icon-delete",
    props: {
      type: "text",
      style: { color: "#f56c6c" },
    },
    show: (row) => row.status === 1, // 条件显示
  },
];

// 配置说明
/* 
  // 列配置
{
  prop: '字段名',       // 必需
  label: '列名',        // 必需
  type: 'text/tag/button/image', // 类型
  width: 120,          // 列宽
  align: 'left/center/right',
  slot: 'slot-name',   // 自定义插槽名称
  tagMap: {            // 标签类型颜色映射
    value: 'el-tag-type'
  },
  formatter: row => {} // 数据格式化函数
}

// 操作按钮配置
{
  label: '按钮文字',     // 必需
  name: 'action-name', // 操作标识
  icon: 'el-icon-name',// 图标类名
  props: {             // el-button的props
    type: 'primary',
    size: 'small'
  },
  show: row => true    // 显示条件
}

*/

使用案例

<template>
  <div class="app-container">
    <search-form
      :config="formConfig"
      :value="queryParams"
      @search="handleSearch"
      @reset="handleReset"
      :collapse-threshold="3"
    >
      <!-- 自定义插槽 -->
      <template #custom-slot="{ scope }">
        <el-input v-model="scope.customField" placeholder="自定义内容" />
      </template>
    </search-form>

    <smart-table
      :columns="columns"
      :data="tableData"
      :actions="actions"
      :pagination="pagination"
      :loading="loading"
      @pagination-change="handlePagination"
      @action="handleTableAction"
    >
      <!-- 顶部工具栏 -->
      <template #toolbar>
        <el-button type="primary" size="small" @click="handleAdd"
          >新增</el-button
        >
      </template>
    </smart-table>
  </div>
</template>

<script>
import { SEARCH_CONFIG } from "./formConfig";
import { TABLE_COLUMNS, TABLE_ACTIONS } from "./tableConfig";
import SearchForm from "./components/SearchForm.vue";
import SmartTable from "./components/SmartTable.vue";
export default {
  components: {
    SearchForm,
    SmartTable,
  },
  data() {
    return {
      queryParams: {
        /* ... */
      },
      formConfig: SEARCH_CONFIG,
      columns: TABLE_COLUMNS,
      actions: TABLE_ACTIONS,
      tableData: [],
      pagination: {
        page: 1,
        pageSize: 10,
        total: 0,
      },
      loading: false,
    };
  },

  methods: {
    // 表单搜索相关
    handleSearch(v) {
      console.log(v);
    },
    handleReset() {},

    // 表格相关
    handlePagination(pagination) {
      this.fetchData(pagination);
    },
    handleTableAction({ action, row }) {
      if (action === "edit") {
        this.handleEdit(row);
      } else if (action === "delete") {
        this.handleDelete(row);
      }
    },
    async fetchData(pagination) {
      this.loading = true;
      // 调用API获取数据...
      this.loading = false;
    },


    // 表格操作
    handleAdd(){
      
    }
  },
};
</script>

如果这篇文章能帮助到你的话,希望点赞留下一个小星星吧!你的鼓励是我最大的动力!!!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐