Browse Source

feat(form): 添加快捷表单功能并优化列数据处理

- 新增 QUICK_FORM 类型常量和相关数据库字段映射
- 实现快捷表单保存、更新、删除和克隆功能
- 添加在线首页配置模型、控制器、服务和数据访问层
- 修复同表同名列重复插入导致的数据脏问题
- 优化 OnlineColumn 查询避免 TooManyResultsException 异常
- 支持快捷表单组件 JSON 中的列 ID 回填功能
- 增强字段引用解析和过滤规则写入机制
- 优化事务处理确保数据一致性
pull/1/head
chenchuchuan 4 weeks ago
parent
commit
70b806709c
  1. 431
      common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java
  2. 58
      common/common-online/src/main/java/apelet/common/online/controller/OnlineHomepageConfigController.java
  3. 13
      common/common-online/src/main/java/apelet/common/online/dao/OnlineHomepageConfigMapper.java
  4. 13
      common/common-online/src/main/java/apelet/common/online/dao/OnlineQuickFormFieldMapper.java
  5. 26
      common/common-online/src/main/java/apelet/common/online/dao/OnlineQuickFormMapper.java
  6. 12
      common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineFormMapper.xml
  7. 59
      common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineQuickFormMapper.xml
  8. 19
      common/common-online/src/main/java/apelet/common/online/dto/OnlineFormDto.java
  9. 12
      common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java
  10. 66
      common/common-online/src/main/java/apelet/common/online/model/OnlineHomepageConfig.java
  11. 142
      common/common-online/src/main/java/apelet/common/online/model/OnlineQuickForm.java
  12. 187
      common/common-online/src/main/java/apelet/common/online/model/OnlineQuickFormField.java
  13. 5
      common/common-online/src/main/java/apelet/common/online/model/constant/FormType.java
  14. 30
      common/common-online/src/main/java/apelet/common/online/service/OnlineHomepageConfigService.java
  15. 13
      common/common-online/src/main/java/apelet/common/online/service/OnlineQuickFormFieldService.java
  16. 36
      common/common-online/src/main/java/apelet/common/online/service/OnlineQuickFormService.java
  17. 28
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineColumnServiceImpl.java
  18. 73
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineHomepageConfigServiceImpl.java
  19. 4
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlinePageServiceImpl.java
  20. 35
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineQuickFormFieldServiceImpl.java
  21. 81
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineQuickFormServiceImpl.java
  22. 584
      common/common-online/src/main/java/apelet/common/online/util/QuickFormWidgetUtil.java
  23. 2
      common/common-online/src/main/java/apelet/common/online/util/WidgetFieldTypeMapping.java
  24. 19
      common/common-online/src/main/java/apelet/common/online/vo/OnlineFormVo.java
  25. 1
      common/common-online/src/main/resources/quick-form/quick-form-edit.json
  26. 1
      common/common-online/src/main/resources/quick-form/quick-form-list.json

431
common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java

