From ce9dc8efd8bfabb2d5947d76ea79a312ddf1d2dc Mon Sep 17 00:00:00 2001 From: geht <2947093423@qq.com> Date: Thu, 7 May 2026 09:47:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0MybatisPlusSaasConfig?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E7=A7=9F=E6=88=B7ID=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=80=BC=E4=B8=BA1002=EF=BC=9B=E5=9C=A8ShiroConfig=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0MES=E5=AF=86=E7=82=BC=E7=89=A9=E6=96=99?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=92=8C=E7=B3=BB=E7=BB=9F=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E5=AD=97=E5=85=B8=E7=9A=84=E5=85=8D=E5=AF=86=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=9B=E5=9C=A8MesMixerMaterialController=E4=B8=AD=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=AF=86=E7=82=BC=E7=89=A9=E6=96=99=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E7=9A=84=E5=85=8D=E5=AF=86=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9F=A5?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0=E7=9B=B8?= =?UTF-8?q?=E5=BA=94=E7=9A=84=E4=BA=8B=E4=BB=B6=E5=B9=BF=E6=92=AD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=9B=E5=9C=A8SysCategoryController=E4=B8=AD?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=88=86=E7=B1=BB=E5=AD=97=E5=85=B8=E7=9A=84?= =?UTF-8?q?=E5=85=8D=E5=AF=86=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=EF=BC=9B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=89=8D=E7=AB=AF=E5=AF=BC=E8=88=AA=E5=92=8C?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=95=B0=E6=8D=AE=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AF=86=E7=82=BC=E7=89=A9=E6=96=99=E4=BF=A1=E6=81=AF=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/mybatis/MybatisPlusSaasConfig.java | 2 +- .../org/jeecg/config/shiro/ShiroConfig.java | 4 + .../service/MesXslStompNotifyService.java | 5 + .../MesMixerMaterialController.java | 82 ++++++ .../controller/SysCategoryController.java | 72 ++++++ .../Core/Events/MixerMaterialChangedEvent.cs | 12 + .../Core/Services/IMixerMaterialService.cs | 27 ++ .../Entity/JeecgSysCategoryItem.cs | 38 +++ .../YY.Admin.Core/Entity/MesMixerMaterial.cs | 40 +++ .../YY.Admin.Core/SeedData/SysMenuSeedData.cs | 6 + .../SeedData/SysTenantMenuSeedData.cs | 4 + .../Category/Dto/JeecgCategoryItemOutput.cs | 13 + .../Category/Dto/JeecgCategoryTreeNodeDto.cs | 11 + .../Dto/PageJeecgCategoryItemInput.cs | 11 + .../Category/IJeecgCategorySyncService.cs | 11 + .../Category/JeecgCategorySyncService.cs | 240 ++++++++++++++++++ .../MixerMaterial/MixerMaterialService.cs | 215 ++++++++++++++++ .../MixerMaterialSyncCoordinator.cs | 63 +++++ .../Hubs/StompWebSocketService.cs | 4 + .../YY.Admin/Module/NavigationExtensions.cs | 4 + yy-admin-master/YY.Admin/Module/SyncModule.cs | 6 + .../ViewModels/Control/MenuTreeViewModel.cs | 14 +- .../MixerMaterialEditDialogViewModel.cs | 186 ++++++++++++++ .../MixerMaterialListViewModel.cs | 232 +++++++++++++++++ .../CategoryDictionaryManagementViewModel.cs | 143 +++++++++++ .../MixerMaterialEditDialogView.xaml | 174 +++++++++++++ .../MixerMaterialEditDialogView.xaml.cs | 21 ++ .../MixerMaterial/MixerMaterialListView.xaml | 174 +++++++++++++ .../MixerMaterialListView.xaml.cs | 12 + .../CategoryDictionaryManagementView.xaml | 117 +++++++++ .../CategoryDictionaryManagementView.xaml.cs | 26 ++ 31 files changed, 1967 insertions(+), 2 deletions(-) create mode 100644 yy-admin-master/YY.Admin.Core/Core/Events/MixerMaterialChangedEvent.cs create mode 100644 yy-admin-master/YY.Admin.Core/Core/Services/IMixerMaterialService.cs create mode 100644 yy-admin-master/YY.Admin.Core/Entity/JeecgSysCategoryItem.cs create mode 100644 yy-admin-master/YY.Admin.Core/Entity/MesMixerMaterial.cs create mode 100644 yy-admin-master/YY.Admin.Services/Service/Category/Dto/JeecgCategoryItemOutput.cs create mode 100644 yy-admin-master/YY.Admin.Services/Service/Category/Dto/JeecgCategoryTreeNodeDto.cs create mode 100644 yy-admin-master/YY.Admin.Services/Service/Category/Dto/PageJeecgCategoryItemInput.cs create mode 100644 yy-admin-master/YY.Admin.Services/Service/Category/IJeecgCategorySyncService.cs create mode 100644 yy-admin-master/YY.Admin.Services/Service/Category/JeecgCategorySyncService.cs create mode 100644 yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialService.cs create mode 100644 yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialSyncCoordinator.cs create mode 100644 yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialEditDialogViewModel.cs create mode 100644 yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialListViewModel.cs create mode 100644 yy-admin-master/YY.Admin/ViewModels/SysManage/CategoryDictionaryManagementViewModel.cs create mode 100644 yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialEditDialogView.xaml create mode 100644 yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialEditDialogView.xaml.cs create mode 100644 yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialListView.xaml create mode 100644 yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialListView.xaml.cs create mode 100644 yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml create mode 100644 yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml.cs diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java index 5bc4d02..f8b9b38 100644 --- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java +++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java @@ -113,7 +113,7 @@ public class MybatisPlusSaasConfig { } } if (oConvertUtils.isEmpty(tenantId)) { - tenantId = "0"; + tenantId = "1002"; } return new LongValue(tenantId); } diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java index b10921b..7ee6d49 100644 --- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java +++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java @@ -203,6 +203,10 @@ public class ShiroConfig { filterChainDefinitionMap.put("/xslmes/mesXslSupplier/anon/**", "anon"); // MES磅单管理免密接口(供桌面端调用) filterChainDefinitionMap.put("/xslmes/mesXslWeightRecord/anon/**", "anon"); + // MES密炼物料管理免密接口(供桌面端调用) + filterChainDefinitionMap.put("/mes/material/mixerMaterial/anon/**", "anon"); + // 系统分类字典免密接口(供桌面端调用) + filterChainDefinitionMap.put("/sys/category/anon/**", "anon"); // 桌面端用户反同步批量上报(Outbox -> /sys/sync/batch) filterChainDefinitionMap.put("/sys/sync/batch", "anon"); diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java index 780e595..8a9691e 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java @@ -40,6 +40,11 @@ public class MesXslStompNotifyService { publish("/topic/sync/mes-weight-records", "MES_WEIGHT_RECORD_CHANGED", "weightRecordId", weightRecordId, action); } + /** 广播密炼物料数据变更事件到 /topic/sync/mes-mixer-materials */ + public void publishMixerMaterialChanged(String action, String mixerMaterialId) { + publish("/topic/sync/mes-mixer-materials", "MIXER_MATERIAL_CHANGED", "mixerMaterialId", mixerMaterialId, action); + } + // ─────────────────────────── 私有辅助 ──────────────────────────── private void publish(String topic, String cmd, String idKey, String idValue, String action) { diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMixerMaterialController.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMixerMaterialController.java index 158bddd..093bfe5 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMixerMaterialController.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/mes/material/controller/MesMixerMaterialController.java @@ -1,5 +1,6 @@ package org.jeecg.modules.mes.material.controller; +import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -17,14 +18,23 @@ import org.jeecg.common.system.base.controller.JeecgController; import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.modules.mes.material.entity.MesMixerMaterial; import org.jeecg.modules.mes.material.service.IMesMixerMaterialService; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; +import java.util.HashMap; +import java.util.Map; + @Slf4j @Tag(name = "MES-密炼物料信息") @RestController @RequestMapping("/mes/material/mixerMaterial") public class MesMixerMaterialController extends JeecgController { + private final SimpMessagingTemplate messagingTemplate; + + public MesMixerMaterialController(SimpMessagingTemplate messagingTemplate) { + this.messagingTemplate = messagingTemplate; + } @GetMapping("/list") public Result> queryPageList( @@ -43,6 +53,7 @@ public class MesMixerMaterialController extends JeecgController add(@RequestBody MesMixerMaterial model) { service.save(model); + publishChanged("add", model.getId()); return Result.OK("添加成功!"); } @@ -52,6 +63,7 @@ public class MesMixerMaterialController extends JeecgController edit(@RequestBody MesMixerMaterial model) { service.updateById(model); + publishChanged("edit", model.getId()); return Result.OK("编辑成功!"); } @@ -61,6 +73,7 @@ public class MesMixerMaterialController extends JeecgController delete(@RequestParam(name = "id") String id) { service.removeById(id); + publishChanged("delete", id); return Result.OK("删除成功!"); } @@ -71,6 +84,62 @@ public class MesMixerMaterialController extends JeecgController deleteBatch(@RequestParam(name = "ids") String ids) { List idList = Arrays.asList(ids.split(",")); service.removeByIds(idList); + publishChanged("batchDelete", ids); + return Result.OK("批量删除成功!"); + } + + @Operation(summary = "MES-密炼物料信息-免密分页查询") + @GetMapping("/anon/list") + public Result> anonList( + MesMixerMaterial model, + @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, + HttpServletRequest req) { + QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(model, req.getParameterMap()); + IPage pageList = service.page(new Page<>(pageNo, pageSize), queryWrapper); + return Result.OK(pageList); + } + + @Operation(summary = "MES-密炼物料信息-免密通过id查询") + @GetMapping("/anon/queryById") + public Result anonQueryById(@RequestParam(name = "id") String id) { + MesMixerMaterial data = service.getById(id); + return data != null ? Result.OK(data) : Result.error("未找到对应数据"); + } + + @Operation(summary = "MES-密炼物料信息-免密添加") + @PostMapping("/anon/add") + public Result anonAdd(@RequestBody MesMixerMaterial model) { + service.save(model); + publishChanged("add", model.getId()); + return Result.OK("添加成功!"); + } + + @Operation(summary = "MES-密炼物料信息-免密编辑") + @RequestMapping(value = "/anon/edit", method = {RequestMethod.PUT, RequestMethod.POST}) + public Result anonEdit(@RequestBody MesMixerMaterial model) { + boolean ok = service.updateById(model); + if (!ok) { + return Result.error("数据已被他人修改,请刷新后重试"); + } + publishChanged("edit", model.getId()); + return Result.OK("编辑成功!"); + } + + @Operation(summary = "MES-密炼物料信息-免密删除") + @DeleteMapping("/anon/delete") + public Result anonDelete(@RequestParam(name = "id") String id) { + service.removeById(id); + publishChanged("delete", id); + return Result.OK("删除成功!"); + } + + @Operation(summary = "MES-密炼物料信息-免密批量删除") + @DeleteMapping("/anon/deleteBatch") + public Result anonDeleteBatch(@RequestParam(name = "ids") String ids) { + List idList = Arrays.asList(ids.split(",")); + service.removeByIds(idList); + publishChanged("batchDelete", ids); return Result.OK("批量删除成功!"); } @@ -90,4 +159,17 @@ public class MesMixerMaterialController extends JeecgController importExcel(HttpServletRequest request, HttpServletResponse response) { return super.importExcel(request, response, MesMixerMaterial.class); } + + private void publishChanged(String action, String mixerMaterialId) { + try { + Map event = new HashMap<>(); + event.put("cmd", "MIXER_MATERIAL_CHANGED"); + event.put("action", action); + event.put("mixerMaterialId", mixerMaterialId); + event.put("timestamp", System.currentTimeMillis()); + messagingTemplate.convertAndSend("/topic/sync/mes-mixer-materials", JSON.toJSONString(event)); + } catch (Exception e) { + log.debug("广播密炼物料 STOMP 事件失败: {}", e.getMessage()); + } + } } diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java index c8840e9..a16e3ad 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java @@ -482,6 +482,78 @@ public class SysCategoryController { return result; } + /** + * 分类字典树控件(免密)加载节点 + */ + @RequestMapping(value = "/anon/loadTreeData", method = RequestMethod.GET) + public Result> anonLoadDict(@RequestParam(name="pid",required = false) String pid,@RequestParam(name="pcode",required = false) String pcode, @RequestParam(name="condition",required = false) String condition) { + Result> result = new Result>(); + String queryPid = pid; + if (oConvertUtils.isEmpty(queryPid)) { + if (oConvertUtils.isEmpty(pcode) || ISysCategoryService.ROOT_PID_VALUE.equals(pcode)) { + queryPid = ISysCategoryService.ROOT_PID_VALUE; + } else { + queryPid = this.sysCategoryService.queryIdByCode(pcode); + } + } + if (oConvertUtils.isEmpty(queryPid)) { + result.setSuccess(false); + result.setMessage("加载分类字典树参数有误.[code]!"); + return result; + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (ISysCategoryService.ROOT_PID_VALUE.equals(queryPid)) { + queryWrapper.and(w -> w.eq("pid", ISysCategoryService.ROOT_PID_VALUE).or().isNull("pid")); + } else { + queryWrapper.eq("pid", queryPid); + } + queryWrapper.orderByAsc("code"); + List categories = this.sysCategoryService.list(queryWrapper); + List list = new ArrayList<>(); + for (SysCategory c : categories) { + TreeSelectModel tsm = new TreeSelectModel(); + tsm.setKey(c.getId()); + tsm.setValue(c.getId()); + tsm.setTitle(c.getName()); + tsm.setParentId(c.getPid()); + tsm.setCode(c.getCode()); + tsm.setLeaf(!CommonConstant.STATUS_1.equals(c.getHasChild())); + list.add(tsm); + } + result.setSuccess(true); + result.setResult(list); + return result; + } + + /** + * 分类字典子节点(免密) + */ + @GetMapping(value = "/anon/childList") + public Result> anonChildList(SysCategory sysCategory, HttpServletRequest req) { + Result> result = new Result>(); + String pid = sysCategory.getPid(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (oConvertUtils.isEmpty(pid) || ISysCategoryService.ROOT_PID_VALUE.equals(pid)) { + queryWrapper.and(w -> w.eq("pid", ISysCategoryService.ROOT_PID_VALUE).or().isNull("pid")); + } else { + queryWrapper.eq("pid", pid); + } + queryWrapper.orderByAsc("code"); + List list = this.sysCategoryService.list(queryWrapper); + result.setSuccess(true); + result.setResult(list); + return result; + } + + /** + * 分类字典详情(免密) + */ + @GetMapping(value = "/anon/queryById") + public Result anonQueryById(@RequestParam(name="id",required=true) String id) { + return queryById(id); + } + /** * 分类字典控件数据回显[表单页面] * diff --git a/yy-admin-master/YY.Admin.Core/Core/Events/MixerMaterialChangedEvent.cs b/yy-admin-master/YY.Admin.Core/Core/Events/MixerMaterialChangedEvent.cs new file mode 100644 index 0000000..14befac --- /dev/null +++ b/yy-admin-master/YY.Admin.Core/Core/Events/MixerMaterialChangedEvent.cs @@ -0,0 +1,12 @@ +using Prism.Events; + +namespace YY.Admin.Core.Events; + +public class MixerMaterialChangedPayload +{ + public string Action { get; set; } = string.Empty; + public string? MixerMaterialId { get; set; } +} + +public class MixerMaterialChangedEvent : PubSubEvent { } + diff --git a/yy-admin-master/YY.Admin.Core/Core/Services/IMixerMaterialService.cs b/yy-admin-master/YY.Admin.Core/Core/Services/IMixerMaterialService.cs new file mode 100644 index 0000000..a736e9b --- /dev/null +++ b/yy-admin-master/YY.Admin.Core/Core/Services/IMixerMaterialService.cs @@ -0,0 +1,27 @@ +using YY.Admin.Core.Entity; + +namespace YY.Admin.Core.Services; + +public record MixerMaterialPageResult(List Records, long Total, int Current, int Size); + +public interface IMixerMaterialService +{ + Task PageAsync( + int pageNo, + int pageSize, + string? materialCode = null, + string? materialName = null, + string? erpCode = null, + string? majorCategoryId = null, + string? minorCategoryId = null, + CancellationToken ct = default); + + Task GetByIdAsync(string id, CancellationToken ct = default); + Task AddAsync(MesMixerMaterial material, CancellationToken ct = default); + Task EditAsync(MesMixerMaterial material, CancellationToken ct = default); + Task DeleteAsync(string id, CancellationToken ct = default); + Task DeleteBatchAsync(string ids, CancellationToken ct = default); + Task>> GetMajorCategoryOptionsAsync(CancellationToken ct = default); + Task>> GetMinorCategoryOptionsAsync(string majorCategoryId, CancellationToken ct = default); +} + diff --git a/yy-admin-master/YY.Admin.Core/Entity/JeecgSysCategoryItem.cs b/yy-admin-master/YY.Admin.Core/Entity/JeecgSysCategoryItem.cs new file mode 100644 index 0000000..4274ab1 --- /dev/null +++ b/yy-admin-master/YY.Admin.Core/Entity/JeecgSysCategoryItem.cs @@ -0,0 +1,38 @@ +namespace YY.Admin.Core.Entity; + +/// +/// Jeecg 分类字典镜像表 +/// +[SugarTable("jeecg_sys_category_item", "Jeecg分类字典镜像表")] +[SysTable] +[SugarIndex("index_{table}_PC", nameof(Pid), OrderByType.Asc, nameof(Code), OrderByType.Asc)] +public class JeecgSysCategoryItem +{ + [SugarColumn(IsPrimaryKey = true, ColumnDescription = "主键ID", Length = 64)] + public string Id { get; set; } = string.Empty; + + [SugarColumn(ColumnDescription = "父ID", Length = 64)] + public string? Pid { get; set; } + + [SugarColumn(ColumnDescription = "分类名称", Length = 128)] + public string? Name { get; set; } + + [SugarColumn(ColumnDescription = "分类编码", Length = 128)] + public string? Code { get; set; } + + [SugarColumn(ColumnDescription = "是否有子节点", Length = 1)] + public string? HasChild { get; set; } + + [SugarColumn(ColumnDescription = "创建人", Length = 64)] + public string? CreateBy { get; set; } + + [SugarColumn(ColumnDescription = "创建时间")] + public DateTime? CreateTime { get; set; } + + [SugarColumn(ColumnDescription = "更新人", Length = 64)] + public string? UpdateBy { get; set; } + + [SugarColumn(ColumnDescription = "更新时间")] + public DateTime? UpdateTime { get; set; } +} + diff --git a/yy-admin-master/YY.Admin.Core/Entity/MesMixerMaterial.cs b/yy-admin-master/YY.Admin.Core/Entity/MesMixerMaterial.cs new file mode 100644 index 0000000..d5698c9 --- /dev/null +++ b/yy-admin-master/YY.Admin.Core/Entity/MesMixerMaterial.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace YY.Admin.Core.Entity; + +public class MesMixerMaterial +{ + public string? Id { get; set; } + public string? MaterialCode { get; set; } + public string? MaterialName { get; set; } + public string? ErpCode { get; set; } + public string? MajorCategoryId { get; set; } + public string? MinorCategoryId { get; set; } + public string? MaterialDesc { get; set; } + public string? AliasName { get; set; } + public int? FeedManageStatus { get; set; } + public int? UseStatus { get; set; } + public double? SpecificGravity { get; set; } + public int? ShelfLifeDays { get; set; } + public int? MinBakeMinutes { get; set; } + public double? TotalSafetyStockKg { get; set; } + public double? QualifiedSafetyStockKg { get; set; } + public string? Remark { get; set; } + public int? TenantId { get; set; } + public string? SysOrgCode { get; set; } + public string? CreateBy { get; set; } + public DateTime? CreateTime { get; set; } + public string? UpdateBy { get; set; } + public DateTime? UpdateTime { get; set; } + public int? DelFlag { get; set; } + + [JsonPropertyName("majorCategoryId_dictText")] + public string? MajorCategoryText { get; set; } + + [JsonPropertyName("minorCategoryId_dictText")] + public string? MinorCategoryText { get; set; } + + public string FeedManageStatusText => FeedManageStatus == 1 ? "在投管" : "未投管"; + public string UseStatusText => UseStatus == 1 ? "使用中" : "停用"; +} + diff --git a/yy-admin-master/YY.Admin.Core/SeedData/SysMenuSeedData.cs b/yy-admin-master/YY.Admin.Core/SeedData/SysMenuSeedData.cs index c40756a..4d2a209 100644 --- a/yy-admin-master/YY.Admin.Core/SeedData/SysMenuSeedData.cs +++ b/yy-admin-master/YY.Admin.Core/SeedData/SysMenuSeedData.cs @@ -36,6 +36,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData new SysMenu{ Id=1300150010401, Pid=1300150000101, Title="磅单记录管理", Path="/xslmes/mesXslWeightRecord", Name="mesXslWeightRecord", Component="WeightRecordListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=103 }, // 地磅称重操作(操作台大页面) new SysMenu{ Id=1300150010501, Pid=1300150000101, Title="地磅称重操作", Path="/xslmes/weightRecordOperation", Name="weightRecordOperation", Component="WeightRecordOperationView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=104 }, + // 密炼物料信息 + new SysMenu{ Id=1300150010601, Pid=1300150000101, Title="密炼物料信息", Path="/xslmes/mesMixerMaterial", Name="mesMixerMaterial", Component="MixerMaterialListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=105 }, #endregion @@ -60,6 +62,10 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData new SysMenu{ Id=1300200012001, Pid=1300200000101, Title="数据字典", Path="DataDictionaryManagementView", Name="sysDict", Component="/system/dict/index", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=105 }, new SysMenu{ Id=1300200012011, Pid=1300200012001, Title="查询", Permission="sysDict:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, new SysMenu{ Id=1300200012021, Pid=1300200012001, Title="同步", Permission="sysDict:sync", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + // 分类字典(SCADA同步) + new SysMenu{ Id=1300200012101, Pid=1300200000101, Title="分类字典", Path="CategoryDictionaryManagementView", Name="sysCategory", Component="/system/category/index", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=106 }, + new SysMenu{ Id=1300200012111, Pid=1300200012101, Title="查询", Permission="sysCategory:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1300200012121, Pid=1300200012101, Title="同步", Permission="sysCategory:sync", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, // 登录设置(桌面端会话与检查间隔) new SysMenu{ Id=1300200013001, Pid=1300200000101, Title="登录设置", Path="LoginSettingsView", Name="loginSettings", Component="LoginSettingsView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=107 }, diff --git a/yy-admin-master/YY.Admin.Core/SeedData/SysTenantMenuSeedData.cs b/yy-admin-master/YY.Admin.Core/SeedData/SysTenantMenuSeedData.cs index ab59737..c512df7 100644 --- a/yy-admin-master/YY.Admin.Core/SeedData/SysTenantMenuSeedData.cs +++ b/yy-admin-master/YY.Admin.Core/SeedData/SysTenantMenuSeedData.cs @@ -26,6 +26,10 @@ public class SysTenantMenuSeedData : ISqlSugarEntitySeedData new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010201}, new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010401}, new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010501}, + new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010601}, + new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012101}, + new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012111}, + new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012121}, new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200010701 }, new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300300100601 }, new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200090401 }, diff --git a/yy-admin-master/YY.Admin.Services/Service/Category/Dto/JeecgCategoryItemOutput.cs b/yy-admin-master/YY.Admin.Services/Service/Category/Dto/JeecgCategoryItemOutput.cs new file mode 100644 index 0000000..b2f74fc --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/Category/Dto/JeecgCategoryItemOutput.cs @@ -0,0 +1,13 @@ +namespace YY.Admin.Services.Service.Category.Dto; + +public class JeecgCategoryItemOutput +{ + public string Id { get; set; } = string.Empty; + public string? Pid { get; set; } + public string? Name { get; set; } + public string? Code { get; set; } + public string? HasChild { get; set; } + public DateTime? CreateTime { get; set; } + public DateTime? UpdateTime { get; set; } +} + diff --git a/yy-admin-master/YY.Admin.Services/Service/Category/Dto/JeecgCategoryTreeNodeDto.cs b/yy-admin-master/YY.Admin.Services/Service/Category/Dto/JeecgCategoryTreeNodeDto.cs new file mode 100644 index 0000000..5184b0b --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/Category/Dto/JeecgCategoryTreeNodeDto.cs @@ -0,0 +1,11 @@ +namespace YY.Admin.Services.Service.Category.Dto; + +public class JeecgCategoryTreeNodeDto +{ + public string Id { get; set; } = string.Empty; + public string? Pid { get; set; } + public string? Name { get; set; } + public string? Code { get; set; } + public List Children { get; set; } = []; +} + diff --git a/yy-admin-master/YY.Admin.Services/Service/Category/Dto/PageJeecgCategoryItemInput.cs b/yy-admin-master/YY.Admin.Services/Service/Category/Dto/PageJeecgCategoryItemInput.cs new file mode 100644 index 0000000..bcd27a3 --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/Category/Dto/PageJeecgCategoryItemInput.cs @@ -0,0 +1,11 @@ +namespace YY.Admin.Services.Service.Category.Dto; + +public class PageJeecgCategoryItemInput +{ + public int Page { get; set; } = 1; + public int PageSize { get; set; } = 20; + public string? Name { get; set; } + public string? Code { get; set; } + public string? Pid { get; set; } +} + diff --git a/yy-admin-master/YY.Admin.Services/Service/Category/IJeecgCategorySyncService.cs b/yy-admin-master/YY.Admin.Services/Service/Category/IJeecgCategorySyncService.cs new file mode 100644 index 0000000..702cf99 --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/Category/IJeecgCategorySyncService.cs @@ -0,0 +1,11 @@ +using YY.Admin.Services.Service.Category.Dto; + +namespace YY.Admin.Services.Service.Category; + +public interface IJeecgCategorySyncService +{ + Task> PageAsync(PageJeecgCategoryItemInput input); + Task SyncFromJeecgAsync(); + Task> LoadTreeAsync(); +} + diff --git a/yy-admin-master/YY.Admin.Services/Service/Category/JeecgCategorySyncService.cs b/yy-admin-master/YY.Admin.Services/Service/Category/JeecgCategorySyncService.cs new file mode 100644 index 0000000..fc3b322 --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/Category/JeecgCategorySyncService.cs @@ -0,0 +1,240 @@ +using Microsoft.Extensions.Configuration; +using SqlSugar; +using System.Text.Json; +using YY.Admin.Core; +using YY.Admin.Core.Entity; +using YY.Admin.Services.Service.Category.Dto; +using YY.Admin.Services.Service.Jeecg; + +namespace YY.Admin.Services.Service.Category; + +public class JeecgCategorySyncService : IJeecgCategorySyncService, ISingletonDependency +{ + private readonly ISqlSugarClient _dbContext; + private readonly IConfiguration _configuration; + private readonly IJeecgBackendGateway _jeecgGateway; + + public JeecgCategorySyncService( + ISqlSugarClient dbContext, + IConfiguration configuration, + IJeecgBackendGateway jeecgGateway) + { + _dbContext = dbContext; + _configuration = configuration; + _jeecgGateway = jeecgGateway; + } + + public async Task> PageAsync(PageJeecgCategoryItemInput input) + { + var name = input.Name; + var code = input.Code; + var pid = input.Pid; + var query = _dbContext.Queryable().ClearFilter() + .WhereIF(!string.IsNullOrWhiteSpace(name), x => x.Name != null && x.Name.Contains(name!)) + .WhereIF(!string.IsNullOrWhiteSpace(code), x => x.Code != null && x.Code.Contains(code!)) + .WhereIF(!string.IsNullOrWhiteSpace(pid), x => x.Pid == pid); + + query = query.OrderBy(x => SqlFunc.IsNull(x.Code, "")) + .OrderBy(x => SqlFunc.IsNull(x.Name, "")) + .OrderBy(x => SqlFunc.Desc(x.UpdateTime)); + + RefAsync total = 0; + var list = await query.ToPageListAsync(input.Page, input.PageSize, total); + var items = list.Select(x => new JeecgCategoryItemOutput + { + Id = x.Id, + Pid = x.Pid, + Name = x.Name, + Code = x.Code, + HasChild = x.HasChild, + CreateTime = x.CreateTime, + UpdateTime = x.UpdateTime + }).ToList(); + + return new SqlSugarPagedList + { + Page = input.Page, + PageSize = input.PageSize, + Total = total, + TotalPages = input.PageSize > 0 ? (int)Math.Ceiling(total / (double)input.PageSize) : 0, + HasNextPage = input.PageSize > 0 && input.Page < (int)Math.Ceiling(total / (double)input.PageSize), + HasPrevPage = input.Page > 1, + Items = items + }; + } + + public async Task SyncFromJeecgAsync() + { + var baseUrl = _configuration.GetValue("JeecgIntegration:BaseUrl")?.TrimEnd('/'); + if (string.IsNullOrWhiteSpace(baseUrl)) + { + return 0; + } + + var queue = new Queue(); + var visited = new HashSet(StringComparer.OrdinalIgnoreCase); + queue.Enqueue("0"); + visited.Add("0"); + var synced = 0; + + while (queue.Count > 0) + { + var pid = queue.Dequeue(); + var requestUrl = $"{baseUrl}/sys/category/anon/childList?pid={Uri.EscapeDataString(pid)}"; + var json = await _jeecgGateway.ExecuteGetStringAsync(requestUrl); + if (string.IsNullOrWhiteSpace(json)) + { + continue; + } + + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + if (!(root.TryGetProperty("success", out var successEl) && successEl.GetBoolean())) + { + continue; + } + if (!root.TryGetProperty("result", out var listEl) || listEl.ValueKind != JsonValueKind.Array) + { + continue; + } + + foreach (var row in listEl.EnumerateArray()) + { + var id = GetString(row, "id"); + if (string.IsNullOrWhiteSpace(id)) + { + continue; + } + + var existing = await _dbContext.Queryable() + .ClearFilter() + .Where(x => x.Id == id) + .FirstAsync(); + + if (existing == null) + { + existing = new JeecgSysCategoryItem { Id = id }; + } + + existing.Pid = GetString(row, "pid"); + existing.Name = GetString(row, "name"); + existing.Code = GetString(row, "code"); + existing.HasChild = GetString(row, "hasChild"); + existing.CreateBy = GetString(row, "createBy"); + existing.CreateTime = GetDateTime(row, "createTime"); + existing.UpdateBy = GetString(row, "updateBy"); + existing.UpdateTime = GetDateTime(row, "updateTime"); + + var existsCount = await _dbContext.Queryable() + .ClearFilter() + .Where(x => x.Id == existing.Id) + .CountAsync(); + if (existsCount > 0) + { + await _dbContext.Updateable(existing).ExecuteCommandAsync(); + } + else + { + await _dbContext.Insertable(existing).ExecuteCommandAsync(); + } + + synced++; + + if (existing.HasChild == "1" && !visited.Contains(existing.Id)) + { + queue.Enqueue(existing.Id); + visited.Add(existing.Id); + } + } + } + + return synced; + } + + public async Task> LoadTreeAsync() + { + return await BuildLocalTreeAsync(); + } + + private async Task> BuildLocalTreeAsync() + { + var allItems = await _dbContext.Queryable() + .ClearFilter() + .ToListAsync(); + + if (allItems == null || allItems.Count == 0) + { + return []; + } + + var nodeMap = allItems + .Where(x => !string.IsNullOrWhiteSpace(x.Id)) + .ToDictionary( + x => x.Id, + x => new JeecgCategoryTreeNodeDto + { + Id = x.Id, + Pid = x.Pid, + Name = x.Name, + Code = x.Code + }, + StringComparer.OrdinalIgnoreCase); + + var roots = new List(); + foreach (var node in nodeMap.Values) + { + if (string.IsNullOrWhiteSpace(node.Pid) || node.Pid == "0" || !nodeMap.TryGetValue(node.Pid, out var parent)) + { + roots.Add(node); + continue; + } + parent.Children.Add(node); + } + + SortTreeNodes(roots); + return roots; + } + + private static void SortTreeNodes(List nodes) + { + nodes.Sort((a, b) => + { + var codeCompare = string.Compare(a.Code, b.Code, StringComparison.OrdinalIgnoreCase); + return codeCompare != 0 ? codeCompare : string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); + }); + foreach (var node in nodes) + { + if (node.Children.Count > 0) + { + SortTreeNodes(node.Children); + } + } + } + + private static string? GetString(JsonElement row, string propertyName) + { + if (!row.TryGetProperty(propertyName, out var el)) + { + return null; + } + if (el.ValueKind == JsonValueKind.String) + { + return el.GetString(); + } + return el.ToString(); + } + + private static DateTime? GetDateTime(JsonElement row, string propertyName) + { + if (!row.TryGetProperty(propertyName, out var el)) + { + return null; + } + if (el.ValueKind == JsonValueKind.String && DateTime.TryParse(el.GetString(), out var dt)) + { + return dt; + } + return null; + } +} + diff --git a/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialService.cs b/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialService.cs new file mode 100644 index 0000000..62711a6 --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialService.cs @@ -0,0 +1,215 @@ +using Microsoft.Extensions.Configuration; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Web; +using YY.Admin.Core; +using YY.Admin.Core.Entity; +using YY.Admin.Core.Services; + +namespace YY.Admin.Services.Service.MixerMaterial; + +public class MixerMaterialService : IMixerMaterialService, ISingletonDependency +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfiguration _configuration; + private readonly ILoggerService _logger; + + private static readonly JsonSerializerOptions _jsonOpts = new() + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new NullableDateTimeJsonConverter() } + }; + + public MixerMaterialService( + IHttpClientFactory httpClientFactory, + IConfiguration configuration, + ILoggerService logger) + { + _httpClientFactory = httpClientFactory; + _configuration = configuration; + _logger = logger; + } + + private string BaseUrl => (_configuration.GetValue("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); + private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi"); + + public async Task PageAsync( + int pageNo, + int pageSize, + string? materialCode = null, + string? materialName = null, + string? erpCode = null, + string? majorCategoryId = null, + string? minorCategoryId = null, + CancellationToken ct = default) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["pageNo"] = pageNo.ToString(); + query["pageSize"] = pageSize.ToString(); + if (!string.IsNullOrWhiteSpace(materialCode)) query["materialCode"] = materialCode; + if (!string.IsNullOrWhiteSpace(materialName)) query["materialName"] = materialName; + if (!string.IsNullOrWhiteSpace(erpCode)) query["erpCode"] = erpCode; + if (!string.IsNullOrWhiteSpace(majorCategoryId)) query["majorCategoryId"] = majorCategoryId; + if (!string.IsNullOrWhiteSpace(minorCategoryId)) query["minorCategoryId"] = minorCategoryId; + + var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/list?{query}"; + using var client = CreateClient(); + var resp = await client.GetAsync(url, ct).ConfigureAwait(false); + resp.EnsureSuccessStatusCode(); + + var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + using var doc = JsonDocument.Parse(json); + var result = doc.RootElement.GetProperty("result"); + var records = result.GetProperty("records").Deserialize>(_jsonOpts) ?? new(); + var total = result.TryGetProperty("total", out var totalEl) ? totalEl.GetInt64() : records.Count; + return new MixerMaterialPageResult(records, total, pageNo, pageSize); + } + + public async Task GetByIdAsync(string id, CancellationToken ct = default) + { + var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/queryById?id={Uri.EscapeDataString(id)}"; + using var client = CreateClient(); + var resp = await client.GetAsync(url, ct).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) return null; + + var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("result", out var resultEl)) return null; + return resultEl.Deserialize(_jsonOpts); + } + + public Task AddAsync(MesMixerMaterial material, CancellationToken ct = default) + => PostJsonAsync($"{BaseUrl}/mes/material/mixerMaterial/anon/add", material, ct); + + public Task EditAsync(MesMixerMaterial material, CancellationToken ct = default) + => PostJsonAsync($"{BaseUrl}/mes/material/mixerMaterial/anon/edit", material, ct); + + public async Task DeleteAsync(string id, CancellationToken ct = default) + { + var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/delete?id={Uri.EscapeDataString(id)}"; + using var client = CreateClient(); + var resp = await client.DeleteAsync(url, ct).ConfigureAwait(false); + return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false); + } + + public async Task DeleteBatchAsync(string ids, CancellationToken ct = default) + { + var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/deleteBatch?ids={Uri.EscapeDataString(ids)}"; + using var client = CreateClient(); + var resp = await client.DeleteAsync(url, ct).ConfigureAwait(false); + return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false); + } + + public async Task>> GetMajorCategoryOptionsAsync(CancellationToken ct = default) + { + var url = $"{BaseUrl}/sys/category/anon/loadTreeData?pcode=XSLMES_MATERIAL"; + using var client = CreateClient(); + var resp = await client.GetAsync(url, ct).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) return []; + var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + using var doc = JsonDocument.Parse(json); + var arr = doc.RootElement.TryGetProperty("result", out var resultEl) ? resultEl : doc.RootElement; + return ParseTreeOptions(arr); + } + + public async Task>> GetMinorCategoryOptionsAsync(string majorCategoryId, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(majorCategoryId)) return []; + var url = $"{BaseUrl}/sys/category/anon/loadTreeData?pid={Uri.EscapeDataString(majorCategoryId)}"; + using var client = CreateClient(); + var resp = await client.GetAsync(url, ct).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) return []; + var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + using var doc = JsonDocument.Parse(json); + var arr = doc.RootElement.TryGetProperty("result", out var resultEl) ? resultEl : doc.RootElement; + return ParseTreeOptions(arr); + } + + private static List> ParseTreeOptions(JsonElement root) + { + var list = new List>(); + if (root.ValueKind != JsonValueKind.Array) return list; + foreach (var item in root.EnumerateArray()) + { + var text = item.TryGetProperty("title", out var titleEl) ? titleEl.GetString() : null; + var key = item.TryGetProperty("key", out var keyEl) ? keyEl.GetString() : null; + if (!string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(key)) + { + list.Add(new KeyValuePair(text!, key!)); + } + } + return list; + } + + private async Task PostJsonAsync(string url, object body, CancellationToken ct) + { + var content = new StringContent(JsonSerializer.Serialize(body, _jsonOpts), Encoding.UTF8, "application/json"); + using var client = CreateClient(); + var resp = await client.PostAsync(url, content, ct).ConfigureAwait(false); + return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false); + } + + private static async Task IsSuccessResultAsync(HttpResponseMessage resp, CancellationToken ct) + { + try + { + var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + using var doc = JsonDocument.Parse(json); + if (doc.RootElement.TryGetProperty("code", out var code)) + { + return code.GetInt32() == 200; + } + if (doc.RootElement.TryGetProperty("success", out var success)) + { + return success.GetBoolean(); + } + return true; + } + catch + { + return true; + } + } + + private sealed class NullableDateTimeJsonConverter : JsonConverter + { + private static readonly string[] SupportedFormats = + [ + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.fff", + "yyyy-MM-ddTHH:mm:ss", + "yyyy-MM-ddTHH:mm:ss.fff", + "yyyy-MM-ddTHH:mm:ssZ", + "yyyy-MM-ddTHH:mm:ss.fffZ" + ]; + + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) return null; + if (reader.TokenType == JsonTokenType.String) + { + var raw = reader.GetString(); + if (string.IsNullOrWhiteSpace(raw)) return null; + if (DateTime.TryParseExact(raw, SupportedFormats, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AssumeLocal, out var exact)) + { + return exact; + } + if (DateTime.TryParse(raw, out var fallback)) + { + return fallback; + } + } + throw new JsonException($"无法将 JSON 值转换为 DateTime?,token={reader.TokenType}"); + } + + public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) + { + if (value.HasValue) writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss")); + else writer.WriteNullValue(); + } + } +} + diff --git a/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialSyncCoordinator.cs b/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialSyncCoordinator.cs new file mode 100644 index 0000000..cdea6fe --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialSyncCoordinator.cs @@ -0,0 +1,63 @@ +using Prism.Events; +using System.Text.Json; +using YY.Admin.Core; +using YY.Admin.Core.Events; +using YY.Admin.Core.Services; + +namespace YY.Admin.Services.Service.MixerMaterial; + +public class MixerMaterialSyncCoordinator : ISingletonDependency +{ + private readonly IEventAggregator _eventAggregator; + private readonly ILoggerService _logger; + + public MixerMaterialSyncCoordinator(IEventAggregator eventAggregator, ILoggerService logger) + { + _eventAggregator = eventAggregator; + _logger = logger; + _eventAggregator.GetEvent() + .Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread); + _eventAggregator.GetEvent() + .Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread); + _logger.Information("[密炼物料推送] MixerMaterialSyncCoordinator 已启动"); + } + + private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload) + { + if (!payload.IsOnline) return; + _eventAggregator.GetEvent() + .Publish(new MixerMaterialChangedPayload { Action = "reconnect" }); + } + + private void OnRemoteCommand(RemoteCommandPayload payload) + { + try + { + var json = payload.CommandJson ?? string.Empty; + if (string.IsNullOrWhiteSpace(json)) return; + + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("cmd", out var cmdEl)) return; + if (!cmdEl.GetString().Equals("MIXER_MATERIAL_CHANGED", StringComparison.OrdinalIgnoreCase)) return; + + doc.RootElement.TryGetProperty("action", out var actionEl); + doc.RootElement.TryGetProperty("mixerMaterialId", out var idEl); + if (idEl.ValueKind != JsonValueKind.String && doc.RootElement.TryGetProperty("id", out var id2El)) + { + idEl = id2El; + } + + var changed = new MixerMaterialChangedPayload + { + Action = actionEl.GetString() ?? string.Empty, + MixerMaterialId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null + }; + _eventAggregator.GetEvent().Publish(changed); + } + catch (Exception ex) + { + _logger.Warning($"[密炼物料推送] 处理STOMP命令失败:{ex.Message}"); + } + } +} + diff --git a/yy-admin-master/YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs b/yy-admin-master/YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs index bdea801..565f2f3 100644 --- a/yy-admin-master/YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs +++ b/yy-admin-master/YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs @@ -138,6 +138,10 @@ public class StompWebSocketService : ISignalRService await SendFrameAsync( BuildSubscribeFrame("sub-mes-weight-records", "/topic/sync/mes-weight-records"), cancellationToken).ConfigureAwait(false); + // 密炼物料数据变更:订阅 /topic/sync/mes-mixer-materials + await SendFrameAsync( + BuildSubscribeFrame("sub-mes-mixer-material", "/topic/sync/mes-mixer-materials"), + cancellationToken).ConfigureAwait(false); // 订阅服务端 PONG 回复(应用层假在线检测) await SendFrameAsync( diff --git a/yy-admin-master/YY.Admin/Module/NavigationExtensions.cs b/yy-admin-master/YY.Admin/Module/NavigationExtensions.cs index 9e76d49..cd3b20f 100644 --- a/yy-admin-master/YY.Admin/Module/NavigationExtensions.cs +++ b/yy-admin-master/YY.Admin/Module/NavigationExtensions.cs @@ -9,6 +9,7 @@ using YY.Admin.Views.Dialogs; using YY.Admin.Views.SysManage; using YY.Admin.Views.Customer; using YY.Admin.Views.Supplier; +using YY.Admin.Views.MixerMaterial; using YY.Admin.ViewModels.Vehicle; using YY.Admin.Views.Vehicle; using YY.Admin.Views.WeightRecord; @@ -52,6 +53,7 @@ namespace YY.Admin containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); @@ -66,6 +68,8 @@ namespace YY.Admin containerRegistry.RegisterForNavigation(); // 地磅称重操作(大页面操作台) containerRegistry.RegisterForNavigation(); + // 密炼物料信息 + containerRegistry.RegisterForNavigation(); } } public class DialogWindow : Window, IDialogWindow diff --git a/yy-admin-master/YY.Admin/Module/SyncModule.cs b/yy-admin-master/YY.Admin/Module/SyncModule.cs index 2311dfa..9f59519 100644 --- a/yy-admin-master/YY.Admin/Module/SyncModule.cs +++ b/yy-admin-master/YY.Admin/Module/SyncModule.cs @@ -15,6 +15,7 @@ using YY.Admin.Infrastructure.Storage; using YY.Admin.Infrastructure.Sync; using YY.Admin.Services.Service.Customer; using YY.Admin.Services.Service.Supplier; +using YY.Admin.Services.Service.MixerMaterial; using YY.Admin.Services.Service.Vehicle; using YY.Admin.Services.Service.WeightRecord; @@ -46,6 +47,9 @@ public class SyncModule : IModule // 磅单管理:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); + // 密炼物料信息:API直连 + STOMP实时通知 + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); var serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(); @@ -98,6 +102,8 @@ public class SyncModule : IModule _ = containerProvider.Resolve(); // 强制实例化磅单同步协调器 _ = containerProvider.Resolve(); + // 强制实例化密炼物料同步协调器 + _ = containerProvider.Resolve(); } private static IAsyncPolicy GetRetryPolicy() diff --git a/yy-admin-master/YY.Admin/ViewModels/Control/MenuTreeViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/Control/MenuTreeViewModel.cs index 4ae2f29..ce8666b 100644 --- a/yy-admin-master/YY.Admin/ViewModels/Control/MenuTreeViewModel.cs +++ b/yy-admin-master/YY.Admin/ViewModels/Control/MenuTreeViewModel.cs @@ -54,6 +54,13 @@ namespace YY.Admin.ViewModels.Control ["/platform/dict"] = "DataDictionaryManagementView", ["sysDict"] = "DataDictionaryManagementView", + // 已实现页面:分类字典 + ["CategoryDictionaryManagementView"] = "CategoryDictionaryManagementView", + ["/system/category"] = "CategoryDictionaryManagementView", + ["/system/category/index"] = "CategoryDictionaryManagementView", + ["/platform/category"] = "CategoryDictionaryManagementView", + ["sysCategory"] = "CategoryDictionaryManagementView", + // 已实现页面:角色管理 ["RoleManagementView"] = "RoleManagementView", ["/system/role"] = "RoleManagementView", @@ -108,7 +115,12 @@ namespace YY.Admin.ViewModels.Control // 已实现页面:地磅称重操作 ["WeightRecordOperationView"] = "WeightRecordOperationView", ["/xslmes/weightRecordOperation"] = "WeightRecordOperationView", - ["weightRecordOperation"] = "WeightRecordOperationView" + ["weightRecordOperation"] = "WeightRecordOperationView", + + // 已实现页面:密炼物料信息 + ["MixerMaterialListView"] = "MixerMaterialListView", + ["/xslmes/mesMixerMaterial"] = "MixerMaterialListView", + ["mesMixerMaterial"] = "MixerMaterialListView" }; private MenuItem? _selectedMenuItem; diff --git a/yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialEditDialogViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialEditDialogViewModel.cs new file mode 100644 index 0000000..684c636 --- /dev/null +++ b/yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialEditDialogViewModel.cs @@ -0,0 +1,186 @@ +using HandyControl.Controls; +using HandyControl.Tools.Extension; +using System.Collections.ObjectModel; +using YY.Admin.Core; +using YY.Admin.Core.Entity; +using YY.Admin.Core.Services; + +namespace YY.Admin.ViewModels.MixerMaterial; + +public class MixerMaterialEditDialogViewModel : BaseViewModel, IDialogResultable +{ + private readonly IMixerMaterialService _mixerMaterialService; + + private MesMixerMaterial? _material; + public MesMixerMaterial? Material + { + get => _material; + set => SetProperty(ref _material, value); + } + + public bool IsAddMode => string.IsNullOrWhiteSpace(Material?.Id); + public string DialogTitle => IsAddMode ? "新增密炼物料" : "编辑密炼物料"; + + public ObservableCollection> MajorCategoryOptions { get; } = new(); + public ObservableCollection> MinorCategoryOptions { get; } = new(); + public ObservableCollection> FeedManageStatusOptions { get; } = + [ + new("在投管", 1), + new("未投管", 0) + ]; + public ObservableCollection> UseStatusOptions { get; } = + [ + new("使用中", 1), + new("停用", 0) + ]; + + private bool _result; + public bool Result { get => _result; set => SetProperty(ref _result, value); } + public Action? CloseAction { get; set; } + + public DelegateCommand SaveCommand { get; } + public DelegateCommand CancelCommand { get; } + public DelegateCommand MajorCategoryChangedCommand { get; } + + public MixerMaterialEditDialogViewModel( + IMixerMaterialService mixerMaterialService, + IContainerExtension container, + IRegionManager regionManager) : base(container, regionManager) + { + _mixerMaterialService = mixerMaterialService; + SaveCommand = new DelegateCommand(async () => await SaveAsync()); + CancelCommand = new DelegateCommand(() => CloseAction?.Invoke()); + MajorCategoryChangedCommand = new DelegateCommand(async () => await OnMajorCategoryChangedAsync()); + _ = LoadMajorCategoryOptionsAsync(); + } + + public void InitializeForAdd() + { + Material = new MesMixerMaterial + { + FeedManageStatus = 1, + UseStatus = 1 + }; + MinorCategoryOptions.Clear(); + RaisePropertyChanged(nameof(IsAddMode)); + RaisePropertyChanged(nameof(DialogTitle)); + } + + public void InitializeForEdit(MesMixerMaterial material) + { + Material = new MesMixerMaterial + { + Id = material.Id, + MaterialCode = material.MaterialCode, + MaterialName = material.MaterialName, + ErpCode = material.ErpCode, + MajorCategoryId = material.MajorCategoryId, + MinorCategoryId = material.MinorCategoryId, + MaterialDesc = material.MaterialDesc, + AliasName = material.AliasName, + FeedManageStatus = material.FeedManageStatus, + UseStatus = material.UseStatus, + SpecificGravity = material.SpecificGravity, + ShelfLifeDays = material.ShelfLifeDays, + MinBakeMinutes = material.MinBakeMinutes, + TotalSafetyStockKg = material.TotalSafetyStockKg, + QualifiedSafetyStockKg = material.QualifiedSafetyStockKg, + Remark = material.Remark, + TenantId = material.TenantId, + }; + _ = LoadMinorCategoryOptionsAsync(Material.MajorCategoryId); + RaisePropertyChanged(nameof(IsAddMode)); + RaisePropertyChanged(nameof(DialogTitle)); + } + + private async Task LoadMajorCategoryOptionsAsync() + { + try + { + MajorCategoryOptions.Clear(); + var options = await _mixerMaterialService.GetMajorCategoryOptionsAsync(); + foreach (var item in options) + { + MajorCategoryOptions.Add(item); + } + } + catch + { + // ignore + } + } + + private async Task LoadMinorCategoryOptionsAsync(string? majorCategoryId) + { + MinorCategoryOptions.Clear(); + if (string.IsNullOrWhiteSpace(majorCategoryId)) + { + return; + } + try + { + var options = await _mixerMaterialService.GetMinorCategoryOptionsAsync(majorCategoryId); + foreach (var item in options) + { + MinorCategoryOptions.Add(item); + } + } + catch + { + // ignore + } + } + + private async Task OnMajorCategoryChangedAsync() + { + if (Material == null) return; + Material.MinorCategoryId = null; + RaisePropertyChanged(nameof(Material)); + await LoadMinorCategoryOptionsAsync(Material.MajorCategoryId); + } + + private async Task SaveAsync() + { + if (Material == null) return; + if (string.IsNullOrWhiteSpace(Material.MaterialCode)) + { + HandyControl.Controls.MessageBox.Warning("物料编码不能为空!"); + return; + } + if (string.IsNullOrWhiteSpace(Material.MaterialName)) + { + HandyControl.Controls.MessageBox.Warning("物料名称不能为空!"); + return; + } + if (string.IsNullOrWhiteSpace(Material.MajorCategoryId)) + { + HandyControl.Controls.MessageBox.Warning("物料大类不能为空!"); + return; + } + if (string.IsNullOrWhiteSpace(Material.MinorCategoryId)) + { + HandyControl.Controls.MessageBox.Warning("物料小类不能为空!"); + return; + } + + try + { + bool ok = IsAddMode + ? await _mixerMaterialService.AddAsync(Material) + : await _mixerMaterialService.EditAsync(Material); + if (!ok) + { + HandyControl.Controls.MessageBox.Error($"{(IsAddMode ? "新增" : "编辑")}密炼物料失败!"); + return; + } + + Result = true; + CloseAction?.Invoke(); + } + catch (Exception ex) + { + HandyControl.Controls.MessageBox.Error($"操作失败:{ex.Message}"); + } + } +} + diff --git a/yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialListViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialListViewModel.cs new file mode 100644 index 0000000..af16fbf --- /dev/null +++ b/yy-admin-master/YY.Admin/ViewModels/MixerMaterial/MixerMaterialListViewModel.cs @@ -0,0 +1,232 @@ +using HandyControl.Controls; +using HandyControl.Tools.Extension; +using Prism.Events; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Windows; +using YY.Admin.Core; +using YY.Admin.Core.Entity; +using YY.Admin.Core.Events; +using YY.Admin.Core.Helper; +using YY.Admin.Core.Services; +using YY.Admin.Views.MixerMaterial; + +namespace YY.Admin.ViewModels.MixerMaterial; + +public class MixerMaterialListViewModel : BaseViewModel +{ + private readonly IMixerMaterialService _mixerMaterialService; + private SubscriptionToken? _materialChangedToken; + + private ObservableCollection _materials = new(); + public ObservableCollection Materials + { + get => _materials; + set => SetProperty(ref _materials, value); + } + + private long _total; + public long Total { get => _total; set => SetProperty(ref _total, value); } + + private int _pageNo = 1; + public int PageNo { get => _pageNo; set => SetProperty(ref _pageNo, value); } + + private int _pageSize = 20; + public int PageSize { get => _pageSize; set => SetProperty(ref _pageSize, value); } + + private string? _filterMaterialCode; + public string? FilterMaterialCode { get => _filterMaterialCode; set => SetProperty(ref _filterMaterialCode, value); } + + private string? _filterMaterialName; + public string? FilterMaterialName { get => _filterMaterialName; set => SetProperty(ref _filterMaterialName, value); } + + private string? _filterErpCode; + public string? FilterErpCode { get => _filterErpCode; set => SetProperty(ref _filterErpCode, value); } + + private string? _filterMajorCategoryId; + public string? FilterMajorCategoryId + { + get => _filterMajorCategoryId; + set + { + if (SetProperty(ref _filterMajorCategoryId, value)) + { + _ = OnMajorCategoryChangedAsync(); + } + } + } + + private string? _filterMinorCategoryId; + public string? FilterMinorCategoryId { get => _filterMinorCategoryId; set => SetProperty(ref _filterMinorCategoryId, value); } + + public ObservableCollection> MajorCategoryOptions { get; } = new(); + public ObservableCollection> MinorCategoryOptions { get; } = new(); + + public DelegateCommand SearchCommand { get; } + public DelegateCommand ResetCommand { get; } + public DelegateCommand AddCommand { get; } + public DelegateCommand EditCommand { get; } + public DelegateCommand DeleteCommand { get; } + public DelegateCommand PrevPageCommand { get; } + public DelegateCommand NextPageCommand { get; } + + public MixerMaterialListViewModel( + IMixerMaterialService mixerMaterialService, + IContainerExtension container, + IRegionManager regionManager) : base(container, regionManager) + { + _mixerMaterialService = mixerMaterialService; + + SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); }); + ResetCommand = new DelegateCommand(async () => + { + FilterMaterialCode = null; + FilterMaterialName = null; + FilterErpCode = null; + FilterMajorCategoryId = null; + FilterMinorCategoryId = null; + PageNo = 1; + await LoadMinorCategoryOptionsAsync(); + await LoadAsync(); + }); + AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync()); + EditCommand = new DelegateCommand(async m => await ShowEditDialogAsync(m)); + DeleteCommand = new DelegateCommand(async m => await DeleteAsync(m)); + PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } }); + NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } }); + + _materialChangedToken = _eventAggregator + .GetEvent() + .Subscribe(async _ => await LoadAsync(), ThreadOption.UIThread); + + _ = InitializeAsync(); + } + + private async Task InitializeAsync() + { + try + { + await LoadMajorCategoryOptionsAsync(); + await LoadMinorCategoryOptionsAsync(); + await UIHelper.WaitForRenderAsync(); + await LoadAsync(); + } + catch (Exception ex) + { + Debug.WriteLine($"密炼物料列表初始化失败: {ex.Message}"); + } + } + + private async Task LoadMajorCategoryOptionsAsync() + { + MajorCategoryOptions.Clear(); + var options = await _mixerMaterialService.GetMajorCategoryOptionsAsync(); + MajorCategoryOptions.Add(new KeyValuePair("全部", "")); + foreach (var item in options) + { + MajorCategoryOptions.Add(item); + } + } + + private async Task LoadMinorCategoryOptionsAsync() + { + MinorCategoryOptions.Clear(); + MinorCategoryOptions.Add(new KeyValuePair("全部", "")); + if (string.IsNullOrWhiteSpace(FilterMajorCategoryId)) + { + return; + } + var options = await _mixerMaterialService.GetMinorCategoryOptionsAsync(FilterMajorCategoryId!); + foreach (var item in options) + { + MinorCategoryOptions.Add(item); + } + } + + private async Task OnMajorCategoryChangedAsync() + { + FilterMinorCategoryId = null; + await LoadMinorCategoryOptionsAsync(); + } + + public async Task LoadAsync() + { + try + { + IsLoading = true; + var result = await _mixerMaterialService.PageAsync( + PageNo, PageSize, FilterMaterialCode, FilterMaterialName, FilterErpCode, FilterMajorCategoryId, FilterMinorCategoryId); + Materials = new ObservableCollection(result.Records); + Total = result.Total; + } + catch (Exception ex) + { + Growl.Error($"加载密炼物料列表失败:{ex.Message}"); + } + finally + { + IsLoading = false; + } + } + + private async Task ShowAddDialogAsync() + { + try + { + var result = await HandyControl.Controls.Dialog.Show() + .Initialize(vm => vm.InitializeForAdd()) + .GetResultAsync(); + if (result) await LoadAsync(); + } + catch (Exception ex) + { + Growl.Error($"打开新增对话框失败:{ex.Message}"); + } + } + + private async Task ShowEditDialogAsync(MesMixerMaterial material) + { + if (material == null) return; + try + { + var result = await HandyControl.Controls.Dialog.Show() + .Initialize(vm => vm.InitializeForEdit(material)) + .GetResultAsync(); + if (result) await LoadAsync(); + } + catch (Exception ex) + { + Growl.Error($"打开编辑对话框失败:{ex.Message}"); + } + } + + private async Task DeleteAsync(MesMixerMaterial material) + { + if (material?.Id == null) return; + var confirm = System.Windows.MessageBox.Show($"确定删除物料 {material.MaterialName}?此操作不可恢复!", "确认删除", + MessageBoxButton.OKCancel, MessageBoxImage.Question); + if (confirm != System.Windows.MessageBoxResult.OK) return; + + var ok = await _mixerMaterialService.DeleteAsync(material.Id); + if (ok) + { + Growl.Success("删除成功!"); + await LoadAsync(); + } + else + { + Growl.Error("删除失败!"); + } + } + + protected override void CleanUp() + { + base.CleanUp(); + if (_materialChangedToken != null) + { + _eventAggregator.GetEvent().Unsubscribe(_materialChangedToken); + _materialChangedToken = null; + } + } +} + diff --git a/yy-admin-master/YY.Admin/ViewModels/SysManage/CategoryDictionaryManagementViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/SysManage/CategoryDictionaryManagementViewModel.cs new file mode 100644 index 0000000..f023d3d --- /dev/null +++ b/yy-admin-master/YY.Admin/ViewModels/SysManage/CategoryDictionaryManagementViewModel.cs @@ -0,0 +1,143 @@ +using HandyControl.Controls; +using System.Collections.ObjectModel; +using YY.Admin.Core; +using YY.Admin.Core.Helper; +using YY.Admin.Services.Service.Category; +using YY.Admin.Services.Service.Category.Dto; +using YY.Admin.ViewModels.Control; + +namespace YY.Admin.ViewModels.SysManage; + +public class CategoryDictionaryManagementViewModel : BaseViewModel +{ + private readonly IJeecgCategorySyncService _categorySyncService; + + private PaginationDataGridViewModel _paginationDataGridViewModel; + public PaginationDataGridViewModel PaginationDataGridViewModel + { + get => _paginationDataGridViewModel; + set => SetProperty(ref _paginationDataGridViewModel, value); + } + + private PageJeecgCategoryItemInput _input; + public PageJeecgCategoryItemInput Input + { + get => _input; + set => SetProperty(ref _input, value); + } + + public DelegateCommand SearchCommand { get; } + public DelegateCommand ResetCommand { get; } + public DelegateCommand SyncCommand { get; } + public ObservableCollection TreeNodes { get; } = []; + + private CategoryTreeNode? _selectedTreeNode; + public CategoryTreeNode? SelectedTreeNode + { + get => _selectedTreeNode; + set => SetProperty(ref _selectedTreeNode, value); + } + + public CategoryDictionaryManagementViewModel( + IJeecgCategorySyncService categorySyncService, + IContainerExtension container, + IRegionManager regionManager) : base(container, regionManager) + { + _categorySyncService = categorySyncService; + _paginationDataGridViewModel = new PaginationDataGridViewModel(FetchAsync); + _input = new PageJeecgCategoryItemInput(); + + SearchCommand = new DelegateCommand(async () => await SearchAsync()); + ResetCommand = new DelegateCommand(async () => await ResetAsync()); + SyncCommand = new DelegateCommand(async () => await SyncAsync()); + + _ = InitializeAsync(); + } + + private async Task InitializeAsync() + { + await LoadTreeAsync(); + await UIHelper.WaitForRenderAsync(); + await PaginationDataGridViewModel.LoadDataAsync(); + } + + private async Task<(IEnumerable data, int totalCount)> FetchAsync() + { + Input.Page = PaginationDataGridViewModel.PageIndex; + Input.PageSize = PaginationDataGridViewModel.DataCountPerPage; + var result = await _categorySyncService.PageAsync(Input); + return (result.Items, result.Total); + } + + private async Task SearchAsync() + { + PaginationDataGridViewModel.PageIndex = 1; + await PaginationDataGridViewModel.LoadDataAsync(); + } + + private async Task ResetAsync() + { + Input = new PageJeecgCategoryItemInput(); + SelectedTreeNode = null; + await UIHelper.WaitForRenderAsync(); + await LoadTreeAsync(); + await SearchAsync(); + } + + private async Task SyncAsync() + { + var count = await _categorySyncService.SyncFromJeecgAsync(); + if (count > 0) + { + Growl.Success($"同步完成,共处理 {count} 条分类字典数据"); + } + else + { + Growl.Warning("未同步到分类字典,请确认后端可访问"); + } + await LoadTreeAsync(); + await SearchAsync(); + } + + public async Task OnTreeSelectedAsync(CategoryTreeNode? node) + { + SelectedTreeNode = node; + Input.Pid = node?.Id; + await SearchAsync(); + } + + private async Task LoadTreeAsync() + { + TreeNodes.Clear(); + var tree = await _categorySyncService.LoadTreeAsync(); + foreach (var item in tree) + { + TreeNodes.Add(MapTreeNode(item)); + } + } + + private static CategoryTreeNode MapTreeNode(JeecgCategoryTreeNodeDto dto) + { + var node = new CategoryTreeNode + { + Id = dto.Id, + Name = dto.Name ?? dto.Code ?? dto.Id, + Code = dto.Code + }; + foreach (var child in dto.Children) + { + node.Children.Add(MapTreeNode(child)); + } + return node; + } + + public class CategoryTreeNode + { + public string Id { get; set; } = string.Empty; + public string? Name { get; set; } + public string? Code { get; set; } + public ObservableCollection Children { get; set; } = []; + public string DisplayName => string.IsNullOrWhiteSpace(Code) ? (Name ?? Id) : $"{Name} ({Code})"; + } +} + diff --git a/yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialEditDialogView.xaml b/yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialEditDialogView.xaml new file mode 100644 index 0000000..f16fd9a --- /dev/null +++ b/yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialEditDialogView.xaml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml.cs b/yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml.cs new file mode 100644 index 0000000..95d8717 --- /dev/null +++ b/yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml.cs @@ -0,0 +1,26 @@ +using System.Windows.Controls; +using System.Windows; +using YY.Admin.ViewModels.SysManage; + +namespace YY.Admin.Views.SysManage +{ + /// + /// CategoryDictionaryManagementView.xaml 的交互逻辑 + /// + public partial class CategoryDictionaryManagementView : UserControl + { + public CategoryDictionaryManagementView() + { + InitializeComponent(); + } + + private async void CategoryTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (DataContext is CategoryDictionaryManagementViewModel vm) + { + await vm.OnTreeSelectedAsync(e.NewValue as CategoryDictionaryManagementViewModel.CategoryTreeNode); + } + } + } +} +