Arco-Design + Vue3 封装通过JSON数组类型配置表单组件

一、业务场景

在开发过程中频繁会使用到一些表单,封装一个简单的表单组件来处理这些业务逻辑

二、开发环境

Arco-Design + Vite + Vue3

三、挂载全局组件 TableForm

// components/index.ts注册组件
import { App } from 'vue';

// ...其它组件
import TableForm from './table-form/index.vue';

export default {
  install(Vue: App) {
  	// ...其它组件
    Vue.component('TableForm', TableForm);
  },
};

四、TableForm.vue组件介绍

<template>
  <a-row :gutter="gutter">
    <a-form
      ref="tableForm"
      class="table-form"
      :model="formModel"
      auto-label-width
      :rules="rules"
      :layout="layout"
      :label-align="labelAlign"
    >
      <a-col
        v-for="field in visibleFields"
        :key="field.name"
        :span="field.span ? field.span : 22"
        :data-index="field.index"
      >
        <a-form-item
          v-if="field.name"
          :field="field.name"
          :label="field.label"
          :tooltip="field.tooltip"
          :label-col-flex="`${field.labelWidth}px`"
        >
          <component
            :is="field.type"
            v-model="formModel[field.name]"
            allow-clear
            :multiple="field.multiple"
            :style="field.style"
            :allow-search="field.search"
            :placeholder="field.placeholder"
            :options="field.options"
            :type="field.elType"
            :field-names="field.fieldNames"
            :disabled="field.disabled"
            :checked-value="field.checkedValue"
            :unchecked-value="field.uncheckedValue"
            :show-time="field.showTime"
            :allow-create="field.allowCreate"
            :min="field.min"
            :max="field.max"
            :step="field.step"
            :precision="field.precision"
            :mode="field.mode"
            @change="change"
          />
        </a-form-item>
      </a-col>
      <slot name="custom"></slot>
    </a-form>
  </a-row>
</template>

<script lang="ts" setup>
  import { toRefs, ref, watch, computed, nextTick } from 'vue';
  import { FormInstance, ResponsiveValue } from '@arco-design/web-vue';
  import type { FieldProps } from '@/types/global';

  const props = withDefaults(
    defineProps<{
      formJson: Array<any>;
      rules?: any;
      formState: FieldProps;
      layout?: 'horizontal' | 'vertical' | 'inline' | undefined;
      labelAlign?: 'left' | 'right' | undefined;
      gutter?:
        | number
        | ResponsiveValue
        | [number | ResponsiveValue, number | ResponsiveValue];
      display?: boolean | void; // 按条件展示/隐藏组件
    }>(),
    {
      layout: 'horizontal',
      formJson: () => [],
      display: false,
    }
  );
  const emit = defineEmits(['change']);

  // ref绑定组件
  const tableForm = ref<FormInstance>();
  const formModel = ref({} as any);
  const { formJson, rules, formState, layout, labelAlign, gutter } =
    toRefs(props);

  defineExpose({
    formModel,
    tableForm,
  });

  /*
   ** 表单过滤,特殊业务场景需要,没有则可以删除
   ** 用于条件判断是否通过表单条件展示需要的内容
   */
  const visibleFields = computed(() => {
    if (!props.display) {
      return formJson?.value === undefined ? false : formJson.value;
    }
    return props?.display();
  }) as unknown as Array<FieldProps>;

  /*
  * 按 data-index 将组件进行排序
  **/
  nextTick(() => {
    const elements = document.querySelectorAll('.table-form .arco-col');
    const formBox = document.querySelector('.table-form');
    // 将 elements 转换为数组
    const elementsArray = Array.from(elements);
    // 按照 data-index 进行排序
    elementsArray.sort((a, b) => {
      const indexA = a.getAttribute('data-index');
      const indexB = b.getAttribute('data-index');
      return indexA - indexB;
    });
    elementsArray.forEach((element) => {
      // 如果没有data-index则不需要
      if (!element.getAttribute('data-index')) return;
      // 将元素插入到 DOM 中
      formBox?.appendChild(element);
    });
  });

  const change = (val: any) => {
    emit('change', val);
  };

  // 监听表单数据变化赋值
  watch(
    () => props.formState,
    (val) => {
      if (!val) return;
      formModel.value = val;
    },
    { deep: true, immediate: true }
  );
</script>

<script lang="ts">
  export default {
    name: 'TableForm',
  };
</script>

<style lang="less" scoped></style>



表单配置项

  1. allow-clear 是否支持清空
  2. multiple 选择框是否支持多选
  3. style a-form-item的样式
  4. allow-search 选择框是否支持搜索
  5. placeholder 占位符
  6. options选择框的选择项
  7. 表单组件内的type样式
  8. field-names options选项值配置

    其它自定义可以依次添加

五、使用场景

<template>
  <a-modal v-model:visible="visible" @ok="handleOk" @cancel="handleCancel">
    <template #title>{{ title }}</template>
    <TableForm
      ref="formRef"
      :form-json="newFormJson"
      :form-state="state.formModel"
      :rules="rules"
      :display="filterFormJson"
    ></TableForm>
  </a-modal>
