一套代码实现表单新增,编辑和预览

省流

  • 策略模式渲染(需要二次封装)
  • 参数注入,状态下沉
  • 不能处理模板差异大的场景

传统方案的痛点

在前端开发中,我们经常遇到这样的场景:同一个页面需要支持新增、编辑和预览三种不同的状态。 在没有统一封装的情况下,开发者通常采用两种思路:

方案一:多套代码分离

为每种状态创建独立的页面组件

问题:

  • 代码重复严重:表单字段、布局逻辑大量重复
  • 维护成本高:字段变更需要同时修改多个文件
  • 一致性难保证:容易出现页面间展示不一致的问题

方案二:单组件参数控制

在一个组件内通过参数控制不同状态:

问题:

  • 组件臃肿:大量条件判断使组件变得复杂难读
  • 可维护性差:字段增多时条件判断呈指数级增长

核心解决思路

我们的解决方案基于状态下沉组件自治的设计理念:

1. 状态管理下沉到子组件

不再在父组件中集中处理所有的状态判断逻辑,而是将页面模式通过依赖注入的方式传递给子组件,让每个子组件根据当前模式自主决定如何渲染和交互。

2. 组件职责分离

  • 容器组件:负责提供全局状态(模式)和通用逻辑(数据加载、提交等)
  • 表单组件:专注于自身的渲染逻辑,根据注入的模式状态自动适配
  • 业务页面:只需关注表单结构,无需处理状态管理细节

3. 三种模式抽象

将页面状态抽象为三种模式:

  • add:新增模式,显示空白可编辑表单
  • edit:编辑模式,显示预填充的可编辑表单
  • view:预览模式,显示只读内容

具体实现方案

1. 基础表单组件封装

以输入框为例,创建一个表单组件my-input:

vue 复制代码
<template>
  <!-- 预览状态默认展示span -->
  <span v-if="mode === 'view'">
    {{ $attrs.modelValue }}
  </span>
  <el-input v-else v-bind="$attrs" />
</template>
<script setup>
// input没有内部逻辑,封装为了处理预览状态
import { inject } from 'vue';
const mode = inject('mode') || 'add';
</script>

2. 页面容器

二级页面逻辑统一,业务处理放在模板中;同时页面不作为组件,为了方便处理如按钮文案不同这类的业务场景; 依靠useAddPage来复用逻辑。

如果新增功能是个弹窗,只要把这个组件换成dialog就可以了,这里不多展示

vue 复制代码
<template>
  <div class="common-layout">
    <el-card>
      <el-container>
        <el-main :view-mode="mode">
          <tempForm
            ref="tempFormRef"
            @finish="closeAndRefresh"
            :mode="mode"
            :extra="extra" />
        </el-main>
        <el-footer>
          <el-row justify="end">
            <template v-if="mode === 'view'">
              <el-button type="primary" @click="close">
                确定
              </el-button>
            </template>
            <template v-else>
              <el-button @click="close">取消</el-button>
              <el-button type="primary" @click="submit">
                提交
              </el-button>
            </template>
          </el-row>
        </el-footer>
      </el-container>
    </el-card>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import tempForm from './tempForm.vue';
import useAddPage from '@/hooks/useAddPage';

const tempFormRef = ref(null);
const {
  extra,
  mode,
  submit,
  close,
  closeAndRefresh
} = useAddPage(tempFormRef);
</script>

useAddPage,核心是接收路由参数和provide注入mode属性

javascript 复制代码
import { ref, provide } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from 'vuex';
import { closeTab, closeAndRefreshTab } from '@/utils/route';

/**
 * @description: 页面级新增的处理
 */
export default function (tempFormRef) {
  const route = useRoute();
  const query = route.query;
  const mode = query.mode;
  // 提供 mode 给组件处理预览
  provide('mode', mode);
  const extra = ref({
    ...query
  });
  const title = query.title;
  const store = useStore();
  // 根据路由参数设置页面标题
  if (title) {
    store.dispatch('tab/changeTitle', {
      name: route.name,
      title
    });
  }
  const submit = () => {
    tempFormRef.value.submit();
  };
  const close = () => {
    closeTab(`${ route.name }-${ route.meta.id }-{}-{}`);
  };
  const closeAndRefresh = () => {
    closeAndRefreshTab(`${ route.name }-${ route.meta.id }-{}-{}`);
  };
  return {
    extra,
    mode,
    submit,
    close,
    closeAndRefresh
  };
}

3. 业务页面使用

在具体的业务页面中,只需要关注表单结构,无需重复处理状态逻辑; 从props处理透传的mode和extra参数,保留submit函数供外部调用

