This commit is contained in:
geht
2026-07-30 17:02:10 +08:00
352 changed files with 43647 additions and 385 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -230,6 +230,10 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/sys/category/anon/**", "anon");
// 桌面端用户反同步批量上报Outbox -> /sys/sync/batch
filterChainDefinitionMap.put("/sys/sync/batch", "anon");
//update-begin---author:cursor ---date:2026-07-20 for【设备库桥接】桌面代理免密接口-----------
filterChainDefinitionMap.put("/sys/device/agent/**", "anon");
filterChainDefinitionMap.put("/xslmes/deviceAgent/anon/**", "anon");
//update-end---author:cursor ---date:2026-07-20 for【设备库桥接】桌面代理免密接口-----------
// 添加自己的过滤器并且取名为jwt
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);

View File

@@ -1258,7 +1258,111 @@ JeecgUniapp-master/src/pages/rawMaterialInspect/rawMaterialInspect.vue
JeecgUniapp-master/src/pages/rawMaterialInspect/inspectResult.vue
JeecgUniapp-master/src/common/work.ts
-- author:GHT---date:20260730--for: <20><>XSLMES-20260730-DING-PULL<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̨<EFBFBD><EFBFBD><EFBFBD>ֶ<EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܼ<EFBFBD><EFBFBD><EFBFBD> ---
-- author:jiangxh---date:20260728--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD>Ϊͬһ<CDAC><EFBFBD><E8B1B8>Ψһ ---
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerConditionService.java
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerConditionServiceImpl.java
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerConditionController.java
jeecgboot-vue3/src/views/xslmes/mesXslMixerCondition/MesXslMixerCondition.api.ts
jeecgboot-vue3/src/views/xslmes/mesXslMixerCondition/MesXslMixerCondition.data.ts
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϸ<EFBFBD><CFB8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>%/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λС<CEBB><D0A1> ---
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/components/MesXslFormulaSpecModal.vue
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD><CFB5><33><CEBB>ĸ<EFBFBD><C4B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><33><CEBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2λ ---
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD>λС<CEBB><D0A1> ---
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD>ϲ<EFBFBD>ѯ<EFBFBD><D1AF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD>ϼ<EFBFBD>ʾ<EFBFBD><CABE>ѡ<EFBFBD>ϸ<EFBFBD>Ϊģ<CEAA><C4A3><EFBFBD><EFBFBD>ѯ ---
jeecgboot-vue3/src/views/mes/material/materialQuery.util.ts
jeecgboot-vue3/src/views/mes/material/MesMixerMaterialList.vue
jeecgboot-vue3/src/views/mes/material/MesMaterialList.vue
jeecgboot-vue3/src/views/mes/material/modules/MesMixerMaterialSelectModal.vue
jeecgboot-vue3/src/views/mes/material/modules/MesMaterialSelectModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD>ϡ<EFBFBD><CFA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<E0BCAD><D0A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ψһ ---
jeecgboot-vue3/src/views/mes/material/MesMixerMaterial.api.ts
jeecgboot-vue3/src/views/mes/material/MesMixerMaterial.data.ts
jeecgboot-vue3/src/views/mes/material/MesMaterial.api.ts
jeecgboot-vue3/src/views/mes/material/MesMaterial.data.ts
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD><CFA1><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD>ϱ<EFBFBD><CFB1><EFBFBD><EFBFBD><EFBFBD>365<36><35> ---
jeecgboot-vue3/src/views/mes/material/MesMixerMaterial.data.ts
yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialEditDialogViewModel.cs
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϸ<EFBFBD><CFB8><EFBFBD><EFBFBD>%/<2F><><EFBFBD>ͳһ<CDB3><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λС<CEBB><D0A1> ---
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_171__mes_xsl_formula_spec_line_decimal3.sql
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/components/MesXslFormulaSpecModal.vue
-- author:jiangxh---date:20260729--for: <20><>WebSocket<65><74>pongTimeout 1s<31><73>10s <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ---
jeecgboot-vue3/src/hooks/web/useWebSocket.ts
-- author:jiangxh---date:20260729--for: <20><>WebSocket<65><74><EFBFBD>޸<EFBFBD><DEB8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Redis<69>ذ<EFBFBD><D8B0><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>Ƶ<EFBFBD><C6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ---
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/message/websocket/WebSocket.java
jeecgboot-vue3/src/hooks/web/useWebSocket.ts
-- author:jiangxh---date:20260729--for: <20><>HTTP<54><50>Ĭ<EFBFBD>ϳ<EFBFBD>ʱ10s<30><73>60s<30><73><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>120s ---
jeecgboot-vue3/src/utils/http/axios/index.ts
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.api.ts
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD>ɡ<EFBFBD>B/F<><46><EFBFBD>ϸ<EFBFBD>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>ƥ<EFBFBD><EFBFBD><E4A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE>ά<EFBFBD><CEAC> ---
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD>ɡ<EFBFBD><C9A1>ս<EFBFBD><D5BD><EFBFBD>ϸĸ<CFB8><C4B8><EFBFBD>д<EFBFBD><D0B4><EFBFBD><EBBDBA><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ---
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
-- author:jiangxh---date:20260729--for: <20><>ѡ<EFBFBD>ϵ<EFBFBD><CFB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE>/<2F><><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD>ȥ<EFBFBD><C8A5><EFBFBD><EFBFBD><EFBFBD>ϱ<EFBFBD><CFB1> ---
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue
jeecgboot-vue3/src/views/mes/material/modules/MesMaterialSelectModal.vue
jeecgboot-vue3/src/views/mes/material/materialQuery.util.ts
-- author:jiangxh---date:20260730--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD>ɡ<EFBFBD><C9A1>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɻ<EFBFBD><C9BB><EFBFBD>ʾ<EFBFBD><CABE>Ԥ<EFBFBD><D4A4>ʱ<EFBFBD><CAB1>ƥ<EFBFBD><EFBFBD><E4BDBA><EFBFBD>б<EFBFBD> ---
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/components/MesXslFormulaGenerateMixingModal.vue
-- author:jiangxh---date:20260730--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD>ɡ<EFBFBD>B/Fƥ<46><C6A5><EFBFBD>Ž<EFBFBD>ǰ׺+<2B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƴSA01<30>Ȱ汾<C8B0><E6B1BE>׺ ---
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
-- author:jiangxh---date:20260730--for: <20><><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD>ȥ<EFBFBD><C8A5><EFBFBD><EFBFBD><EFBFBD>ϱ<EFBFBD><CFB1>ҵ<EFBFBD><D2B5><EFBFBD>ֶΣ<D6B6>ƥ<EFBFBD><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ---
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/entity/MesMaterial.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMaterialController.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/service/IMesMaterialService.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/service/impl/MesMaterialServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java
jeecgboot-vue3/src/views/mes/material/MesMaterial.data.ts
jeecgboot-vue3/src/views/mes/material/MesMaterial.api.ts
jeecgboot-vue3/src/views/mes/material/modules/MesMaterialSelectModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts
-- author:jiangxh---date:20260730--for: <20><><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD><CFA1>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>ȥ<EFBFBD><C8A5><EFBFBD><EFBFBD>ŸĶ<C5B8> ---
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/entity/MesMaterial.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMaterialController.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/service/IMesMaterialService.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/service/impl/MesMaterialServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java
jeecgboot-vue3/src/views/mes/material/MesMaterial.data.ts
jeecgboot-vue3/src/views/mes/material/MesMaterial.api.ts
jeecgboot-vue3/src/views/mes/material/modules/MesMaterialSelectModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts
-- author:jiangxh---date:20260730--for: <20><><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD><CFA1><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD><D8A3><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EBBDBA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB> ---
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMaterialController.java
jeecgboot-vue3/src/views/mes/material/MesMaterial.data.ts
-- author:GHT---date:20260730--for: <20><>XSLMES-20260730-DING-PULL<4C><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̨<EFBFBD><CCA8><EFBFBD>ֶ<EFBFBD><D6B6><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǿ<EFBFBD><C7BF><EFBFBD><EFBFBD><EFBFBD>ܼ<EFBFBD><DCBC><EFBFBD> ---
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationForceReplayHolder.java
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationOrchestrator.java
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingApprovalManualPullService.java
@@ -1267,4 +1371,4 @@ jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/app
jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_164__mes_xsl_approval_record_pull_ding_result.sql
jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecordList.vue
jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecord.api.ts
-- author:GHT---date:20260730--for: <20><>XSLMES-20260730-DING-PULL<4C><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̨<EFBFBD><CCA8><EFBFBD>ֶ<EFBFBD><D6B6><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܼ<EFBFBD><EFBFBD><EFBFBD> ---
-- author:GHT---date:20260730--for: <20><>XSLMES-20260730-DING-PULL<4C><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̨<EFBFBD><CCA8><EFBFBD>ֶ<EFBFBD><D6B6><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǿ<EFBFBD><C7BF><EFBFBD><EFBFBD><EFBFBD>ܼ<EFBFBD><DCBC><EFBFBD> ---

View File

@@ -36,5 +36,11 @@
<artifactId>jeecg-module-print</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- 设备同步/桌面代理命令总线 -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-module-device-sync</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -130,6 +130,7 @@ public class MesXslAutoSmallMaterialDemandPlanController {
}
sql.append("GROUP BY ");
sql.append(statDateExpr).append(", ");
if (groupedByMachine) {
sql.append(machineExpr).append(", ");
}
@@ -160,14 +161,20 @@ public class MesXslAutoSmallMaterialDemandPlanController {
private String buildOrderBy(boolean groupedByMachine) {
if (groupedByMachine) {
return " ORDER BY machineName, rawMaterialName";
return " ORDER BY statDate DESC, machineName, rawMaterialName";
}
return " ORDER BY rawMaterialName";
return " ORDER BY statDate DESC, rawMaterialName";
}
private String buildRowId(MesXslSmallMaterialDemandPlanSummary row, boolean groupedByMachine, int seq) {
String machine = groupedByMachine ? StringUtils.defaultString(row.getMachineName()) : "ALL";
return machine + "_" + StringUtils.defaultString(row.getRawMaterialName()) + "_" + seq;
return StringUtils.defaultString(row.getStatDate())
+ "_"
+ machine
+ "_"
+ StringUtils.defaultString(row.getRawMaterialName())
+ "_"
+ seq;
}
private String trim(String value) {

View File

@@ -130,6 +130,7 @@ public class MesXslManualSmallMaterialDemandPlanController {
}
sql.append("GROUP BY ");
sql.append(statDateExpr).append(", ");
if (groupedByMachine) {
sql.append(machineExpr).append(", ");
}
@@ -160,14 +161,20 @@ public class MesXslManualSmallMaterialDemandPlanController {
private String buildOrderBy(boolean groupedByMachine) {
if (groupedByMachine) {
return " ORDER BY machineName, rawMaterialName";
return " ORDER BY statDate DESC, machineName, rawMaterialName";
}
return " ORDER BY rawMaterialName";
return " ORDER BY statDate DESC, rawMaterialName";
}
private String buildRowId(MesXslSmallMaterialDemandPlanSummary row, boolean groupedByMachine, int seq) {
String machine = groupedByMachine ? StringUtils.defaultString(row.getMachineName()) : "ALL";
return machine + "_" + StringUtils.defaultString(row.getRawMaterialName()) + "_" + seq;
return StringUtils.defaultString(row.getStatDate())
+ "_"
+ machine
+ "_"
+ StringUtils.defaultString(row.getRawMaterialName())
+ "_"
+ seq;
}
private String trim(String value) {

View File

@@ -0,0 +1,60 @@
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 org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.xslmes.entity.MesXslMillAction;
import org.jeecg.modules.xslmes.service.IMesXslMillActionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/**
* MES 开炼机动作接收表
*/
@Tag(name = "开炼机动作接收")
@RestController
@RequestMapping("/xslmes/mesXslMillAction")
public class MesXslMillActionController extends JeecgController<MesXslMillAction, IMesXslMillActionService> {
@Autowired private IMesXslMillActionService mesXslMillActionService;
@Operation(summary = "开炼机动作接收-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslMillAction>> queryPageList(
MesXslMillAction model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslMillAction> queryWrapper = QueryGenerator.initQueryWrapper(model, req.getParameterMap());
queryWrapper.orderByDesc("update_time");
IPage<MesXslMillAction> pageList = mesXslMillActionService.page(new Page<>(pageNo, pageSize), queryWrapper);
return Result.OK(pageList);
}
@Operation(summary = "开炼机动作接收-通过id查询")
@GetMapping("/queryById")
public Result<MesXslMillAction> queryById(@RequestParam(name = "id", required = true) String id) {
MesXslMillAction entity = mesXslMillActionService.getById(id);
if (entity == null) {
return Result.error("未找到对应数据");
}
return Result.OK(entity);
}
@RequiresPermissions("xslmes:mes_xsl_mill_action:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, MesXslMillAction model) {
return super.exportXls(request, model, MesXslMillAction.class, "开炼机动作接收");
}
}

View File

@@ -110,31 +110,43 @@ public class MesXslMixerConditionController
return Result.OK(entity);
}
@Operation(summary = "校验条件名称是否重复")
@Operation(summary = "校验条件名称是否重复(同一设备下)")
@GetMapping("/checkConditionName")
public Result<String> checkConditionName(
@RequestParam(name = "conditionName", required = true) String conditionName,
@RequestParam(name = "equipmentId", required = false) String equipmentId,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(conditionName) || conditionName.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesXslMixerConditionService.isConditionNameDuplicated(conditionName, dataId)) {
return Result.error("条件名称不能重复");
//update-begin---author:jiangxh ---date:20260728 for【密炼机条件】条件名称按设备唯一-----------
if (oConvertUtils.isEmpty(equipmentId) || equipmentId.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesXslMixerConditionService.isConditionNameDuplicated(conditionName, equipmentId, dataId)) {
return Result.error("同一设备下条件名称不能重复");
}
//update-end---author:jiangxh ---date:20260728 for【密炼机条件】条件名称按设备唯一-----------
return Result.OK("该值可用!");
}
@Operation(summary = "校验条件代码是否重复")
@Operation(summary = "校验条件代码是否重复(同一设备下)")
@GetMapping("/checkConditionCode")
public Result<String> checkConditionCode(
@RequestParam(name = "conditionCode", required = true) String conditionCode,
@RequestParam(name = "equipmentId", required = false) String equipmentId,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(conditionCode) || conditionCode.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesXslMixerConditionService.isConditionCodeDuplicated(conditionCode, dataId)) {
return Result.error("条件代码不能重复");
//update-begin---author:jiangxh ---date:20260728 for【密炼机条件】条件代码按设备唯一-----------
if (oConvertUtils.isEmpty(equipmentId) || equipmentId.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesXslMixerConditionService.isConditionCodeDuplicated(conditionCode, equipmentId, dataId)) {
return Result.error("同一设备下条件代码不能重复");
}
//update-end---author:jiangxh ---date:20260728 for【密炼机条件】条件代码按设备唯一-----------
return Result.OK("该值可用!");
}
@@ -153,16 +165,20 @@ public class MesXslMixerConditionController
return "条件名称不能为空";
}
model.setConditionName(model.getConditionName().trim());
if (mesXslMixerConditionService.isConditionNameDuplicated(model.getConditionName(), excludeId)) {
return "条件名称不能重复";
//update-begin---author:jiangxh ---date:20260728 for【密炼机条件】条件名称/代码按设备唯一-----------
if (mesXslMixerConditionService.isConditionNameDuplicated(
model.getConditionName(), model.getEquipmentId(), excludeId)) {
return "同一设备下条件名称不能重复";
}
if (oConvertUtils.isEmpty(model.getConditionCode()) || StringUtils.isBlank(model.getConditionCode())) {
return "条件代码不能为空";
}
model.setConditionCode(model.getConditionCode().trim());
if (mesXslMixerConditionService.isConditionCodeDuplicated(model.getConditionCode(), excludeId)) {
return "条件代码不能重复";
if (mesXslMixerConditionService.isConditionCodeDuplicated(
model.getConditionCode(), model.getEquipmentId(), excludeId)) {
return "同一设备下条件代码不能重复";
}
//update-end---author:jiangxh ---date:20260728 for【密炼机条件】条件名称/代码按设备唯一-----------
return null;
}
}

View File

@@ -95,9 +95,12 @@ public class MesXslRawMaterialDemandPlanController {
private String buildGroupedSql(
MesXslRawMaterialDemandPlanSummary query, boolean groupedByMachine, List<Object> params) {
String planDateExpr = "DATE(t.plan_date)";
boolean hasPlanDate = hasColumn("mes_xsl_raw_material_demand_plan", "plan_date");
boolean hasMaterialId = hasColumn("mes_xsl_raw_material_demand_plan", "material_id");
String planDateExpr = hasPlanDate ? "t.plan_date" : "DATE(t.create_time)";
String machineExpr = "COALESCE(NULLIF(TRIM(t.machine_name),''), '')";
String materialIdExpr = "COALESCE(NULLIF(TRIM(t.material_id),''), '')";
String materialIdExpr =
hasMaterialId ? "COALESCE(NULLIF(TRIM(t.material_id),''), '')" : "''";
String erpCodeExpr = "COALESCE(NULLIF(TRIM(t.erp_code),''), '')";
String rawMaterialExpr = "COALESCE(NULLIF(TRIM(t.raw_material_name),''), '')";
@@ -192,4 +195,12 @@ public class MesXslRawMaterialDemandPlanController {
private String trim(String value) {
return value == null ? "" : value.trim();
}
private boolean hasColumn(String tableName, String columnName) {
String sql =
"SELECT COUNT(1) FROM information_schema.COLUMNS "
+ "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?";
Long count = jdbcTemplate.queryForObject(sql, Long.class, tableName, columnName);
return count != null && count > 0;
}
}

View File

@@ -0,0 +1,113 @@
package org.jeecg.modules.xslmes.deviceagent;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.device.sync.agent.DeviceAgentCommandService;
import org.jeecg.modules.device.sync.agent.DeviceAgentResult;
import org.jeecg.modules.device.sync.agent.DeviceAgentSession;
import org.jeecg.modules.device.sync.agent.DeviceAgentSessionRegistry;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 桌面设备代理免密接口(注册/心跳/分块回传)。
*/
@Tag(name = "桌面设备代理")
@Slf4j
@RestController
@RequestMapping("/xslmes/deviceAgent/anon")
@RequiredArgsConstructor
public class MesXslDeviceAgentAnonController {
private final DeviceAgentSessionRegistry sessionRegistry;
private final DeviceAgentCommandService commandService;
@Operation(summary = "注册/心跳")
@PostMapping("/register")
public Result<Map<String, Object>> register(@RequestBody Map<String, Object> body) {
String deviceId = str(body, "deviceId");
if (StringUtils.isBlank(deviceId)) {
return Result.error("deviceId不能为空");
}
String hostName = str(body, "hostName");
Boolean dbConnected = bool(body.get("dbConnected"));
String dbMessage = str(body, "dbMessage");
sessionRegistry.registerOrHeartbeat(deviceId, hostName, dbConnected, dbMessage);
Map<String, Object> data = new HashMap<>();
data.put("deviceId", deviceId);
data.put("accepted", true);
return Result.OK(data);
}
@Operation(summary = "心跳")
@PostMapping("/heartbeat")
public Result<String> heartbeat(@RequestBody Map<String, Object> body) {
String deviceId = str(body, "deviceId");
if (StringUtils.isBlank(deviceId)) {
return Result.error("deviceId不能为空");
}
sessionRegistry.registerOrHeartbeat(deviceId, str(body, "hostName"), bool(body.get("dbConnected")), str(body, "dbMessage"));
return Result.OK("ok");
}
@Operation(summary = "命令结果分块回传(大数据量)")
@PostMapping("/resultChunk")
public Result<String> resultChunk(@RequestBody DeviceAgentResult result) {
if (result == null || StringUtils.isBlank(result.getRequestId())) {
return Result.error("requestId不能为空");
}
// 当前实现:整包完成;桌面可在单次 chunk 中带 lastChunk=true 直接完成
if (Boolean.TRUE.equals(result.getLastChunk()) || result.getChunkTotal() == null || result.getChunkTotal() <= 1) {
commandService.completeResult(result);
} else {
log.info("收到分块回传 requestId={} chunk={}/{}", result.getRequestId(), result.getChunkIndex(), result.getChunkTotal());
// 简化:仅最后一块完成;中间块忽略合并(桌面端大结果优先走单次 STOMP 或一次 REST 全量)
if (Boolean.TRUE.equals(result.getLastChunk())) {
commandService.completeResult(result);
}
}
return Result.OK("ok");
}
@Operation(summary = "在线代理列表")
@GetMapping("/online")
public Result<List<DeviceAgentSession>> online() {
return Result.OK(sessionRegistry.listOnline());
}
@Operation(summary = "查询代理状态")
@GetMapping("/status")
public Result<DeviceAgentSession> status(@RequestParam(name = "deviceId", required = false) String deviceId) {
return Result.OK(sessionRegistry.pickOnlineAgent(deviceId));
}
private static String str(Map<String, Object> body, String key) {
if (body == null || body.get(key) == null) {
return null;
}
String v = String.valueOf(body.get(key)).trim();
return v.isEmpty() || "null".equalsIgnoreCase(v) ? null : v;
}
private static Boolean bool(Object v) {
if (v == null) {
return null;
}
if (v instanceof Boolean) {
return (Boolean) v;
}
return Boolean.parseBoolean(String.valueOf(v));
}
}

View File

@@ -0,0 +1,86 @@
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.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* MES 开炼机动作接收表(采集自 MCSToMES_Act_Mill
*/
@Data
@TableName("mes_xsl_mill_action")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES开炼机动作接收表")
public class MesXslMillAction implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Excel(name = "GUID", width = 36)
@Schema(description = "源表GUID")
private String guid;
@Excel(name = "动作地址", width = 10)
@Schema(description = "动作地址")
private Integer actAddr;
@Excel(name = "动作名称", width = 20)
@Schema(description = "动作名称")
private String actName;
@Excel(name = "动作名称(英)", width = 20)
@Schema(description = "动作名称(英)")
private String actNameEn;
@Excel(name = "动作备注", width = 15)
@Schema(description = "动作备注")
private String actMemo;
@Excel(name = "动作关联地址", width = 12)
@Schema(description = "动作关联地址")
private Integer actRepaddr;
@Excel(name = "写入时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "写入时间")
private Date writeTime;
@Excel(name = "读取时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "读取时间")
private Date readTime;
@Excel(name = "读写标识", width = 10)
@Schema(description = "读写标识")
private Integer rwFlag;
private Integer tenantId;
private String sysOrgCode;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
private Integer delFlag;
}

View File

@@ -0,0 +1,6 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslMillAction;
public interface MesXslMillActionMapper extends BaseMapper<MesXslMillAction> {}

View File

@@ -0,0 +1,85 @@
package org.jeecg.modules.xslmes.mcs.access;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.device.sync.agent.DeviceAgentCommandService;
import org.jeecg.modules.device.sync.agent.DeviceAgentResult;
import org.jeecg.modules.device.sync.agent.DeviceAgentSession;
import org.jeecg.modules.device.sync.agent.DeviceAgentSessionRegistry;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 经桌面端代理访问设备库。
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DesktopAgentMcsDataAccess implements IMcsDataAccess {
private final DeviceAgentCommandService commandService;
private final DeviceAgentSessionRegistry sessionRegistry;
private final McsDataSourceManager mcsDataSourceManager;
@Override
public boolean isAgentMode() {
return true;
}
@Override
public String resolveAgentDeviceId() {
MesXslMcsDbConfig cfg = mcsDataSourceManager.getCachedConfig();
String preferred = cfg == null ? null : cfg.getAgentDeviceId();
DeviceAgentSession session = sessionRegistry.pickOnlineAgent(preferred);
if (session == null) {
throw new IllegalStateException("无在线桌面设备代理,无法访问设备库");
}
return session.getDeviceId();
}
@Override
public List<Map<String, Object>> query(String sql, List<Object> params) {
String deviceId = resolveAgentDeviceId();
DeviceAgentResult result = commandService.query(deviceId, sql, params, 60_000);
return result.getRows() == null ? List.of() : result.getRows();
}
@Override
public int execute(String sql, List<Object> params) {
String deviceId = resolveAgentDeviceId();
DeviceAgentResult result = commandService.write(deviceId, sql, params, 60_000);
return result.getAffected() == null ? 0 : result.getAffected();
}
@Override
public int pushRows(String table, String mode, List<String> keys, List<Map<String, Object>> rows) {
String deviceId = resolveAgentDeviceId();
Map<String, Object> payload = new HashMap<>();
payload.put("mode", StringUtils.defaultIfBlank(mode, "insert"));
if (keys != null) {
payload.put("keys", keys);
}
payload.put("rows", rows == null ? new ArrayList<>() : rows);
DeviceAgentResult result = commandService.syncPush(deviceId, table, payload, 60_000);
return result.getAffected() == null ? 0 : result.getAffected();
}
@Override
public boolean testConnection() {
try {
String deviceId = resolveAgentDeviceId();
DeviceAgentResult result = commandService.test(deviceId, 10_000);
return result != null && result.isSuccess();
} catch (Exception e) {
log.warn("桌面代理中间库测试失败: {}", e.getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,159 @@
package org.jeecg.modules.xslmes.mcs.access;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 服务端直连 SQL Server调试/兼容保留)。
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DirectJdbcMcsDataAccess implements IMcsDataAccess {
private final DataSource dataSource;
private final McsDataSourceManager mcsDataSourceManager;
@Override
public boolean isAgentMode() {
return false;
}
@Override
public String resolveAgentDeviceId() {
return null;
}
@Override
public List<Map<String, Object>> query(String sql, List<Object> params) {
JdbcTemplate jt = new JdbcTemplate(requireDs());
if (params == null || params.isEmpty()) {
return jt.queryForList(sql);
}
return jt.queryForList(sql, params.toArray());
}
@Override
public int execute(String sql, List<Object> params) {
JdbcTemplate jt = new JdbcTemplate(requireDs());
if (params == null || params.isEmpty()) {
return jt.update(sql);
}
return jt.update(sql, params.toArray());
}
@Override
public int pushRows(String table, String mode, List<String> keys, List<Map<String, Object>> rows) {
if (rows == null || rows.isEmpty()) {
return 0;
}
int affected = 0;
for (Map<String, Object> row : rows) {
if ("update".equalsIgnoreCase(mode) || "upsert".equalsIgnoreCase(mode)) {
int u = updateByKeys(table, keys, row);
if (u > 0) {
affected += u;
continue;
}
if ("update".equalsIgnoreCase(mode)) {
continue;
}
}
affected += insertRow(table, row);
}
return affected;
}
@Override
public boolean testConnection() {
try {
query("SELECT 1", null);
return true;
} catch (Exception e) {
log.warn("直连中间库测试失败: {}", e.getMessage());
return false;
}
}
private int insertRow(String table, Map<String, Object> row) {
List<String> cols = new ArrayList<>(row.keySet());
StringBuilder sql = new StringBuilder("INSERT INTO [").append(table).append("] (");
StringBuilder values = new StringBuilder(" VALUES (");
List<Object> params = new ArrayList<>();
for (int i = 0; i < cols.size(); i++) {
if (i > 0) {
sql.append(',');
values.append(',');
}
sql.append('[').append(cols.get(i)).append(']');
values.append('?');
params.add(row.get(cols.get(i)));
}
sql.append(')').append(values).append(')');
return execute(sql.toString(), params);
}
private int updateByKeys(String table, List<String> keys, Map<String, Object> row) {
if (keys == null || keys.isEmpty()) {
return 0;
}
List<String> setCols = new ArrayList<>();
for (String c : row.keySet()) {
boolean isKey = keys.stream().anyMatch(k -> k.equalsIgnoreCase(c));
if (!isKey) {
setCols.add(c);
}
}
if (setCols.isEmpty()) {
return 0;
}
StringBuilder sql = new StringBuilder("UPDATE [").append(table).append("] SET ");
List<Object> params = new ArrayList<>();
for (int i = 0; i < setCols.size(); i++) {
if (i > 0) {
sql.append(',');
}
sql.append('[').append(setCols.get(i)).append("]=?");
params.add(getIgnoreCase(row, setCols.get(i)));
}
sql.append(" WHERE ");
for (int i = 0; i < keys.size(); i++) {
if (i > 0) {
sql.append(" AND ");
}
sql.append('[').append(keys.get(i)).append("]=?");
params.add(getIgnoreCase(row, keys.get(i)));
}
return execute(sql.toString(), params);
}
private Object getIgnoreCase(Map<String, Object> row, String key) {
if (row.containsKey(key)) {
return row.get(key);
}
for (Map.Entry<String, Object> e : row.entrySet()) {
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) {
return e.getValue();
}
}
return null;
}
private DataSource requireDs() {
DynamicRoutingDataSource routing = (DynamicRoutingDataSource) dataSource;
DataSource src = routing.getDataSources().get(McsDataSourceManager.DS_KEY);
if (src == null) {
throw new IllegalStateException("中间库数据源 " + McsDataSourceManager.DS_KEY + " 未注册");
}
return src;
}
}

View File

@@ -0,0 +1,31 @@
package org.jeecg.modules.xslmes.mcs.access;
import java.util.List;
import java.util.Map;
/**
* MCS 中间库访问抽象:直连 JDBC 或经桌面代理。
*/
public interface IMcsDataAccess {
boolean isAgentMode();
String resolveAgentDeviceId();
List<Map<String, Object>> query(String sql, List<Object> params);
int execute(String sql, List<Object> params);
/**
* 批量写入中间表EQ_SYNC_PUSH
*
* @param table 白名单表名
* @param mode insert / update / upsert
* @param keys 匹配键update/upsert
* @param rows 行数据
* @return 影响行数
*/
int pushRows(String table, String mode, List<String> keys, List<Map<String, Object>> rows);
boolean testConnection();
}

View File

@@ -0,0 +1,65 @@
package org.jeecg.modules.xslmes.mcs.access;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* 按配置在「桌面代理 / 直连」之间切换。
*/
@Service
@RequiredArgsConstructor
public class McsDataAccessFacade implements IMcsDataAccess {
public static final String MODE_DESKTOP_AGENT = "DESKTOP_AGENT";
public static final String MODE_DIRECT = "DIRECT";
private final McsDataSourceManager mcsDataSourceManager;
private final DesktopAgentMcsDataAccess desktopAgentMcsDataAccess;
private final DirectJdbcMcsDataAccess directJdbcMcsDataAccess;
public IMcsDataAccess current() {
return isAgentMode() ? desktopAgentMcsDataAccess : directJdbcMcsDataAccess;
}
@Override
public boolean isAgentMode() {
MesXslMcsDbConfig cfg = mcsDataSourceManager.getCachedConfig();
if (cfg == null) {
// 默认优先桌面代理
return true;
}
String mode = StringUtils.defaultIfBlank(cfg.getAccessMode(), MODE_DESKTOP_AGENT).trim().toUpperCase();
return MODE_DESKTOP_AGENT.equals(mode);
}
@Override
public String resolveAgentDeviceId() {
return current().resolveAgentDeviceId();
}
@Override
public List<Map<String, Object>> query(String sql, List<Object> params) {
return current().query(sql, params);
}
@Override
public int execute(String sql, List<Object> params) {
return current().execute(sql, params);
}
@Override
public int pushRows(String table, String mode, List<String> keys, List<Map<String, Object>> rows) {
return current().pushRows(table, mode, keys, rows);
}
@Override
public boolean testConnection() {
return current().testConnection();
}
}

View File

@@ -0,0 +1,30 @@
package org.jeecg.modules.xslmes.mcs.access;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* MES → MCS 经桌面代理下发EQ_SYNC_PUSH
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MesToMcsPushService {
private final McsDataAccessFacade mcsDataAccessFacade;
public int push(String table, String mode, List<String> keys, List<Map<String, Object>> rows) {
if (StringUtils.isBlank(table)) {
throw new IllegalArgumentException("table 不能为空");
}
int affected = mcsDataAccessFacade.pushRows(table, mode, keys, rows);
log.info("MES→MCS 下发完成 table={} mode={} rows={} affected={}",
table, mode, rows == null ? 0 : rows.size(), affected);
return affected;
}
}

View File

@@ -0,0 +1,290 @@
package org.jeecg.modules.xslmes.mcs.access.proxy;
import org.jeecg.modules.xslmes.mcs.access.IMcsDataAccess;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
public class AgentProxyConnection implements Connection {
private final IMcsDataAccess dataAccess;
private boolean closed;
public AgentProxyConnection(IMcsDataAccess dataAccess) {
this.dataAccess = dataAccess;
}
@Override
public Statement createStatement() {
return new AgentProxyStatement(dataAccess, null);
}
@Override
public PreparedStatement prepareStatement(String sql) {
return new AgentProxyPreparedStatement(dataAccess, sql);
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
throw new SQLException("CallableStatement not supported by agent proxy");
}
@Override
public String nativeSQL(String sql) {
return sql;
}
@Override
public void setAutoCommit(boolean autoCommit) {
}
@Override
public boolean getAutoCommit() {
return true;
}
@Override
public void commit() {
}
@Override
public void rollback() {
}
@Override
public void close() {
closed = true;
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
throw new SQLException("DatabaseMetaData not supported by agent proxy");
}
@Override
public void setReadOnly(boolean readOnly) {
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public void setCatalog(String catalog) {
}
@Override
public String getCatalog() {
return null;
}
@Override
public void setTransactionIsolation(int level) {
}
@Override
public int getTransactionIsolation() {
return Connection.TRANSACTION_READ_COMMITTED;
}
@Override
public SQLWarning getWarnings() {
return null;
}
@Override
public void clearWarnings() {
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) {
return createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) {
return prepareStatement(sql);
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
throw new SQLException("CallableStatement not supported");
}
@Override
public Map<String, Class<?>> getTypeMap() {
return Map.of();
}
@Override
public void setTypeMap(Map<String, Class<?>> map) {
}
@Override
public void setHoldability(int holdability) {
}
@Override
public int getHoldability() {
return 0;
}
@Override
public Savepoint setSavepoint() throws SQLException {
throw new SQLException("Savepoint not supported");
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
throw new SQLException("Savepoint not supported");
}
@Override
public void rollback(Savepoint savepoint) {
}
@Override
public void releaseSavepoint(Savepoint savepoint) {
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
return createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
return prepareStatement(sql);
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
throw new SQLException("CallableStatement not supported");
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) {
return prepareStatement(sql);
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) {
return prepareStatement(sql);
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) {
return prepareStatement(sql);
}
@Override
public Clob createClob() throws SQLException {
throw new SQLException("not supported");
}
@Override
public Blob createBlob() throws SQLException {
throw new SQLException("not supported");
}
@Override
public NClob createNClob() throws SQLException {
throw new SQLException("not supported");
}
@Override
public SQLXML createSQLXML() throws SQLException {
throw new SQLException("not supported");
}
@Override
public boolean isValid(int timeout) {
return !closed;
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
}
@Override
public String getClientInfo(String name) {
return null;
}
@Override
public Properties getClientInfo() {
return new Properties();
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
throw new SQLException("not supported");
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setSchema(String schema) {
}
@Override
public String getSchema() {
return null;
}
@Override
public void abort(Executor executor) {
close();
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) {
}
@Override
public int getNetworkTimeout() {
return 0;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return iface.cast(this);
}
throw new SQLException("Not a wrapper");
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return iface.isInstance(this);
}
}

View File

@@ -0,0 +1,68 @@
package org.jeecg.modules.xslmes.mcs.access.proxy;
import org.jeecg.modules.xslmes.mcs.access.IMcsDataAccess;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 将 JDBC 调用转发到桌面设备代理的 DataSource供 @DS("sqlserver_mcs") 复用。
*/
public class AgentProxyDataSource implements DataSource {
private final IMcsDataAccess dataAccess;
public AgentProxyDataSource(IMcsDataAccess dataAccess) {
this.dataAccess = dataAccess;
}
@Override
public Connection getConnection() {
return new AgentProxyConnection(dataAccess);
}
@Override
public Connection getConnection(String username, String password) {
return getConnection();
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return iface.cast(this);
}
throw new SQLException("Not a wrapper for " + iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return iface.isInstance(this);
}
@Override
public PrintWriter getLogWriter() {
return null;
}
@Override
public void setLogWriter(PrintWriter out) {
}
@Override
public void setLoginTimeout(int seconds) {
}
@Override
public int getLoginTimeout() {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
}

View File

@@ -0,0 +1,565 @@
package org.jeecg.modules.xslmes.mcs.access.proxy;
import org.jeecg.modules.xslmes.mcs.access.IMcsDataAccess;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
public class AgentProxyPreparedStatement implements PreparedStatement {
private final IMcsDataAccess dataAccess;
private final String sql;
private final List<Object> params = new ArrayList<>();
private ResultSet currentRs;
private int updateCount = -1;
public AgentProxyPreparedStatement(IMcsDataAccess dataAccess, String sql) {
this.dataAccess = dataAccess;
this.sql = sql;
ensureCapacity(16);
}
private void ensureCapacity(int index1Based) {
while (params.size() < index1Based) {
params.add(null);
}
}
private void setParam(int parameterIndex, Object value) {
ensureCapacity(parameterIndex);
params.set(parameterIndex - 1, value);
}
@Override
public ResultSet executeQuery() throws SQLException {
try {
List<Map<String, Object>> rows = dataAccess.query(sql, new ArrayList<>(params));
currentRs = new MapListResultSet(rows);
updateCount = -1;
return currentRs;
} catch (Exception e) {
throw new SQLException(e.getMessage(), e);
}
}
@Override
public int executeUpdate() throws SQLException {
try {
updateCount = dataAccess.execute(sql, new ArrayList<>(params));
currentRs = null;
return updateCount;
} catch (Exception e) {
throw new SQLException(e.getMessage(), e);
}
}
@Override
public boolean execute() throws SQLException {
String trimmed = sql == null ? "" : sql.trim();
if (trimmed.regionMatches(true, 0, "SELECT", 0, 6)) {
executeQuery();
return true;
}
executeUpdate();
return false;
}
@Override
public void setNull(int parameterIndex, int sqlType) {
setParam(parameterIndex, null);
}
@Override
public void setBoolean(int parameterIndex, boolean x) {
setParam(parameterIndex, x);
}
@Override
public void setByte(int parameterIndex, byte x) {
setParam(parameterIndex, x);
}
@Override
public void setShort(int parameterIndex, short x) {
setParam(parameterIndex, x);
}
@Override
public void setInt(int parameterIndex, int x) {
setParam(parameterIndex, x);
}
@Override
public void setLong(int parameterIndex, long x) {
setParam(parameterIndex, x);
}
@Override
public void setFloat(int parameterIndex, float x) {
setParam(parameterIndex, x);
}
@Override
public void setDouble(int parameterIndex, double x) {
setParam(parameterIndex, x);
}
@Override
public void setBigDecimal(int parameterIndex, BigDecimal x) {
setParam(parameterIndex, x);
}
@Override
public void setString(int parameterIndex, String x) {
setParam(parameterIndex, x);
}
@Override
public void setBytes(int parameterIndex, byte[] x) {
setParam(parameterIndex, x);
}
@Override
public void setDate(int parameterIndex, Date x) {
setParam(parameterIndex, x);
}
@Override
public void setTime(int parameterIndex, Time x) {
setParam(parameterIndex, x);
}
@Override
public void setTimestamp(int parameterIndex, Timestamp x) {
setParam(parameterIndex, x);
}
@Override
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
throw new SQLException("setAsciiStream not supported");
}
@Override
@Deprecated
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void clearParameters() {
params.clear();
}
@Override
public void setObject(int parameterIndex, Object x, int targetSqlType) {
setParam(parameterIndex, x);
}
@Override
public void setObject(int parameterIndex, Object x) {
setParam(parameterIndex, x);
}
@Override
public void addBatch() {
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setRef(int parameterIndex, Ref x) {
setParam(parameterIndex, x);
}
@Override
public void setBlob(int parameterIndex, Blob x) {
setParam(parameterIndex, x);
}
@Override
public void setClob(int parameterIndex, Clob x) {
setParam(parameterIndex, x);
}
@Override
public void setArray(int parameterIndex, Array x) {
setParam(parameterIndex, x);
}
@Override
public ResultSetMetaData getMetaData() {
return currentRs == null ? null : safeMeta();
}
private ResultSetMetaData safeMeta() {
try {
return currentRs.getMetaData();
} catch (SQLException e) {
return null;
}
}
@Override
public void setDate(int parameterIndex, Date x, Calendar cal) {
setParam(parameterIndex, x);
}
@Override
public void setTime(int parameterIndex, Time x, Calendar cal) {
setParam(parameterIndex, x);
}
@Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) {
setParam(parameterIndex, x);
}
@Override
public void setNull(int parameterIndex, int sqlType, String typeName) {
setParam(parameterIndex, null);
}
@Override
public void setURL(int parameterIndex, URL x) {
setParam(parameterIndex, x);
}
@Override
public ParameterMetaData getParameterMetaData() throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setRowId(int parameterIndex, RowId x) {
setParam(parameterIndex, x);
}
@Override
public void setNString(int parameterIndex, String value) {
setParam(parameterIndex, value);
}
@Override
public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setNClob(int parameterIndex, NClob value) {
setParam(parameterIndex, value);
}
@Override
public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setSQLXML(int parameterIndex, SQLXML xmlObject) {
setParam(parameterIndex, xmlObject);
}
@Override
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) {
setParam(parameterIndex, x);
}
@Override
public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setClob(int parameterIndex, Reader reader) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
throw new SQLException("not supported");
}
@Override
public void setNClob(int parameterIndex, Reader reader) throws SQLException {
throw new SQLException("not supported");
}
// Statement methods
@Override
public ResultSet executeQuery(String sql) throws SQLException {
throw new SQLException("use executeQuery()");
}
@Override
public int executeUpdate(String sql) throws SQLException {
throw new SQLException("use executeUpdate()");
}
@Override
public void close() {
currentRs = null;
}
@Override
public int getMaxFieldSize() {
return 0;
}
@Override
public void setMaxFieldSize(int max) {
}
@Override
public int getMaxRows() {
return 0;
}
@Override
public void setMaxRows(int max) {
}
@Override
public void setEscapeProcessing(boolean enable) {
}
@Override
public int getQueryTimeout() {
return 0;
}
@Override
public void setQueryTimeout(int seconds) {
}
@Override
public void cancel() {
}
@Override
public SQLWarning getWarnings() {
return null;
}
@Override
public void clearWarnings() {
}
@Override
public void setCursorName(String name) {
}
@Override
public boolean execute(String sql) throws SQLException {
throw new SQLException("use execute()");
}
@Override
public ResultSet getResultSet() {
return currentRs;
}
@Override
public int getUpdateCount() {
return updateCount;
}
@Override
public boolean getMoreResults() {
return false;
}
@Override
public void setFetchDirection(int direction) {
}
@Override
public int getFetchDirection() {
return ResultSet.FETCH_FORWARD;
}
@Override
public void setFetchSize(int rows) {
}
@Override
public int getFetchSize() {
return 0;
}
@Override
public int getResultSetConcurrency() {
return ResultSet.CONCUR_READ_ONLY;
}
@Override
public int getResultSetType() {
return ResultSet.TYPE_FORWARD_ONLY;
}
@Override
public void addBatch(String sql) {
}
@Override
public void clearBatch() {
}
@Override
public int[] executeBatch() {
return new int[0];
}
@Override
public Connection getConnection() {
return null;
}
@Override
public boolean getMoreResults(int current) {
return false;
}
@Override
public ResultSet getGeneratedKeys() {
return null;
}
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
throw new SQLException("not supported");
}
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
throw new SQLException("not supported");
}
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
throw new SQLException("not supported");
}
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
throw new SQLException("not supported");
}
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
throw new SQLException("not supported");
}
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
throw new SQLException("not supported");
}
@Override
public int getResultSetHoldability() {
return 0;
}
@Override
public boolean isClosed() {
return false;
}
@Override
public void setPoolable(boolean poolable) {
}
@Override
public boolean isPoolable() {
return false;
}
@Override
public void closeOnCompletion() {
}
@Override
public boolean isCloseOnCompletion() {
return false;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLException("not a wrapper");
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return false;
}
}

