vue2+element-ui封装

295 阅读1分钟

筛选表单

<template>
  <el-form ref="filterForm" class="filter-form" :rules="rules" :model="formModel" :size="size" :inline="isInline" :label-width="labelWidth" :label-position="labelPosition">
    <div class="data_container">
      <el-form-item v-for="item in formItemList" :key="item.prop" :label="item.label" :prop="item.prop">
        <slot :name="item.prop || item.selectProp">
          <template v-if="item.type === 'input'">
            <el-input v-model.trim="formModel[item.prop]" :placeholder="item.placeholder || '请输入内容'" clearable :style="[item.width ? { width: item.width } : '']">
              <span v-if="item.suffix" slot="suffix">{{ item.suffix }}</span>
            </el-input>
          </template>
          <template v-if="item.type === 'number-input'">
            <el-input v-model.trim="formModel[item.prop]" :placeholder="item.placeholder || '请输入内容'" :clearable="item.clearable === false ? item.clearable : true" type="number" :style="[item.width ? { width: item.width } : '']" />
          </template>
          <template v-if="item.type === 'select'">
            <el-select v-model="formModel[item.prop]" :placeholder="item.placeholder || '请选择'" :clearable="item.clearable === false ? item.clearable : true" :multiple="item.multiple || false" collapse-tags :style="[item.width ? { width: item.width } : '']">
              <el-option v-for="option in item.options || []" :key="item.prop + '_option_' + (item.valueKey ? option[item.valueKey] : option.value)" :label="item.labelKey ? option[item.labelKey] : option.name" :value="item.valueKey ? option[item.valueKey] : option.value" />
            </el-select>
          </template>
          <template v-if="item.type === 'select-input'">
            <el-select v-model="formModel[item.selectProp]" :placeholder="item.selectPlaceholder || '请选择'" :clearable="item.clearable === false ? item.clearable : true" :style="[item.selectWidth ? { width: item.selectWidth } : '']">
              <el-option v-for="option in item.options || []" :key="item.selectProp + '_option_' + option.value" :label="option.name" :value="option.value" />
            </el-select>
            <el-input v-model.trim="formModel[item.inputProp]" :placeholder="item.inputPlaceholder || '请输入内容'" clearable :style="[
                    item.inputWidth ? { width: item.inputWidth } : '',
                    { marginLeft: '10px' }
                  ]" />
          </template>
          <template v-if="item.type === 'daterange'">
            <el-date-picker v-model="formModel[item.prop]" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd" clearable :style="[item.width ? { width: item.width } : '']" />
          </template>
          <template v-if="item.type === 'yearrange'">
            <date-picker v-model="formModel[item.prop]" type="yearrange" start-placeholder="开始时间" end-placeholder="结束时间" value-format="yyyy" clearable :style="[item.width ? { width: item.width } : '']" />
          </template>
          <template v-if="item.type === 'date'">
            <el-date-picker v-model="formModel[item.prop]" type="date" placeholder="选择日期" value-format="yyyy-MM-dd" clearable :style="[item.width ? { width: item.width } : '']" />
          </template>
        </slot>
      </el-form-item>
    </div>
    <div class="handle_container">
      <el-form-item>
        <el-button type="primary" @click="submitForm">搜索</el-button>
        <el-button @click="resetForm">重置</el-button>
        <slot name="handle"></slot>
      </el-form-item>
    </div>
  </el-form>
</template>
  
  <script>

export default {
  name: 'FilterForm',
  components: {
  },
  props: {
    rules: {
      type: Object,
      default: () => ({})
    },
    labelPosition: {
      type: String,
      default: 'right'
    },
    isInline: {
      type: Boolean,
      default: true
    },
    size: {
      type: String,
      default: 'mini'
    },
    formItemList: {
      type: Array,
      default: () => []
    },
    labelWidth: {
      type: String,
    },

  },
  data() {
    return {
      formModel: {},
    };
  },
  computed: {

  },
  watch: {

  },
  created() {
  },
  methods: {
    submitForm() {
      console.log('submitForm',this.formModel);
      this.$emit('search',this.formModel)
    },
    resetForm() {
      this.$refs['filterForm'].resetFields();
      this.$emit('search',this.formModel)
    },
  }
};
  </script>
  
  <style lang="scss" scoped>
.filter-form {
  padding: 0 24px;
  display: flex;
  align-items: flex-end;
  .data_container {
    flex: 1;
  }
}
</style>

table

<template>
  <el-table ref="tableRef" @header-dragend="onHeaderDragend" :data="tableData" style="width: 100%" stripe :border="border" @selection-change="handleSelectionChange" @row-dblclick="rowDblclick" :height="tableHeight ? tableHeight : '100%'" v-bind="$attrs" v-loading="loading">
    <template v-for="(item, index) in tableColumn">
      <el-table-column v-if="['index', 'selection'].includes(item.type)" :key="index" :type="item.type || ''" :index="item.index || columnIndex" :label="item.label" :align="item.align || 'center'" v-bind="item.attrs || {}" width="100" />
      <el-table-column v-else :key="item.prop" :prop="item.prop" :label="item.label" :align="item.align || 'center'" :show-overflow-tooltip="item.showOverflowTooltip || showOverflowTooltip" v-bind="item.attrs || {}">
        <template slot-scope="{ row, $index, column }">
          <slot :name="item.prop" :row="row" :$index="$index" :column="column">
            {{ item.formatter ? item.formatter(row[column.property], row) : row[column.property] }}
          </slot>
        </template>
      </el-table-column>
    </template>
  </el-table>
</template>

<script>

