diff --git a/jeecg-boot/db/mes-xsl-biz-entity-field-detail-alter-slot-columns.sql b/jeecg-boot/db/mes-xsl-biz-entity-field-detail-alter-slot-columns.sql
new file mode 100644
index 00000000..7d716f73
--- /dev/null
+++ b/jeecg-boot/db/mes-xsl-biz-entity-field-detail-alter-slot-columns.sql
@@ -0,0 +1,28 @@
+-- 升级脚本:仅为缺失的列执行 ADD,可重复执行(不会因「列已存在」报错)
+-- 适用:旧库 mes_xsl_biz_entity_field_detail 缺少 detail_property_name / detail_slot_kind
+
+SET NAMES utf8mb4;
+
+SELECT COUNT(*) INTO @jeecg_chk_dpn FROM information_schema.COLUMNS
+WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = 'mes_xsl_biz_entity_field_detail'
+ AND COLUMN_NAME = 'detail_property_name';
+
+SET @jeecg_sql_dpn := IF(@jeecg_chk_dpn = 0,
+ 'ALTER TABLE mes_xsl_biz_entity_field_detail ADD COLUMN detail_property_name varchar(128) DEFAULT NULL COMMENT ''主实体明细属性名(与打印绑定 detailProperty 一致)'' AFTER profile_id',
+ 'SELECT ''detail_property_name 已存在,跳过'' AS msg');
+PREPARE jeecg_stmt_dpn FROM @jeecg_sql_dpn;
+EXECUTE jeecg_stmt_dpn;
+DEALLOCATE PREPARE jeecg_stmt_dpn;
+
+SELECT COUNT(*) INTO @jeecg_chk_dsk FROM information_schema.COLUMNS
+WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = 'mes_xsl_biz_entity_field_detail'
+ AND COLUMN_NAME = 'detail_slot_kind';
+
+SET @jeecg_sql_dsk := IF(@jeecg_chk_dsk = 0,
+ 'ALTER TABLE mes_xsl_biz_entity_field_detail ADD COLUMN detail_slot_kind varchar(16) DEFAULT NULL COMMENT ''LIST 或 OBJECT'' AFTER detail_property_name',
+ 'SELECT ''detail_slot_kind 已存在,跳过'' AS msg');
+PREPARE jeecg_stmt_dsk FROM @jeecg_sql_dsk;
+EXECUTE jeecg_stmt_dsk;
+DEALLOCATE PREPARE jeecg_stmt_dsk;
diff --git a/jeecg-boot/db/mes-xsl-biz-entity-field-profile.sql b/jeecg-boot/db/mes-xsl-biz-entity-field-profile.sql
new file mode 100644
index 00000000..816960d2
--- /dev/null
+++ b/jeecg-boot/db/mes-xsl-biz-entity-field-profile.sql
@@ -0,0 +1,38 @@
+-- 业务实体字段配置(主表 + 明细:每条明细对应一类「明细表」及其字段列表)
+SET NAMES utf8mb4;
+
+CREATE TABLE IF NOT EXISTS `mes_xsl_biz_entity_field_profile` (
+ `id` varchar(32) NOT NULL COMMENT '主键',
+ `business_name` varchar(200) NOT NULL COMMENT '业务名称',
+ `business_code` varchar(64) NOT NULL COMMENT '业务编码(菜单 permission id,与打印 biz_code 一致,唯一)',
+ `entity_class_name` varchar(512) DEFAULT NULL COMMENT '主实体 Java 全限定类名',
+ `main_fields_json` text COMMENT '主表实体字段列表(JSON 数组,元素可为字符串字段名或含 name/comment/javaType 的对象)',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `tenant_id` int DEFAULT NULL COMMENT '租户ID',
+ `create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
+ `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+ `update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
+ `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_mxbefp_bcode` (`business_code`),
+ KEY `idx_mxbefp_tenant` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES业务实体字段配置-主表';
+
+CREATE TABLE IF NOT EXISTS `mes_xsl_biz_entity_field_detail` (
+ `id` varchar(32) NOT NULL COMMENT '主键',
+ `profile_id` varchar(32) NOT NULL COMMENT '主表ID',
+ `detail_property_name` varchar(128) DEFAULT NULL COMMENT '主实体明细属性名(与打印绑定 detailProperty 一致)',
+ `detail_slot_kind` varchar(16) DEFAULT NULL COMMENT 'LIST 或 OBJECT',
+ `detail_name` varchar(200) DEFAULT NULL COMMENT '明细展示名称',
+ `detail_entity_class_name` varchar(512) DEFAULT NULL COMMENT '明细实体 Java 全限定类名',
+ `detail_fields_json` text COMMENT '明细表字段列表(JSON 数组,规则同 main_fields_json)',
+ `sort_no` int DEFAULT NULL COMMENT '排序号',
+ `create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
+ `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+ `update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
+ `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `idx_mxbefd_profile` (`profile_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES业务实体字段配置-明细表字段清单';
+
+-- ─── 旧表缺列时执行(见 db/mes-xsl-biz-entity-field-detail-alter-slot-columns.sql 或 Flyway V3.9.2_55)───
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/bootstrap/BizEntityFieldCatalogSyncRunner.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/bootstrap/BizEntityFieldCatalogSyncRunner.java
new file mode 100644
index 00000000..f11b61a2
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/bootstrap/BizEntityFieldCatalogSyncRunner.java
@@ -0,0 +1,164 @@
+package org.jeecg.modules.xslmes.bootstrap;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.modules.print.entity.PrintBizPermEntity;
+import org.jeecg.modules.print.service.IPrintBizPermEntityService;
+import org.jeecg.modules.print.util.PrintBizDetailPropertyScanner;
+import org.jeecg.modules.print.util.PrintBizEntityFieldIntrospector;
+import org.jeecg.modules.print.vo.PrintBizDetailSlotVO;
+import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
+import org.jeecg.modules.system.entity.SysPermission;
+import org.jeecg.modules.system.service.ISysPermissionService;
+import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldDetail;
+import org.jeecg.modules.xslmes.service.IMesXslBizEntityFieldProfileService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+/**
+ * 启动后异步:遍历 {@code print_biz_perm_entity} 中已配置实体类的业务,反射主表与明细槽位字段并写入 {@code mes_xsl_biz_entity_field_*},
+ * 供「业务打印绑定」弹窗读取(避免运行时频繁反射)。
+ *
+ *
关闭:{@code jeecg.print.biz-entity-field-catalog-sync=false}
+ *
+ *
建议在 {@link org.jeecg.modules.print.bootstrap.PrintBizPermEntityWarmupRunner}(Order 2000)之后执行。
+ */
+@Slf4j
+@Component
+@Order(2100)
+public class BizEntityFieldCatalogSyncRunner implements ApplicationRunner {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ @Resource
+ private IPrintBizPermEntityService printBizPermEntityService;
+
+ @Resource
+ private IMesXslBizEntityFieldProfileService bizEntityFieldProfileService;
+
+ @Resource
+ private ISysPermissionService sysPermissionService;
+
+ @Value("${jeecg.print.biz-entity-field-catalog-sync:true}")
+ private boolean syncEnabled;
+
+ /** 延迟执行秒数,便于晚于「打印菜单实体映射预热」写完 print_biz_perm_entity */
+ @Value("${jeecg.print.biz-entity-field-catalog-sync-delay-seconds:10}")
+ private int catalogSyncDelaySeconds;
+
+ @Override
+ public void run(ApplicationArguments args) {
+ if (!syncEnabled) {
+ log.info("业务实体字段缓存同步已关闭(jeecg.print.biz-entity-field-catalog-sync=false)");
+ return;
+ }
+ log.info(
+ "业务实体字段缓存:将在 {} 秒后异步同步(来源 print_biz_perm_entity → mes_xsl_biz_entity_field_*)",
+ Math.max(0, catalogSyncDelaySeconds));
+ CompletableFuture.runAsync(
+ this::doSync,
+ CompletableFuture.delayedExecutor(
+ Math.max(0, catalogSyncDelaySeconds),
+ TimeUnit.SECONDS,
+ ForkJoinPool.commonPool()));
+ }
+
+ private void doSync() {
+ try {
+ log.info("业务实体字段缓存:开始异步同步(来源 print_biz_perm_entity)");
+ List rows = printBizPermEntityService.list();
+ if (rows == null || rows.isEmpty()) {
+ log.info("业务实体字段缓存:print_biz_perm_entity 无数据,跳过");
+ return;
+ }
+ int ok = 0;
+ int skip = 0;
+ for (PrintBizPermEntity row : rows) {
+ if (row == null || StringUtils.isBlank(row.getPermId())) {
+ skip++;
+ continue;
+ }
+ String permId = row.getPermId().trim();
+ String entityFqn = StringUtils.trimToNull(row.getEntityClass());
+ if (entityFqn == null) {
+ skip++;
+ continue;
+ }
+ Class> clazz = PrintBizEntityFieldIntrospector.tryLoadClass(entityFqn);
+ if (clazz == null) {
+ skip++;
+ continue;
+ }
+ try {
+ syncOne(permId, clazz, entityFqn);
+ ok++;
+ } catch (Exception ex) {
+ log.warn("业务实体字段缓存同步失败 permId={} entity={}", permId, entityFqn, ex);
+ skip++;
+ }
+ }
+ log.info("业务实体字段缓存同步完成:成功 {} 条,跳过 {} 条", ok, skip);
+ } catch (Exception e) {
+ log.warn("业务实体字段缓存同步异常(不影响系统启动)", e);
+ }
+ }
+
+ private void syncOne(String permId, Class> clazz, String entityFqn) throws Exception {
+ List mainFields = PrintBizEntityFieldIntrospector.listFields(clazz);
+ String mainJson = OBJECT_MAPPER.writeValueAsString(mainFields);
+
+ List slots = PrintBizDetailPropertyScanner.listSlots(clazz);
+ List detailRows = new ArrayList<>();
+ int sort = 0;
+ Date now = new Date();
+ for (PrintBizDetailSlotVO slot : slots) {
+ Class> itemClazz =
+ PrintBizDetailPropertyScanner.resolveItemClassForSlot(
+ clazz, slot.getPropertyName(), slot.getSlotKind());
+ if (itemClazz == null) {
+ continue;
+ }
+ List itemFields = PrintBizEntityFieldIntrospector.listFields(itemClazz);
+ MesXslBizEntityFieldDetail d = new MesXslBizEntityFieldDetail();
+ d.setDetailPropertyName(slot.getPropertyName());
+ d.setDetailSlotKind(slot.getSlotKind());
+ d.setDetailName(slot.getLabel());
+ d.setDetailEntityClassName(fqn(itemClazz));
+ d.setDetailFieldsJson(OBJECT_MAPPER.writeValueAsString(itemFields));
+ d.setSortNo(sort++);
+ d.setCreateTime(now);
+ d.setUpdateTime(now);
+ detailRows.add(d);
+ }
+
+ String bizName = resolveMenuName(permId);
+ bizEntityFieldProfileService.upsertScannedProfile(permId, bizName, entityFqn, mainJson, detailRows);
+ }
+
+ private static String fqn(Class> c) {
+ if (c == null) {
+ return null;
+ }
+ String cn = c.getCanonicalName();
+ return cn != null ? cn : c.getName();
+ }
+
+ private String resolveMenuName(String permId) {
+ SysPermission p = sysPermissionService.getById(permId);
+ if (p != null && StringUtils.isNotBlank(p.getName())) {
+ return p.getName().trim();
+ }
+ return permId;
+ }
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslBizEntityFieldProfileController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslBizEntityFieldProfileController.java
new file mode 100644
index 00000000..4fbed799
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslBizEntityFieldProfileController.java
@@ -0,0 +1,128 @@
+package org.jeecg.modules.xslmes.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.aspect.annotation.AutoLog;
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldProfile;
+import org.jeecg.modules.xslmes.service.IMesXslBizEntityFieldProfileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * 业务实体字段配置:主表存业务名称与主实体字段 JSON;子表存各明细表的字段 JSON。
+ */
+@Tag(name = "业务实体字段配置")
+@RestController
+@RequestMapping("/xslmes/mesXslBizEntityFieldProfile")
+@Slf4j
+public class MesXslBizEntityFieldProfileController extends JeecgController {
+
+ @Autowired
+ private IMesXslBizEntityFieldProfileService bizEntityFieldProfileService;
+
+ @Operation(summary = "分页列表(不含明细,减轻负载)")
+ @GetMapping(value = "/list")
+ public Result> queryPageList(
+ MesXslBizEntityFieldProfile query,
+ @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+ @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
+ HttpServletRequest req) {
+ QueryWrapper qw = QueryGenerator.initQueryWrapper(query, req.getParameterMap());
+ Page page = new Page<>(pageNo, pageSize);
+ IPage pageList = bizEntityFieldProfileService.page(page, qw);
+ return Result.OK(pageList);
+ }
+
+ @AutoLog(value = "业务实体字段配置-添加")
+ @Operation(summary = "添加(请求体可含 detailList)")
+ @RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:add")
+ @PostMapping(value = "/add")
+ public Result add(@RequestBody MesXslBizEntityFieldProfile entity) {
+ if (oConvertUtils.isEmpty(entity.getBusinessName())) {
+ return Result.error("业务名称不能为空");
+ }
+ if (oConvertUtils.isEmpty(entity.getBusinessCode())) {
+ return Result.error("业务编码不能为空(建议填写菜单 permission id)");
+ }
+ bizEntityFieldProfileService.saveWithDetails(entity);
+ return Result.OK("添加成功");
+ }
+
+ @AutoLog(value = "业务实体字段配置-编辑")
+ @Operation(summary = "编辑(明细全量替换)")
+ @RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:edit")
+ @RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
+ public Result edit(@RequestBody MesXslBizEntityFieldProfile entity) {
+ if (oConvertUtils.isEmpty(entity.getId())) {
+ return Result.error("主键不能为空");
+ }
+ if (oConvertUtils.isEmpty(entity.getBusinessName())) {
+ return Result.error("业务名称不能为空");
+ }
+ if (oConvertUtils.isEmpty(entity.getBusinessCode())) {
+ return Result.error("业务编码不能为空(建议填写菜单 permission id)");
+ }
+ bizEntityFieldProfileService.updateWithDetails(entity);
+ return Result.OK("编辑成功");
+ }
+
+ @AutoLog(value = "业务实体字段配置-删除")
+ @Operation(summary = "删除")
+ @RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:delete")
+ @DeleteMapping(value = "/delete")
+ public Result delete(@RequestParam(name = "id", required = true) String id) {
+ bizEntityFieldProfileService.removeWithDetails(id);
+ return Result.OK("删除成功");
+ }
+
+ @AutoLog(value = "业务实体字段配置-批量删除")
+ @Operation(summary = "批量删除")
+ @RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:deleteBatch")
+ @DeleteMapping(value = "/deleteBatch")
+ public Result deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
+ bizEntityFieldProfileService.removeBatchWithDetails(Arrays.asList(ids.split(",")));
+ return Result.OK("批量删除成功");
+ }
+
+ @Operation(summary = "按 id 查询(含 detailList)")
+ @GetMapping(value = "/queryById")
+ public Result queryById(@RequestParam(name = "id", required = true) String id) {
+ MesXslBizEntityFieldProfile entity = bizEntityFieldProfileService.getByIdWithDetails(id);
+ if (entity == null) {
+ return Result.error("未找到对应数据");
+ }
+ return Result.OK(entity);
+ }
+
+ @RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:exportXls")
+ @RequestMapping(value = "/exportXls")
+ public ModelAndView exportXls(HttpServletRequest request, MesXslBizEntityFieldProfile query) {
+ return super.exportXls(request, query, MesXslBizEntityFieldProfile.class, "业务实体字段配置");
+ }
+
+ @RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:importExcel")
+ @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
+ public Result> importExcel(HttpServletRequest request, HttpServletResponse response) {
+ return super.importExcel(request, response, MesXslBizEntityFieldProfile.class);
+ }
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslBizEntityFieldDetail.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslBizEntityFieldDetail.java
new file mode 100644
index 00000000..2c70743e
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslBizEntityFieldDetail.java
@@ -0,0 +1,68 @@
+package org.jeecg.modules.xslmes.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+/**
+ * 业务实体字段配置子表:一类明细表对应一行,字段清单存 JSON。
+ */
+@Data
+@TableName("mes_xsl_biz_entity_field_detail")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "业务实体字段配置-明细表字段清单")
+public class MesXslBizEntityFieldDetail implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(type = IdType.ASSIGN_ID)
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "主表ID")
+ private String profileId;
+
+ @Schema(description = "主实体上明细属性名(与打印绑定 detailProperty 一致,如 lines)")
+ private String detailPropertyName;
+
+ @Schema(description = "槽位类型:LIST 或 OBJECT")
+ private String detailSlotKind;
+
+ @Schema(description = "明细展示名称")
+ private String detailName;
+
+ @Schema(description = "明细实体 Java 全限定类名")
+ private String detailEntityClassName;
+
+ /** 明细表字段列表 JSON */
+ @Schema(description = "明细表字段列表(JSON 数组)")
+ private String detailFieldsJson;
+
+ @Schema(description = "排序号")
+ private Integer sortNo;
+
+ @Schema(description = "创建人")
+ private String createBy;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Schema(description = "创建时间")
+ private Date createTime;
+
+ @Schema(description = "更新人")
+ private String updateBy;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Schema(description = "更新时间")
+ private Date updateTime;
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslBizEntityFieldProfile.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslBizEntityFieldProfile.java
new file mode 100644
index 00000000..c3bf52c7
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslBizEntityFieldProfile.java
@@ -0,0 +1,72 @@
+package org.jeecg.modules.xslmes.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+/**
+ * 业务实体字段配置主表:业务名称、主实体类名、主表字段 JSON;明细通过 detailList 关联子表。
+ */
+@Data
+@TableName("mes_xsl_biz_entity_field_profile")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "业务实体字段配置主表")
+public class MesXslBizEntityFieldProfile implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(type = IdType.ASSIGN_ID)
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "业务名称")
+ private String businessName;
+
+ @Schema(description = "业务编码(菜单 permission id,与业务打印绑定 biz_code、print_biz_perm_entity.perm_id 一致)")
+ private String businessCode;
+
+ @Schema(description = "主实体 Java 全限定类名")
+ private String entityClassName;
+
+ /** 主表实体字段列表 JSON */
+ @Schema(description = "主表实体字段列表(JSON 数组)")
+ private String mainFieldsJson;
+
+ @Schema(description = "备注")
+ private String remark;
+
+ @Schema(description = "租户ID")
+ private Integer tenantId;
+
+ @Schema(description = "创建人")
+ private String createBy;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Schema(description = "创建时间")
+ private Date createTime;
+
+ @Schema(description = "更新人")
+ private String updateBy;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Schema(description = "更新时间")
+ private Date updateTime;
+
+ /** 各明细表对应的字段清单(不落主表) */
+ @TableField(exist = false)
+ @Schema(description = "明细表字段配置列表")
+ private List detailList;
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslBizEntityFieldDetailMapper.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslBizEntityFieldDetailMapper.java
new file mode 100644
index 00000000..e05176ec
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslBizEntityFieldDetailMapper.java
@@ -0,0 +1,8 @@
+package org.jeecg.modules.xslmes.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldDetail;
+
+@Mapper
+public interface MesXslBizEntityFieldDetailMapper extends BaseMapper {}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslBizEntityFieldProfileMapper.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslBizEntityFieldProfileMapper.java
new file mode 100644
index 00000000..d1a73091
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslBizEntityFieldProfileMapper.java
@@ -0,0 +1,8 @@
+package org.jeecg.modules.xslmes.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldProfile;
+
+@Mapper
+public interface MesXslBizEntityFieldProfileMapper extends BaseMapper {}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/print/catalog/PrintBizEntityFieldCatalogProviderImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/print/catalog/PrintBizEntityFieldCatalogProviderImpl.java
new file mode 100644
index 00000000..994e3950
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/print/catalog/PrintBizEntityFieldCatalogProviderImpl.java
@@ -0,0 +1,226 @@
+package org.jeecg.modules.xslmes.print.catalog;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.modules.print.catalog.IPrintBizEntityFieldCatalogProvider;
+import org.jeecg.modules.print.vo.PrintBizDetailSlotVO;
+import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
+import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldDetail;
+import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldProfile;
+import org.jeecg.modules.xslmes.mapper.MesXslBizEntityFieldDetailMapper;
+import org.jeecg.modules.xslmes.service.IMesXslBizEntityFieldProfileService;
+import org.springframework.stereotype.Component;
+
+/**
+ * 将 mes_xsl_biz_entity_field_* 中的缓存提供给打印绑定接口(SPI 实现)。
+ *
+ * bizCode = {@code print_biz_perm_entity.perm_id}。
+ *
+ *
「新增绑定」下拉组装时对每条业务若单独查库会产生严重 N+1;{@link #beginBulkLookup(Collection)} 在同一请求线程内改为单次 IN 查询。
+ */
+@Slf4j
+@Component
+public class PrintBizEntityFieldCatalogProviderImpl implements IPrintBizEntityFieldCatalogProvider {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ /** null=非批量模式;非 null=批量模式(可能为空 Map) */
+ private static final ThreadLocal