平台本身提供插件式开发,旨在将可变的功能,用户需求多变的功能,可以通过插件式开发的形式,来扩展平台的功能。
插件开发目前广泛使用的场景为过站界面,因为过站界面需要根据现场业务的逻辑发生变化,所以众多企业要求是不一样的。所以,我们可以通过一系列的插件,来定制客户需要的功能。另外,随着插件的增多,用户的可选择性也会变大,可以使平台的功能更加完善。
其实一切独立可变的功能都可以使用该系统来实现,如WMS的收料,IQA等,如各种系统的看板等功能。
2. 定制项目中如何引用
在项目中使用插件非常简单,只需要引入对应的jar包即可。
2.1. 定制项目sdk项目依赖引入
在项目父模块中引入stage父模块依赖以确定版本号信息
<dependencyManagenemnt>
......
<dependency>
<groupId>com.ags.lumosframework</groupId>
<artifactId>lumos-workflow-parent</artifactId>
<version>3.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
......
</dependencyManagenemnt>
将如下依赖加入定制项目的SDK(也就是接口定义项目)中。
<dependency>
<groupId>com.ags.lumosframework</groupId>
<artifactId>lumos-stage-sdk</artifactId>
</dependency>
2.2. 定制项目后端依赖引入
将如下依赖加入定制项目的后端服务实现中。
<dependency>
<groupId>com.ags.lumosframework</groupId>
<artifactId>lumos-stage-impl</artifactId>
</dependency>
2.3. 定制项目前端依赖引入
将如下依赖加入定制项目的后端服务实现中。
<dependency>
<groupId>com.ags.lumosframework</groupId>
<artifactId>lumos-stage-vaadin</artifactId>
</dependency>
如果定制项目的项目接口没有这么详细的划分,比如说所有的项目实现都在一个项目中,那么将上述依赖全局加入这个项目的pom文件即可。 |
2.4. 配置界面进入
一旦加入上述的依赖,重新启动程序后,你将会看到如下界面
该界面用于插件的具体展示界面,是使用者的入口。该使用者包含产线操作工,仓库管理人员,以及看板等。
如果需要创建插件,可以进入插件定义窗口,在该界面,可以进行插件的增删改查等工作,也可以预览插件,如下图所示:
2.5. 插件开发
2.5.1. 基类
插件开发相对比较简单,在引入具体的依赖后,你将会看到如下抽象类 “AbstractCustomizedComponent”,该抽象类是所有插件的基类,创建新的类,继承自该类即可。
其中,平台提供了两种类型的插件,一种为“操作中心”,一种为“看板”,不同类型的插件会在不同的界面展示。
该抽象类的插件类型默认为“操作中心”,若想改变插件类型,只要重写getStageCategory方法即可。 |
package com.ags.lumosframework.stage.web.webbase.ui.view.external;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import com.ags.lumosframework.stage.api.domain.Stage;
import com.ags.lumosframework.stage.api.enums.StageCategory;
import com.ags.lumosframework.web.vaadin.base.BaseComponent;
public abstract class AbstractCustomizedComponent extends BaseComponent {
/**
*
*/
private static final long serialVersionUID = 3780261730528717346L;
private String name;
private Map<String, ParameterInstance> instanceMap = new HashMap<>();
private boolean preview = false;
public AbstractCustomizedComponent() {
}
public boolean isPreview() {
return preview;
}
/**
* 判定当前程序是否是预览界面,如果是预览界面,将不做数据的初始化。
*
* @param preview
*/
public void setPreview(boolean preview) {
this.preview = preview;
}
@PostConstruct
private void initParameterInstance() {
List<ParameterDefinition> parameterDefinitions = CustomizedComponentHelper.getDefinitions(this.getClass());
parameterDefinitions.forEach(parameterDefinition -> {
ParameterInstance parameterInstance =
new ParameterInstance(parameterDefinition, parameterDefinition.readValue(this));
instanceMap.put(parameterDefinition.getName(), parameterInstance);
});
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 在插件页面添加插件时,该名称作为国际化的名称,展示给用户,所以一般情况下,你可以返回一个国际化的值。
* <p>
* 如在过站界面,系统将返回WorkStation.Runtime.Name。通过该Key,系统可以获得国际化的值,显示给用户
*
* @return
*/
public abstract String getDisplayValue();
@Override
public String toString() {
return getDisplayValue();
}
/**
* 获得该插件所支持的所有参数,这些参数可以提供给用户在界面做配置,然后插件开发者,可以根据这些设定,来定义该插件的行为。
*
* @return
*/
public Collection<ParameterInstance> getParameterInstances() {
return instanceMap.values();
}
/**
* internal use
*
* @param stage
*/
public void initParameter(Stage stage) {
Map<String, Object> parameters = stage.getParameters();
parameters.forEach((key, value) -> {
ParameterInstance parameterInstance = instanceMap.get(key);
if (parameterInstance != null) {
parameterInstance.setValue(value);
parameterInstance.getParameterDefinition().writeValue(this, value);
}
});
}
public void _enter() {
activateParameters();
enter();
}
/**
* 进入该插件时调用该方法,可以用于除页面之外的数据初始化操作
*/
public abstract void enter();
/**
* 当用户在界面设定了插件开发者的参数,然后在打开界面(或者预览)的时候,如果系统加载到了参数信息,将调用这个方法。
* <p>
* 一个典型的应用,比如开发者提供了一个参数,允许用户选择工位,那么一旦工位在插件配置界面被选择后,当加载完这个参数后,会调用该方法,按照用户选择的工位进行动作。
*/
public abstract void activateParameters();
public StageCategory getStageCategory() {
return StageCategory.OPERATION_CENTER;
}
}
2.5.2. 参数指定
插件支持参数功能,参数主要是由插件开发者提供,允许用户在界面设定,然后插件开发者根据插件的参数,在插件中作出不同的反应。
如下代码片段,则是定义了一个插件,并且提供了一些参数:
@UIScope
@SpringComponent
public class RuntimeWorkStationView extends AbstractCustomizedComponent implements ClickListener {
private static final long serialVersionUID = 2448127387625413009L;
@ParameterDef(nameKey = "WorkStation.Packing.Parameter.SOPShow", dataType = DataType.Boolean)
private boolean isMpiShow = true;
@ParameterDef(nameKey = "WorkStation.Packing.Parameter.DataCollectionShow", dataType = DataType.Boolean)
private boolean isDatacollectionShow = true;
@ParameterDef(nameKey = "WorkStation.Packing.Parameter.UploadPhotoShow", dataType = DataType.Boolean)
private boolean isUploadPhotoShow = true;
@ParameterDef(nameKey = "WorkStation.Packing.Parameter.QTAGShow", dataType = DataType.Boolean)
private boolean isQtagShow;
@ParameterDef(nameKey = "WorkStation.Packing.Parameter.PrintSequenceShow", dataType = DataType.List, provider = PrinterProvider.class)
private String printerName;
}
正如你所看到的那样,参数提供非常简单,只需要一个注解即可 “@ParameterDef”,具体内容如下。
package com.ags.lumosframework.stage.web.webbase.ui.view.external;
import java.lang.annotation.*;
import com.ags.lumosframework.sdk.base.entity.DataType;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ParameterDef {
/**
* 国际化的参数的名称信息
*
* @return
*/
String nameKey();
/**
* 该参数的数据类型
*
* @return
*/
DataType dataType();
/**
* 在数据类型为List的情况下,系统会自动注入一个Combo Box,列出选项供用户选择,该参数提供下拉列表里的数据集合。
*
* @return
*/
String[] options() default {};
/**
* 在数据类型为List的情况下,如果下拉列表是一个动态的值,需要实时到数据库中查询的,如列出所有的工位信息,那么可以实现一个ParameterValueProvider传入
*
* @return
*/
@SuppressWarnings("rawtypes")
Class<? extends ParameterValueProvider> provider() default ParameterValueProvider.class;
}
系统还为集成系统中的对象提供了便利,例如,可以在定义插件参数的时候,将系统中的全部工位信息提供出来,供用户选择,那么这个就不能在定义插件的时候,预先定义。因为这些事动态变化的值。 用户只需要从以下接口继承就好。
package com.ags.lumosframework.stage.web.webbase.ui.view.external;
import java.util.List;
import java.util.function.Supplier;
@FunctionalInterface
public interface ParameterValueProvider<T> extends Supplier<List<T>> {
}
如下是一个简单实现,供参考:
public class WorkStationProvider implements ParameterValueProvider<String> {
@Override
public List<String> get() {
IWorkStationService workStationService = BeanManager.getService(IWorkStationService.class);
List<WorkStation> workstation = workStationService.list(0, Integer.MAX_VALUE);
return workstation.stream().map(WorkStation::getName).collect(Collectors.toList());
}
}
一旦用户定义了如上的参数,那么在插件配置界面,就可以看到如下参数配置信息:
2.5.3. 开发示例
@UIScope
@SpringComponent
public class PackingStationView extends AbstractCustomizedComponent implements ClickListener {
private static final long serialVersionUID = -7578836084244167502L;
@ParameterDef(nameKey = "WorkStation.Packing.Parameter.IsPrintNeeded", dataType = DataType.Boolean)
private boolean isPrintNeeded = false;
@ParameterDef(nameKey = "WorkStation.Packing.Parameter.PackagePrinter", dataType = DataType.List, provider = PrinterProvider.class)
private String packagePrinter;
@ParameterDef(nameKey = "WorkStation.Packing.Parameter.PackageType", dataType = DataType.List, provider = PackageTypeProvider.class)
private String packageType;
@Inject
private PackingStationViewPresenter presenter;
@I18Support(caption = "Barcode:", captionKey = "WorkStation.Barcode")
private TextField tfBarcode = new TextField();
@I18Support(caption = "Scan the SN", captionKey = "WorkStation.InputTips")
private InputLabel lblInputTips = new InputLabel();
@I18Support(caption = "Seal", captionKey = "WorkStation.Package.Seal")
private Button btnSeal = new Button();
@I18Support(caption = "Complete", captionKey = "WorkStation.Complete")
private Button btnComplete = new Button();
@I18Support(caption = "Print", captionKey = "WorkStation.Print")
private Button btnPrint = new Button();
private Button[] topTools = { btnSeal, btnComplete, btnPrint };
private HorizontalLayout hlTips = new HorizontalLayout();
private TabSheet tabSheet = new TabSheet();
private Tab packingTab;
@Inject
private IStationMPITab mpiTabContent;
@Inject
private IPackingScheduleTab scheduleTab;
@Inject
private IPackingTab packingTabContent;
@Inject
private IApproveConfirmDialog approveConfirmDialog;
private void setElementsId() {
tfBarcode.setId("tf_barcode");
lblInputTips.setId("ilbl_inputtips");
btnSeal.setId("btn_seal");
btnComplete.setId("btn_complete");
btnPrint.setId("btn_print");
hlTips.setId("hl_tips");
}
public PackingStationView() {
VerticalLayout vlRoot = new VerticalLayout();
vlRoot.setSpacing(false);
vlRoot.setMargin(false);
vlRoot.setSizeFull();
HorizontalLayout hlToolBox = new HorizontalLayout();
hlToolBox.setWidth("100%");
hlToolBox.setSpacing(true);
hlToolBox.setMargin(true);
hlToolBox.addStyleName(CoreTheme.TOOLBOX);
vlRoot.addComponent(hlToolBox);
HorizontalLayout hlTempToolBox = new HorizontalLayout();
hlToolBox.addComponent(hlTempToolBox);
hlTempToolBox.addStyleName(CoreTheme.INPUT_DISPLAY_INLINE);
hlTempToolBox.addComponent(tfBarcode);
tfBarcode.addStyleName(CoreTheme.BACKGROUND_YELLOW);
hlTempToolBox.addComponent(hlTips);
hlTips.addStyleName(CoreTheme.STAGE_LAYOUT_TIPS + " " + CoreTheme.BACKGROUND_ORANGE);
hlTips.addComponent(lblInputTips);
for (Button btn : topTools) {
hlTempToolBox.addComponent(btn);
//btn.setWidth(120, Unit.PIXELS);
btn.addClickListener(this);
btn.setDisableOnClick(true);
}
btnSeal.setIcon(VaadinIcons.PACKAGE);
btnPrint.setIcon(VaadinIcons.PRINT);
btnComplete.setIcon(VaadinIcons.CHECK);
// TabSheet
tabSheet.setSizeFull();
tabSheet.addStyleNames(ValoTheme.TABSHEET_FRAMED, CoreTheme.JASPER_TABSHEET);
vlRoot.addComponent(tabSheet);
vlRoot.setExpandRatio(tabSheet, 1);
this.setSizeFull();
this.setCompositionRoot(vlRoot);
setElementsId();
}
@Override
public void init() {
tabSheet.addSelectedTabChangeListener(new SelectedTabChangeListener() {
private static final long serialVersionUID = -3265758415179285552L;
@Override
public void selectedTabChange(SelectedTabChangeEvent event) {
if (event.getComponent() != null && event.getTabSheet().getSelectedTab() instanceof IPackingTab) {
tfBarcode.focus();
}
}
});
tabSheet.addTab((Component) mpiTabContent, I18NUtility.getValue("WorkStation.MPI", "MPI")).setId("tab_mpi");
packingTab = tabSheet.addTab((Component) packingTabContent,
I18NUtility.getValue("WorkStation.Packing", "Packing"));
packingTab.setId("tab_packing");
tabSheet.addTab((Component) scheduleTab, I18NUtility.getValue("WorkStation.Schedule", "Schedule"))
.setId("tab_schedule");
tabSheet.setSelectedTab((Component) scheduleTab);
tfBarcode.addShortcutListener(new ShortcutListener(null, KeyCode.ENTER, null) {
private static final long serialVersionUID = 1L;
@Override
public void handleAction(Object sender, Object target) {
try {
String value = tfBarcode.getValue();
processSNInput(value);
} catch (Exception e) {
handlingException(e);
} finally {
tfBarcode.setValue("");
}
}
});
scheduleTab.addScheduleItemClickListener(new ScheduleItemClickListener() {
@Override
public void scheduleItemClick(WIP<?> wip) {
try {
processSNInput(wip.getSerialNumber());
} catch (Exception e) {
tfBarcode.setValue("");
handlingException(e);
}
}
});
}
private void processSNInput(String serialNumber) {
if (serialNumber != null && !serialNumber.equals("")) {
if (btnComplete.isEnabled() && serialNumber.equals(presenter.getCompleteBarcode())) {
clickCompleteBtn();
return;
}
tabSheet.setSelectedTab(packingTab);
packingTabContent.checkPackageDefSelect();
if (packingTabContent.isSealed()) {
throw new PlatformException(
I18NUtility.getValue("WorkStation.Packing.BoxSealed", "The box is sealed!"));
}
if (isStartedPackage(serialNumber)) {
setEnable(btnPrint, true);
presenter.setPacking(true);
updateButtonStatus();
return;
}
packingTabContent.addSubInstance(serialNumber);
if (packingTabContent.isSealed()) {
presenter.print(packagePrinter, packingTabContent.getPackageInstance(),
packingTabContent.getSubPackageInstance());
updateButtonStatus();
}
presenter.setPacking(true);
updateButtonStatus();
NotificationUtils.notificationInfo(I18NUtility.getValue("Common.Success", "Success"));
}
}
private boolean isStartedPackage(String value) {
List<PackageInstance> startedPackage = scheduleTab.getStartedPackage();
PackageInstance parentPackage = presenter.getParentPackage(value, startedPackage);
if (Objects.isNull(parentPackage)) {
return false;
} else {
packingTabContent.setPackageInstance(parentPackage);
return true;
}
}
private void updateButtonStatus() {
btnSeal.setEnabled(presenter.isPacking() && !packingTabContent.isSealed());
btnComplete.setEnabled(presenter.isPacking());
setEnable(btnPrint, presenter.isPacking());
}
@Override
public void buttonClick(ClickEvent event) {
event.getButton().setEnabled(true);
try {
if (event.getButton().equals(btnSeal)) {
packingTabContent.sealPackage();
presenter.print(packagePrinter, packingTabContent.getPackageInstance(),
packingTabContent.getSubPackageInstance());
updateButtonStatus();
} else if (event.getButton().equals(btnComplete)) {
packingTabContent.checkPackageSealed();
PackageInstance packageInstance = packingTabContent.getPackageInstance();
presenter.complete(packageInstance);
presenter.setPacking(false);
scheduleTab.refresh();
clearAll();
} else if (event.getButton().equals(btnPrint)) {
approveConfirmDialog.setPrivalege(MesPrivilegeConstants.PACKING_STAGE_PRINT_ALLOWED);
approveConfirmDialog.show(getUI(), new DialogCallBack() {
@Override
public void done(ConfirmResult result) {
if (ConfirmResult.Result.OK.equals(result.getResult())) {
packingTabContent.checkPackageSealed();
presenter.print(packagePrinter, packingTabContent.getPackageInstance(),
packingTabContent.getSubPackageInstance());
}
}
});
}
updateButtonStatus();
} catch (Exception e) {
handlingException(e);
}
}
private void clickCompleteBtn() {
btnComplete.click();
}
private void clearAll() {
presenter.setPacking(false);
mpiTabContent.refresh(null, null);
scheduleTab.refresh();
packingTabContent.clear();
tfBarcode.setValue("");
}
@Override
public String getDisplayValue() {
return "WorkStation.Packingstation.Packing";
}
@Override
public void enter() {
try {
if (!isPreview()) {
packingTabContent.clear();
clearAll();
updateButtonStatus();
scheduleTab.refresh();
} else {
btnSeal.setEnabled(false);
btnPrint.setEnabled(false);
btnComplete.setEnabled(false);
tfBarcode.setEnabled(false);
}
} catch (Exception e) {
handlingException(e);
}
}
@Override
public void activateParameters() {
btnPrint.setVisible(isPrintNeeded);
if (Strings.isNullOrEmpty(packageType)) {
NotificationUtils.notificationError(
I18NUtility.getValue("WorkStation.Packing.PackageTypeNotSet", "Package type is not set!"));
return;
} else {
packingTabContent.setPackageType(packageType);
scheduleTab.setPackageType(packageType);
}
if (Strings.isNullOrEmpty(packagePrinter)) {
NotificationUtils.notificationError(
I18NUtility.getValue("WorkStation.Packing.PrinterNotSet", "Package printer is not set!"));
}
packingTabContent.setPreview(isPreview());
}
@Override
public BasePresenter<?> getPresenter() {
return presenter;
}