export default {
  name: 'Table',
  props: {
    tableData: {
      type: Array,
      default: () => []
    },
    tableColumn: {
      type: Array,
      default: () => []
    },
    border: {
      type: Boolean,
      default: false
    },
    showOverflowTooltip: {
      type: Boolean,
      default: false
    },
    tableHeight: {
      type: [String, Number],
      default: ''
    },
    loading: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      multipleSelection: []
    };
  },
  methods: {
    onHeaderDragend() {
      this.doLayout()
    },
    async doLayout() {
      await this.$nextTick();
      this.$refs.tableRef.doLayout()
    },
    columnIndex(index) {
      return index + 1;
    },
    handleSelectionChange(val) {
      this.multipleSelection = val;
    },
    rowDblclick(row, column, event) {
      this.$emit('row-dblclick', row, column, event);
    }
  },
  watch: {
    tableData: {
      handler(nVal) {
        this.doLayout()
      },
      deep: true
    }
  }
};
</script>

<style lang="scss" scoped>
.el-table {
  margin-top: 10px;
}
</style>

pagination

<template>
  <div class="pagination_container">
    <el-pagination :current-page.sync="page" :page-size="size" :total="total" background :page-sizes="pageSizes" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
  </div>
</template>
  
  <script>
export default {
  name: 'Pagination',
  props: {
    currentPage: {
      type: Number,
      default: 1
    },
    pageSize: {
      type: Number,
      default: 10
    },
    total: {
      type: Number,
      default: 0
    },
    pageSizes: {
      type: Array,
      default: () => [10, 20, 50, 100]
    }
  },
  data() {
    return {
      page: this.currentPage,
      size: this.pageSize
    };
  },
  watch: {
    currentPage(val) {
      if (val !== this.page) {
        this.page = val;
      }
    },
    pageSize(val) {
      if (val !== this.size) {
        this.size = val;
      }
    }
  },
  methods: {
    handleSizeChange(val) {
      this.$emit('update:currentPage', 1);
      this.$emit('update:pageSize', val);
      this.$emit('change', { currentPage: this.currentPage, pageSize: val });
    },
    handleCurrentChange(val) {
      this.$emit('update:currentPage', val);
      this.$emit('change', { currentPage: val, pageSize: this.pageSize });
    }
  }
};
  </script>
  
  <style lang="scss" scoped>
.pagination_container {
  margin-top: 10px;
  display: flex;
  justify-content: flex-end;
  padding: 0 24px;
}
</style>

筛选表单 + table + 分页组合

<template>
  <div class="filter_table_pagination">
    <FilterForm ref="FilterForm" :formItemList="filterFormProp.formItemList" @search="filterFormProp.getListByForm">
      <template #FilterFormExOperation>
        <slot name="FilterFormExOperation"></slot>
      </template>
    </FilterForm>

    <div class="table_container">
      <Table ref="Table" border :tableData="tableProp.tableData" :tableColumn="tableProp.tableColumn" showOverflowTooltip>
        <template #TableOperation="{ row, column, $index }">
          <slot name="TableOperation" :row="row" :$index="$index" :column="column"></slot>
        </template>
      </Table>
    </div>
    <Pagination :total="paginationProp.total" :currentPage="paginationProp.currentPage" :pageSize="paginationProp.pageSize" @change="paginationProp.handlePageChange" />
  </div>
</template>

<script>

import Table from './Table.vue'
import FilterForm from './FilterForm.vue'
import Pagination from './Pagination.vue'
export default {
  name: 'FilterTablePagination',
  props: {
    filterFormProp: {
      type: Object,
      default: () => ({ formItemList: [], getListByForm: () => { } })
    },
    tableProp: {
      type: Object,
      default: () => ({ tableData: [], tableColumn: [], })
    },
    paginationProp: {
      type: Object,
      default: () => ({ total: 0, currentPage: 1, pageSize: 10, handlePageChange: () => { } })
    },
  },
  components: {
    FilterForm,
    Table,
    Pagination,
  },
  data() {
    return {

    };
  },
  created() {

  },
  watch: {

  },
  computed: {

  },
  mounted() {

  },
  methods: {

  },
};
</script>

<style lang="scss" scoped>
.filter_table_pagination {
  height: 100vh;
  display: flex;
  flex-direction: column;
  .table_container {
    flex: 0 1 auto;
    overflow: auto;
  }
}
</style>

  • 筛选表单 + table + 分页组合使用示例
<template>
  <div class="filter_table_pagination_page">
    <FilterTablePagination :filterFormProp="filterFormProp" :tableProp="tableProp" :paginationProp="paginationProp" ref="FilterTablePaginationRef">
      <template #FilterFormExOperation>
        <el-button type="primary" @click="smartSearch">高级搜索</el-button>
      </template>
      <template #TableOperation="{ row, $index, column }">
        <i class="el-icon-edit" @click="handleEdit(row, $index, column)"></i>
      </template>
    </FilterTablePagination>
  </div>
</template>

<script>

import FilterTablePagination from '../components/FilterTablePagination.vue'

