场景

该设计多适用于MESERPWMS 等管理类型的项目。

在做管理类型项目的时候,前端经常会使用到下拉框,比如:设备,工厂等等。下拉组件中一般只需要他们的ID,Name属性,而这些数据都创建于其他模块并存储在数据库中。

如图:

写一个设备的下拉组件的数据需要通过请求后端去获取,如:localhost:5555/api/services/app/Resource/GetNdoCombox,然后携带参数filterText

写一个工厂的下拉组件也是同样的要去请求,如:localhost:5555/api/services/app/Factory/GetNdoCombox

如果你的后端代码足够规范,那么就会像我写的这样,每个需要有下拉需求的实体建模都有一个GetNdoCombox的接口。

问题点

为了代码的复用性,你一定不会每个用到设备下拉的地方都去写select标签,然后请求数据再渲染,而是会封装一个设备下拉框和一个工厂下拉框。于是便出现了一些问题:

前端下拉组件除了请求的接口不一样,placeholder不一样,其他的代码都是尽数相同的。如果有下拉需求的地方多了,就会出现很多个xx下拉组件,如何优化?如果你有此类相同需求的问题时值得参考这个方案。

方案

思路

在后端写一个接口做统一查询,前端做一个统一的下拉组件,请求后端的统一查询接口,前端传递标识性的参数给后端,后端通过参数自动匹配并查询前端所需要的值。那么重点就在后端如何实现这样的匹配逻辑呢?

核心实现

我的实践架构:.NET CORE + VUE

前端

前端就是写个组件去请求接口,传递参数,这里就不细说了

后端

先粗浅的介绍需要准备的东西:

  1. 自定义服务描述类,包含:服务接口,服务实现,泛型实体类型,生命周期
  2. 定义单例存储器:定义Ndo字典,用于存储对应的服务描述,以实体完全限定名为key,定义存和取的方法
  3. 通过反射获取指定程序集中实现了IComboxQuery接口并且必须实现了IComboxQuery<>的下拉服务的服务描述,再将其注入到IOC容器中,并同时在存储器的字典中添加映射关系。
  4. 统一获取服务的Hub:从存储器中根据实体名称获取对应的服务描述,再根据自定义服务描述类中的服务接口类型从IOC容器中获取实现的IComboxQuery

详细说明

Ndo:其实就是普通的实体的意思。

首先通过提供的IComboxQuery接口和IComboxQuery接口约束ControllerService必须实现GetNdoCombox方法。也就是说所有需要下拉的实体的服务都要实现IComboxQuery。(IComboxQuery继承于IComboxQuery)

程序启动时利用反射将实现了IComboxQuery并且实现了IComboxQuery的服务添加到IOC容器和存储器的字典中去,以实体完全限定名为key,value为自定义的服务描述类。

定义统一获取服务的Hub,从存储器中根据实体名称获取对应的服务描述,再根据自定义服务描述类中的服务接口类型从IOC容器中获取实现IComboxQuery的ControllerService,然后调用GetNdoCombox方法

定义统一的ControllerService,随便定义一个方法定义需要的参数为EntityNamefilterText,方法中使用统一获取服务的Hub,通过参数EntityName获取实际实现IComboxQuery的对象,然后调用GetNdoCombox返回数据。

核心的查询逻辑仍然是由服务自己实现的,因为不同的实体,过滤条件的字段名不一样,Hub只负责调用方法得到结果,不关心具体实现。

代码

返回的数据NdoDto

public class NdoDto
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual DateTime CreationTime { get; set; }
}

公共接口查询的参数类

/// <summary>
/// 下拉框查询的模糊搜索输入
/// </summary>
public class GetQueryFilterInput
{
/// <summary>
/// 类型全名称,不涉及业务,用于区分本次请求是哪个实体的接口
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// 模糊查询
/// </summary>
public virtual string FilterText { get; set; }
}

统一规范的公共接口

/// <summary>
/// 下拉查询
/// </summary>
public interface IComboxQuery
{
Task<List<NdoDto>> GetCombox(GetQueryFilterInput input);
} /// <summary>
/// 下拉查询
/// </summary>
public interface IComboxQuery<TEntity> : IComboxQuery
{ }

自定义的服务映射描述类