View File

@@ -0,0 +1,249 @@
package org.jeecg.modules.xslmes.mcs.access.proxy;
import org.jeecg.modules.xslmes.mcs.access.IMcsDataAccess;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class AgentProxyStatement implements Statement {
private final IMcsDataAccess dataAccess;
private ResultSet currentRs;
private int updateCount = -1;
public AgentProxyStatement(IMcsDataAccess dataAccess, String ignored) {
this.dataAccess = dataAccess;
}
@Override
public ResultSet executeQuery(String sql) throws SQLException {
try {
List<Map<String, Object>> rows = dataAccess.query(sql, new ArrayList<>());
currentRs = new MapListResultSet(rows);
updateCount = -1;
return currentRs;
} catch (Exception e) {
throw new SQLException(e.getMessage(), e);
}
}
@Override
public int executeUpdate(String sql) throws SQLException {
try {
updateCount = dataAccess.execute(sql, new ArrayList<>());
currentRs = null;
return updateCount;
} catch (Exception e) {
throw new SQLException(e.getMessage(), e);
}
}
@Override
public void close() {
currentRs = null;
}
@Override
public int getMaxFieldSize() {
return 0;
}
@Override
public void setMaxFieldSize(int max) {
}
@Override
public int getMaxRows() {
return 0;
}
@Override
public void setMaxRows(int max) {
}
@Override
public void setEscapeProcessing(boolean enable) {
}
@Override
public int getQueryTimeout() {
return 0;
}
@Override
public void setQueryTimeout(int seconds) {
}
@Override
public void cancel() {
}
@Override
public SQLWarning getWarnings() {
return null;
}
@Override
public void clearWarnings() {
}
@Override
public void setCursorName(String name) {
}
@Override
public boolean execute(String sql) throws SQLException {
String trimmed = sql == null ? "" : sql.trim();
if (trimmed.regionMatches(true, 0, "SELECT", 0, 6)) {
executeQuery(sql);
return true;
}
executeUpdate(sql);
return false;
}
@Override
public ResultSet getResultSet() {
return currentRs;
}
@Override
public int getUpdateCount() {
return updateCount;
}
@Override
public boolean getMoreResults() {
return false;
}
@Override
public void setFetchDirection(int direction) {
}
@Override
public int getFetchDirection() {
return ResultSet.FETCH_FORWARD;
}
@Override
public void setFetchSize(int rows) {
}
@Override
public int getFetchSize() {
return 0;
}
@Override
public int getResultSetConcurrency() {
return ResultSet.CONCUR_READ_ONLY;
}
@Override
public int getResultSetType() {
return ResultSet.TYPE_FORWARD_ONLY;
}
@Override
public void addBatch(String sql) {
}
@Override
public void clearBatch() {
}
@Override
public int[] executeBatch() {
return new int[0];
}
@Override
public Connection getConnection() {
return null;
}
@Override
public boolean getMoreResults(int current) {
return false;
}
@Override
public ResultSet getGeneratedKeys() {
return null;
}
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
return executeUpdate(sql);
}
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
return executeUpdate(sql);
}
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
return executeUpdate(sql);
}
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
return execute(sql);
}
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
return execute(sql);
}
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
return execute(sql);
}
@Override
public int getResultSetHoldability() {
return 0;
}
@Override
public boolean isClosed() {
return false;
}
@Override
public void setPoolable(boolean poolable) {
}
@Override
public boolean isPoolable() {
return false;
}
@Override
public void closeOnCompletion() {
}
@Override
public boolean isCloseOnCompletion() {
return false;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLException("not a wrapper");
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return false;
}
}

View File

@@ -0,0 +1,59 @@
package org.jeecg.modules.xslmes.mcs.controller;
import jakarta.servlet.http.HttpServletRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.xslmes.mcs.util.McsQueryHelper;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesActMill;
import org.jeecg.modules.xslmes.mcs.service.IMcsToMesActMillService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.base.controller.JeecgController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
/**
* 开炼机动作(中间库 MCSToMES_Act_Mill
*/
@Tag(name = "开炼机动作(中间库)")
@RestController
@RequestMapping("/xslmes/mcs/mcsToMesActMill")
@Slf4j
public class McsToMesActMillController extends JeecgController<McsToMesActMill, IMcsToMesActMillService> {
@Autowired
private IMcsToMesActMillService mcsToMesActMillService;
@Operation(summary = "开炼机动作(中间库)-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<McsToMesActMill>> queryPageList(McsToMesActMill mcsToMesActMill,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<McsToMesActMill> queryWrapper = McsQueryHelper.buildWrapper(mcsToMesActMill, req.getParameterMap());
Page<McsToMesActMill> page = new Page<>(pageNo, pageSize);
IPage<McsToMesActMill> pageList = mcsToMesActMillService.page(page, queryWrapper);
return Result.OK(pageList);
}
@Operation(summary = "开炼机动作(中间库)-通过id查询")
@GetMapping(value = "/queryById")
public Result<McsToMesActMill> queryById(@RequestParam(name = "id", required = true) String id) {
McsToMesActMill entity = mcsToMesActMillService.getById(id);
if (entity == null) {
return Result.error("未找到对应数据");
}
return Result.OK(entity);
}
@RequiresPermissions("xslmes:mcsToMesActMill:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, McsToMesActMill mcsToMesActMill) {
return super.exportXls(request, mcsToMesActMill, McsToMesActMill.class, "开炼机动作(中间库)");
}
}

View File

@@ -0,0 +1,41 @@
package org.jeecg.modules.xslmes.mcs.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.xslmes.mcs.access.MesToMcsPushService;
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.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* MES → MCS 经桌面代理下发。
*/
@Tag(name = "MES上辅机下发")
@RestController
@RequestMapping("/xslmes/mcs/push")
@RequiredArgsConstructor
public class MesToMcsPushController {
private final MesToMcsPushService mesToMcsPushService;
@Operation(summary = "经桌面代理写入中间表")
@PostMapping("/rows")
@SuppressWarnings("unchecked")
public Result<Map<String, Object>> pushRows(@RequestBody Map<String, Object> body) {
String table = body == null ? null : String.valueOf(body.getOrDefault("table", ""));
String mode = body == null ? "insert" : String.valueOf(body.getOrDefault("mode", "insert"));
List<String> keys = body == null ? null : (List<String>) body.get("keys");
List<Map<String, Object>> rows = body == null ? null : (List<Map<String, Object>>) body.get("rows");
int affected = mesToMcsPushService.push(table, mode, keys, rows);
Map<String, Object> data = new HashMap<>();
data.put("affected", affected);
return Result.OK(data);
}
}

View File