</template>

<script setup lang="ts">
  import { reactive, toRefs, ref, watch, computed } from 'vue';
  import { omit } from 'lodash';
  import { cpDetail } from '@/api/settings';
  import { jsonToFormData } from '@/utils';
  import { FormModelProps } from '../types';
  import { adminFormJson } from '../formJson';

  const props = defineProps({
    visible: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: '',
    },
    type: {
      type: String,
      default: '',
    },
    record: {
      type: Object,
      default: () => ({}),
    },
    serviceList: {
      type: Array,
      default: () => [],
    },
    logList: {
      type: Array,
      default: () => [],
    },
  });

  const rules = {
    real_name: [
      {
        required: true,
        message: `所属人员不能为空!`,
      },
    ],
    account: [
      {
        required: true,
        message: `账号不能为空!`,
      },
    ],
    pwd: [
      {
        required: true,
        message: `请输入密码!`,
      },
      {
        minLength: 6,
        message: `密码不能少于六位!`,
      },
    ],
    comfirmpwd: [
      {
        required: true,
        message: `请确认密码!`,
      },
      {
        minLength: 6,
        message: `密码不能少于六位!`,
      },
    ],
    pid: [
      {
        required: true,
        message: `上级账号不能为空!`,
      },
    ],
    status: [
      {
        required: true,
        message: `状态不能为空!`,
      },
    ],
  };
  
  const { visible, title, type, record, serviceList, logList } = toRefs(props);
  const state = reactive({
    formModel: {
      level: '1',
    } as FormModelProps,
  });
  const formRef = ref();
  // 生成一个新的json,补全选择框的options
  const newFormJson = computed(() => {
    return adminFormJson.map((item: any) => {
      if (item.name === 'service_ids') {
        return {
          ...item,
          options: serviceList.value,
        };
      }
      return { ...item };
    });
  });
  
  const filterFormJson = () => {
    return newFormJson.value.filter((item) => {
      if (item.name === 'pid') {
        return state.formModel.level === '2';
      }
      return true;
    });
  };

  const emit = defineEmits(['onOk', 'onCancel']);
  const handleCancel = async () => {
    emit('onCancel', false);
  };
  const handleOk = async () => {
  	// 表单校验
    const res = await formRef.value.tableForm?.validate();
    if (!res) {
      state.formModel.service_ids = state.formModel.service_ids.join();
      if (state.formModel.level === '1')
        state.formModel = omit(state.formModel, ['pid']);
      emit('onOk', false, state.formModel);
    }
  };

  const getCpDetail = async (id: string) => {
    const { result } = await cpDetail(jsonToFormData({ id }));
    state.formModel = result;
    state.formModel.service_ids = result.service_list.map((item) => item.service_id);
    state.formModel.pid = data.pid === '0' ? '' : data.pid;
  };

  watch(
    () => type,
    (val) => {
      if (val.value === 'edit') {
        getCpDetail(record.value.id);
         state.formModel = omit(state.formModel, [
           'last_time',
           'service_list',
           'p_name',
         ]);
      }
    },
    { deep: true, immediate: true }
  );
</script>

<style scoped lang="less">
  .arco-btn {
    width: 80px;
  }
  .arco-col {
    text-align: center;
  }
</style>

在这里插入图片描述

选择否也就是条件判断是否需要显示其它表单组件

在这里插入图片描述

下面贴一下formJson.ts

export const adminFormJson = [
  {
    name: 'real_name',
    label: '所属人员',
    type: 'a-input',
    placeholder: '请输入所属人员名称',
    options: [],
  },
  {
    name: 'account',
    label: '账号',
    type: 'a-input',
    placeholder: '请输入账号',
    options: [],
  },
  {
    name: 'service_ids',
    label: '授权小程序',
    type: 'a-select',
    multiple: true,
    style: `{textAlign: 'left'}`,
    placeholder: '请选择授权小程序',
    fieldNames: { value: 'id', label: 'service_name' },
    options: [],
  },
  {
    name: 'pwd',
    label: '密码',
    type: 'a-input',
    placeholder: '请输入密码',
    options: [],
  },
  {
    name: 'comfirmpwd',
    label: '确认密码',
    type: 'a-input',
    placeholder: '请确认密码',
    options: [],
  },
  {
    name: 'level',
    label: '是否一级账号',
    type: 'a-radio-group',
    placeholder: '',
    elType: 'button',
    options: [
      {
        value: '1',
        label: '是',
      },
      {
        value: '2',
        label: '否',
      },
    ],
  },
  {
    name: 'pid',
    label: '上级账号ID',
    type: 'a-select',
    placeholder: '请选择上级账号ID',
    fieldNames: { value: 'id', label: 'real_name' },
    options: [],
  },
  {
    name: 'status',
    label: '状态',
    type: 'a-select',
    placeholder: '请选择账号状态',
    options: [
      {
        value: '1',
        label: '正常',
      },
      {
        value: '2',
        label: '禁用',
      },
    ],
  },
];
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值