Vue3+Vite+TypeScript+Element Plus开发-23.客制Form组件

 系列文档目录

Vue3+Vite+TypeScript安装

Element Plus安装与配置

主页设计与router配置

静态菜单设计

Pinia引入

Header响应式菜单缩展

Mockjs引用与Axios封装

登录设计

登录成功跳转主页

多用户动态加载菜单

Pinia持久化

动态路由 -动态增加路由

动态路由-动态删除路由 

路由守卫-无路由跳转404

 路由守卫-未登录跳转登录界面

 登录退出

Tags-组件构建

Tags-与菜单联动 

Pinia持久化优化

按钮权限

客制按钮组件

客制Table组件

客制Form组件

国际化

配置文件


文章目录

目录

 系列文档目录

文章目录

前言

子组件-Form组件构建

 父组件-Form组件调用

演示效果

​编辑

模拟测试

父组件-Form组件多界面

后续

代码下载 


前言

        在实际项目开发中,表单(Form)的数据编辑功能是常见需求。为了提高开发效率,本章节将重点介绍如何对表单组件进行封装,从而实现快速开发。


子组件-Form组件构建

创建文件:  components/ActionFormCont.vue  

实现功能:

1. 栏位定义: 控件配置由父组件传入,控件可自定义隐藏。

2. 数据来源: 控件数据由父组件提供。

3. 帅选功能: 数据选择由父组件提供。

4. 数据检证: 数据验证由父组件提供。

<template>
    <el-form
      ref="formRef"
      :model="formData"
      label-width="120px"
      :rules="rules"
      class="form"
    >
      <div v-for="item in fields" :key="item.prop">
        <el-form-item
          v-if="!item.hide"
          :label="item.label"
          :prop="item.prop"
          :rules="item.rules"
        >
          <el-input
            v-if="item.InputType === 'text'"
            v-model="formData[item.prop]"
            :disabled="item.ReadOnly"
            :placeholder="`请输入${item.label}`"
          ></el-input>
          <el-select
            v-else-if="item.InputType === 'select'"
            v-model="formData[item.prop]"
            :disabled="item.ReadOnly"
            placeholder="请选择"
          >
            <el-option
              v-for="option in item.options"
              :key="option.value"
              :label="option.label"
              :value="option.value"
            ></el-option>
          </el-select>
          <el-date-picker
            v-else-if="item.InputType === 'date'"
            v-model="formData[item.prop]"
            :disabled="item.ReadOnly"
            type="date"
            placeholder="选择日期"
            @change="formatDate(item.prop, $event)"
          ></el-date-picker>
        </el-form-item>
      </div>
    </el-form>
  </template>
  
  <script lang="ts" setup>
  import { ref, watch, PropType, defineEmits } from "vue";
  import type { FormInstance } from "element-plus";
  
  interface Field {
    prop: string;
    label: string;
    width?: number;
    hide?: boolean;
    ReadOnly?: boolean;
    InputType: "text" | "select" | "date";
    IsNull?: boolean;
    Validation?: any; // 校验规则
    options?: { label: string; value: any }[];
  }
  
  const props = defineProps({
    fields: {
      type: Array as PropType<Field[]>,
      required: true,
    },
    data: {
      type: Object,
      default: () => ({}),
    },
  });
  
  const emit = defineEmits(["update:data"]);
  const formRef = ref<FormInstance | null>(null);
  const formData = ref({ ...props.data });
  
  watch(() => props.data, (newValue) => {
    formData.value = { ...newValue };
  }, { deep: true });
  
  watch(formData, (newValue) => {
    emit("update:data", newValue);
  }, { deep: true });
  
  const rules = props.fields.reduce((acc, field) => {
    acc[field.prop] = [];
    if (!field.IsNull) {
      acc[field.prop].push({
        required: true,
        message: `${field.label}不能为空`,
        trigger: "blur",
      });
    }
    if (field.Validation) {
      acc[field.prop].push(field.Validation);
    }
    return acc;
  }, {} as Record<string, any>);
  
  const formatDate = (prop: string, date: Date) => {
    if (date) {
      formData.value[prop] = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
    } else {
      formData.value[prop] = '';
    }
  };
  </script>
  
  <style scoped>
  .form {
    max-width: 600px;
    margin: 0 auto;
  }
  </style>
  

 父组件-Form组件调用

组件控件定义:

姓名:增加数据验证

性别:有下选框

