基于Vue2+AntD的「表单/表格」的解决方案

1,572 阅读6分钟

背景

项目遇到大量的各类表单,以及搜索表格的页面。设计师出的页面以及交互几乎都差不多,也算是遵循设计的统一性吧。当我们开发的时候,总感觉每天在重复手动搬砖,那我们是不是可以推个小车来搬砖呢?本着想偷偷懒的想法,就有了以下发生的事情...

you-want1.png

you-want

至于为啥叫you-want,其灵感来源于《中国好声音》的 “I Want You”。另外的意思就是想表明:你想要啥,都可以来告诉我,我尽量满足你!(有点夸张,不过也算是一个努力💪的方向...)

「表单/表格」开箱即用的解决方案。该组件库是在 Vue2 + Ant Design Vue 的基础进行二次开发的,所以项目必须是vue2+ antd架构(其他版本敬请期待...)。至于为什么非要造这个轮子?我也看了大量市场上提供的相似解决方案,感觉场景以及使用都比较复杂,有一定的学习成本,我只想让事情简单化,不想让使用者有学习负担,怎么简单怎么来,也就是说只要大家会Vue2 + Ant Design Vue就可以了。 主要收录了常用的几个组件。如果大家有其它组件需求,都可以给我提,我努力完成...

快速上手

首先,安装组件库

npm install you-want
#或
yarn add you-want
#或
pnpm install you-want

接着,在项目入口文件 main.js 注册组件

import YouWant from 'you-want'

Vue.use(YouWant.YFormList)
Vue.use(YouWant.YSearchTable)
Vue.use(YouWant.YSearchForm)

最后,就可以在页面或组件里边直接使用

<template>
  <div class="">
    <!-- 带检索功能的表格 -->
    <y-search-table
      :FormProps="FormProps"
      :FormListColumns="FormListColumns"
      :TableColumns="columns"
      :TableData="data"
      :TableProps="TableProps"
    ></y-search-table>
    <!-- 表单列表组件 -->
    <y-form-list 
      ref="formListRef" 
      :FormListColumns="FormListColumns"
    >
    </y-form-list>
  </div>
</template>

使用说明

  • YFormList --- 让表单简单化,配置一下就可以

  • YSearchTable --- 让表格固定化,做自己业务就好

  • YSearchForm --- 让检索适配化,大小屏通吃

下边可看到具体文档:

FormList 组件使用文档说明

让表单简易化,配置一下就可以!不用再堆积大量的HTML标签了,代码层次逻辑清晰明了!JSON配置简单易上手!

Attributes

FormListColumns

FormListColumns 参数为对象object,必传。

参数说明类型可选值默认值
keykey值,对应接口字段string
tag标签,首字母大写stringtag
optionsform组件配置object{}
formItemPropsformItem 配置项object{label: "名称"}{}
nodeProps内置标签节点对应的属性object{}
hidden配置节点的显示/隐藏(可通过配置表达式智能联动)boolean/表达式true/false/"{{formData.key === value}}"false
afterProps节点后边可能出现单位、按钮、或说明...object{}
colSpan栅格布局占位number1~2424
otherNode其他特殊节点object[array]
tag

tag 参数为字符串string,标签介绍,首字母大写。现在支持一下标签:

tag说明配置文档
InputInput输入框文档
BadgeBadge徽标数文档
RadioGroupRadioGroup单选框文档
DatePickerDatePicker日期选择框文档
TextareaTextarea输入框文档
SelectSelect选择器文档
InputNumberInputNumber数字输入框文档
SwitchSwitch开关文档
SliderSlider滑动输入条文档
RateRate评分组件文档
UploadUpload上传文档
RangePickerRangePicker日期区间文档
TimePickerTimePicker时间选择框文档
AutoCompleteAutoComplete自动完成文档
CascaderCascader级联选择文档
SpanSpan展示值为:options.initialValue 或 DetailData[key]
InputNumberSelectInputNumberSelect数字带单位参考InputNumberSelect配置
RangeTimePickerRangeTimePicker时间区间RangeTimePicker
InputSelectInputSelect文本带选择框参考InputSelect配置
Slot插槽渲染,可自定义复杂的表单额外单独配置

RangeTimePicker

其它配置不变,和其它节点一样,仅 nodeProps 的配置如下:

参数说明类型可选值默认值
format展示的时间格式string"HH:mm:ss"
disabled禁用全部操作booleanfalse
hourStep小时选项间隔number1
minuteStep分钟选项间隔number1
secondStep秒选项间隔number1
disabledStartTime禁用的开始时间string例:"09:10"
disabledEndTime禁用的结束时间string例:"21:30"
FormProps

FormProps 参数为对象object,非必传,Form 表单属性,配置参考

参数说明类型可选值默认值
labelCollabel 标签布局object{span: 6}
wrapperCol节点标签布局object{span: 18}
...............
DetailData

DetailData 参数为对象object,非必传,只有在需要回显数据的时候(详情/编辑)传递该参数。

使用案例

文字表述感觉不明朗,不直观,直接上案例代码吧,简单粗暴明朗,朗朗上手!

:::整体代码配置示例。

<template>
  <y-form-list 
    ref="formListRef"
    :FormProps="FormProps"
    :FormListColumns="FormListColumns"
  >
  </y-form-list>
  <a-button type="primary" @click="getData">获取表单数据</a-button>
</template>

<script>
// 可忽略不用配置,内置默认值
const FormProps =  {
  labelCol: { span: 5 },
  wrapperCol: { span: 13 },
};
// 生成表单配置,必传
const FormListColumns = {
  key0: {
    tag: "Input",
    formItemProps: { label: "Input" },
    options: {
      rules: [{ required: true, message: "必选!" }],
    },
    nodeProps: {
      disabled: false,
    },
    afterProps: {
      tag: "Button",
      label: "选择",
      click: () => { },
    },
    hidden: false,
  },
  key1: {
    tag: 'Badge',
    formItemProps: {
      label: "Badge",
    },
    nodeProps: {
      text: '已解决'
    }
  },
  key2: {
    tag: "Select",
    formItemProps: {
      label: "Select",
    },
    options: {
      initialValue: '',
    },
    nodeProps: {
      options: [
        { label: "全部", value: '' },
        { label: "是", value: 1 },
        { label: "否", value: 2 },
      ],
    },
  },
  key3: {
    type: "RadioGroup",
    formItemProps: { label: "RadioGroup" },
    options: {
      initialValue: 1,
    },
    nodeProps: {
      options: [
        { label: "时间区间", value: 1 },
        { label: "间隔区间", value: 2 },
      ],
    },
  },
  key4: {
    type: "DatePicker",
    formItemProps: { label: "DatePicker" },
    options: {
      rules: [{ required: true, message: "必选!" }],
    },
    nodeProps: {
    },
  },
  key5: {
    tag: "Textarea",
    formItemProps: { label: "Textarea" },
    options: {
      rules: [{ required: true, message: "必填!" }],
    },
    nodeProps: {},
  },
  key6: {
    tag: "InputNumber",
    formItemProps: { label: "InputNumber" },
    options: {
      rules: [
        { required: true, message: "必选!" },
        { pattern: /^[1-9]\d*$/, message: "请输入正整数!" },
      ],
    },
    nodeProps: {
      placeholder: "请输入正整数",
      min: 1
    },
  },
  key7: {
    tag: "RangePicker",
    formItemProps: { label: "RangePicker" },
    options: {
      rules: [{ required: true, message: "必选!" }],
    },
    nodeProps: {
      format: "YYYY-MM-DD HH:mm:ss",
      disabledDate(current) {
        return current && current < moment().add(-1, "days");
      },
    },
  },
  key8: {
    type: "TimePicker",
    formItemProps: { label: "TimePicker" },
    options: {
      rules: [
        { required: true, message: "必选!" }, 
      ],
    },
    nodeProps: {
    },
  },
  key9: {
    tag: "Span",
    formItemProps: {
      label: "Span",
    },
    options: {
      initialValue: '我是Span',
    },
    nodeProps: {
    },
  },
  key10: {
    type: "InputNumberSelect",
    formItemProps: { label: "InputNumberSelect" },
    options: {
      initialValue: { number: 0, unit: '分' },
      rules: [
        { required: true, message: "必填!" }, 
      ],
    },
    nodeProps: {
      options: [
        { label: "秒", value: "s" },
        { label: "分", value: "m" },
      ],
    },
  },
  key11: {
    tag: "RangeTimePicker",
    formItemProps: {
      label: "RangeTimePicker",
    },
    options: {
      initialValue: [moment("10:20", "HH:mm:ss"), moment("20:30", "HH:mm:ss")],
      rules: [{ required: true, message: "必选!" }],
    },
    nodeProps: {
      format: "HH:mm:ss",
      disabled: false,
      disabledStartTime: '09:10',
      disabledEndTime: '21:30',
      hourStep: 1,
      minuteStep: 1,
      secondStep: 1,
    },
  },
}