/// <summary>
/// 服务映射描述
/// </summary>
public class SampleServiceDescriptor
{
/// <summary>
/// 瞬时依赖注入服务接口
/// </summary>
public static Type TransientInterface { get; } = typeof(ITransientDependency);
/// <summary>
/// 单例依赖注入服务接口
/// </summary>
public static Type SingletonInterface { get; } = typeof(ISingletonDependency); /// <summary>
/// 服务类型 接口
/// </summary>
public virtual Type ServiceType { get; } /// <summary>
/// 实现类型
/// </summary>
public virtual Type ImplementationType { get; } /// <summary>
/// 建模实体类型
/// </summary>
public virtual Type EntityType { get; } /// <summary>
/// 服务依赖注入生命周期
/// </summary>
public virtual ServiceLifetime ServiceLifetime { get; } /// <summary>
/// 依赖注入服务
/// </summary>
/// <param name="serviceType">服务类型</param>
/// <param name="implementationType">实现类型</param>
public SampleServiceDescriptor(Type serviceType, Type implementationType)
{
this.ServiceType = serviceType;
this.ImplementationType = implementationType; if (serviceType != null && serviceType.GenericTypeArguments.Length > 0)
{
// 获取IComboxQuery<>中的泛型参数TEntity
this.EntityType = serviceType.GenericTypeArguments[0];
} if (SingletonInterface.IsAssignableFrom(this.ImplementationType))
{
this.ServiceLifetime = ServiceLifetime.Singleton;
}
else
{
this.ServiceLifetime = ServiceLifetime.Transient;
}
} /// <summary>
/// 转换为 <see cref="ServiceDescriptor"/>
/// </summary>
/// <returns></returns>
public ServiceDescriptor ToServiceDescriptor()
{
return new ServiceDescriptor(this.ServiceType, this.ImplementationType, this.ServiceLifetime);
}
}

程序启动时的扫描器(反射获取实现了接口的服务)

/// <summary>
/// 依赖注入服务描述器
/// </summary>
public static class SampleServiceDescriptorHelper
{
/// <summary>
/// 扫描程序集中的某个接口的实现
/// </summary>
/// <param name="interfaceType">接口</param>
/// <param name="genericInterfaceTypes">接口泛型实现</param>
/// <param name="assemblies">程序集列表</param>
/// <returns></returns>
public static IEnumerable<SampleServiceDescriptor> ScanAssembliesServices
(Type interfaceType, IEnumerable<Type> genericInterfaceTypes, params Assembly[] assemblies)
{
// 泛型接口转字典
var genericInterfaceTypeDict = new Dictionary<Type, bool>();
foreach (var item in genericInterfaceTypes)
{
genericInterfaceTypeDict[item] = true;
} // 遍历程序集中所有的符合条件的类型
foreach (var assembly in assemblies)
{
var services = assembly.GetTypes()
.Where(o => interfaceType.IsAssignableFrom(o)
&& o.IsPublic
&& !o.IsInterface
&& !o.IsAbstract
)
.Select(o =>
{
// 筛选某个接口
var entityInterfaceType = o.GetInterfaces()
.Where(x =>
{
if (!x.IsGenericType)
{
return false;
}
var genericTypeDefinition = x.GetGenericTypeDefinition(); return genericInterfaceTypeDict.ContainsKey(genericTypeDefinition);
})
.FirstOrDefault();
// entityInterfaceType = IComboxQuery<> 目前只有一种
return new SampleServiceDescriptor(entityInterfaceType, o);
})
.Where(o => o != null && o.ServiceType != null); foreach (var service in services)
{
yield return service;
}
}
}
// interfaceType用于获取所有实现了IComboxQuery的类型
// genericInterfaceTypes用于筛选,必须要实现了IComboxQuery<>的类型,因为需要获取其TEntity的类型
// 如果只是实现了IComboxQuery的类型,是没有TEntity的,会导致ComboxQueryInfoStorage中无法添加映射关系
}

单例的存储器

public class ComboxQueryInfoStorage : IComboxQueryInfoStorage
{
/// <summary>
/// ModelingComboxQueryInfo 存储器实例
/// </summary>
public static IComboxQueryInfoStorage Instace { get; set; } = new ComboxQueryInfoStorage(); /// <summary>
/// 数据存储器
/// </summary>
protected readonly Dictionary<string, SampleServiceDescriptor> _Dict; protected ComboxQueryInfoStorage()
{
this._Dict = new Dictionary<string, SampleServiceDescriptor>();
} public void Add(params SampleServiceDescriptor[] comboxQueryInfos)
{
foreach (var item in comboxQueryInfos)
{
this._Dict[item.EntityType.FullName] = item;
}
} public SampleServiceDescriptor Get(string name)
{
if (this._Dict.TryGetValue(name,out var comboxQueryInfo))
{
return comboxQueryInfo;
}
throw new Exception($"found Ndo type: {name}");
}
}

统一获取服务的Hub

