Skip to content

效果预览

自定义操作按钮(h函数)

成员信息
成员姓名:
角色类型:
工作职责:
头像上传:

示例代码

vue
<template>
  <h3>自定义操作按钮(h函数)</h3>
  <FastForm>
    <template #points2="{ formValue, nestedKey, nestedProp, modelValue }">
      <el-input placeholder="请输入" v-model="formValue[nestedProp][nestedKey].points2" />
    </template>
  </FastForm>

  <el-space>
    <el-button @click="submit" type="primary">提交</el-button>
    <el-button @click="reset">重置</el-button>
  </el-space>
</template>

<script lang="ts" setup>
import { useForm } from "element-plus-fast-form";
import { formConfig, attrs } from "./config";
import { ElMessage } from "element-plus";

const { FastForm, formValue, formRef } = useForm({
  ...attrs,
  formConfig,
});

const submit = () => {
  if (formRef.value) {
    formRef.value.validate((valid: boolean) => {
      if (valid) {
        ElMessage.warning("查看控制台");
        console.log(formValue);
      }
    });
  }
};
const reset = () => {
  if (formRef.value) {
    formRef.value.resetFields();
  }
};
</script>
ts
import { defineAsyncComponent, h, resolveComponent } from "vue";
import type { IOperateButtonProps } from "element-plus-fast-form";

// 使用 h 函数定义添加按钮(替代组件式的 AddButton.vue)
function renderAddButton(props: IOperateButtonProps) {
  return h(resolveComponent("el-button"), {
    type: "primary",
    onClick: props.onClick,
  }, "添加");
}

// 使用 h 函数定义删除按钮(替代组件式的 DeleteButton.vue):仅当 length > 1 时渲染
function renderDeleteButton(props: IOperateButtonProps) {
  if (props.length !== undefined && props.length <= 1) {
    return null;
  }
  return h(resolveComponent("el-button"), {
    type: "danger",
    onClick: props.onClick,
  }, "删除");
}

// 表单样式与操作按钮配置(操作按钮通过 h 函数实现)
export const attrs = {
  colProps: {
    span: 12,
  },
  rowProps: {
    gutter: 24,
  },
  formProps: {
    "label-position": "right",
    "label-suffix": ":",
    "label-width": "160",
    model: {
      "el-input": "前端管理系统开发",
      children: [
        {
          "el-input": "张三",
          "el-radio-group": "leader",
          "points2": "负责前端架构设计",
        },
      ],
    },
  },
  showOperate: true,
  operatePosition: "tr",
  showOperateAdd: true,
  showOperateDelete: true,
  operateButtons: {
    addButton: renderAddButton,
    deleteButton: renderDeleteButton,
  },
};

// 表单配置(与 formlistButton 保持一致,便于对比)
export const formConfig = [
  {
    formItemProps: {
      prop: "children",
      label: "项目成员",
    },
    children: [
      [
        {
          component: "span",
          formItemProps: {},
          componentProps: {
            style: {
              fontSize: "18px",
              marginLeft: "-90px",
            },
          },
          defaultValue: "成员信息",
          colProps: {
            span: 24,
          },
        },
        {
          component: "el-input",
          formItemProps: {
            prop: "el-input",
            label: "成员姓名",
            rules: [
              {
                required: true,
                message: "请输入成员姓名",
              },
            ],
          },
          componentProps: {
            placeholder: "请输入成员姓名",
          },
        },
        {
          component: "el-radio-group",
          formItemProps: {
            prop: "el-radio-group",
            label: "角色类型",
          },
          componentProps: {
            placeholder: "请选择角色",
            options: [
              { label: "项目经理", value: "leader" },
              { label: "开发工程师", value: "developer" },
            ],
          },
        },
        {
          component: "slot",
          formItemProps: {
            prop: "points2",
            label: "工作职责",
            rules: [
              {
                required: true,
                message: "请输入工作职责",
              },
            ],
          },
        },
        {
          colProps: {
            span: 24,
          },
          component: defineAsyncComponent<any>(
            () => import("@/components/Avatar-upload/index.vue")
          ),
          formItemProps: {
            prop: "avatar",
            label: "头像上传",
          },
          componentProps: {},
        },
      ],
    ],
  },
];
vue
<template>
  <el-upload
    class="avatar-uploader"
    action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
    :show-file-list="false"
    :on-success="handleAvatarSuccess"
    :before-upload="beforeAvatarUpload"
  >
    <img v-if="imageUrl" :src="imageUrl" class="avatar" />
    <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
  </el-upload>
</template>

<script lang="ts" setup>
import { ref, defineProps, watch, defineEmits } from "vue";
import { ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";

import type { UploadProps } from "element-plus";
const imageUrl = ref("");

const props = defineProps({
  formValue: { // 表单数据
    type: Object,
  },
  modelValue: { // 当前组件数据
    type: String,
    default: "",
  },
  prop: {
    type: String,
  },
});

watch(
  () => props.modelValue,
  () => {
    // 重置表单时,赋值
    imageUrl.value = props.modelValue
  }
);

const emits = defineEmits(["update:modelValue"]);

const getImageInfo = (file: any): Promise<string> => {
  let fileReader = new FileReader();
  fileReader.readAsDataURL(file);
  return new Promise((resolve) => {
    fileReader.onload = function (e) {
      let base64 = this.result;
      resolve(base64 as string);
    };
  });
};


const handleAvatarSuccess: UploadProps["onSuccess"] = (
  response,
  uploadFile
) => {
  imageUrl.value = URL.createObjectURL(uploadFile.raw!);
};

const beforeAvatarUpload: UploadProps["beforeUpload"] = async (rawFile) => {
  if (!["image/jpeg", "image/jpg", "image/png"].includes(rawFile.type)) {
    ElMessage.error("请传图片");
    return false;
  } else if (rawFile.size / 1024 / 1024 > 2) {
    ElMessage.error("Avatar picture size can not exceed 2MB!");
    return false;
  }

  // mock start
  const filedata: string = await getImageInfo(rawFile);
  imageUrl.value = filedata;
  emits("update:modelValue", filedata);
  // mock end

  return true;
};
</script>

<style scoped>
.avatar-uploader .avatar {
  width: 80px;
  height: 80px;
  display: block;
}
</style>

<style>
.avatar-uploader .el-upload {
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
  border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 80px;
  height: 80px;
  text-align: center;
}
</style>