export default {
  name: "Demo",
  data() {
    return {
      FormProps,
      FormListColumns
    };
  },
  computed: {
  },
  methods: {
    getData() {
      this.$refs.formListRef?.getFromValue((values) => {
        // values 是表单的值
        console.log(values);
      });
    },
  },
};
</script>  

:::

效果图

formList.jpg

SearchTable 组件使用文档说明

该组件的设计只为UI组件封装,统一方法处理,统一页面适配,不造成多余学习成本,主要还是学习UI组件库的使用。

特点

  • SearchForm 头部的筛选框部分支持屏幕适配,根据窗口大小一排展示不同数量

  • Table 底部的 table 表格固定UI的设计样式,支持分页器,各种常见模式都支持配置

Attributes

FormListColumns

FormListColumns 参数为对象 object,必传。

参数说明类型可选值默认值
keykey值,对应接口字段string
tag标签,首字母大写stringtag
optionsform组件配置object{}
formItemPropsformItem 配置项object{label: "名称"}{}
nodeProps内置标签节点对应的属性object{}
tag

tag 参数为字符串 string,标签介绍,首字母大写。现在支持一下标签:

tag说明配置文档
InputInput输入框文档
RadioGroupRadioGroup单选框文档
DatePickerDatePicker日期选择框文档
TextareaTextarea输入框文档
SelectSelect选择器文档
InputNumberInputNumber数字输入框文档
SwitchSwitch开关文档
SliderSlider滑动输入条文档
RateRate评分组件文档
UploadUpload上传文档
RangePickerRangePicker日期区间文档
TimePickerTimePicker时间选择框文档
AutoCompleteAutoComplete自动完成文档
InputNumberSelectInputNumberSelect数字带单位参考InputNumberSelect配置
RangeTimePickerRangeTimePicker时间区间RangeTimePicker

RangeTimePicker

其它配置不变,和其它节点一样,仅 nodeProps 的配置如下:

参数说明类型可选值默认值
format展示的时间格式string"HH:mm:ss"
disabled禁用全部操作booleanfalse
hourStep小时选项间隔number1
minuteStep分钟选项间隔number1
secondStep秒选项间隔number1
disabledStartTime禁用的开始时间string例:"09:10"
disabledEndTime禁用的结束时间string例:"21:30"
FormProps

FormProps 参数为对象 object,非必传,Form 表单属性,配置参考

参数说明类型可选值默认值
labelCollabel 标签布局object{span: 6}
wrapperCol节点标签布局object{span: 18}
...............
TableColumns

该参数为数组 object[array],是配置表格的API。文档见Table#Column

TableData

该参数为数组 array,下边表格的数据data,一般是接口请求回来的数据。

TableProps

该参数为对象 object,是关于表格属性的配置。文档见Table

使用案例

文字表述感觉不明朗,不直观,直接上案例代码吧,简单粗暴明朗,朗朗上手!

:::整体代码配置示例。

<template>
  <div>
    <y-search-table
      ref="searchTableRef"
      :FormProps="FormProps"
      :FormListColumns="FormListColumns"
      :TableColumns="columns"
      :TableData="tableData"
      :TableProps="TableProps"
      @onValuesChange="onValuesChange"
      @change="searchTableChange"
    >
      <template slot="extra">
        <a-button type="primary" @click="createClick">新建</a-button>
      </template>
      <span slot="source" slot-scope="{ record }">
        {{ record.tbSource === "1" ? "hive" : "其他" }}
      </span>
      <span slot="tbSource" slot-scope="{ record }">
        {{ record.tbSource === "1" ? "hive" : "其他" }}
      </span>
      <span slot="monitorSwitch" slot-scope="{ record }">
        {{ record.monitorSwitch === "1" ? "是" : "否" }}
      </span>
      <span slot="tbDegree" slot-scope="{ record }">
        {{ `P${record.tbDegree}` }}
      </span>
      <div slot="action" slot-scope="{ record }">
        <a
          v-if="record.monitorSwitch === '1'"
          style="margin-right: 8px"
          @click="tableItemsClick(record)"
        >
          表的监控项
        </a>
        <a v-else style="margin-right: 8px" @click="createClick(record)">
          新建监控
        </a>
      </div>
    </y-search-table>
  </div> 