const fields = ref([
  {
    prop: "name",
    label: "姓名",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "text",
    IsNull: false,
    Validation: {
      validator: nameValidator,
      trigger: "blur",
    },
  },
  {
    prop: "age",
    label: "年龄",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "text",
    IsNull: true,
  },
  {
    prop: "gender",
    label: "性别",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "select",
    options: [
      { label: "男", value: "male" },
      { label: "女", value: "female" },
    ],
    IsNull: false,
  },
  {
    prop: "birthDate",
    label: "出生日期",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "date",
    IsNull: true,
  },
]);

const handleAdd = () => {
  formData.value = {
    name: "默认姓名",
   
  };
  dialogVisible.value = true;
};

完整代码: 

<template>
  <div>
    <el-button type="primary" @click="handleAdd">新增</el-button>
    <el-button type="success" @click="handleEdit">编辑</el-button>
    <el-dialog
      v-model="dialogVisible"
      title="表单操作"
      width="50%"
      :before-close="handleClose"
    >
      <FormComponent
        ref="formComponentRef"
        :fields="fields"
        v-model:data="formData"
        @update:data="handleFormDataUpdate"
      ></FormComponent>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="handleCancel">取消</el-button>
          <el-button type="primary" @click="handleSave">保存</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import FormComponent from "@/components/FormComponent.vue";

const formComponentRef = ref(null);
const formData = ref({});
const dialogVisible = ref(false);
const nameValidator = (rule: any, value: any, callback: any) => {
  if (value && value.length < 3) {
    callback(new Error("姓名长度不能小于3"));
  } else {
    callback();
  }
};

const fields = ref([
  {
    prop: "name",
    label: "姓名",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "text",
    IsNull: false,
    Validation: {
      validator: nameValidator,
      trigger: "blur",
    },
  },
  {
    prop: "age",
    label: "年龄",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "text",
    IsNull: true,
  },
  {
    prop: "gender",
    label: "性别",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "select",
    options: [
      { label: "男", value: "male" },
      { label: "女", value: "female" },
    ],
    IsNull: false,
  },
  {
    prop: "birthDate",
    label: "出生日期",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "date",
    IsNull: true,
  },
]);

const handleAdd = () => {
  formData.value = {
    name: "默认姓名",
   
  };
  dialogVisible.value = true;
};

const handleEdit = () => {
  formData.value = {
    name: "张三",
    age: "25",
    gender: "male",
    birthDate: "2025-04-09",
  };
  dialogVisible.value = true;
};

const handleSave = () => {
  (formComponentRef.value as any).$refs.formRef.validate((valid: boolean, errors: any) => {
    if (valid) {
      // 获取最新的表单数据
      const latestFormData = { ...formData.value };
      console.log("保存的数据:", latestFormData);
      alert("保存成功");
      dialogVisible.value = false;
    } else {
      // 获取具体的错误信息
      const errorMessages = Object.values(errors).map((error: any) => error.message).join("\n");
      alert(`表单校验失败:\n${errorMessages}`);
    }
  });
};

const handleCancel = () => {
  dialogVisible.value = false;
};

const handleClose = (done: () => void) => {
  dialogVisible.value = false;
  done();
};

const handleFormDataUpdate = (newData: any) => {
  formData.value = { ...newData };
};
</script>

说明:该功能演示按钮未引用自定义组件

功能说明:
• 新增:新增时自动填充默认值,其余字段保持空白。

• 编辑:加载全部数据进行编辑。

• 保存:进行数据验证(包括长度检查和非空校验),若验证通过则保存数据。

• 取消:关闭当前窗口。

演示效果

模拟测试

保存时,获取资料正确。

验证测试已通过,此处暂不提供截图

父组件-Form组件多界面

在原有功能基础上,新增一个“新增明细”按钮,点击后可实现明细数据的新增操作。

<template>
  <div>
    <el-button type="primary" @click="handleAdd">新增</el-button>
    <el-button type="success" @click="handleEdit">编辑</el-button>
    <el-button type="info" @click="handleDetailAdd">明细新增</el-button>
    <el-dialog
      v-model="dialogVisible"
      title="表单操作"
      width="50%"
      :before-close="handleClose"
    >
      <FormComponent
        ref="formComponentRef"
        :fields="fields"
        v-model:data="formData"
        @update:data="handleFormDataUpdate"
      ></FormComponent>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="handleCancel">取消</el-button>
          <el-button type="primary" @click="handleSave">保存</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog
      v-model="detailDialogVisible"
      title="明细新增"
      width="50%"
      :before-close="handleDetailClose"
    >
      <FormComponent
        ref="detailFormComponentRef"
        :fields="detailFields"
        v-model:data="detailFormData"
        @update:data="handleDetailFormDataUpdate"
      ></FormComponent>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="handleDetailCancel">取消</el-button>
          <el-button type="primary" @click="handleDetailSave">保存</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import FormComponent from "@/components/ActionFormCont.vue";

