vue3-hahake-form
TypeScript icon, indicating that this package has built-in type declarations

1.1.2 • Public • Published

预览地址:vue3-hahake-form

vue3-hahake-from

基于 Vue3 + Element-plus 封装的 Form 组件,支持所有 Element-plus Form 组件配置项 文档

使用方法

  • 根目录下执行 npm i vue3-hahake-form 命令
npm i vue3-hahake-form
  • 全局挂载组件
import { createApp } from 'vue'
import App from './App.vue'
import hahakeform from 'vue3-hahake-form'

createApp(App).use(hahakeform).mount('#app')
  • 在页面上使用
<!-- template -->
<vue3-hahake-form
    :formData="formData"
    :formColumns="formColumns"
    :formRules="formRules"
    label-width="120px"
    ref="baseForm"
    >
    <!-- 大标题 -->
    <template v-slot:baseTitle>
        <h1>基于 Element-plus 封装的表单组件</h1>
    </template>
    <!-- 操作按钮 -->
    <template v-slot:Actions>
        <div style="text-align: center">
        <el-button type="primary" @click="onSubmit(baseForm)"
            >提交</el-button
        >
        <el-button @click="handlerReset">重置</el-button>
        </div>
    </template>
</vue3-hahake-form>

Form 属性

除此之外支持所有 el-form 所有 属性

参数 说明 类型 默认值
formData 表单数据,双向绑定(字段需与 prop 属性一样) Object -
formColumns 表单配置项,详情见下方 Column 属性 Array -
formRules 表单规则验证,校验规则请参考 el-form Object -

Form 方法

表单组件已给 el-form 绑定 ref 并用 defineExpose 暴露出来,我们只需要在引入组件中绑定ref,即可调用 el-form 的方法

<XmwForm :formData="formData" :formColumns="formColumns" :formRules="formRules" ref="baseForm"></XmwForm>

调用方式

const baseForm = ref<HTMLElement | null>(null)
baseForm.value.formRef.resetFields()

具体写法可参考 Demo

Column 配置

参数 说明 类型 默认值
xType 表单类型,详情见下方 xType 属性 String -
slotName 插槽,开启 slot 支持(开启这个属性,其它属性无效) Boolean false
label el-form-item label 属性 String -
prop el-form-item prop 属性 String -
span 栅格占据的列数 Number -
offset 栅格左侧的间隔格数 Number -
formItemOpts 支持 el-form-item 的所有属性 Object -
$event 支持 xType 表单类型的所有事件(事件名需与 Element 组件事件名一样) Function -

xType 表单类型

参数 类型 说明
Input 输入框 支持 el-input 的所有属性和事件
Autocomplete 自动补全输入框 支持 el-autocomplete 的所有属性和事件
Select 下拉框 支持 el-select 的所有属性和事件
SelectV2 虚拟列表选择器 支持 el-select-v2 的所有属性和事件
DatePicker 日期时间选择器 支持 el-date-picker 的所有属性和事件
TimePicker 时间选择器 支持 el-time-picker 的所有属性和事件
TimeSelect 时间选择 支持 el-time-select 的所有属性和事件
InputNumber 数字输入框 支持 el-input-number 的所有属性和事件
Radio 单选框 支持 el-radio 的所有属性和事件
Checkbox 多选框 支持 el-checkbox 的所有属性和事件
Switch Switch 开关 支持 el-switch 的所有属性和事件
Slider Slider 滑块 支持 el-slider 的所有属性和事件
Rate Rate 评分 支持 el-rate 的所有属性和事件
Transfer 穿梭框 支持 el-transfer 的所有属性和事件
Cascader 级联框 支持 el-cascader 的所有属性和事件
ColorPicker 颜色选择器 支持 el-color-picker 的所有属性和事件
Tree 树形控件 支持 el-tree 的所有属性和事件
TreeSelect 树形选择 支持 el-tree-select 的所有属性和事件
TreeV2 虚拟化树形控件 支持 el-tree-v2 的所有属性和事件

