vue3【组件封装】超级表单 S-form.vue

最终效果


代码实现

components/SUI/S-form.vue

c 复制代码
<script lang="ts" setup>
import type { FormInstance } from "element-plus";

// 使用索引签名定义对象类型
type GenericObject = {
  [key: string]: any;
};

const props = defineProps<{
  Model?: GenericObject;
  disabled?: boolean;
  hideHandle?: boolean;
  saveAPI?: string;
  saveOK?: () => void;
  local_save?: (formData: GenericObject) => void;
  cancel?: () => void;
  colNum?: number;
  action?: string;
  PageConfig?: GenericObject;
}>();

const formData = defineModel<GenericObject>({});

const formItemConfigList = computed(() => {
  let result: any = [];
  if (props.Model) {
    for (const [key, value] of Object.entries(props.Model)) {
      let temp_value = JSON.parse(JSON.stringify(value));
      // 解析 -- 必填
      if ("require" in temp_value && temp_value.require) {
        if (
          "formRules" in temp_value &&
          temp_value.formRules &&
          Array.isArray(temp_value.formRules)
        ) {
          temp_value.formRules.push({
            required: true,
            message: "请输入" + temp_value.label,
          });
        } else {
          temp_value.formRules = [
            {
              required: true,
              message: "请输入" + temp_value.label,
            },
          ];
        }
      }

      result.push({
        prop: key,
        ...(temp_value as object),
      });
    }
  }
  return result;
});

const group_formItemConfigList_Obj = computed(() => {
  let result: any = {};
  if (props.PageConfig && props.PageConfig.formGrouped) {
    let final_formItemConfigList: any[] = [];
    formItemConfigList.value.forEach((formItemConfig: any) => {
      if (
        !(
          formItemConfig.formHide &&
          (formItemConfig.formHide === "all" ||
            (Array.isArray(formItemConfig.formHide) &&
              formItemConfig.formHide.includes(props.action)))
        )
      ) {
        final_formItemConfigList.push(formItemConfig);
      }
    });
    result = groupBy(
      final_formItemConfigList,
      "group",
      props.PageConfig.groupName_default
    );
  }
  return result;
});

const activeGroups: string[] = Object.keys(group_formItemConfigList_Obj.value);

const pageData = reactive<{
  localFomrData: GenericObject;
}>({
  localFomrData: formData.value || {},
});

const { localFomrData } = toRefs(pageData);

const formRef = ref<FormInstance>();

const callbackMessage = ref({
  show: false,
  valid: true,
  content: "",
});

// 按钮 -- 保存
const submitForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.validate(async (valid) => {
    if (valid) {
      if (props.local_save) {
        props.local_save(pageData.localFomrData);
        return;
      }

      try {
        await $fetch(`/api${props.saveAPI}`, {
          body: pageData.localFomrData,
          method: "POST",
        });
        callbackMessage.value = {
          show: true,
          valid: true,
          content: "操作成功",
        };
        if (props.saveOK) {
          props.saveOK();
        }
      } catch (e: any) {
        callbackMessage.value = {
          show: true,
          valid: false,
          content: e.data.message,
        };
      }
    } else {
      console.log("提交报错!");
    }
  });
};

// 将方法暴露给父组件
defineExpose({
  submitForm,
  localFomrData,
  formRef,
});
</script>
<template>
  <div class="relative mt-10">
    <el-scrollbar max-height="460px" class="px10">
      <el-form
        ref="formRef"
        :inline="true"
        :model="localFomrData"
        :disabled="props.disabled"
      >
        <el-collapse
          v-if="props.PageConfig && props.PageConfig.formGrouped"
          v-model="activeGroups"
        >
          <el-collapse-item
            :name="group"
            v-for="(formItemConfigList, group) in group_formItemConfigList_Obj"
            :key="group"
          >
            <template #title>
              <div class="font-bold text-14px">
                {{ group }}
              </div>
            </template>
            <S-formRow
              :formItemConfigList="formItemConfigList"
              :colNum="props.colNum"
              :action="props.action"
              v-model="localFomrData"
              :disabled="props.disabled"
            >
              <template
                v-for="formItemConfig in formItemConfigList.filter(
              (item:any) => item.type === 'custom'
            )"
                :key="formItemConfig.prop"
                #[formItemConfig.prop]
              >
                <slot :name="formItemConfig.prop" />
              </template>
            </S-formRow>
          </el-collapse-item>
        </el-collapse>
        <S-formRow
          v-else
          :formItemConfigList="formItemConfigList"
          :colNum="props.colNum"
          :action="props.action"
          :disabled="props.disabled"
          v-model="localFomrData"
        >
          <template
            v-for="formItemConfig in formItemConfigList.filter(
              (item:any) => item.type === 'custom'
            )"
            :key="formItemConfig.prop"
            #[formItemConfig.prop]
          >
            <slot :name="formItemConfig.prop" />
          </template>
        </S-formRow>
      </el-form>
    </el-scrollbar>
    <div class="flex justify-center p4" v-if="!props.disabled && !hideHandle">
      <el-button @click="props.cancel">取消</el-button>
      <el-button type="primary" @click="submitForm(formRef)">保存</el-button>
    </div>
    <S-msgWin :msg="callbackMessage" />
  </div>
</template>