export default {
  name: 'FilterTablePaginationPage',
  components: {
    FilterTablePagination,
  },
  props: {

  },
  data() {
    return {
      filterFormProp: {
        formItemList: [
          {
            prop: "articleTitle",
            type: "input",
            label: "文章标题:",
            width: "150px",
          },
          {
            prop: "accountType",
            type: "select",
            label: "文章分类:",
            width: "100px",
            options: [{ name: '有趣', value: 'youqu' }, { name: "经典", value: 'jingdian' }, { name: "科学", value: 'kexue' }],
          },
          {
            prop: "date",
            type: "daterange",
            label: "评论时间:",
            width: "220px",
          },
          {
            prop: "inp1",
            type: "input",
            label: "inp1:",
            width: "220px",
          },
          {
            prop: "inp2",
            type: "input",
            label: "inp2:",
            width: "220px",
          },
          {
            prop: "inp3",
            type: "input",
            label: "inp3:",
            width: "220px",
          },
        ],
        getListByForm: this.getListByForm,
        smartSearch: this.smartSearch,
      },

      tableProp: {
        tableData: [
          { id: "111", articleTitle: "标题1", commentStatus: 2, },
          { id: "222", articleTitle: "标题1", commentStatus: 1, },
          { id: "333", articleTitle: "标标题1标题标题1题1", commentStatus: 2, },
          { id: "444", articleTitle: "标题1", commentStatus: 1, },
        ],
        tableColumn: [
          { type: 'index', label: "序号", },
          { prop: "TableOperation", label: "操作", width: 150, attrs: { "class-name": "operation", fixed: 'right', }, },
          { prop: "articleTitle", label: "文章标题", attrs: { 'min-width': 800 } },
          { prop: "author", label: "作者", attrs: { 'min-width': 200 } },
          { prop: "author2", label: "作者2", },
          { prop: "author3", label: "作者2", },
          { prop: "author4", label: "作者2", },
          {
            prop: "commentStatus", label: "评论状态",
            formatter: (val) => {
              if (val === 1) {
                return '已评论'
              } else if (val === 2) {
                return '未评论'
              }
            },
          },
        ],
      },

      paginationProp: {
        total: 0,
        currentPage: 1,
        pageSize: 10,
        handlePageChange: this.handlePageChange
      },
    }
  },
  computed: {

  },
  watch: {

  },
  created() {

  },
  mounted() {

  },
  methods: {
    handlePageChange({ currentPage, pageSize } = {}) {
      console.log('handlePageChange', currentPage, pageSize);
      this.pageSize = pageSize;
      this.currentPage = currentPage;
    },
    handleEdit(row, $index, column) {
      console.log('row', row, $index, column);
    },
    getListByForm(formData = {}) {
      console.log('getListByForm---formData', formData);
    },
    smartSearch() {
      console.log('smartSearch---formData', this.$refs.FilterTablePaginationRef.$refs.FilterForm.formModel);
    },
  },
};
</script>

<style lang="scss">
</style>

编辑性表单

<template>
  <el-form ref="addEditFormRef" :model="form" :rules="rules" class="addEdit_Form" size="small" :label-width="labelWidth"
    :disabled="disabled">
    <el-row type="flex">
      <template v-for="item in formItemList">
        <el-col v-if="isShowItem(item.prop)" :key="item.prop" :span="item.span || colSpan">
          <el-form-item :prop="item.prop" :label="item.label" :label-width="item.labelWidth"
            :class="{ 'required': item.isRequired }">
            <slot :name="item.prop" :value="form[item.prop]" :form="form" :change="handleChange">
              <template v-if="item.type === 'input'">
                <el-input v-model.trim="form[item.prop]" :placeholder="disabled ? '' : '请输入内容'" clearable
                  :maxlength="item.maxlength || null" :disabled="item.disabled || false" class="width100"
                  @clear="inputClear(item.prop)">
                  <span v-if="item.suffix" slot="suffix">{{ item.suffix }}</span>
                </el-input>
              </template>
              <template v-else-if="item.type === 'password'">
                <el-input v-model="form[item.prop]" placeholder="请输入" clearable class="width100" type="password"
                  :disabled="item.disabled || false" autocomplete="new-password"
                  @paste.native.capture.prevent="inputPress" />
              </template>
              <template v-else-if="item.type === 'textarea'">
                <el-input v-model="form[item.prop]" :placeholder="disabled ? '' : '请输入内容'" clearable
                  :autosize="{ minRows: 3 }" type="textarea" class="width100" :disabled="item.disabled || false"
                  :maxlength="item.maxlength || null" show-word-limit />
              </template>
              <template v-else-if="item.type === 'text'">
                <span class="form-text">{{ item.formatter ? item.formatter(form[item.prop]) : form[item.prop] }}</span>
              </template>
              <template v-else-if="item.type === 'select'">
                <el-select v-model="form[item.prop]" :placeholder="disabled ? '' : '请选择'" clearable
                  :filterable="item.filterable" class="width100" :disabled="item.disabled || false"
                  @change="val => formChange(item.prop, val)">
                  <el-option v-for="option in item.options || []" :key="item.prop + '_option_' + option.value"
                    :label="option.name" :value="option.value" />
                </el-select>
              </template>
              <template v-else-if="item.type === 'radio'">
                <el-radio-group v-model="form[item.prop]" :disabled="item.disabled || false"
                  @change="val => formChange(item.prop, val)">
                  <el-radio v-for="option in item.options || []" :key="item.prop + '_option_' + option.value"
                    :label="option.value">
                    {{ option.name }}
                  </el-radio>
                </el-radio-group>
              </template>
              <template v-else-if="item.type === 'checkbox'">
                <el-checkbox-group v-model="form[item.prop]" :disabled="item.disabled || false"
                  @change="val => formChange(item.prop, val)">
                  <el-checkbox v-for="option in item.options || []" :key="item.prop + '_option_' + option.value"
                    :label="option.value">
                    {{ option.name }}
                  </el-checkbox>
                </el-checkbox-group>
              </template>
              <template v-else-if="item.type === 'daterange'">
                <el-date-picker v-model="form[item.prop]" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期"
                  value-format="yyyy-MM-dd" clearable :disabled="item.disabled || false" class="width100" />
              </template>
              <template v-else-if="item.type === 'date'">
                <el-date-picker v-model="form[item.prop]" type="date" placeholder="选择日期" value-format="yyyy-MM-dd"
                  clearable :disabled="item.disabled || false" class="width100" />
              </template>
              <template v-else-if="item.type === 'yearrange'">
                <date-picker v-model="form[item.prop]" type="yearrange" start-placeholder="开始时间" end-placeholder="结束时间"
                  value-format="yyyy" clearable :disabled="item.disabled || false" class="width100" />
              </template>
              <template v-else-if="item.type === 'year'"><el-date-picker v-model="form[item.prop]" type="year"
                  value-format="yyyy" placeholder="请选择" class="width100"></el-date-picker></template>
            </slot>
          </el-form-item>
        </el-col>
      </template>
    </el-row>
  </el-form>