存在的问题

  1. 还没找到办法支持所有 xType 的所有方法,如果需要用到组件的方法,目前只能用 slotName 引入 Element 原生组件,有想法的伙伴可以交流一下
  2. 由于获取不到 el-tree 的方法,TreeTreeV2 组件目前还不能正确回显和数据绑定
  3. 目前已支持大部分的表单类型,还缺少一个 LasySelect 懒加载,带有空封装

完整使用技巧

<script setup lang="ts">
import type { FormInstance, FormRules } from "element-plus";
import { ElMessage } from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
import { onMounted, reactive, ref } from "vue";
import {
areaOpts, cityGdList,
cityHnList, departmentList, jobsList, predefineColors,
predefineTrees, provinces
} from "./data";

// 穿梭框数据
interface Option {
  key: number;
  label: string;
  disabled: boolean;
}

const generateData = (): Option[] => {
  const data: Option[] = [];
  for (let i = 1; i <= 15; i++) {
    data.push({
      key: i,
      label: `列表 ${i}`,
      disabled: i % 4 === 0,
    });
  }
  return data;
};

const transferData = ref(generateData());

// 虚拟列表模拟数据
const initials = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
const selectV2Options = Array.from({ length: 1000 }).map((_, idx) => ({
  value: `Option ${idx + 1}`,
  label: `${initials[idx % 10]}${idx}`,
}));

// 权限菜单数据
const permissionsItem = [
  {
    id: "setting",
    label: "系统设置",
    children: [
      {
        id: "menu",
        label: "菜单管理",
      },
    ],
  },
];

interface Tree {
  id: string;
  label: string;
  children?: Tree[];
}

const getKey = (prefix: string, id: number) => {
  return `${prefix}-${id}`;
};

const createData = (
  maxDeep: number,
  maxChildren: number,
  minNodesNumber: number,
  deep = 1,
  key = "node"
): Tree[] => {
  let id = 0;
  return Array.from({ length: minNodesNumber })
    .fill(deep)
    .map(() => {
      const childrenNumber =
        deep === maxDeep ? 0 : Math.round(Math.random() * maxChildren);
      const nodeKey = getKey(key, ++id);
      return {
        id: nodeKey,
        label: nodeKey,
        children: childrenNumber
          ? createData(maxDeep, maxChildren, childrenNumber, deep + 1, nodeKey)
          : undefined,
      };
    });
};