const formComponentRef = ref(null);
const detailFormComponentRef = ref(null);
const formData = ref({});
const detailFormData = ref({});
const dialogVisible = ref(false);
const detailDialogVisible = ref(false);

// 主表单的校验函数
const nameValidator = (rule: any, value: any, callback: any) => {
  if (value && value.length < 3) {
    callback(new Error("姓名长度不能小于3"));
  } else {
    callback();
  }
};

// 明细表单的校验函数
const detailNameValidator = (rule: any, value: any, callback: any) => {
  if (value && value.length < 3) {
    callback(new Error("明细名称长度不能小于3"));
  } else {
    callback();
  }
};

const fields = ref([
  {
    prop: "name",
    label: "姓名",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "text",
    IsNull: false,
    Validation: {
      validator: nameValidator,
      trigger: "blur",
    },
  },
  {
    prop: "age",
    label: "年龄",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "text",
    IsNull: true,
  },
  {
    prop: "gender",
    label: "性别",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "select",
    options: [
      { label: "男", value: "male" },
      { label: "女", value: "female" },
    ],
    IsNull: false,
  },
  {
    prop: "birthDate",
    label: "出生日期",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "date",
    IsNull: true,
  },
]);

const detailFields = ref([
  {
    prop: "detailName",
    label: "明细名称",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "text",
    IsNull: false,
    Validation: {
      validator: detailNameValidator,
      trigger: "blur",
    },
  },
  {
    prop: "detailAmount",
    label: "明细金额",
    width: 200,
    hide: false,
    ReadOnly: false,
    InputType: "text",
    IsNull: true,
  },
]);

const handleAdd = () => {
  formData.value = {
    name: "默认姓名",
    age: "",
    gender: "",
    birthDate: "",
  };
  dialogVisible.value = true;
};

const handleEdit = () => {
  formData.value = {
    name: "张三",
    age: "25",
    gender: "male",
    birthDate: "2025-04-09",
  };
  dialogVisible.value = true;
};

const handleDetailAdd = () => {
  detailFormData.value = {
    detailName: "默认明细名称",
    detailAmount: "100",
  };
  detailDialogVisible.value = true;
};

const handleSave = () => {
  (formComponentRef.value as any).$refs.formRef.validate((valid: boolean, errors: any) => {
    if (valid) {
      const latestFormData = { ...formData.value };
      console.log("保存的数据:", latestFormData);
      alert("保存成功");
      dialogVisible.value = false;
    } else {
      const errorMessages = Object.values(errors).map((error: any) => error.message).join("\n");
      alert(`表单校验失败:\n${errorMessages}`);
    }
  });
};

const handleDetailSave = () => {
  (detailFormComponentRef.value as any).$refs.formRef.validate((valid: boolean, errors: any) => {
    if (valid) {
      const latestDetailFormData = { ...detailFormData.value };
      console.log("明细保存的数据:", latestDetailFormData);
      alert("明细保存成功");
      detailDialogVisible.value = false;
    } else {
      const errorMessages = Object.values(errors).map((error: any) => error.message).join("\n");
      alert(`明细表单校验失败:\n${errorMessages}`);
    }
  });
};

const handleCancel = () => {
  dialogVisible.value = false;
};

const handleDetailCancel = () => {
  detailDialogVisible.value = false;
};

const handleClose = (done: () => void) => {
  dialogVisible.value = false;
  done();
};

const handleDetailClose = (done: () => void) => {
  detailDialogVisible.value = false;
  done();
};

const handleFormDataUpdate = (newData: any) => {
  formData.value = { ...newData };
};

const handleDetailFormDataUpdate = (newData: any) => {
  detailFormData.value = { ...newData };
};
</script>

多界面演示

 


后续

  下一章讲解国际化

代码下载 

GitCode - 全球开发者的开源社区,开源代码托管平台GitCode是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。https://gitcode.com/sen_shan/ssVue3Demo.gitss.vue3.demo: 本项目以 Vue3、Vite、TypeScript、Element Plus 为核心框架,结合 Vue Router、Element Plus Icons、Less、Axios、Pinia、Mock 等技术,初始的构建登录,主界面,权限控制,按钮组件,table组件,form组件等小模型,可以随意搭建web管理系统https://gitee.com/sen_shan/ssVue3Demo.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值