</template>
  
<script>
//   import reg from '@/coreCommon/utils/reg.js';

export default {
  name: 'AddEditForm',
  props: {
    rules: {
      type: Object,
      default: () => { }
    },
    col: {
      type: Number,
      default: 1
    },
    labelWidth: {
      type: String,
      default: '80px'
    },
    formItemList: {
      type: Array,
      default: () => []
    },
    formData: {
      type: Object,
      default: () => { }
    },
    hiddenFormItems: {
      type: Array,
      default: () => []
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      form: {}
    };
  },
  computed: {
    colSpan() {
      return Math.floor(24 / this.col);
    }
  },
  watch: {
    formData: {
      deep: true,
      immediate: true,
      handler(val) {
        this.form = JSON.parse(JSON.stringify(val));
      }
    }
  },
  created() {
    // this.init();
  },
  methods: {
    inputPress() {
      return false
    },
    init() {
      this.formItemList.forEach((item) => {
        const oldVal = this.form[item.prop];
        //   this.$set(this.form, item.prop, this.$utils.isEmpty(oldVal) ? (item.defaultValue || '') : oldVal);
        this.$set(this.form, item.prop, oldVal);
      });
    },
    handleChange(key, val = '') {
      // if (key.indexOf('.') > -1) {
      //   const arr = key.split('.');
      //   arr.reduce((total, item, index) => {
      //     if (index === arr.length - 1) {
      //       this.$set(total, item, val);
      //     } else {
      //       if (this.$utils.isEmpty(total[item])) {
      //         const isNumber = reg.zeroPositiveInteger.test(arr[index + 1]);
      //         this.$set(total, item, isNumber ? [] : {});
      //       }
      //     }
      //     return total[item];
      //   }, this.form);
      // } else {
      //   this.$set(this.form, key, val);
      // }
    },
    formChange(key, val) {
      this.$emit('formChange', key, val, this.handleChange);
    },
    submitForm() {
      this.$refs.addEditFormRef.validate((valid) => {
        if (valid) {
          const form = JSON.parse(JSON.stringify(this.form));
          this.$emit('submitForm', form);
        } else {
          return false;
        }
      });
    },
    getFormData() {
      return JSON.parse(JSON.stringify(this.form));;
    },
    isShowItem(prop) {
      return this.hiddenFormItems.indexOf(prop) === -1;
    },
    clearValidate() {
      const addEditFormRef = this.$refs.addEditFormRef;
      addEditFormRef && addEditFormRef.clearValidate();
    },
    resetFields() {
      const addEditFormRef = this.$refs.addEditFormRef;
      addEditFormRef && addEditFormRef.resetFields();
    },
    inputClear(prop) {
      if (this.rules[prop]) {
        this.$refs.addEditFormRef.validateField(prop)
      }
    },
    validateField(val) {
      this.$refs.addEditFormRef.validateField(val);
    }
  }
}
</script>
  