const treeV2Data = createData(4, 30, 40);
// 表单数据
const formData = reactive({
  userName: "张三",
  email: "843348394@qq.com",
  remark: "为中华之崛起而读书",
  age: 18,
  jobs: "FrontEndEngineer",
  department: ["hr", "manager"],
  province: "guangdong",
  city: "zhanjiang",
  birthday: "2022-01-01",
  birthTime: new Date(2022, 12, 31, 9, 40, 32),
  getupTime: "09:00",
  sex: "0",
  officeArea: ["guangdong", "shanghai"],
  openService: true,
  scoresRange: [20, 80],
  rate: 5,
  hometown:  "guangdong,zhanjiang",
  virtualList: [],
  permissionsMenu: permissionsItem,
  treeSelect: ["home", "log"],
  treeSelectV2: [],
  transfer: [1],
  loveColor: "rgba(255, 69, 0, 0.68)",
});
// 表单配置项
const formColumns = reactive([
  {
    slotName: "baseTitle",
  },
  {
    xType: "Input",
    label: "姓名",
    prop: "userName",
    clearable: true,
    span: 8,
    input,
  },
  {
    xType: "Autocomplete",
    label: "邮箱",
    prop: "email",
    span: 8,
    "fetch-suggestions": querySearch,
  },
  {
    xType: "InputNumber",
    label: "年龄",
    prop: "age",
    min: 1,
    max: 120,
    "controls-position": "right",
    step: 2,
    span: 8,
  },
  {
    xType: "Select",
    label: "岗位",
    prop: "jobs",
    span: 8,
    options: jobsList,
  },
  {
    xType: "Select",
    label: "部门",
    prop: "department",
    span: 8,
    clearable: true,
    valueFiled: "id",
    labelFiled: "name",
    multiple: true,
    "collapse-tags": true,
    options: departmentList,
  },
  {
    xType: "Select",
    label: "地区",
    prop: "province",
    span: 5,
    options: provinces,
    change: changeCity,
  },
  {
    xType: "Select",
    prop: "city",
    label: "-",
    span: 3,
    formItemOpts: {
      labelWidth: "30px",
    },
    options: [],
  },
  {
    xType: "DatePicker",
    label: "出生日期",
    type: "date",
    prop: "birthday",
    span: 8,
  },
  {
    xType: "TimePicker",
    label: "出生时间",
    prop: "birthTime",
    placeholder: "请选择时间",
    span: 8,
  },
  {
    xType: "TimeSelect",
    label: "起床时间",
    prop: "getupTime",
    placeholder: "请选择时间",
    start: "08:30",
    step: "00:15",
    end: "18:30",
    span: 8,
  },
  {
    xType: "Radio",
    label: "性别",
    prop: "sex",
    span: 8,
    options: [
      {
        value: "0",
        label: "男",
      },
      {
        value: "1",
        label: "女",
      },
    ],
  },
  {
    xType: "Checkbox",
    label: "办公地区",
    prop: "officeArea",
    span: 8,
    options: provinces,
  },
  {
    xType: "Switch",
    label: "开启服务",
    prop: "openService",
    span: 8,
    "active-color": "#13ce66",
    "inactive-color": "#ff4949",
    "active-text": "是",
    "inactive-text": "否",
    "inline-prompt": true,
  },
  {
    xType: "ColorPicker",
    label: "喜欢的颜色",
    prop: "loveColor",
    "show-alpha": true,
    predefine: predefineColors,
    span: 8,
  },
  {
    xType: "Slider",
    label: "分数范围",
    prop: "scoresRange",
    range: true,
    "show-stops": true,
    max: 100,
    step: 10,
    span: 8,
  },
  {
    xType: "Rate",
    label: "评分",
    prop: "rate",
    "show-text": true,
    texts: ["非常不好", "不好", "一般", "好", "非常好"],
    colors: ["#99A9BF", "#F7BA2A", "#FF9900"],
    span: 8,
  },
  {
    xType: "Cascader",
    label: "家乡",
    prop: "hometown",
    options: areaOpts,
    props: {
      expandTrigger: "hover",
    },
    span: 8,
  },
  {
    xType: "SelectV2",
    label: "虚拟列表",
    prop: "virtualList",
    span: 8,
    filterable: true,
    multiple: true,
    "multiple-limit": 3,
    options: selectV2Options,
  },
  {
    xType: "TreeSelect",
    label: "树形选择",
    prop: "treeSelect",
    multiple: true,
    data: predefineTrees,
    "show-checkbox": true,
    "node-key": "id",
    span: 8,
  },
  {
    xType: "Tree",
    label: "树形控件",
    prop: "permissionsMenu",
    data: predefineTrees,
    "show-checkbox": true,
    "node-key": "id",
    check: checkRoles,
    span: 8,
  },
  {
    xType: "TreeV2",
    label: "虚拟树形控件",
    prop: "treeSelectV2",
    data: treeV2Data,
    "show-checkbox": true,
    "node-key": "id",
    span: 8,
  },
  {
    xType: "Transfer",
    label: "穿梭框",
    prop: "transfer",
    filterable: true,
    "left-default-checked": [2, 3],
    "right-default-checked": [1],
    titles: ["Source", "Target"],
    "button-texts": ["To left", "To right"],
    format: {
      noChecked: "${total}",
      hasChecked: "${checked}/${total}",
    },
    data: transferData,
    style: "display: flex",
    span: 24,
  },
  {
    xType: "Input",
    label: "备注",
    prop: "remark",
    clearable: true,
    type: "textarea",
    rows: 4,
    maxlength: 200,
    "show-word-limit": true,
    span: 24,
  },
  {
    slotName: "Actions",
  },
]);
// 表单验证规则
const formRules = reactive<FormRules>({
  userName: [
    { required: true, message: "请输入名字", trigger: "blur" },
    { min: 2, max: 5, message: "名字长度在2-5个字", trigger: "blur" },
  ],
  email: [
    {
      type: "email",
      message: "请输入正确的邮箱格式",
      trigger: ["blur", "change"],
    },
  ],
  department: [
    {
      required: true,
      message: "请选择部门",
      trigger: "change",
    },
  ],
});

