SpringBoot3+Vue3 低代码业务表单:从拖拽设计、独立建表到审批与数据查询

SpringBoot3+Vue3 低代码业务表单:从拖拽设计、独立建表到审批与数据查询

🌐 文档地址 :https://ruoyioffice.com

👇 文章底部获取源码和演示地址 👇

💬 :17156169080(获取产品咨询)

采购部门让你做一张申请表,拖几个控件很快就能完成。一个月后,他们开始追问:能按项目统计预算吗?能关联采购订单吗?增加"交付日期"后,以前的申请还看得懂吗?低代码真正进入业务,往往不是从拖拽完成开始,而是从这些问题开始。

▲ 设计器负责表达输入,模型负责管理定义,独立表负责保存业务事实。三者分开,才有演进空间。

一、先把目标说清:我们要的不是另一张电子表格

业务表单是结构化信息的入口。

它既可能只是一次审批所需的输入,也可能成为后续采购、核算和统计的业务依据。

这两种用途看起来都在"填表",但对数据的要求完全不同。

如果只是申请参加一次活动,审批结束后查阅一下原始内容,流程变量可能已经够用。

如果是采购申请,后面需要逐行关联商品、汇总金额、核对到货情况,表单数据就不能只被当成审批引擎的附属信息。

采购负责人关心"这个项目到底申请了多少预算",不是"有哪些流程实例的 JSON 包含这个项目名称"。

开发人员需要先理解这种差别,再决定存储方式。

RuoYi Office 的流程表单提供了设计、存储方式选择、独立表模型管理和流程绑定等能力。

本文沿着一张采购申请的业务需求,解释这些能力如何组合。

截图使用本地已有的出差申请表单,展示同一套设计器和独立表能力;采购金额与明细是贯穿文章的说明案例,不伪装成截图中的真实单据。

使用者 真正需要的能力 仅有拖拽页面还缺什么
业务配置人员 调整字段、明细与填写规则 变更影响和发布边界
申请人与审批人 填写、查看、按节点处理 单据身份与审批状态绑定
管理人员 按项目、部门、期间查询 稳定字段与可控查询口径
系统开发人员 接入采购、打印和其他模块 数据契约与版本兼容

本文不把低代码描述为"完全不需要开发"。

更合理的目标是:重复的数据采集与基础审批可以配置完成,复杂计算、跨系统一致性和访问治理仍保留明确的工程入口。

1.1 一张采购申请会经历什么

假设申请人要购买三种办公设备,每一行都有名称、数量、单价、预计交付日期。

申请单上还有申请部门、项目、用途和总预算。

审批人可以核减数量,但必须留下最终核准结果。

后续采购订单要引用申请单,管理者还要按项目汇总已批准金额。

这时至少存在四种不同的对象:

  • 页面定义:用户看到哪些控件、怎样排版。
  • 业务模型:哪些字段进入主表,哪些进入明细表。
  • 流程定义:由谁审批、何时允许修改、使用哪版表单。
  • 业务单据:某次申请实际填写了什么、现在处于什么状态。

如果四者共用一份不断修改的 JSON,最先出现的问题通常不是性能,而是解释不清历史。

今天设计器里的字段,未必就是上个月申请人实际填写的字段。

1.2 先做数据承诺,再谈配置效率

所谓数据承诺,是系统愿意向下游保证哪些内容长期稳定。

例如申请单主键不变、明细关联有依据、审批结果可以识别、历史字段能够解释。

拖拽设计可以非常灵活,业务数据却不能无限随意。

字段标识、金额精度、日期含义和关联键一旦被其他模块使用,就已经变成了接口契约。

低代码平台的成熟程度,不只是控件数量多,而是能否把这种契约显式管理起来。

二、选择存储方式,就是选择这张表单的未来

独立数据表是为某类表单建立可识别的主表与明细表,用业务列保存单据内容。

它与流程变量、绑定已有物理表,是不同的使用策略。

方式 适合的需求 需要接受的边界
流程变量 数据主要服务于审批和回看 跨单统计、复杂关联需另做处理
独立数据表 新业务需要稳定存储和扩展查询 必须管理发布、字段与数据库适配
绑定已有物理表 已有业务数据需要接入流程 需要对齐原有主键、字段和写入责任

这不是"独立表一定优于流程变量"的排名。