components/SUI/S-formRow.vue

c 复制代码
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { defineAsyncComponent } from "vue";

const props = defineProps<{
  formItemConfigList: any;
  colNum?: number;
  action?: string;
  disabled?: boolean;
}>();

const localFomrData = defineModel<any>({});

// 标记客户端环境
const isClient = ref(false);

// 动态导入组件,禁用SSR
const AvatarCropper = defineAsyncComponent({
  loader: () => import("~/components/SUI/S-avatar.vue"),
  suspensible: false, // 关键:禁止在服务端渲染该组件,使用 suspensible 替代 ssr
});

onMounted(() => {
  isClient.value = true; // 确保在客户端挂载后才显示组件
});
</script>
<template>
  <el-row :sapn="24">
    <template v-for="formItemConfig in formItemConfigList">
      <el-col
        v-if="
          !(
            formItemConfig.formHide &&
            (formItemConfig.formHide === 'all' ||
              (Array.isArray(formItemConfig.formHide) &&
                formItemConfig.formHide.includes(props.action)))
          )
        "
        :span="formItemConfig.span || (props.colNum && 24 / props.colNum) || 12"
        :key="formItemConfig.prop"
      >
        <el-form-item
          :label="formItemConfig.label"
          :label-width="160"
          :rules="formItemConfig.formRules"
          :prop="formItemConfig.prop"
        >
          <el-date-picker
            v-if="formItemConfig.type === 'date'"
            v-model="localFomrData[formItemConfig.prop as string]"
            type="date"
            placeholder="选择日期"
            v-bind="formItemConfig"
          />
          <el-input-number
            v-else-if="formItemConfig.type === 'number'"
            v-model="localFomrData[formItemConfig.prop as string]"
            v-bind="formItemConfig"
            controls-position="right"
            class="w-220px!"
          >
            <template #suffix>
              <span>{{ formItemConfig.unit }}</span>
            </template>
          </el-input-number>
          <el-switch
            v-else-if="formItemConfig.type === 'switch'"
            v-model="localFomrData[formItemConfig.prop as string]"
            v-bind="formItemConfig"
            class="w-220px!"
          />
          <el-select
            v-else-if="formItemConfig.type === 'select'"
            v-model="localFomrData[formItemConfig.prop as string]"
            filterable
            clearable
            :multiple="formItemConfig.multSelect"
            class="w-220px!"
            placeholder=""
          >
            <el-option
              v-for="item in formItemConfig.options || []"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
          <el-tree-select
            v-else-if="formItemConfig.type === 'treeSelect'"
            v-model="localFomrData[formItemConfig.prop as string]"
            :data="formItemConfig.treeData"
            :render-after-expand="false"
            class="w-220px!"
            filterable
            clearable
            :node-key="formItemConfig.key"
            default-expand-all
          />

          <AvatarCropper
            v-else-if="isClient && formItemConfig.type === 'avatar'"
            :disabled="
              (formItemConfig.formDisable &&
                formItemConfig.formDisable.includes(props.action)) ||
              props.disabled
            "
            v-model="localFomrData[formItemConfig.prop as string]"
          />
          <template v-else-if="formItemConfig.type === 'custom'">
            <slot :name="formItemConfig.prop" />
          </template>
          <el-input
            v-else
            v-model="localFomrData[formItemConfig.prop as string]"
            v-bind="formItemConfig"
            class="w-220px!"
            :type="formItemConfig.type || 'text'"
            :disabled="
              formItemConfig.formDisable &&
              formItemConfig.formDisable.includes(props.action)
            "
            :autosize="formItemConfig.autosize || { minRows: 2, maxRows: 4 }"
            show-word-limit
          />
        </el-form-item>
      </el-col>
    </template>
  </el-row>
</template>

相关组件

头像 S-avatar.vue

https://blog.csdn.net/weixin_41192489/article/details/149716009

消息弹窗 S-msgWin.vue

https://blog.csdn.net/weixin_41192489/article/details/149717948

相关推荐
清岚_lxn6 天前
vue3 antd modal对话框里的前端html导出成pdf并下载
pdf·vue3·html2canvas·jspdf
伍哥的传说10 天前
Vue3 Anime.js超级炫酷的网页动画库详解
开发语言·前端·javascript·vue.js·vue·ecmascript·vue3
科技D人生10 天前
Vue.js 学习总结(18)—— Vue 3.6.0-alpha1:性能“核弹“来袭,你的应用准备好“起飞“了吗?!
前端·vue.js·vue3·vue 3.6·vue3.6
伍哥的传说12 天前
Webpack5 新特性与详细配置指南
webpack·前端框架·vue·vue3·react·webpack5·前端构建
JosieBook12 天前
【前端】Vue3 前端项目实现动态显示当前系统时间
前端·vue3·系统时间
摆烂式编程13 天前
APP端定位实现(uniapp Vue3)(腾讯地图)
uni-app·app·vue3·定位·腾讯
一只小阿乐13 天前
前端vue3 H5实现 静态页面使用本地json 并且需要上下滑动 可以切换tabs 栏
前端·json·vue3·h5开发
知识分享小能手14 天前
Vue3 学习教程,从入门到精通,Vue 3 表单控件绑定详解与案例(7)
前端·javascript·vue.js·学习·前端框架·vue3·anti-design-vue