npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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 } |

存在的问题

  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: "[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>