</template>

<script>
// 这个是默认值,可以不传,如果需要定制,可选传
const FormPropsIndex = {
  labelCol: { span: 6 },
  wrapperCol: { span: 18 },
};
// 不传就是空
const FormListColumnsIndex = {
  monitor: {
    tag: "Select",
    formItemProps: {
      label: "是否监控",
    },
    options: {
      initialValue: '',
    },
    nodeProps: {
      options: [
        { label: "全部", value: '' },
        { label: "是", value: 1 },
        { label: "否", value: 2 },
      ],
    },
  },
  tableType: {
    tag: "Select",
    formItemProps: { label: "表类型" },
    options: {
      initialValue: 1,
    },
    nodeProps: {
      options: [
        { label: "离线表", value: 1 },
        { label: "实时表", value: 2 },
      ],
    },
  },
  tableImportance: {
    tag: "Select",
    formItemProps: { label: "表重要程度" },
    options: {
      initialValue: "",
    },
    nodeProps: {
      options: [
        { label: "全部", value: "" },
        { label: "P0", value: 0 },
        { label: "P1", value: 1 },
        { label: "P2", value: 2 },
        { label: "P3", value: 3 },
      ],
    },
  },
  source: {
    tag: "Select",
    formItemProps: { label: "数据源" },
    options: {
      initialValue: "hive",
    },
    nodeProps: {
      options: [
        { label: "全部", value: "" },
        { label: "hive", value: "hive" },
      ],
    },
  },
  database: {
    tag: "Select",
    formItemProps: { label: "数据库" },
    options: {},
    nodeProps: {
      options: [],
    },
  },
  tableName: {
    tag: "Select",
    formItemProps: { label: "数据表" },
    options: {},
    nodeProps: {
      options: [],
      placeholder: "可模糊搜索",
      showSearch: true,
      search: () => {}
    },
  },
  userName: {
    tag: "Select",
    formItemProps: { label: "负责人" },
    options: {
    },
    nodeProps: {
      options: [],
      placeholder: "请先选择数据库",
    },
  },
};
const columnsIndex = [
  {
    title: "数据源",
    dataIndex: "source",
    width: 100,
    scopedSlots: { customRender: "source" },
  },
  { title: "库", dataIndex: "db" },
  { title: "数据表名", dataIndex: "tb" },
  { title: "数据表备注", dataIndex: "tbComment" },
  {
    title: "是否监控",
    dataIndex: "monitorSwitch",
    width: 100,
    scopedSlots: { customRender: "monitorSwitch" },
  },
  {
    title: "表类型",
    dataIndex: "tbSource",
    width: 100,
    scopedSlots: { customRender: "tbSource" },
  },
  {
    title: "表的重要程度",
    dataIndex: "tbDegree",
    width: 150,
    scopedSlots: { customRender: "tbDegree" },
  },
  { title: "已配置规则数", dataIndex: "ruleCnt", width: 150 },
  { title: "负责人", dataIndex: "tbOwner", width: 150 },
  {
    title: "操作",
    dataIndex: "action",
    fixed: "right",
    width: 150,
    scopedSlots: { customRender: "action" },
  },
];

export default {
  name: "PageName",
  data(){
    return {
      FormProps: FormPropsIndex, // 该参数可以不传,有默认值
      FormListColumns: FormListColumnsIndex,
      columns: columnsIndex,
      tableData: [],
      TableProps: {
        title: "",
        loading: false,
        rowKey: (record) => `${record.db}${record.tb}`,
        pagination: {
          current: 1,
          pageSize: 10,
          total: 0
        },
      },
      feildValue: {},
      formData: {}
    }
  },
  mounted() {
  },
  methods: {
    // 监听刷选项发生改变事件
    onValuesChange(props, values) {
      this.formData = Object.assign(this.formData, values); // 缓存数据,做对比
      // Todo
    },
    // 表格事件,也是查询的回调事件,values 查询参数,pagination 页码变化参数
    searchTableChange(values = {}, pagination) {
      this.TableProps.pagination = Object.assign(this.TableProps.pagination, pagination)
      this.getFromData(Object.assign(this.feildValue, values));
    },
    // 请求表格列表数据
    getFromData(values){
      console.log(values)
      this.feildValue = values // 保存查询参数,切换页码时需要
      // Todo
    },
  }
}
</script>

:::

效果图

searchTable.jpg