@ -10,7 +10,6 @@ import apelet.common.core.constant.GlobalDeletedFlag;
import apelet.common.core.object.*; import apelet.common.core.object.*;
import apelet.common.core.util.MyCommonUtil; import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyModelUtil; import apelet.common.core.util.MyModelUtil;
import apelet.common.core.util.MyPageUtil;
import apelet.common.core.validator.UpdateGroup; import apelet.common.core.validator.UpdateGroup;
import apelet.common.dbutil.object.SqlTable; import apelet.common.dbutil.object.SqlTable;
import apelet.common.dbutil.object.SqlTableColumn; import apelet.common.dbutil.object.SqlTableColumn;
@ -26,12 +25,16 @@ import apelet.common.online.config.OnlineProperties;
import apelet.common.online.dto.OnlineEventPluginExecuteDto; import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.dto.OnlineFormDto; import apelet.common.online.dto.OnlineFormDto;
import apelet.common.online.model.*; import apelet.common.online.model.*;
import apelet.common.online.model.constant.FieldFilterType;
import apelet.common.online.model.constant.FormKind;
import apelet.common.online.model.constant.FormType; import apelet.common.online.model.constant.FormType;
import apelet.common.online.service.*; import apelet.common.online.service.*;
import apelet.common.online.util.OnlineRedisKeyUtil; import apelet.common.online.util.OnlineRedisKeyUtil;
import apelet.common.online.util.QuickFormWidgetUtil;
import apelet.common.online.util.WidgetFieldTypeMapping; import apelet.common.online.util.WidgetFieldTypeMapping;
import apelet.common.online.util.WidgetJsonUtil; import apelet.common.online.util.WidgetJsonUtil;
import apelet.common.online.vo.OnlineFormVo; import apelet.common.online.vo.OnlineFormVo;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
@ -40,7 +43,6 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.page.PageMethod;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -51,6 +53,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -109,6 +112,13 @@ public class OnlineFormController {
@Autowired(required = false) @Autowired(required = false)
private FormButtonPermissionChecker buttonPermissionChecker; private FormButtonPermissionChecker buttonPermissionChecker;
@Autowired
private OnlineQuickFormService onlineQuickFormService;
@Autowired
private OnlineQuickFormFieldService onlineQuickFormFieldService;
@Autowired
private IdGeneratorWrapper idGenerator;
/** /**
* 自动同步字段到历史数据给所有缺该字段的 onl_form_head 表补字段可按表类型过滤 * 自动同步字段到历史数据给所有缺该字段的 onl_form_head 表补字段可按表类型过滤
* 已同步的表补字段+同步数据库+回写 zz_online_column未同步的表只补字段 * 已同步的表补字段+同步数据库+回写 zz_online_column未同步的表只补字段
@ -138,7 +148,12 @@ public class OnlineFormController {
@ApiOperationSupport(ignoreParameters = {"onlineFormDto.formId"}) @ApiOperationSupport(ignoreParameters = {"onlineFormDto.formId"})
@OperationLog(type = SysOperationLogType.ADD) @OperationLog(type = SysOperationLogType.ADD)
@PostMapping("/add") @PostMapping("/add")
@Transactional(rollbackFor = Exception.class)
public ResponseResult<Long> add(@MyRequestBody OnlineFormDto onlineFormDto) { public ResponseResult<Long> add(@MyRequestBody OnlineFormDto onlineFormDto) {
// 快捷表单:走快捷表单保存逻辑
if (onlineFormDto.getFormType() != null && onlineFormDto.getFormType() == FormType.QUICK_FORM) {
return ResponseResult.success(this.quickFormSave(onlineFormDto, null));
}
String errorMessage = MyCommonUtil.getModelValidationError(onlineFormDto); String errorMessage = MyCommonUtil.getModelValidationError(onlineFormDto);
if (errorMessage != null) { if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@ -177,7 +192,13 @@ public class OnlineFormController {
*/ */
@OperationLog(type = SysOperationLogType.UPDATE) @OperationLog(type = SysOperationLogType.UPDATE)
@PostMapping("/update") @PostMapping("/update")
@Transactional(rollbackFor = Exception.class)
public ResponseResult<Void> update(@MyRequestBody OnlineFormDto onlineFormDto) { public ResponseResult<Void> update(@MyRequestBody OnlineFormDto onlineFormDto) {
// 快捷表单:formType=QUICK_FORM 才是快捷表单,formId 即快捷表单id,走快捷表单保存逻辑
if (onlineFormDto.getFormType() != null && onlineFormDto.getFormType() == FormType.QUICK_FORM) {
this.quickFormSave(onlineFormDto, onlineFormDto.getFormId());
return ResponseResult.success();
}
String errorMessage = MyCommonUtil.getModelValidationError(onlineFormDto, Default.class, UpdateGroup.class); String errorMessage = MyCommonUtil.getModelValidationError(onlineFormDto, Default.class, UpdateGroup.class);
if (errorMessage != null) { if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@ -264,7 +285,6 @@ public class OnlineFormController {
onlineFormDto.setWidgetJson(updatedJson.toJSONString()); onlineFormDto.setWidgetJson(updatedJson.toJSONString());
} }
} }
if (!onlineFormService.update(onlineForm, originalOnlineForm, datasourceIdSet)) { if (!onlineFormService.update(onlineForm, originalOnlineForm, datasourceIdSet)) {
redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineFormKey(onlineForm.getFormId())).delete(); redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineFormKey(onlineForm.getFormId())).delete();
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
@ -286,7 +306,23 @@ public class OnlineFormController {
String errorMessage; String errorMessage;
ResponseResult<OnlineForm> verifyResult = this.doVerifyAndGet(formId); ResponseResult<OnlineForm> verifyResult = this.doVerifyAndGet(formId);
if (!verifyResult.isSuccess()) { if (!verifyResult.isSuccess()) {
return ResponseResult.errorFrom(verifyResult); // formId 不是在线表单:可能是快捷表单,先去快捷表单表确认
OnlineQuickForm quickForm = onlineQuickFormService.getById(formId);
if (quickForm == null) {
return ResponseResult.errorFrom(verifyResult);
}
// 1. 先删除快捷表单及其字段
onlineQuickFormFieldService.remove(new LambdaQueryWrapper<OnlineQuickFormField>().eq(OnlineQuickFormField::getQuickFormId, formId));
if (!onlineQuickFormService.removeById(formId)) {
errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
}
// 2. 再删除该快捷表单下的生成表单(复用现有删除逻辑,会同步清理数据源关联与缓存)
List<OnlineForm> generatedFormList = onlineFormService.list(new LambdaQueryWrapper<OnlineForm>().eq(OnlineForm::getQuickFormId, formId).eq(OnlineForm::getIsQuick, 1));
for (OnlineForm generatedForm : generatedFormList) {
onlineFormService.remove(generatedForm.getFormId());
}
return ResponseResult.success();
} }
if (!onlineFormService.remove(formId)) { if (!onlineFormService.remove(formId)) {
errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!"; errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
@ -306,8 +342,51 @@ public class OnlineFormController {
public ResponseResult<Long> clone(@MyRequestBody Long formId) { public ResponseResult<Long> clone(@MyRequestBody Long formId) {
ResponseResult<OnlineForm> verifyResult = this.doVerifyAndGet(formId); ResponseResult<OnlineForm> verifyResult = this.doVerifyAndGet(formId);
if (!verifyResult.isSuccess()) { if (!verifyResult.isSuccess()) {
return ResponseResult.errorFrom(verifyResult); // formId 不是在线表单:可能是快捷表单,按快捷表单复制
OnlineQuickForm sourceQuickForm = onlineQuickFormService.getById(formId);
if (sourceQuickForm == null) {
return ResponseResult.errorFrom(verifyResult);
}
TokenData tokenData = TokenData.takeFromRequest();
if (!StrUtil.equals(sourceQuickForm.getAppCode(), tokenData.getAppCode())) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "数据验证失败,当前应用不包含该表单!");
}
if (ObjectUtil.notEqual(sourceQuickForm.getTenantId(), tokenData.getTenantId())) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "数据验证失败,当前租户不包含该表单!");
}
// 查询快捷表单字段
List<OnlineQuickFormField> sourceFieldList = onlineQuickFormFieldService.list(new LambdaQueryWrapper<OnlineQuickFormField>()
.eq(OnlineQuickFormField::getQuickFormId, formId));
OnlineFormDto cloneDto = new OnlineFormDto();
cloneDto.setPageId(sourceQuickForm.getPageId());
cloneDto.setFormCode(sourceQuickForm.getQuickFormCode() + "_copy_" + System.currentTimeMillis());
cloneDto.setFormName(sourceQuickForm.getQuickFormName() + "_copy");
cloneDto.setLayoutType(sourceQuickForm.getLayoutType());
cloneDto.setMasterTableId(sourceQuickForm.getMasterTableId());
cloneDto.setOnlineQuickFormFields(sourceFieldList);
if (CollUtil.isNotEmpty(sourceFieldList)) {
// 有字段:与保存逻辑一致,生成编辑页/列表页
return ResponseResult.success(this.quickFormSave(cloneDto, null));
}
// 无字段:只复制快捷表单,不生成编辑页/列表页
OnlineQuickForm cloneQuickForm = new OnlineQuickForm();
cloneQuickForm.setId(idGenerator.nextLongId());
cloneQuickForm.setAppCode(tokenData.getAppCode());
cloneQuickForm.setTenantId(tokenData.getTenantId());
cloneQuickForm.setPageId(cloneDto.getPageId());
cloneQuickForm.setQuickFormCode(cloneDto.getFormCode());
cloneQuickForm.setQuickFormName(cloneDto.getFormName());
cloneQuickForm.setLayoutType(cloneDto.getLayoutType());
cloneQuickForm.setMasterTableId(cloneDto.getMasterTableId());
Date now = new Date();
cloneQuickForm.setCreateTime(now);
cloneQuickForm.setCreateUserId(tokenData.getUserId());
cloneQuickForm.setUpdateTime(now);
cloneQuickForm.setUpdateUserId(tokenData.getUserId());
onlineQuickFormService.save(cloneQuickForm);
return ResponseResult.success(cloneQuickForm.getId());
} }
// 原先逻辑不动
OnlineForm form = verifyResult.getData(); OnlineForm form = verifyResult.getData();
form.setFormName(form.getFormName() + "_copy"); form.setFormName(form.getFormName() + "_copy");
form.setFormCode(form.getFormCode() + "_copy_" + System.currentTimeMillis()); form.setFormCode(form.getFormCode() + "_copy_" + System.currentTimeMillis());
@ -327,18 +406,23 @@ public class OnlineFormController {
* @return 应答结果对象包含查询结果集 * @return 应答结果对象包含查询结果集
*/ */
@PostMapping("/list") @PostMapping("/list")
public ResponseResult<MyPageData<OnlineFormVo>> list( public ResponseResult<MyPageData<OnlineFormVo>> list(@MyRequestBody OnlineFormDto onlineFormDtoFilter, @MyRequestBody MyOrderParam orderParam, @MyRequestBody MyPageParam pageParam) {
@MyRequestBody OnlineFormDto onlineFormDtoFilter, // 不做分页:非快捷表单 + 快捷表单合并,再按前端排序字段排序
@MyRequestBody MyOrderParam orderParam, List<OnlineFormVo> voList = new ArrayList<>();
@MyRequestBody MyPageParam pageParam) {
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
OnlineForm onlineFormFilter = MyModelUtil.copyTo(onlineFormDtoFilter, OnlineForm.class); OnlineForm onlineFormFilter = MyModelUtil.copyTo(onlineFormDtoFilter, OnlineForm.class);
onlineFormFilter.setIsQuick(0);
String orderBy = MyOrderParam.buildOrderBy(orderParam, OnlineForm.class); String orderBy = MyOrderParam.buildOrderBy(orderParam, OnlineForm.class);
List<OnlineForm> onlineFormList = List<OnlineForm> onlineFormList = onlineFormService.getOnlineFormListWithRelation(onlineFormFilter, orderBy);
onlineFormService.getOnlineFormListWithRelation(onlineFormFilter, orderBy); onlineFormList.forEach(form -> voList.add(OnlineForm.INSTANCE.fromModel(form)));
return ResponseResult.success(MyPageUtil.makeResponseData(onlineFormList, OnlineForm.INSTANCE)); OnlineQuickForm quickFormFilter = new OnlineQuickForm();
if (onlineFormDtoFilter != null) {
quickFormFilter.setPageId(onlineFormDtoFilter.getPageId());
quickFormFilter.setQuickFormCode(onlineFormDtoFilter.getFormCode());
quickFormFilter.setQuickFormName(onlineFormDtoFilter.getFormName());
}
List<OnlineQuickForm> quickFormList = onlineQuickFormService.getOnlineFormListWithRelation(quickFormFilter, MyOrderParam.buildOrderBy(orderParam, OnlineQuickForm.class));
quickFormList.forEach(qf -> voList.add(OnlineQuickForm.INSTANCE.fromModel(qf)));
return ResponseResult.success(new MyPageData<>(voList, (long) voList.size()));
} }
/** /**
@ -575,6 +659,313 @@ public class OnlineFormController {
return ResponseResult.success(datasourceIdSet); return ResponseResult.success(datasourceIdSet);
} }
// ============ 快捷表单保存逻辑 ============
/**
* 快捷表单保存逻辑文档技术方案第 1~4 + 字段同步
* 1. 基础信息保存进 zz_online_quick_form
* 2. 字段信息存入 zz_online_quick_form_field
* 3. 生成编辑页 zz_online_formcode+Edit / name+编辑pc+小程序 组件json
* 4. 生成列表页 zz_online_formcode+List / name+列表编辑页id关联到新建/编辑按钮
* 之后调用 syncNewFieldsAndSubTables 完成 onl_form_field 字段同步
*
* @param dto 快捷表单配置formCode/formName 即快捷表单编码/名称
* @param quickFormId 已有快捷表单idnull 表示新增
* @return 编辑页 formId
*/
private Long quickFormSave(OnlineFormDto dto, Long quickFormId) {
if (dto.getMasterTableId() == null) {
throw new IllegalArgumentException("数据验证失败,表单主表id不能为空!");
}
if (StringUtils.isBlank(dto.getFormCode())) {
throw new IllegalArgumentException("数据验证失败,快捷表单编码不能为空!");
}
if (StringUtils.isBlank(dto.getFormName())) {
throw new IllegalArgumentException("数据验证失败,快捷表单名称不能为空!");
}
TokenData tokenData = TokenData.takeFromRequest();
// 1. 基础信息存 zz_online_quick_form
OnlineQuickForm quickForm;
if (quickFormId == null) {
boolean codeExists = CollUtil.isNotEmpty(onlineQuickFormService.list(new LambdaQueryWrapper<OnlineQuickForm>()
.eq(OnlineQuickForm::getPageId, dto.getPageId())
.eq(OnlineQuickForm::getQuickFormCode, dto.getFormCode())));
if (codeExists) {
throw new IllegalArgumentException("数据验证失败,快捷表单编码已经存在!");
}
quickForm = new OnlineQuickForm();
quickForm.setId(idGenerator.nextLongId());
quickForm.setAppCode(tokenData.getAppCode());
quickForm.setTenantId(tokenData.getTenantId());
quickForm.setCreateTime(new Date());
quickForm.setCreateUserId(tokenData.getUserId());
} else {
quickForm = onlineQuickFormService.getById(quickFormId);
if (quickForm == null) {
throw new IllegalArgumentException("数据验证失败,快捷表单不存在!");
}
}
quickForm.setPageId(dto.getPageId());
quickForm.setQuickFormCode(dto.getFormCode());
quickForm.setQuickFormName(dto.getFormName());
quickForm.setLayoutType(dto.getLayoutType());
quickForm.setMasterTableId(dto.getMasterTableId());
quickForm.setUpdateTime(new Date());
quickForm.setUpdateUserId(tokenData.getUserId());
onlineQuickFormService.saveOrUpdate(quickForm);
Long savedQuickFormId = quickForm.getId();
// 2. 字段信息存 zz_online_quick_form_field
onlineQuickFormFieldService.remove(new LambdaQueryWrapper<OnlineQuickFormField>().eq(OnlineQuickFormField::getQuickFormId, savedQuickFormId));
List<OnlineQuickFormField> fieldList = dto.getOnlineQuickFormFields();
if (CollUtil.isNotEmpty(fieldList)) {
Date now = new Date();
for (OnlineQuickFormField field : fieldList) {
field.setId(idGenerator.nextLongId());
field.setQuickFormId(savedQuickFormId);
field.setTenantId(tokenData.getTenantId());
field.setCreateTime(now);
field.setCreateUserId(tokenData.getUserId());
field.setUpdateTime(now);
field.setUpdateUserId(tokenData.getUserId());
onlineQuickFormFieldService.save(field);
}
}
// 3. 解析数据源与主表
OnlineDatasource datasource = onlineDatasourceService.getOnlineDatasourceByMasterTableId(dto.getMasterTableId());
if (datasource == null) {
throw new IllegalArgumentException("数据验证失败,关联的数据源不存在!");
}
OnlineTable masterTable = onlineTableService.getById(dto.getMasterTableId());
if (masterTable == null) {
throw new IllegalArgumentException("数据验证失败,关联的主表不存在!");
}
Set<Long> datasourceIdSet = Collections.singleton(datasource.getDatasourceId());
String masterTableName = masterTable.getTableName();
// 4. 复用已生成的列表页/编辑页:保留 formId,避免菜单等配置引用的 formId 因删除重建而失效
List<OnlineForm> generatedFormList = onlineFormService.list(new LambdaQueryWrapper<OnlineForm>()
.eq(OnlineForm::getQuickFormId, savedQuickFormId)
.eq(OnlineForm::getIsQuick, 1));
OnlineForm existingEdit = generatedFormList.stream().filter(f -> f.getFormType() != null && f.getFormType() == FormType.EDIT_FORM).findFirst().orElse(null);
OnlineForm existingList = generatedFormList.stream().filter(f -> f.getFormType() != null && f.getFormType() == FormType.QUERY).findFirst().orElse(null);
// 5. 编辑页 zz_online_form:存在则原地更新(formId 不变),不存在才新建
OnlineForm editForm = existingEdit;
if (editForm == null) {
editForm = new OnlineForm();
}
editForm.setPageId(dto.getPageId());
editForm.setFormCode(dto.getFormCode() + "Edit");
editForm.setFormName(dto.getFormName() + "编辑");
editForm.setFormKind(FormKind.DIALOG);
editForm.setFormType(FormType.EDIT_FORM);
editForm.setMasterTableId(dto.getMasterTableId());
editForm.setIsQuick(1);
editForm.setQuickFormId(savedQuickFormId);
if (editForm.getFormId() == null) {
// 新生成的编辑页才插入;此时 pageId/formName 等必填字段已就绪
editForm = onlineFormService.saveNew(editForm, datasourceIdSet);
}
// 6. 生成编辑页组件json,并同步字段到 onl_form_head/onl_form_field
this.resolveQuickFormFieldRefs(fieldList);
JSONObject editWidget = QuickFormWidgetUtil.buildEditWidgetJson(fieldList, savedQuickFormId, dto.getMasterTableId(), masterTableName);
OnlineFormDto editSyncDto = new OnlineFormDto();
editSyncDto.setDatasourceIdList(Collections.singletonList(datasource.getDatasourceId()));
editSyncDto.setFormType(FormType.EDIT_FORM);
JSONObject updatedEdit = syncNewFieldsAndSubTables(editForm, editSyncDto, editWidget);
if (updatedEdit != null) {
editWidget = updatedEdit;
}
this.backfillColumnIds(editWidget, dto.getMasterTableId());
editForm.setWidgetJson(editWidget.toJSONString());
onlineFormService.update(editForm, editForm, datasourceIdSet);
// 7. 列表页 zz_online_form:存在则原地更新(formId 不变),不存在才新建
OnlineForm listForm = existingList;
if (listForm == null) {
listForm = new OnlineForm();
}
listForm.setPageId(dto.getPageId());
listForm.setFormCode(dto.getFormCode() + "List");
listForm.setFormName(dto.getFormName() + "列表");
listForm.setFormKind(FormKind.NEW_PAGE);
listForm.setFormType(FormType.QUERY);
listForm.setMasterTableId(dto.getMasterTableId());
listForm.setIsQuick(1);
listForm.setQuickFormId(savedQuickFormId);
if (listForm.getFormId() == null) {
// 新生成的列表页才插入;此时 pageId/formName/masterTableId 等必填字段已就绪
listForm = onlineFormService.saveNew(listForm, datasourceIdSet);
}
// 8. 生成列表页组件json,新建/编辑按钮关联编辑页id;tableColumnList 需要列id,先构建 字段名→columnId 映射
Map<String, Long> columnIdMap = this.buildColumnIdMap(dto.getMasterTableId());
// 写回字段 column_id:新字段在编辑页同步时已建列,回填后前端再次编辑能区分新旧字段
this.writeBackFieldColumnId(fieldList, columnIdMap);
JSONObject listWidget = QuickFormWidgetUtil.buildListWidgetJson(fieldList, listForm.getFormId(), editForm.getFormId(), savedQuickFormId, dto.getMasterTableId(), masterTableName, columnIdMap);
this.backfillColumnIds(listWidget, dto.getMasterTableId());
listForm.setWidgetJson(listWidget.toJSONString());
onlineFormService.update(listForm, listForm, datasourceIdSet);
// 8.5 清理历史残留的多余生成页(正常情况下仅编辑页+列表页各一个)
for (OnlineForm generatedForm : generatedFormList) {
if (generatedForm.getFormId().equals(editForm.getFormId()) || generatedForm.getFormId().equals(listForm.getFormId())) {
continue;
}
onlineFormService.remove(generatedForm.getFormId());
}
// 9. 过滤规则回写 zz_online_column(文档第4步:上面所有逻辑走完再处理)
this.writeColumnFilterType(fieldList, dto.getMasterTableId(), tokenData);
return editForm.getFormId();
}
/**
* 构建 字段名(小写)columnId 映射供列表页 tableColumnList 回填列id
*/
private Map<String, Long> buildColumnIdMap(Long tableId) {
Map<String, Long> columnIdMap = new HashMap<>();
OnlineTable table = onlineTableService.getOnlineTableFromCache(tableId);
if (table == null || table.getColumnMap() == null) {
return columnIdMap;
}
table.getColumnMap().values().forEach(column -> {
if (column != null && StringUtils.isNotBlank(column.getColumnName())) {
columnIdMap.put(column.getColumnName().toLowerCase(), column.getColumnId());
}
});
return columnIdMap;
}
/**
* 将解析出的 columnId 写回快捷表单字段记录前端新字段不传 columnId表示待新增
* 编辑页同步建列后回填保证再次编辑时前端能区分新旧字段
*/
private void writeBackFieldColumnId(List<OnlineQuickFormField> fieldList, Map<String, Long> columnIdMap) {
if (CollUtil.isEmpty(fieldList) || columnIdMap == null || columnIdMap.isEmpty()) {
return;
}
for (OnlineQuickFormField field : fieldList) {
Long columnId = columnIdMap.get(field.getFieldName().toLowerCase());
if (columnId != null && !Objects.equals(columnId, field.getColumnId())) {
field.setColumnId(columnId);
onlineQuickFormFieldService.updateById(field);
}
}
}
/**
* 过滤字段是过滤条件且有过滤方案 表id+字段名 zz_online_column把过滤规则写进去
*/
private void writeColumnFilterType(List<OnlineQuickFormField> fieldList, Long masterTableId, TokenData tokenData) {
if (CollUtil.isEmpty(fieldList)) {
return;
}
for (OnlineQuickFormField field : fieldList) {
if (field.getIsFilter() == null || field.getIsFilter() != 1 || field.getFilterType() == null || field.getFilterType() == FieldFilterType.NO_FILTER) {
continue;
}
OnlineColumn column = onlineColumnService.getOnlineColumnByTableIdAndColumnName(masterTableId, field.getFieldName());
if (column != null && !Objects.equals(column.getFilterType(), field.getFilterType())) {
column.setFilterType(field.getFilterType());
column.setUpdateTime(new Date());
column.setUpdateUserId(tokenData.getUserId());
onlineColumnService.updateById(column);
redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(masterTableId)).delete();
}
}
}
// ============ 组件json列id回填 ============
/**
* F7 关联字段解析关联表信息前端 refProperty 传的是字段所在列idF7 真正关联的是在线表单对应主表的表头(onl_form_head)
* formId兜底 refDict 在线表单 主表 表头 解析结果放入临时字段供控件生成与 onl_form_field.ref_propert 回写
*/
private void resolveQuickFormFieldRefs(List<OnlineQuickFormField> fieldList) {
if (CollUtil.isEmpty(fieldList)) {
return;
}
for (OnlineQuickFormField field : fieldList) {
if (field.getFieldType() == null || field.getFieldType() != 402) {
continue;
}
Long relatedFormId = field.getFormId() != null ? field.getFormId() : field.getRefDict();
if (relatedFormId == null) {
continue;
}
OnlineForm relatedForm = onlineFormService.getById(relatedFormId);
if (relatedForm == null || relatedForm.getMasterTableId() == null) {
continue;
}
OnlineTable relatedTable = onlineTableService.getById(relatedForm.getMasterTableId());
if (relatedTable == null) {
continue;
}
OnlFormHead relatedHead = onlFormHeadService.getOne(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getTableName, relatedTable.getTableName()));
if (relatedHead == null) {
continue;
}
field.setRefTableId(relatedHead.getId());
field.setRefTableName(relatedTable.getTableName());
field.setRefFormCode(relatedForm.getFormCode());
}
}
/**
* 从主表 OnlineColumn 按列名回填控件 bindData.columnId/tableId覆盖 pc/mobile 全部控件
*/
private void backfillColumnIds(JSONObject widgetJson, Long tableId) {
OnlineTable table = onlineTableService.getOnlineTableFromCache(tableId);
if (table == null || table.getColumnMap() == null || table.getColumnMap().isEmpty()) {
return;
}
Map<String, Long> nameToId = new HashMap<>();
table.getColumnMap().values().forEach(column -> {
if (column != null && StringUtils.isNotBlank(column.getColumnName())) {
nameToId.put(column.getColumnName().toLowerCase(), column.getColumnId());
}
});
for (String device : new String[]{"pc", "mobile"}) {
JSONObject deviceObj = widgetJson.getJSONObject(device);
if (deviceObj == null) {
continue;
}
backfillColumnIdsInWidgets(deviceObj.getJSONArray("widgetList"), nameToId, tableId);
JSONObject tableWidget = deviceObj.getJSONObject("tableWidget");
if (tableWidget != null) {
backfillColumnIdsInWidgets(tableWidget.getJSONArray("childWidgetList"), nameToId, tableId);
}
}
}
private void backfillColumnIdsInWidgets(JSONArray widgetList, Map<String, Long> nameToId, Long tableId) {
if (widgetList == null) {
return;
}
for (int i = 0; i < widgetList.size(); i++) {
JSONObject widget = widgetList.getJSONObject(i);
if (widget == null) {
continue;
}
JSONArray children = widget.getJSONArray("childWidgetList");
if (children != null && !children.isEmpty()) {
backfillColumnIdsInWidgets(children, nameToId, tableId);
}
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) {
continue;
}
String columnName = bindData.getString("columnFieldName");
if (StrUtil.isEmpty(columnName)) {
columnName = bindData.getString("columnName");
}
if (StrUtil.isEmpty(columnName)) {
continue;
}
Long columnId = nameToId.get(columnName.toLowerCase());
if (columnId != null) {
bindData.put("columnId", columnId);
bindData.put("tableId", tableId);
}
}
}
/** /**
* 获取流程对应的在线表单 * 获取流程对应的在线表单
* @param bosType * @param bosType
@ -657,13 +1048,19 @@ public class OnlineFormController {
JSONObject widget = columnEntry.getValue(); JSONObject widget = columnEntry.getValue();
OnlFormField existingField = fieldByColumnName.get(columnName); OnlFormField existingField = fieldByColumnName.get(columnName);
if (existingField != null) { if (existingField != null) {
// 已有字段:带 slaveTableId 则更新 refPropert(子表列仅关联列 comType=402 生效) // 已有字段:字段控件带 slaveTableId(关联组件)则更新 refPropert;不带(如字典下拉)则清空残留,避免实体表误按关联表强转 Map 报错
boolean isTableColumn = !widget.containsKey("bindData"); boolean isTableColumn = !widget.containsKey("bindData");
Integer comType = widget.getInteger("comType"); Integer comType = widget.getInteger("comType");
boolean isAssociation = !isTableColumn || (comType != null && comType == 402); boolean isAssociation = !isTableColumn || (comType != null && comType == 402);
String slaveTableId = extractSlaveTableId(widget); String slaveTableId = extractSlaveTableId(widget);
if (isAssociation && StrUtil.isNotEmpty(slaveTableId)) { if (isAssociation && StrUtil.isNotEmpty(slaveTableId)) {
existingField.setRefPropert(Long.valueOf(slaveTableId)); if (!Objects.equals(existingField.getRefPropert(), Long.valueOf(slaveTableId))) {
existingField.setRefPropert(Long.valueOf(slaveTableId));
onlFormFieldService.updateById(existingField);
changed = true;
}
} else if (existingField.getRefPropert() != null) {
existingField.setRefPropert(null);
onlFormFieldService.updateById(existingField); onlFormFieldService.updateById(existingField);
changed = true; changed = true;
} }

58
common/common-online/src/main/java/apelet/common/online/controller/OnlineHomepageConfigController.java

@ -0,0 +1,58 @@
package apelet.common.online.controller;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.object.ResponseResult;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import apelet.common.online.model.OnlineHomepageConfig;
import apelet.common.online.service.OnlineHomepageConfigService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 功能: 在线首页配置
*
* @author {chenchuchuan}
* @version 1.0
*/
@Tag(name = "在线首页配置接口")
@Slf4j
@RestController
@RequestMapping("${common-online.urlPrefix}/onlineHomepageConfig")
@ConditionalOnProperty(name = "common-online.operationEnabled", havingValue = "true")
public class OnlineHomepageConfigController {
@Autowired
private OnlineHomepageConfigService onlineHomepageConfigService;
/**
* 保存或更新在线表单数据
*
* @param onlineHomepageConfig 更新对象
* @return 应答结果对象
*/
@OperationLog(type = SysOperationLogType.UPDATE)
@PostMapping("/saveOrUpdate")
public ResponseResult<Void> saveOrUpdate(@MyRequestBody String onlineHomepageConfig) {
onlineHomepageConfigService.updateOrSave(onlineHomepageConfig);
return ResponseResult.success();
}
/**
* 根据用户信息查询在线表单数据
*
* @return 应答结果对象
*/
@PostMapping("/queryByUserInfo")
public ResponseResult<OnlineHomepageConfig> queryByUserInfo() {
return ResponseResult.success(onlineHomepageConfigService.queryByUser());
}
}

13
common/common-online/src/main/java/apelet/common/online/dao/OnlineHomepageConfigMapper.java

@ -0,0 +1,13 @@
package apelet.common.online.dao;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.online.model.OnlineHomepageConfig;
/**
* 功能: 在线首页配置数据访问层
*
* @author {chenchuchuan}
* @version 1.0
*/
public interface OnlineHomepageConfigMapper extends BaseDaoMapper<OnlineHomepageConfig> {
}

13
common/common-online/src/main/java/apelet/common/online/dao/OnlineQuickFormFieldMapper.java

@ -0,0 +1,13 @@
package apelet.common.online.dao;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.online.model.OnlineQuickFormField;
/**
* 在线快捷表单字段数据操作访问接口
*
* @author chenchuchuan
* @date 2026-08-18
*/
public interface OnlineQuickFormFieldMapper extends BaseDaoMapper<OnlineQuickFormField> {
}

26
common/common-online/src/main/java/apelet/common/online/dao/OnlineQuickFormMapper.java

@ -0,0 +1,26 @@
package apelet.common.online.dao;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.online.model.OnlineQuickForm;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 在线快捷表单数据操作访问接口
*
* @author chenchuchuan
* @date 2026-08-18
*/
public interface OnlineQuickFormMapper extends BaseDaoMapper<OnlineQuickForm> {
/**
* 获取过滤后的对象列表
*
* @param onlineFormFilter 主表过滤对象
* @param orderBy 排序字符串order by从句的参数
* @return 对象列表
*/
List<OnlineQuickForm> getOnlineFormList(
@Param("onlineFormFilter") OnlineQuickForm onlineFormFilter, @Param("orderBy") String orderBy);
}

12
common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineFormMapper.xml

@ -13,6 +13,8 @@
<result column="master_table_id" jdbcType="BIGINT" property="masterTableId"/> <result column="master_table_id" jdbcType="BIGINT" property="masterTableId"/>
<result column="widget_json" jdbcType="LONGVARCHAR" property="widgetJson"/> <result column="widget_json" jdbcType="LONGVARCHAR" property="widgetJson"/>
<result column="params_json" jdbcType="LONGVARCHAR" property="paramsJson"/> <result column="params_json" jdbcType="LONGVARCHAR" property="paramsJson"/>
<result column="is_quick" jdbcType="INTEGER" property="isQuick"/>
<result column="quick_form_id" jdbcType="BIGINT" property="quickFormId"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/> <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="create_user_id" jdbcType="BIGINT" property="createUserId"/> <result column="create_user_id" jdbcType="BIGINT" property="createUserId"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/> <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
@ -68,6 +70,16 @@
#{item} #{item}
</foreach> </foreach>
</if> </if>
<if test="onlineFormFilter.isQuick != null">
<choose>
<when test="onlineFormFilter.isQuick == 0">
AND (zz_online_form.is_quick = 0 OR zz_online_form.is_quick IS NULL)
</when>
<otherwise>
AND zz_online_form.is_quick = #{onlineFormFilter.isQuick}
</otherwise>
</choose>
</if>
</if> </if>
</sql> </sql>

59
common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineQuickFormMapper.xml

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="apelet.common.online.dao.OnlineQuickFormMapper">
<resultMap id="BaseResultMap" type="apelet.common.online.model.OnlineQuickForm">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="tenant_id" jdbcType="BIGINT" property="tenantId"/>
<result column="app_code" jdbcType="VARCHAR" property="appCode"/>
<result column="page_id" jdbcType="BIGINT" property="pageId"/>
<result column="quick_form_code" jdbcType="VARCHAR" property="quickFormCode"/>
<result column="quick_form_name" jdbcType="VARCHAR" property="quickFormName"/>
<result column="layout_type" jdbcType="INTEGER" property="layoutType"/>
<result column="master_table_id" jdbcType="BIGINT" property="masterTableId"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="create_user_id" jdbcType="BIGINT" property="createUserId"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="update_user_id" jdbcType="BIGINT" property="updateUserId"/>
</resultMap>
<!-- 这里仅包含调用接口输入的主表过滤条件 -->
<sql id="inputFilterRef">
<if test="onlineFormFilter != null">
<if test="onlineFormFilter.tenantId == null">
AND zz_online_quick_form.tenant_id IS NULL
</if>
<if test="onlineFormFilter.tenantId != null">
AND zz_online_quick_form.tenant_id = #{onlineFormFilter.tenantId}
</if>
<if test="onlineFormFilter.appCode == null">
AND zz_online_quick_form.app_code IS NULL
</if>
<if test="onlineFormFilter.appCode != null">
AND zz_online_quick_form.app_code = #{onlineFormFilter.appCode}
</if>
<if test="onlineFormFilter.pageId != null">
AND zz_online_quick_form.page_id = #{onlineFormFilter.pageId}
</if>
<if test="onlineFormFilter.quickFormCode != null and onlineFormFilter.quickFormCode != ''">
AND zz_online_quick_form.quick_form_code = #{onlineFormFilter.quickFormCode}
</if>
<if test="onlineFormFilter.quickFormName != null and onlineFormFilter.quickFormName != ''">
<bind name="safeQuickFormName" value="'%' + onlineFormFilter.quickFormName + '%'"/>
AND zz_online_quick_form.quick_form_name LIKE #{safeQuickFormName}
</if>
<if test="onlineFormFilter.masterTableId != null">
AND zz_online_quick_form.master_table_id = #{onlineFormFilter.masterTableId}
</if>
</if>
</sql>
<select id="getOnlineFormList" resultMap="BaseResultMap" parameterType="apelet.common.online.model.OnlineQuickForm">
SELECT * FROM zz_online_quick_form
<where>
<include refid="inputFilterRef"/>
</where>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy}
</if>
</select>
</mapper>