如果只需要一个短期报名审批,独立建表反而可能带来不必要的维护成本。

如果已有完整采购模块,再新建一张功能相同的独立表,则可能形成两个相互竞争的申请台账。

2.1 用三个问题做判断

第一,审批结束之后,是否还有其他业务使用这些数据?

如果采购订单、合同或者费用核算还会引用它,就需要把单据身份和数据契约摆到前面。

第二,查询是否依赖稳定的业务字段?

按部门、金额区间、交付日期筛选,与搜索一段描述文字,是两种完全不同的查询。

第三,是否已经存在业务表及其责任服务?

如果有,就应评估复用或者绑定,而不是因为设计器支持建表就复制一套存储。

这三个问题能帮团队把"想要低代码"转化为更具体的架构选择。

2.2 新建、保存、发布应当分开理解

保存设计意味着保留配置草稿。

发布模型意味着把确认后的定义转成可执行的数据模型,并登记版本。

把表单绑定到流程后,还要关注流程定义是否已经使用新的版本。

这些操作的用户界面可以做得很顺畅,但语义不能混在一起。

否则业务人员以为"刚刚点了保存,所有在途申请就都升级了",开发人员却以为"只保存了设计器草稿",双方会对同一动作产生完全不同的预期。

▲ 本地已有出差申请设计器:关注独立数据表标识、已发布版本和数据表入口,而不是把截图中的表单名称当作本文采购案例。

三、把页面字段落成业务模型,先解决"它是谁"

字段标题用于阅读,字段标识用于程序访问,组件身份用于识别设计变更。

这三者不应被当成同一个值。

"预估金额"改成"申请预算",可能只是业务文案变化。

把文本输入框改成金额控件,则可能涉及类型、精度和历史值转换。

删除一个字段后,又新建一个同名字段,也未必是恢复了原来的字段。

因此,识别变更不能只比较标题。

3.1 主表和明细表回答不同问题

申请部门、申请事由、项目和申请日期,通常属于单据主表。

设备名称、数量、单价和交付日期,属于可重复的明细行。

总预算即使可以由明细计算,也仍然需要明确它是实时派生值,还是审批时确认的快照值。

这不是"多存一个字段还是少存一个字段"的小优化,而是后续解释金额的依据。

业务信息 建模位置 要明确的含义
申请部门 主表 申请时部门还是当前部门
设备数量 明细表 申请数量还是核准数量
交付日期 明细表 纯日期,不应自动加入时区偏移
核准总预算 主表或核准快照 是否允许随着明细变化重算

原有设计器中,独立表字段会经过规则解析,形成主表、明细表和列的模型描述。

发布时再与登记模型比较,计算哪些内容新增、变更、停用或移除。

对于已发布字段,前端还会围绕组件标识和字段标识做保护。

这样,用户修改控件表现时,不至于轻易让系统把它当成另一个全新字段。

3.2 一张 ER 图,分清定义和事实

▲ 表单版本记录定义,业务主表记录一次申请,明细通过父单据关联;图中的关系是逻辑关系,不代表都存在数据库外键。

这里最重要的不是表名,而是生命周期不同。

定义可以反复编辑,版本必须有确定含义,单据需要可追溯。

把三种生命周期拆开,才能讨论兼容,而不是每次改字段都碰运气。

当前源码中可以看到模型表、模型列、表单版本以及业务独立表各自承担不同职责。

版本记录包含配置、字段、模型快照和变更摘要。

实际单据也会记录 form_version。

不过,保存版本号不意味着系统已经自动解决了一切历史问题。

历史页面渲染、物理列保留、旧类型转换和打印模板兼容,仍需要分别验证。

3.3 金额、日期与关联对象不要交给默认猜测

金额字段需要确定精度和舍入规则。

数量可能允许小数,设备件数与服务工时也不一定共用精度。

同一张表单的两个数字控件,不必天然映射成同一种业务数字。

日期同样需要区分纯日期和时间点。

"预计交付日期"通常表达日历上的某一天,"提交时间"表达实际发生时刻。

前者不应因为浏览器时区不同变成前一天。

关联项目也不能只存项目名称。

名称会变化,身份引用却应稳定。

如果业务需要复原申请当时的名称,可以在关联 ID 之外保留名称快照,但应说明两个字段分别服务于什么场景。