// 输入框触发事件
function input(val: string | number) {
  console.log(val);
}
// 自动补全建议列表
interface RestaurantItem {
  value: string;
}
const restaurants = ref<RestaurantItem[]>([]);
function querySearch(queryString: string, cb: any) {
  let emailItem = ["qq.com", "163.com"];
  const results = queryString
    ? emailItem.map((el) => {
        return { value: queryString.split("@")[0] + "@" + el };
      })
    : restaurants.value;
  cb(results);
}

// 省市联动
function changeCity(val: string | number) {
  formData.city = "";
  formColumns.find((el) => el.prop == "city").options = {
    guangdong: cityGdList,
    hunan: cityHnList,
  }[val];
}

// 勾选权限菜单
function checkRoles(node, data) {
  console.log(node, data);
  // formData.permissionsMenu = [...permissionsItem, ...[node]]
}

// 递归过滤有子节点的父节点id
let childKeys: string[] = [];
const loop = function (tree: any) {
  tree.map((node) => {
    if (node.children) {
      loop(node.children);
    } else {
      childKeys.push(node.id);
    }
  });
};
loop(formData.permissionsMenu);

const baseForm = ref<HTMLElement | null>(null);

// 提交操作
async function onSubmit(formEl: FormInstance | undefined) {
  if (!formEl) return;
  await formEl.formRef.validate((valid, fields) => {
    if (!valid) return;
    console.log(formData);
    ElMessage({
      message: "请到控制台查看数据",
      type: "success",
    });
  });
}

// 重置操作
function handlerReset() {
  baseForm.value.formRef.resetFields();
  formData.permissionsMenu = permissionsItem;
  formColumns.find((el) => el.prop == "permissionsMenu").defaultCheckedKeys =
    childKeys;
}

onMounted(() => {
  formColumns.find((el) => el.prop == "city").options = {
    guangdong: cityGdList,
    hunan: cityHnList,
  }[formData.province];
  // 权限菜单回显
  formColumns.find((el) => el.prop == "permissionsMenu").defaultCheckedKeys =
    childKeys;
});
</script>

<template>
  <el-config-provider :locale="zhCn">
    <div
      class="container"
      style="width: 90%; margin: 0 auto; padding-bottom: 30px"
    >
      <vue3-hahake-form
        :formData="formData"
        :formColumns="formColumns"
        :formRules="formRules"
        label-width="120px"
        ref="baseForm"
      >
        <!-- 大标题 -->
        <template v-slot:baseTitle>
          <h1>基于 Element-plus 封装的表单组件</h1>
        </template>
        <!-- 操作按钮 -->
        <template v-slot:Actions>
          <div style="text-align: center">
            <el-button type="primary" @click="onSubmit(baseForm)"
              >提交</el-button
            >
            <el-button @click="handlerReset">重置</el-button>
          </div>
        </template>
      </vue3-hahake-form>
    </div>
  </el-config-provider>
</template>
<style scoped>
:deep .el-transfer__buttons {
  display: flex;
  align-items: center;
}

:deep .el-rate__item {
  display: flex;
}
</style>

Readme

Keywords

none

Package Sidebar

Install

npm i vue3-hahake-form

Weekly Downloads

6

Version

1.1.2

License

none

Unpacked Size

95.5 kB

Total Files

6

Last publish

Collaborators

  • yuanquanke