@@ -29,6 +29,9 @@ public class MesXslMcsDbConfigController {
@Autowired
private McsDataSourceManager mcsDataSourceManager;
@Autowired
private org.jeecg.modules.device.sync.agent.DeviceAgentSessionRegistry agentSessionRegistry;
private Integer resolveTenantId(Integer tenantId) {
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
if (tenantId == null) {
@@ -75,11 +78,21 @@ public class MesXslMcsDbConfigController {
data.put("dbConfigActive", mcsDataSourceManager.isDbConfigActive());
data.put("readEnabled", mcsDataSourceManager.isReadEnabled());
data.put("writeEnabled", mcsDataSourceManager.isWriteEnabled());
boolean agentMode = mcsDataSourceManager.isDesktopAgentMode();
data.put("accessMode", agentMode ? "DESKTOP_AGENT" : "DIRECT");
data.put("agentMode", agentMode);
if (cfg != null) {
data.put("serverHost", cfg.getServerHost());
data.put("serverPort", cfg.getServerPort());
data.put("dbName", cfg.getDbName());
data.put("agentDeviceId", cfg.getAgentDeviceId());
}
var agent = agentSessionRegistry.pickOnlineAgent(cfg == null ? null : cfg.getAgentDeviceId());
data.put("agentOnline", agent != null && agent.isOnline());
data.put("agentDbConnected", agent != null && agent.isDbConnected());
data.put("agentHostName", agent == null ? null : agent.getHostName());
data.put("agentDbMessage", agent == null ? null : agent.getDbMessage());
data.put("agentLastSeenAt", agent == null ? null : agent.getLastSeenAt());
return Result.OK(data);
}
}

View File

@@ -9,7 +9,11 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.util.dynamic.db.DataSourceCachePool;
import org.jeecg.modules.system.util.SecurityUtil;
import org.jeecg.modules.xslmes.mcs.access.DesktopAgentMcsDataAccess;
import org.jeecg.modules.xslmes.mcs.access.McsDataAccessFacade;
import org.jeecg.modules.xslmes.mcs.access.proxy.AgentProxyDataSource;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -37,6 +41,9 @@ public class McsDataSourceManager {
@Autowired
private DruidDataSourceCreator dataSourceCreator;
@Autowired
private ObjectProvider<DesktopAgentMcsDataAccess> desktopAgentMcsDataAccessProvider;
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.url:}")
private String ymlUrl;
@@ -98,6 +105,17 @@ public class McsDataSourceManager {
}
if (Integer.valueOf(1).equals(config.getStatus())) {
try {
if (isDesktopAgentMode(config)) {
DesktopAgentMcsDataAccess agent = desktopAgentMcsDataAccessProvider.getIfAvailable();
if (agent == null) {
throw new IllegalStateException("桌面代理访问组件未就绪");
}
routing.addDataSource(DS_KEY, new AgentProxyDataSource(agent));
cachedConfig.set(config);
log.info("[MCS中间库] 已切换为桌面代理模式 sqlserver_mcs -> AgentProxy, deviceId={}",
config.getAgentDeviceId());
return;
}
String plainPassword = decryptPassword(config.getDbPassword());
DataSourceProperty property = buildDataSourceProperty(config, plainPassword);
DataSource mcsDs = dataSourceCreator.createDataSource(property);
@@ -117,6 +135,21 @@ public class McsDataSourceManager {
log.info("[MCS中间库] 连接未启用,读写开关仍按页面配置生效");
}
public boolean isDesktopAgentMode(MesXslMcsDbConfig config) {
MesXslMcsDbConfig cfg = config != null ? config : cachedConfig.get();
if (cfg == null) {
return true;
}
String mode = StringUtils.isBlank(cfg.getAccessMode())
? McsDataAccessFacade.MODE_DESKTOP_AGENT
: cfg.getAccessMode().trim().toUpperCase();
return McsDataAccessFacade.MODE_DESKTOP_AGENT.equals(mode);
}
public boolean isDesktopAgentMode() {
return isDesktopAgentMode(null);
}
private void closeAndRemoveDataSource(DynamicRoutingDataSource routing) {
if (!routing.getDataSources().containsKey(DS_KEY)) {
return;

View File

@@ -0,0 +1,76 @@
package org.jeecg.modules.xslmes.mcs.entity;
import java.io.Serializable;
import java.util.Date;
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 lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 开炼机动作(中间库 MCSToMES_Act_Mill
*/
@Data
@TableName("MCSToMES_Act_Mill")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "开炼机动作(中间库)")
public class McsToMesActMill implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "GUID", type = IdType.INPUT)
@Schema(description = "GUID")
@Excel(name = "GUID", width = 36)
private String guid;
@Excel(name = "动作地址", width = 10)
@Schema(description = "动作地址")
@TableField("act_addr")
private Integer actAddr;
@Excel(name = "动作名称", width = 20)
@Schema(description = "动作名称")
@TableField("act_name")
private String actName;
@Excel(name = "动作名称(英)", width = 20)
@Schema(description = "动作名称(英)")
@TableField("act_name_en")
private String actNameEn;
@Excel(name = "动作备注", width = 15)
@Schema(description = "动作备注")
@TableField("act_memo")
private String actMemo;
@Excel(name = "动作关联地址", width = 12)
@Schema(description = "动作关联地址")
@TableField("act_Repaddr")
private Integer actRepaddr;
@Excel(name = "写入时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "写入时间")
@TableField("WriteTime")
private Date writeTime;
@Excel(name = "读取时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "读取时间")
@TableField("ReadTime")
private Date readTime;
@Excel(name = "读写标识", width = 10)
@Schema(description = "读写标识")
@TableField("RW_Flag")
private Integer rwFlag;
}

View File

@@ -66,6 +66,14 @@ public class MesXslMcsDbConfig implements Serializable {
@Schema(description = "备注")
private String remark;
//update-begin---author:cursor ---date:2026-07-20 for【设备库桥接】访问模式与绑定桌面代理-----------
@Schema(description = "访问模式DESKTOP_AGENT=桌面代理DIRECT=服务端直连")
private String accessMode;
@Schema(description = "绑定的桌面代理 deviceId")
private String agentDeviceId;
//update-end---author:cursor ---date:2026-07-20 for【设备库桥接】访问模式与绑定桌面代理-----------
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")

View File

@@ -0,0 +1,10 @@
package org.jeecg.modules.xslmes.mcs.mapper;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesActMill;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 开炼机动作(中间库)
*/
public interface McsToMesActMillMapper extends BaseMapper<McsToMesActMill> {
}

View File

@@ -0,0 +1,10 @@
package org.jeecg.modules.xslmes.mcs.service;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesActMill;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 开炼机动作(中间库)
*/
public interface IMcsToMesActMillService extends IService<McsToMesActMill> {
}

View File

@@ -0,0 +1,16 @@
package org.jeecg.modules.xslmes.mcs.service.impl;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesActMill;
import org.jeecg.modules.xslmes.mcs.mapper.McsToMesActMillMapper;
import org.jeecg.modules.xslmes.mcs.service.IMcsToMesActMillService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.dynamic.datasource.annotation.DS;
/**
* 开炼机动作(中间库)
*/
@Service
@DS("sqlserver_mcs")
public class McsToMesActMillServiceImpl extends ServiceImpl<McsToMesActMillMapper, McsToMesActMill> implements IMcsToMesActMillService {
}

View File

@@ -30,6 +30,9 @@ public class MesXslMcsDbConfigServiceImpl extends ServiceImpl<MesXslMcsDbConfigM
@Autowired
private McsDataSourceManager mcsDataSourceManager;
@Autowired
private org.jeecg.modules.xslmes.mcs.access.McsDataAccessFacade mcsDataAccessFacade;
@Override
public MesXslMcsDbConfig getCurrentConfig(Integer tenantId) {
int tid = tenantId != null ? tenantId : 0;
@@ -61,10 +64,14 @@ public class MesXslMcsDbConfigServiceImpl extends ServiceImpl<MesXslMcsDbConfigM
if (config == null) {
return Result.error("配置不能为空");
}
if (StringUtils.isBlank(config.getServerHost())) {
if (StringUtils.isBlank(config.getAccessMode())) {
config.setAccessMode("DESKTOP_AGENT");
}
boolean agentMode = "DESKTOP_AGENT".equalsIgnoreCase(config.getAccessMode().trim());
if (!agentMode && StringUtils.isBlank(config.getServerHost())) {
return Result.error("服务器地址不能为空");
}
if (StringUtils.isBlank(config.getDbUsername())) {
if (!agentMode && StringUtils.isBlank(config.getDbUsername())) {
return Result.error("用户名不能为空");
}
if (config.getTenantId() == null) {
@@ -93,13 +100,20 @@ public class MesXslMcsDbConfigServiceImpl extends ServiceImpl<MesXslMcsDbConfigM
}
String plainPassword = resolvePlainPassword(config);
if (StringUtils.isBlank(plainPassword)) {
if (!agentMode && StringUtils.isBlank(plainPassword)) {
return Result.error("密码不能为空");
}
String err = mcsDataSourceManager.testConnection(config, plainPassword);
if (err != null && Integer.valueOf(1).equals(config.getStatus())) {
return Result.error("连接测试失败:" + err);
if (agentMode && StringUtils.isBlank(config.getServerHost())) {
config.setServerHost("desktop-agent");
}
if (agentMode && StringUtils.isBlank(config.getDbUsername())) {
config.setDbUsername("desktop-agent");
}
if (!agentMode) {
String err = mcsDataSourceManager.testConnection(config, plainPassword);
if (err != null && Integer.valueOf(1).equals(config.getStatus())) {
return Result.error("连接测试失败:" + err);
}
}
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
@@ -111,12 +125,16 @@ public class MesXslMcsDbConfigServiceImpl extends ServiceImpl<MesXslMcsDbConfigM
if (old == null) {
return Result.error("配置不存在");
}
config.setDbPassword(mcsDataSourceManager.encryptPassword(plainPassword));
if (StringUtils.isNotBlank(plainPassword)) {
config.setDbPassword(mcsDataSourceManager.encryptPassword(plainPassword));
} else {
config.setDbPassword(old.getDbPassword());
}
config.setUpdateBy(username);
config.setUpdateTime(now);
updateById(config);
} else {
config.setDbPassword(mcsDataSourceManager.encryptPassword(plainPassword));
config.setDbPassword(mcsDataSourceManager.encryptPassword(StringUtils.defaultString(plainPassword)));
config.setCreateBy(username);
config.setCreateTime(now);
config.setUpdateBy(username);
@@ -151,6 +169,14 @@ public class MesXslMcsDbConfigServiceImpl extends ServiceImpl<MesXslMcsDbConfigM
if (config == null) {
return Result.error("尚未配置中间库连接");
}
if (mcsDataSourceManager.isDesktopAgentMode(config)) {
try {
boolean ok = mcsDataAccessFacade.testConnection();
return ok ? Result.OK("桌面代理设备库连接成功") : Result.error("桌面代理设备库连接失败(代理离线或库不可达)");
} catch (Exception e) {
return Result.error("桌面代理探测失败:" + e.getMessage());
}
}
String plainPassword = mcsDataSourceManager.decryptPassword(config.getDbPassword());
String err = mcsDataSourceManager.testConnection(config, plainPassword);
if (err == null) {

View File

@@ -1,9 +1,9 @@
package org.jeecg.modules.xslmes.mcs.sync;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.xslmes.mcs.access.McsDataAccessFacade;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncConfig;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncField;
@@ -69,6 +69,9 @@ public class GenericMcsSyncEngine {
@Autowired
private org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager mcsDataSourceManager;
@Autowired
private McsDataAccessFacade mcsDataAccessFacade;
//update-begin---author:GHT ---date:20260617 for【MES上辅机】采集模式 全量/时间/增量-----------
public String sync(MesXslMcsSyncConfig cfg, List<MesXslMcsSyncField> fields) {
String sourceTable = trim(cfg.getSourceTable());
@@ -91,7 +94,6 @@ public class GenericMcsSyncEngine {
}
String mode = StringUtils.isBlank(cfg.getSyncMode()) ? MODE_FULL : cfg.getSyncMode().trim().toUpperCase();
JdbcTemplate sourceJt = new JdbcTemplate(getSourceDataSource());
JdbcTemplate targetJt = new JdbcTemplate(dataSource);
// 目标表标准字段探测 + 自动填充列
@@ -134,7 +136,7 @@ public class GenericMcsSyncEngine {
String predicate = flagPredicate(flagCol, cfg.getFlagCondition(), cfg.getFlagMatchValue());
String sql = "SELECT TOP " + limit + " " + colList(srcCols) + " FROM [" + sourceTable + "]"
+ " WHERE (" + predicate + ")";
rows = sourceJt.queryForList(sql);
rows = mcsDataAccessFacade.query(sql, null);
if (rows.isEmpty()) {
return "增量采集:无待采集数据";
}
@@ -149,10 +151,10 @@ public class GenericMcsSyncEngine {
sql.append(" AND [").append(incrCol).append("] < ?");
args.add(window[1]);
}
rows = sourceJt.queryForList(sql.toString(), args.toArray());
rows = mcsDataAccessFacade.query(sql.toString(), args);
} else {
// FULL
rows = sourceJt.queryForList("SELECT " + colList(srcCols) + " FROM [" + sourceTable + "]");
rows = mcsDataAccessFacade.query("SELECT " + colList(srcCols) + " FROM [" + sourceTable + "]", null);
}
if (rows.isEmpty()) {
@@ -209,7 +211,7 @@ public class GenericMcsSyncEngine {
// INCR(标记回写):对本批所有源行回写标记值,下轮不再采集
if (flagMode) {
String writeValue = StringUtils.isBlank(cfg.getFlagWriteValue()) ? FLAG_SYNCED : cfg.getFlagWriteValue();
int marked = writeBackFlag(sourceJt, sourceTable, flagCol, cfg.getFlagCondition(), cfg.getFlagMatchValue(), writeValue, keyMaps, rows);
int marked = writeBackFlag(sourceTable, flagCol, cfg.getFlagCondition(), cfg.getFlagMatchValue(), writeValue, keyMaps, rows);
return String.format("增量采集:新增%d更新%d未变%d回写标记%d源%d条",
ins, upd, unchanged, marked, rows.size());
}
@@ -252,14 +254,14 @@ public class GenericMcsSyncEngine {
* 避免误标在本轮 SELECT 之后才进入中间库、尚未采集的新数据;
* 并以采集条件谓词做守卫,避开本轮已被其他进程改动的行。</p>
*/
private int writeBackFlag(JdbcTemplate sourceJt, String sourceTable, String flagCol, String condition,
private int writeBackFlag(String sourceTable, String flagCol, String condition,
String matchValue, String writeValue, List<MesXslMcsSyncField> keyMaps, List<Map<String, Object>> rows) {
validateIdent(flagCol);
StringBuilder sql = new StringBuilder("UPDATE [").append(sourceTable).append("] SET [")
.append(flagCol).append("] = ? WHERE ");
sql.append(keyMaps.stream().map(k -> "[" + k.getSourceField() + "] = ?").collect(Collectors.joining(" AND ")));
sql.append(" AND (").append(flagPredicate(flagCol, condition, matchValue)).append(")");
List<Object[]> argsList = new ArrayList<>();
int total = 0;
Set<String> handled = new HashSet<>();
for (Map<String, Object> row : rows) {
Map<String, Object> rci = ci(row);
@@ -272,9 +274,9 @@ public class GenericMcsSyncEngine {
for (MesXslMcsSyncField k : keyMaps) {
args.add(rci.get(k.getSourceField()));
}
argsList.add(args.toArray());
total += mcsDataAccessFacade.execute(sql.toString(), args);
}
return batch(sourceJt, sql.toString(), argsList);
return total;
}
//update-end---author:GHT ---date:20260617 for【MES上辅机】增量匹配改为标记位回写-----------
@@ -502,15 +504,6 @@ public class GenericMcsSyncEngine {
return maps.stream().anyMatch(m -> m.getTargetField() != null && m.getTargetField().equalsIgnoreCase(targetCol));
}
private DataSource getSourceDataSource() {
DynamicRoutingDataSource routing = (DynamicRoutingDataSource) dataSource;
DataSource src = routing.getDataSources().get(McsDataSourceManager.DS_KEY);
if (src == null) {
throw new IllegalStateException("中间库数据源 " + McsDataSourceManager.DS_KEY + " 未注册");
}
return src;
}
private void validateIdent(String name) {
if (name == null || !IDENT.matcher(name).matches()) {
throw new IllegalArgumentException("非法的表名或字段名: " + name);

View File

@@ -0,0 +1,6 @@
package org.jeecg.modules.xslmes.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.xslmes.entity.MesXslMillAction;
public interface IMesXslMillActionService extends IService<MesXslMillAction> {}

View File

@@ -7,7 +7,9 @@ public interface IMesXslMixerConditionService extends IService<MesXslMixerCondit
void fillEquipmentName(MesXslMixerCondition model);
boolean isConditionNameDuplicated(String conditionName, String excludeId);
//update-begin---author:jiangxh ---date:20260728 for【密炼机条件】条件名称/代码按设备唯一-----------
boolean isConditionNameDuplicated(String conditionName, String equipmentId, String excludeId);
boolean isConditionCodeDuplicated(String conditionCode, String excludeId);
boolean isConditionCodeDuplicated(String conditionCode, String equipmentId, String excludeId);
//update-end---author:jiangxh ---date:20260728 for【密炼机条件】条件名称/代码按设备唯一-----------
}

View File

@@ -12,6 +12,7 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -300,6 +301,9 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
//update-begin---author:cursor ---date:20260521 for配合示方明细重量%按PHR/总PHR自动计算-----------
fillWeightPercent(line, weightBase);
//update-end---author:cursor ---date:20260521 for配合示方明细重量%按PHR/总PHR自动计算-----------
//update-begin---author:jiangxh ---date:20260729 for【配合示方】明细重量%/体积统一保留三位小数-----------
normalizeLineDecimals(line);
//update-end---author:jiangxh ---date:20260729 for【配合示方】明细重量%/体积统一保留三位小数-----------
if (line.getCreateTime() == null) {
line.setCreateTime(now);
}
@@ -339,7 +343,9 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
if (material == null || material.getSpecificGravity() == null || material.getSpecificGravity().compareTo(BigDecimal.ZERO) <= 0) {
return;
}
line.setVolume(line.getPhr().divide(material.getSpecificGravity(), 6, RoundingMode.HALF_UP));
//update-begin---author:jiangxh ---date:20260729 for【配合示方】明细体积保留三位小数-----------
line.setVolume(line.getPhr().divide(material.getSpecificGravity(), 3, RoundingMode.HALF_UP));
//update-end---author:jiangxh ---date:20260729 for【配合示方】明细体积保留三位小数-----------
}
private void fillWeightPercent(MesXslFormulaSpecLine line, BigDecimal totalPhr) {
@@ -347,10 +353,27 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
if (line.getWeightPercent() != null || line.getPhr() == null || totalPhr == null || totalPhr.compareTo(BigDecimal.ZERO) <= 0) {
return;
}
line.setWeightPercent(line.getPhr().multiply(BigDecimal.valueOf(100)).divide(totalPhr, 4, RoundingMode.HALF_UP));
//update-begin---author:jiangxh ---date:20260729 for【配合示方】明细重量%/体积保留三位小数-----------
line.setWeightPercent(line.getPhr().multiply(BigDecimal.valueOf(100)).divide(totalPhr, 3, RoundingMode.HALF_UP));
//update-end---author:jiangxh ---date:20260729 for【配合示方】明细重量%/体积保留三位小数-----------
//update-end---author:cursor ---date:20260521 for配合示方明细重量%按PHR/总PHR自动计算可手工覆盖-----------
}
//update-begin---author:jiangxh ---date:20260729 for【配合示方】明细重量%/体积统一保留三位小数-----------
/** 明细重量%、体积统一四舍五入到三位小数(含手工录入值) */
private void normalizeLineDecimals(MesXslFormulaSpecLine line) {
if (line == null) {
return;
}
if (line.getWeightPercent() != null) {
line.setWeightPercent(line.getWeightPercent().setScale(3, RoundingMode.HALF_UP));
}
if (line.getVolume() != null) {
line.setVolume(line.getVolume().setScale(3, RoundingMode.HALF_UP));
}
}
//update-end---author:jiangxh ---date:20260729 for【配合示方】明细重量%/体积统一保留三位小数-----------
//update-begin---author:cursor ---date:20260521 for【配合示方】保存后汇总A/Q胶比重写入数据库-----------
/** 根据明细重新汇总主表 TOTAL PHR、A胶TOTAL PHR、比重与人工配料标记 */
private void refreshMainSummary(String mainId) {
@@ -457,7 +480,9 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
if (material == null || material.getSpecificGravity() == null || material.getSpecificGravity().compareTo(BigDecimal.ZERO) <= 0) {
return null;
}
return line.getPhr().divide(material.getSpecificGravity(), 6, RoundingMode.HALF_UP);
//update-begin---author:jiangxh ---date:20260729 for【配合示方】明细体积保留三位小数-----------
return line.getPhr().divide(material.getSpecificGravity(), 3, RoundingMode.HALF_UP);
//update-end---author:jiangxh ---date:20260729 for【配合示方】明细体积保留三位小数-----------
}
//update-end---author:cursor ---date:20260521 for【配合示方】保存后汇总A/Q胶比重写入数据库-----------
@@ -540,7 +565,6 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
throw new IllegalArgumentException("请先维护混合段数");
}
String rubberName = resolveRubberName(main);
String formulaCodeSuffix = resolveFormulaCodeSuffix(main);
boolean hasQ = hasStepQ(main.getLineList());
List<MesXslFormulaMixingGeneratePreviewItemVO> items = new ArrayList<>();
int aIndex = 0;
@@ -551,16 +575,24 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
item.setStageIndex(i);
if (isQSegment) {
item.setStepType("Q");
item.setSpecCode(appendFormulaCodeSuffix("F" + rubberName, formulaCodeSuffix));
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】B/F匹配编号不拼版本后缀-----------
item.setSpecCode(buildBfRubberSpecCode("F", rubberName));
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】B/F匹配编号不拼版本后缀-----------
item.setASegmentIndex(null);
} else {
aIndex++;
item.setStepType("A");
item.setASegmentIndex(aIndex);
item.setSpecCode(appendFormulaCodeSuffix("B" + aIndex + rubberName, formulaCodeSuffix));
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】B/F匹配编号不拼版本后缀-----------
item.setSpecCode(buildBfRubberSpecCode("B" + aIndex, rubberName));
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】B/F匹配编号不拼版本后缀-----------
}
items.add(item);
}
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】列表点生成混炼示方时即匹配胶料列表-----------
// 点击「生成混炼示方」打开预览时即校验拼接示方编号是否已在胶料列表维护
assertRequiredRubberMaterialsExist(toGenerateRowsFromPreviewItems(items), main, main.getLineList());
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】列表点生成混炼示方时即匹配胶料列表-----------
MesXslFormulaMixingGeneratePreviewVO preview = new MesXslFormulaMixingGeneratePreviewVO();
preview.setFormulaSpecId(formulaSpecId);
preview.setRubberName(rubberName);
@@ -598,10 +630,10 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
int created = 0;
int totalStages = normalizeMixingStages(formula.getMixingStages());
Date today = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());
//update-begin---author:cursor ---date:20260525 forXSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料-----------
Set<String> syncedRubberSpecCodes = new HashSet<>();
//update-begin---author:jiangxh ---date:20260729 for混炼示方生成】B/F胶料改为胶料列表匹配不存在则提示先维护-----------
Map<String, String> categoryIdCache = new HashMap<>();
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料-----------
assertRequiredRubberMaterialsExist(request.getRows(), formula, lines);
//update-end---author:jiangxh ---date:20260729 for【混炼示方生成】B/F胶料改为胶料列表匹配不存在则提示先维护-----------
for (MesXslFormulaMixingGenerateRowVO row : request.getRows()) {
if (row == null || oConvertUtils.isEmpty(row.getSpecCode()) || row.getStageIndex() == null) {
continue;
@@ -609,11 +641,6 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
if (CollectionUtils.isEmpty(row.getMachines())) {
throw new IllegalArgumentException("示方「" + row.getSpecCode() + "」请至少选择一个机台");
}
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料-----------
if (syncedRubberSpecCodes.add(row.getSpecCode().trim())) {
syncGeneratedRubberMixerMaterial(row, formula, compoundSpecificGravity, categoryIdCache);
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料-----------
String materialStep = resolveMixingMaterialStep(row);
int mixingColumn = resolveMixingColumn(row, formula);
List<MesXslMixingSpecMaterial> materials = buildMixingMaterials(
@@ -647,11 +674,15 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A44】生成混炼示方时按设备有效体积计算填充体积-----------
mixing.setConvertFactor(BigDecimal.ONE);
boolean isFinalStage = "Q".equalsIgnoreCase(row.getStepType());
//update-begin---author:jiangxh ---date:20260729 for【混炼示方】母胶/终炼胶比重保留三位小数-----------
if (isFinalStage) {
mixing.setFinalRubberSg(compoundSpecificGravity);
} else {
mixing.setMotherRubberSg(formula.getARubberSg());
if (compoundSpecificGravity != null) {
mixing.setFinalRubberSg(compoundSpecificGravity.setScale(3, RoundingMode.HALF_UP));
}
} else if (formula.getARubberSg() != null) {
mixing.setMotherRubberSg(formula.getARubberSg().setScale(3, RoundingMode.HALF_UP));
}
//update-end---author:jiangxh ---date:20260729 for【混炼示方】母胶/终炼胶比重保留三位小数-----------
BigDecimal specificGravity = isFinalStage ? compoundSpecificGravity : formula.getARubberSg();
String machineLabel = StringUtils.defaultIfBlank(machine.getMachineName(), machine.getMachineId());
MesXslEquipmentLedger equipment = equipmentCache.computeIfAbsent(
@@ -766,15 +797,88 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
.divide(effectiveVolume, 6, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.multiply(factor)
.setScale(6, RoundingMode.HALF_UP);
//update-begin---author:jiangxh ---date:20260729 for【混炼示方】填充体积保留两位小数-----------
.setScale(2, RoundingMode.HALF_UP);
//update-end---author:jiangxh ---date:20260729 for【混炼示方】填充体积保留两位小数-----------
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A44】生成混炼示方时按设备有效体积计算填充体积-----------
//update-begin---author:cursor ---date:20260601 for【XSLMES-20260601-A62】生成混炼示方改为同步B/F段胶至胶料信息-----------
//update-begin---author:jiangxh ---date:20260729 for【混炼示方生成】B/F胶料改为胶料列表匹配不存在则提示先维护-----------
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】预览项转生成行供胶料匹配校验-----------
/** 预览行转为生成行结构,供胶料匹配校验复用 */
private List<MesXslFormulaMixingGenerateRowVO> toGenerateRowsFromPreviewItems(
List<MesXslFormulaMixingGeneratePreviewItemVO> items) {
List<MesXslFormulaMixingGenerateRowVO> rows = new ArrayList<>();
if (CollectionUtils.isEmpty(items)) {
return rows;
}
for (MesXslFormulaMixingGeneratePreviewItemVO item : items) {
if (item == null) {
continue;
}
MesXslFormulaMixingGenerateRowVO row = new MesXslFormulaMixingGenerateRowVO();
row.setSpecCode(item.getSpecCode());
row.setStageIndex(item.getStageIndex());
row.setASegmentIndex(item.getASegmentIndex());
row.setStepType(item.getStepType());
rows.add(row);
}
return rows;
}
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】预览项转生成行供胶料匹配校验-----------
/**
* 生成混炼示方时,将 B 段/F 段胶料同步写入「胶料信息」(mes_material)
* 除 ERP 编号留空、编码/名称为示方编号外,其余字段按配合示方所选原胶料信息赋值
* 生成前校验:按 B段序号/F + 胶料名 拼出的示方编号,必须已在胶料信息中存在;不再自动新建。
* 在列表点击「生成混炼示方」预览时即调用,确认生成时再次校验
*/
private void assertRequiredRubberMaterialsExist(
List<MesXslFormulaMixingGenerateRowVO> rows,
MesXslFormulaSpec formula,
List<MesXslFormulaSpecLine> lines) {
if (CollectionUtils.isEmpty(rows)) {
return;
}
LinkedHashSet<String> requiredCodes = new LinkedHashSet<>();
String rubberName = resolveRubberName(formula);
for (MesXslFormulaMixingGenerateRowVO row : rows) {
if (row == null || StringUtils.isBlank(row.getSpecCode())) {
continue;
}
requiredCodes.add(row.getSpecCode().trim());
String stepFilter = resolveMixingMaterialStep(row);
int mixingColumn = resolveMixingColumn(row, formula);
if ("Q".equalsIgnoreCase(stepFilter)) {
int lastBSegment = resolveLastBSegmentIndex(formula, lines);
if (lastBSegment > 0 && StringUtils.isNotBlank(rubberName)) {
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】母胶匹配不拼版本后缀-----------
requiredCodes.add(buildBfRubberSpecCode("B" + lastBSegment, rubberName));
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】母胶匹配不拼版本后缀-----------
}
} else if (mixingColumn > 1 && StringUtils.isNotBlank(rubberName)) {
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】母胶匹配不拼版本后缀-----------
requiredCodes.add(buildBfRubberSpecCode("B" + (mixingColumn - 1), rubberName));
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】母胶匹配不拼版本后缀-----------
}
}
List<String> missing = new ArrayList<>();
for (String code : requiredCodes) {
if (StringUtils.isBlank(code)) {
continue;
}
if (findRubberMaterialByCodeOrName(code.trim()) == null) {
missing.add(code.trim());
}
}
if (!missing.isEmpty()) {
throw new IllegalArgumentException("请先维护母胶和终胶信息:" + String.join("", missing));
}
}
/**
* @deprecated 已改为仅匹配胶料列表,保留方法避免历史调用编译缺口;生成流程不再自动写入胶料。
*/
@Deprecated
private void syncGeneratedRubberMixerMaterial(
MesXslFormulaMixingGenerateRowVO row,
MesXslFormulaSpec formula,
@@ -784,60 +888,9 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
return;
}
String specCode = row.getSpecCode().trim();
boolean isFinalStage = "Q".equalsIgnoreCase(resolveMixingMaterialStep(row));
String materialPhase = resolveGeneratedMaterialPhase(row);
MesMaterial sourceRubber = resolveSourceRubberMaterial(formula);
String categoryId = sourceRubber != null ? sourceRubber.getCategoryId() : null;
String rubberName = resolveRubberName(formula);
String generatedName = buildGeneratedRubberMaterialName(row, rubberName);
MesMaterial existing = findGeneratedRubberMaterialByCodeOrName(specCode, materialPhase);
Date now = new Date();
if (existing != null) {
copyGeneratedMaterialFieldsFromSource(existing, sourceRubber);
if (StringUtils.isNotBlank(categoryId)) {
existing.setCategoryId(categoryId);
}
if (StringUtils.isBlank(existing.getMaterialCode())) {
existing.setMaterialCode(specCode);
}
existing.setMaterialName(generatedName);
if (StringUtils.isBlank(existing.getAliasName()) && StringUtils.isNotBlank(rubberName)) {
existing.setAliasName(rubberName);
}
existing.setMaterialPhase(materialPhase);
existing.setEnableFlag(existing.getEnableFlag() == null ? 1 : existing.getEnableFlag());
existing.setUpdateTime(now);
mesMaterialService.updateById(existing);
log.info(
"[混炼示方生成] 更新胶料信息 id={}, code={}, phase={}, categoryId={}, isFinal={}",
existing.getId(),
specCode,
materialPhase,
categoryId,
isFinalStage);
return;
if (findRubberMaterialByCodeOrName(specCode) == null) {
throw new IllegalArgumentException("请先维护母胶和终胶信息:" + specCode);
}
MesMaterial material = new MesMaterial();
copyGeneratedMaterialFieldsFromSource(material, sourceRubber);
material.setMaterialCode(specCode);
material.setMaterialName(generatedName);
material.setAliasName(StringUtils.isNotBlank(rubberName) ? rubberName : null);
material.setCategoryId(categoryId);
material.setMaterialPhase(materialPhase);
material.setEnableFlag(1);
material.setIsSpecialRubber(0);
material.setDelFlag(CommonConstant.DEL_FLAG_0);
material.setCreateTime(now);
material.setUpdateTime(now);
mesMaterialService.save(material);
log.info(
"[混炼示方生成] 新增胶料信息 id={}, code={}, phase={}, categoryId={}, isFinal={}",
material.getId(),
specCode,
materialPhase,
categoryId,
isFinalStage);
}
private void copyGeneratedMaterialFieldsFromSource(MesMaterial target, MesMaterial source) {
@@ -867,6 +920,7 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
target.setLastErpSyncTime(source.getLastErpSyncTime());
target.setRemark(source.getRemark());
}
//update-end---author:jiangxh ---date:20260729 for【混炼示方生成】B/F胶料改为胶料列表匹配不存在则提示先维护-----------
private MesMaterial resolveSourceRubberMaterial(MesXslFormulaSpec formula) {
if (formula == null || StringUtils.isBlank(formula.getRubberMaterialId())) {
@@ -967,8 +1021,10 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
}
/**
* 配合示方胶料代号/示方编号中,胶料名称之后的后缀原样截取(如 SA01、SB01 等,不做固定拼接)。
* 配合示方胶料代号/示方编号中,胶料名称之后的后缀原样截取(如 SA01、SB01 等)。
* @deprecated 生成混炼示方匹配胶料已改为仅 B/F+胶料名,不再拼接版本后缀
*/
@Deprecated
private String resolveFormulaCodeSuffix(MesXslFormulaSpec main) {
if (main == null) {
return "";
@@ -995,6 +1051,10 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
return "";
}
/**
* @deprecated 生成混炼示方匹配胶料已改为仅 B/F+胶料名,请使用 {@link #buildBfRubberSpecCode(String, String)}
*/
@Deprecated
private String appendFormulaCodeSuffix(String baseSpecCode, String formulaCodeSuffix) {
if (StringUtils.isBlank(baseSpecCode)) {
return baseSpecCode;
@@ -1005,6 +1065,16 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
return baseSpecCode + formulaCodeSuffix;
}
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】B/F匹配编号仅前缀+胶料名-----------
/**
* 生成/匹配用示方编号仅「B{n}/F + 胶料名」,不拼接 SA01 等版本号。
* 例:胶料 XC001 三段 → B1XC001、B2XC001、FXC001
*/
private String buildBfRubberSpecCode(String phasePrefix, String rubberName) {
return StringUtils.defaultString(phasePrefix) + StringUtils.defaultString(rubberName).trim();
}
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】B/F匹配编号仅前缀+胶料名-----------
private boolean hasStepQ(List<MesXslFormulaSpecLine> lines) {
if (CollectionUtils.isEmpty(lines)) {
return false;
@@ -1071,10 +1141,11 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
if ("Q".equals(stepFilter)) {
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A38】F段加入最后一段B炼好胶料一条再加Q配合剂-----------
String rubberName = resolveRubberName(formula);
String codeSuffix = resolveFormulaCodeSuffix(formula);
int lastBSegment = resolveLastBSegmentIndex(formula, lines);
if (lastBSegment > 0) {
String motherSpecCode = appendFormulaCodeSuffix("B" + lastBSegment + rubberName, codeSuffix);
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】母胶编号不拼版本后缀-----------
String motherSpecCode = buildBfRubberSpecCode("B" + lastBSegment, rubberName);
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】母胶编号不拼版本后缀-----------
BigDecimal motherWeight = resolveStageCumulativeTotal(formula, lines, lastBSegment);
materials.add(
createMotherRubberMaterial(
@@ -1095,7 +1166,6 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
}
int bColumn = Math.max(stageIndex, 1);
String rubberName = resolveRubberName(formula);
String codeSuffix = resolveFormulaCodeSuffix(formula);
if (bColumn <= 1) {
appendBStageColumnAgents(lines, 1, materials, mixerCache, categoryNameCache, kindLookup);
reindexMaterialSortNo(materials);
@@ -1103,7 +1173,9 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
}
// B2/B3…上一段炼好胶料一条(如 B1+胶料名) + 本段列配合剂
int prevB = bColumn - 1;
String motherSpecCode = appendFormulaCodeSuffix("B" + prevB + rubberName, codeSuffix);
//update-begin---author:jiangxh ---date:20260730 for【混炼示方生成】母胶编号不拼版本后缀-----------
String motherSpecCode = buildBfRubberSpecCode("B" + prevB, rubberName);
//update-end---author:jiangxh ---date:20260730 for【混炼示方生成】母胶编号不拼版本后缀-----------
BigDecimal motherWeight = resolveStageCumulativeTotal(formula, lines, prevB);
materials.add(
createMotherRubberMaterial(
@@ -1205,7 +1277,7 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
//update-end---author:cursor ---date:20260601 for【XSLMES-20260601-A62】上一段母胶明细行改绑胶料信息-----------
}
/** 上一段 B 段胶尚未写入胶料信息时,按配合示方胶料类别补建一条胶料信息 */
/** 上一段 B 段胶必须已在胶料信息中维护,不再自动补建 */
private void ensureMotherRubberMaterialSynced(
String motherSpecCode,
MesXslFormulaSpec formula,
@@ -1214,13 +1286,12 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
return;
}
String normalized = motherSpecCode.trim();
//update-begin---author:jiangxh ---date:20260729 for【混炼示方生成】上一段母胶改为胶料列表匹配不存在则提示-----------
if (findRubberMaterialByCodeOrName(normalized) != null) {
return;
}
MesXslFormulaMixingGenerateRowVO stub = new MesXslFormulaMixingGenerateRowVO();
stub.setSpecCode(normalized);
stub.setStepType("A");
syncGeneratedRubberMixerMaterial(stub, formula, null, categoryIdCache);
throw new IllegalArgumentException("请先维护母胶和终胶信息:" + normalized);
//update-end---author:jiangxh ---date:20260729 for【混炼示方生成】上一段母胶改为胶料列表匹配不存在则提示-----------
}
/** 按密炼物料主数据回填混炼示方明细行(名称/大小类/种类) */
@@ -1272,9 +1343,17 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
material.setMaterialMajor(null);
material.setMaterialMinor(categoryName);
material.setMixerMaterialId(rubber.getId());
material.setMaterialKind(
mesXslMixerMaterialKindCfgService.resolveMixingMaterialKind(
kindLookup, rubber.getCategoryId(), null, categoryName));
//update-begin---author:jiangxh ---date:20260729 for【混炼示方生成】母胶行种类按配置解析未命中则带入胶料兜底种类-----------
String kind = mesXslMixerMaterialKindCfgService.resolveMixingMaterialKind(
kindLookup, rubber.getCategoryId(), null, categoryName);
if (StringUtils.isBlank(kind) && kindLookup != null && StringUtils.isNotBlank(kindLookup.getRubberKindName())) {
kind = kindLookup.getRubberKindName().trim();
}
if (StringUtils.isBlank(kind)) {
kind = "胶料";
}
material.setMaterialKind(kind);
//update-end---author:jiangxh ---date:20260729 for【混炼示方生成】母胶行种类按配置解析未命中则带入胶料兜底种类-----------
}
/** 未查到胶料信息时,按配合示方胶料类别从配置表解析种类 */
@@ -1287,9 +1366,17 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
String categoryName = resolveCategoryName(categoryId, categoryNameCache);
material.setMaterialMajor(null);
material.setMaterialMinor(categoryName);
material.setMaterialKind(
mesXslMixerMaterialKindCfgService.resolveMixingMaterialKind(
kindLookup, categoryId, null, categoryName));
//update-begin---author:jiangxh ---date:20260729 for【混炼示方生成】母胶兜底种类带入胶料种类-----------
String kind = mesXslMixerMaterialKindCfgService.resolveMixingMaterialKind(
kindLookup, categoryId, null, categoryName);
if (StringUtils.isBlank(kind) && kindLookup != null && StringUtils.isNotBlank(kindLookup.getRubberKindName())) {
kind = kindLookup.getRubberKindName().trim();
}
if (StringUtils.isBlank(kind)) {
kind = "胶料";
}
material.setMaterialKind(kind);
//update-end---author:jiangxh ---date:20260729 for【混炼示方生成】母胶兜底种类带入胶料种类-----------
}
//update-end---author:cursor ---date:20260601 for【XSLMES-20260601-A62】母胶明细行按胶料信息回填-----------
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A54】母炼胶明细行绑定已同步密炼物料主数据-----------

View File

@@ -0,0 +1,10 @@
package org.jeecg.modules.xslmes.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.xslmes.entity.MesXslMillAction;
import org.jeecg.modules.xslmes.mapper.MesXslMillActionMapper;
import org.jeecg.modules.xslmes.service.IMesXslMillActionService;
import org.springframework.stereotype.Service;
@Service
public class MesXslMillActionServiceImpl extends ServiceImpl<MesXslMillActionMapper, MesXslMillAction> implements IMesXslMillActionService {}

View File

@@ -18,13 +18,16 @@ public class MesXslMixerConditionServiceImpl
@Autowired private MesXslEquipmentLedgerMapper equipmentLedgerMapper;
//update-begin---author:jiangxh ---date:20260728 for【密炼机条件】条件名称/代码按设备唯一-----------
@Override
public boolean isConditionNameDuplicated(String conditionName, String excludeId) {
if (StringUtils.isBlank(conditionName)) {
public boolean isConditionNameDuplicated(String conditionName, String equipmentId, String excludeId) {
if (StringUtils.isBlank(conditionName) || StringUtils.isBlank(equipmentId)) {
return false;
}
LambdaQueryWrapper<MesXslMixerCondition> wrapper =
new LambdaQueryWrapper<MesXslMixerCondition>().eq(MesXslMixerCondition::getConditionName, conditionName.trim());
new LambdaQueryWrapper<MesXslMixerCondition>()
.eq(MesXslMixerCondition::getEquipmentId, equipmentId.trim())
.eq(MesXslMixerCondition::getConditionName, conditionName.trim());
if (StringUtils.isNotBlank(excludeId)) {
wrapper.ne(MesXslMixerCondition::getId, excludeId.trim());
}
@@ -32,17 +35,20 @@ public class MesXslMixerConditionServiceImpl
}
@Override
public boolean isConditionCodeDuplicated(String conditionCode, String excludeId) {
if (StringUtils.isBlank(conditionCode)) {
public boolean isConditionCodeDuplicated(String conditionCode, String equipmentId, String excludeId) {
if (StringUtils.isBlank(conditionCode) || StringUtils.isBlank(equipmentId)) {
return false;
}
LambdaQueryWrapper<MesXslMixerCondition> wrapper =
new LambdaQueryWrapper<MesXslMixerCondition>().eq(MesXslMixerCondition::getConditionCode, conditionCode.trim());
new LambdaQueryWrapper<MesXslMixerCondition>()
.eq(MesXslMixerCondition::getEquipmentId, equipmentId.trim())
.eq(MesXslMixerCondition::getConditionCode, conditionCode.trim());
if (StringUtils.isNotBlank(excludeId)) {
wrapper.ne(MesXslMixerCondition::getId, excludeId.trim());
}
return this.count(wrapper) > 0;
}
//update-end---author:jiangxh ---date:20260728 for【密炼机条件】条件名称/代码按设备唯一-----------
@Override
public void fillEquipmentName(MesXslMixerCondition model) {

View File

@@ -103,6 +103,8 @@ public class MesXslMixingProductionPlanServiceImpl
private void rebuildRawMaterialDemandPlan(List<MesXslMixingProductionPlan> savedPlans) {
jdbcTemplate.update("DELETE FROM mes_xsl_raw_material_demand_plan");
jdbcTemplate.update("DELETE FROM mes_xsl_auto_small_material_demand_plan");
jdbcTemplate.update("DELETE FROM mes_xsl_manual_small_material_demand_plan");
if (CollectionUtils.isEmpty(savedPlans)) {
return;
}
@@ -184,6 +186,8 @@ public class MesXslMixingProductionPlanServiceImpl
}
Map<String, RawDemandAggregate> aggregateMap = new HashMap<>();
Map<String, SmallDemandAggregate> autoAggregateMap = new HashMap<>();
Map<String, SmallDemandAggregate> manualAggregateMap = new HashMap<>();
for (MesXslMixingProductionPlan plan : validPlans) {
String specId = specIdByName.get(StringUtils.trimToEmpty(plan.getFormulaName()));
if (StringUtils.isBlank(specId)) {
@@ -241,19 +245,39 @@ public class MesXslMixingProductionPlanServiceImpl
StringUtils.defaultString(erpCode),
rawMaterialName));
aggregate.demandWeight = aggregate.demandWeight.add(demandWeight);
String smallKey =
String.join(
"|",
planDateKey,
StringUtils.defaultString(plan.getMachineId()),
StringUtils.defaultString(plan.getMachineName()),
rawMaterialName);
boolean isAutoKind = StringUtils.contains(StringUtils.trimToEmpty(detail.getMaterialKind()), "自动");
Map<String, SmallDemandAggregate> targetSmallMap = isAutoKind ? autoAggregateMap : manualAggregateMap;
SmallDemandAggregate smallAggregate =
targetSmallMap.computeIfAbsent(
smallKey,
k ->
new SmallDemandAggregate(
plan.getPlanDate(),
StringUtils.trimToNull(plan.getMachineId()),
StringUtils.trimToNull(plan.getMachineName()),
rawMaterialName));
smallAggregate.demandWeight = smallAggregate.demandWeight.add(demandWeight);
}
}
if (aggregateMap.isEmpty()) {
if (aggregateMap.isEmpty() && autoAggregateMap.isEmpty() && manualAggregateMap.isEmpty()) {
return;
}
List<Object[]> batchArgs = new ArrayList<>();
List<Object[]> rawBatchArgs = new ArrayList<>();
Date now = new Date();
for (RawDemandAggregate aggregate : aggregateMap.values()) {
if (aggregate.demandWeight.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
batchArgs.add(
rawBatchArgs.add(
new Object[] {
IdWorker.getIdStr(),
aggregate.machineId,
@@ -270,14 +294,53 @@ public class MesXslMixingProductionPlanServiceImpl
CommonConstant.DEL_FLAG_0
});
}
if (batchArgs.isEmpty()) {
return;
if (!rawBatchArgs.isEmpty()) {
jdbcTemplate.batchUpdate(
"INSERT INTO mes_xsl_raw_material_demand_plan "
+ "(id, machine_id, machine_name, material_id, erp_code, raw_material_name, demand_weight, standard_weight, actual_weight, plan_date, create_time, update_time, del_flag) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
rawBatchArgs);
}
jdbcTemplate.batchUpdate(
"INSERT INTO mes_xsl_raw_material_demand_plan "
+ "(id, machine_id, machine_name, material_id, erp_code, raw_material_name, demand_weight, standard_weight, actual_weight, plan_date, create_time, update_time, del_flag) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
batchArgs);
List<Object[]> autoBatchArgs = buildSmallDemandBatchArgs(autoAggregateMap, now);
if (!autoBatchArgs.isEmpty()) {
jdbcTemplate.batchUpdate(
"INSERT INTO mes_xsl_auto_small_material_demand_plan "
+ "(id, stat_date, machine_id, machine_name, raw_material_name, demand_weight, create_time, update_time, del_flag) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
autoBatchArgs);
}
List<Object[]> manualBatchArgs = buildSmallDemandBatchArgs(manualAggregateMap, now);
if (!manualBatchArgs.isEmpty()) {
jdbcTemplate.batchUpdate(
"INSERT INTO mes_xsl_manual_small_material_demand_plan "
+ "(id, stat_date, machine_id, machine_name, raw_material_name, demand_weight, create_time, update_time, del_flag) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
manualBatchArgs);
}
}
private List<Object[]> buildSmallDemandBatchArgs(
Map<String, SmallDemandAggregate> aggregateMap, Date now) {
List<Object[]> batchArgs = new ArrayList<>();
for (SmallDemandAggregate aggregate : aggregateMap.values()) {
if (aggregate.demandWeight.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
batchArgs.add(
new Object[] {
IdWorker.getIdStr(),
aggregate.statDate,
aggregate.machineId,
aggregate.machineName,
aggregate.rawMaterialName,
aggregate.demandWeight,
now,
now,
CommonConstant.DEL_FLAG_0
});
}
return batchArgs;
}
private static class RawDemandAggregate {
@@ -305,6 +368,22 @@ public class MesXslMixingProductionPlanServiceImpl
}
}
private static class SmallDemandAggregate {
private final Date statDate;
private final String machineId;
private final String machineName;
private final String rawMaterialName;
private BigDecimal demandWeight = BigDecimal.ZERO;
private SmallDemandAggregate(
Date statDate, String machineId, String machineName, String rawMaterialName) {
this.statDate = statDate;
this.machineId = machineId;
this.machineName = machineName;
this.rawMaterialName = rawMaterialName;
}
}
private boolean hasBusinessData(MesXslMixingProductionPlan row) {
return StringUtils.isNotBlank(row.getMachineId())
|| StringUtils.isNotBlank(row.getPlanId())

View File

@@ -0,0 +1,34 @@
package org.jeecg.modules.device.sync.agent;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* MES → 桌面设备库命令。
*/
@Data
public class DeviceAgentCommand implements Serializable {
private static final long serialVersionUID = 1L;
public static final String CMD_QUERY = "EQ_QUERY";
public static final String CMD_WRITE = "EQ_WRITE";
public static final String CMD_SYNC_PULL = "EQ_SYNC_PULL";
public static final String CMD_SYNC_PUSH = "EQ_SYNC_PUSH";
public static final String CMD_TEST = "EQ_TEST";
private String requestId;
private String cmd;
private String deviceId;
/** 白名单表名(可选,与 sql 二选一或同时校验) */
private String table;
/** 已登记 sqlKey 或受限 SQL桌面端白名单校验 */
private String sql;
private String sqlKey;
private List<Object> params;
private Map<String, Object> payload;
private Integer timeoutMs;
private Long sentAt;
}

View File

@@ -0,0 +1,166 @@
package org.jeecg.modules.device.sync.agent;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 设备代理命令总线:下发 EQ_* 并等待回执。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceAgentCommandService {
public static final int DEFAULT_TIMEOUT_MS = 30_000;
private final SimpMessagingTemplate messagingTemplate;
private final DeviceAgentSessionRegistry sessionRegistry;
private final ConcurrentHashMap<String, CompletableFuture<DeviceAgentResult>> pending = new ConcurrentHashMap<>();
public DeviceAgentResult sendAndWait(DeviceAgentCommand command) {
if (command == null) {
throw new IllegalArgumentException("command 不能为空");
}
if (StringUtils.isBlank(command.getRequestId())) {
command.setRequestId(UUID.randomUUID().toString().replace("-", ""));
}
if (StringUtils.isBlank(command.getDeviceId())) {
DeviceAgentSession picked = sessionRegistry.pickOnlineAgent(null);
if (picked == null) {
throw new IllegalStateException("无在线桌面设备代理,无法访问设备库");
}
command.setDeviceId(picked.getDeviceId());
}
if (!sessionRegistry.isOnline(command.getDeviceId())) {
throw new IllegalStateException("桌面设备代理不在线: " + command.getDeviceId());
}
int timeout = command.getTimeoutMs() != null && command.getTimeoutMs() > 0
? command.getTimeoutMs() : DEFAULT_TIMEOUT_MS;
command.setSentAt(System.currentTimeMillis());
CompletableFuture<DeviceAgentResult> future = new CompletableFuture<>();
CompletableFuture<DeviceAgentResult> previous = pending.putIfAbsent(command.getRequestId(), future);
if (previous != null) {
throw new IllegalStateException("重复的 requestId: " + command.getRequestId());
}
try {
String destination = "/topic/device/" + command.getDeviceId() + "/cmd";
messagingTemplate.convertAndSend(destination, JSON.toJSONString(command));
log.info("已下发设备库命令 cmd={} requestId={} deviceId={}",
command.getCmd(), command.getRequestId(), command.getDeviceId());
return future.get(timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
pending.remove(command.getRequestId(), future);
throw new IllegalStateException("等待桌面设备库回执超时(" + timeout + "ms), requestId=" + command.getRequestId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
pending.remove(command.getRequestId(), future);
throw new IllegalStateException("等待桌面设备库回执被中断", e);
} catch (Exception e) {
pending.remove(command.getRequestId(), future);
if (e instanceof IllegalStateException) {
throw (IllegalStateException) e;
}
throw new IllegalStateException("下发设备库命令失败: " + e.getMessage(), e);
}
}
public void completeResult(DeviceAgentResult result) {
if (result == null || StringUtils.isBlank(result.getRequestId())) {
return;
}
if (StringUtils.isNotBlank(result.getDeviceId())) {
sessionRegistry.registerOrHeartbeat(
result.getDeviceId(),
null,
result.getDbConnected(),
result.getDbMessage());
}
CompletableFuture<DeviceAgentResult> future = pending.remove(result.getRequestId());
if (future != null) {
future.complete(result);
} else {
log.debug("收到无等待方的设备库回执 requestId={}", result.getRequestId());
}
}
public DeviceAgentResult query(String deviceId, String sql, List<Object> params, Integer timeoutMs) {
DeviceAgentCommand cmd = new DeviceAgentCommand();
cmd.setCmd(DeviceAgentCommand.CMD_QUERY);
cmd.setDeviceId(deviceId);
cmd.setSql(sql);
cmd.setParams(params);
cmd.setTimeoutMs(timeoutMs);
DeviceAgentResult result = sendAndWait(cmd);
ensureSuccess(result);
return result;
}
public DeviceAgentResult write(String deviceId, String sql, List<Object> params, Integer timeoutMs) {
DeviceAgentCommand cmd = new DeviceAgentCommand();
cmd.setCmd(DeviceAgentCommand.CMD_WRITE);
cmd.setDeviceId(deviceId);
cmd.setSql(sql);
cmd.setParams(params);
cmd.setTimeoutMs(timeoutMs);
DeviceAgentResult result = sendAndWait(cmd);
ensureSuccess(result);
return result;
}
public DeviceAgentResult test(String deviceId, Integer timeoutMs) {
DeviceAgentCommand cmd = new DeviceAgentCommand();
cmd.setCmd(DeviceAgentCommand.CMD_TEST);
cmd.setDeviceId(deviceId);
cmd.setTimeoutMs(timeoutMs != null ? timeoutMs : 10_000);
return sendAndWait(cmd);
}
public DeviceAgentResult syncPull(String deviceId, String table, String sql, List<Object> params, Integer timeoutMs) {
DeviceAgentCommand cmd = new DeviceAgentCommand();
cmd.setCmd(DeviceAgentCommand.CMD_SYNC_PULL);
cmd.setDeviceId(deviceId);
cmd.setTable(table);
cmd.setSql(sql);
cmd.setParams(params);
cmd.setTimeoutMs(timeoutMs != null ? timeoutMs : 60_000);
DeviceAgentResult result = sendAndWait(cmd);
ensureSuccess(result);
return result;
}
public DeviceAgentResult syncPush(String deviceId, String table, Map<String, Object> payload, Integer timeoutMs) {
DeviceAgentCommand cmd = new DeviceAgentCommand();
cmd.setCmd(DeviceAgentCommand.CMD_SYNC_PUSH);
cmd.setDeviceId(deviceId);
cmd.setTable(table);
cmd.setPayload(payload);
cmd.setTimeoutMs(timeoutMs != null ? timeoutMs : 60_000);
DeviceAgentResult result = sendAndWait(cmd);
ensureSuccess(result);
return result;
}
private void ensureSuccess(DeviceAgentResult result) {
if (result == null) {
throw new IllegalStateException("桌面设备库无回执");
}
if (!result.isSuccess()) {
throw new IllegalStateException(StringUtils.defaultIfBlank(result.getError(), "桌面设备库执行失败"));
}
}
}

View File

@@ -0,0 +1,30 @@
package org.jeecg.modules.device.sync.agent;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 桌面 → MES 设备库命令回执。
*/
@Data
public class DeviceAgentResult implements Serializable {
private static final long serialVersionUID = 1L;
private String requestId;
private String deviceId;
private String cmd;
private boolean success;
private String error;
private Integer affected;
private List<Map<String, Object>> rows;
private Boolean dbConnected;
private String dbMessage;
private Long respondedAt;
/** 分块回传时使用 */
private Integer chunkIndex;
private Integer chunkTotal;
private Boolean lastChunk;
}

View File

@@ -0,0 +1,22 @@
package org.jeecg.modules.device.sync.agent;
import lombok.Data;
import java.io.Serializable;
/**
* 桌面设备代理在线会话(内存)。
*/
@Data
public class DeviceAgentSession implements Serializable {
private static final long serialVersionUID = 1L;
private String deviceId;
private String hostName;
private boolean online;
/** 设备库SQL Server是否连通 */
private boolean dbConnected;
private String dbMessage;
private long lastSeenAt;
private long registeredAt;
}

View File

@@ -0,0 +1,121 @@
package org.jeecg.modules.device.sync.agent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 桌面设备代理会话注册表。
*/
@Slf4j
@Component
public class DeviceAgentSessionRegistry {
private static final long ONLINE_TTL_MS = 60_000L;
private final ConcurrentHashMap<String, DeviceAgentSession> sessions = new ConcurrentHashMap<>();
public void registerOrHeartbeat(String deviceId, String hostName, Boolean dbConnected, String dbMessage) {
if (StringUtils.isBlank(deviceId)) {
return;
}
long now = System.currentTimeMillis();
sessions.compute(deviceId, (id, old) -> {
DeviceAgentSession s = old == null ? new DeviceAgentSession() : old;
if (old == null) {
s.setRegisteredAt(now);
}
s.setDeviceId(deviceId);
if (StringUtils.isNotBlank(hostName)) {
s.setHostName(hostName);
}
s.setOnline(true);
if (dbConnected != null) {
s.setDbConnected(dbConnected);
}
if (dbMessage != null) {
s.setDbMessage(dbMessage);
}
s.setLastSeenAt(now);
return s;
});
}
public void markOffline(String deviceId) {
if (StringUtils.isBlank(deviceId)) {
return;
}
DeviceAgentSession s = sessions.get(deviceId);
if (s != null) {
s.setOnline(false);
s.setLastSeenAt(System.currentTimeMillis());
}
}
public DeviceAgentSession get(String deviceId) {
if (StringUtils.isBlank(deviceId)) {
return null;
}
DeviceAgentSession s = sessions.get(deviceId);
if (s == null) {
return null;
}
refreshOnlineFlag(s);
return s;
}
public boolean isOnline(String deviceId) {
DeviceAgentSession s = get(deviceId);
return s != null && s.isOnline();
}
/**
* 选取一个在线代理:优先指定 deviceId否则取最近心跳的在线设备。
*/
public DeviceAgentSession pickOnlineAgent(String preferredDeviceId) {
if (StringUtils.isNotBlank(preferredDeviceId)) {
DeviceAgentSession preferred = get(preferredDeviceId);
if (preferred != null && preferred.isOnline()) {
return preferred;
}
}
return listOnline().stream()
.max(Comparator.comparingLong(DeviceAgentSession::getLastSeenAt))
.orElse(null);
}
public List<DeviceAgentSession> listOnline() {
List<DeviceAgentSession> list = new ArrayList<>();
for (Map.Entry<String, DeviceAgentSession> e : sessions.entrySet()) {
DeviceAgentSession s = e.getValue();
refreshOnlineFlag(s);
if (s.isOnline()) {
list.add(s);
}
}
list.sort(Comparator.comparingLong(DeviceAgentSession::getLastSeenAt).reversed());
return list;
}
public List<DeviceAgentSession> listAll() {
List<DeviceAgentSession> list = new ArrayList<>(sessions.values());
list.forEach(this::refreshOnlineFlag);
list.sort(Comparator.comparingLong(DeviceAgentSession::getLastSeenAt).reversed());
return list;
}
private void refreshOnlineFlag(DeviceAgentSession s) {
if (s == null) {
return;
}
if (System.currentTimeMillis() - s.getLastSeenAt() > ONLINE_TTL_MS) {
s.setOnline(false);
}
}
}

View File

@@ -1,20 +1,29 @@
package org.jeecg.modules.device.sync.controller;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.device.sync.agent.DeviceAgentCommand;
import org.jeecg.modules.device.sync.agent.DeviceAgentCommandService;
import org.jeecg.modules.device.sync.agent.DeviceAgentResult;
import org.jeecg.modules.device.sync.agent.DeviceAgentSession;
import org.jeecg.modules.device.sync.agent.DeviceAgentSessionRegistry;
import org.jeecg.modules.device.sync.entity.DeviceStatus;
import org.jeecg.modules.device.sync.mapper.DeviceStatusMapper;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -29,6 +38,8 @@ public class DeviceWebSocketController {
private final DeviceStatusMapper deviceStatusMapper;
private final SimpMessagingTemplate messagingTemplate;
private final DeviceAgentSessionRegistry agentSessionRegistry;
private final DeviceAgentCommandService agentCommandService;
@MessageMapping("/device/status")
public void receiveDeviceStatus(DeviceStatus payload) {
@@ -70,6 +81,8 @@ public class DeviceWebSocketController {
if (deviceId.isBlank()) {
return;
}
// 心跳同时刷新设备代理在线状态
agentSessionRegistry.registerOrHeartbeat(deviceId, null, null, null);
Map<String, Object> pong = new HashMap<>();
pong.put("cmd", "PONG_DEVICE");
pong.put("deviceId", deviceId);
@@ -91,6 +104,8 @@ public class DeviceWebSocketController {
commandPayload.put("sentAt", System.currentTimeMillis());
messagingTemplate.convertAndSendToUser(deviceId, "/queue/command", commandPayload);
// 匿名桌面同样订阅 /topic/device/{id}/cmd
messagingTemplate.convertAndSend("/topic/device/" + deviceId + "/cmd", commandJson);
log.info("下发设备指令成功, deviceId={}", deviceId);
Map<String, Object> result = new HashMap<>();
@@ -98,4 +113,87 @@ public class DeviceWebSocketController {
result.put("queued", true);
return Result.OK(result);
}
//update-begin---author:cursor ---date:2026-07-20 for【设备库桥接】代理状态/回执/在线列表-----------
/**
* 桌面代理上报在线与设备库连通状态。
* destination: /app/device/agent/status
*/
@MessageMapping("/device/agent/status")
public void receiveAgentStatus(@org.springframework.messaging.handler.annotation.Payload(required = false) Map<String, Object> payload) {
if (payload == null) {
return;
}
String deviceId = String.valueOf(payload.getOrDefault("deviceId", "")).trim();
if (deviceId.isBlank()) {
return;
}
String hostName = payload.get("hostName") == null ? null : String.valueOf(payload.get("hostName"));
Boolean dbConnected = null;
if (payload.get("dbConnected") instanceof Boolean) {
dbConnected = (Boolean) payload.get("dbConnected");
} else if (payload.get("dbConnected") != null) {
dbConnected = Boolean.parseBoolean(String.valueOf(payload.get("dbConnected")));
}
String dbMessage = payload.get("dbMessage") == null ? null : String.valueOf(payload.get("dbMessage"));
agentSessionRegistry.registerOrHeartbeat(deviceId, hostName, dbConnected, dbMessage);
messagingTemplate.convertAndSend("/topic/device/" + deviceId + "/status", payload);
log.debug("设备代理状态已更新 deviceId={} dbConnected={}", deviceId, dbConnected);
}
/**
* 桌面代理回传设备库命令结果。
* destination: /app/device/agent/result
*/
@MessageMapping("/device/agent/result")
public void receiveAgentResult(@org.springframework.messaging.handler.annotation.Payload(required = false) Map<String, Object> payload) {
if (payload == null) {
return;
}
DeviceAgentResult result = JSON.parseObject(JSON.toJSONString(payload), DeviceAgentResult.class);
agentCommandService.completeResult(result);
}
@GetMapping("/agent/online")
public Result<List<DeviceAgentSession>> listOnlineAgents() {
return Result.OK(agentSessionRegistry.listOnline());
}
@GetMapping("/agent/list")
public Result<List<DeviceAgentSession>> listAgents() {
return Result.OK(agentSessionRegistry.listAll());
}
@PostMapping("/agent/test")
public Result<DeviceAgentResult> testAgentDb(@RequestBody(required = false) Map<String, Object> body) {
String deviceId = body == null ? null : String.valueOf(body.getOrDefault("deviceId", "")).trim();
if (deviceId == null || deviceId.isBlank()) {
DeviceAgentSession picked = agentSessionRegistry.pickOnlineAgent(null);
if (picked == null) {
return Result.error("无在线桌面代理");
}
deviceId = picked.getDeviceId();
}
try {
return Result.OK(agentCommandService.test(deviceId, 10_000));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@PostMapping("/agent/command")
public Result<DeviceAgentResult> sendAgentCommand(@RequestBody DeviceAgentCommand command) {
try {
return Result.OK(agentCommandService.sendAndWait(command));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@GetMapping("/agent/status")
public Result<DeviceAgentSession> agentStatus(@RequestParam(name = "deviceId", required = false) String deviceId) {
DeviceAgentSession session = agentSessionRegistry.pickOnlineAgent(deviceId);
return Result.OK(session);
}
//update-end---author:cursor ---date:2026-07-20 for【设备库桥接】代理状态/回执/在线列表-----------
}

View File

@@ -606,3 +606,26 @@ jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/components/MesXslRub
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/impl/SysImChatServiceImpl.java
jeecgboot-vue3/src/views/system/im/imCache.ts
jeecgboot-vue3/src/views/system/im/ImChat.vue
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD>ϡ<EFBFBD><CFA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<E0BCAD><D0A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ψһ ---
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/service/IMesMixerMaterialService.java
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/service/impl/MesMixerMaterialServiceImpl.java
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMixerMaterialController.java
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/service/IMesMaterialService.java
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/service/impl/MesMaterialServiceImpl.java
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMaterialController.java
jeecgboot-vue3/src/views/mes/material/MesMixerMaterial.api.ts
jeecgboot-vue3/src/views/mes/material/MesMixerMaterial.data.ts
jeecgboot-vue3/src/views/mes/material/MesMaterial.api.ts
jeecgboot-vue3/src/views/mes/material/MesMaterial.data.ts
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD><CFA1><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD>ϱ<EFBFBD><CFB1><EFBFBD><EFBFBD><EFBFBD>365<36><35> ---
jeecgboot-vue3/src/views/mes/material/MesMixerMaterial.data.ts
-- author:jiangxh---date:20260729--for: <20><>WebSocket<65><74><EFBFBD>޸<EFBFBD><DEB8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Redis<69>ذ<EFBFBD><D8B0><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>Ƶ<EFBFBD><C6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ---
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/message/websocket/WebSocket.java
jeecgboot-vue3/src/hooks/web/useWebSocket.ts
-- author:jiangxh---date:20260729--for: <20><><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD><CFA1>б<EFBFBD>/<2F><><EFBFBD><EFBFBD>ȥ<EFBFBD><C8A5><EFBFBD><EFBFBD><EFBFBD>ϱ<EFBFBD><CFB1><EFBFBD><EFBFBD>ֶ<EFBFBD> ---
jeecgboot-vue3/src/views/mes/material/MesMaterial.data.ts
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMaterialController.java

View File

@@ -10,11 +10,13 @@ import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.mes.material.entity.MesMaterial;
import org.jeecg.modules.mes.material.service.IMesMaterialService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -59,6 +61,12 @@ public class MesMaterialController extends JeecgController<MesMaterial, IMesMate
@RequiresPermissions("mes:mes_material:add")
@PostMapping("/add")
public Result<String> add(@RequestBody MesMaterial model) {
//update-begin---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验-----------
String err = validateUnique(model, null);
if (err != null) {
return Result.error(err);
}
//update-end---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验-----------
mesMaterialService.save(model);
mesMaterialService.notifyMaterialChanged("material.created", model);
return Result.OK("添加成功!");
@@ -69,6 +77,12 @@ public class MesMaterialController extends JeecgController<MesMaterial, IMesMate
@RequiresPermissions("mes:mes_material:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody MesMaterial model) {
//update-begin---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验-----------
String err = validateUnique(model, model.getId());
if (err != null) {
return Result.error(err);
}
//update-end---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验-----------
mesMaterialService.updateById(model);
mesMaterialService.notifyMaterialChanged("material.updated", model);
return Result.OK("编辑成功!");
@@ -106,6 +120,57 @@ public class MesMaterialController extends JeecgController<MesMaterial, IMesMate
return Result.OK(mesMaterialService.getById(id));
}
//update-begin---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验接口-----------
@Operation(summary = "校验胶料编码是否重复")
@GetMapping("/checkMaterialCode")
public Result<String> checkMaterialCode(
@RequestParam(name = "materialCode", required = true) String materialCode,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(materialCode) || materialCode.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesMaterialService.isMaterialCodeDuplicated(materialCode, dataId)) {
return Result.error("胶料编码不能重复");
}
return Result.OK("该值可用!");
}
@Operation(summary = "校验胶料名称是否重复")
@GetMapping("/checkMaterialName")
public Result<String> checkMaterialName(
@RequestParam(name = "materialName", required = true) String materialName,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(materialName) || materialName.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesMaterialService.isMaterialNameDuplicated(materialName, dataId)) {
return Result.error("胶料名称不能重复");
}
return Result.OK("该值可用!");
}
private String validateUnique(MesMaterial model, String excludeId) {
if (model == null) {
return "数据不能为空";
}
if (oConvertUtils.isEmpty(model.getMaterialName()) || StringUtils.isBlank(model.getMaterialName())) {
return "胶料名称不能为空";
}
model.setMaterialName(model.getMaterialName().trim());
//update-begin---author:jiangxh ---date:20260730 for【胶料】保存时编号与名称保持一致界面隐藏编号-----------
// 表单/列表不展示编号,保存时自动用胶料名称回填胶料编号
model.setMaterialCode(model.getMaterialName());
//update-end---author:jiangxh ---date:20260730 for【胶料】保存时编号与名称保持一致界面隐藏编号-----------
if (mesMaterialService.isMaterialNameDuplicated(model.getMaterialName(), excludeId)) {
return "胶料名称不能重复";
}
if (mesMaterialService.isMaterialCodeDuplicated(model.getMaterialCode(), excludeId)) {
return "胶料编码不能重复";
}
return null;
}
//update-end---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验接口-----------
@RequiresPermissions("mes:mes_material:exportXls")
@RequestMapping("/exportXls")
public ModelAndView exportXls(HttpServletRequest request, MesMaterial model) {

View File

@@ -11,11 +11,13 @@ import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.mes.material.entity.MesMixerMaterial;
import org.jeecg.modules.mes.material.service.IMesMixerMaterialService;
import org.springframework.messaging.simp.SimpMessagingTemplate;
@@ -52,6 +54,12 @@ public class MesMixerMaterialController extends JeecgController<MesMixerMaterial
@RequiresPermissions("mes:mes_mixer_material:add")
@PostMapping("/add")
public Result<String> add(@RequestBody MesMixerMaterial model) {
//update-begin---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
String err = validateUnique(model, null);
if (err != null) {
return Result.error(err);
}
//update-end---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
service.save(model);
publishChanged("add", model.getId());
return Result.OK("添加成功!");
@@ -62,6 +70,12 @@ public class MesMixerMaterialController extends JeecgController<MesMixerMaterial
@RequiresPermissions("mes:mes_mixer_material:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody MesMixerMaterial model) {
//update-begin---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
String err = validateUnique(model, model.getId());
if (err != null) {
return Result.error(err);
}
//update-end---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
service.updateById(model);
publishChanged("edit", model.getId());
return Result.OK("编辑成功!");
@@ -110,6 +124,12 @@ public class MesMixerMaterialController extends JeecgController<MesMixerMaterial
@Operation(summary = "MES-密炼物料信息-免密添加")
@PostMapping("/anon/add")
public Result<String> anonAdd(@RequestBody MesMixerMaterial model) {
//update-begin---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
String err = validateUnique(model, null);
if (err != null) {
return Result.error(err);
}
//update-end---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
service.save(model);
publishChanged("add", model.getId());
return Result.OK("添加成功!");
@@ -118,6 +138,12 @@ public class MesMixerMaterialController extends JeecgController<MesMixerMaterial
@Operation(summary = "MES-密炼物料信息-免密编辑")
@RequestMapping(value = "/anon/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> anonEdit(@RequestBody MesMixerMaterial model) {
//update-begin---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
String err = validateUnique(model, model.getId());
if (err != null) {
return Result.error(err);
}
//update-end---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
boolean ok = service.updateById(model);
if (!ok) {
return Result.error("数据已被他人修改,请刷新后重试");
@@ -148,6 +174,57 @@ public class MesMixerMaterialController extends JeecgController<MesMixerMaterial
return Result.OK(service.getById(id));
}
//update-begin---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验接口-----------
@Operation(summary = "校验物料编码是否重复")
@GetMapping("/checkMaterialCode")
public Result<String> checkMaterialCode(
@RequestParam(name = "materialCode", required = true) String materialCode,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(materialCode) || materialCode.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (service.isMaterialCodeDuplicated(materialCode, dataId)) {
return Result.error("物料编码不能重复");
}
return Result.OK("该值可用!");
}
@Operation(summary = "校验物料名称是否重复")
@GetMapping("/checkMaterialName")
public Result<String> checkMaterialName(
@RequestParam(name = "materialName", required = true) String materialName,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(materialName) || materialName.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (service.isMaterialNameDuplicated(materialName, dataId)) {
return Result.error("物料名称不能重复");
}
return Result.OK("该值可用!");
}
private String validateUnique(MesMixerMaterial model, String excludeId) {
if (model == null) {
return "数据不能为空";
}
if (oConvertUtils.isEmpty(model.getMaterialCode()) || StringUtils.isBlank(model.getMaterialCode())) {
return "物料编码不能为空";
}
model.setMaterialCode(model.getMaterialCode().trim());
if (service.isMaterialCodeDuplicated(model.getMaterialCode(), excludeId)) {
return "物料编码不能重复";
}
if (oConvertUtils.isEmpty(model.getMaterialName()) || StringUtils.isBlank(model.getMaterialName())) {
return "物料名称不能为空";
}
model.setMaterialName(model.getMaterialName().trim());
if (service.isMaterialNameDuplicated(model.getMaterialName(), excludeId)) {
return "物料名称不能重复";
}
return null;
}
//update-end---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验接口-----------
@RequiresPermissions("mes:mes_mixer_material:exportXls")
@RequestMapping("/exportXls")
public ModelAndView exportXls(HttpServletRequest request, MesMixerMaterial model) {

View File

@@ -5,4 +5,10 @@ import org.jeecg.modules.mes.material.entity.MesMaterial;
public interface IMesMaterialService extends IService<MesMaterial> {
void notifyMaterialChanged(String eventType, MesMaterial material);
//update-begin---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验-----------
boolean isMaterialCodeDuplicated(String materialCode, String excludeId);
boolean isMaterialNameDuplicated(String materialName, String excludeId);
//update-end---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验-----------
}

View File

@@ -3,4 +3,11 @@ package org.jeecg.modules.mes.material.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.mes.material.entity.MesMixerMaterial;
public interface IMesMixerMaterialService extends IService<MesMixerMaterial> {}
public interface IMesMixerMaterialService extends IService<MesMixerMaterial> {
//update-begin---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
boolean isMaterialCodeDuplicated(String materialCode, String excludeId);
boolean isMaterialNameDuplicated(String materialName, String excludeId);
//update-end---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
}

View File

@@ -1,9 +1,11 @@
package org.jeecg.modules.mes.material.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.mes.material.entity.MesMaterial;
import org.jeecg.modules.mes.material.mapper.MesMaterialMapper;
import org.jeecg.modules.mes.material.service.IMesMaterialService;
@@ -25,4 +27,32 @@ public class MesMaterialServiceImpl extends ServiceImpl<MesMaterialMapper, MesMa
event.put("timestamp", System.currentTimeMillis());
webSocket.sendMessage(JSON.toJSONString(event));
}
//update-begin---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验-----------
@Override
public boolean isMaterialCodeDuplicated(String materialCode, String excludeId) {
if (StringUtils.isBlank(materialCode)) {
return false;
}
LambdaQueryWrapper<MesMaterial> wrapper =
new LambdaQueryWrapper<MesMaterial>().eq(MesMaterial::getMaterialCode, materialCode.trim());
if (StringUtils.isNotBlank(excludeId)) {
wrapper.ne(MesMaterial::getId, excludeId.trim());
}
return this.count(wrapper) > 0;
}
@Override
public boolean isMaterialNameDuplicated(String materialName, String excludeId) {
if (StringUtils.isBlank(materialName)) {
return false;
}
LambdaQueryWrapper<MesMaterial> wrapper =
new LambdaQueryWrapper<MesMaterial>().eq(MesMaterial::getMaterialName, materialName.trim());
if (StringUtils.isNotBlank(excludeId)) {
wrapper.ne(MesMaterial::getId, excludeId.trim());
}
return this.count(wrapper) > 0;
}
//update-end---author:jiangxh ---date:20260729 for【胶料】名称/编码唯一校验-----------
}

View File

@@ -1,6 +1,8 @@
package org.jeecg.modules.mes.material.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.mes.material.entity.MesMixerMaterial;
import org.jeecg.modules.mes.material.mapper.MesMixerMaterialMapper;
import org.jeecg.modules.mes.material.service.IMesMixerMaterialService;
@@ -8,4 +10,33 @@ import org.springframework.stereotype.Service;
@Service
public class MesMixerMaterialServiceImpl extends ServiceImpl<MesMixerMaterialMapper, MesMixerMaterial>
implements IMesMixerMaterialService {}
implements IMesMixerMaterialService {
//update-begin---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
@Override
public boolean isMaterialCodeDuplicated(String materialCode, String excludeId) {
if (StringUtils.isBlank(materialCode)) {
return false;
}
LambdaQueryWrapper<MesMixerMaterial> wrapper =
new LambdaQueryWrapper<MesMixerMaterial>().eq(MesMixerMaterial::getMaterialCode, materialCode.trim());
if (StringUtils.isNotBlank(excludeId)) {
wrapper.ne(MesMixerMaterial::getId, excludeId.trim());
}
return this.count(wrapper) > 0;
}
@Override
public boolean isMaterialNameDuplicated(String materialName, String excludeId) {
if (StringUtils.isBlank(materialName)) {
return false;
}
LambdaQueryWrapper<MesMixerMaterial> wrapper =
new LambdaQueryWrapper<MesMixerMaterial>().eq(MesMixerMaterial::getMaterialName, materialName.trim());
if (StringUtils.isNotBlank(excludeId)) {
wrapper.ne(MesMixerMaterial::getId, excludeId.trim());
}
return this.count(wrapper) > 0;
}
//update-end---author:jiangxh ---date:20260729 for【密炼物料】名称/编码唯一校验-----------
}

View File

@@ -61,16 +61,30 @@ public class WebSocket {
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
try {
sessionPool.put(userId, session);
//update-begin---author:jiangxh ---date:20260729 for【WebSocket】同 userId 新连接顶替旧连接,避免双连接互踢-----------
Session previous = sessionPool.put(userId, session);
if (previous != null && previous.isOpen() && !previous.getId().equals(session.getId())) {
try {
previous.close();
} catch (Exception ignore) {
// ignore
}
}
//update-end---author:jiangxh ---date:20260729 for【WebSocket】同 userId 新连接顶替旧连接,避免双连接互踢-----------
log.info("【系统 WebSocket】连接建立 userId={}, sessionId={}, online={}", userId, session.getId(), sessionPool.size());
} catch (Exception e) {
}
}
@OnClose
public void onClose(@PathParam("userId") String userId) {
public void onClose(Session session, @PathParam("userId") String userId) {
try {
sessionPool.remove(userId);
//update-begin---author:jiangxh ---date:20260729 for【WebSocket】仅移除当前 session避免旧连接 onClose 清掉新连接-----------
Session current = sessionPool.get(userId);
if (current != null && session != null && current.getId().equals(session.getId())) {
sessionPool.remove(userId, current);
}
//update-end---author:jiangxh ---date:20260729 for【WebSocket】仅移除当前 session避免旧连接 onClose 清掉新连接-----------
log.info("【系统 WebSocket】连接断开 userId={}, online={}", userId, sessionPool.size());
} catch (Exception e) {
e.printStackTrace();
@@ -155,13 +169,24 @@ public class WebSocket {
* ws接受客户端消息
*/
@OnMessage
public void onMessage(String message, @PathParam(value = "userId") String userId) {
public void onMessage(String message, Session session, @PathParam(value = "userId") String userId) {
String normalized = message == null ? "" : message.trim();
if (isHeartbeatOrBlank(normalized)) {
log.debug("【系统 WebSocket】收到客户端心跳/空消息:{}", normalized);
if ("ping".equalsIgnoreCase(normalized) || WebsocketConst.CMD_CHECK.equalsIgnoreCase(normalized)) {
//update-begin---author:jiangxh ---date:20260729 for【WebSocket】心跳直接回包避免走 Redis 导致 pong 超时断线-----------
// 代码逻辑说明: 【issues/1161】前端websocket因心跳导致监听不起作用---
this.sendMessage(userId, "ping");
// 心跳必须同连接即时回包;走 Redis 发布订阅可能延迟/失败,触发前端 pongTimeout 断线重连
try {
if (session != null && session.isOpen()) {
synchronized (session) {
session.getBasicRemote().sendText("ping");
}
}
} catch (Exception e) {
log.warn("【系统 WebSocket】心跳回包失败 userId={}, err={}", userId, e.getMessage());
}
//update-end---author:jiangxh ---date:20260729 for【WebSocket】心跳直接回包避免走 Redis 导致 pong 超时断线-----------
}
return;
}

View File

@@ -0,0 +1,67 @@
<#if po.fieldDbType=='Blob'>
private transient java.lang.String ${po.fieldName}String;
private byte[] ${po.fieldName};
public byte[] get${po.fieldName?cap_first}(){
if(${po.fieldName}String==null){
return null;
}
try {
return ${po.fieldName}String.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public String get${po.fieldName?cap_first}String(){
if(${po.fieldName}==null || ${po.fieldName}.length==0){
return "";
}
try {
return new String(${po.fieldName},"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
<#elseif po.classType=='switch'>
<#assign switch_extend_arr=['Y','N']>
<#if po.dictField?default("")?contains("[")>
<#assign switch_extend_arr=po.dictField?eval>
</#if>
<#list switch_extend_arr as a>
<#if a_index == 0>
<#assign switch_extend_arr1=a>
<#else>
<#assign switch_extend_arr2=a>
</#if>
</#list>
@Excel(name = "${po.filedComment}", width = 15,replace = {"是_${switch_extend_arr1}","否_${switch_extend_arr2}"} )
@Schema(description = "${po.filedComment}")
private ${po.fieldType} ${po.fieldName};
<#elseif po.classType=='pca'>
@Excel(name = "${po.filedComment}", width = 15,exportConvert=true,importConvert = true )
@Schema(description = "${po.filedComment}")
private ${po.fieldType} ${po.fieldName};
public String convertis${po.fieldName?cap_first}() {
return SpringContextUtils.getBean(ProvinceCityArea.class).getText(${po.fieldName});
}
public void convertset${po.fieldName?cap_first}(String text) {
this.${po.fieldName} = SpringContextUtils.getBean(ProvinceCityArea.class).getCode(text);
}
<#elseif po.classType=='cat_tree'>
<#assign list_field_dictCode=', dictTable = "sys_category", dicText = "name", dicCode = "id"'>
@Excel(name = "${po.filedComment}", width = 15${list_field_dictCode})
@Schema(description = "${po.filedComment}")
private ${po.fieldType} ${po.fieldName};
<#else>
@Schema(description = "${po.filedComment}")
<#if po.fieldDbName == 'del_flag'>
@TableLogic
</#if>
private ${po.fieldType} ${po.fieldName};
</#if>

View File

@@ -0,0 +1,53 @@
<#if need_select_tag>
JDictSelectTag,
</#if>
<#if need_switch>
JSwitch,
</#if>
<#if need_multi>
JSelectMultiple,
</#if>
<#if need_search>
JSearchSelect,
</#if>
<#if need_popup>
JPopup,
</#if>
<#if need_popup_dict>
JPopupDict,
</#if>
<#if need_category>
JCategorySelect,
</#if>
<#if need_dept>
JSelectDept,
</#if>
<#if need_dept_user>
<#-- update-begin---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
JSelectUser,
<#-- update-end---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
</#if>
<#if need_select_tree>
JTreeSelect,
</#if>
<#if need_time>
TimePicker,
</#if>
<#if need_pca>
JAreaLinkage,
</#if>
<#if need_upload>
JUpload,
</#if>
<#if need_image_upload>
JImageUpload,
</#if>
<#if need_markdown>
JMarkdownEditor,
</#if>
<#if need_editor>
JEditor,
</#if>
<#if need_checkbox>
JCheckbox,
</#if>

View File

@@ -0,0 +1,119 @@
<#include "/common/utils.ftl">
<#-- update-begin---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#if po.isShow =='Y' && po.fieldName != 'id' && po.fieldName !='delFlag' && isNotPidField(tableVo, po.fieldDbName)>
<#-- update-end---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#assign form_field_dictCode="">
<#if po.dictTable?default("")?trim?length gt 1 && po.dictText?default("")?trim?length gt 1 && po.dictField?default("")?trim?length gt 1>
<#assign form_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign form_field_dictCode="${po.dictField}">
</#if>
<a-col :span="${form_span}">
<a-form-item label="${po.filedComment}" v-bind="validateInfos.${autoStringSuffixForModel(po)}" id="${formEntityName}-${autoStringSuffixForModel(po)}" name="${autoStringSuffixForModel(po)}">
<#if po.classType =='date'>
<a-date-picker placeholder="请选择${po.filedComment}" <#if po.extendParams?exists && po.extendParams.picker?exists>picker="${po.extendParams.picker}"</#if> v-model:value="formData.${po.fieldName}" value-format="YYYY-MM-DD" style="width: 100%" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType =='datetime'>
<a-date-picker placeholder="请选择${po.filedComment}" v-model:value="formData.${po.fieldName}" showTime value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType =='time'>
<#assign need_time = true>
<time-picker placeholder="请选择${po.filedComment}" value-format="HH:mm:ss" v-model:value="formData.${po.fieldName}" style="width: 100%" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType =='popup'>
<#assign need_popup = true>
<#assign sourceFields = po.dictField?default("")?trim?split(",")/>
<#assign targetFields = po.dictText?default("")?trim?split(",")/>
<j-popup
placeholder="请选择${po.filedComment}"
v-model:value="formData.${po.fieldName}"
code="${po.dictTable}"
:fieldConfig="[
<#list sourceFields as fieldName>
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
]"
:multi="${po.extendParams.popupMulti?c}"
:setFieldsValue="setFieldsValue"
<#if po.readonly=='Y'>disabled</#if><#rt> allow-clear />
<#elseif po.classType =='popup_dict'>
<#assign need_popup_dict = true>
<#assign sourceFields = po.dictField?default("")?trim?split(",")/>
<#assign targetFields = po.dictText?default("")?trim?split(",")/>
<j-popup-dict
placeholder="请选择${po.filedComment}"
v-model:value="formData.${po.fieldName}"
dictCode="${po.dictTable},${po.dictText},${po.dictField}"
:multi="${po.extendParams.popupMulti?c}" <#if po.readonly=='Y'>disabled</#if> />
<#elseif po.classType =='sel_depart'>
<#assign need_dept = true>
<j-select-dept v-model:value="formData.${po.fieldName}" <#if po.extendParams?exists && po.extendParams.text?exists>labelKey="${po.extendParams.text}"</#if> <#if po.extendParams?exists && po.extendParams.store?exists>rowKey="${po.extendParams.store}"</#if> <#if po.readonly=='Y'>disabled</#if> :multiple="${po.extendParams.multi?default('true')}" checkStrictly <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType =='switch'>
<#assign need_switch = true>
<j-switch v-model:value="formData.${po.fieldName}" <#if po.dictField != 'is_open'>:options="${po.dictField}"</#if> <#if po.readonly=='Y'>disabled</#if>></j-switch>
<#elseif po.classType =='pca'>
<#assign need_pca = true>
<j-area-linkage v-model:value="formData.${po.fieldName}" placeholder="请输入${po.filedComment}" saveCode="region" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType =='markdown'>
<#assign need_markdown = true>
<j-markdown-editor v-model:value="formData.${autoStringSuffixForModel(po)}" id="${po.fieldName}" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if>></j-markdown-editor>
<#elseif po.classType =='password'>
<a-input-password v-model:value="formData.${po.fieldName}" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType =='sel_user'>
<#assign need_dept_user = true>
<#-- update-begin---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
<j-select-user v-model:value="formData.${po.fieldName}" <#if po.extendParams?exists && po.extendParams.text?exists>labelKey="${po.extendParams.text}"</#if> <#if po.extendParams?exists && po.extendParams.store?exists>rowKey="${po.extendParams.store}"</#if> <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#-- update-end---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
<#elseif po.classType =='textarea'>
<a-textarea v-model:value="formData.${autoStringSuffixForModel(po)}" :rows="4" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType=='radio'>
<#assign need_select_tag = true>
<j-dict-select-tag type='radio' v-model:value="formData.${po.fieldName}" dictCode="${form_field_dictCode}" placeholder="请选择${po.filedComment}" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType=='list'>
<#assign need_select_tag = true>
<j-dict-select-tag v-model:value="formData.${po.fieldName}" dictCode="${form_field_dictCode}" placeholder="请选择${po.filedComment}" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType=='list_multi'>
<#assign need_multi = true>
<j-select-multiple type="${po.classType}" v-model:value="formData.${po.fieldName}" dictCode="${form_field_dictCode}" placeholder="请选择${po.filedComment}" <#if po.readonly=='Y'>disabled</#if> :triggerChange="false"/>
<#elseif po.classType=='checkbox'>
<#assign need_checkbox = true>
<j-checkbox type="${po.classType}" v-model:value="formData.${po.fieldName}" dictCode="${form_field_dictCode}" placeholder="请选择${po.filedComment}" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType=='sel_search'>
<#assign need_search = true>
<j-search-select v-model:value="formData.${po.fieldName}" dict="${form_field_dictCode}" <#if po.readonly=='Y'>disabled</#if> allow-clear />
<#elseif po.classType=='cat_tree'>
<#assign need_category = true>
<j-category-select v-model:value="formData.${po.fieldName}" pcode="${po.dictField?default("")}" placeholder="请选择${po.filedComment}" <#if po.dictText?default("")?trim?length gt 1>back="${dashedToCamel(po.dictText)}"</#if> <#if po.readonly=='Y'>disabled</#if> @change="(value) => handleFormChange('${po.fieldName}', value)" allow-clear />
<#elseif po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
<a-input-number v-model:value="formData.${po.fieldName}" placeholder="请输入${po.filedComment}" style="width: 100%" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType=='file'>
<#assign need_upload = true>
<j-upload v-model:value="formData.${po.fieldName}" <#if po.readonly=='Y'>disabled</#if> <#if po.uploadnum??>:maxCount=${po.uploadnum}</#if>></j-upload>
<#elseif po.classType=='image'>
<#assign need_image_upload = true>
<j-image-upload <#if po.uploadnum??>:fileMax=${po.uploadnum}<#else>:fileMax="0"</#if> v-model:value="formData.${po.fieldName}" <#if po.readonly=='Y'>disabled</#if>></j-image-upload>
<#elseif po.classType=='umeditor'>
<#assign need_editor = true>
<j-editor v-model:value="formData.${autoStringSuffixForModel(po)}" <#if po.readonly=='Y'>disabled</#if> :autoFocus="false"/>
<#elseif po.fieldDbType=='Blob'>
<a-input v-model:value="formData.${autoStringSuffixForModel(po)}" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if> allow-clear ></a-input>
<#elseif po.classType == 'sel_tree'>
<#assign need_select_tree = true>
<j-tree-select
<#if po.dictText??>
<#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>
dict="${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}"
<#elseif po.dictText?split(',')[1]??>
pidField="${po.dictText?split(',')[1]}"
<#elseif po.dictText?split(',')[3]??>
hasChildField="${po.dictText?split(',')[3]}"
</#if>
</#if>
pidValue="${po.dictField}"
<#if po.readonly=='Y'>disabled</#if>
v-model:value="formData.${po.fieldName}"
@change="(value) => handleFormChange('${po.fieldName}', value)" allow-clear >
</j-tree-select>
<#else>
<a-input v-model:value="formData.${po.fieldName}" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if> allow-clear ></a-input>
</#if>
</a-form-item>
</a-col>
</#if>

View File

@@ -0,0 +1,59 @@
<#if need_select_tag>
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
</#if>
<#if need_switch>
import JSwitch from '/@/components/Form/src/jeecg/components/JSwitch.vue';
</#if>
<#if need_multi>
import JSelectMultiple from '/@/components/Form/src/jeecg/components/JSelectMultiple.vue';
</#if>
<#if need_search>
import JSearchSelect from '/@/components/Form/src/jeecg/components/JSearchSelect.vue';
</#if>
<#if need_popup>
import JPopup from '/@/components/Form/src/jeecg/components/JPopup.vue';
</#if>
<#if need_popup_dict>
import JPopupDict from '/@/components/Form/src/jeecg/components/JPopupDict.vue';
</#if>
<#if need_category>
import JCategorySelect from '/@/components/Form/src/jeecg/components/JCategorySelect.vue';
</#if>
<#if need_dept>
import JSelectDept from '/@/components/Form/src/jeecg/components/JSelectDept.vue';
</#if>
<#if need_dept_user>
<#-- update-begin---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
import JSelectUser from '/@/components/Form/src/jeecg/components/JSelectUser.vue';
<#-- update-end---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
</#if>
<#if need_select_tree>
import JTreeSelect from '/@/components/Form/src/jeecg/components/JTreeSelect.vue';
</#if>
<#if need_time>
import { TimePicker } from 'ant-design-vue';
</#if>
<#if need_pca>
import JAreaLinkage from '/@/components/Form/src/jeecg/components/JAreaLinkage.vue';
</#if>
<#if need_upload>
import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
</#if>
<#if need_image_upload>
import JImageUpload from '/@/components/Form/src/jeecg/components/JImageUpload.vue';
</#if>
<#if need_markdown>
import JMarkdownEditor from '/@/components/Form/src/jeecg/components/JMarkdownEditor.vue';
</#if>
<#if need_editor>
import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
</#if>
<#if need_checkbox>
import JCheckbox from "/@/components/Form/src/jeecg/components/JCheckbox.vue";
</#if>
<#if need_range_number>
import JRangeNumber from "/@/components/Form/src/jeecg/components/JRangeNumber.vue";
</#if>
<#if is_like>
import JInput from "/@/components/Form/src/jeecg/components/JInput.vue";
</#if>

View File

@@ -0,0 +1,108 @@
<#include "/common/utils.ftl">
<#-- update-begin---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#if po.isQuery=='Y' && po.fieldName !='delFlag'>
<#-- update-end---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#assign query_flag=true>
<#if query_field_no==2>
<template v-if="toggleSearchStatus">
</#if>
<#assign query_field_dictCode="">
<#if po.dictTable?default("")?trim?length gt 1>
<#assign need_select_tag = true>
<#assign need_multi = true>
<#assign query_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign need_select_tag = true>
<#assign need_multi = true>
<#assign query_field_dictCode="${po.dictField}">
</#if>
<#if po.queryMode=='single'>
<#if query_field_no gt 1> </#if><a-col :lg="6">
<#if query_field_no gt 1> </#if><a-form-item name="${autoStringSuffixForModel(po)}">
<#if query_field_no gt 1> </#if><template #label><span title="${po.filedComment}"><#if po.filedComment?default("")?trim?length gt 4>${po.filedComment?substring(0,4)}<#else>${po.filedComment}</#if></span></template>
<#if po.classType=='sel_search'>
<#if query_field_no gt 1> </#if><j-search-select placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" dict="${po.dictTable},${po.dictText},${po.dictField}" allow-clear />
<#elseif po.classType=='sel_user'>
<#-- update-begin---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
<#if query_field_no gt 1> </#if><j-select-user placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" @change="(value)=>handleFormJoinChange('${po.fieldName}',value)" allow-clear />
<#-- update-end---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
<#elseif po.classType=='switch'>
<#if query_field_no gt 1> </#if><j-switch placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" <#if po.dictField!= 'is_open'>:options="${po.dictField}"</#if> query />
<#elseif po.classType=='sel_depart'>
<#if query_field_no gt 1> </#if><j-select-dept placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" checkStrictly allow-clear />
<#elseif po.classType=='list_multi'>
<#if query_field_no gt 1> </#if><j-select-multiple placeholder="请选择${po.filedComment}" dictCode="${query_field_dictCode?default("")}" v-model:value="queryParam.${po.fieldName}" allow-clear />
<#elseif po.classType=='cat_tree'>
<#if query_field_no gt 1> </#if><j-category-select placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" pcode="${po.dictField?default("")}" @change="(value) => handleFormChange('${po.fieldName}', value)" allow-clear />
<#elseif po.classType=='date'>
<#if query_field_no gt 1> </#if><a-date-picker valueFormat="YYYY-MM-DD" placeholder="请选择${po.filedComment}" <#if po.extendParams?exists && po.extendParams.picker?exists>picker="${po.extendParams.picker}"</#if> v-model:value="queryParam.${po.fieldName}" allow-clear />
<#elseif po.classType=='datetime'>
<#if query_field_no gt 1> </#if><a-date-picker showTime valueFormat="YYYY-MM-DD HH:mm:ss" placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" allow-clear />
<#elseif po.classType=='time'>
<#if query_field_no gt 1> </#if><time-picker valueFormat="HH:mm:ss" placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" allow-clear />
<#elseif po.classType=='pca'>
<#if query_field_no gt 1> </#if><j-area-linkage v-model:value="queryParam.${po.fieldName}" placeholder="请选择${po.filedComment}" saveCode="region" @change="(value)=>handleFormJoinChange('${po.fieldName}',value)" allow-clear />
<#elseif po.classType=='sel_tree'>
<#if query_field_no gt 1> </#if><j-tree-select v-model:value="queryParam.${po.fieldName}" placeholder="请选择${po.filedComment}" <#if po.dictText??><#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>dict="${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}" <#elseif po.dictText?split(',')[1]??>pidField:"${po.dictText?split(',')[1]}", <#elseif po.dictText?split(',')[3]??>hasChildField:"${po.dictText?split(',')[3]}"</#if> </#if>pidValue="${po.dictField}" allow-clear />
<#elseif po.classType=='popup'>
<#assign sourceFields = po.dictField?default("")?trim?split(",")/>
<#assign targetFields = po.dictText?default("")?trim?split(",")/>
<#if query_field_no gt 1> </#if><j-popup
<#if query_field_no gt 1> </#if>placeholder="请选择${po.filedComment}"
<#if query_field_no gt 1> </#if>v-model:value="queryParam.${po.fieldName}"
<#if query_field_no gt 1> </#if>code="${po.dictTable}"
<#if query_field_no gt 1> </#if>:fieldConfig="[
<#list sourceFields as fieldName>
<#if query_field_no gt 1> </#if>{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
<#if query_field_no gt 1> </#if>]"
<#if query_field_no gt 1> </#if>:multi="${po.extendParams.popupMulti?c}"
<#if query_field_no gt 1> </#if>:setFieldsValue="setFieldsValue" allow-clear />
<#elseif po.classType=='popup_dict'>
<#if query_field_no gt 1> </#if><j-popup-dict
<#if query_field_no gt 1> </#if>placeholder="请选择${po.filedComment}"
<#if query_field_no gt 1> </#if>v-model:value="queryParam.${po.fieldName}"
<#if query_field_no gt 1> </#if>dictCode="${po.dictTable},${po.dictText},${po.dictField}"
<#if query_field_no gt 1> </#if>:multi="${po.extendParams.popupMulti?c}"
<#if query_field_no gt 1> </#if><#if po.readonly=='Y'>disabled</#if> />
<#elseif po.classType=='list' || po.classType=='radio' || po.classType=='checkbox'>
<#-- ---------------------------下拉或是单选 判断数据字典是表字典还是普通字典------------------------------- -->
<#if po.dictTable?default("")?trim?length gt 1>
<#if query_field_no gt 1> </#if><j-select-multiple placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" dictCode="${po.dictTable},${po.dictText},${po.dictField}" allow-clear />
<#elseif po.dictField?default("")?trim?length gt 1>
<#if query_field_no gt 1> </#if><j-select-multiple placeholder="请选择${po.filedComment}" v-model:value="queryParam.${po.fieldName}" dictCode="${po.dictField}" allow-clear />
<#else>
<#if query_field_no gt 1> </#if><a-input placeholder="请输入${po.filedComment}" v-model:value="queryParam.${po.fieldName}" allow-clear ></a-input>
</#if>
<#elseif po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
<#if query_field_no gt 1> </#if><a-input-number placeholder="请输入${po.filedComment}" v-model:value="queryParam.${po.fieldName}"></a-input-number>
<#else>
<#if query_field_no gt 1> </#if><a-input placeholder="请输入${po.filedComment}" v-model:value="queryParam.${autoStringSuffixForModel(po)}" allow-clear ></a-input>
</#if>
<#if query_field_no gt 1> </#if></a-form-item>
<#if query_field_no gt 1> </#if></a-col>
<#elseif po.queryMode=='like'>
<#if query_field_no gt 1> </#if><a-col :lg="6">
<#if query_field_no gt 1> </#if><a-form-item name="${autoStringSuffixForModel(po)}">
<#if query_field_no gt 1> </#if><template #label><span title="${po.filedComment}"><#if po.filedComment?default("")?trim?length gt 4>${po.filedComment?substring(0,4)}<#else>${po.filedComment}</#if></span></template>
<#if query_field_no gt 1> </#if><JInput v-model:value="queryParam.${po.fieldName}"/>
<#if query_field_no gt 1> </#if></a-form-item>
<#if query_field_no gt 1> </#if></a-col>
<#else>
<#if query_field_no gt 1> </#if><a-col :lg="6">
<#if query_field_no gt 1> </#if><a-form-item name="${autoStringSuffixForModel(po)}">
<#if query_field_no gt 1> </#if><template #label><span title="${po.filedComment}"><#if po.filedComment?default("")?trim?length gt 4>${po.filedComment?substring(0,4)}<#else>${po.filedComment}</#if></span></template>
<#if po.classType=='date'>
<#if query_field_no gt 1> </#if><a-range-picker value-format="YYYY-MM-DD" <#if po.extendParams?exists && po.extendParams.picker?exists>picker="${po.extendParams.picker}"</#if> v-model:value="queryParam.${po.fieldName}" class="query-group-cust"/>
<#elseif po.classType=='time'>
<#if query_field_no gt 1> </#if><a-time-range-picker value-format="HH:mm:ss" v-model:value="queryParam.${po.fieldName}" class="query-group-cust" />
<#elseif po.classType=='datetime'>
<#if query_field_no gt 1> </#if><a-range-picker showTime value-format="YYYY-MM-DD HH:mm:ss" v-model:value="queryParam.${po.fieldName}" class="query-group-cust"/>
<#else>
<#if query_field_no gt 1> </#if><JRangeNumber v-model:value="queryParam.${po.fieldName}" class="query-group-cust"></JRangeNumber>
</#if>
<#if query_field_no gt 1> </#if></a-form-item>
<#if query_field_no gt 1> </#if></a-col>
</#if>
<#assign query_field_no=query_field_no+1>
</#if>

View File

@@ -0,0 +1,25 @@
<#-- 原生查询区域样式 -->
.jeecg-basic-table-form-container {
padding: 0;
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
.query-group-cust{
min-width: 100px !important;
}
.query-group-split-cust{
width: 30px;
display: inline-block;
text-align: center
}
.ant-form-item:not(.ant-form-item-with-help){
margin-bottom: 16px;
height: 32px;
}
:deep(.ant-picker),:deep(.ant-input-number){
width: 100%;
}
}

View File

@@ -0,0 +1,16 @@
<#assign sourceFields = col.dictField?default("")?trim?split(",")/>
<#assign targetFields = col.dictText?default("")?trim?split(",")/>
type: JVxeTypes.popup,
popupCode:"${col.dictTable}",
fieldConfig: [
<#list sourceFields as fieldName>
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
],
props:{
multi: ${col.extendParams.popupMulti?c},
},
<#if col.readonly=='Y'>
disabled:true,
</#if>

View File

@@ -0,0 +1,5 @@
<#-- 非原生查询区域样式 -->
:deep(.ant-picker),:deep(.ant-input-number){
width: 100%;
}

View File

@@ -0,0 +1,17 @@
<#assign sourceFields = po.dictField?default("")?trim?split(",")/>
<#assign targetFields = po.dictText?default("")?trim?split(",")/>
component: 'JPopup',
componentProps: ({ formActionType }) => {
const {setFieldsValue} = formActionType;
return{
setFieldsValue:setFieldsValue,
code:"${po.dictTable}",
fieldConfig: [
<#list sourceFields as fieldName>
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
],
multi:${po.extendParams.popupMulti?c}
}
},

View File

@@ -0,0 +1,13 @@
<#list columns as po>
<#if po.isShow == 'Y'>
<#if po.fieldName != 'id'>
<#if po.defaultVal??>
<#if po.fieldDbType=="BigDecimal" || po.fieldDbType=="double" || po.fieldDbType=="int">
${po.fieldName}:${po.defaultVal},
<#else>
${po.fieldName}:"${po.defaultVal}",
</#if>
</#if>
</#if>
</#if>
</#list>

View File

@@ -0,0 +1,13 @@
<#list sub.colums as po>
<#if po.isShow == 'Y'>
<#if po.fieldName != 'id'>
<#if po.defaultVal??>
<#if po.fieldDbType=="BigDecimal" || po.fieldDbType=="double" || po.fieldDbType=="int">
${po.fieldName}:${po.defaultVal},
<#else>
${po.fieldName}:"${po.defaultVal}",
</#if>
</#if>
</#if>
</#if>
</#list>

View File

@@ -0,0 +1,11 @@
<#list columns as po>
<#if po.isShow == 'Y' && po.fieldName != 'id'>
<#if po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
${po.fieldName}: <#if po.defaultVal??>${po.defaultVal}<#else>undefined</#if>,
<#elseif po.fieldDbType=='Blob'>
${po.fieldName}String: <#if po.defaultVal??>'${po.defaultVal}'<#else>''</#if>,
<#else>
${po.fieldName}: <#if po.defaultVal??>'${po.defaultVal}'<#else>''</#if>,
</#if>
</#if>
</#list>

View File

@@ -0,0 +1,11 @@
<#list columns as po>
<#if po.isShow == 'Y' && po.fieldName != 'id'>
<#if po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
${po.fieldName}: <#if po.defaultVal??>${po.defaultVal}<#else>undefined</#if>,
<#elseif po.fieldDbType=='Blob'>
${po.fieldName}String: <#if po.defaultVal??>'${po.defaultVal}'<#else>''</#if>,
<#else>
${po.fieldName}: <#if po.defaultVal??>'${po.defaultVal}'<#else>''</#if>,
</#if>
</#if>
</#list>

View File

@@ -0,0 +1,11 @@
<#list sub.colums as po>
<#if po.isShow == 'Y' && po.fieldName != 'id'>
<#if po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
${po.fieldName}: <#if po.defaultVal??>${po.defaultVal}<#else>undefined</#if>,
<#elseif po.fieldDbType=='Blob'>
${po.fieldName}String: <#if po.defaultVal??>'${po.defaultVal}'<#else>''</#if>,
<#else>
${po.fieldName}: <#if po.defaultVal??>'${po.defaultVal}'<#else>''</#if>,
</#if>
</#if>
</#list>

View File

@@ -0,0 +1,47 @@
-- 注意该页面对应的前台目录为views/${entityPackagePath}文件夹下
-- 如果你想更改到其他目录请修改sql中component字段对应的值
<#assign mainId = "${.now?long}01">
<#assign addId = "${.now?long}02">
<#assign editId = "${.now?long}03">
<#assign delId = "${.now?long}04">
<#assign batchDelId = "${.now?long}05">
<#assign exportId = "${.now?long}06">
<#assign importId = "${.now?long}07">
-- 主菜单
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
VALUES ('${mainId}', NULL, '${tableVo.ftlDescription}', '/${entityPackagePath}/${entityName?uncap_first}List', '${entityPackagePath}/${entityName}List', NULL, NULL, 0, NULL, '1', 0.00, 0, NULL, 1, 0, 0, 0, 0, NULL, '1', 0, 0, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0);
-- 新增
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('${addId}', '${mainId}', '添加${tableVo.ftlDescription}', NULL, NULL, 0, NULL, NULL, 2, '${entityPackage}:${tableName}:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0, 0, '1', 0);
-- 编辑
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('${editId}', '${mainId}', '编辑${tableVo.ftlDescription}', NULL, NULL, 0, NULL, NULL, 2, '${entityPackage}:${tableName}:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0, 0, '1', 0);
-- 删除
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('${delId}', '${mainId}', '删除${tableVo.ftlDescription}', NULL, NULL, 0, NULL, NULL, 2, '${entityPackage}:${tableName}:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0, 0, '1', 0);
-- 批量删除
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('${batchDelId}', '${mainId}', '批量删除${tableVo.ftlDescription}', NULL, NULL, 0, NULL, NULL, 2, '${entityPackage}:${tableName}:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0, 0, '1', 0);
-- 导出excel
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('${exportId}', '${mainId}', '导出excel_${tableVo.ftlDescription}', NULL, NULL, 0, NULL, NULL, 2, '${entityPackage}:${tableName}:exportXls', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0, 0, '1', 0);
-- 导入excel
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('${importId}', '${mainId}', '导入excel_${tableVo.ftlDescription}', NULL, NULL, 0, NULL, NULL, 2, '${entityPackage}:${tableName}:importExcel', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0, 0, '1', 0);
-- 角色授权 admin 角色为例role_id 可替换
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('${.now?long}08', 'f6817f48af4fb3af11b9e8bf182f618b', '${mainId}', NULL, '${.now?string["yyyy-MM-dd HH:mm:ss"]}', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('${.now?long}09', 'f6817f48af4fb3af11b9e8bf182f618b', '${addId}', NULL, '${.now?string["yyyy-MM-dd HH:mm:ss"]}', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('${.now?long}10', 'f6817f48af4fb3af11b9e8bf182f618b', '${editId}', NULL, '${.now?string["yyyy-MM-dd HH:mm:ss"]}', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('${.now?long}11', 'f6817f48af4fb3af11b9e8bf182f618b', '${delId}', NULL, '${.now?string["yyyy-MM-dd HH:mm:ss"]}', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('${.now?long}12', 'f6817f48af4fb3af11b9e8bf182f618b', '${batchDelId}', NULL, '${.now?string["yyyy-MM-dd HH:mm:ss"]}', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('${.now?long}13', 'f6817f48af4fb3af11b9e8bf182f618b', '${exportId}', NULL, '${.now?string["yyyy-MM-dd HH:mm:ss"]}', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('${.now?long}14', 'f6817f48af4fb3af11b9e8bf182f618b', '${importId}', NULL, '${.now?string["yyyy-MM-dd HH:mm:ss"]}', '127.0.0.1');

View File

@@ -0,0 +1,254 @@
<#---->
<#-- freemarker 的一些工具方法 -->
<#---->
<#-- 驼峰转其他字符 -->
<#-- @param str 待转换的文本 -->
<#-- @param character 要转换成的字符 -->
<#-- @param case 转换大小写normal 不转换lower 小写upper 大写 -->
<#function camelToChar(str, character, case='normal')>
<#assign text=str?replace("([a-z])([A-Z]+)","$1${character}$2","r")/>
<#if case=="upper">
<#return text?upper_case>
<#elseif case=="lower">
<#return text?lower_case>
<#else>
<#return text>
</#if>
</#function>
<#--下划线转驼峰-->
<#function dashedToCamel(str)>
<#assign text=""/>
<#assign strlist = str?split("_")/>
<#list strlist as v>
<#assign text=text+v?cap_first/>
</#list>
<#return text?uncap_first>
</#function>
<#-- 驼峰转下划线 -->
<#function camelToDashed(str, case='lower')>
<#return camelToChar(str, "_", case)>
</#function>
<#---->
<#-- 驼峰转横线 -->
<#function camelToHorizontal(str, case='normal')>
<#return camelToChar(str, "-", case)>
</#function>
<#---->
<#-- 获取 v-model 属性 -->
<#function getVModel po,suffix="">
<#return "v-model=\"queryParam.${po.fieldName}${suffix}\"">
</#function>
<#-- 获取 placeholder 属性 -->
<#function getPlaceholder po,prefix,fillComment=true>
<#if fillComment>
<#return "placeholder=\"${prefix}${po.filedComment}\"">
<#else>
<#return "placeholder=\"${prefix}\"">
</#if>
</#function>
<#-- ** 判断某字段是否配置了校验 * -->
<#function poHasCheck po>
<#if (po.fieldValidType!'')?trim?length gt 0 || po.nullable == 'N'>
<#if po.fieldName != 'id'>
<#if po.nullable == 'N'
|| po.fieldValidType == '*'
|| po.fieldValidType == 'only'
|| po.fieldValidType == 'n6-16'
|| po.fieldValidType == '*6-16'
|| po.fieldValidType == 's6-18'
|| po.fieldValidType == 'url'
|| po.fieldValidType == 'e'
|| po.fieldValidType == 'm'
|| po.fieldValidType == 'p'
|| po.fieldValidType == 's'
|| po.fieldValidType == 'n'
|| po.fieldValidType == 'z'
|| po.fieldValidType == 'money'
|| po.fieldValidType != ''
>
<#return true>
</#if>
</#if>
</#if>
<#return false>
</#function>
<#-- ** 如果配置了校验就显示 validatorRules * -->
<#function autoWriteRules po>
<#if poHasCheck(po)>
<#return ", validatorRules.${po.fieldName}">
<#else>
<#return "">
</#if>
</#function>
<#-- ** 如果Blob就显示 String * -->
<#function autoStringSuffix po>
<#if po.fieldDbType=='Blob'>
<#return "'${po.fieldName}String'">
<#else>
<#return "'${po.fieldName}'">
</#if>
</#function>
<#-- ** 如果Blob就显示model方式 String * -->
<#function autoStringSuffixForModel po>
<#if po.fieldDbType=='Blob'>
<#return "${po.fieldName}String">
<#else>
<#return "${po.fieldName}">
</#if>
</#function>
<#-- ** 高级查询生成 * -->
<#function superQueryFieldList po>
<#assign superQuery_dictTable="">
<#assign superQuery_dictText="">
<#if po.dictTable?default("")?trim?length gt 1>
<#assign superQuery_dictTable="${po.dictTable}">
</#if>
<#if po.dictText?default("")?trim?length gt 1>
<#assign superQuery_dictText="${po.dictText}">
</#if>
<#if po.classType=="popup">
<#return "{type:'${po.classType}',value:'${po.fieldName}',text:'${po.filedComment}', popup:{code:'${po.dictTable}',field:'${po.dictField?split(',')[0]}',orgFields:'${po.dictField?split(',')[0]}',destFields:'${po.dictText?split(',')[0]}'}}">
<#elseif po.classType=="sel_user" || po.classType=="sel_depart" || po.classType=="datetime" || po.classType=="date" || po.classType=="pca" || po.classType=="switch">
<#return "{type:'${po.classType}',value:'${po.fieldName}',text:'${po.filedComment}'}">
<#else>
<#if po.classType=="sel_search" || po.classType=="list_multi">
<#return "{type:'${po.classType}',value:'${po.fieldName}',text:'${po.filedComment}',dictTable:\"${superQuery_dictTable}\", dictText:'${superQuery_dictText}', dictCode:'${po.dictField}'}">
<#elseif po.dictTable?? && po.dictTable!="" && po.classType!="sel_tree" && po.classType!="cat_tree" && po.classType!="link_down">
<#return "{type:'${po.fieldDbType}',value:'${po.fieldName}',text:'${po.filedComment}',dictCode:\"${po.dictTable},${po.dictText},${po.dictField}\"}">
<#elseif po.dictField?? && po.classType!="sel_tree" && po.classType!="cat_tree" && po.classType!="link_down">
<#return "{type:'${po.fieldDbType}',value:'${po.fieldName}',text:'${po.filedComment}',dictCode:'${po.dictField}'}">
<#elseif po.fieldDbType=="Text" || po.fieldDbType=="LongText">
<#return "{type:'string',value:'${po.fieldName}',text:'${po.filedComment}'}">
<#elseif po.fieldDbType=="Blob">
<#return "{type:'byte',value:'${po.fieldName}',text:'${po.filedComment}'}">
<#elseif po.fieldDbType=="BigDecimal" || po.fieldDbType=="double">
<#return "{type:'number',value:'${po.fieldName}',text:'${po.filedComment}'}">
<#else>
<#return "{type:'${po.fieldDbType}',value:'${po.fieldName}',text:'${po.filedComment}'}">
</#if>
</#if>
</#function>
<#-- update-begin---author:chenrui ---date:20231228 for:[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
<#-- ** 高级查询生成(Vue3 * -->
<#function superQueryFieldListForVue3(po,order)>
<#-- 高级查询日期格式化 -->
<#assign picker=''>
<#if po.extendParams?exists && po.extendParams.picker?exists>
<#assign picker='fieldExtendJson:"{\\"picker\\":\\"${po.extendParams.picker}\\"}",'>
</#if>
<#-- 字段展示/DB类型 -->
<#assign baseAttrs="view: '${po.classType}', type: 'string',">
<#if po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
<#assign baseAttrs="view: 'number', type: 'number',">
</#if>
<#-- 特殊类型控件扩展字段 -->
<#assign extAttrs="">
<#assign dictCode="">
<#if po.dictTable?default('')?trim?length gt 1 && po.dictText?default('')?trim?length gt 1 && po.dictField?default("")?trim?length gt 1>
<#-- update-begin---author:chenrui ---date:20231228 for:fix 带条件字典存在单引号导致js编译错误---------- -->
<#assign dictCode="dictTable: \"${po.dictTable}\", dictCode: '${po.dictField}', dictText: '${po.dictText}'">
<#-- update-begin---author:chenrui ---date:20231228 for:fix 带条件字典存在单引号导致js编译错误---------- -->
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign dictCode="dictCode: '${po.dictField}'">
<#else>
<#assign dictCode="dictCode: ''">
</#if>
<#if po.classType=='list' || po.classType=='list_multi' || po.classType=='sel_search' || po.classType=='checkbox' || po.classType=='radio'>
<#assign extAttrs="${dictCode},">
<#elseif po.classType=='cat_tree'>
<#-- 分类字典树 -->
<#-- update-begin---author:chenrui ---date:20240109 for[issue/5787]增加非空判断防止代码生成时空指针异常---------- -->
<#assign extAttrs="pcode: '${po.dictField?default('')}',">
<#-- update-end---author:chenrui ---date:20240109 for[issue/5787]增加非空判断防止代码生成时空指针异常---------- -->
<#elseif po.classType=='sel_tree'>
<#-- 自定义树 -->
<#if po.dictText??>
<#-- dictText示例:id,pid,name,has_child -->
<#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>
<#assign extAttrs="dict: '${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}'">
<#elseif po.dictText?split(',')[1]??>
<#assign extAttrs="pidField: '${po.dictText?split(',')[1]}'">
<#elseif po.dictText?split(',')[3]??>
<#assign extAttrs="hasChildField: '${po.dictText?split(',')[3]}'">
</#if>
</#if>
<#-- update-begin---author:chenrui ---date:20240109 for[issue/5787]增加非空判断防止代码生成时空指针异常---------- -->
<#assign extAttrs="${extAttrs}, pidValue: '${po.dictField?default('')}',">
<#-- update-end---author:chenrui ---date:20240109 for[issue/5787]增加非空判断防止代码生成时空指针异常---------- -->
<#elseif po.classType=='popup'>
<#-- popup -->
<#-- update-begin---author:chenrui ---date:20240109 for[issue/5787]增加非空判断防止代码生成时空指针异常---------- -->
<#if po.dictText?default("")?trim?length gt 1 && po.dictText?index_of(',') gt 0 && po.dictField?default("")?trim?length gt 1>
<#-- update-begin---author:chenrui ---date:20240109 for[issue/5787]增加非空判断防止代码生成时空指针异常---------- -->
<#-- 如果有多个回填字段,找到popup字段对应的来源字段 -->
<#assign orgFieldIx=po.dictText?split(',')?seq_index_of(po.fieldDbName)>
<#assign orgField=po.dictField?split(',')[orgFieldIx]>
<#else>
<#assign orgField=po.dictField?default("")>
</#if>
<#assign extAttrs="code: '${po.dictTable?default('')}', orgFields: '${orgField}', destFields: '${po.fieldName}', popupMulti: false,">
</#if>
<#return "${po.fieldName}: {title: '${po.filedComment}',order: ${order},${baseAttrs}${extAttrs}${picker}}" >
</#function>
<#-- update-end---author:chenrui ---date:20231228 for:[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
<#-- vue3 获取表单modal的宽度-->
<#function getModalWidth fieldRowNum>
<#assign modal_width = 800>
<#if fieldRowNum==2>
<#assign modal_width = 896>
<#elseif fieldRowNum==3>
<#assign modal_width = 1024>
<#elseif fieldRowNum==4>
<#assign modal_width = 1280>
</#if>
<#return modal_width>
</#function>
<#-- vue3 获取表单 colspan -->
<#function getFormSpan fieldRowNum>
<#assign form_span = 24>
<#if fieldRowNum==2>
<#assign form_span = 12>
<#elseif fieldRowNum==3>
<#assign form_span = 8>
<#elseif fieldRowNum==4>
<#assign form_span = 6>
</#if>
<#return form_span>
</#function>
<#-- vue3 native 判断字段名不是 pidField -->
<#function isNotPidField(tableVo, fieldDbName) >
<#assign flag = true>
<#if tableVo??>
<#if tableVo.extendParams??>
<#if tableVo.extendParams.pidField?default("")?trim == fieldDbName>
<#assign flag = false>
</#if>
</#if>
</#if>
<#return flag>
</#function>
<#-- vue3 native 获取范围字段 -->
<#function getRangeField(columns) >
<#assign rangeField = "">
<#list columns as po>
<#if po.isQuery=='Y'>
<#if po.queryMode=='group'>
<#if po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal' || po.classType=='time' || po.classType=='date' || po.classType=='datetime'>
<#assign rangeField = rangeField + "${po.fieldName},">
</#if>
</#if>
</#if>
</#list>
<#return rangeField>
</#function>

View File

@@ -0,0 +1,57 @@
<#include "/common/utils.ftl">
<#if po.isShow == 'Y' && poHasCheck(po)>
<#if po.fieldName != 'id'>
${po.fieldName}: [
<#assign fieldValidType = po.fieldValidType!''>
<#-- 非空校验 -->
<#if po.nullable == 'N' || fieldValidType == '*'>
{ required: true, message: '请输入${po.filedComment}!'},
<#elseif fieldValidType!=''>
{ required: false},
</#if>
<#-- 唯一校验 -->
<#if fieldValidType == 'only'>
{ validator: (rule, value, callback) => validateDuplicateValue(<#if sub?default("")?trim?length gt 1>'${sub.tableName}'<#else>'${tableName}'</#if>, '${po.fieldDbName}', value, this.model.id, callback)},
<#-- 6到16位数字 -->
<#elseif fieldValidType == 'n6-16'>
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'},
<#-- 6到16位任意字符 -->
<#elseif fieldValidType == '*6-16'>
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'},
<#-- 6到18位字符串 -->
<#elseif fieldValidType == 's6-18'>
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},
<#-- 网址 -->
<#elseif fieldValidType == 'url'>
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'},
<#-- 电子邮件 -->
<#elseif fieldValidType == 'e'>
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'},
<#-- 手机号码 -->
<#elseif fieldValidType == 'm'>
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'},
<#-- 邮政编码 -->
<#elseif fieldValidType == 'p'>
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'},
<#-- 字母 -->
<#elseif fieldValidType == 's'>
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'},
<#-- 数字 -->
<#elseif fieldValidType == 'n'>
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'},
<#-- 整数 -->
<#elseif fieldValidType == 'z'>
{ pattern: /^-?\d+$/, message: '请输入整数!'},
<#-- 金额 -->
<#elseif fieldValidType == 'money'>
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'},
<#-- 正则校验 -->
<#elseif fieldValidType != '' && fieldValidType != '*'>
{ pattern: '${fieldValidType}', message: '不符合校验规则!'},
<#-- 无校验 -->
<#else>
<#t>
</#if>
],
</#if>
</#if>

View File

@@ -0,0 +1,5 @@
validatorRules: {
<#list columns as po>
<#include "core.ftl">
</#list>
},

View File

@@ -0,0 +1,49 @@
<#assign fieldValidType = po.fieldValidType!''>
<#-- 非空校验 -->
<#if po.nullable == 'N' || fieldValidType == '*'>
{ required: true, message: '请输入${po.filedComment}!'}<#rt>,
<#elseif fieldValidType!=''>
{ required: false}<#rt>,
</#if>
<#-- 唯一校验 -->
<#if fieldValidType == 'only'>
{ validator: ${po.fieldName}Duplicatevalidate }<#rt>
<#-- 6到16位数字 -->
<#elseif fieldValidType == 'n6-16'>
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'}<#rt>,
<#-- 6到16位任意字符 -->
<#elseif fieldValidType == '*6-16'>
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'}<#rt>,
<#-- 6到18位字符串 -->
<#elseif fieldValidType == 's6-18'>
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},<#rt>,
<#-- 网址 -->
<#elseif fieldValidType == 'url'>
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'}<#rt>,
<#-- 电子邮件 -->
<#elseif fieldValidType == 'e'>
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'}<#rt>,
<#-- 手机号码 -->
<#elseif fieldValidType == 'm'>
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'}<#rt>,
<#-- 邮政编码 -->
<#elseif fieldValidType == 'p'>
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'}<#rt>,
<#-- 字母 -->
<#elseif fieldValidType == 's'>
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'}<#rt>,
<#-- 数字 -->
<#elseif fieldValidType == 'n'>
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'}<#rt>,
<#-- 整数 -->
<#elseif fieldValidType == 'z'>
{ pattern: /^-?\d+$/, message: '请输入整数!'}<#rt>,
<#-- 金额 -->
<#elseif fieldValidType == 'money'>
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'}<#rt>,
<#-- 正则校验 -->
<#elseif fieldValidType != '' && fieldValidType != '*'>
{ pattern: '${fieldValidType}', message: '不符合校验规则!'}<#rt>,
<#else>
<#t>
</#if>

View File

@@ -0,0 +1,6 @@
<#include "/common/utils.ftl">
<#list columns as po>
<#if po.isShow == 'Y' && poHasCheck(po)>
${po.fieldName}: [<#include "vue3CoreNative.ftl">],
</#if>
</#list>

View File

@@ -0,0 +1,23 @@
<#include "/common/utils.ftl">
<#if col.isShow == 'Y' && poHasCheck(col)>
validateRules: [
<#if col.fieldName != 'id'>
<#assign subFieldValidType = col.fieldValidType!''>
<#-- 非空校验 -->
<#if col.nullable == 'N' || subFieldValidType == '*'>
{ required: true, message: '${'$'}{title}不能为空' },
<#elseif subFieldValidType!=''>
{ required: false},
</#if>
<#-- 其他情况下只要有值就被认为是正则校验 -->
<#if subFieldValidType?length gt 0>
<#assign subMessage = '格式不正确'>
<#if subFieldValidType == 'only' >
<#assign subMessage = '不能重复'>
</#if>
{ pattern: "${subFieldValidType}", message: "${'$'}{title}${subMessage}" }
<#t>
</#if>
</#if>
],
</#if>

View File

@@ -0,0 +1,5 @@
validatorRules: {
<#list sub.colums as po>
<#include "core.ftl">
</#list>
},

View File

@@ -0,0 +1,275 @@
package ${bussiPackage}.${entityPackage}.controller;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.oConvertUtils;
import ${bussiPackage}.${entityPackage}.entity.${entityName};
import ${bussiPackage}.${entityPackage}.service.I${entityName}Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.jeecg.common.system.base.controller.JeecgController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSON;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.apache.shiro.authz.annotation.RequiresPermissions;
<#assign bpm_flag=false>
<#assign has_multi_query_field=false>
<#list originalColumns as po>
<#if po.fieldDbName=='bpm_status'>
<#assign bpm_flag=true>
</#if>
<#if po.isQuery=='Y' && (po.classType=='list' || po.classType=='list_multi' || po.classType=='radio' || po.classType=='checkbox')>
<#assign has_multi_query_field=true>
</#if>
</#list>
<#assign enhanceJavaList=[]>
<#if tableVo.extendParams?? && tableVo.extendParams.enhanceJavaList??>
<#assign enhanceJavaList = tableVo.extendParams.enhanceJavaList?filter(enhance -> enhance??)>
</#if>
/**
* @Description: ${tableVo.ftlDescription}
* @Author: jeecg-boot
* @Date: ${.now?string["yyyy-MM-dd"]}
* @Version: V1.0
*/
@Tag(name="${tableVo.ftlDescription}")
@RestController
@RequestMapping("/${entityPackagePath}/${entityName?uncap_first}")
@Slf4j
public class ${entityName}Controller extends JeecgController<${entityName}, I${entityName}Service> {
@Autowired
private I${entityName}Service ${entityName?uncap_first}Service;
/**
* 分页列表查询
*
* @param ${entityName?uncap_first}
* @param pageNo
* @param pageSize
* @param req
* @return
*/
//@AutoLog(value = "${tableVo.ftlDescription}-分页列表查询")
@Operation(summary="${tableVo.ftlDescription}-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<${entityName}>> queryPageList(${entityName} ${entityName?uncap_first},
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
<#if enhanceJavaList?size gt 0>
<#list enhanceJavaList as enhanceJava>
<#if enhanceJava.buttonCode=='query' && enhanceJava.event=='start' && enhanceJava.activeStatus=='1'>
//TODO 查询前触发的方法,代码生成后,请手工实现增强类逻辑;
//${entityName?uncap_first}Service.beforeQuery()
</#if>
</#list>
</#if>
<#if has_multi_query_field>
// 自定义查询规则
Map<String, QueryRuleEnum> customeRuleMap = new HashMap<>();
// 自定义多选的查询规则为LIKE_WITH_OR
<#list originalColumns as po>
<#if po.isQuery=='Y' && (po.classType=='list' || po.classType=='list_multi' || po.classType=='radio' || po.classType=='checkbox')>
customeRuleMap.put("${po.fieldName}", QueryRuleEnum.LIKE_WITH_OR);
</#if>
</#list>
QueryWrapper<${entityName}> queryWrapper = QueryGenerator.initQueryWrapper(${entityName?uncap_first}, req.getParameterMap(),customeRuleMap);
<#else>
QueryWrapper<${entityName}> queryWrapper = QueryGenerator.initQueryWrapper(${entityName?uncap_first}, req.getParameterMap());
</#if>
Page<${entityName}> page = new Page<${entityName}>(pageNo, pageSize);
IPage<${entityName}> pageList = ${entityName?uncap_first}Service.page(page, queryWrapper);
<#if enhanceJavaList?size gt 0>
<#list enhanceJavaList as enhanceJava>
<#if enhanceJava.buttonCode=='query' && enhanceJava.event=='end' && enhanceJava.activeStatus=='1'>
//TODO 查询后触发的方法,代码生成后,请手工实现增强类逻辑;
//${entityName?uncap_first}Service.afterQuery()
</#if>
</#list>
</#if>
return Result.OK(pageList);
}
/**
* 添加
*
* @param ${entityName?uncap_first}
* @return
*/
@AutoLog(value = "${tableVo.ftlDescription}-添加")
@Operation(summary="${tableVo.ftlDescription}-添加")
@RequiresPermissions("${entityPackage}:${tableName}:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody ${entityName} ${entityName?uncap_first}) {
<#if enhanceJavaList?size gt 0>
<#list enhanceJavaList as enhanceJava>
<#if enhanceJava.buttonCode=='add' && enhanceJava.event=='start' && enhanceJava.activeStatus=='1'>
//TODO 新增前的处理方法,代码生成后,请手工实现增强类逻辑;
//${entityName?uncap_first}Service.beforeAdd()
</#if>
</#list>
</#if>
<#if bpm_flag>
${entityName?uncap_first}.setBpmStatus("1");
</#if>
${entityName?uncap_first}Service.save(${entityName?uncap_first});
<#if enhanceJavaList?size gt 0>
<#list enhanceJavaList as enhanceJava>
<#if enhanceJava.buttonCode=='add' && enhanceJava.event=='end' && enhanceJava.activeStatus=='1'>
//TODO 新增后的处理方法,代码生成后,请手工实现增强类逻辑;
//${entityName?uncap_first}Service.afterAdd()
</#if>
</#list>
</#if>
return Result.OK("添加成功!");
}
/**
* 编辑
*
* @param ${entityName?uncap_first}
* @return
*/
@AutoLog(value = "${tableVo.ftlDescription}-编辑")
@Operation(summary="${tableVo.ftlDescription}-编辑")
@RequiresPermissions("${entityPackage}:${tableName}:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<String> edit(@RequestBody ${entityName} ${entityName?uncap_first}) {
<#if enhanceJavaList?size gt 0>
<#list enhanceJavaList as enhanceJava>
<#if enhanceJava.buttonCode=='edit' && enhanceJava.event=='start' && enhanceJava.activeStatus=='1'>
//TODO 编辑前,代码生成后,请手工实现增强类逻辑;
//${entityName?uncap_first}Service.beforeEdit()
</#if>
</#list>
</#if>
${entityName?uncap_first}Service.updateById(${entityName?uncap_first});
<#if enhanceJavaList?size gt 0>
<#list enhanceJavaList as enhanceJava>
<#if enhanceJava.buttonCode=='edit' && enhanceJava.event=='end' && enhanceJava.activeStatus=='1'>
//TODO 编辑后,代码生成后,请手工实现增强类逻辑;
//${entityName?uncap_first}Service.afterEdit()
</#if>
</#list>
</#if>
return Result.OK("编辑成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@AutoLog(value = "${tableVo.ftlDescription}-通过id删除")
@Operation(summary="${tableVo.ftlDescription}-通过id删除")
@RequiresPermissions("${entityPackage}:${tableName}:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name="id",required=true) String id) {
${entityName?uncap_first}Service.removeById(id);
return Result.OK("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@AutoLog(value = "${tableVo.ftlDescription}-批量删除")
@Operation(summary="${tableVo.ftlDescription}-批量删除")
@RequiresPermissions("${entityPackage}:${tableName}:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
this.${entityName?uncap_first}Service.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
//@AutoLog(value = "${tableVo.ftlDescription}-通过id查询")
@Operation(summary="${tableVo.ftlDescription}-通过id查询")
@GetMapping(value = "/queryById")
public Result<${entityName}> queryById(@RequestParam(name="id",required=true) String id) {
${entityName} ${entityName?uncap_first} = ${entityName?uncap_first}Service.getById(id);
if(${entityName?uncap_first}==null) {
return Result.error("未找到对应数据");
}
return Result.OK(${entityName?uncap_first});
}
/**
* 导出excel
*
* @param request
* @param ${entityName?uncap_first}
*/
@RequiresPermissions("${entityPackage}:${tableName}:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, ${entityName} ${entityName?uncap_first}) {
<#if enhanceJavaList?size gt 0>
<#list enhanceJavaList as enhanceJava>
<#if enhanceJava.buttonCode=='export' && enhanceJava.event=='start' && enhanceJava.activeStatus=='1'>
//TODO 导出前,代码生成后,请手工实现增强类逻辑;
//${entityName?uncap_first}Service.beforeExport()
</#if>
</#list>
</#if>
return super.exportXls(request, ${entityName?uncap_first}, ${entityName}.class, "${tableVo.ftlDescription}");
}
/**
* 通过excel导入数据
*
* @param request
* @param response
* @return
*/
@RequiresPermissions("${entityPackage}:${tableName}:importExcel")
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
<#if enhanceJavaList?size gt 0>
<#list enhanceJavaList as enhanceJava>
<#if enhanceJava.buttonCode=='import' && enhanceJava.event=='start' && enhanceJava.activeStatus=='1'>
//TODO 导入前,代码生成后,请手工实现增强类逻辑;
//${entityName?uncap_first}Service.beforeImport()
</#if>
</#list>
</#if>
return super.importExcel(request, response, ${entityName}.class);
}
}

View File

@@ -0,0 +1,93 @@
<#include "/common/utils.ftl">
package ${bussiPackage}.${entityPackage}.entity;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecg.common.aspect.annotation.Dict;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Description: ${tableVo.ftlDescription}
* @Author: jeecg-boot
* @Date: ${.now?string["yyyy-MM-dd"]}
* @Version: V1.0
*/
@Data
@TableName("${tableName}")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description="${tableVo.ftlDescription}")
public class ${entityName} implements Serializable {
private static final long serialVersionUID = 1L;
<#assign excel_ignore_arr=['createBy','createTime','updateBy','updateTime','sysOrgCode']>
<#assign excel_ignore_classType_arr=['pca','switch','cat_tree']>
<#list originalColumns as po>
<#-- 生成字典Code -->
<#assign list_field_dictCode="">
<#if po.classType='sel_user'>
<#assign list_field_dictCode=', dictTable = "sys_user", dicText = "${camelToDashed(po.extendParams.text?default(\"realname\")?trim)}", dicCode = "${camelToDashed(po.extendParams.store?default(\"username\")?trim)}"'>
<#elseif po.classType='sel_depart'>
<#assign list_field_dictCode=', dictTable = "sys_depart", dicText = "${camelToDashed(po.extendParams.text?default(\"depart_name\")?trim)}", dicCode = "${camelToDashed(po.extendParams.store?default(\"id\")?trim)}"'>
<#elseif po.classType=='list' || po.classType=='list_multi' || po.classType=='sel_search' || po.classType=='radio' || po.classType=='checkbox'>
<#if po.dictTable?default("")?trim?length gt 1>
<#assign list_field_dictCode=', dictTable = "${po.dictTable}", dicText = "${po.dictText}", dicCode = "${po.dictField}"'>
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign list_field_dictCode=', dicCode = "${po.dictField}"'>
</#if>
<#elseif po.classType=='sel_tree'>
<#assign list_field_dictCode=', dictTable = "${po.dictTable}", dicText = "${po.dictText?split(",")[2]}", dicCode = "${po.dictText?split(",")[0]}"'>
<#elseif po.classType=='link_table'>
<#assign list_field_dictCode=', dictTable = "${po.dictTable}", dicCode = "${po.dictField}", dicText = "${po.dictText?split(",")[0]}"'>
</#if>
/**${po.filedComment}*/
<#if po.fieldName == primaryKeyField>
@TableId(type = IdType.ASSIGN_ID)
<#else>
<#if po.fieldDbType =='Date' || po.fieldDbType =='Datetime'>
<#if po.classType=='date'>
<#if !excel_ignore_arr?seq_contains("${po.fieldName}")>
@Excel(name = "${po.filedComment}", width = 15, format = "yyyy-MM-dd")
</#if>
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern="yyyy-MM-dd")
<#else>
<#if !excel_ignore_arr?seq_contains("${po.fieldName}")>
@Excel(name = "${po.filedComment}", width = 20, format = "yyyy-MM-dd HH:mm:ss")
</#if>
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
</#if>
<#else>
<#if !excel_ignore_arr?seq_contains("${po.fieldName}") && !excel_ignore_classType_arr?seq_contains("${po.classType}")>
@Excel(name = "${po.filedComment}", width = 15${list_field_dictCode})
</#if>
</#if>
<#if list_field_dictCode?length gt 1>
@Dict(${list_field_dictCode?substring(2)})
</#if>
<#-- <#if po.classType!='popup'>
<#if po.dictTable?default("")?trim?length gt 1>
@Dict(dicCode="${po.dictField}",dicText="${po.dictText}",dictTable="${po.dictTable}")
<#elseif po.dictField?default("")?trim?length gt 1>
@Dict(dicCode="${po.dictField}")
</#if>
</#if>-->
</#if>
<#include "/common/blob.ftl">
</#list>
}

View File

@@ -0,0 +1,17 @@
package ${bussiPackage}.${entityPackage}.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import ${bussiPackage}.${entityPackage}.entity.${entityName};
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @Description: ${tableVo.ftlDescription}
* @Author: jeecg-boot
* @Date: ${.now?string["yyyy-MM-dd"]}
* @Version: V1.0
*/
public interface ${entityName}Mapper extends BaseMapper<${entityName}> {
}

View File

@@ -0,0 +1,5 @@
<?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="${bussiPackage}.${entityPackage}.mapper.${entityName}Mapper">
</mapper>

View File

@@ -0,0 +1,14 @@
package ${bussiPackage}.${entityPackage}.service;
import ${bussiPackage}.${entityPackage}.entity.${entityName};
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @Description: ${tableVo.ftlDescription}
* @Author: jeecg-boot
* @Date: ${.now?string["yyyy-MM-dd"]}
* @Version: V1.0
*/
public interface I${entityName}Service extends IService<${entityName}> {
}

View File

@@ -0,0 +1,19 @@
package ${bussiPackage}.${entityPackage}.service.impl;
import ${bussiPackage}.${entityPackage}.entity.${entityName};
import ${bussiPackage}.${entityPackage}.mapper.${entityName}Mapper;
import ${bussiPackage}.${entityPackage}.service.I${entityName}Service;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
/**
* @Description: ${tableVo.ftlDescription}
* @Author: jeecg-boot
* @Date: ${.now?string["yyyy-MM-dd"]}
* @Version: V1.0
*/
@Service
public class ${entityName}ServiceImpl extends ServiceImpl<${entityName}Mapper, ${entityName}> implements I${entityName}Service {
}

View File

@@ -0,0 +1,93 @@
<template>
<view>
<!--标题和返回-->
<cu-custom :bgColor="NavBarColor" isBack :backRouterName="backRouteName">
<block slot="backText">返回</block>
<block slot="content">${tableVo.ftlDescription}</block>
</cu-custom>
<!--表单区域-->
<view>
<form>
<#list columns as po><#rt/>
<#if po.fieldName !='id'><#rt/>
<#if po.classType =='date'>
<my-date label="${po.filedComment}" fields="day" v-model="model.${po.fieldName}" placeholder="请输入${po.filedComment}"></my-date>
<#elseif po.classType =='datetime'>
<my-date label="${po.filedComment}" v-model="model.${po.fieldName}" placeholder="请输入${po.filedComment}"></my-date>
<#else>
<view class="cu-form-group">
<view class="flex align-center">
<view class="title"><text space="ensp">${po.filedComment}</text></view>
<input <#if "int,BigDecimal,double,"?contains(po.fieldDbType)>type="number"</#if> placeholder="请输入${po.filedComment}" v-model="model.${po.fieldName}"/>
</view>
</view>
</#if>
</#if>
</#list>
<view class="padding">
<button class="cu-btn block bg-blue margin-tb-sm lg" @click="onSubmit">
<text v-if="loading" class="cuIcon-loading2 cuIconfont-spin"></text>提交
</button>
</view>
</form>
</view>
</view>
</template>
<script>
import myDate from '@/components/my-componets/my-date.vue'
export default {
name: "${entityName}Form",
components:{ myDate },
props:{
formData:{
type:Object,
default:()=>{},
required:false
}
},
data(){
return {
CustomBar: this.CustomBar,
NavBarColor: this.NavBarColor,
loading:false,
model: {},
backRouteName:'index',
url: {
queryById: "/${entityPackagePath}/${entityName?uncap_first}/queryById",
add: "/${entityPackagePath}/${entityName?uncap_first}/add",
edit: "/${entityPackagePath}/${entityName?uncap_first}/edit",
},
}
},
created(){
this.initFormData();
},
methods:{
initFormData(){
if(this.formData){
let dataId = this.formData.dataId;
this.$http.get(this.url.queryById,{params:{id:dataId}}).then((res)=>{
if(res.data.success){
console.log("表单数据",res);
this.model = res.data.result;
}
})
}
},
onSubmit() {
let myForm = {...this.model};
this.loading = true;
let url = myForm.id?this.url.edit:this.url.add;
this.$http.post(url,myForm).then(res=>{
console.log("res",res)
this.loading = false
this.$Router.push({name:this.backRouteName})
}).catch(()=>{
this.loading = false
});
}
}
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
<view>
<!--标题和返回-->
<cu-custom :bgColor="NavBarColor" isBack>
<block slot="backText">返回</block>
<block slot="content">${tableVo.ftlDescription}</block>
</cu-custom>
<!--滚动加载列表-->
<mescroll-body ref="mescrollRef" bottom="88" @init="mescrollInit" :up="upOption" :down="downOption" @down="downCallback" @up="upCallback">
<view class="cu-list menu">
<view class="cu-item" v-for="(item,index) in list" :key="index" @click="goHome">
<view class="flex" style="width:100%">
<text class="text-lg" style="color: #000;">
{{ item.createBy}}
</text>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
import Mixin from "@/common/mixin/Mixin.js";
export default {
name: '${tableVo.ftlDescription}',
mixins: [MescrollMixin,Mixin],
data() {
return {
CustomBar:this.CustomBar,
NavBarColor:this.NavBarColor,
url: "/${entityPackagePath}/${entityName?uncap_first}/list",
};
},
methods: {
goHome(){
this.$Router.push({name: "index"})
}
}
}
</script>

View File

@@ -0,0 +1,49 @@
import { render } from '@/common/renderUtils';
//列表数据
export const columns = [
<#list columns as po>
<#if po.isShowList =='Y' && po.fieldName !='id' && po.fieldName !='delFlag'>
{
title: '${po.filedComment}',
align:"center",
<#if po.sort=='Y'>
sorter: true,
</#if>
<#if po.classType=='date'>
dataIndex: '${po.fieldName}',
<#elseif po.fieldDbType=='Blob'>
dataIndex: '${po.fieldName}String'
<#elseif po.classType=='umeditor'>
dataIndex: '${po.fieldName}',
<#elseif po.classType=='pca'>
dataIndex: '${po.fieldName}',
<#elseif po.classType=='file'>
dataIndex: '${po.fieldName}',
<#elseif po.classType=='image'>
dataIndex: '${po.fieldName}',
customRender:render.renderImage,
<#elseif po.classType=='switch'>
dataIndex: '${po.fieldName}',
<#assign switch_extend_arr=['Y','N']>
<#if po.dictField?default("")?contains("[")>
<#assign switch_extend_arr=po.dictField?eval>
</#if>
<#list switch_extend_arr as a>
<#if a_index == 0>
<#assign switch_extend_arr1=a>
<#else>
<#assign switch_extend_arr2=a>
</#if>
</#list>
customRender:({text}) => {
return render.renderSwitch(text, [{text:'是',value:'${switch_extend_arr1}'},{text:'否',value:'${switch_extend_arr2}'}])
},
<#elseif po.classType == 'sel_tree' || po.classType=='list' || po.classType=='list_multi' || po.classType=='sel_search' || po.classType=='radio' || po.classType=='checkbox' || po.classType=='sel_depart' || po.classType=='sel_user' || po.classType=='popup_dict'>
dataIndex: '${po.fieldName}_dictText'
<#else>
dataIndex: '${po.fieldName}'
</#if>
},
</#if>
</#list>
];

View File

@@ -0,0 +1,560 @@
<#include "/common/utils.ftl">
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationStyle: 'custom',
navigationBarTitleText: '${tableVo.ftlDescription}',
},
}
</route>
<template>
<#assign onlyFields = []>
<PageLayout :navTitle="navTitle" :backRouteName="backRouteName">
<scroll-view class="scrollArea" scroll-y>
<view class="form-container">
<wd-form ref="form" :model="myFormData">
<wd-cell-group border>
<#list columns as po><#rt/>
<#assign form_field_dictCode="">
<#if po.dictTable?default("")?trim?length gt 1 && po.dictText?default("")?trim?length gt 1 && po.dictField?default("")?trim?length gt 1>
<#assign form_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign form_field_dictCode="${po.dictField}">
</#if>
<view class="{ 'mt-14px': ${po_index % 2} == 0 }">
<#if po.fieldName !='id' && po.isShow =='Y' && po.fieldName !='delFlag'><#rt/>
<#if po.classType =='image'>
<!-- 图片 -->
<wd-cell
:title="get4Label('${po.filedComment}')"
title-width="100px"
<#if po.nullable == 'N'>
:required="true"
</#if>
>
<online-image
v-model:value="myFormData[${autoStringSuffix(po)}]"
name=${autoStringSuffix(po)}
<#if po.uploadnum??>
:maxNum="${po.uploadnum}"
</#if>
/>
</wd-cell>
<#elseif po.classType =='file'>
<wd-cell
:title="get4Label('${po.filedComment}')"
title-width="100px"
<#if po.nullable == 'N'>
:required="true"
</#if>
>
<online-file-custom
v-model:value="myFormData[${autoStringSuffix(po)}]"
name=${autoStringSuffix(po)}
></online-file-custom>
</wd-cell>
<#elseif po.classType =='datetime' || po.classType =='time'>
<DateTime
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
type="${po.classType}"
<#if po.classType =='datetime'>
format="YYYY-MM-DD HH:mm:ss"
<#else>
format="HH:mm:ss"
</#if>
name=${autoStringSuffix(po)}
v-model="myFormData[${autoStringSuffix(po)}]"
></DateTime>
<#elseif po.classType =='date'>
<online-date
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
<#if po.extendParams?exists && po.extendParams.picker?exists>
:type="getDateExtendType('${po.extendParams.picker}')"
<#else>
type="${po.classType}"
</#if>
name=${autoStringSuffix(po)}
v-model:value="myFormData[${autoStringSuffix(po)}]"
></online-date>
<#elseif po.classType =='switch'>
<#assign switch_extend_arr=['Y','N']>
<#if po.dictField?default("")?contains("[")>
<#assign switch_extend_arr=po.dictField?eval>
</#if>
<!-- 开关 -->
<wd-cell
:label="get4Label('${po.filedComment}')"
name=${autoStringSuffix(po)}
title-width="100px"
center
>
<wd-switch
:label="get4Label('${po.filedComment}')"
name=${autoStringSuffix(po)}
size="18px"
v-model="myFormData[${autoStringSuffix(po)}]"
active-value="${switch_extend_arr[0]}"
inactive-value="${switch_extend_arr[1]}"
/>
</wd-cell>
<#elseif po.classType =='list' || po.classType =='sel_search'>
<online-select
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
type="${po.classType}"
name=${autoStringSuffix(po)}
dict="${form_field_dictCode}"
v-model="myFormData[${autoStringSuffix(po)}]"
></online-select>
<#elseif po.classType =='checkbox'>
<online-checkbox
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
type="${po.classType}"
name=${autoStringSuffix(po)}
dict="${form_field_dictCode}"
v-model="myFormData[${autoStringSuffix(po)}]"
></online-checkbox>
<#elseif po.classType =='radio'>
<online-radio
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
type="${po.classType}"
name=${autoStringSuffix(po)}
dict="${form_field_dictCode}"
v-model="myFormData[${autoStringSuffix(po)}]"
></online-radio>
<#elseif po.classType =='list_multi'>
<online-multi
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
type="${po.classType}"
name=${autoStringSuffix(po)}
dict="${form_field_dictCode}"
v-model="myFormData[${autoStringSuffix(po)}]"
></online-multi>
<#elseif po.classType =='textarea' || po.classType =='markdown' || po.classType =='umeditor'>
<wd-textarea
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
type="${po.classType}"
name=${autoStringSuffix(po)}
prop=${autoStringSuffix(po)}
clearable
:maxlength="300"
v-model="myFormData[${autoStringSuffix(po)}]"
></wd-textarea>
<#elseif po.classType =='password'>
<wd-input
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
show-password
name=${autoStringSuffix(po)}
prop=${autoStringSuffix(po)}
clearable
v-model="myFormData[${autoStringSuffix(po)}]"
></wd-input>
<#elseif po.classType =='pca'>
<online-pca
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
name=${autoStringSuffix(po)}
prop=${autoStringSuffix(po)}
clearable
v-model:value="myFormData[${autoStringSuffix(po)}]"
></online-pca>
<#elseif po.classType =='popup_dict'>
<PopupDict
labelWidth="100px"
:label="get4Label('${po.filedComment}')"
v-model="myFormData[${autoStringSuffix(po)}]"
:multi="${po.extendParams.popupMulti?c}"
dictCode="${po.dictTable},${po.dictText},${po.dictField}"
></PopupDict>
<#elseif po.classType =='popup'>
<#assign sourceFields = po.dictField?default("")?trim?split(",")/>
<#assign targetFields = po.dictText?default("")?trim?split(",")/>
<Popup
labelWidth="100px"
:label="get4Label('${po.filedComment}')"
v-model="myFormData[${autoStringSuffix(po)}]"
:multi="${po.extendParams.popupMulti?c}"
code="${po.dictTable}"
:setFieldsValue="setFieldsValue"
:fieldConfig="[
<#list sourceFields as fieldName>
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
]"
></Popup>
<#elseif po.classType =='link_table'>
<online-popup-link-record
:label="get4Label('${po.filedComment}')"
labelWidth="100px"
name=${autoStringSuffix(po)}
:formSchema="getFormSchema('${po.dictTable}','${po.dictField}','${po.dictText}')"
v-model:value="myFormData[${autoStringSuffix(po)}]"
></online-popup-link-record>
<#elseif po.classType =='sel_user'>
<select-user
labelWidth="100px"
:label="get4Label('${po.filedComment}')"
name=${autoStringSuffix(po)}
v-model="myFormData[${autoStringSuffix(po)}]"
<#if po.extendParams?exists && po.extendParams.text?exists>labelKey="${po.extendParams.text}"</#if>
<#if po.extendParams?exists && po.extendParams.store?exists>rowKey="${po.extendParams.store}"</#if>
></select-user>
<#elseif po.classType =='sel_depart'>
<select-dept
labelWidth="100px"
:label="get4Label('${po.filedComment}')"
name=${autoStringSuffix(po)}
v-model="myFormData[${autoStringSuffix(po)}]"
<#if po.extendParams?exists && po.extendParams.text?exists>labelKey="${po.extendParams.text}"</#if>
<#if po.extendParams?exists && po.extendParams.store?exists>rowKey="${po.extendParams.store}"</#if>
:multiple="${po.extendParams.multi?default('true')}"
></select-dept>
<#elseif po.classType =='cat_tree'>
<CategorySelect
labelWidth="100px"
:label="get4Label('${po.filedComment}')"
v-model="myFormData[${autoStringSuffix(po)}]"
pcode="${po.dictField?default("")}"
></CategorySelect>
<#elseif po.classType =='sel_tree'>
<TreeSelect
labelWidth="100px"
:label="get4Label('${po.filedComment}')"
v-model="myFormData[${autoStringSuffix(po)}]"
<#if po.dictText??>
<#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>
dict="${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}"
<#elseif po.dictText?split(',')[1]??>
pidField="${po.dictText?split(',')[1]}"
<#elseif po.dictText?split(',')[3]??>
hasChildField="${po.dictText?split(',')[3]}"
</#if>
</#if>
:pidValue="`${po.dictField}`"
></TreeSelect>
<#elseif po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
<wd-input
label-width="100px"
v-model="myFormData[${autoStringSuffix(po)}]"
:label="get4Label('${po.filedComment}')"
name=${autoStringSuffix(po)}
prop=${autoStringSuffix(po)}
placeholder="请选择${po.filedComment}"
inputMode="numeric"
:rules="[
<#if po.fieldName != 'id'>
<#assign fieldValidType = po.fieldValidType!''>
<#-- 非空校验 -->
<#if po.nullable == 'N' || fieldValidType == '*'>
{ required: true, message: '请输入${po.filedComment}!'},
<#elseif fieldValidType!=''>
{ required: false},
</#if>
<#-- 6到16位数字 -->
<#if fieldValidType == 'n6-16'>
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'},
<#-- 6到16位任意字符 -->
<#elseif fieldValidType == '*6-16'>
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'},
<#-- 6到18位字母 -->
<#elseif fieldValidType == 's6-18'>
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},
<#-- 网址 -->
<#elseif fieldValidType == 'url'>
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'},
<#-- 电子邮件 -->
<#elseif fieldValidType == 'e'>
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'},
<#-- 手机号码 -->
<#elseif fieldValidType == 'm'>
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'},
<#-- 邮政编码 -->
<#elseif fieldValidType == 'p'>
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'},
<#-- 字母 -->
<#elseif fieldValidType == 's'>
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'},
<#-- 数字 -->
<#elseif fieldValidType == 'n'>
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'},
<#-- 整数 -->
<#elseif fieldValidType == 'z'>
{ pattern: /^-?\d+$/, message: '请输入整数!'},
<#-- 金额 -->
<#elseif fieldValidType == 'money'>
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'},
<#-- 唯一校验 -->
<#elseif fieldValidType != '' && fieldValidType == 'only'>
<#assign onlyFields = onlyFields + [autoStringSuffix(po)]>
<#-- 正则校验 -->
<#elseif fieldValidType != '' && fieldValidType != '*'>
{ pattern: /${fieldValidType}/, message: '不符合校验规则!'},
<#-- 无校验 -->
<#else>
<#t>
</#if>
</#if>
]"
clearable
/>
<#else>
<wd-input
label-width="100px"
v-model="myFormData[${autoStringSuffix(po)}]"
:label="get4Label('${po.filedComment}')"
name=${autoStringSuffix(po)}
prop=${autoStringSuffix(po)}
placeholder="请选择${po.filedComment}"
:rules="[
<#if po.fieldName != 'id'>
<#assign fieldValidType = po.fieldValidType!''>
<#-- 非空校验 -->
<#if po.nullable == 'N' || fieldValidType == '*'>
{ required: true, message: '请输入${po.filedComment}!'},
<#elseif fieldValidType!=''>
{ required: false},
</#if>
<#-- 6到16位数字 -->
<#if fieldValidType == 'n6-16'>
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'},
<#-- 6到16位任意字符 -->
<#elseif fieldValidType == '*6-16'>
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'},
<#-- 6到18位字母 -->
<#elseif fieldValidType == 's6-18'>
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},
<#-- 网址 -->
<#elseif fieldValidType == 'url'>
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'},
<#-- 电子邮件 -->
<#elseif fieldValidType == 'e'>
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'},
<#-- 手机号码 -->
<#elseif fieldValidType == 'm'>
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'},
<#-- 邮政编码 -->
<#elseif fieldValidType == 'p'>
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'},
<#-- 字母 -->
<#elseif fieldValidType == 's'>
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'},
<#-- 数字 -->
<#elseif fieldValidType == 'n'>
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'},
<#-- 整数 -->
<#elseif fieldValidType == 'z'>
{ pattern: /^-?\d+$/, message: '请输入整数!'},
<#-- 金额 -->
<#elseif fieldValidType == 'money'>
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'},
<#-- 唯一校验 -->
<#elseif fieldValidType != '' && fieldValidType == 'only'>
<#assign onlyFields = onlyFields + [autoStringSuffix(po)]>
<#-- 正则校验 -->
<#elseif fieldValidType != '' && fieldValidType != '*'>
{ pattern: /${fieldValidType}/, message: '不符合校验规则!'},
<#-- 无校验 -->
<#else>
<#t>
</#if>
</#if>
]"
clearable
/>
</#if>
</#if>
</view>
</#list>
</wd-cell-group>
</wd-form>
</view>
</scroll-view>
<view class="footer">
<wd-button :disabled="loading" block :loading="loading" @click="handleSubmit">提交</wd-button>
</view>
</PageLayout>
</template>
<script lang="ts" setup>
import { onLoad } from '@dcloudio/uni-app'
import { http } from '@/utils/http'
import { useToast } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { ref, onMounted, computed,reactive } from 'vue'
import OnlineImage from '@/components/online/view/online-image.vue'
import OnlineFile from '@/components/online/view/online-file.vue'
import OnlineFileCustom from '@/components/online/view/online-file-custom.vue'
import OnlineSelect from '@/components/online/view/online-select.vue'
import OnlineTime from '@/components/online/view/online-time.vue'
import OnlineDate from '@/components/online/view/online-date.vue'
import OnlineRadio from '@/components/online/view/online-radio.vue'
import OnlineCheckbox from '@/components/online/view/online-checkbox.vue'
import OnlineMulti from '@/components/online/view/online-multi.vue'
import OnlinePopupLinkRecord from '@/components/online/view/online-popup-link-record.vue'
import OnlinePca from '@/components/online/view/online-pca.vue'
import SelectDept from '@/components/SelectDept/SelectDept.vue'
import SelectUser from '@/components/SelectUser/SelectUser.vue'
import {duplicateCheck} from "@/service/api";
defineOptions({
name: '${entityName}Form',
options: {
styleIsolation: 'shared',
},
})
const toast = useToast()
const router = useRouter()
const form = ref(null)
// 定义响应式数据
const myFormData = reactive({})
const loading = ref(false)
const navTitle = ref('新增')
const dataId = ref('')
const backRouteName = ref('${entityName}List')
// 定义 initForm 方法
const initForm = (item) => {
console.log('initForm item', item)
if(item?.dataId){
dataId.value = item.dataId;
navTitle.value = item.dataId?'编辑':'新增';
initData();
}
}
// 初始化数据
const initData = () => {
http.get("/${entityPackagePath}/${entityName?uncap_first}/queryById",{id:dataId.value}).then((res) => {
if (res.success) {
let obj = res.result
Object.assign(myFormData, { ...obj })
}else{
toast.error(res?.message || '表单加载失败!')
}
})
}
const handleSuccess = () => {
uni.$emit('refreshList');
router.back()
}
/**
* 校验唯一
* @param values
* @returns {boolean}
*/
async function fieldCheck(values: any) {
const onlyField = [
<#list onlyFields as field>
${field}<#sep>, </#sep>
</#list>
];
for (const field of onlyField) {
if (values[field]) {
// 仅校验有值的字段
const res: any = await duplicateCheck({
tableName: '${tableName}',
fieldName: field, // 使用处理后的字段名
fieldVal: values[field],
dataId: values.id,
});
if (!res.success) {
toast.warning(res.message);
return true; // 校验失败
}
}
}
return false; // 校验通过
}
// 提交表单
const handleSubmit = async () => {
// 判断字段必填和正则
if (await fieldCheck(myFormData)) {
return
}
let url = dataId.value?'/${entityPackagePath}/${entityName?uncap_first}/edit':'/${entityPackagePath}/${entityName?uncap_first}/add';
form.value
.validate()
.then(({ valid, errors }) => {
if (valid) {
loading.value = true;
http.post(url,myFormData).then((res) => {
loading.value = false;
if (res.success) {
toast.success('保存成功');
handleSuccess()
}else{
toast.error(res?.message || '表单保存失败!')
}
})
}
})
.catch((error) => {
console.log(error, 'error')
loading.value = false;
})
}
// 标题
const get4Label = computed(() => {
return (label) => {
return label && label.length > 4 ? label.substring(0, 4) : label;
}
})
// 标题
const getFormSchema = computed(() => {
return (dictTable,dictCode,dictText) => {
return {
dictCode,
dictTable,
dictText
};
}
})
/**
* 获取日期控件的扩展类型
* @param picker
* @returns {string}
*/
const getDateExtendType = (picker: string) => {
let mapField = {
month: 'year-month',
year: 'year',
quarter: 'quarter',
week: 'week',
day: 'date',
}
return picker && mapField[picker]
? mapField[picker]
: 'date'
}
//设置pop返回值
const setFieldsValue = (data) => {
Object.assign(myFormData, {...data })
}
// onLoad 生命周期钩子
onLoad((option) => {
initForm(option)
})
</script>
<style lang="scss" scoped>
.footer {
width: 100%;
padding: 10px 20px;
padding-bottom: calc(constant(safe-area-inset-bottom) + 10px);
padding-bottom: calc(env(safe-area-inset-bottom) + 10px);
}
:deep(.wd-cell__label) {
font-size: 14px;
color: #444;
}
:deep(.wd-cell__value) {
text-align: left;
}
</style>

View File

@@ -0,0 +1,148 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '${tableVo.ftlDescription}',
navigationStyle: 'custom',
},
}
</route>
<template>
<PageLayout navTitle="${tableVo.ftlDescription}" backRouteName="index" routeMethod="pushTab">
<view class="wrap">
<z-paging
ref="paging"
:fixed="false"
v-model="dataList"
@query="queryList"
:default-page-size="15"
>
<template v-for="item in dataList" :key="item.id">
<wd-swipe-action>
<view class="list" @click="handleEdit(item)">
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
<view v-if="cIndex < 3" class="box" :style="getBoxStyle">
<view class="field ellipsis">{{ cItem.title }}</view>
<view class="value cu-text-grey">{{ item[cItem.dataIndex] }}</view>
</view>
</template>
</view>
<template #right>
<view class="action">
<view class="button" @click="handleAction('del', item)">删除</view>
</view>
</template>
</wd-swipe-action>
</template>
</z-paging>
<view class="add u-iconfont u-icon-add" @click="handleAdd"></view>
</view>
</PageLayout>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { http } from '@/utils/http'
import usePageList from '@/hooks/usePageList'
import {columns} from './${entityName}Data';
defineOptions({
name: '${entityName}List',
options: {
styleIsolation: 'shared',
}
})
//分页加载配置
let { toast, router, paging, dataList, queryList } = usePageList('/${entityPackagePath}/${entityName?uncap_first}/list');
//样式
const getBoxStyle = computed(() => {
return { width: "calc(33% - 5px)" }
})
// 其他操作
const handleAction = (val, item) => {
if (val == 'del') {
http.delete("/${entityPackagePath}/${entityName?uncap_first}/delete?id="+item.id,{id:item.id}).then((res) => {
toast.success('删除成功~')
paging.value.reload()
})
}
}
// go 新增页
const handleAdd = () => {
router.push({
name: '${entityName}Form'
})
}
//go 编辑页
const handleEdit = (record) => {
router.push({
name: '${entityName}Form',
params: {dataId: record.id},
})
}
onMounted(() => {
// 监听刷新列表事件
uni.$on('refreshList', () => {
queryList(1,10)
})
})
</script>
<style lang="scss" scoped>
.wrap {
height: 100%;
}
:deep(.wd-swipe-action) {
margin-top: 10px;
background-color: #fff;
}
.list {
padding: 10px 10px;
width: 100%;
text-align: left;
display: flex;
justify-content: space-between;
.box {
width: 33%;
.field {
margin-bottom: 10px;
line-height: 20px;
}
}
}
.action {
width: 60px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.button {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 100%;
color: #fff;
&:first-child {
background-color: #fa4350;
}
}
}
.add {
height: 70upx;
width: 70upx;
text-align: center;
line-height: 70upx;
background-color: #fff;
border-radius: 50%;
position: fixed;
bottom: 80upx;
right: 30upx;
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
color: #666;
}
</style>

View File

@@ -0,0 +1,426 @@
<#include "/common/utils.ftl">
<template>
<a-card :bordered="false">
<!-- 查询区域 -->
<div class="table-page-search-wrapper">
<a-form layout="inline" @keyup.enter.native="searchQuery">
<a-row :gutter="24">
<#assign query_field_no=0>
<#assign query_flag=false>
<#assign list_need_dict=false>
<#assign list_need_category=false>
<#assign list_need_pca=false>
<#assign list_need_switch=false>
<#assign bpm_flag=false>
<#-- 开始循环 -->
<#list columns as po>
<#if po.fieldDbName=='bpm_status'>
<#assign bpm_flag=true>
</#if>
<#if po.isQuery=='Y'>
<#assign query_flag=true>
<#if query_field_no==2>
<template v-if="toggleSearchStatus">
</#if>
<#assign query_field_dictCode="">
<#if po.dictTable?default("")?trim?length gt 1>
<#assign query_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign query_field_dictCode="${po.dictField}">
</#if>
<#if po.queryMode=='single'>
<#if query_field_no gt 1> </#if><a-col :xl="6" :lg="7" :md="8" :sm="24">
<#if query_field_no gt 1> </#if><a-form-item label="${po.filedComment}">
<#if po.classType=='sel_search'>
<#if query_field_no gt 1> </#if><j-search-select-tag placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}" dict="${po.dictTable},${po.dictText},${po.dictField}"/>
<#elseif po.classType=='sel_user'>
<#if query_field_no gt 1> </#if><j-select-user-by-dep placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}"/>
<#elseif po.classType=='switch'>
<#if query_field_no gt 1> </#if><j-switch placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}" <#if po.dictField!= 'is_open'>:options="${po.dictField}"</#if> query></j-switch>
<#elseif po.classType=='sel_depart'>
<#if query_field_no gt 1> </#if><j-select-depart placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}"/>
<#elseif po.classType=='list_multi'>
<#if query_field_no gt 1> </#if><j-multi-select-tag placeholder="请选择${po.filedComment}" dictCode="${query_field_dictCode?default("")}" v-model="queryParam.${po.fieldName}"/>
<#elseif po.classType=='cat_tree'>
<#if query_field_no gt 1> </#if><j-category-select placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}" pcode="${po.dictField?default("")}"/>
<#elseif po.classType=='date'>
<#if query_field_no gt 1> </#if><j-date placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}"></j-date>
<#elseif po.classType=='datetime'>
<#if query_field_no gt 1> </#if><j-date :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}"></j-date>
<#elseif po.classType=='pca'>
<#if query_field_no gt 1> </#if><j-area-linkage type="cascader" v-model="queryParam.${po.fieldName}" placeholder="请选择省市区"/>
<#elseif po.classType=='popup'>
<#if query_field_no gt 1> </#if><j-popup placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}" code="${po.dictTable}" org-fields="${po.dictField}" dest-fields="${po.dictText}" :field="getPopupField('${po.dictText}')" :multi="${po.extendParams.popupMulti?c}"/>
<#elseif po.classType=='list' || po.classType=='radio' || po.classType=='checkbox'>
<#-- ---------------------------下拉或是单选 判断数据字典是表字典还是普通字典------------------------------- -->
<#if po.dictTable?default("")?trim?length gt 1>
<#if query_field_no gt 1> </#if><j-dict-select-tag placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}" dictCode="${po.dictTable},${po.dictText},${po.dictField}"/>
<#elseif po.dictField?default("")?trim?length gt 1>
<#if query_field_no gt 1> </#if><j-dict-select-tag placeholder="请选择${po.filedComment}" v-model="queryParam.${po.fieldName}" dictCode="${po.dictField}"/>
<#else>
<#if query_field_no gt 1> </#if><a-input placeholder="请输入${po.filedComment}" v-model="queryParam.${po.fieldName}"></a-input>
</#if>
<#else>
<#if query_field_no gt 1> </#if><a-input placeholder="请输入${po.filedComment}" v-model="queryParam.${po.fieldName}"></a-input>
</#if>
<#if query_field_no gt 1> </#if></a-form-item>
<#if query_field_no gt 1> </#if></a-col>
<#else>
<#if query_field_no gt 1> </#if><a-col :xl="10" :lg="11" :md="12" :sm="24">
<#if query_field_no gt 1> </#if><a-form-item label="${po.filedComment}">
<#if po.classType=='date'>
<#if query_field_no gt 1> </#if><j-date placeholder="请选择开始日期" class="query-group-cust" v-model="queryParam.${po.fieldName}_begin"></j-date>
<#if query_field_no gt 1> </#if><span class="query-group-split-cust"></span>
<#if query_field_no gt 1> </#if><j-date placeholder="请选择结束日期" class="query-group-cust" v-model="queryParam.${po.fieldName}_end"></j-date>
<#elseif po.classType=='datetime'>
<#if query_field_no gt 1> </#if><j-date :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择开始时间" class="query-group-cust" v-model="queryParam.${po.fieldName}_begin"></j-date>
<#if query_field_no gt 1> </#if><span class="query-group-split-cust"></span>
<#if query_field_no gt 1> </#if><j-date :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择结束时间" class="query-group-cust" v-model="queryParam.${po.fieldName}_end"></j-date>
<#else>
<#if query_field_no gt 1> </#if><a-input placeholder="请输入最小值" class="query-group-cust" v-model="queryParam.${po.fieldName}_begin"></a-input>
<#if query_field_no gt 1> </#if><span class="query-group-split-cust"></span>
<#if query_field_no gt 1> </#if><a-input placeholder="请输入最大值" class="query-group-cust" v-model="queryParam.${po.fieldName}_end"></a-input>
</#if>
<#if query_field_no gt 1> </#if></a-form-item>
<#if query_field_no gt 1> </#if></a-col>
</#if>
<#assign query_field_no=query_field_no+1>
</#if>
<#if !list_need_dict && po.fieldShowType!='popup' && po.dictField?default("")?trim?length gt 1>
<#assign list_need_dict=true>
</#if>
<#if po.classType=='cat_tree' && po.dictText?default("")?trim?length == 0>
<#assign list_need_category=true>
</#if>
<#if po.classType=='pca'>
<#assign list_need_pca=true>
</#if>
<#if po.classType=='switch'>
<#assign list_need_switch=true>
</#if>
</#list>
<#-- 结束循环 -->
<#t>
<#if query_field_no gt 2>
</template>
</#if>
<#if query_flag>
<a-col :xl="6" :lg="7" :md="8" :sm="24">
<span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
<a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
<a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
<a @click="handleToggleSearch" style="margin-left: 8px">
{{ toggleSearchStatus ? '收起' : '展开' }}
<a-icon :type="toggleSearchStatus ? 'up' : 'down'"/>
</a>
</span>
</a-col>
</#if>
</a-row>
</a-form>
</div>
<!-- 查询区域-END -->
<!-- 操作按钮区域 -->
<div class="table-operator">
<a-button @click="handleAdd" type="primary" icon="plus">新增</a-button>
<a-button type="primary" icon="download" @click="handleExportXls('${tableVo.ftlDescription}')">导出</a-button>
<a-upload name="file" :showUploadList="false" :multiple="false" :headers="tokenHeader" :action="importExcelUrl" @change="handleImportExcel">
<a-button type="primary" icon="import">导入</a-button>
</a-upload>
<!-- 高级查询区域 -->
<j-super-query :fieldList="superFieldList" ref="superQueryModal" @handleSuperQuery="handleSuperQuery"></j-super-query>
<a-dropdown v-if="selectedRowKeys.length > 0">
<a-menu slot="overlay">
<a-menu-item key="1" @click="batchDel"><a-icon type="delete"/>删除</a-menu-item>
</a-menu>
<a-button style="margin-left: 8px"> 批量操作 <a-icon type="down" /></a-button>
</a-dropdown>
</div>
<!-- table区域-begin -->
<div>
<div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">
<i class="anticon anticon-info-circle ant-alert-icon"></i> 已选择 <a style="font-weight: 600">{{ selectedRowKeys.length }}</a>项
<a style="margin-left: 24px" @click="onClearSelected">清空</a>
</div>
<a-table
ref="table"
size="middle"
<#if tableVo.extendParams.scroll=='1'>
:scroll="{x:true}"
</#if>
bordered
rowKey="id"
:columns="columns"
:dataSource="dataSource"
:pagination="ipagination"
:loading="loading"
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
class="j-table-force-nowrap"
@change="handleTableChange">
<template slot="htmlSlot" slot-scope="text">
<div v-html="text"></div>
</template>
<template slot="imgSlot" slot-scope="text,record">
<span v-if="!text" style="font-size: 12px;font-style: italic;">无图片</span>
<img v-else :src="getImgView(text)" :preview="record.id" height="25px" alt="" style="max-width:80px;font-size: 12px;font-style: italic;"/>
</template>
<#if list_need_pca>
<template slot="pcaSlot" slot-scope="text">
<div>{{ getPcaText(text) }}</div>
</template>
</#if>
<template slot="fileSlot" slot-scope="text">
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
<a-button
v-else
:ghost="true"
type="primary"
icon="download"
size="small"
@click="downloadFile(text)">
下载
</a-button>
</template>
<span slot="action" slot-scope="text, record">
<a @click="handleEdit(record)">编辑</a>
<a-divider type="vertical" />
<a-dropdown>
<a class="ant-dropdown-link">更多 <a-icon type="down" /></a>
<a-menu slot="overlay">
<a-menu-item>
<a @click="handleDetail(record)">详情</a>
</a-menu-item>
<#if bpm_flag>
<a-menu-item v-if="record.bpmStatus === '1'">
<a @click="startProcess(record)">发起流程</a>
</a-menu-item>
</#if>
<a-menu-item>
<a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
<a>删除</a>
</a-popconfirm>
</a-menu-item>
</a-menu>
</a-dropdown>
</span>
</a-table>
</div>
<${Format.humpToShortbar(entityName)}-modal ref="modalForm" @ok="modalFormOk"></${Format.humpToShortbar(entityName)}-modal>
</a-card>
</template>
<script>
import '@/assets/less/TableExpand.less'
import { mixinDevice } from '@/utils/mixin'
import { JeecgListMixin } from '@/mixins/JeecgListMixin'
import ${entityName}Modal from './modules/${entityName}Modal'
<#if bpm_flag>
import { postAction } from '@/api/manage'
</#if>
<#if list_need_category>
import { loadCategoryData } from '@/api/api'
</#if>
<#if list_need_dict>
import {filterMultiDictText} from '@/components/dict/JDictSelectUtil'
</#if>
<#if list_need_pca>
import Area from '@/components/_util/Area'
</#if>
export default {
name: '${entityName}List',
mixins:[JeecgListMixin, mixinDevice],
components: {
${entityName}Modal
},
data () {
return {
description: '${tableVo.ftlDescription}管理页面',
// 表头
columns: [
{
title: '#',
dataIndex: '',
key:'rowIndex',
width:60,
align:"center",
customRender:function (t,r,index) {
return parseInt(index)+1;
}
},
<#assign showColNum=0>
<#list columns as po>
<#if po.isShowList =='Y' && po.fieldName !='id'>
<#assign showColNum=showColNum+1>
{
title:'${po.filedComment}',
align:"center",
<#if po.sort=='Y'>
sorter: true,
</#if>
<#if po.classType=='date'>
dataIndex: '${po.fieldName}',
customRender:function (text) {
return !text?"":(text.length>10?text.substr(0,10):text)
}
<#elseif po.fieldDbType=='Blob'>
dataIndex: '${po.fieldName}String'
<#elseif po.classType=='umeditor'>
dataIndex: '${po.fieldName}',
scopedSlots: {customRender: 'htmlSlot'}
<#elseif po.classType=='pca'>
dataIndex: '${po.fieldName}',
scopedSlots: {customRender: 'pcaSlot'}
<#elseif po.classType=='file'>
dataIndex: '${po.fieldName}',
scopedSlots: {customRender: 'fileSlot'}
<#elseif po.classType=='image'>
dataIndex: '${po.fieldName}',
scopedSlots: {customRender: 'imgSlot'}
<#elseif po.classType=='switch'>
dataIndex: '${po.fieldName}',
customRender: (text) => (text ? filterMultiDictText(this.dictOptions['${po.fieldName}'], text) : ''),
<#elseif po.classType == 'sel_tree' || po.classType=='list' || po.classType=='list_multi' || po.classType=='sel_search' || po.classType=='radio' || po.classType=='checkbox' || po.classType=='sel_depart' || po.classType=='sel_user'>
dataIndex: '${po.fieldName}_dictText'
<#elseif po.classType=='cat_tree'>
<#if list_need_category>
dataIndex: '${po.fieldName}',
customRender: (text) => (text ? filterMultiDictText(this.dictOptions['${po.fieldName}'], text) : '')
<#else>
dataIndex: '${po.fieldName}',
customRender: (text, record) => (text ? record['${dashedToCamel(po.dictText)}'] : '')
</#if>
<#else>
dataIndex: '${po.fieldName}'
</#if>
},
</#if>
</#list>
{
title: '操作',
dataIndex: 'action',
align:"center",
<#if tableVo.extendParams.scroll=='1'>
fixed:"right",
width:147,
</#if>
scopedSlots: { customRender: 'action' }
}
],
url: {
list: "/${entityPackagePath}/${entityName?uncap_first}/list",
delete: "/${entityPackagePath}/${entityName?uncap_first}/delete",
deleteBatch: "/${entityPackagePath}/${entityName?uncap_first}/deleteBatch",
exportXlsUrl: "/${entityPackagePath}/${entityName?uncap_first}/exportXls",
importExcelUrl: "${entityPackagePath}/${entityName?uncap_first}/importExcel",
<#if bpm_flag>startProcess: '/act/process/extActProcess/startMutilProcess'</#if>
},
<#if bpm_flag>
//代码生成后需手动设置流程编码
flowCode: 'dev_${tableName}_001',
</#if>
dictOptions:{},
<#if list_need_pca>
pcaData:'',
</#if>
superFieldList:[],
}
},
created() {
<#if list_need_pca>
this.pcaData = new Area()
</#if>
<#if list_need_switch>
<#list columns as po>
<#if po.classType=='switch'>
<#assign switch_extend_arr=['Y','N']>
<#if po.dictField?default("")?contains("[")>
<#assign switch_extend_arr=po.dictField?eval>
</#if>
<#list switch_extend_arr as a>
<#if a_index == 0>
<#assign switch_extend_arr1=a>
<#else>
<#assign switch_extend_arr2=a>
</#if>
</#list>
this.$set(this.dictOptions, '${po.fieldName}', [{text:'是',value:'${switch_extend_arr1}'},{text:'否',value:'${switch_extend_arr2}'}])
</#if>
</#list>
</#if>
this.getSuperFieldList();
},
computed: {
importExcelUrl: function(){
<#noparse>return `${window._CONFIG['domianURL']}/${this.url.importExcelUrl}`;</#noparse>
},
},
methods: {
<#if list_need_pca>
getPcaText(code){
return this.pcaData.getText(code);
},
</#if>
<#if bpm_flag>
startProcess(record){
this.$confirm({
title:'提示',
content:'确认提交流程吗?',
onOk:()=>{
let params = {
flowCode: this.flowCode,
id: record.id,
formUrl: '${entityPackagePath}/modules/${entityName}Form',
formUrlMobile: ''
}
postAction(this.url.startProcess, params).then(res=>{
if(res.success){
this.$message.success(res.message);
this.loadData();
this.onClearSelected();
}else{
this.$message.warning(res.message);
}
}).catch((e)=>{
this.$message.warning('不识别的请求!');
})
}
})
},
</#if>
initDictConfig(){
<#list columns as po>
<#if (po.isQuery=='Y' || po.isShowList=='Y') && po.classType!='popup'>
<#if po.classType=='cat_tree' && list_need_category==true>
loadCategoryData({code:'${po.dictField?default("")}'}).then((res) => {
if (res.success) {
this.$set(this.dictOptions, '${po.fieldName}', res.result)
}
})
</#if>
</#if>
</#list>
},
getSuperFieldList(){
let fieldList=[];
<#list columns as po>
fieldList.push(${superQueryFieldList(po)})
</#list>
this.superFieldList = fieldList
}
}
}
</script>
<style scoped>
@import '~@assets/less/common.less';
</style>

View File

@@ -0,0 +1,266 @@
<#include "/common/utils.ftl">
<template>
<a-spin :spinning="confirmLoading">
<j-form-container :disabled="formDisabled">
<a-form-model ref="form" :model="model" :rules="validatorRules" slot="detail">
<a-row>
<#assign form_popup = false>
<#assign form_cat_tree = false>
<#assign form_cat_back = "">
<#assign bpm_flag=false>
<#assign form_span = 24>
<#if tableVo.fieldRowNum==2>
<#assign form_span = 12>
<#elseif tableVo.fieldRowNum==3>
<#assign form_span = 8>
<#elseif tableVo.fieldRowNum==4>
<#assign form_span = 6>
</#if>
<#list columns as po>
<#if po.fieldDbName=='bpm_status'>
<#assign bpm_flag=true>
</#if>
<#if po.isShow =='Y' && po.fieldName != 'id'>
<#assign form_field_dictCode="">
<#if po.dictTable?default("")?trim?length gt 1 && po.dictText?default("")?trim?length gt 1 && po.dictField?default("")?trim?length gt 1>
<#assign form_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign form_field_dictCode="${po.dictField}">
</#if>
<a-col :span="${form_span}">
<a-form-model-item label="${po.filedComment}" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="${autoStringSuffixForModel(po)}">
<#if po.classType =='date'>
<j-date placeholder="请选择${po.filedComment}" v-model="model.${po.fieldName}" style="width: 100%" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType =='datetime'>
<j-date placeholder="请选择${po.filedComment}" v-model="model.${po.fieldName}" :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType =='time'>
<j-time placeholder="请选择${po.filedComment}" v-model="model.${po.fieldName}" style="width: 100%" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType =='popup'>
<#assign form_popup=true>
<j-popup
v-model="model.${po.fieldName}"
field="${po.fieldName}"
org-fields="${po.dictField}"
dest-fields="${Format.underlineToHump(po.dictText)}"
code="${po.dictTable}"
:multi="${po.extendParams.popupMulti?c}"
@input="popupCallback"
<#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType =='sel_depart'>
<j-select-depart v-model="model.${po.fieldName}" :multi="${po.extendParams.multi?default('true')}"<#if po.extendParams.store?default("")?trim?length gt 0> store="${po.extendParams.store}"</#if><#if po.extendParams.text?default("")?trim?length gt 0> text="${po.extendParams.text}"</#if> <#if po.readonly=='Y'>disabled</#if> />
<#elseif po.classType =='switch'>
<j-switch v-model="model.${po.fieldName}" <#if po.dictField != 'is_open'>:options="${po.dictField}"</#if> <#if po.readonly=='Y'>disabled</#if>></j-switch>
<#elseif po.classType =='pca'>
<j-area-linkage type="cascader" v-model="model.${po.fieldName}" placeholder="请输入省市区" <#if po.readonly=='Y'>disabled</#if> />
<#elseif po.classType =='markdown'>
<j-markdown-editor v-model="model.${autoStringSuffixForModel(po)}" id="${po.fieldName}"></j-markdown-editor>
<#elseif po.classType =='password'>
<a-input-password v-model="model.${po.fieldName}" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType =='sel_user'>
<j-select-user-by-dep v-model="model.${po.fieldName}" :multi="${po.extendParams.multi?default('true')}"<#if po.extendParams.store?default("")?trim?length gt 0> store="${po.extendParams.store}"</#if><#if po.extendParams.text?default("")?trim?length gt 0> text="${po.extendParams.text}"</#if> <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType =='textarea'>
<a-textarea v-model="model.${autoStringSuffixForModel(po)}" rows="4" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType=='list' || po.classType=='radio'>
<j-dict-select-tag type="${po.classType}" v-model="model.${po.fieldName}" dictCode="${form_field_dictCode}" placeholder="请选择${po.filedComment}" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType=='list_multi' || po.classType=='checkbox'>
<j-multi-select-tag type="${po.classType}" v-model="model.${po.fieldName}" dictCode="${form_field_dictCode}" placeholder="请选择${po.filedComment}" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType=='sel_search'>
<j-search-select-tag v-model="model.${po.fieldName}" dict="${form_field_dictCode}" <#if po.readonly=='Y'>disabled</#if> />
<#elseif po.classType=='cat_tree'>
<#assign form_cat_tree = true>
<j-category-select v-model="model.${po.fieldName}" pcode="${po.dictField?default("")}" placeholder="请选择${po.filedComment}" <#if po.dictText?default("")?trim?length gt 1>back="${dashedToCamel(po.dictText)}" @change="handleCategoryChange"</#if> <#if po.readonly=='Y'>disabled</#if>/>
<#if po.dictText?default("")?trim?length gt 1>
<#assign form_cat_back = "${po.dictText}">
</#if>
<#elseif po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
<a-input-number v-model="model.${po.fieldName}" placeholder="请输入${po.filedComment}" style="width: 100%" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.classType=='file'>
<j-upload v-model="model.${po.fieldName}" <#if po.readonly=='Y'>disabled</#if> <#if po.uploadnum??>:number=${po.uploadnum}</#if>></j-upload>
<#elseif po.classType=='image'>
<j-image-upload isMultiple <#if po.uploadnum??>:number=${po.uploadnum}</#if> v-model="model.${po.fieldName}" <#if po.readonly=='Y'>disabled</#if>></j-image-upload>
<#elseif po.classType=='umeditor'>
<j-editor v-model="model.${autoStringSuffixForModel(po)}" <#if po.readonly=='Y'>disabled</#if>/>
<#elseif po.fieldDbType=='Blob'>
<a-input v-model="model.${autoStringSuffixForModel(po)}" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if>></a-input>
<#elseif po.classType == 'sel_tree'>
<j-tree-select
ref="treeSelect"
placeholder="请选择${po.filedComment}"
v-model="model.${po.fieldName}"
<#if po.dictText??>
<#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>
dict="${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}"
<#elseif po.dictText?split(',')[1]??>
pidField="${po.dictText?split(',')[1]}"
<#elseif po.dictText?split(',')[3]??>
hasChildField="${po.dictText?split(',')[3]}"
</#if>
</#if>
pidValue="${po.dictField}"
<#if po.readonly=='Y'>disabled</#if>>
</j-tree-select>
<#else>
<a-input v-model="model.${po.fieldName}" placeholder="请输入${po.filedComment}" <#if po.readonly=='Y'>disabled</#if> ></a-input>
</#if>
</a-form-model-item>
</a-col>
</#if>
</#list>
<#if bpm_flag>
<a-col v-if="showFlowSubmitButton" :span="24" style="width: 100%;text-align: center;">
<a-button icon="check" style="width: 126px" type="primary" @click="submitForm">提 交</a-button>
</a-col>
</#if>
</a-row>
</a-form-model>
</j-form-container>
</a-spin>
</template>
<script>
import { httpAction, getAction } from '@/api/manage'
import { validateDuplicateValue } from '@/utils/util'
export default {
name: '${entityName}Form',
components: {
},
props: {
<#if bpm_flag>
//流程表单data
formData: {
type: Object,
default: () => ({}),
required: false
},
//表单模式true流程表单 false普通表单
formBpm: {
type: Boolean,
default: false,
required: false
},
</#if>
//表单禁用
disabled: {
type: Boolean,
default: false,
required: false
}
},
data () {
return {
model:{
<#include "/common/init/initValue.ftl">
},
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
confirmLoading: false,
<#include "/common/validatorRulesTemplate/main.ftl">
url: {
add: "/${entityPackagePath}/${entityName?uncap_first}/add",
edit: "/${entityPackagePath}/${entityName?uncap_first}/edit",
queryById: "/${entityPackagePath}/${entityName?uncap_first}/queryById"
}
}
},
computed: {
formDisabled(){
<#if bpm_flag>
if(this.formBpm===true){
if(this.formData.disabled===false){
return false
}
return true
}
</#if>
return this.disabled
},
<#if bpm_flag>
showFlowSubmitButton(){
if(this.formBpm===true){
if(this.formData.disabled===false){
return true
}
}
return false
}
</#if>
},
created () {
//备份model原始值
this.modelDefault = JSON.parse(JSON.stringify(this.model));
<#if bpm_flag>
//如果是流程中表单则需要加载流程表单data
this.showFlowData();
</#if>
},
methods: {
add () {
this.edit(this.modelDefault);
},
edit (record) {
this.model = Object.assign({}, record);
this.visible = true;
},
<#if bpm_flag>
//渲染流程表单数据
showFlowData(){
if(this.formBpm === true){
let params = {id:this.formData.dataId};
getAction(this.url.queryById,params).then((res)=>{
if(res.success){
this.edit (res.result);
}
});
}
},
</#if>
submitForm () {
const that = this;
// 触发表单验证
this.$refs.form.validate(valid => {
if (valid) {
that.confirmLoading = true;
let httpurl = '';
let method = '';
if(!this.model.id){
httpurl+=this.url.add;
method = 'post';
}else{
httpurl+=this.url.edit;
method = 'put';
}
httpAction(httpurl,this.model,method).then((res)=>{
if(res.success){
that.$message.success(res.message);
that.$emit('ok');
}else{
that.$message.warning(res.message);
}
}).finally(() => {
that.confirmLoading = false;
})
}
})
},
<#if form_popup>
popupCallback(value,row){
this.model = Object.assign(this.model, row);
},
</#if>
<#if form_cat_tree>
handleCategoryChange(value,backObj){
this.model = Object.assign(this.model, backObj);
}
</#if>
}
}
</script>

View File

@@ -0,0 +1,69 @@
<#include "/common/utils.ftl">
<#assign modal_width = 800>
<#if tableVo.fieldRowNum==2>
<#assign modal_width = 896>
<#elseif tableVo.fieldRowNum==3>
<#assign modal_width = 1024>
<#elseif tableVo.fieldRowNum==4>
<#assign modal_width = 1280>
</#if>
<template>
<j-modal
:title="title"
:width="width"
:visible="visible"
switchFullscreen
@ok="handleOk"
:okButtonProps="{ class:{'jee-hidden': disableSubmit} }"
@cancel="handleCancel"
cancelText="关闭">
<${Format.humpToShortbar(entityName)}-form ref="realForm" @ok="submitCallback" :disabled="disableSubmit"></${Format.humpToShortbar(entityName)}-form>
</j-modal>
</template>
<script>
import ${entityName}Form from './${entityName}Form'
export default {
name: '${entityName}Modal',
components: {
${entityName}Form
},
data () {
return {
title:'',
width:${modal_width},
visible: false,
disableSubmit: false
}
},
methods: {
add () {
this.visible=true
this.$nextTick(()=>{
this.$refs.realForm.add();
})
},
edit (record) {
this.visible=true
this.$nextTick(()=>{
this.$refs.realForm.edit(record);
})
},
close () {
this.$emit('close');
this.visible = false;
},
handleOk () {
this.$refs.realForm.submitForm();
},
submitCallback(){
this.$emit('ok');
this.visible = false;
},
handleCancel () {
this.close()
}
}
}
</script>

View File

@@ -0,0 +1,93 @@
<#include "/common/utils.ftl">
<#assign modal_width = 800>
<#if tableVo.fieldRowNum==2>
<#assign modal_width = 896>
<#elseif tableVo.fieldRowNum==3>
<#assign modal_width = 1024>
<#elseif tableVo.fieldRowNum==4>
<#assign modal_width = 1280>
</#if>
<template>
<a-drawer
:title="title"
:width="width"
placement="right"
:closable="false"
@close="close"
destroyOnClose
:visible="visible">
<${Format.humpToShortbar(entityName)}-form ref="realForm" @ok="submitCallback" :disabled="disableSubmit" normal></${Format.humpToShortbar(entityName)}-form>
<div class="drawer-footer">
<a-button @click="handleCancel" style="margin-bottom: 0;">关闭</a-button>
<a-button v-if="!disableSubmit" @click="handleOk" type="primary" style="margin-bottom: 0;">提交</a-button>
</div>
</a-drawer>
</template>
<script>
import ${entityName}Form from './${entityName}Form'
export default {
name: '${entityName}Modal',
components: {
${entityName}Form
},
data () {
return {
title:"操作",
width:${modal_width},
visible: false,
disableSubmit: false
}
},
methods: {
add () {
this.visible=true
this.$nextTick(()=>{
this.$refs.realForm.add();
})
},
edit (record) {
this.visible=true
this.$nextTick(()=>{
this.$refs.realForm.edit(record);
});
},
close () {
this.$emit('close');
this.visible = false;
},
submitCallback(){
this.$emit('ok');
this.visible = false;
},
handleOk () {
this.$refs.realForm.submitForm();
},
handleCancel () {
this.close()
}
}
}
</script>
<style lang="less" scoped>
/** Button按钮间距 */
.ant-btn {
margin-left: 30px;
margin-bottom: 30px;
float: right;
}
.drawer-footer{
position: absolute;
bottom: -8px;
width: 100%;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
left: 0;
background: #fff;
border-radius: 0 0 2px 2px;
}
</style>

View File

@@ -0,0 +1,443 @@
<template>
<div>
<#assign list_need_category=false>
<#assign list_need_pca=false>
<#assign list_has_popup_dict=false>
<#assign bpm_flag=false>
<#-- 开始循环 -->
<#list columns as po>
<#if po.fieldDbName=='bpm_status'>
<#assign bpm_flag=true>
</#if>
<#if po.classType=='cat_tree' && po.dictText?default("")?trim?length == 0>
<#assign list_need_category=true>
</#if>
<#if po.classType=='popup_dict'>
<#assign list_has_popup_dict=true>
</#if>
<#if po.classType=='pca'>
<#assign list_need_pca=true>
</#if>
</#list>
<#assign buttonList=[]>
<#if tableVo.extendParams?? && tableVo.extendParams.cgButtonList??>
<#assign buttonList = tableVo.extendParams.cgButtonList?filter(btn -> btn??)>
</#if>
<#-- 结束循环 -->
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" v-auth="'${entityPackage}:${tableName}:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
<a-button type="primary" v-auth="'${entityPackage}:${tableName}:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
<j-upload-button type="primary" v-auth="'${entityPackage}:${tableName}:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle == 'button'>
<a-button type="primary" @click="handle${btn.buttonCode?cap_first}" <#if btn.buttonIcon??> preIcon="ant-design:${btn.buttonIcon}" </#if>>${btn.buttonName}</a-button>
</#if>
</#list>
</#if>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined"></Icon>
删除
</a-menu-item>
</a-menu>
</template>
<a-button v-auth="'${entityPackage}:${tableName}:deleteBatch'">批量操作
<Icon icon="mdi:chevron-down"></Icon>
</a-button>
</a-dropdown>
<#-- update-begin---author:chenrui ---date:20231228 for[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
<!-- 高级查询 -->
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
<#-- update-end---author:chenrui ---date:20231228 for[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"/>
</template>
<!--字段回显插槽-->
<template v-slot:bodyCell="{ column, record, index, text }">
<#list columns as po>
<#if po.classType=='umeditor' || po.classType=='pca' || po.classType=='file'>
<template v-if="column.dataIndex==='${po.fieldName}'">
<#if po.classType=='umeditor'>
<!--富文本件字段回显插槽-->
<div v-html="text"></div>
</#if>
<#if po.classType=='pca'>
<!--省市区字段回显插槽-->
{{ getAreaTextByCode(text) }}
</#if>
<#if po.classType=='file'>
<!--文件字段回显插槽-->
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download-outlined" size="small" @click="downloadFile(text)">下载</a-button>
</#if>
</template>
</#if>
</#list>
</template>
</BasicTable>
<!-- 表单区域 -->
<${entityName}Modal @register="registerModal" @success="handleSuccess"></${entityName}Modal>
<#if bpm_flag==true>
<!-- 审批记录 -->
<BpmPictureModal @register="registerBpmModal" />
</#if>
</div>
</template>
<script lang="ts" name="${entityPackage}-${entityName?uncap_first}" setup>
import {ref, reactive, computed, unref} from 'vue';
import {BasicTable, useTable, TableAction} from '/@/components/Table';
import {useModal} from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage'
import ${entityName}Modal from './components/${entityName}Modal.vue'
import {columns, searchFormSchema, superQuerySchema} from './${entityName}.data';
import {list, deleteOne, batchDelete, getImportUrl,getExportUrl} from './${entityName}.api';
import { downloadFile } from '/@/utils/common/renderUtils';
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
<#if list_need_pca>
import { getAreaTextByCode } from '/@/components/Form/src/utils/Area';
</#if>
<#if list_need_category>
import { loadCategoryData } from '/@/api/common/api'
import { getAuthCache, setAuthCache } from '/@/utils/auth';
import { DB_DICT_DATA_KEY } from '/@/enums/cacheEnum';
</#if>
<#if list_has_popup_dict>
import {getPopDictByCode} from "@/utils/dict";
import {filterMultiDictText} from "@/utils/dict/JDictSelectUtil";
</#if>
import { getDateByPicker } from '/@/utils';
//日期个性化选择
const fieldPickers = reactive({
<#list columns as po>
<#if po.extendParams?exists && po.extendParams.picker?exists>
${po.fieldName}: '${po.extendParams.picker}',
</#if>
</#list>
});
<#if bpm_flag==true>
import { startProcess } from '/@/api/common/api';
const [registerBpmModal, { openModal: bpmPicModal }] = useModal();
</#if>
const queryParam = reactive<any>({});
const checkedKeys = ref<Array<string | number>>([]);
const userStore = useUserStore();
const { createMessage } = useMessage();
//注册model
const [registerModal, {openModal}] = useModal();
//注册table数据
const { prefixCls,tableContext,onExportXls,onImportXls } = useListPage({
tableProps:{
title: '${tableVo.ftlDescription}',
api: list,
columns,
canResize:true,
formConfig: {
//labelWidth: 120,
schemas: searchFormSchema,
autoSubmitOnEnter:true,
showAdvancedButton:true,
fieldMapToNumber: [
<#list columns as po>
<#if po.isQuery=='Y'>
<#if po.queryMode=='group'>
<#-- update-begin---author:chenrui ---date:20240527 for[TV360X-388]时间范围查询控件---------- -->
<#if po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal' || po.classType=='time'>
<#-- update-end---author:chenrui ---date:20240527 for[TV360X-388]时间范围查询控件---------- -->
['${po.fieldName}', ['${po.fieldName}_begin', '${po.fieldName}_end']],
</#if>
</#if>
</#if>
</#list>
],
fieldMapToTime: [
<#list columns as po>
<#if po.isQuery=='Y'>
<#if po.queryMode=='group'>
<#if po.classType=='date'>
['${po.fieldName}', ['${po.fieldName}_begin', '${po.fieldName}_end'], 'YYYY-MM-DD'],
<#elseif po.classType=='datetime'>
['${po.fieldName}', ['${po.fieldName}_begin', '${po.fieldName}_end'], 'YYYY-MM-DD HH:mm:ss'],
</#if>
</#if>
</#if>
</#list>
],
},
actionColumn: {
width: 120,
fixed:'right'
},
beforeFetch: (params) => {
if (params && fieldPickers) {
for (let key in fieldPickers) {
if (params[key]) {
params[key] = getDateByPicker(params[key], fieldPickers[key]);
}
}
}
return Object.assign(params, queryParam);
},
<#if list_has_popup_dict>
afterFetch: afterFetch
</#if>
},
exportConfig: {
name:"${tableVo.ftlDescription}",
url: getExportUrl,
params: queryParam,
},
importConfig: {
url: getImportUrl,
success: handleSuccess
},
})
const [registerTable, {reload},{ rowSelection, selectedRowKeys }] = tableContext
<#-- update-begin---author:chenrui ---date:20231228 for[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
// 高级查询配置
const superQueryConfig = reactive(superQuerySchema);
/**
* 高级查询事件
*/
function handleSuperQuery(params) {
Object.keys(params).map((k) => {
queryParam[k] = params[k];
});
reload();
}
<#-- update-end---author:chenrui ---date:20231228 for[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
/**
* 新增事件
*/
function handleAdd() {
openModal(true, {
isUpdate: false,
showFooter: true,
});
}
/**
* 编辑事件
*/
function handleEdit(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: true,
});
}
/**
* 详情
*/
function handleDetail(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: false,
});
}
/**
* 删除事件
*/
async function handleDelete(record) {
await deleteOne({id: record.id}, handleSuccess);
}
/**
* 批量删除事件
*/
async function batchHandleDelete() {
await batchDelete({ids: selectedRowKeys.value}, handleSuccess);
}
/**
* 成功回调
*/
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
/**
* 操作栏
*/
function getTableAction(record){
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: '${entityPackage}:${tableName}:edit'
}
]
}
/**
* 下拉操作栏
*/
function getDropDownAction(record){
<#if bpm_flag==true>
let dropDownAction = [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
auth: '${entityPackage}:${tableName}:delete'
},
{
label: '审批进度',
onClick: handlePreviewPic.bind(null, record),
ifShow: !!record.bpmStatus && record.bpmStatus !== '1',
}
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle == 'link'>
,{
label: '${btn.buttonName}',
onClick: handle${btn.buttonCode?cap_first}.bind(null, record),
}
</#if>
</#list>
</#if>
];
if(record.bpmStatus == '1'){
dropDownAction.push({
label: '发起流程',
popConfirm: {
title: '确认提交流程吗?',
confirm: handleProcess.bind(null, record),
placement: 'topLeft',
}
})
}
return dropDownAction;
<#else>
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
auth: '${entityPackage}:${tableName}:delete'
}
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle == 'link'>
,{
label: '${btn.buttonName}',
onClick: handle${btn.buttonCode?cap_first}.bind(null, record),
}
</#if>
</#list>
</#if>
]
</#if>
}
<#if bpm_flag==true>
/**
* 提交流程
*/
async function handleProcess(record) {
let params = {
flowCode: 'dev_${tableName}_001',
id: record.id,
formUrl: '${entityPackagePath}/components/${entityName}Form',
formUrlMobile: ''
}
await startProcess(params);
handleSuccess();
}
/**
* 审批进度
*/
async function handlePreviewPic(record) {
bpmPicModal(true, {
flowCode: 'dev_${tableName}_001',
dataId: record.id,
});
}
</#if>
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle=='button'>
function handle${btn.buttonCode?cap_first}(){
createMessage.info('点击了${btn.buttonName}按钮,对应的业务逻辑需自行实现!');
}
</#if>
<#if btn.buttonStyle=='link'>
function handle${btn.buttonCode?cap_first}(record){
createMessage.info('点击了${btn.buttonName}按钮,对应的业务逻辑需自行实现!');
}
</#if>
</#list>
</#if>
<#if list_need_category>
/**
* 初始化字典配置
*/
function initDictConfig(){
<#list columns as po>
<#if (po.isQuery=='Y' || po.isShowList=='Y') && po.classType!='popup'>
<#if po.classType=='cat_tree' && list_need_category==true>
loadCategoryData({code:'${po.dictField?default("")}'}).then((res) => {
if (res) {
const allDictDate = userStore.getAllDictItems;
if(!allDictDate['${po.dictField?default("")}']){
userStore.setAllDictItems({...allDictDate,'${po.dictField?default("")}':res});
}
}
})
</#if>
</#if>
</#list>
}
initDictConfig();
</#if>
<#if list_has_popup_dict>
/**
* 翻译Popup字典配置
*/
async function afterFetch(records){
<#list columns as po>
<#if po.isQuery=='Y' || po.isShowList=='Y'>
<#if po.classType=='popup_dict' && list_has_popup_dict==true>
const ${po.fieldName}Keys = [...new Set(records.map((item) => item['${po.fieldName}']).flatMap((item) => item && item.split(',')))];
if(${po.fieldName}Keys && ${po.fieldName}Keys.length){
const dictOptions = await getPopDictByCode(${po.fieldName}Keys.join(','), '${po.dictTable},${po.dictField},${po.dictText}');
records.forEach((item) => {
item['${po.fieldName}_dictText'] = filterMultiDictText(dictOptions, item['${po.fieldName}']);
});
}
</#if>
</#if>
</#list>
return records;
}
</#if>
</script>
<style lang="less" scoped>
<#include "/common/form/vue3SearchStyle.ftl">
</style>

View File

@@ -0,0 +1,64 @@
import {defHttp} from '/@/utils/http/axios';
import { useMessage } from "/@/hooks/web/useMessage";
const { createConfirm } = useMessage();
enum Api {
list = '/${entityPackagePath}/${entityName?uncap_first}/list',
save='/${entityPackagePath}/${entityName?uncap_first}/add',
edit='/${entityPackagePath}/${entityName?uncap_first}/edit',
deleteOne = '/${entityPackagePath}/${entityName?uncap_first}/delete',
deleteBatch = '/${entityPackagePath}/${entityName?uncap_first}/deleteBatch',
importExcel = '/${entityPackagePath}/${entityName?uncap_first}/importExcel',
exportXls = '/${entityPackagePath}/${entityName?uncap_first}/exportXls',
}
/**
* 导出api
* @param params
*/
export const getExportUrl = Api.exportXls;
/**
* 导入api
*/
export const getImportUrl = Api.importExcel;
/**
* 列表接口
* @param params
*/
export const list = (params) =>
defHttp.get({url: Api.list, params});
/**
* 删除单个
*/
export const deleteOne = (params,handleSuccess) => {
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
handleSuccess();
});
}
/**
* 批量删除
* @param params
*/
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
handleSuccess();
});
}
});
}
/**
* 保存或者更新
* @param params
*/
export const saveOrUpdate = (params, isUpdate,showTip = true) => {
let url = isUpdate ? Api.edit : Api.save;
return defHttp.post({url: url, params},{successMessageMode:showTip?'success':'none'});
}

View File

@@ -0,0 +1,538 @@
<#include "/common/utils.ftl">
import {BasicColumn} from '/@/components/Table';
import {FormSchema} from '/@/components/Table';
import { rules} from '/@/utils/helper/validator';
import { render } from '/@/utils/common/renderUtils';
import { getWeekMonthQuarterYear } from '/@/utils';
//列表数据
export const columns: BasicColumn[] = [
<#list columns as po>
<#-- update-begin---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#if po.isShowList =='Y' && po.fieldName !='id' && po.fieldName !='delFlag'>
<#-- update-end---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
{
title: '${po.filedComment}',
align:"center",
<#if po.sort=='Y'>
sorter: true,
</#if>
<#if po.classType=='date'>
dataIndex: '${po.fieldName}',
customRender:({text}) =>{
text = !text ? "" : (text.length > 10 ? text.substr(0,10) : text);
<#if po.extendParams?exists && po.extendParams.picker?exists>
if(text) {
return getWeekMonthQuarterYear(text)['${po.extendParams.picker}'];
} else {
return text;
}
<#else>
return text;
</#if>
},
<#elseif po.fieldDbType=='Blob'>
dataIndex: '${po.fieldName}String'
<#elseif po.classType=='umeditor'>
dataIndex: '${po.fieldName}',
<#elseif po.classType=='pca'>
dataIndex: '${po.fieldName}',
<#elseif po.classType=='file'>
dataIndex: '${po.fieldName}',
<#elseif po.classType=='image'>
dataIndex: '${po.fieldName}',
customRender:render.renderImage,
<#elseif po.classType=='switch'>
dataIndex: '${po.fieldName}',
<#assign switch_extend_arr=['Y','N']>
<#if po.dictField?default("")?contains("[")>
<#assign switch_extend_arr=po.dictField?eval>
</#if>
<#list switch_extend_arr as a>
<#if a_index == 0>
<#assign switch_extend_arr1=a>
<#else>
<#assign switch_extend_arr2=a>
</#if>
</#list>
customRender:({text}) => {
return render.renderSwitch(text, [{text:'是',value:'${switch_extend_arr1}'},{text:'否',value:'${switch_extend_arr2}'}])
},
<#elseif po.classType == 'sel_tree' || po.classType=='list' || po.classType=='list_multi' || po.classType=='sel_search' || po.classType=='radio' || po.classType=='checkbox' || po.classType=='sel_depart' || po.classType=='sel_user' || po.classType=='popup_dict' || po.classType=='link_table'>
dataIndex: '${po.fieldName}_dictText'
<#elseif po.classType=='cat_tree'>
dataIndex: '${po.fieldName}',
<#if po.dictText?default("")?trim?length == 0>
customRender:({text}) => {
return render.renderCategoryTree(text,'${po.dictField?default("")}')
},
<#else>
customRender: ({text, record}) => (text ? record['${po.dictText}'] : '')
</#if>
<#else>
dataIndex: '${po.fieldName}'
</#if>
},
</#if>
</#list>
];
//查询数据
export const searchFormSchema: FormSchema[] = [
<#-- 开始循环 -->
<#list columns as po>
<#if po.fieldDbName=='bpm_status'>
<#assign bpm_flag=true>
</#if>
<#-- update-begin---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#if po.isQuery=='Y' && po.fieldName !='delFlag'>
<#-- update-end---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#assign query_flag=true>
<#assign query_field_dictCode="">
<#if po.dictTable?default("")?trim?length gt 1>
<#assign query_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign query_field_dictCode="${po.dictField}">
</#if>
<#if po.queryMode=='single'>
{
label: "${po.filedComment}",
field: ${autoStringSuffix(po)},
<#if po.classType=='sel_search'>
component: 'JSearchSelect',
componentProps:{
dict:"${query_field_dictCode}"
},
<#elseif po.classType=='sel_user'>
<#-- update-begin---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
component: 'JSelectUser',
componentProps:{
<#if po.extendParams?exists && po.extendParams.text?exists>
labelKey: '${po.extendParams.text}',
</#if>
<#if po.extendParams?exists && po.extendParams.store?exists>
rowKey: '${po.extendParams.store}',
</#if>
},
<#-- update-end---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
<#elseif po.classType=='switch'>
component: 'JSwitch',
componentProps:{
query:true,
<#if po.dictField != 'is_open'>
options:${po.dictField}
</#if>
},
<#elseif po.classType=='sel_depart'>
component: 'JSelectDept',
componentProps:{
<#if po.extendParams?exists && po.extendParams.text?exists>
labelKey: '${po.extendParams.text}',
</#if>
<#if po.extendParams?exists && po.extendParams.store?exists>
rowKey: '${po.extendParams.store}',
</#if>
},
<#elseif po.classType=='list_multi'>
component: 'JSelectMultiple',
componentProps:{
<#if po.dictTable?default("")?trim?length gt 1>
dictCode:"${po.dictTable},${po.dictText},${po.dictField}",
<#elseif po.dictField?default("")?trim?length gt 1>
dictCode:"${po.dictField}",
</#if>
triggerChange: true
},
<#elseif po.classType=='cat_tree'>
component: 'JCategorySelect',
componentProps:{
pcode:"${po.dictField?default("")}",//back和事件未添加暂时有问题
},
<#elseif po.classType=='date'>
component: 'DatePicker',
componentProps: {
<#if po.extendParams?exists && po.extendParams.picker?exists>
picker: '${po.extendParams.picker}',
</#if>
valueFormat: 'YYYY-MM-DD'
},
<#elseif po.classType=='datetime'>
component: 'DatePicker',
componentProps: {
showTime:true,
valueFormat: 'YYYY-MM-DD HH:mm:ss'
},
<#elseif po.classType =='time'>
component: 'TimePicker',
componentProps: {
valueFormat: 'HH:mm:ss'
},
<#elseif po.classType=='pca'>
component: 'JAreaLinkage',
componentProps: {
saveCode: 'region',
},
<#elseif po.classType=='popup'>
<#include "/common/form/vue3popup.ftl">
<#elseif po.classType=='popup_dict'>
component: 'JPopupDict',
componentProps: {
placeholder: '请选择${po.filedComment}',
dictCode: '${po.dictTable},${po.dictText},${po.dictField}',
multi: ${po.extendParams.popupMulti?c}
},
<#elseif po.classType=='list' || po.classType=='radio' || po.classType=='checkbox'>
<#-- ---------------------------下拉或是单选 判断数据字典是表字典还是普通字典------------------------------- -->
component: 'JSelectMultiple',
componentProps:{
<#if po.dictTable?default("")?trim?length gt 1>
dictCode:"${po.dictTable},${po.dictText},${po.dictField}"
<#elseif po.dictField?default("")?trim?length gt 1>
dictCode:"${po.dictField}"
</#if>
},
<#elseif po.classType == 'sel_tree'>
component: 'JTreeSelect',
componentProps:{
<#if po.dictText??>
<#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>
dict:"${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}",
<#elseif po.dictText?split(',')[1]??>
pidField:"${po.dictText?split(',')[1]}",
<#elseif po.dictText?split(',')[3]??>
hasChildField:"${po.dictText?split(',')[3]}",
</#if>
</#if>
pidValue:"${po.dictField}",
},
<#elseif po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
component: 'InputNumber',
<#else>
component: 'Input',
</#if>
//colProps: {span: 6},
},
<#elseif po.queryMode=='like'>
{
label: "${po.filedComment}",
field: "${po.fieldName}",
component: 'JInput',
},
<#else>
{
label: "${po.filedComment}",
field: "${po.fieldName}",
<#if po.classType=='date'>
component: 'RangePicker',
componentProps: {
<#if po.extendParams?exists && po.extendParams.picker?exists>
picker: '${po.extendParams.picker}',
</#if>
valueType: 'Date',
},
<#elseif po.classType=='datetime'>
component: 'RangePicker',
componentProps: {
valueType: 'Date',
showTime:true
},
<#elseif po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
component: 'JRangeNumber',
<#-- update-begin---author:chenrui ---date:20240527 for[TV360X-388]时间范围查询控件---------- -->
<#elseif po.classType=='time'>
component: 'RangeTime',
<#-- update-end---author:chenrui ---date:20240527 for[TV360X-388]时间范围查询控件---------- -->
<#else>
component: 'Input', //TODO 范围查询
</#if>
//colProps: {span: 6},
},
</#if>
</#if>
</#list>
<#-- 结束循环 -->
];
//表单数据
export const formSchema: FormSchema[] = [
<#assign form_cat_tree = false>
<#assign form_cat_back = "">
<#assign bpm_flag=false>
<#assign id_exists = false>
<#list columns as po><#rt/>
<#if po.fieldDbName=='bpm_status'>
<#assign bpm_flag=true>
</#if>
<#if po.fieldDbName == 'id'>
<#assign id_exists = true>
</#if>
<#-- update-begin---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#if po.isShow =='Y' && po.fieldName !='delFlag'>
<#-- update-end---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#assign form_field_dictCode="">
<#if po.dictTable?default("")?trim?length gt 1 && po.dictText?default("")?trim?length gt 1 && po.dictField?default("")?trim?length gt 1>
<#assign form_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
<#elseif po.dictField?default("")?trim?length gt 1>
<#assign form_field_dictCode="${po.dictField}">
</#if>
{
label: '${po.filedComment}',
field: ${autoStringSuffix(po)},
<#-- update-begin-author:taoyan date:2022-6-24 for: VUEN-1190【代码生成】默认值未生成 -->
<#if po.defaultVal??>
<#if po.fieldDbType=="BigDecimal" || po.fieldDbType=="double" || po.fieldDbType=="int">
defaultValue: ${po.defaultVal},
<#else>
defaultValue: "${po.defaultVal}",
</#if>
</#if>
<#-- update-end-author:taoyan date:2022-6-24 for: VUEN-1190【代码生成】默认值未生成 -->
<#if po.classType =='date'>
component: 'DatePicker',
componentProps: {
<#if po.extendParams?exists && po.extendParams.picker?exists>
picker: '${po.extendParams.picker}',
</#if>
valueFormat: 'YYYY-MM-DD'
},
<#elseif po.classType =='datetime'>
component: 'DatePicker',
componentProps: {
showTime: true,
valueFormat: 'YYYY-MM-DD HH:mm:ss'
},
<#elseif po.classType =='time'>
component: 'TimePicker',
componentProps: {
valueFormat: 'HH:mm:ss'
},
<#elseif po.classType =='popup'>
<#include "/common/form/vue3popup.ftl">
<#elseif po.classType=='popup_dict'>
component: 'JPopupDict',
componentProps: {
placeholder: '请选择${po.filedComment}',
dictCode: '${po.dictTable},${po.dictText},${po.dictField}',
multi: ${po.extendParams.popupMulti?c}
},
<#elseif po.classType =='sel_depart'>
component: 'JSelectDept',
componentProps:{
<#if po.extendParams?exists && po.extendParams.text?exists>
labelKey: '${po.extendParams.text}',
</#if>
<#if po.extendParams?exists && po.extendParams.store?exists>
rowKey: '${po.extendParams.store}',
</#if>
},
<#elseif po.classType =='switch'>
component: 'JSwitch',
componentProps:{
<#if po.dictField != 'is_open'>
options:${po.dictField}
</#if>
},
<#elseif po.classType =='pca'>
component: 'JAreaLinkage',
componentProps: {
saveCode: 'region',
},
<#elseif po.classType =='markdown'>
component: 'JMarkdownEditor',//注意string转换问题
<#elseif po.classType =='password'>
component: 'InputPassword',
<#elseif po.classType =='sel_user'>
<#-- update-begin---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
component: 'JSelectUser',
<#-- update-end---author:chenrui ---date:20240102 for[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件---------- -->
componentProps:{
<#if po.extendParams?exists && po.extendParams.text?exists>
labelKey: '${po.extendParams.text}',
</#if>
<#if po.extendParams?exists && po.extendParams.store?exists>
rowKey: '${po.extendParams.store}',
</#if>
},
<#elseif po.classType =='textarea'>
component: 'InputTextArea',
<#elseif po.classType=='list'>
component: 'JDictSelectTag',
componentProps:{
dictCode:"${form_field_dictCode}",
<#if po.fieldDbType=='int'>
stringToNumber: true
</#if>
},
<#elseif po.classType=='radio'>
component: 'JDictSelectTag',
componentProps:{
dictCode:"${form_field_dictCode}",
type: "radio",
<#if po.fieldDbType=='int'>
stringToNumber: true
</#if>
},
<#-- update-begin---author:chenrui ---date:20231228 for[QQYUN-7583] Vue3风格表单页面多选控件渲染成了下拉多选---------- -->
<#elseif po.classType=='list_multi'>
component: 'JSelectMultiple',
componentProps:{
dictCode:"${form_field_dictCode}"
},
<#elseif po.classType=='checkbox'>
component: 'JCheckbox',
componentProps:{
dictCode:"${form_field_dictCode}"
},
<#-- update-end---author:chenrui ---date:20231228 for[QQYUN-7583] Vue3风格表单页面多选控件渲染成了下拉多选---------- -->
<#elseif po.classType=='sel_search'>
component: 'JSearchSelect',
componentProps:{
dict:"${form_field_dictCode}"
},
<#elseif po.classType=='cat_tree'>
<#assign form_cat_tree = true>
component: 'JCategorySelect',
componentProps:{
pcode:"${po.dictField?default("")}", //TODO back和事件未添加暂时有问题
},
<#if po.dictText?default("")?trim?length gt 1>
<#assign form_cat_back = "${po.dictText}">
</#if>
<#elseif po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
component: 'InputNumber',
<#elseif po.classType=='file'>
component: 'JUpload',
componentProps:{
<#if po.uploadnum??>
maxCount:${po.uploadnum}
</#if>
},
<#elseif po.classType=='image'>
component: 'JImageUpload',
componentProps:{
<#if po.uploadnum??>
fileMax:${po.uploadnum}
<#else>
fileMax: 0
</#if>
},
<#elseif po.classType=='umeditor'>
component: 'JEditor',
<#elseif po.classType == 'sel_tree'>
component: 'JTreeSelect',
componentProps:{
<#if po.dictText??>
<#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>
dict:"${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}",
<#elseif po.dictText?split(',')[1]??>
pidField:"${po.dictText?split(',')[1]}",
<#elseif po.dictText?split(',')[3]??>
hasChildField:"${po.dictText?split(',')[3]}",
</#if>
</#if>
pidValue:"${po.dictField}",
},
<#elseif po.classType=='link_table'>
component: 'JLinkTableCard',
componentProps: {
valueField: '${po.dictField}',
textField: '${po.dictText}',
tableName: '${po.dictTable}',
multi: <#if (po.queryMode!"") == "multi">true<#else>false</#if>
},
<#else>
component: 'Input',
</#if>
<#if po.isShow == 'Y' && poHasCheck(po)>
dynamicRules: ({model,schema}) => {
<#if po.fieldName != 'id'>
<#assign fieldValidType = po.fieldValidType!''>
return [
<#-- 非空校验 -->
<#if po.nullable == 'N' || fieldValidType == '*'>
{ required: true, message: '请输入${po.filedComment}!'},
<#elseif fieldValidType!=''>
{ required: false},
</#if>
<#-- 唯一校验 -->
<#if fieldValidType == 'only'>
{...rules.duplicateCheckRule(<#if sub?default("")?trim?length gt 1>'${sub.tableName}'<#else>'${tableName}'</#if>, '${po.fieldDbName}',model,schema)[0]},
<#-- 6到16位数字 -->
<#elseif fieldValidType == 'n6-16'>
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'},
<#-- 6到16位任意字符 -->
<#elseif fieldValidType == '*6-16'>
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'},
<#-- 6到18位字母 -->
<#elseif fieldValidType == 's6-18'>
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},
<#-- 网址 -->
<#elseif fieldValidType == 'url'>
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'},
<#-- 电子邮件 -->
<#elseif fieldValidType == 'e'>
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'},
<#-- 手机号码 -->
<#elseif fieldValidType == 'm'>
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'},
<#-- 邮政编码 -->
<#elseif fieldValidType == 'p'>
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'},
<#-- 字母 -->
<#elseif fieldValidType == 's'>
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'},
<#-- 数字 -->
<#elseif fieldValidType == 'n'>
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'},
<#-- 整数 -->
<#elseif fieldValidType == 'z'>
{ pattern: /^-?\d+$/, message: '请输入整数!'},
<#-- 金额 -->
<#elseif fieldValidType == 'money'>
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'},
<#-- 正则校验 -->
<#elseif fieldValidType != '' && fieldValidType != '*'>
{ pattern: '${fieldValidType}', message: '不符合校验规则!'},
<#-- 无校验 -->
<#else>
<#t>
</#if>
];
</#if>
},
</#if>
<#if po.readonly=='Y'>
dynamicDisabled:true
</#if>
},
</#if>
</#list>
<#if id_exists == false>
// TODO 主键隐藏字段目前写死为ID
{
label: '',
field: 'id',
component: 'Input',
show: false
},
</#if>
];
<#-- update-begin-author:chenrui date:2023-12-28 for:[QQYUN-7527]vue3代码生成默认带上高级查询 -->
// 高级查询数据
export const superQuerySchema = {
<#list columns as po>
<#-- update-begin---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
<#if po.isShowList =='Y' && po.fieldName !='id' && po.fieldName !='delFlag'>
<#-- update-end---author:chenrui ---date:20240108 for[issues/5755]vue代码不加入逻辑删除字段---------- -->
${superQueryFieldListForVue3(po,po_index)},
</#if>
</#list>
};
<#-- update-end-author:chenrui date:2023-12-28 for:[QQYUN-7527]vue3代码生成默认带上高级查询 -->
/**
* 流程表单调用这个方法获取formSchema
* @param param
*/
export function getBpmFormSchema(_formData): FormSchema[]{
// 默认和原始表单保持一致 如果流程中配置了权限数据这里需要单独处理formSchema
return formSchema;
}

View File

@@ -0,0 +1,82 @@
<#include "/common/utils.ftl">
<template>
<div style="min-height: 400px">
<BasicForm @register="registerForm"></BasicForm>
<div style="width: 100%;text-align: center" v-if="!formDisabled && showSubmitButton">
<a-button @click="submitForm" pre-icon="ant-design:check" type="primary">提 交</a-button>
</div>
</div>
</template>
<script lang="ts">
import {BasicForm, useForm} from '/@/components/Form/index';
import {computed, defineComponent} from 'vue';
import {defHttp} from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes';
import {getBpmFormSchema} from '../${entityName}.data';
import {saveOrUpdate} from '../${entityName}.api';
export default defineComponent({
name: "${entityName}Form",
components:{
BasicForm
},
props:{
formData: propTypes.object.def({}),
formBpm: propTypes.bool.def(true),
showSubmitButton: propTypes.bool.def(true),
},
setup(props){
const [registerForm, { setFieldsValue, setProps,validate, scrollToField, getFieldsValue }] = useForm({
labelWidth: 150,
schemas: getBpmFormSchema(props.formData),
showActionButtonGroup: false,
baseColProps: {span: ${getFormSpan(tableVo.fieldRowNum?default(1))}}
});
const formDisabled = computed(()=>{
if(props.formData.disabled === false){
return false;
}
return true;
});
let formData = {};
const queryByIdUrl = '/${entityPackagePath}/${entityName?uncap_first}/queryById';
async function initFormData(){
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
formData = {...data}
//设置表单的值
await setFieldsValue(formData);
//默认是禁用
await setProps({disabled: formDisabled.value})
}
async function submitForm(showTip = true) {
try {
let values = await validate();
console.log('表单数据', values)
//提交表单
await saveOrUpdate(values, true,showTip);
} catch ({ errorFields }) {
if (errorFields) {
const firstField = errorFields[0];
if (firstField) {
scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
}
}
return Promise.reject(errorFields);
}
}
initFormData();
return {
registerForm,
formDisabled,
submitForm,
}
}
});
</script>

View File

@@ -0,0 +1,130 @@
<#include "/common/utils.ftl">
<#assign buttonList=[]>
<#if tableVo.extendParams?? && tableVo.extendParams.cgButtonList??>
<#assign buttonList = tableVo.extendParams.cgButtonList?filter(btn -> btn??)>
</#if>
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :maxHeight="500" :width="${getModalWidth(tableVo.fieldRowNum?default(1))}" @ok="handleSubmit">
<BasicForm @register="registerForm" name="${entityName}Form" />
<#if buttonList?? && buttonList?size gt 0>
<template #insertFooter>
<#list buttonList as btn>
<#if btn.buttonStyle=='form'>
<a-button type="primary" @click="handle${btn.buttonCode?cap_first}" <#if btn.buttonIcon??> preIcon="ant-design:${btn.buttonIcon}" </#if>>${btn.buttonName}</a-button>
</#if>
</#list>
</template>
</#if>
</BasicModal>
</template>
<script lang="ts" setup>
import {ref, computed, unref, reactive} from 'vue';
import {BasicModal, useModalInner} from '/@/components/Modal';
import {BasicForm, useForm} from '/@/components/Form/index';
import {formSchema} from '../${entityName}.data';
import {saveOrUpdate} from '../${entityName}.api';
import { useMessage } from '/@/hooks/web/useMessage';
import { getDateByPicker } from '/@/utils';
const { createMessage } = useMessage();
// Emits声明
const emit = defineEmits(['register','success']);
const isUpdate = ref(true);
const isDetail = ref(false);
//表单配置
const [registerForm, { setProps,resetFields, setFieldsValue, validate, scrollToField }] = useForm({
<#if tableVo.fieldRowNum == 1>
labelWidth: 150,
</#if>
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: {span: ${getFormSpan(tableVo.fieldRowNum?default(1))}},
baseRowStyle: { padding: "0 20px" }
});
//表单赋值
const [registerModal, {setModalProps, closeModal}] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({confirmLoading: false,showCancelBtn:!!data?.showFooter,showOkBtn:!!data?.showFooter});
isUpdate.value = !!data?.isUpdate;
isDetail.value = !!data?.showFooter;
if (unref(isUpdate)) {
//表单赋值
await setFieldsValue({
...data.record,
});
}
// 隐藏底部时禁用整个表单
setProps({ disabled: !data?.showFooter })
});
//日期个性化选择
const fieldPickers = reactive({
<#list columns as po>
<#if po.extendParams?exists && po.extendParams.picker?exists>
${po.fieldName}: '${po.extendParams.picker}',
</#if>
</#list>
});
//设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(isDetail) ? '详情' : '编辑'));
//表单提交事件
async function handleSubmit(v) {
try {
let values = await validate();
// 预处理日期数据
changeDateValue(values);
setModalProps({confirmLoading: true});
//提交表单
await saveOrUpdate(values, isUpdate.value);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} catch ({ errorFields }) {
if (errorFields) {
const firstField = errorFields[0];
if (firstField) {
scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
}
}
return Promise.reject(errorFields);
} finally {
setModalProps({confirmLoading: false});
}
}
/**
* 处理日期值
* @param formData 表单数据
*/
const changeDateValue = (formData) => {
if (formData && fieldPickers) {
for (let key in fieldPickers) {
if (formData[key]) {
formData[key] = getDateByPicker(formData[key], fieldPickers[key]);
}
}
}
};
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle=='form'>
function handle${btn.buttonCode?cap_first}(){
createMessage.info('点击了${btn.buttonName}按钮,对应的业务逻辑需自行实现!');
}
</#if>
</#list>
</#if>
</script>
<style lang="less" scoped>
/** 时间和数字输入框样式 */
:deep(.ant-input-number) {
width: 100%;
}
:deep(.ant-calendar-picker) {
width: 100%;
}
</style>

View File

@@ -0,0 +1,583 @@
<#include "/common/utils.ftl">
<template>
<div class="p-2">
<#assign query_field_no=0>
<#assign need_category = false>
<#assign need_pca = false>
<#assign need_search = false>
<#assign need_dept_user = false>
<#assign need_switch = false>
<#assign need_dept = false>
<#assign need_multi = false>
<#assign need_popup = false>
<#assign need_popup_dict = false>
<#assign need_select_tag = false>
<#assign need_select_tree = false>
<#assign need_time = false>
<#assign bpm_flag=false>
<#assign need_markdown = false>
<#assign need_upload = false>
<#assign need_image_upload = false>
<#assign need_editor = false>
<#assign need_checkbox = false>
<#assign need_range_number = false>
<#assign is_range = false>
<#assign query_flag = false>
<#assign buttonList=[]>
<#if tableVo.extendParams?? && tableVo.extendParams.cgButtonList??>
<#assign buttonList = tableVo.extendParams.cgButtonList?filter(btn -> btn??)>
</#if>
<#assign is_like = false>
<!--查询区域-->
<div class="jeecg-basic-table-form-container">
<a-form ref="formRef" @keyup.enter.native="searchQuery" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-row :gutter="24">
<#-- 开始循环 -->
<#list columns as po>
<#if po.fieldDbName=='bpm_status'>
<#assign bpm_flag=true>
</#if>
<#if po.classType=='cat_tree' && po.dictText?default("")?trim?length == 0>
<#assign need_category=true>
</#if>
<#if po.classType=='pca'>
<#assign need_pca=true>
</#if>
<#if po.classType=='sel_search'>
<#assign need_search = true>
</#if>
<#if po.classType=='sel_user'>
<#assign need_dept_user = true>
</#if>
<#if po.classType=='sel_depart'>
<#assign need_dept = true>
</#if>
<#if po.classType=='switch'>
<#assign need_switch = true>
</#if>
<#if po.classType=='list_multi'>
<#assign need_multi = true>
</#if>
<#if po.classType=='popup'>
<#assign need_popup = true>
</#if>
<#if po.classType=='popup_dict'>
<#assign need_popup_dict = true>
</#if>
<#if po.classType=='sel_tree'>
<#assign need_select_tree = true>
</#if>
<#if po.classType=='time'>
<#assign need_time = true>
</#if>
<#if po.queryMode=='group' && (po.fieldDbType=='int' || po.fieldDbType=='long' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal')>
<#assign need_range_number = true>
</#if>
<#if po.queryMode=='group'>
<#assign is_range = true>
</#if>
<#if po.queryMode=='like'>
<#assign is_like = true>
</#if>
<#include "/common/form/native/vue3NativeSearch.ftl">
</#list>
<#if query_field_no gt 2>
</template>
</#if>
<#if query_flag>
<a-col :xl="6" :lg="7" :md="8" :sm="24">
<span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
<a-col :lg="6">
<a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button>
<a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset" style="margin-left: 8px">重置</a-button>
<a @click="toggleSearchStatus = !toggleSearchStatus" style="margin-left: 8px">
{{ toggleSearchStatus ? '收起' : '展开' }}
<Icon :icon="toggleSearchStatus ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
</a>
</a-col>
</span>
</a-col>
</#if>
</a-row>
</a-form>
</div>
<#-- 结束循环 -->
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" v-auth="'${entityPackage}:${tableName}:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
<a-button type="primary" v-auth="'${entityPackage}:${tableName}:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
<j-upload-button type="primary" v-auth="'${entityPackage}:${tableName}:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle == 'button'>
<a-button type="primary" @click="handle${btn.buttonCode?cap_first}" <#if btn.buttonIcon??> preIcon="ant-design:${btn.buttonIcon}" </#if>>${btn.buttonName}</a-button>
</#if>
</#list>
</#if>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined"></Icon>
删除
</a-menu-item>
</a-menu>
</template>
<a-button v-auth="'${entityPackage}:${tableName}:deleteBatch'">批量操作
<Icon icon="mdi:chevron-down"></Icon>
</a-button>
</a-dropdown>
<#-- update-begin---author:chenrui ---date:20231228 for[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
<!-- 高级查询 -->
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
<#-- update-end---author:chenrui ---date:20231228 for[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"/>
</template>
<template v-slot:bodyCell="{ column, record, index, text }">
<#list columns as po>
<#if po.classType=='umeditor' || po.classType=='pca' || po.classType=='file'>
<template v-if="column.dataIndex==='${po.fieldName}'">
<#if po.classType=='umeditor'>
<!--富文本件字段回显插槽-->
<div v-html="text"></div>
</#if>
<#if po.classType=='pca'>
<!--省市区字段回显插槽-->
{{ getAreaTextByCode(text) }}
</#if>
<#if po.classType=='file'>
<!--文件字段回显插槽-->
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download-outlined" size="small" @click="downloadFile(text)">下载</a-button>
</#if>
</template>
</#if>
</#list>
</template>
</BasicTable>
<!-- 表单区域 -->
<${entityName}Modal ref="registerModal" @success="handleSuccess"></${entityName}Modal>
<#if bpm_flag==true>
<!-- 审批记录 -->
<BpmPictureModal @register="registerBpmModal" />
</#if>
</div>
</template>
<script lang="ts" name="${entityPackage}-${entityName?uncap_first}" setup>
import { ref, reactive } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns, superQuerySchema } from './${entityName}.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './${entityName}.api';
import { downloadFile } from '/@/utils/common/renderUtils';
import ${entityName}Modal from './components/${entityName}Modal.vue'
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
import {useModal} from '/@/components/Modal';
<#include "/common/form/native/vue3NativeImport.ftl">
<#if need_category>
import { loadCategoryData } from '/@/api/common/api';
import { getAuthCache, setAuthCache } from '/@/utils/auth';
import { DB_DICT_DATA_KEY } from '/@/enums/cacheEnum';
</#if>
<#if need_pca>
import { getAreaTextByCode } from '/@/components/Form/src/utils/Area';
</#if>
import { getDateByPicker } from '/@/utils';
<#if need_popup_dict>
import {getPopDictByCode} from "@/utils/dict";
import {filterMultiDictText} from "@/utils/dict/JDictSelectUtil";
</#if>
<#if bpm_flag==true>
import { startProcess } from '/@/api/common/api';
const [registerBpmModal, { openModal: bpmPicModal }] = useModal();
</#if>
<#if is_range>
import { cloneDeep } from "lodash-es";
</#if>
const fieldPickers = reactive({
<#list columns as po>
<#if po.extendParams?exists && po.extendParams.picker?exists>
${po.fieldName}: '${po.extendParams.picker}',
</#if>
</#list>
});
const formRef = ref();
const queryParam = reactive<any>({});
const toggleSearchStatus = ref<boolean>(false);
const registerModal = ref();
const userStore = useUserStore();
const { createMessage } = useMessage();
//注册table数据
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '${tableVo.ftlDescription}',
api: list,
columns,
canResize:true,
useSearchForm: false,
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: async (params) => {
<#if is_range>
let rangerQuery = await setRangeQuery();
return Object.assign(params, rangerQuery);
<#else>
for (let key in fieldPickers) {
if (queryParam[key] && fieldPickers[key]) {
queryParam[key] = getDateByPicker(queryParam[key], fieldPickers[key]);
}
}
return Object.assign(params, queryParam);
</#if>
},
<#if need_popup_dict>
afterFetch: afterFetch
</#if>
},
exportConfig: {
name: "${tableVo.ftlDescription}",
url: getExportUrl,
<#if is_range>
params: setRangeQuery,
<#else>
params: queryParam,
</#if>
},
importConfig: {
url: getImportUrl,
success: handleSuccess
},
});
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
const labelCol = reactive({
xs:24,
sm:4,
xl:6,
xxl:4
});
const wrapperCol = reactive({
xs: 24,
sm: 20,
});
<#-- update-begin---author:chenrui ---date:20231228 for[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
// 高级查询配置
const superQueryConfig = reactive(superQuerySchema);
/**
* 高级查询事件
*/
function handleSuperQuery(params) {
Object.keys(params).map((k) => {
queryParam[k] = params[k];
});
searchQuery();
}
<#-- update-end---author:chenrui ---date:20231228 for[QQYUN-7527]vue3代码生成默认带上高级查询---------- -->
/**
* 新增事件
*/
function handleAdd() {
registerModal.value.disableSubmit = false;
registerModal.value.add();
}
/**
* 编辑事件
*/
function handleEdit(record: Recordable) {
registerModal.value.disableSubmit = false;
registerModal.value.edit(record);
}
/**
* 详情
*/
function handleDetail(record: Recordable) {
registerModal.value.disableSubmit = true;
registerModal.value.edit(record);
}
/**
* 删除事件
*/
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
/**
* 批量删除事件
*/
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
/**
* 成功回调
*/
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: '${entityPackage}:${tableName}:edit'
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
<#if bpm_flag==true>
let dropDownAction = [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
auth: '${entityPackage}:${tableName}:delete'
},
{
label: '审批进度',
onClick: handlePreviewPic.bind(null, record),
ifShow: !!record.bpmStatus && record.bpmStatus !== '1',
}
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle == 'link'>
,{
label: '${btn.buttonName}',
onClick: handle${btn.buttonCode?cap_first}.bind(null, record),
}
</#if>
</#list>
</#if>
];
if(record.bpmStatus == '1'){
dropDownAction.push({
label: '发起流程',
popConfirm: {
title: '确认提交流程吗?',
confirm: handleProcess.bind(null, record),
placement: 'topLeft',
}
})
}
return dropDownAction;
<#else>
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
auth: '${entityPackage}:${tableName}:delete'
}
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle == 'link'>
,{
label: '${btn.buttonName}',
onClick: handle${btn.buttonCode?cap_first}.bind(null, record),
}
</#if>
</#list>
</#if>
]
</#if>
}
/**
* 查询
*/
function searchQuery() {
reload();
}
/**
* 重置
*/
function searchReset() {
formRef.value.resetFields();
selectedRowKeys.value = [];
//刷新数据
reload();
}
<#if need_popup>
/**
* popup组件值改变事件
*/
function setFieldsValue(map) {
Object.keys(map).map((key) => {
queryParam[key] = map[key];
});
}
</#if>
<#if need_pca || need_dept_user>
/**
* form点击事件(以逗号分割)
* @param key
* @param value
*/
function handleFormJoinChange(key, value) {
if (typeof value != 'string') {
queryParam[key] = value.join(',');
}
}
</#if>
<#if bpm_flag==true>
/**
* 提交流程
*/
async function handleProcess(record) {
let params = {
flowCode: 'dev_${tableName}_001',
id: record.id,
formUrl: '${entityPackagePath}/components/${entityName}Form',
formUrlMobile: ''
}
await startProcess(params);
handleSuccess();
}
/**
* 审批进度
*/
async function handlePreviewPic(record) {
bpmPicModal(true, {
flowCode: 'dev_${tableName}_001',
dataId: record.id,
});
}
</#if>
<#if buttonList?size gt 0>
<#list buttonList as btn>
<#if btn.buttonStyle=='button'>
function handle${btn.buttonCode?cap_first}(){
createMessage.info('点击了${btn.buttonName}按钮,对应的业务逻辑需自行实现!');
}
</#if>
<#if btn.buttonStyle=='link'>
function handle${btn.buttonCode?cap_first}(record){
createMessage.info('点击了${btn.buttonName}按钮,对应的业务逻辑需自行实现!');
}
</#if>
</#list>
</#if>
<#if need_category>
/**
* form点击事件
* @param value
*/
function handleFormChange(key, value) {
queryParam[key] = value;
}
/**
* 初始化字典配置
*/
function initDictConfig() {
<#list columns as po>
<#if (po.isQuery=='Y' || po.isShowList=='Y') && po.classType!='popup'>
<#if po.classType=='cat_tree' && need_category==true>
loadCategoryData({code:'${po.dictField?default("")}'}).then((res) => {
if (res) {
const allDictDate = userStore.getAllDictItems;
if(!allDictDate['${po.dictField?default("")}']){
userStore.setAllDictItems({...allDictDate,'${po.dictField?default("")}':res});
}
}
});
</#if>
</#if>
</#list>
}
initDictConfig();
</#if>
<#if is_range>
let rangeField = '${getRangeField(columns)}'
/**
* 设置范围查询条件
*/
async function setRangeQuery(){
let queryParamClone = cloneDeep(queryParam);
if (rangeField) {
let fieldsValue = rangeField.split(',');
fieldsValue.forEach(item => {
if (queryParamClone[item]) {
let range = queryParamClone[item];
queryParamClone[item+'_begin'] = range[0];
queryParamClone[item+'_end'] = range[1];
delete queryParamClone[item];
} else {
queryParamClone[item+'_begin'] = '';
queryParamClone[item+'_end'] = '';
}
})
}
return queryParamClone;
}
</#if>
<#if need_popup_dict>
/**
* 翻译Popup字典配置
*/
async function afterFetch(records){
<#list columns as po>
<#if po.isQuery=='Y' || po.isShowList=='Y'>
<#if po.classType=='popup_dict'>
const ${po.fieldName}Keys = [...new Set(records.map((item) => item['${po.fieldName}']).flatMap((item) => item && item.split(',')))];
if(${po.fieldName}Keys && ${po.fieldName}Keys.length){
const dictOptions = await getPopDictByCode(${po.fieldName}Keys.join(','), '${po.dictTable},${po.dictField},${po.dictText}');
records.forEach((item) => {
item['${po.fieldName}_dictText'] = filterMultiDictText(dictOptions, item['${po.fieldName}']);
});
}
</#if>
</#if>
</#list>
return records;
}
</#if>
</script>
<style lang="less" scoped>
<#include "/common/form/native/vueNativeSearchStyle.ftl">
</style>

Some files were not shown because too many files have changed in this diff Show More