vue3 + antd demo:实现动态form表单内容

form组件内容(具体需要的控件根据所需配置): 

   <!-- 组件内容 -->

<template>
  <a-form
    :model="formState"
    :rules="formRules"
    ref="formRef"
    :label-col="{ span: 6 }"
    :wrapper-col="{ span: 16 }"
    @finish="handleSubmit"
  >
    <template v-for="item in formItems" :key="item.name">
      <!-- 输入框 -->
      <a-form-item v-if="item.type === 'input' || !item.type" :label="item.label" :name="item.name" :extra="item.extra">
        <a-input
          v-model:value="formState[item.name]"
          :placeholder="item.placeholder || `请输入${item.label}`"
          :disabled="item.disabled"
          :allow-clear="item.allowClear !== false"
        />
      </a-form-item>

      <!-- 数字输入框 -->
      <a-form-item v-else-if="item.type === 'number'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-input-number
          v-model:value="formState[item.name]"
          :placeholder="item.placeholder || `请输入${item.label}`"
          :disabled="item.disabled"
          :min="item.min"
          :max="item.max"
          :step="item.step || 1"
          style="width: 100%"
        />
      </a-form-item>

      <!-- 密码框 -->
      <a-form-item v-else-if="item.type === 'password'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-input-password
          v-model:value="formState[item.name]"
          :placeholder="item.placeholder || `请输入${item.label}`"
          :disabled="item.disabled"
          :allow-clear="item.allowClear !== false"
        />
      </a-form-item>

      <!-- 下拉选择 -->
      <a-form-item v-else-if="item.type === 'select'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-select
          v-model:value="formState[item.name]"
          :placeholder="item.placeholder || `请选择${item.label}`"
          :disabled="item.disabled"
          :allow-clear="item.allowClear !== false"
          :options="item.options"
          :mode="item.mode"
          :show-search="item.showSearch"
        />
      </a-form-item>

      <!-- 单选按钮 -->
      <a-form-item v-else-if="item.type === 'radio'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-radio-group v-model:value="formState[item.name]" :disabled="item.disabled" :options="item.options" />
      </a-form-item>

      <!-- 多选框 -->
      <a-form-item v-else-if="item.type === 'checkbox'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-checkbox-group v-model:value="formState[item.name]" :disabled="item.disabled" :options="item.options" />
      </a-form-item>

      <!-- 开关 -->
      <a-form-item v-else-if="item.type === 'switch'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-switch
          v-model:checked="formState[item.name]"
          :disabled="item.disabled"
          :checked-children="item.checkedChildren || '开启'"
          :un-checked-children="item.unCheckedChildren || '关闭'"
        />
      </a-form-item>

      <!-- 日期选择器 -->
      <a-form-item v-else-if="item.type === 'date'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-date-picker
          v-model:value="formState[item.name]"
          :placeholder="item.placeholder || `请选择${item.label}`"
          :disabled="item.disabled"
          :allow-clear="item.allowClear !== false"
          style="width: 100%"
          :show-time="item.showTime"
          :format="item.format || 'YYYY-MM-DD'"
        />
      </a-form-item>

      <!-- 时间选择器 -->
      <a-form-item v-else-if="item.type === 'time'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-time-picker
          v-model:value="formState[item.name]"
          :placeholder="item.placeholder || `请选择${item.label}`"
          :disabled="item.disabled"
          :allow-clear="item.allowClear !== false"
          style="width: 100%"
          :format="item.format || 'HH:mm:ss'"
        />
      </a-form-item>

      <!-- 日期范围选择器 -->
      <a-form-item v-else-if="item.type === 'range'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-range-picker
          v-model:value="formState[item.name]"
          :placeholder="item.placeholder || ['开始日期', '结束日期']"
          :disabled="item.disabled"
          :allow-clear="item.allowClear !== false"
          style="width: 100%"
          :show-time="item.showTime"
          :format="item.format || 'YYYY-MM-DD'"
        />
      </a-form-item>

      <!-- 上传组件 -->
      <a-form-item v-else-if="item.type === 'upload'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-upload
          v-model:file-list="formState[item.name]"
          :disabled="item.disabled"
          :multiple="item.multiple"
          :accept="item.accept"
          :list-type="item.listType || 'text'"
          :before-upload="item.beforeUpload || beforeUpload"
        >
          <a-button v-if="item.listType !== 'picture-card'">
            <upload-outlined></upload-outlined>
            点击上传
          </a-button>
          <div v-else>
            <plus-outlined></plus-outlined>
            <div style="margin-top: 8px">上传</div>
          </div>
        </a-upload>
      </a-form-item>

      <!-- 文本域 -->
      <a-form-item v-else-if="item.type === 'textarea'" :label="item.label" :name="item.name" :extra="item.extra">
        <a-textarea
          v-model:value="formState[item.name]"
          :placeholder="item.placeholder || `请输入${item.label}`"
          :disabled="item.disabled"
          :allow-clear="item.allowClear !== false"
          :rows="item.rows || 4"
          :show-count="item.showCount"
          :maxlength="item.maxlength"
        />
      </a-form-item>
    </template>

    <a-form-item :wrapper-col="{ offset: 6, span: 16 }">
      <a-space>
        <a-button type="primary" html-type="submit">提交</a-button>
        <a-button @click="handleReset">重置</a-button>
      </a-space>
    </a-form-item>
  </a-form>