这些规则最好在设计阶段提示,而不是等建表后再靠人工修正。

四、发布不是保存:变更计划才是安全边界

模型发布应回答四件事:准备改什么、是否允许、用户确认的是否仍是同一份变更、执行后如何记录。

当前发布服务会先取得表单级发布锁,重新计算发布计划,再检查阻断项和预览哈希。

只有这些条件满足,才执行 DDL 并登记版本。

下面是按现有方法压缩的核心摘录,保留检查顺序,省略返回对象组装:

java 复制代码
public BpmFormManagedPublishRespVO publish(
        BpmFormManagedPublishReqVO reqVO) {
    RLock lock = redissonClient.getLock(
            PUBLISH_LOCK_KEY + reqVO.getFormId());
    if (!lock.tryLock()) {
        throw exception(FORM_MANAGED_PUBLISH_LOCKED);
    }
    try {
        BpmFormDO form = validateManagedForm(reqVO.getFormId());
        var plan = computePlan(form);
        var preview = buildPreview(form, plan);
        if (preview.getBlockedCount() > 0) {
            throw exception(FORM_MANAGED_PUBLISH_BLOCKED);
        }
        if (!Objects.equals(preview.getPreviewHash(), reqVO.getPreviewHash())) {
            throw exception(FORM_MANAGED_PUBLISH_EXPIRED);
        }
        executeDdl(form.getId(), plan);
        transactionTemplate.executeWithoutResult(
                status -> applyPublish(form, plan));
        return buildResponse(plan); // 返回组装在此用示意方法代替
    } finally {
        lock.unlock();
    }
}

这段逻辑拦的不是一种风险,而是三种风险。

发布锁减少同一表单并发发布的冲突。

阻断检查拒绝不允许的模型变更。

预览哈希则避免用户确认了旧计划,系统却执行了另一份新计划。

4.1 为什么确认过预览,还要重新比较

设想两名管理员同时打开设计器。

甲看到的是增加交付日期的预览,乙随后修改了金额字段。

如果甲点击确认后直接执行"当前最新草稿",甲实际确认的内容和系统执行内容就不相同。

预览哈希的价值,是把确认对象锁定为某一份确定的变更内容。

发生变化时让用户重新确认,比悄悄帮用户合并更可信。

▲ 数据表视图用于核对表单字段对应的结构及版本信息。真实表名和列应从这里或模型接口取得,不靠控件标题猜测。

4.2 不能把 DDL 当成普通更新语句

建表、改列等 DDL 的事务语义依赖数据库。

MySQL 中的许多 DDL 会引发隐式提交,因此不能认为给发布方法加一个事务注解,就能把物理结构和模型登记一起无条件回滚。

当前代码先执行 DDL,再在事务中应用发布登记。

这使故障恢复成为必须解释的边界:如果结构已变更而登记失败,下次发布应如何识别现状,如何避免再次错误执行?

工程上可以进一步考虑发布执行日志、变更步骤状态、执行后结构复核和受控恢复入口。

这些是强化建议,不把它们描述成现有服务已经提供的一整套迁移平台。

4.3 删除字段,不应默认等于销毁历史

对于已经进入业务的字段,停用与物理删除是不同动作。

停用意味着新单不再使用,但历史解释可能仍需要它。

物理删除意味着数据本身不再存在,恢复成本和审批要求都不一样。

源码存在停用登记和允许移除的分支,说明平台不是简单地把设计器里删掉的东西全部丢弃。

具体哪一种变更被允许,需要看发布计划及阻断规则,不能只凭"删字段"三个字做结论。

对于客户环境,建议把删除、类型缩窄和长度缩短归入高风险变更,先做备份、抽样验证和影响分析。

这是数据治理要求,不只是界面上的二次确认。

五、进入审批后,单据身份和流程身份各做各的事

一张业务单据回答"发生了什么业务",一次流程实例回答"这次审批怎样推进"。

它们需要关联,但不应相互替代。

独立表单据创建时,除了业务字段,还会写入租户、状态、表单版本、单据编号和审计字段。

随后,流程使用业务行 ID 作为 businessKey,启动成功后再回写流程实例 ID。

下面是创建数据方法的主线摘录:

java 复制代码
public Long createData(BpmProcessDefinitionInfoDO info,
        Map<String, Object> variables, Long userId) {
    Model model = loadModel(info);
    Map<String, Object> values = new LinkedHashMap<>();
    values.put("tenant_id", currentTenantId());
    values.put("process_status",
            BpmProcessInstanceStatusEnum.RUNNING.getStatus());
    values.put("form_version", info.getFormVersion());
    values.put("bill_no",
            variables.get(BpmProcessVariableConstants.BILL_CODE));
    putAudit(values, userId, true);
    putFieldValues(values, model.mainColumns, variables);
    Long id = insert(model.main.getTableName(), values);
    for (var detail : model.details.entrySet()) {
        String field = detail.getKey().getContainerField();
        if (variables.containsKey(field)) {
            insertDetailRows(detail.getKey(), detail.getValue(),
                    id, variables.get(field), userId);
        }
    }
    return id;
}

摘录省略部门字段的转换细节,核心是"先有业务行,再建立流程关联"。

主表与明细也不是任意 Map 全量灌入数据库,而是通过模型列挑选、转换后写入。

5.1 状态回写应发生在明确的业务事件上

▲ 创建业务行后,以业务行 ID 启动流程,再绑定流程实例 ID;节点修改与状态变化走各自的同步入口。

当前服务具有创建数据、绑定流程实例、更新业务数据和更新流程状态等入口。

因此,独立数据表不是流程结束后才一次性导出的附属台账。

审批过程中如果允许修改采购数量,业务数据更新需要跟随受控的节点操作。

如果只是流程状态变更,就不应随手把整张表单重写一遍。

把这两种更新分开,能减少无关覆盖。

这里还要区分"代码中存在更新入口"和"所有业务字段都可以在所有节点修改"。

字段是否可写仍应服从流程和业务权限,不能因为底层支持更新就放开任意写入。

5.2 在途单据不能盲目读取最新设计

流程发布时会保存表单字段与配置,并记录绑定的表单版本。

这使流程定义能够携带它当时使用的表单形态。

例如 v1 没有交付日期,v2 增加交付日期。

旧流程实例仍应按照它所属定义解释输入,不能要求已经提交的申请人补填一个当时不存在的必填项。

还要注意,表单发布到新版本后,已有流程模型可能仍绑定旧版本。

设计器中的提示会提醒仍使用旧版的模型,需要重新发布相关流程模型,后续新单才会使用新版本。

▲ 本地已有流程实例展示填写结果与审批信息。它与设计器处于不同阶段,可用于核对业务单据和流程实例的关联。

不能由此推导出"任何历史结构变更都自动兼容"。

尤其是业务数据服务仍要使用模型登记来定位物理表和列;类型转换、列停用和明细更新行为,应加入版本回归测试。

六、进入查询阶段,独立表只是起点

独立存储提高了数据可访问性,但不会自动生成一套安全、准确、快速的报表系统。

要按项目统计已批准预算,至少需要明确项目身份、金额口径、流程状态、作废规则和时间维度。

如果只执行金额求和,草稿、驳回和撤销单也可能混进结果。

6.1 查询条件先有业务含义,再有 SQL

查询问题 必须确定的口径 常见误解
某项目批准了多少预算 核准金额、通过状态、有效单据 对所有申请金额求和
哪些设备未交付 申请与订单、交付数据的关联 认为审批通过等于交付完成
部门历史申请趋势 申请时组织或当前组织 不说明部门变更后的归属
某笔费用源于哪张申请 稳定单据 ID 与引用关系 仅通过名称或备注匹配

如果查询需要复杂的业务联动,就应该由对应业务服务提供稳定接口。

不要把数据库表名直接暴露给前端,让页面自由拼接筛选条件。

6.2 动态 SQL 更要保住两条边界

一条是标识符边界。

表名和列名来自经过校验的模型登记,而不是直接使用用户传来的任意字符串。

参数占位符能保护值,但不能替代对表名、列名的管理。

另一条是数据边界。

当前独立表数据服务使用 JdbcTemplate,不自动经过 MyBatis 的多租户插件。

所以它在更新和删除明细等语句中显式增加租户条件。

下面是更新方法的关键实现:

java 复制代码
private void update(String tableName,
        Map<String, Object> values, Long id) {
    if (values.isEmpty()) {
        return;
    }
    BpmFormDdlDialect dialect = formManagedService.getDialect();
    List<String> names = new ArrayList<>(values.keySet());
    String assignments = names.stream()
            .map(name -> dialect.quote(name) + " = ?")
            .collect(Collectors.joining(", "));
    String sql = "UPDATE " + dialect.quote(tableName)
            + " SET " + assignments
            + " WHERE id = ? AND tenant_id = ?";
    List<Object> args = new ArrayList<>();
    names.forEach(name -> args.add(values.get(name)));
    args.add(id);
    args.add(currentTenantId());
    jdbcTemplate.update(sql, args.toArray());
}

这段代码说明租户隔离不能只寄希望于某个插件自动生效。

但 tenant_id 也不是权限的全部:同一租户内,部门、本人、角色和单据授权仍可能有不同范围。

新增查询、导出和打印入口时,都应验证这些边界。

6.3 主子表更新还有一个容易忽略的问题

当前明细更新路径会在收到对应明细容器时,删除该父单在当前租户下的旧明细,再重新插入。

这种方式简单、明确,适合明细行主要依附于主单的场景。

但如果下游订单已经直接引用某条明细的数据库 ID,重建明细就可能破坏引用。

因此,是否允许外部业务依赖明细行 ID,必须提前约定。

需要稳定明细身份时,应评估按行标识做差异更新,或引入不会随重建改变的业务行编号。

这属于接入业务的额外设计,不应把"独立表可引用"理解成"所有内部明细 ID 都天然稳定"。

七、把演进能力做成可验收的约束

一套表单系统最有价值的测试,不是"输入框能不能输入",而是"设计变化后,业务事实是否仍然可信"。

决策点 推荐处理 为什么
页面标题与字段身份 分开管理 改文案不应变成新字段
保存与发布 独立动作 草稿不直接改变线上契约
发布确认 校验预览版本 防止确认内容与执行内容不一致
历史定义 保存快照并按绑定版本解释 防止新必填项污染旧单
数据访问 模型白名单与权限同时校验 有物理表不等于可任意查询

7.1 用一组变化检验设计

先建立 v1 表单并发起申请 A,保留它的业务主键和流程实例 ID。

增加交付日期后发布 v2,但暂时不重新发布流程模型,再发起申请 B,观察绑定版本。

随后重新发布流程模型,发起申请 C,核对新字段是否出现。

这组测试不是为了刻意制造复杂步骤,而是把"设计版本"和"流程部署版本"的差异显式验证出来。

A、B、C 的预期应写在测试记录里,不依赖开发人员口头解释。

再把一个字段从显示状态改为停用,查看历史单据是否仍能读懂。

如果改变金额精度,应准备具有边界小数位的历史值,核对页面、存储和导出是否一致。

7.2 不要遗漏失败与并发

  • 两个管理员同时发布,后到者应得到明确结果。
  • 预览后草稿变化,旧确认不能静默执行新计划。
  • 写业务主行后流程启动失败,要核对事务边界及残留处理。
  • 相同事件重复触发,状态同步不应制造重复业务单据。
  • 从另一个租户访问相同业务 ID,不应读到数据。
  • 同租户的普通员工,不应因为知道表名就绕过数据范围。

这些检查比"发布按钮成功提示"更接近企业系统的真实验收标准。

7.3 哪些需求应该退出低代码的舒适区

如果一张申请已经涉及库存预占、预算扣减、供应商准入和复杂分摊,核心规则就不宜散落在多个组件表达式里。

可以保留低代码负责输入和展示,让业务服务负责有副作用的执行。

判断标准不是字段多少,而是错误的代价和一致性边界。

一个只有三个字段的转账指令,可能比几十个字段的调查问卷更需要专门服务。

八、如何按本文路线体验

在线演示可从文末地址进入,公开演示账号为 admin / admin123。

本地已有环境优先访问管理端;没有环境时,按项目部署文档准备依赖并启动后端,再在 PC 工程执行 pnpm dev:antd。

仅启动前端不会自动获得业务数据。

建议按以下顺序体验,不要直接在共享环境发布结构变更:

  1. 进入流程中心的流程表单,查看不同存储方式。
  2. 打开已有独立表单的设计器,识别编码与已发布版本。
  3. 打开数据表视图,对照主表、明细表和字段模型。
  4. 查看关联流程模型,确认使用的表单及其版本。
  5. 打开已有流程实例,核对填写内容、单据编号与审批信息。
  6. 在自己的测试环境复制案例,验证保存、发布和流程重发的差别。
  7. 最后验证历史单据、查询权限和异常恢复,不只验证正常提交。

