当前位置: 首页 > news >正文

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

最终效果

在这里插入图片描述
在这里插入图片描述

代码实现

components/SUI/S-form.vue

<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-formref="formRef":inline="true":model="localFomrData":disabled="props.disabled"><el-collapsev-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"><templatev-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-formRowv-else:formItemConfigList="formItemConfigList":colNum="props.colNum":action="props.action":disabled="props.disabled"v-model="localFomrData"><templatev-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

<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-colv-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-pickerv-if="formItemConfig.type === 'date'"v-model="localFomrData[formItemConfig.prop as string]"type="date"placeholder="选择日期"v-bind="formItemConfig"/><el-input-numberv-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-switchv-else-if="formItemConfig.type === 'switch'"v-model="localFomrData[formItemConfig.prop as string]"v-bind="formItemConfig"class="w-220px!"/><el-selectv-else-if="formItemConfig.type === 'select'"v-model="localFomrData[formItemConfig.prop as string]"filterableclearable:multiple="formItemConfig.multSelect"class="w-220px!"placeholder=""><el-optionv-for="item in formItemConfig.options || []":key="item.value":label="item.label":value="item.value"/></el-select><el-tree-selectv-else-if="formItemConfig.type === 'treeSelect'"v-model="localFomrData[formItemConfig.prop as string]":data="formItemConfig.treeData":render-after-expand="false"class="w-220px!"filterableclearable:node-key="formItemConfig.key"default-expand-all/><AvatarCropperv-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-inputv-elsev-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

http://www.xdnf.cn/news/1203877.html

相关文章:

  • django ManyToManyField 如何添加数据
  • 多光谱相机助力第四次全国农业普查-农业用地调查
  • JAVA后端开发——“全量同步”和“增量同步”
  • 基于百度 iframe 框架与语音解析服务的数字人交互系统实现
  • Docker搭建Hadoop集群
  • Apache Ignite 的 JDBC Client Driver(JDBC 客户端驱动)
  • 基于电动自行车控制器设计方案
  • PyTorch中flatten()函数详解以及与view()和 reshape()的对比和实战代码示例
  • dapp前端⾯试题
  • 【QT搭建opencv环境】
  • <RT1176系列11>DMAMUX解读
  • Spring AI 1.0 提供简单的 AI 系统和服务
  • TS面试题
  • 分布式IO详解:2025年分布式无线远程IO采集控制方案选型指南
  • simple-mock-proxy,自动拾取后端接口数据,生成本地mock接口与数据
  • idea启动java应用报错
  • keepalived原理及实战部署
  • vue怎么实现导入excel表功能
  • 最新!Polkadot 更新 2025 路线图
  • C++-关于协程的一些思考
  • ERC20 和 XCM Precompile|详解背后技术逻辑
  • 【Kotlin】如何实现静态方法?(单例类、伴生对象、@JvmStatic)
  • Android中应用进程中Binder创建机制
  • VUE2 学习笔记11 脚手架
  • 从0到500账号管理:亚矩阵云手机多开组队与虚拟定位实战指南
  • 数据结构之顺序表链表栈
  • 分享一个脚本,从mysql导出数据csv到hdfs临时目录
  • CFIHL: 水培生菜的多种叶绿素 a 荧光瞬态图像数据集
  • 雷达系统设计学习:自制6GHz FMCW Radar
  • 深入解析 Spring 获取 XML 验证模式的过程