vue 复制代码
<template>
  <!-- 主体表单 -->
  <div class="temp-form-normal">
    <el-form
      :model="formData"
      ref="formRef"
      v-loading="loading"
      label-width="100px"
      :inline="true"
      :rules="rules">
      <h3>| 基础信息</h3>
      <el-form-item label="A" prop="A">
        <my-input v-model="formData.A" />
      </el-form-item>
      <el-form-item label="B" prop="B">
        <my-select v-model="formData.B" />
      </el-form-item>
    </el-form>
  </div>
</template>
<script setup>
import { ref, reactive, defineExpose } from 'vue';

const props = defineProps({
  // enum: add, edit, copy
  mode: {
    type: String,
    default: 'add'
  },
  extra: {
    type: Object,
    default: () => ({})
  }
});
const rules = {};
const dataForm = reactive({
});
const formData = reactive({
});
// 新增修改弹出框提交
const formRef = ref(null);
const validateForm = () => {
  return new Promise((resolve, reject) => {
    formRef.value.validate((valid) => {
      if (!valid) {
        ElMessage.warning('请填写完整信息');
        resolve(false);
      } else {
        resolve(true);
      }
    });
  });
};
// 处理差异化参数
const handleDifferent = (params) => {
  if (props.mode === 'edit') {
    params.id = props.extra.id;
  }
};

const addConfirm = (params) => {
  addRdAPI(params).then((res) => {
    close();
  });
};
const editConfirm = (params) => {
  updatedRdAPI(params).then((res) => {
    close();
  });
};
// 外部提交
const submit = async () => {
  const valid = await validateForm();
  if (!valid) {
    return;
  }
  const params = {
    ...formData
  };
  // 处理差异化参数
  handleDifferent(params);
  if (props.mode === 'add' || props.mode === 'turn') {
    addConfirm(params);
  } else {
    editConfirm(params);
  }
};

defineExpose({
  submit
});
const emit = defineEmits(['finish']);
// 外部关闭
const close = () => {
  emit('finish');
};
const getDetail = () => {
  getDetailApi({ id: props.extra.id }).then((res) => {
  });
};
const addInit = () => {
};
const editInit = () => {
};
// 初始化
const init = () => {
  if (props.mode === 'add') {
    addInit();
  } else if (props.mode === 'turn') {
    //
  } else {
    editInit();
  }
};
init();
</script>

真实目标

这套流程主要有几方面的考虑:

  1. 快速的搭建简单的增删改查页面(方便用AI,方便复制粘贴)
  2. 减少状态判断的心智负担,以及减少template中大段的if代码
  3. 规范常规流程代码,预留每个阶段的处理函数,控制开发成员自由发挥的空间

其余的细节:

查看时,可能需要一些样式处理,比如去掉表单必填属性的星号,这块考虑用属性选择器去处理,如:view-mode="mode"

less 复制代码
[view-mode='view'] {
  .el-form-item__label:before {
    display: none;
  }
}

总结

通过将状态管理责任下沉到子组件的设计方案,既避免了多套代码的重复问题,又解决了单组件参数控制的复杂性。只针对三种状态下,模板差异不大的场景。好处在于写一套代码可以报3次工时。

相关推荐
Light607 小时前
架构矩阵实战:业务边界×技术分层的双螺旋落地法
架构·业务模块·ai 原生·架构矩阵·技术分层·契约治理
喂完待续11 小时前
【序列晋升】28 云原生时代的消息驱动架构 Spring Cloud Stream的未来可能性
spring cloud·微服务·云原生·重构·架构·big data·序列晋升
夫子39611 小时前
OnlyOffice的高可用方案如何做
运维·架构
薛定谔的算法12 小时前
手写React:从Dideact理解前端框架的核心原理
前端·react.js·架构
掘金-我是哪吒12 小时前
分布式微服务系统架构第170集:Kafka消费者并发-多节点消费-可扩展性
分布式·微服务·架构·kafka·系统架构
胡耀超15 小时前
大模型架构演进全景:从Transformer到下一代智能系统的技术路径(MoE、Mamba/SSM、混合架构)
人工智能·深度学习·ai·架构·大模型·transformer·技术趋势分析
小马哥编程1 天前
【软考架构】第七章 系统架构设计基础知识-7.2基于架构的软件开发方法:Architecture-Based Software Design,ABSD
架构·系统架构
西陵1 天前
Nx带来极致的前端开发体验——任务编排
前端·javascript·架构
LQ深蹲不写BUG1 天前
微服务的保护方式以及Sentinel详解
微服务·云原生·架构