</template>

<script setup>
import { ref, reactive, watch, onMounted } from "vue";
import { UploadOutlined, PlusOutlined } from "@ant-design/icons-vue";

const props = defineProps({
  // 表单配置项
  formItems: {
    type: Array,
    required: true,
    default: () => [],
    validator: (value) => {
      return value.every((item) => item.name && item.label);
    },
  },
  // 初始值
  initialValues: {
    type: Object,
    default: () => ({}),
  },
});

const emit = defineEmits(["submit", "reset"]);

const formRef = ref();
const formState = reactive({});
const formRules = reactive({});

// 初始化表单数据和校验规则
const initForm = () => {
  props.formItems.forEach((item) => {
    // 确保所有字段都被初始化
    if (!(item.name in formState)) {
      formState[item.name] = props.initialValues[item.name] ?? getDefaultValueByType(item.type);
    }

    // 初始化校验规则
    if (item.rules) {
      formRules[item.name] = item.rules;
    }
  });
};
// 提交表单
const handleSubmit = () => {
  formRef.value
    .validate()
    .then(() => {
      emit("submit", formState);
    })
    .catch((error) => {
      console.log("验证失败:", error);
    });
};

// 重置表单
const handleReset = () => {
  formRef.value.resetFields();
  emit("reset");
};

// 上传前处理
const beforeUpload = (file) => {
  const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png";
  if (!isJpgOrPng) {
    message.error("只能上传 JPG/PNG 文件!");
  }
  const isLt2M = file.size / 1024 / 1024 < 2;
  if (!isLt2M) {
    message.error("图片大小不能超过 2MB!");
  }
  return isJpgOrPng && isLt2M;
};

// 暴露方法给父组件
defineExpose({
  validate: () => formRef.value.validate(),
  resetFields: () => formRef.value.resetFields(),
  getFormData: () => formState,
  setFieldsValue: (values) => {
    Object.keys(values).forEach((key) => {
      if (formState[key] !== undefined) {
        formState[key] = values[key];
      }
    });
  },
});
// 根据类型获取默认值
const getDefaultValueByType = (type) => {
  switch (type) {
    case "checkbox":
      return [];
    case "switch":
      return false;
    case "upload":
      return [];
    case "number":
      return null;
    case "select":
      return undefined;
    case "radio":
      return undefined;
    default:
      return undefined;
  }
};
// 监听初始值变化
watch(
  () => props.initialValues,
  (newVal) => {
    Object.keys(newVal).forEach((key) => {
      if (formState[key] !== undefined) {
        formState[key] = newVal[key];
      }
    });
  },
  { deep: true }
);

// 组件挂载时初始化
onMounted(() => {
  initForm();
});
</script>

 组件的引用和数据处理:

<template>
  <DynamicForm :form-items="formItems" :initial-values="initialValues" @submit="handleSubmit"> </DynamicForm>
</template>
const formItems = ref([
  {
    name: "username",
    label: "用户名",
    type: "input",
    width: "200px",
    rules: [
      { required: true, message: "请输入用户名" },
      { min: 3, max: 10, message: "用户名长度在3-10个字符" },
    ],
  },
  {
    name: "password",
    label: "密码",
    width: "200px",
    type: "password",
    rules: [
      { required: true, message: "请输入密码" },
      { pattern: /^[a-zA-Z0-9]{6,12}$/, message: "密码必须是6-12位字母或数字" },
    ],
  },
  {
    name: "age",
    label: "年龄",
    width: "200px",
    type: "number",
    min: 0,
    max: 120,
    rules: [{ required: false, message: "请输入年龄" }],
  },
  {
    name: "gender",
    label: "性别",
    width: "200px",
    type: "select",
    options: [
      { label: "男", value: "male" },
      { label: "女", value: "female" },
    ],
    rules: [{ required: true, message: "请选择性别" }],
  },
  {
    name: "hobbies",
    label: "爱好",
    width: "200px",
    type: "checkbox",
    options: [
      { label: "游泳", value: "swimming" },
      { label: "跑步", value: "running" },
      { label: "阅读", value: "reading" },
    ],
  },
  {
    name: "agree",
    label: "同意协议",
    width: "200px",
    type: "switch",
    rules: [
      {
        validator: (_, value) => (value ? Promise.resolve() : Promise.reject("请同意协议")),
      },
    ],
  },
  {
    name: "birthday",
    label: "生日",
    width: "200px",
    type: "date",
    rules: [{ required: true, message: "请选择生日" }],
  },
  {
    name: "resume",
    label: "个人简介",
    width: "200px",
    type: "textarea",
    maxlength: 200,
    showCount: true,
  },
  {
    name: "avatar",
    label: "头像",
    width: "200px",
    type: "upload",
    listType: "picture-card",
    accept: "image/*",
  },
]);

const initialValues = ref({
  username: "张三",
  age: 25,
  gender: "male",
  hobbies: ["reading"],
  agree: true,
  customField: "", // 确保自定义字段有初始值
});

const handleSubmit = (values) => {
  console.log("提交数据:", values);
  message.success("表单提交成功");
};

const handleReset = () => {
  console.log("表单已重置");
};

最后的效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值