public class ComboxQueryHub : IComboxQueryHub
{
/// <summary>
/// 依赖注入容器
/// </summary>
protected readonly IServiceProvider _serviceProvider; /// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
public ComboxQueryHub(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
} public async Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
{
var comboxQuery = this.GetComboxQuery(input.Name);
return await comboxQuery.GetCombox(input);
} public IComboxQuery GetComboxQuery(string name)
{
var comboxQueryInfo = ComboxQueryInfoStorage.Instace.Get(name);
var comboxQuery = _serviceProvider.GetService(comboxQueryInfo.ServiceType) as IComboxQuery;
return comboxQuery;
}
}

用于将服务注册到IOC和存储器的扩展类

public static class ComboxQueryExtensions
{
/// <summary>
/// ComboxQuery 接口类型
/// </summary>
public static Type InterfaceType { get; } = typeof(IComboxQuery); /// <summary>
/// IComboxQuery 接口类型
/// </summary>
public static List<Type> GenericInterfaceTypes { get; } = new List<Type>()
{
typeof(IComboxQuery<>)
}; /// <summary>
/// 注册程序集中的 ComboxQuery
/// </summary>
/// <returns></returns> public static void RegistrarComboxQuery(this IServiceCollection services, params Assembly[] assemblies)
{
// query hub
if (!services.Any(x=>x.ServiceType == typeof(IComboxQueryHub)))
{
services.AddTransient<IComboxQueryHub, ComboxQueryHub>();
} // querys
var sampleServiceDescriptors = ScanComboxQuerys(assemblies);
foreach (var sampleServiceDescriptor in sampleServiceDescriptors)
{
if (services.Any(x => x.ServiceType == sampleServiceDescriptor.ServiceType))
{
continue;
} ComboxQueryInfoStorage.Instace.Add(sampleServiceDescriptor); if (sampleServiceDescriptor.ServiceLifetime == ServiceLifetime.Singleton)
{
services.AddSingleton(sampleServiceDescriptor.ServiceType,sampleServiceDescriptor.ImplementationType);
}
else
{
services.AddTransient(sampleServiceDescriptor.ServiceType, sampleServiceDescriptor.ImplementationType);
}
}
} /// <summary>
/// 扫描程序集中的 ComboxQuery 实现
/// </summary>
/// <param name="assemblies"></param>
/// <returns></returns>
public static IEnumerable<SampleServiceDescriptor> ScanComboxQuerys(params Assembly[] assemblies)
{
return SampleServiceDescriptorHelper.ScanAssembliesServices(
InterfaceType,
GenericInterfaceTypes,
assemblies
);
}
}

使用

人员建模:PersonController

public class PersonController : ApiControllerBase, IComboxQuery<Person>
{
[HttpPost]
public Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
{
var persons = Person.GetPeoples();
var ndos = persons.Select(x => new NdoDto
{
Id = x.Id,
Name = x.PersonName,
}).ToList();
return Task.FromResult(ndos);
}
}

设备建模:ResourceController

public class ResourceController : ApiControllerBase, IComboxQuery<Resource>
{
[HttpPost]
public Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
{
var resources = Resource.GetResources();
var ndos = resources.Select(x => new NdoDto
{
Id = x.Id,
Name = x.ResourceName
}).ToList();
return Task.FromResult(ndos);
}
}

统一查询接口:CommonBoxController

public class CommonBoxController : ApiControllerBase
{
/// <summary>
/// ioc容器
/// </summary>
protected readonly IServiceProvider _serviceProvider; public CommonBoxController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[HttpPost]
public virtual async Task<List<NdoDto>> GetNdoCombox(GetQueryFilterInput input)
{
var queryHub = this._serviceProvider.GetService<IComboxQueryHub>(); return await queryHub.GetCombox(input);
}
}

效果

单独请求PersonController

单独请求ResourceController

请求公共接口CommonBoxController

代码仓库

地址:https://gitee.com/huang-yuxiang/common-combox/tree/main/

版权声明

作者:不想只会CURD的猿某人

更多原著文章请参考:https://www.cnblogs.com/hyx1229/