本文核对的主要实现包括 BpmFormManagedServiceImpl、BpmFormManagedDataServiceImpl、BpmProcessDefinitionServiceImpl 和 BpmProcessInstanceServiceImpl。

前端对应流程表单设计器、数据模型抽屉与发布预览。

代码摘录为阅读目的做了压缩,未列出的校验、响应组装和事务上下文应以完整源码为准。

常见问题

独立数据表能代替传统业务模块吗?

能承载一部分数据采集与审批需求,但不能自动代替复杂业务服务。

库存、资金、跨系统幂等和强一致性仍需按领域设计。

低代码减少重复工作,不意味着取消业务边界。

发布表单后,新字段为什么没有立刻出现在新流程里?

需要分别检查表单是否发布,以及流程模型是否重新部署并绑定了新版本。

设计器保存、表单发布和流程发布是不同动作。

不能只根据设计器当前画面判断运行中的定义。

表单有了物理表,是不是可以直接连报表?

可以把它作为报表的数据来源之一,但还要配置字段、状态口径、租户和数据权限。

独立建表本身不等于自动生成报表,也不等于授权所有人查询。

删除控件后,历史值是否一定保留?

不能做无条件保证。

需要看发布计划对该变更采用停用还是物理移除,以及历史页面、列类型和查询如何兼容。

正式变更前应验证历史样本与恢复方案。

业务查询应该读流程变量还是独立表?

先确定权威来源。

如果独立表承担业务台账,业务查询应围绕它建立明确接口,流程变量服务于执行需要。

两边数据都存在时,更要明确同步时点和冲突处理,不能让不同页面任意选一个来源。

结语

低代码最难的部分,不是让字段出现,而是让字段进入业务之后仍然有稳定含义。

当页面定义、发布模型、流程版本和业务单据各自拥有清晰职责,一张表单才有机会从短期工具成长为可持续维护的业务入口。

如果你正在建设企业表单平台,不妨先拿一张真正会改版、会被其他模块引用的单据做验证。

它会比十张只做展示的演示表单,更快暴露架构中需要补齐的部分。

延伸阅读:《SpringBoot3 企业数据导入:从 Excel 校验到批次处理、错误回执与安全重试》;《SpringBoot3 企业文件生命周期:上传、业务关联、权限、版本与安全清理》。

你们的表单系统中,最难处理的是历史版本、跨模块引用,还是查询权限?欢迎结合真实场景讨论。

如果这篇对你有用,点个「在看」或收藏。

🌐 演示地址 :https://ruoyioffice.com/web

📦 GitHub 源码 :https://github.com/yuqing2026/ruoyi-office

📦 Gitee 源码 :https://gitee.com/yqzy1688/ruoyi-office

💬 微信:17156169080(获取产品咨询)

打开演示地址直接查看系统。

相关推荐
需要8263 小时前
MySQL MVCC 与事务隔离级别:从一条 update 看版本链
java·数据库·spring boot·mysql·spring cloud
RuoyiOffice3 小时前
SpringBoot3 企业薪酬核算:考勤、绩效与薪资规则如何算出一张可解释的工资单
spring boot·vue3·规则引擎·hrm·ruoyi office·薪酬核算·薪资试算
阿狗童鞋4 小时前
SpringBoot微服务实战指南
spring boot·后端·微服务
程序猿乐锅5 小时前
【黑马点评 | 第九篇】秒杀优化-异步实现
java·spring boot·redis·后端·spring·中间件·mybatis
Wx-bishekaifayuan5 小时前
springboot户外登山社交小程序19787-计算机课程设计、毕业设计
spring boot·后端·python·spring·elasticsearch·django·课程设计
+VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue外卖点餐系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
FYKJ_20105 小时前
springboot刑事案件管理系统03047-计算机课程设计、毕业设计
vue.js·spring boot·python·mysql·typescript·spark·django
小辰爱喝汤6 小时前
新闻管理系统|SpringBoot + Vue 毕业设计完整方案
spring boot·毕业设计·课程设计
FYKJ_20106 小时前
springboot助农产品销售商城05829-计算机课程设计、毕业设计
java·spring boot·python·mysql·spark·django