vue3-hahake-form
v1.1.2
Published
# vue3-hahake-from
Downloads
1
Readme
预览地址: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 的所有属性和事件 | | { row, column, $index } |
存在的问题
- 还没找到办法支持所有
xType
的所有方法,如果需要用到组件的方法,目前只能用slotName
引入Element
原生组件,有想法的伙伴可以交流一下 - 由于获取不到
el-tree
的方法,Tree
和TreeV2
组件目前还不能正确回显和数据绑定 - 目前已支持大部分的表单类型,还缺少一个
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: "[email protected]",
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>