适用于MES、WMS、ERP等管理系统的实体下拉框设计的更多相关文章

  1. jQuery实现的3个基础案例(仿QQ列表分组,二级联动下拉框,模拟员工信息管理系统)

    1.仿QQ列表分组 <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type&quo ...

  2. 适用于iOS6之后的苹果提供的下拉刷新

    一:iOS6.0及以后: 下拉刷新控件UIRefreshControl TableView属性:refreshControl 二:使用 - (void)colseTheTB { [self dismi ...

  3. CRM/PLM/SCM/MES与ERP的联系与区别

    企业通过专设信息机构.信息主管,配备适应现代企业管理运营要求的自动化.智能化.高技术硬件.软件.设备.设施,建立包括网络.数据库和各类信息管理系统在内的工作平台,提高企业经营管理效率的发展模式. 那么 ...

  4. MES与ERP的区别(转)

    MES和ERP有很大的不同,主要体现在以下几个方面: 1.管理的目标不同 ERP的重点在于财务,也就是从财务的角度出发来对企业的资源进行计划,相关的模块也是以财务为核心的展开,最终的管理数据也是集中到 ...

  5. 工厂里懂得mes和erp有发展吗?

    在工厂里懂得MES.ERP肯定有发展啊,现在数字化转型.智能制造正当时,ERP.MES之类的系统是刚需,只是不同工厂启动的早晚有别,使用的系统不相同而已,但知识体系.逻辑.理念等大都是相通的.比如你熟 ...

  6. MES总结:CBF.Common 文件Net下的有类型转换

    MES总结:CBF.Common 文件Net下的有类型转换. using System.Text;using System.Data;using System.ComponentModel;using ...

  7. 一个简单的适用于Vue的下拉刷新,触底加载组件

    一个简单的适用于Vue的上拉刷新,触底加载组件,没有发布npm需要时直接粘贴定制修改即可 <template> <div class="list-warp-template ...

  8. ERP仓库管理系统查询(十)

    需求:    1.根据仓库编号,获取仓库信息绑定至页面相关控件. 2.根据仓库编号,获取管理员信息绑定到页面相关控件 修改的界面: <%@ Page Language="C#" ...

  9. ERP仓库管理系统(九)

    需求分析: 1.设计库房表,至少包括两个字段,库房名称,库房所属公司的ID(在客户资质审批表中找到对应公司的ID) 2.设计增.删.改.查一套程序,其中的删除要做限制,只要有库存数据存在则不允许删除对 ...

随机推荐

  1. 五二不休息,今天也学习,从JS执行栈角度图解递归以及二叉树的前、中、后遍历的底层差异

    壹 ❀ 引 想必凡是接触过二叉树算法的同学,在刚上手那会,一定都经历过题目无从下手,甚至连题解都看不懂的痛苦.由于leetcode不方便调试,题目做错了也不知道错在哪里,最后无奈的cv答案后心里还不断 ...

  2. 从防御者视角来看APT攻击

    前言 APT防御的重要性毋庸讳言,为了帮助各位师傅在防御方面建立一个总体认识,本文会将APT防御方法分为三类,分别是:监控.检测和缓解技术,并分别进行梳理,介绍分析代表性技术.这一篇分析现有的监控技术 ...

  3. Java遇上SPL:架构优势和开发效率,一个不放过

    摘要:如果我们在Java中也提供有一套完整的结构化数据处理和计算类库,那这个问题就能得到解决:即享受到架构的优势,又不致于降低开发效率. 本文分享自华为云社区<Java结构化处理SPL>, ...

  4. Node.js + TypeScript + ESM +HotReload ( TypeScript 类型的 Node.js 项目从 CommJS 转为 ESM 的步骤)

    当前 Node.js 版本:v16.14.0 当前 TypeScript 版本:^4.6.3 步骤 安装必要的依赖 yarn add -D typescript ts-node @tsconfig/n ...

  5. 好客租房8-React基础阶段总结

    React总结 1react是构建用户组件的javascript库 2使用react是,推荐使用脚手架方式 3初始化项目命令:npx create-react-app my-app 4启动项目命令:y ...

  6. Android.mk编译App源码

    在Andriod源码环境编译APP主要考虑如何引入第三方jar包和arr包的问题,初次尝试,步步是坑,这里给出一个模板: LOCAL_PATH := $(call my-dir) include $( ...

  7. 腾讯产品快速尝鲜,蓝鲸智云社区版V6.1灰度测试开启

    这周小鲸悄悄推送了社区版V6.1(二进制部署版本,包含基础套餐.监控日志套餐),没过一天就有用户来问6.1的使用问题了.小鲸大吃一鲸,原来你还是爱我的. ![请添加图片描述](https://img- ...

  8. Win10 pycharm中显示PyTorch tensorboard图

    import numpy import numpy as np import torch import matplotlib.pyplot as plt import torch.nn as nn i ...

  9. ExtJS 布局-Absolute布局(Absolute layout)

    更新记录: 2022年5月31日 发布本篇 1.说明 使用xy配置项设置子组件在父容器中绝对位置,本质是将子组件的CSS的position设置为absolute,然后使用x和y配置项映射到CSS的to ...

  10. 轻量级多级菜单控制框架程序(C语言)

    1.前言 作为嵌入式软件开发,可能经常会使用命令行或者显示屏等设备实现人机交互的功能,功能中通常情况都包含 UI 菜单设计:很多开发人员都会有自己的菜单框架模块,防止重复造轮子,网上有很多这种菜单框架 ...