<style lang="scss" scoped>
.addEdit_Form {
  ::v-deep .el-row {
    flex-wrap: wrap;
  }

  ::v-deep .required {
    .el-form-item__label {
      &::before {
        content: "*";
        color: var(--global_dan_color, #ff4d4f);
        margin-right: 4px;
      }
    }
  }

  .width100 {
    width: 100%;
  }

  ::v-deep .form-text {
    padding-left: 10px;
    //   color: $global_font_color;
  }
}
</style>
  • 使用示例
<template>
    <div class=''>
        <AddEditForm ref="AddEditFormRef" :formData="formData" :col="1" :formItemList="formItemList" :rules="rules"
            :disabled="isFormDisabled" labelWidth="180px">
            <template #imgs="{ form ,}">
                <Upload v-model="form.imgs"></Upload>
            </template>
        </AddEditForm>
        <el-button type="primary" @click="onSave">保存</el-button>
    </div>
</template>

<script>
import AddEditForm from '../components/AddEditForm.vue'
import Upload from '../components/Upload.vue'

export default {
    name: '',
    components: {
        AddEditForm,
        Upload,
    },
    props: {

    },
    data() {
        return {
            isFormDisabled: false,
            formData: {
                artworkName: '', //名称
                type: '', //类型		
                creationYear: '', //创作年代		
                price: '', //价格(元)		
                imgs: [], //图片
                id: '', //主键			
                ifLatest: 0, //是否最新(0:否;1:是;)
                ifSelected: 0, //是否精选(0:否;1:是;)
                showAreaList: [], //最新  精选 
            },
            formItemList: [
                { prop: 'artworkName', label: '艺术品名称:', type: 'input', },
                { prop: 'type', type: 'select', label: '类型:', options: [{ name: "类型1", value: '1' }, { name: "类型2", value: '2' }, { name: "类型3", value: '3' }] },
                { prop: 'creationYear', type: 'year', label: '创作年代:' },
                { prop: 'price', label: '价格:', type: 'input', suffix: "元" },
                { prop: 'imgs', label: '艺术品图片:', isRequired: true },
                { prop: 'showAreaList', label: '是否添加到首页板块:', type: 'checkbox', options: [{ name: "最新上线", value: "ifLatest" }, { name: "作品精选", value: "ifSelected" }] },
            ],
            rules: {
                artworkName: [
                    { required: true, message: '请输入艺术品名称' },
                ],
                type: [
                    { required: true, message: '请选择类型' },
                ],
                creationYear: [
                    { required: true, message: '请选择创作年代' },
                ],
                price: [
                    { required: true, message: '请输入价格' },
                ],
                imgs: [
                    { required: true, message: '请上传艺术品图片' },
                ],
                showAreaList: [
                    { required: true, message: '请选择是否添加首页板块' },
                ],
               
            },
        }
    },
    computed: {

    },
    watch: {

    },
    created() {

    },
    mounted() {

    },
    methods: {
        validateFormsByFormRefs(formRefs = []) {
            return formRefs.map(ref => {
                return new Promise((resolve, reject) => {
                    ref.validate((valid) => {
                        if (valid) resolve();
                    }
                    );
                });
            })
        },
        async onSave() {
            const AddEditFormRef = this.$refs.AddEditFormRef
            await Promise.all(this.validateFormsByFormRefs([AddEditFormRef.$refs.addEditFormRef]))
            const formData = AddEditFormRef.getFormData()
            console.log('AddEditFormRef.getFormData', formData);

        },
    },
};
</script>

<style scoped>
</style>

上传组件

<template>
    <el-upload action="" 
      :on-remove="onRemove" 
      :class="{ 'hidden': isDisabled || value.length >= maxLength }"
      :on-success="onSuccess" 
      multiple 
      :file-list="value" 
      class="uploader" 
      :style="{ width: uploadBoxWidth }"
      :limit="maxLength" 
      :on-exceed="onExceed" 
      :before-upload="beforeUpload" 
      list-type="picture-card"
      :http-request="handleUpload">
        <i class="el-icon-plus"></i>
    </el-upload>
  </template>
  
  <script>
  
  export default {
    name: 'UploadComponent',
    components: {
  
    },
    model: {
      prop: 'value',
      event: 'change'
    },
    props: {
      //  图片列表 url:渲染图片的key  uid:upload组件本身需要的key(必须唯一,用于内部v-for)
      value: {
        type: Array,
      },
      // 上传的文件最大限制 :maxLength="1" 为单张上传
      maxLength: {
        type: Number,
        default: 10
      },
      uploadBoxWidth: {
        type: String,
        default: '900px'
      },
      // 是否隐藏上传入口
      isDisabled: {
        type: Boolean,
        default: false
      },
    },
    data() {
      return {
      };
    },
    created() {
  
    },
    watch: {
  
    },
    computed: {
  
    },
    mounted() {
  
    },
    methods: {
      onRemove(file, fileList) {
        this.$emit('change', fileList)
      },
      onExceed(files, fileList) {
        this.$message.warning(`最大允许上传${this.maxLength}张图片`)
      },
      /**
       * @desc 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。
       * @param {file}  文件对象
       * @return false 或者返回 Promise 且被 reject,则停止上传。
       */
      beforeUpload(file) {
        const isJPG = file.type === 'image/jpeg';
        const isLt2M = file.size / 1024 / 1024 < 2;
        if (!isJPG) {
          this.$message.error('上传头像图片只能是 JPG 格式!');
        }
        if (!isLt2M) {
          this.$message.error('上传头像图片大小不能超过 2MB!');
        }
        return isJPG && isLt2M;
      },
      /**
       * @desc 调用后端服务实现手动上传
       * @param {e}  
       */
      async handleUpload(e) {
        // const res = await 
        // return res
      },
       /**
       * @desc 上传成功的回调 
       * @param {res}  handleUpload 返回的结果
       * @param {fileList}  存量 + 增量 图片列表
       */
       onSuccess(res, file, fileList) {
        const files = fileList.map(item => {
          const file = { ...(item.response ?? item), uid: item.uid }
          file.imgUrl = file.url
          return file
        })
        this.$emit('change', files)
      },
    },
  };
  </script>
  
  <style lang="scss">
  .uploader {
    &.hidden {
      .el-upload {
        display: none;
      }
    }
  }
  </style>
  

带有form的table

<template>
  <!-- 带有form表单验证的table -->
  <div class="table_with_form">
    <slot name="addRow">
      <div style="text-align:right">
        <el-button type="primary" style="text-align:right" @click="handleAddRow(tableName)">添加</el-button>
      </div>
    </slot>

    <el-form :model="form" ref="tableWithForm">
      <el-table :data="form[tableName]" border style="width: 100%; margin: 10px 0 20px;">
        <el-table-column v-for="(item,index) in columns" :key="index" :label="item.label" :width="item.width">
          <template slot-scope="scope">
            <el-form-item :prop="tableName +'.' + scope.$index +'.'+item.prop" :rules="item.rules">
              <!-- 暂时只封装三种类型 input select date-picker -->
              <el-input v-model="scope.row[item.prop]" v-if="item.type=='input'" :placeholder="item.label"></el-input>
              <el-select v-model="scope.row[item.prop]" v-if="item.type=='select'">
                <el-option :label="option.label" :value="option.value" v-for="(option,i) in item.options" :key="i"></el-option>
              </el-select>
              <el-date-picker type="date" value-format="yyyy-MM-dd" v-if="item.type=='date'" v-model="scope.row[item.prop]" style="width: 100%;"></el-date-picker>
            </el-form-item>
          </template>
        </el-table-column>

        <el-table-column fixed="right" label="操作" width="100">
          <template slot-scope="scope">
            <!-- 子向父组件传参 scope tableName -->
            <slot name="handlerColumn" :scope="scope" :tableName="tableName">
              <el-button @click="handleDelRow(scope.$index)" type="text" size="small">删除</el-button>
            </slot>
          </template>
        </el-table-column>

      </el-table>
    </el-form>
  </div>
</template>
<script>
export default {
  name: "TableWithForm",
  // props: ["form", "tableName", "columns"],
  props: {
    form: {
      type: Object
    },
    tableName: {
      type: String
    },
    columns: {
      type: Array
    }
  },
  components: {},
  created () { },
  mounted () { },
  computed: {},
  watch: {},
  data: function () {
    return {};
  },
  methods: {
    handleAddRow (tableName) {
      this.form[tableName].push({});
    },

    handleDelRow (index) {
      this.form[this.tableName].splice(index, 1);
    }
  }
};
</script>
<style scoped>
</style>
  • 使用示例(而且演示了同时校验多表单 + slot传参)
<template>
 <div id="app">
   <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
     <el-form-item label="活动名称" prop="name">
       <el-input v-model="ruleForm.name"></el-input>
     </el-form-item>
     <el-form-item label="活动区域" prop="region">
       <el-select v-model="ruleForm.region" placeholder="请选择活动区域">
         <el-option label="区域一" value="shanghai"></el-option>
         <el-option label="区域二" value="beijing"></el-option>
       </el-select>
     </el-form-item>
   </el-form>

   <TableWithForm ref="tableForm" :form="form" tableName="tableData" :columns='columns' />

   <TableWithForm ref="otherTableForm" :form="form" tableName="otherTableData" :columns='otherColumns'>
     <!-- 子向父组件传参 scope tableName -->
     <template v-slot:handlerColumn="{scope, tableName}">
       <el-button @click="handleDelRow(scope.$index, tableName)" type="text" size="small">来自父组件 删除</el-button>
     </template>
   </TableWithForm>

   <div style="text-align:center">
     <el-button type="primary" @click="submitForm">提交</el-button>
     <el-button @click="resetForm">重置</el-button>
   </div>
 </div>
</template>

<script>
import TableWithForm from '@/components/TableWithForm'
export default {
 components: {
   TableWithForm,
 },
 data () {
   return {
     ruleForm: {
       name: '',
       region: '',
     },
     rules: {
       name: [
         { required: true, message: '请输入活动名称', trigger: 'blur' },
         { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
       ],
       region: [
         { required: true, message: '请选择活动区域', trigger: 'change' }
       ],
     },
     form: {
       tableData: [{ name: "bwf", province: "北京", date: "2022-09-09", city: "上海" }],
       otherTableData: [],
     },
     columns: [
       {
         prop: "name",
         label: "姓名",
         width: "",
         type: "input",
         rules: [{
           required: true,
           message: '请输入'
         },
         {
           min: 3,
           max: 5,
           message: '长度在 3 到 5 个字符'
         }
         ],
       },
       {
         prop: "province",
         label: "省份",
         width: "",
         type: "input"
       },
       {
         prop: "date",
         label: "日期",
         width: "",
         type: "date",
         rules: [
           {
             required: true,
             message: '请选择日期',
             trigger: 'change'
           }
         ]
       },
       {
         prop: "city",
         label: "市区",
         width: "",
         type: "select",
         options: [{
           label: "北京",
           value: "bj"
         }, {
           label: "上海",
           value: "sh"
         }],
         rules: [{
           required: true,
           message: '请选择活动区域',
           trigger: 'change'
         }],
       }
     ],
     otherColumns: [
       {
         prop: "activeName",
         label: "活动名称",
         width: "",
         type: "input",
         rules: [
           {
             required: true,
             message: '请输入'
           }
         ],
       }
     ]
   };
 },
 created () {
   console.log('App---created---start', process.env, process.env.VUE_APP_BASE_API);
 },
 mounted () {
 },
 computed: {

 },
 methods: {
   handleDelRow (index, tableName) {
     this.form[tableName].splice(index, 1);
   },

   /**
    * @desc 批量校验
    * @param {formRefs}  ref
    */
   validateFormsByFormRefs (formRefs) {
     return formRefs.map(ref => {
       return new Promise((resolve, reject) => {
         ref.validate((valid) => {
           if (valid) resolve();
         }
         );
       });
     })
   },
   submitForm () {
     const formRef1 = this.$refs.tableForm.$refs.tableWithForm
     const formRef2 = this.$refs.otherTableForm.$refs.tableWithForm
     const formRef3 = this.$refs.ruleForm
     Promise.all(this.validateFormsByFormRefs([formRef1, formRef2, formRef3])).then(() => {
       console.log('all formRules passed', this.ruleForm, this.form);
     })

   },
   resetForm () {
     const formRef1 = this.$refs.tableForm.$refs.tableWithForm
     const formRef2 = this.$refs.otherTableForm.$refs.tableWithForm
     const formRef3 = this.$refs.ruleForm
     formRef1.resetFields();
     formRef2.resetFields();
     formRef3.resetFields();
   }
 },
}
</script>
<style lang="scss">
</style>

动态el-menu

  • Menu
<template>
 <el-menu :default-active="activeIndex" style="width:300px" class="el_menu" background-color="#409eff" text-color="white" active-text-color="yellow" :unique-opened="true" router ref="elMenu">
   <MenuItem :data="menuArr">
   </MenuItem>
 </el-menu>
</template>

<script>
import MenuItem from "./MenuItem.vue";
export default {
 name: "Menu",
 components: {
   MenuItem,
 },
 data () {
   return {
     activeIndex: this.$route.path,
     menuArr: [
       {
         path: "/home",
         name: "首页",
         icon: "el-icon-location",
         children: [],
       },
       {
         path: "/work",
         name: "我的工作台",
         icon: "el-icon-s-tools",
         children: [
           {
             path: "/work1",
             name: "工作台1",
             children: [
               {
                 path: "/work1-1",
                 name: "我的工作台1-1",
                 children: [],
               },
               {
                 path: "/work1-2",
                 name: "我的工作台1-2",
                 children: [],
               }
             ],
           },
           {
             path: "/work2",
             name: "工作台2",
             children: [],
           },
         ],
       },
       {

         path: "/user",
         name: "用户",
         icon: "el-icon-user",
         children: [],
       },
     ]
   };
 }
};
</script>

<style scoped>
</style>

  • MenuItem
<template>
 <div class="menu_item">
   <!-- 注意: 在template标签上使用v-for,:key="index"不能写在template标签上,因为其标签不会被渲染,会引起循环错误 -->
   <template v-for="(item, index) in data">

     <el-submenu :key="index" :index="item.path" v-if="item.children.length > 0">
       <template slot="title">
         <i :class="item.icon"></i>
         <span>{{ item.name }}</span>
       </template>

       <!-- 递归组件自己 -->
       <MenuItem :data="item.children" />
     </el-submenu>

     <el-menu-item :key="index" v-else :index="item.path">
       <i :class="item.icon"></i>
       <span slot="title">{{ item.name }}</span>
     </el-menu-item>

   </template>
 </div>
</template>

<script>
export default {
 name: "MenuItem",
 props: {
   data: {
     type: Array,
     default: [],
   },
 },
};
</script>
<style lang="scss">
.menu_item {
 [class*='el-icon-'] {
   color: yellow;
 }
}
</style>

selectTree

<template>
 <el-select v-model="selectValue" multiple placeholder="请选择" :popper-append-to-body="false" size="small" @remove-tag="removetag" collapse-tags @clear="clearall" clearable popper-class="select_tree">
   <el-option :value="selectedIds" class="select_option" disabled>
     <!-- 单选 check-strictly = true -->
     <el-tree :data="treeList" :props="defaultProps" ref="tree" :check-strictly="isSingleChoice" show-checkbox :expand-on-click-node="false" @check="treeCurrentChange" node-key="id" check-on-click-node></el-tree>
   </el-option>
 </el-select>
</template>

<script>
import { areaListRequest, orgListRequest } from "@/coreCommon/request/common.js"
export default {
 name: "SelectTree",
 props: {
   // 业务类型: 省市区 area | 机构部门 org 
   businessType: {
     type: String,
     default: "area",
     validator(value) {
       // 这个值必须匹配下列字符串中的一个
       const islegal = ['area', 'org'].includes(value)
       if (!islegal) {
         console.error(`The businessType must match one of the following strings:${['area', 'org']}`)
       }
       return islegal;
     }
   },
   // 是否是单选: true:单选 false:多选
   isSingleChoice: {
     type: Boolean,
     default: false
   }
 },
 computed: {

 },
 data() {
   return {
     // [businessType, {url,method}]
     businessMap: new Map([
       ["area", areaListRequest],
       ["org", orgListRequest]]),
     // select值为当前被选中的 checkedNodes 的 name 
     selectValue: [],
     // 选中的项的id组成的列表
     selectedIds: [],
     selectedItems:[],
     // 取别名 treeKey : dataKey
     defaultProps: { children: "children", label: "name", id: "id" },
     // 树形数据
     treeList: [],
     // mockTreeList: [{
     //   name: '发行统计',
     //   id: "1",
     //   parentId: "0",
     //   children: [{
     //     name: '汇票统计',
     //     id: "1-1",
     //     parentId: "1",
     //     children: [{
     //       name: '日度统计',
     //       id: "1-1-1",
     //       parentId: "1-1",
     //       children: [
     //         { name: "承兑金额(当日)", parentId: "1-1-1", id: "1-1-1-1", frequency: "20", unit: "年", source: "来源", country: "中国", updateTime: "2020-02-02", },
     //         { name: "承兑张数(当日)", parentId: "1-1-1", id: "1-1-1-2", frequency: "90", unit: "年", source: "来源", country: "中国", updateTime: "2020-02-02", },
     //       ]
     //     },
     //     {
     //       name: '月度统计',
     //       id: "1-1-2",
     //       parentId: "1-1",
     //       children: [
     //         { name: "承兑发生笔数", parentId: "1-1-2", id: "1-1-2-1", },
     //         { name: "承兑发生额", parentId: "1-1-2", id: "1-1-2-2", },
     //       ]
     //     },]
     //   }
     //   ],
     // }],


   }
 },
 watch: {
   /**
    * @desc 选中的节点的文字描述组成的列表 (select里的value发生变化时触发)
    * @param {newValue}  选中的节点的文字描述组成的列表 
    */
   selectValue(newValue) {
   },

   /**
    * @desc 选中的节点组成的列表(选中时触发)
    * @param {newValue}  选中的节点组成的列表
    */
   selectedIds(newValue) {
   },
 },
 created() {
   console.log('SelectTree---created---start---businessType', this.businessType);
   this.getTreeList(this.businessType)
 },
 methods: {
   async getTreeList(businessType) {
     const treeRes = await this.businessMap.get(businessType)();
     if (businessType === 'area') {
       treeRes.forEach(item => { item.name = item.areaSimpleName })
     }
     this.treeList = this.$utils.listToTree(treeRes)
     console.log('this.treeList', this.treeList);
   },

   /**
    * @desc 当复选框被点击的时候触发(复选框取消或选中时都会触发)	
    * @param {currentNode}  点击的节点
    */
   treeCurrentChange(currentNode, { checkedKeys, checkedNodes }) {
     console.log('treeCurrentChange---start---currentNode', currentNode);
     // 单选
     if (this.isSingleChoice && checkedKeys.length > 0) {
       this.$refs.tree.setCheckedKeys([currentNode.id]);
       this.$refs.tree.getCheckedNodes([currentNode]);
     }

     const checkedNodeList = this.$refs.tree.getCheckedNodes()
     this.selectedIds = []
     this.selectedItems = []
     this.selectValue = []
     checkedNodeList.forEach((item) => {
       this.selectedIds.push(item.id)
       this.selectedItems.push(item)
       this.selectValue.push(item.name)
     })

   },
   /**
    * @desc 删除select里面的内容时触发
    */
   removetag() {
     this.selectedIds.splice(0, 1)
     this.selectedItems.splice(0, 1)
     const checkedNodes = this.$refs.tree.getCheckedNodes()
     checkedNodes.splice(0, 1)
     this.$nextTick(() => {
       this.$refs.tree.setCheckedNodes(checkedNodes)
     })
   },

   /**
    * @desc 清除所有时触发
    */
   clearall() {
     this.selectedIds = []
     this.selectedItems = []
     this.$nextTick(() => {
       this.$refs.tree.setCheckedNodes([])
     })
   },
 },
}
</script>

<style lang="scss" scoped>
.select_option {
 overflow: hidden;
 min-height: 200px;
 padding: 0 !important;
 margin: 0;
 overflow: auto;
 cursor: default !important;
}
</style>

selectList

<template>
 <el-select v-loadMore="loadMore" v-model="selectedId" filterable remote reserve-keyword placeholder="请输入" :remote-method="getOptions" :loading="loading" size="small">
   <el-option v-for="item in options" :key="item.value" :label="item.name" :value="item.value">
   </el-option>
 </el-select>

</template>

<script>
import { employeePageRequest, orgInfoPageRequest, prepaidSearchRequest } from "@/coreCommon/request/common.js"

export default {
 name: "SelectList",
 directives: {
   'loadMore': {
     bind(el, binding) {
       const dropdownDom = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
       dropdownDom.addEventListener('scroll', function () {
         const condition = this.scrollHeight - this.scrollTop <= this.clientHeight
         console.log(' this.scrollHeight - this.scrollTop <= this.clientHeight', this.scrollHeight, this.scrollTop, this.clientHeight);
         if (condition) {
           console.log('directives---loadMore---start');
           binding.value()
         }
       })

     }
   }
 },
 components: {

 },
 props: {
   // 业务类型: 
   businessType: {
     type: String,
     default: "employeePage",
   },
 },
 data() {
   return {
     businessMap: new Map([
       // 员工(经纪人)
       ["employeePage", { request: employeePageRequest, data: { keyName: "name", currentName: 'current', sizeName: "size" }, resAlias: { name: "name", value: "userId" } }],
       // 超值储账户
       ["prepaid", { request: prepaidSearchRequest, data: { keyName: "key", currentName: 'pageIndex', sizeName: "pageSize" }, resAlias: { name: "customerName", value: "id" } }],
       //所有机构
       ["allOrgs", { request: orgInfoPageRequest, data: { keyName: "orgName", currentName: 'current', sizeName: "size" }, resAlias: { name: "customerName", value: "id" } }],
       // 承兑行
       ["headOrg", { request: orgInfoPageRequest, data: { keyName: "orgName", currentName: 'current', sizeName: "size", payloads: { level: "0" } }, resAlias: { name: "shortName", value: "id" } }]]),
     // 输入框的关键字
     keyWord: "",
     // 选中后的value
     selectedId: "",
     selectedItem: {},
     // {name, value}
     options: [],
     loading: false,
     current: 1,
     size: 10,
   }
 },
 async created() {
   this.getOptions("")
 },
 watch: {
   selectedId(newValue) {
     console.log('watch---selectedId---start', newValue);
     this.selectedItem = this.options.find(item => item.value === newValue)
   }
 },
 computed: {

 },
 mounted() {

 },
 methods: {

   /**
    * @desc 下拉加载更多
    */
   loadMore() {
     console.log('methods---loadMore---start');
     this.fetchOptions(this.keyWord)
   },

   /**
    * @desc 重置分页信息
    * @param {current}  当前页
    * @param {size}  每页条数
    */
   resetPageInfo(current = 1, size = 10) {
     this.current = current
     this.size = size
     this.options = []
   },

   /**
    * @desc 根据不同的业务去请求接口拉取数据
    * @param {}  
    */
   async fetchOptions(keyWord) {

     const handler = this.businessMap.get(this.businessType);
     if (!handler) {
       throw new Error(`this.businessType:${this.businessType}尚未配置`)
     }
     const data = handler['data']
     const keyName = data['keyName']
     const currentName = data['currentName']
     const sizeName = data['sizeName']
     const { records, total } = await handler['request']({ [keyName]: keyWord, [currentName]: this.current, [sizeName]: this.size, ...data.payloads })

     if (Array.isArray(records) && records.length > 0) {
       const resAlias = handler['resAlias']
       const resName = resAlias['name']
       const valueName = resAlias['value']
       this.current++
       this.options = this.options.concat(records.map(item => ({ name: item[resName], value: item[valueName], ...item })))
     }
   },

   /**
    * @desc 获取下拉选项列表 (执行时机: 组件创建 || 输入框输入时)
    * @param {keyWord}  输入框的关键字
    */
   getOptions(keyWord) {
     console.log('getOptions---start---keyWord', keyWord);
     this.keyWord = keyWord
     this.resetPageInfo();
     this.fetchOptions(keyWord);
   }
 },
};
</script>

<style lang="scss" >
// .el-select-dropdown__wrap {
//   max-height: 80px;
// }
</style>

表单批量校验

/**
* @desc 批量校验
* @param {formRefs}  ref
*/
validateFormsByFormRefsMixin(formRefs = []) {
   return formRefs.map(ref => {
       return new Promise((resolve, reject) => {
           ref.validate((valid) => {
               if (valid) resolve();
           }
           );
       });
   })
},

//使用示例
const formRef1 = this.$refs.businessLicenseFormRef
const formRef2 = this.$refs.legalFormRef
const formRef3 = this.$refs.basicInfoRef
const formRef4 = this.$refs.beneficiaryTableFormRef
const formRef5 = this.$refs.stockHolderFormRef
await Promise.all(this.validateFormsByFormRefsMixin([formRef1, formRef2, formRef3, formRef4, formRef5]))
console.log('all rules passed')