19
common/common-online/src/main/java/apelet/common/online/dto/OnlineFormDto.java

@ -2,6 +2,7 @@ package apelet.common.online.dto;
import apelet.common.core.validator.ConstDictRef; import apelet.common.core.validator.ConstDictRef;
import apelet.common.core.validator.UpdateGroup; import apelet.common.core.validator.UpdateGroup;
import apelet.common.online.model.OnlineQuickFormField;
import apelet.common.online.model.constant.FormKind; import apelet.common.online.model.constant.FormKind;
import apelet.common.online.model.constant.FormType; import apelet.common.online.model.constant.FormType;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@ -88,4 +89,22 @@ public class OnlineFormDto {
*/ */
@Schema(description = "表单参数JSON") @Schema(description = "表单参数JSON")
private String paramsJson; private String paramsJson;
/**
* 快捷表单ID编辑快捷表单配置时回传
*/
@Schema(description = "快捷表单ID")
private Long quickFormId;
/**
* 排版类型快捷表单
*/
@Schema(description = "排版类型")
private Integer layoutType;
/**
* 快捷表单字段列表
*/
@Schema(description = "快捷表单字段列表")
private List<OnlineQuickFormField> onlineQuickFormFields;
} }

12
common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java

@ -119,6 +119,18 @@ public class OnlineForm {
@TableField(value = "update_user_id") @TableField(value = "update_user_id")
private Long updateUserId; private Long updateUserId;
/**
* 是否快捷表单0-1-
*/
@TableField(value = "is_quick")
private Integer isQuick;
/**
* 快捷表单ID
*/
@TableField(value = "quick_form_id")
private Long quickFormId;
@RelationOneToOne( @RelationOneToOne(
masterIdField = "masterTableId", masterIdField = "masterTableId",
slaveModelClass = OnlineTable.class, slaveModelClass = OnlineTable.class,

66
common/common-online/src/main/java/apelet/common/online/model/OnlineHomepageConfig.java

@ -0,0 +1,66 @@
package apelet.common.online.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 在线自定义首页实体
*/
@Data
@TableName("zz_online_homepage_config")
public class OnlineHomepageConfig implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键Id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 租户id
*/
@TableField("tenant_id")
private Long tenantId;
/**
* 应用编码
*/
@TableField("app_code")
private String appCode;
/**
* 首页组件JSON
*/
@TableField("widget_json")
private String widgetJson;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
/**
* 创建者
*/
@TableField("create_user_id")
private Long createUserId;
/**
* 更新时间
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 更新者
*/
@TableField("update_user_id")
private Long updateUserId;
}

142
common/common-online/src/main/java/apelet/common/online/model/OnlineQuickForm.java

@ -0,0 +1,142 @@
package apelet.common.online.model;
import apelet.common.core.annotation.RelationConstDict;
import apelet.common.core.annotation.RelationDict;
import apelet.common.core.annotation.RelationOneToMany;
import apelet.common.core.annotation.RelationOneToOne;
import apelet.common.core.base.mapper.BaseModelMapper;
import apelet.common.online.model.constant.FormType;
import apelet.common.online.vo.OnlineFormVo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 在线快捷表单实体对象
*
* @author chenchuchuan
* @date 2026-08-18
*/
@Data
@TableName(value = "zz_online_quick_form")
public class OnlineQuickForm {
public static final OnlineQuickForm.OnlineFormModelMapper INSTANCE = Mappers.getMapper(OnlineQuickForm.OnlineFormModelMapper.class);
/**
* 主键Id
*/
@TableId(value = "id")
private Long id;
/**
* 租户Id
*/
@TableField(value = "tenant_id")
private Long tenantId;
/**
* 应用编码
*/
@TableField(value = "app_code")
private String appCode;
/**
* 页面id
*/
@TableField(value = "page_id")
private Long pageId;
/**
* 快捷表单编码
*/
@TableField(value = "quick_form_code")
private String quickFormCode;
/**
* 快捷表单名称
*/
@TableField(value = "quick_form_name")
private String quickFormName;
/**
* 排版类型
*/
@TableField(value = "layout_type")
private Integer layoutType;
/**
* 表单主表id
*/
@TableField(value = "master_table_id")
private Long masterTableId;
/**
* 创建时间
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 创建者
*/
@TableField(value = "create_user_id")
private Long createUserId;
/**
* 更新时间
*/
@TableField(value = "update_time")
private Date updateTime;
/**
* 更新者
*/
@TableField(value = "update_user_id")
private Long updateUserId;
/**
* 表单类型快捷表单固定为 FormType.QUICK_FORM非数据库列
*/
@TableField(exist = false)
private Integer formType;
@RelationOneToOne(masterIdField = "masterTableId", slaveModelClass = OnlineTable.class, slaveIdField = "tableId")
@TableField(exist = false)
private OnlineTable onlineTable;
@RelationDict(masterIdField = "masterTableId", equalOneToOneRelationField = "onlineTable", slaveModelClass = OnlineTable.class, slaveIdField = "tableId", slaveNameField = "modelName")
@TableField(exist = false)
private Map<String, Object> masterTableIdDictMap;
@RelationConstDict(
masterIdField = "formType",
constantDictClass = FormType.class)
@TableField(exist = false)
private Map<String, Object> formTypeDictMap;
@RelationOneToMany(masterIdField = "id", slaveModelClass = OnlineQuickFormField.class, slaveIdField = "quickFormId")
@TableField(exist = false)
private List<OnlineQuickFormField> onlineQuickFormFields;
@Mapper
public interface OnlineFormModelMapper extends BaseModelMapper<OnlineFormVo, OnlineQuickForm> {
/**
* 转换Vo对象到实体对象
*
* @param onlineFormVo 域对象
* @return 实体对象
*/
@Mapping(target = "onlineTable", expression = "java(mapToBean(onlineFormVo.getOnlineTable(), apelet.common.online.model.OnlineTable.class))")
@Override
OnlineQuickForm toModel(OnlineFormVo onlineFormVo);
/**
* 转换实体对象到VO对象
*
* @param onlineForm 实体对象
* @return 域对象
*/
@Mapping(target = "formKind", expression = "java(apelet.common.online.model.constant.FormKind.NEW_PAGE)")
@Mapping(target = "formType", expression = "java(apelet.common.online.model.constant.FormType.QUICK_FORM)")
@Mapping(target = "formName", expression = "java(onlineForm.getQuickFormName())")
@Mapping(target = "formCode", expression = "java(onlineForm.getQuickFormCode())")
@Mapping(target = "formId", expression = "java(onlineForm.getId())")
@Mapping(target = "quickFormId", expression = "java(onlineForm.getId())")
@Mapping(target = "onlineTable", expression = "java(beanToMap(onlineForm.getOnlineTable(), false))")
@Mapping(target = "onlineQuickFormFields", expression = "java(onlineForm.getOnlineQuickFormFields() == null ? java.util.Collections.emptyList() : onlineForm.getOnlineQuickFormFields())")
@Override
OnlineFormVo fromModel(OnlineQuickForm onlineForm);
}
}

187
common/common-online/src/main/java/apelet/common/online/model/OnlineQuickFormField.java

@ -0,0 +1,187 @@
package apelet.common.online.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* 在线快捷表单字段实体对象
*
* @author chenchuchuan
* @date 2026-08-18
*/
@Data
@TableName(value = "zz_online_quick_form_field")
public class OnlineQuickFormField {
/**
* 主键Id
*/
@TableId(value = "id")
private Long id;
/**
* 快捷表单id
*/
@TableField(value = "quick_form_id")
private Long quickFormId;
/**
* 字段名称
*/
@TableField(value = "field_name")
private String fieldName;
/**
* 字段备注
*/
@TableField(value = "field_remark")
private String fieldRemark;
/**
* 字段长度
*/
@TableField(value = "field_length")
private Integer fieldLength;
/**
* 小数点位数
*/
@TableField(value = "field_point")
private Integer fieldPoint;
/**
* 字段类型前端展示类型即组件widgetType
*/
@TableField(value = "field_type")
private Integer fieldType;
/**
* 关联 zz_online_column 列ID前端传入为空=新增字段待建列非空=已有字段仅修改
*/
@TableField(value = "column_id")
private Long columnId;
/**
* 是否为主键0-1-
*/
@TableField(value = "is_key")
private Integer isKey;
/**
* 默认值
*/
@TableField(value = "default_value")
private String defaultValue;
/**
* 是否允许为空0-1-
*/
@TableField(value = "is_empty")
private Integer isEmpty;
/**
* 是否为默认字段0-1-
*/
@TableField(value = "is_default")
private Integer isDefault;
/**
* 是否列表展示0-1-
*/
@TableField(value = "is_list")
private Integer isList;
/**
* 是否过滤条件0-1-
*/
@TableField(value = "is_filter")
private Integer isFilter;
/**
* 过滤方案参考FieldFilterType常量
*/
@TableField(value = "filter_type")
private Integer filterType;
/**
* 关联元数据ID
*/
@TableField(value = "ref_property")
private Long refProperty;
/**
* 关联数据字典ID
*/
@TableField(value = "ref_dict")
private Long refDict;
/**
* 关联数据字典名称
*/
@TableField(value = "ref_dict_name")
private String refDictName;
/**
* 关联在线表单idF7关联字段前端传入用于解析关联表不落库
*/
@TableField(exist = false)
private Long formId;
/**
* 关联在线表单显示字段F7关联字段前端传入不落库
*/
@TableField(exist = false)
private String displayField;
/**
* 关联表头idF7关联字段由关联在线表单解析出 onl_form_head.id用于 F7 控件 slaveTableId onl_form_field.ref_propert 回写不落库
*/
@TableField(exist = false)
private Long refTableId;
/**
* 关联表名F7关联字段由关联在线表单解析不落库
*/
@TableField(exist = false)
private String refTableName;
/**
* 关联在线表单编码F7关联字段由关联在线表单解析用于 relativeFormCode不落库
*/
@TableField(exist = false)
private String refFormCode;
/**
* 租户ID
*/
@TableField(value = "tenant_id")
private Long tenantId;
/**
* 创建时间
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 创建人ID
*/
@TableField(value = "create_user_id")
private Long createUserId;
/**
* 更新时间
*/
@TableField(value = "update_time")
private Date updateTime;
/**
* 更新人ID
*/
@TableField(value = "update_user_id")
private Long updateUserId;
}

5
common/common-online/src/main/java/apelet/common/online/model/constant/FormType.java

@ -35,6 +35,10 @@ public final class FormType {
* 流程工单表单 * 流程工单表单
*/ */
public static final int FLOW_WORK_ORDER = 11; public static final int FLOW_WORK_ORDER = 11;
/**
* 快捷表单
*/
public static final int QUICK_FORM = 15;
private static final Map<Object, String> DICT_MAP = new HashMap<>(2); private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
static { static {
@ -44,6 +48,7 @@ public final class FormType {
DICT_MAP.put(EDIT_FORM, "编辑表单"); DICT_MAP.put(EDIT_FORM, "编辑表单");
DICT_MAP.put(FLOW, "流程表单"); DICT_MAP.put(FLOW, "流程表单");
DICT_MAP.put(FLOW_WORK_ORDER, "流程工单表单"); DICT_MAP.put(FLOW_WORK_ORDER, "流程工单表单");
DICT_MAP.put(QUICK_FORM, "快捷表单");
} }
/** /**

30
common/common-online/src/main/java/apelet/common/online/service/OnlineHomepageConfigService.java

@ -0,0 +1,30 @@
package apelet.common.online.service;
import apelet.common.core.base.service.IBaseService;
import apelet.common.online.model.OnlineHomepageConfig;
/**
* 功能: 在线首页配置
*
* @author {chenchuchuan}
* @version 1.0
*/
public interface OnlineHomepageConfigService extends IBaseService<OnlineHomepageConfig, Long> {
/**
* 修改在线首页配置
*
* @param onlineHomepageConfig 在线首页配置
* @return 是否修改成功
*/
boolean updateOrSave(String onlineHomepageConfig);
/**
* 根据用户查询在线首页配置
*
* @return 在线首页配置
*/
OnlineHomepageConfig queryByUser();
}

13
common/common-online/src/main/java/apelet/common/online/service/OnlineQuickFormFieldService.java

@ -0,0 +1,13 @@
package apelet.common.online.service;
import apelet.common.core.base.service.IBaseService;
import apelet.common.online.model.OnlineQuickFormField;
/**
* 在线快捷表单字段数据操作服务接口
*
* @author chenchuchuan
* @date 2026-08-18
*/
public interface OnlineQuickFormFieldService extends IBaseService<OnlineQuickFormField, Long> {
}

36
common/common-online/src/main/java/apelet/common/online/service/OnlineQuickFormService.java

@ -0,0 +1,36 @@
package apelet.common.online.service;
import apelet.common.core.base.service.IBaseService;
import apelet.common.online.model.OnlineQuickForm;
import java.util.List;
/**
* 在线快捷表单数据操作服务接口
*
* @author chenchuchuan
* @date 2026-08-18
*/
public interface OnlineQuickFormService extends IBaseService<OnlineQuickForm, Long> {
/**
* 获取单表查询结果由于没有关联数据查询因此在仅仅获取单表数据的场景下效率更高
* 如果需要同时获取关联数据请移步(getOnlineFormListWithRelation)方法
*
* @param filter 过滤对象
* @param orderBy 排序参数
* @return 查询结果集
*/
List<OnlineQuickForm> getOnlineFormList(OnlineQuickForm filter, String orderBy);
/**
* 获取主表的查询结果以及主表关联的字典数据和一对一从表数据以及一对一从表的字典数据
* 该查询会涉及到一对一从表的关联过滤或一对多从表的嵌套关联过滤因此性能不如单表过滤
* 如果仅仅需要获取主表数据请移步(getOnlineFormList)以便获取更好的查询性能
*
* @param filter 主表过滤对象
* @param orderBy 排序参数
* @return 查询结果集
*/
List<OnlineQuickForm> getOnlineFormListWithRelation(OnlineQuickForm filter, String orderBy);
}

28
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineColumnServiceImpl.java

@ -113,9 +113,25 @@ public class OnlineColumnServiceImpl extends BaseService<OnlineColumn, Long> imp
@Override @Override
public long saveBySqlTable(SqlTableColumn sqlTableColumn, Long tableId, String columnComment) { public long saveBySqlTable(SqlTableColumn sqlTableColumn, Long tableId, String columnComment) {
// 同表同名列已存在则更新,避免重复插入产生脏数据(导致后续按表名列名 selectOne 报多行)
OnlineColumn existing = this.getOnlineColumnByTableIdAndColumnName(tableId, sqlTableColumn.getColumnName());
OnlineColumn onlineColumn = buildOnlineColumn(sqlTableColumn, tableId, columnComment, existing == null ? idGenerator.nextLongId() : existing.getColumnId());
if (existing == null) {
if (super.save(onlineColumn)) {
sqlTableColumn.setColumnName(onlineColumn.getColumnName().toLowerCase());
return onlineColumn.getColumnId();
}
return 0;
}
super.updateById(onlineColumn);
sqlTableColumn.setColumnName(onlineColumn.getColumnName().toLowerCase());
return existing.getColumnId();
}
private OnlineColumn buildOnlineColumn(SqlTableColumn sqlTableColumn, Long tableId, String columnComment, Long columnId) {
OnlineColumn onlineColumn = new OnlineColumn(); OnlineColumn onlineColumn = new OnlineColumn();
BeanUtil.copyProperties(sqlTableColumn, onlineColumn, false); BeanUtil.copyProperties(sqlTableColumn, onlineColumn, false);
onlineColumn.setColumnId(idGenerator.nextLongId()); onlineColumn.setColumnId(columnId);
onlineColumn.setTableId(tableId); onlineColumn.setTableId(tableId);
// 传了描述,并且不等于原先的描述,并且原先的为空或者为"null",就赋值 // 传了描述,并且不等于原先的描述,并且原先的为空或者为"null",就赋值
if (StringUtil.isNotBlank(columnComment) && !StringUtil.equals(columnComment, onlineColumn.getColumnComment()) if (StringUtil.isNotBlank(columnComment) && !StringUtil.equals(columnComment, onlineColumn.getColumnComment())
@ -126,11 +142,7 @@ public class OnlineColumnServiceImpl extends BaseService<OnlineColumn, Long> imp
onlineColumn.setColumnComment(onlineColumn.getColumnName()); onlineColumn.setColumnComment(onlineColumn.getColumnName());
} }
this.setDefault(sqlTableColumn, onlineColumn); this.setDefault(sqlTableColumn, onlineColumn);
if (super.save(onlineColumn)) { return onlineColumn;
sqlTableColumn.setColumnName(onlineColumn.getColumnName().toLowerCase());
return onlineColumn.getColumnId();
}
return 0;
} }
/** /**
@ -264,7 +276,9 @@ public class OnlineColumnServiceImpl extends BaseService<OnlineColumn, Long> imp
OnlineColumn filter = new OnlineColumn(); OnlineColumn filter = new OnlineColumn();
filter.setTableId(tableId); filter.setTableId(tableId);
filter.setColumnName(columnName); filter.setColumnName(columnName);
return onlineColumnMapper.selectOne(new QueryWrapper<>(filter)); // 历史脏数据可能存在同表同名列的多条记录,selectOne 会抛 TooManyResultsException,这里取第一条即可
List<OnlineColumn> list = onlineColumnMapper.selectList(new QueryWrapper<>(filter));
return CollUtil.isEmpty(list) ? null : list.get(0);
} }
@Override @Override

73
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineHomepageConfigServiceImpl.java

@ -0,0 +1,73 @@
package apelet.common.online.service.impl;
import apelet.common.core.annotation.MyDataSourceResolver;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.constant.ApplicationConstant;
import apelet.common.core.object.TokenData;
import apelet.common.core.util.DefaultDataSourceResolver;
import apelet.common.online.dao.OnlineHomepageConfigMapper;
import apelet.common.online.model.OnlineHomepageConfig;
import apelet.common.online.service.OnlineHomepageConfigService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* 功能: 在线首页配置服务实现类
*
* @author {chenchuchuan}
* @version 1.0
*/
@Slf4j
@MyDataSourceResolver(resolver = DefaultDataSourceResolver.class, intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("OnlineHomepageConfigService")
public class OnlineHomepageConfigServiceImpl extends BaseService<OnlineHomepageConfig, Long> implements OnlineHomepageConfigService {
@Autowired
private OnlineHomepageConfigMapper onlineHomepageConfigMapper;
@Override
public OnlineHomepageConfigMapper mapper() {
return onlineHomepageConfigMapper;
}
@Override
public boolean updateOrSave(String onlineHomepageConfig) {
if (StringUtils.isBlank(onlineHomepageConfig)) {
return false;
}
OnlineHomepageConfig homepageConfig = this.queryByUser();
homepageConfig.setWidgetJson(onlineHomepageConfig);
Date date = new Date();
TokenData tokenData = TokenData.takeFromRequest();
homepageConfig.setAppCode(tokenData.getAppCode());
homepageConfig.setTenantId(tokenData.getTenantId());
homepageConfig.setUpdateUserId(tokenData.getUserId());
homepageConfig.setUpdateTime(date);
if (homepageConfig.getId() == null) {
homepageConfig.setCreateTime(date);
homepageConfig.setCreateUserId(tokenData.getUserId());
return onlineHomepageConfigMapper.insert(homepageConfig) >= 0;
}
return onlineHomepageConfigMapper.updateById(homepageConfig) >= 0;
}
@Override
public OnlineHomepageConfig queryByUser() {
TokenData tokenData = TokenData.takeFromRequest();
LambdaQueryWrapper<OnlineHomepageConfig> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(StringUtils.isNotBlank(tokenData.getAppCode()), OnlineHomepageConfig::getAppCode, tokenData.getAppCode());
lambdaQueryWrapper.eq(tokenData.getTenantId() != null, OnlineHomepageConfig::getTenantId, tokenData.getTenantId());
lambdaQueryWrapper.eq(OnlineHomepageConfig::getCreateUserId, tokenData.getUserId());
OnlineHomepageConfig homepageConfig = onlineHomepageConfigMapper.selectOne(lambdaQueryWrapper);
if (homepageConfig == null) {
homepageConfig = new OnlineHomepageConfig();
}
return homepageConfig;
}
}

4
common/common-online/src/main/java/apelet/common/online/service/impl/OnlinePageServiceImpl.java

@ -265,6 +265,10 @@ public class OnlinePageServiceImpl extends BaseService<OnlinePage, Long> impleme
@Override @Override
public boolean existByPageCode(String pageCode) { public boolean existByPageCode(String pageCode) {
// pageCode 为空时不参与判重,避免把 page_code 过滤条件丢掉导致全表命中
if (StrUtil.isBlank(pageCode)) {
return false;
}
OnlinePage filter = new OnlinePage(); OnlinePage filter = new OnlinePage();
filter.setPageCode(pageCode); filter.setPageCode(pageCode);
return CollUtil.isNotEmpty(this.getOnlinePageList(filter, null)); return CollUtil.isNotEmpty(this.getOnlinePageList(filter, null));

35
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineQuickFormFieldServiceImpl.java

@ -0,0 +1,35 @@
package apelet.common.online.service.impl;
import apelet.common.core.annotation.MyDataSourceResolver;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.constant.ApplicationConstant;
import apelet.common.core.util.DefaultDataSourceResolver;
import apelet.common.online.dao.OnlineQuickFormFieldMapper;
import apelet.common.online.model.OnlineQuickFormField;
import apelet.common.online.service.OnlineQuickFormFieldService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 在线快捷表单字段数据操作服务类
*
* @author chenchuchuan
* @date 2026-08-18
*/
@Slf4j
@MyDataSourceResolver(
resolver = DefaultDataSourceResolver.class,
intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("onlineQuickFormFieldService")
public class OnlineQuickFormFieldServiceImpl extends BaseService<OnlineQuickFormField, Long> implements OnlineQuickFormFieldService {
@Autowired
private OnlineQuickFormFieldMapper onlineQuickFormFieldMapper;
@Override
protected BaseDaoMapper<OnlineQuickFormField> mapper() {
return onlineQuickFormFieldMapper;
}
}

81
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineQuickFormServiceImpl.java

@ -0,0 +1,81 @@
package apelet.common.online.service.impl;
import apelet.common.core.annotation.MyDataSourceResolver;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.constant.ApplicationConstant;
import apelet.common.core.object.MyRelationParam;
import apelet.common.core.object.TokenData;
import apelet.common.core.util.DefaultDataSourceResolver;
import apelet.common.online.dao.OnlineQuickFormMapper;
import apelet.common.online.model.OnlineQuickForm;
import apelet.common.online.model.constant.FormType;
import apelet.common.online.service.OnlineQuickFormService;
import com.github.pagehelper.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 在线快捷表单数据操作服务类
*
* @author chenchuchuan
* @date 2026-08-18
*/
@Slf4j
@MyDataSourceResolver(resolver = DefaultDataSourceResolver.class, intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("onlineQuickFormService")
public class OnlineQuickFormServiceImpl extends BaseService<OnlineQuickForm, Long> implements OnlineQuickFormService {
@Autowired
private OnlineQuickFormMapper onlineQuickFormMapper;
@Override
protected BaseDaoMapper<OnlineQuickForm> mapper() {
return onlineQuickFormMapper;
}
/**
* 获取单表查询结果由于没有关联数据查询因此在仅仅获取单表数据的场景下效率更高
* 如果需要同时获取关联数据请移步(getOnlineFormListWithRelation)方法
*
* @param filter 过滤对象
* @param orderBy 排序参数
* @return 查询结果集
*/
@Override
public List<OnlineQuickForm> getOnlineFormList(OnlineQuickForm filter, String orderBy) {
if (filter == null) {
filter = new OnlineQuickForm();
}
TokenData tokenData = TokenData.takeFromRequest();
filter.setTenantId(tokenData.getTenantId());
filter.setAppCode(tokenData.getAppCode());
List<OnlineQuickForm> resultList = onlineQuickFormMapper.getOnlineFormList(filter, orderBy);
// 快捷表单固定为快捷表单类型,回填用于 formTypeDictMap 关联展示
resultList.forEach(qf -> qf.setFormType(FormType.QUICK_FORM));
return resultList;
}
/**
* 获取主表的查询结果以及主表关联的字典数据和一对一从表数据以及一对一从表的字典数据
* 该查询会涉及到一对一从表的关联过滤或一对多从表的嵌套关联过滤因此性能不如单表过滤
* 如果仅仅需要获取主表数据请移步(getOnlineFormList)以便获取更好的查询性能
*
* @param filter 主表过滤对象
* @param orderBy 排序参数
* @return 查询结果集
*/
@Override
public List<OnlineQuickForm> getOnlineFormListWithRelation(OnlineQuickForm filter, String orderBy) {
List<OnlineQuickForm> resultList = this.getOnlineFormList(filter, orderBy);
// 在缺省生成的代码中,如果查询结果resultList不是Page对象,说明没有分页,那么就很可能是数据导出接口调用了当前方法。
// 为了避免一次性的大量数据关联,规避因此而造成的系统运行性能冲击,这里手动进行了分批次读取,开发者可按需修改该值。
int batchSize = resultList instanceof Page ? 0 : 1000;
// 用 full() 加载 @RelationOneToMany 的 onlineQuickFormFields 等全部关联
this.buildRelationForDataList(resultList, MyRelationParam.full(), batchSize);
return resultList;
}
}

584
common/common-online/src/main/java/apelet/common/online/util/QuickFormWidgetUtil.java

@ -0,0 +1,584 @@
package apelet.common.online.util;
import apelet.common.online.model.OnlineQuickFormField;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
/**
* 快捷表单 widgetJson 生成工具类
* <p>
* 基于参考 json 模板列表页/编辑页根据快捷表单字段生成 pc/mobile 两套组件json
* 模板只提供结构外壳operationListtableWidgetlistCardleftWidget
* 字段控件绑定关系按钮 formId 由字段数据重新生成
*
* @author chenchuchuan
* @date 2026-08-18
*/
public class QuickFormWidgetUtil {
private static final String LIST_TEMPLATE = "quick-form/quick-form-list.json";
private static final String EDIT_TEMPLATE = "quick-form/quick-form-edit.json";
private static final JSONObject LIST_TEMPLATE_JSON = load(LIST_TEMPLATE);
private static final JSONObject EDIT_TEMPLATE_JSON = load(EDIT_TEMPLATE);
private QuickFormWidgetUtil() {
}
private static JSONObject load(String path) {
try (InputStream in = QuickFormWidgetUtil.class.getClassLoader().getResourceAsStream(path)) {
if (in == null) {
throw new IllegalStateException("快捷表单模板不存在: " + path);
}
return JSON.parseObject(new String(IoUtil.readBytes(in), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new IllegalStateException("加载快捷表单模板失败: " + path, e);
}
}
/**
* 生成编辑页 widgetJsonpc + mobile
*
* @param fields 快捷表单字段
* @param bindId 控件绑定的数据id参考json中编辑页/列表页为同一个快捷表单id
* @param masterTableId 关联主表id
* @param masterTableName 关联主表名
* @return widgetJson 根对象
*/
public static JSONObject buildEditWidgetJson(List<OnlineQuickFormField> fields, Long bindId, Long masterTableId, String masterTableName) {
JSONObject json = JSON.parseObject(EDIT_TEMPLATE_JSON.toJSONString());
for (String device : new String[]{"pc", "mobile"}) {
JSONObject deviceObj = json.getJSONObject(device);
JSONArray widgetList = buildWidgetList(fields, device, bindId, masterTableId);
// 编辑页控件固定 supportOperation=false,与参考json一致
for (int i = 0; i < widgetList.size(); i++) {
widgetList.getJSONObject(i).put("supportOperation", false);
}
deviceObj.put("widgetList", widgetList);
setSaveButtonEnabled(deviceObj);
rebuildMeta(deviceObj, fields, device, masterTableName, null);
}
return json;
}
/**
* 生成列表页 widgetJsonpc + mobile
* <p>
* 列表页结构文档补充逻辑
* <ul>
* <li>tableWidget 恒存在pc widgetType=100 表格列来自 isList 字段的 tableColumnListmobile widgetType=103 列表卡片只放 isList 字段</li>
* <li>查询区pc widgetListmobile tableWidget.childWidgetListlistCard 之后只放 isFilter 的字段</li>
* <li>其余字段只进编辑页不进列表页</li>
* </ul>
*
* @param fields 快捷表单字段
* @param listFormId 列表页 zz_online_form 主键用于 tableWidget variableName
* @param editFormId 编辑页 zz_online_form 主键关联到新建/编辑按钮
* @param bindId 控件绑定的数据id参考json中编辑页/列表页为同一个快捷表单id
* @param masterTableId 关联主表id
* @param masterTableName 关联主表名
* @param columnIdMap 字段名(小写) zz_online_column.column_id用于 tableColumnList 回填
* @return widgetJson 根对象
*/
public static JSONObject buildListWidgetJson(List<OnlineQuickFormField> fields, Long listFormId, Long editFormId, Long bindId, Long masterTableId, String masterTableName, Map<String, Long> columnIdMap) {
JSONObject json = JSON.parseObject(LIST_TEMPLATE_JSON.toJSONString());
String tableVar = "table" + listFormId;
List<OnlineQuickFormField> listFields = fields == null ? Collections.emptyList() : fields.stream().filter(QuickFormWidgetUtil::isListField).collect(Collectors.toList());
List<OnlineQuickFormField> filterFields = fields == null ? Collections.emptyList() : fields.stream().filter(QuickFormWidgetUtil::isFilterField).collect(Collectors.toList());
for (String device : new String[]{"pc", "mobile"}) {
JSONObject deviceObj = json.getJSONObject(device);
setButtonFormId(deviceObj, editFormId);
JSONObject tableWidget = deviceObj.getJSONObject("tableWidget");
if (tableWidget != null) {
tableWidget.put("variableName", tableVar);
JSONObject bindData = tableWidget.getJSONObject("bindData");
if (bindData == null) {
bindData = new JSONObject();
tableWidget.put("bindData", bindData);
}
if ("pc".equals(device)) {
// 参考:pc tableWidget 才有 id/tableId,mobile 只有 defaultValue/dataType
bindData.put("id", bindId);
bindData.put("tableId", masterTableId);
}
}
if ("pc".equals(device)) {
deviceObj.put("widgetList", buildWidgetList(filterFields, device, bindId, masterTableId));
if (tableWidget != null) {
JSONObject props = tableWidget.getJSONObject("props");
if (props != null) {
props.put("tableColumnList", buildTableColumnList(listFields, masterTableId, columnIdMap));
}
}
rebuildMeta(deviceObj, filterFields, device, masterTableName, tableVar);
} else {
rebuildMobileList(deviceObj, listFields, filterFields, bindId, listFormId, masterTableId);
rebuildMeta(deviceObj, mergeFields(listFields, filterFields), device, masterTableName, tableVar);
}
}
return json;
}
/**
* 是否列表展示字段
*/
private static boolean isListField(OnlineQuickFormField field) {
return field != null && field.getIsList() != null && field.getIsList() == 1;
}
/**
* 是否过滤字段是过滤条件即进查询区filterType 决定数据过滤方式不影响控件展示
*/
private static boolean isFilterField(OnlineQuickFormField field) {
return field != null && field.getIsFilter() != null && field.getIsFilter() == 1;
}
/**
* 合并列表展示字段与过滤字段按字段名去重列表展示优先
*/
private static List<OnlineQuickFormField> mergeFields(List<OnlineQuickFormField> listFields, List<OnlineQuickFormField> filterFields) {
Map<String, OnlineQuickFormField> merged = new LinkedHashMap<>();
if (listFields != null) {
for (OnlineQuickFormField field : listFields) {
merged.put(field.getFieldName(), field);
}
}
if (filterFields != null) {
for (OnlineQuickFormField field : filterFields) {
merged.putIfAbsent(field.getFieldName(), field);
}
}
return new ArrayList<>(merged.values());
}
/**
* 构建 pc 列表表格列tableColumnList仅含列表展示字段
*/
private static JSONArray buildTableColumnList(List<OnlineQuickFormField> listFields, Long masterTableId, Map<String, Long> columnIdMap) {
JSONArray columnList = new JSONArray();
if (listFields == null) {
return columnList;
}
for (int i = 0; i < listFields.size(); i++) {
OnlineQuickFormField field = listFields.get(i);
String fieldName = field.getFieldName();
Long columnId = columnIdMap == null ? null : columnIdMap.get(fieldName.toLowerCase());
JSONObject column = new JSONObject();
column.put("columnId", columnId == null ? "" : String.valueOf(columnId));
column.put("tableId", String.valueOf(masterTableId));
column.put("showName", StringUtils.isNotBlank(field.getFieldRemark()) ? field.getFieldRemark() : fieldName);
column.put("sortable", false);
// 列表表格列固定 fieldType=0,与参考json一致;按真实字段类型映射会导致日期等列无法展示
column.put("fieldType", 0);
column.put("comType", 41);
column.put("switchClose", "false");
column.put("selectCode", 1);
column.put("switchOpen", "true");
column.put("showColumn", true);
column.put("showRequired", false);
column.put("F7Info", "");
column.put("f7ShowName", StringUtils.isNotBlank(field.getDisplayField()) ? field.getDisplayField() : "");
column.put("id", columnId == null ? "column_" + i : "column_" + columnId);
column.put("soleId", (columnId == null ? String.valueOf(i) : columnId + "") + i);
String showFieldName = fieldName;
if (StringUtils.isNotBlank(field.getDisplayField())) {
showFieldName = showFieldName + "." + field.getDisplayField();
}
column.put("showFieldName", showFieldName);
columnList.add(column);
}
return columnList;
}
/**
* 移动端过滤控件类型模板专用类型映射未映射的类型复用字段控件本身类型
*/
private static int resolveMobileFilterType(Integer fieldType) {
if (fieldType == null) {
return 502;
}
switch (fieldType) {
case 1:
return 502; // 文本 → 移动端文本过滤框
case 7:
return 500; // 开关/单选 → 移动端字典过滤
default:
return fieldType; // 下拉(10)、F7(402) 等保持原类型
}
}
/**
* 生成移动端过滤控件查询区放在列表卡片之后的独立过滤组件
*/
private static JSONObject buildMobileFilterWidget(OnlineQuickFormField field, Long formId, Long masterTableId) {
return buildFieldWidget(field, resolveMobileFilterType(field.getFieldType()), "mobile", formId, masterTableId);
}
// ============ 字段控件生成 ============
private static JSONArray buildWidgetList(List<OnlineQuickFormField> fields, String device, Long formId, Long masterTableId) {
JSONArray widgetList = new JSONArray();
if (fields == null) {
return widgetList;
}
for (OnlineQuickFormField field : fields) {
widgetList.add(buildFieldWidget(field, device, formId, masterTableId));
}
return widgetList;
}
private static JSONObject buildFieldWidget(OnlineQuickFormField field, String device, Long formId, Long masterTableId) {
return buildFieldWidget(field, resolveWidgetType(field.getFieldType(), device), device, formId, masterTableId);
}
private static JSONObject buildFieldWidget(OnlineQuickFormField field, int wt, String device, Long formId, Long masterTableId) {
String varName = StrUtil.toCamelCase(field.getFieldName());
String showName = StringUtils.isNotBlank(field.getFieldRemark()) ? field.getFieldRemark() : field.getFieldName();
JSONObject bindData = new JSONObject();
JSONObject defaultValue = new JSONObject();
defaultValue.put("valueType", 20);
bindData.put("defaultValue", defaultValue);
bindData.put("dataType", 0);
bindData.put("columnFieldName", field.getFieldName());
bindData.put("tableId", masterTableId);
bindData.put("id", formId);
if (wt == 402 && field.getRefTableId() != null) {
bindData.put("slaveTableId", field.getRefTableId());
if (StrUtil.isNotBlank(field.getRefTableName())) {
bindData.put("slaveTableName", field.getRefTableName());
}
}
JSONObject widget = new JSONObject();
widget.put("widgetType", wt);
widget.put("bindData", bindData);
widget.put("showName", showName);
widget.put("variableName", varName);
widget.put("props", buildProps(wt, field, device));
widget.put("eventList", new JSONArray());
widget.put("childWidgetList", new JSONArray());
widget.put("style", new JSONObject());
if ("mobile".equals(device)) {
widget.put("supportOperation", false);
}
return widget;
}
private static int resolveWidgetType(Integer fieldType, String device) {
int wt = fieldType == null ? 1 : fieldType;
if ("mobile".equals(device) && wt == 3) {
return 15;
}
return wt;
}
/**
* 元数据widgetListMapData里的控件类型移动端纯过滤字段用过滤控件类型其余用字段控件类型
*/
private static int resolveMetaWidgetType(OnlineQuickFormField field, String device) {
if ("mobile".equals(device) && !isListField(field) && isFilterField(field)) {
return resolveMobileFilterType(field.getFieldType());
}
return resolveWidgetType(field.getFieldType(), device);
}
private static JSONObject buildProps(int wt, OnlineQuickFormField field, String device) {
int span = "mobile".equals(device) ? 24 : 12;
boolean required = field.getIsEmpty() != null && field.getIsEmpty() == 0;
JSONObject props = new JSONObject();
props.put("span", span);
props.put("required", required);
props.put("disabled", false);
props.put("hide", false);
props.put("styleCssText", "");
props.put("actions", new JSONObject());
JSONObject dictInfo = new JSONObject();
dictInfo.put("paramList", new JSONArray());
if (field.getRefDict() != null && wt != 402) {
dictInfo.put("dictId", field.getRefDict());
}
props.put("dictInfo", dictInfo);
if (StringUtils.isNotBlank(field.getDefaultValue())) {
props.put("placeholder", field.getDefaultValue());
}
switch (wt) {
case 1: // 文本输入框
props.put("type", "text");
props.put("inputHight", 40);
props.put("textLineTb", false);
props.put("inputNumMin", 0);
props.put("inputNumMax", 0);
props.put("plarceColor", "");
props.put("inputWidth", "");
props.put("transparentTitle", 0);
props.put("inputWidthBlan", false);
props.put("colorTitle", "");
props.put("colorInput", "");
props.put("labelWight", 2);
props.put("inputHeight", 2);
if (!props.containsKey("placeholder")) props.put("placeholder", "");
props.put("show-password", false);
props.put("show-word-limit", false);
props.put("inCopy", false);
props.put("inCopyImg", false);
props.put("readonly", false);
props.put("teartextMore", false);
props.put("borderBottom", false);
props.put("textRedBd", false);
props.put("readyModeHiddenEmpty", false);
props.put("tooltipText", "");
break;
case 3: // 数字输入框
props.put("step", 1);
props.put("controls", true);
if (!props.containsKey("placeholder")) props.put("placeholder", "");
props.put("tooltipText", "");
break;
case 15: // 移动端数字输入框
props.put("labelWight", 2);
props.put("step", 1);
props.put("input-width", "32px");
props.put("button-size", "28px");
props.put("integer", false);
break;
case 7: // 开关/单选
props.put("inputHight", 40);
props.put("inputWidth", "");
props.put("transparentTitle", 0);
props.put("colorTitle", "");
props.put("labelWight", 2);
props.put("direction", "horizontal");
props.put("borderBottom", false);
props.put("icon-size", "20px");
props.put("tooltipText", "");
break;
case 10: // 下拉选择框
if (!props.containsKey("placeholder")) props.put("placeholder", "");
props.put("inputHight", "44");
props.put("inputWidth", "");
props.put("transparentTitle", 0);
props.put("colorTitle", "");
props.put("colorInput", "");
props.put("labelWight", 2);
props.put("borderBottom", false);
props.put("multiple", false);
props.put("fiterSaicn", false);
props.put("inputSeletUr", false);
props.put("readyModeHiddenEmpty", false);
props.put("tooltipText", "");
break;
case 20: // 日期选择框
if (!props.containsKey("placeholder")) props.put("placeholder", "");
props.put("type", "date");
props.put("tooltipText", "");
break;
case 402: // 用户/关联选择
props.put("inCopy", false);
props.put("plarceColor", "");
props.put("transparentTitle", 0);
props.put("inputHight", 40);
props.put("inputWidth", "");
if (!props.containsKey("placeholder")) props.put("placeholder", "");
props.put("colorTitle", "");
props.put("colorInput", "");
props.put("wightInput", 0);
props.put("borderBottom", false);
props.put("fiterSaicn", false);
props.put("relativeTable", buildRelativeTable(field));
props.put("isMultiple", false);
props.put("readyModeHiddenEmpty", false);
props.put("tooltipText", "");
break;
default:
props.put("type", "text");
props.put("inputHight", 40);
if (!props.containsKey("placeholder")) props.put("placeholder", "");
break;
}
return props;
}
private static JSONObject buildRelativeTable(OnlineQuickFormField field) {
JSONObject relativeTable = new JSONObject();
relativeTable.put("relativeFormId", "");
relativeTable.put("relativeTableName", StrUtil.isNotBlank(field.getRefTableName()) ? field.getRefTableName() : "");
relativeTable.put("relativeColumn", "id");
relativeTable.put("relationId", "");
relativeTable.put("datasourceId", "");
relativeTable.put("variableName", "");
relativeTable.put("displayField", StringUtils.isNotBlank(field.getDisplayField()) ? field.getDisplayField() : "fname");
relativeTable.put("FieldFormId", "");
relativeTable.put("fieldAddSwich", "");
relativeTable.put("relativeFormType", 2);
relativeTable.put("relativeFormCode", StrUtil.isNotBlank(field.getRefFormCode()) ? field.getRefFormCode() : "query_all_pm_user");
relativeTable.put("onlineFormId", "");
relativeTable.put("onlineFormName", "");
return relativeTable;
}
// ============ 移动端列表页结构 ============
private static void rebuildMobileList(JSONObject deviceObj, List<OnlineQuickFormField> listFields, List<OnlineQuickFormField> filterFields, Long bindId, Long listFormId, Long masterTableId) {
JSONObject tableWidget = deviceObj.getJSONObject("tableWidget");
if (tableWidget == null) {
return;
}
JSONArray childList = tableWidget.getJSONArray("childWidgetList");
if (childList == null) {
childList = new JSONArray();
tableWidget.put("childWidgetList", childList);
}
// 找到 listCard(widgetType=303),保留 image(42) 子控件,其余字段重新生成
JSONObject listCard = null;
for (int i = 0; i < childList.size(); i++) {
JSONObject child = childList.getJSONObject(i);
if (child != null && 303 == child.getIntValue("widgetType")) {
listCard = child;
break;
}
}
if (listCard == null) {
listCard = new JSONObject();
listCard.put("widgetType", 303);
listCard.put("bindData", baseBindData());
listCard.put("showName", "listCard");
listCard.put("variableName", "listCard" + listFormId);
JSONObject props = new JSONObject();
props.put("imagePosition", "left");
props.put("imageAlign", "center");
props.put("hide", false);
props.put("styleCssText", "");
props.put("actions", new JSONObject());
listCard.put("props", props);
listCard.put("eventList", new JSONArray());
listCard.put("style", new JSONObject());
listCard.put("supportOperation", false);
}
JSONArray cardChildren = listCard.getJSONArray("childWidgetList");
if (cardChildren == null) {
cardChildren = new JSONArray();
}
JSONArray newCardChildren = new JSONArray();
for (int i = 0; i < cardChildren.size(); i++) {
JSONObject child = cardChildren.getJSONObject(i);
if (child != null && 42 == child.getIntValue("widgetType")) {
newCardChildren.add(child);
break;
}
}
if (listFields != null) {
for (OnlineQuickFormField field : listFields) {
newCardChildren.add(buildFieldWidget(field, "mobile", bindId, masterTableId));
}
}
listCard.put("childWidgetList", newCardChildren);
// 列表卡片 + 过滤控件:过滤字段作为独立查询组件放在 listCard 之后
JSONArray newChildList = new JSONArray();
newChildList.add(listCard);
if (filterFields != null) {
for (OnlineQuickFormField field : filterFields) {
newChildList.add(buildMobileFilterWidget(field, bindId, masterTableId));
}
}
tableWidget.put("childWidgetList", newChildList);
}
private static JSONObject baseBindData() {
JSONObject defaultValue = new JSONObject();
defaultValue.put("valueType", 20);
JSONObject bindData = new JSONObject();
bindData.put("defaultValue", defaultValue);
bindData.put("dataType", 0);
return bindData;
}
// ============ 按钮 formId ============
private static void setButtonFormId(JSONObject deviceObj, Long editFormId) {
JSONArray opList = deviceObj.getJSONArray("operationList");
if (opList == null) {
return;
}
for (int i = 0; i < opList.size(); i++) {
JSONObject op = opList.getJSONObject(i);
Integer type = op.getInteger("type");
if ((type != null && (type == 0 || type == 1)) && Boolean.TRUE.equals(op.getBoolean("enabled"))) {
op.put("formId", editFormId);
}
}
}
private static void setSaveButtonEnabled(JSONObject deviceObj) {
JSONArray opList = deviceObj.getJSONArray("operationList");
if (opList == null) {
return;
}
for (int i = 0; i < opList.size(); i++) {
JSONObject op = opList.getJSONObject(i);
if (30 == op.getIntValue("type")) {
op.put("enabled", true);
JSONArray pluginList = new JSONArray();
JSONObject plugin = new JSONObject();
plugin.put("name", "保存");
plugin.put("class", "apelet.common.online.plugin.pre.SaveOperationServicePlugIn");
plugin.put("order", "1");
plugin.put("describe", "保存");
plugin.put("isEnable", true);
plugin.put("_disabled", true);
plugin.put("_X_ROW_KEY", "row_" + i);
pluginList.add(plugin);
op.put("pluginList", pluginList);
}
}
}
// ============ 元数据(mapData/keyNameObj) ============
private static void rebuildMeta(JSONObject deviceObj, List<OnlineQuickFormField> fields, String device, String masterTableName, String tableVar) {
JSONObject widgetListMapData = new JSONObject();
if (fields != null) {
for (OnlineQuickFormField field : fields) {
String var = StrUtil.toCamelCase(field.getFieldName());
JSONObject m = new JSONObject();
m.put("widgetType", resolveMetaWidgetType(field, device));
m.put("variableName", var);
widgetListMapData.put(var, m);
}
}
JSONObject otherWidgetListMapData = new JSONObject();
if (StringUtils.isNotBlank(tableVar)) {
JSONObject tableMap = new JSONObject();
tableMap.put("widgetType", 100);
tableMap.put("variableName", tableVar);
widgetListMapData.put(tableVar, tableMap);
otherWidgetListMapData.put(tableVar, tableMap);
}
deviceObj.put("widgetListMapData", widgetListMapData);
deviceObj.put("otherWidgetListMapData", otherWidgetListMapData);
deviceObj.put("keyNameObj", buildKeyNameObj(fields, masterTableName));
}
private static JSONObject buildKeyNameObj(List<OnlineQuickFormField> fields, String masterTableName) {
JSONObject keyNameObj = new JSONObject();
if (fields == null) {
return keyNameObj;
}
for (OnlineQuickFormField field : fields) {
JSONArray arr = new JSONArray();
arr.add(masterTableName + "." + StrUtil.toCamelCase(field.getFieldName()));
keyNameObj.put(field.getFieldName(), arr);
}
return keyNameObj;
}
}

2
common/common-online/src/main/java/apelet/common/online/util/WidgetFieldTypeMapping.java

@ -17,6 +17,8 @@ public class WidgetFieldTypeMapping {
DEFAULT_LENGTH.put(1, 255); DEFAULT_LENGTH.put(1, 255);
// 数字输入框 → int // 数字输入框 → int
WIDGET_TO_FIELD_TYPE.put(3, "1"); WIDGET_TO_FIELD_TYPE.put(3, "1");
// 移动端数字输入框 → int
WIDGET_TO_FIELD_TYPE.put(15, "1");
// 开关组件 → int // 开关组件 → int
WIDGET_TO_FIELD_TYPE.put(5, "1"); WIDGET_TO_FIELD_TYPE.put(5, "1");
// 滑块组件 → decimal // 滑块组件 → decimal

19
common/common-online/src/main/java/apelet/common/online/vo/OnlineFormVo.java

@ -1,5 +1,6 @@
package apelet.common.online.vo; package apelet.common.online.vo;
import apelet.common.online.model.OnlineQuickFormField;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@ -102,6 +103,18 @@ public class OnlineFormVo {
private Long updateUserId; private Long updateUserId;
/** /**
* 快捷表单ID
*/
@Schema(description = "快捷表单ID")
private Long quickFormId;
/**
* 排版类型
*/
@Schema(description = "排版类型")
private Integer layoutType;
/**
* masterTableId 的一对一关联数据对象数据对应类型为OnlineTableVo * masterTableId 的一对一关联数据对象数据对应类型为OnlineTableVo
*/ */
@Schema(description = "asterTableId 的一对一关联数据对象") @Schema(description = "asterTableId 的一对一关联数据对象")
@ -124,4 +137,10 @@ public class OnlineFormVo {
*/ */
@Schema(description = "当前表单关联的数据源Id集合") @Schema(description = "当前表单关联的数据源Id集合")
private List<Long> datasourceIdList; private List<Long> datasourceIdList;
/**
* 在线快捷表单字段实体对象
*/
@Schema(description = "在线快捷表单字段实体对象")
private List<OnlineQuickFormField> onlineQuickFormFields;
} }

1
common/common-online/src/main/resources/quick-form/quick-form-edit.json

File diff suppressed because one or more lines are too long

1
common/common-online/src/main/resources/quick-form/quick-form-list.json

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save