整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

fly coding字典改造(一):引用剥离,功能直

fly coding字典改造(一):引用剥离,功能直接关联查询字典

前,我内置表单功能中,已有自动关联字典数据方式。由于,此前字典数据过于依赖表单字段,导致内存占用较高,字典数据重复性较大,字典缓存清理不便捷。

故,我欲改造之,将其与表单字段,单独剥离,并统一与功能对象进行关联,从而解决如上问题。

开发思路

后端,需设计“功能字典关联表”用于查询,独立“字典配置”注解,改造功能管理类。

前端,需提供通用字典获取函数,改造使用字典的元素如(下拉框、单选框、多选框等)。

整体看来,此次改造还是比较简单的,接下来就直接进入编码环节。

后端

orm对象表定义

package com.flycoding.drivenlibrary.engine.function.entity;

import com.flycoding.dblibrary.annotation.create.Column;
import com.flycoding.dblibrary.annotation.create.PrimaryAuto;
import com.flycoding.dblibrary.annotation.create.Table;
import com.flycoding.dblibrary.enums.ColumnType;
import com.flycoding.drivenlibrary.engine.constants.SqlConstants;
import com.flycoding.drivenlibrary.engine.function.entity.base.BaseFunctionMessage;

/**
 * 功能字典关联表
 *
 * @author 赵屈犇
 * @version 1.0
 * @date 创建时间: 2022/10/18 20:47
 * @Copyright(C): 2022 by 赵屈犇
 */
@Table(tableName="Sy_Func_Dict")
public class FuncDictInfo extends BaseFunctionMessage {

    /** 主键 */
    @PrimaryAuto
    private Integer id;
    /** 普通字典编码 */
    @Column(columnName="dict_code", columnType=ColumnType.VARCHAR, length=SqlConstants.DB_CODE_SIZE)
    private String dictCode;
    /** 数据字典编码 */
    @Column(columnName="data_dict_code", columnType=ColumnType.VARCHAR, length=SqlConstants.DB_CODE_SIZE)
    private String dataDictCode;
    /** 字典驱动库关联配置编码 */
    @Column(columnName="db_config_code", columnType=ColumnType.VARCHAR, length=SqlConstants.DB_CODE_SIZE)
    private String dbConfigCode;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id=id;
    }

    public String getDictCode() {
        return dictCode;
    }

    public void setDictCode(String dictCode) {
        this.dictCode=dictCode;
    }

    public String getDataDictCode() {
        return dataDictCode;
    }

    public void setDataDictCode(String dataDictCode) {
        this.dataDictCode=dataDictCode;
    }

    public String getDbConfigCode() {
        return dbConfigCode;
    }

    public void setDbConfigCode(String dbConfigCode) {
        this.dbConfigCode=dbConfigCode;
    }
}

此对象,用于功能与字典之间的关联。当,用户获取功能时,后台通过此函数,自动装载入字典数据。

字典配置注解

package com.threeox.drivenlibrary.engine.annotation.function.form.field;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 字典配置
 *
 * @author 赵屈犇
 * @version 1.0
 * @date 创建时间: 2022/10/18 20:18
 * @Copyright(C): 2022 by 赵屈犇
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.ANNOTATION_TYPE })
public @interface DictConfig {

    /**
     * 普通字典编码
     *
     * @return
     */
    String dictCode() default "";

    /**
     * 数据字典编码
     *
     * @return
     */
    String dataDictCode() default "";

    /**
     * 字典驱动库关联配置
     *
     * @return
     */
    String dbConfigCode() default "";
    /**
     * 是否使用配置
     *
     * @return
     */
    boolean isUseConfig() default true;

}

此注解,暂用于表单字段与字典配置的关联函数。

生成数据预览

可以看到,字典数据已于功能进行绑定。

接下来,我准备将字典数据,直接绑定至功能对象上,并返回给前端。

查询关联字典数据

为提升查询效率, 此处仍采用初次查询,后续缓存的方式。

package com.threeox.drivenlibrary.engine.function.factory.config;

import com.threeox.drivenlibrary.cache.impl.function.FuncDictCacheManager;
import com.threeox.drivenlibrary.engine.constants.config.request.ConfigRequestConstants;
import com.threeox.drivenlibrary.engine.function.entity.FuncDictInfo;
import com.threeox.drivenlibrary.engine.function.factory.base.BaseCustomFactory;
import com.threeox.drivenlibrary.engine.request.execute.ExecuteRequestFactory;

import java.util.List;

/**
* 功能字典关联工厂
*
* @author 赵屈犇
* @version 1.0
* @date 创建时间: 2022/10/20 20:42
* @Copyright(C): 2022 by 赵屈犇
*/
public class FuncDictFactory extends BaseCustomFactory<List<FuncDictInfo>> {

    private static FuncDictFactory inst=null;

    public static FuncDictFactory getInstance() {
        if (inst==null) {
            synchronized (FuncDictFactory.class) {
                if (inst==null) {
                    inst=new FuncDictFactory();
                }
            }
        }
        return inst;
    }

    private FuncDictFactory() {
    }

    @Override
    protected void init() {
    }

    @Override
    protected void initCacheManager() {
        cache=FuncDictCacheManager.getInstance();
    }

    @Override
    protected List<FuncDictInfo> initResult(ExecuteRequestFactory requestFactory, String dbConfigCode, Object key, Integer relatedId, Object... extendParams) throws Exception {
        // 查询关联字典配置根据功能主键
        return getResult(requestFactory, ConfigRequestConstants.QUERY_FUNC_DICT_BY_FUNC_ID, relatedId);
    }
}

绑定字典结果至功能

最后,我在获取功能对象时,动态获取字典配置,并绑定字典结果数据。

为兼容普通字典和数据字典主键可能重复问题,我根据dictCode,dataDictCode,dbConfigCode进行拼接,生成新的key值。

/**
* 初始化功能字典数据
*
* @param functionInfo
* @return a
* @author 赵屈犇
* @date 创建时间: 2022/10/20 20:58
* @version 1.0
*/
private void initFuncDictData(FunctionMessage functionInfo) {
    if (functionInfo !=null) {
    // 获取关联字典数据
    List<FuncDictInfo> dictInfos=funcDictFactory.getResult(functionInfo.getDbConfigCode(), functionInfo.getFuncCode(), functionInfo.getFuncId());
    if (ArrayUtils.isNotEmpty(dictInfos)) {
        Map <String, List<DictDataInfo>> dictMap=new HashMap<>();
        dictInfos.forEach(dictInfo - > {
            // key
            String key=StringUtils.appendStrings(dictInfo.getDictCode(), dictInfo.getDataDictCode(), dictInfo.getDbConfigCode());
            if (!dictMap.containsKey(key)) {
                List<DictDataInfo> dictionaryInfos=null;
                // 普通字典
                if (StringUtils.isNotEmpty(dictInfo.getDictCode())) {
                    // 普通字典类型
                    dictionaryInfos=dictFactory.getResult(dictInfo.getDbConfigCode(), dictInfo.getDictCode());
                    // 数据字典
                } else if (StringUtils.isNotEmpty(dictInfo.getDataDictCode())) {
                    dictionaryInfos=dataDictResultFactory.getResult(dictInfo.getDataDictCode());
                }
                dictMap.put(key, dictionaryInfos);
            }
        });
        functionInfo.setDictDatas(dictMap);
    }
}

至此,后端功能业已完成。此下,将进行改造前端功能。

前端

通用字典获取函数

/**
 * 获取字典数据
 *
 * @param elementConfig
 * @returns {null|*}
 */
engine.getDictData=function (elementConfig) {
    if (self.funcMessage) {
        let key=[elementConfig['dictCode'], elementConfig['dataDictCode'], elementConfig['dictDBConfigCode']].join(
            "");
        return self.funcMessage.dictDatas[key];
    }
    return null;
}

此函数,内置在基础引擎js段中,故可以直接获取功能对象,并通过功能对象直接获取字典数据。

选择框元素

此下,我选了选择框元素进行改造,其他的复刻即可。

// 定义选择框元素
let selectView=factory.byId("${fieldCode}");
// 定义过滤lay
let layFilter="${fieldCode}_" + self.containerId;
// 设置属性
selectView.attr("lay-filter", layFilter);
// 获取字典
let dicts=factory.getDictData(elementConfig);
if (!engineCommon.isListEmpty(dicts)) {
  for (let i=0, length=dicts.length; i < length; i++) {
    try {
      let dict=dicts[i];
      let dictionaryValue=dict["dictionaryValue"];
      let optionHtml="<option name='" + layFilter + "' id='${fieldCode}_" + dictionaryValue + "' value='" + dictionaryValue + "'>" + dictionary["dictionaryName"] + "</option>";
      selectView.append(optionHtml);
    } catch(e) {
      console.log(e);
    }
  }
  layFactory.render('select');
}
// 监听选中事件
layFactory.on(LayEventConstants.SELECT, layFilter, function(data){
  selectView.val(data.value);
  // 回调lay事件
  if (self.factory && self.factory['onLayEvent']) {
    self.factory.onLayEvent('${fieldCode}', LayEventConstants.SELECT, data);
  }
});

至此,其他字典组件相继修改,既完成。

在寻找支持在Java中用编程方法处理各类格式文档的API吗?好巧,Java版企业级文档管理组合套包Spire.Office 2020全新上线!Word、Excel、PPT、PDF、条形码等格式一网打尽。

目前,Spire.Office for Java 迎来v3.1.0版的更新。Excel格式处理控件Spire.XLS(点击下载)强势加入,同时各个产品也增加了新功能。如Spire.PDF 支持转换PDF到SVG时设置宽度和高度像素,并公开了PdfFileLinkAnnotationWidget类及getValue属性;Spire.Presentation 支持转换PPT到HMTL时,可设置内容居中;同时,也修复了将PDF转换为Word/HTML、PPT转换为PDF/图片、Word转换为PDF/HTML、给PPT设置图片背景、对Word文档进行加密时出现的问题。

新功能及问题修复详情,请参阅如下内容。(点击文末“了解更多”可下载Spire.office全部产品)


Spire.PDF for Java

新功能:

  • 公开了PdfFileLinkAnnotationWidget类,支持在转换PDF到Svg时设置宽度和高度像素 pdf.getConvertOptions().setPdfToSvgOptions(float widthPixel, float heightPixel);
  • 公开了getValue属性用以获取radio button的值 PdfRadioButtonWidgetItem item=items.get(j);item.getValue();

问题修复:

  • 修复了转换PDF到Word文档,程序挂起的问题
  • 修复了转换PDF到HTML,程序抛异常的问题
  • 修复了在MAC环境中水印无法绘制成功的问题

Spire.Presentation for Java

新功能:

  • 支持转换PPT到HMTL时,可设置内容居中 Presentation ppt1=new Presentation(); ppt1.loadFromFile(inputFile); ppt1.getSaveToHtmlOption().setCenter(true); ppt1.saveToFile(outputFile, FileFormat.HTML);
  • 新增getTargetSlide()方法获取含超链接类型为GO_SLIDE的目标幻灯片 Presentation ppt=new Presentation(); ppt.loadFromFile("test.pptx"); IAutoShape shape=(IAutoShape) ppt.getSlides().get(0).getShapes().get(0); HyperlinkActionType type=shape.getClick().getActionType(); ISlide slide=shape.getClick().getTargetSlide(); int slideNumber=slide.getSlideNumber();

问题修复:

  • 修复了在MAC系统上保存.pptx文档时,抛异常"cannot found font installed on the system" 的问题
  • 修复了加载.pptx文档程序抛AppException异常的问题
  • 修复了添加HTML到形状里时,进程抛 "Cannot find table '0S/2' in the font file"异常的问题
  • 修复了在MAC系统上给PPT添加背景图抛异常的问题
  • 修复了转换到PPT到PDF后,多出黑线的问题
  • 修复了转换形状到图片进程抛异常的问题
  • 修复了转换组合形状到图片失败的问题
  • 修复了保存Slide 到图片,抛OutOfMemoryError的问题
  • 修复了PPT转图片抛异常的问题

Spire.Doc for Java

问题修复:

  • 修复了垂直或水平合并单元格时,只保留了第一个单元格数据的问题
  • 修复了转换.docx文档到html文档时,公式丢失的问题
  • 修复了修改文档的内容格式后,保存文档失败的问题
  • 修复了移除分节符时,失败的问题
  • 修复了加载.docx文档时,抛异常"Operation is not valid due to the current state of the object."的问题
  • 修复了多线程并发转换Word到PDF失败的问题
  • 修复了转换.docx文档到PDF时,抛出异常“pItem have not found in paragraph items”的问题
  • 修复了保护文档后,输入密码打开显示“密码错误”的问题
  • 修复了转换.docx文档到PDF时,多出空白行的问题
  • 修复了转换doc文档到PDF时,抛出IllegalStateException异常的问题
  • 修复了合并文档时,抛出异常“No have this value 1”的问题
  • 修复了转换Word到PDF耗时长的问题
  • 修复了加载文档抛异常的问题
  • 修复了转换Word到PDF后,页码错误的问题
  • 修复了获取段落ListText,内容不正确的问题
  • 修复了加载文档抛异常的问题
  • 修复了移除分节符后,保存文档抛异常的问题
  • 修复了转换Word到PDF后,页眉图片重复的问题
  • 修复了转换Word到PDF,图片位置不正确的问题
  • 修复了更新TOC目录抛异常的问题
  • 修复了转换Word到PDF后,页面右边距增大的问题
  • 修复了邮件合并后需要保存文档后才能获取文本的问题
  • 修复了添加页眉页脚后,文本重叠的问题
  • 修复了转换Word到PDF抛NullReferenceException异常的问题

在表单的设计过程中,会有一些表单字段需要在已知的内容中进行选择,这在html中会使用select组件来设计该表单字段。而在Material中,同样有与之对应的 <mat-select> 组件。这种组件在选项内容较少的情况下使用非常方便,能很好的引导用户在表单中,输入规范的内容:

但是在选项内容过多时,这种 <mat-select> 组件,对用户来说,想要找到自己想要的选项就比较麻烦,需要一个个选项去对比,查找:

幸运的是,在Material中提供了 mat-autocomplete ,这个组件和我们以前在JQuery时代使用的select2类似。它支持用户键盘输入功能,并根据输入内容在已有的数据集合中进行过滤,选项内容动态的显示过滤后的结果。


MatAutocomplete


基本用法

html:

<form class="example-form">
  <mat-form-field class="example-full-width">
    <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto">
    <mat-autocomplete #auto="matAutocomplete">
      <mat-option *ngFor="let option of options" [value]="option">
        {{option}}      </mat-option>
    </mat-autocomplete>
  </mat-form-field></form>

TypeScript:

import {Component} from '@angular/core';import {FormControl} from '@angular/forms';/**
 * @title Simple autocomplete
 */@Component({
  selector: 'autocomplete-simple-example',
  templateUrl: 'autocomplete-simple-example.html',
  styleUrls: ['autocomplete-simple-example.css'],
})export class AutocompleteSimpleExample {
  myControl=new FormControl();
  options: string[]=['One', 'Two', 'Three'];
}

呈现的效果如下:


高级API

在简单用法中,我们发现,选项内容为字符串,选择中的值和显示的值为相同的。但在实际的应用过程中,我们需要选中的值和显示的内容是不同的。

displayWith

举个例子,我们有个需要选择企业的字段,要存储到数据库的值是企业的编码,而在界面上显示的需要是企业的名称。在这个例子中,我们有个企业的数据结构:

interface Company {
  code: string;
  name: string;
}
companies: Company[]=[]; constructor() {    this.companies.push({code: 'qiye-01', name: '企业01'});    this.companies.push({code: 'qiye-02', name: '企业02'});    this.companies.push({code: 'qiye-03', name: '企业03'});    this.companies.push({code: 'qiye-04', name: '企业04'});
 }
<form class="example-form">
  <mat-form-field class="example-full-width">
    <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto">
    <mat-autocomplete #auto="matAutocomplete">
      <mat-option *ngFor="let option of companies" [value]="option">
        {{option.name}}      </mat-option>
    </mat-autocomplete>
  </mat-form-field></form>

在html中,我们将mat-option的显示内容修改成name,如 {{option.name}} 。这样我们的下拉选项中就会显示企业的名称:

但是如果我们选中其中一个选项,就会发现结果和我们想象的不一样,选中后的结果显示并不是我们想要的企业名称

此时,我们就需要使用API中提供的

在MatAutocomplete的API: displayWith。文档已经很明确的告诉我们 displayWith 需要我们传入一个已定义的函数

@Input()
displayWith: ((value: any)=> string) | null

其中的函数形参value指的是 <mat-option *ngFor="let option of companies" [value]="option"> 中value对应的option,此处option就是一个我们后台定义的Company。这样我们的value就拥有code和name两个属性。

这样我们就可以定义一个displayWith的函数:

displayFn(value: any): string { // value: Company
  return value ? value.name : undefined;
}

同时修改html

<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
  <mat-option *ngFor="let option of companies" [value]="option">
    {{option.name}}  </mat-option></mat-autocomplete>

这样就会显示我们想要的结果

在这种方式下,我们的formControl得到的其实是一个company对象,要存储code,需要在从company对象中获取code属性值。

那么如果我们想在formControl中直接得到code呢?

扩展用法通过分析,我们发现,formControl获得值的内容,其实和中的value是一致的。

因此,我们可以做如下修改, 将value改成option.code

<form class="example-form">
  <mat-form-field class="example-full-width">
    <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto">
    <mat-autocomplete #auto="matAutocomplete">
      <mat-option *ngFor="let option of companies" [value]="option.code">
        {{option.name}}      </mat-option>
    </mat-autocomplete>
  </mat-form-field></form>

得到如下显示结果:

发现显示的是code值,那我们还可以使用displayWith函数,让它显示企业名称吗?

答案是可以的。不过我们就不能再使用上面定义的displayFn方法了。此时我们需要重新定义一个更高级的方法:

displayWith() {  return (value)=> {    if (value) {      const arr=that.companies.filter(item=> item.code===value);      if (arr.length) {        return arr[0].name;
      }
    }    return undefined;
  }
}

在html中的使用方法如下:

<form class="example-form">
  <mat-form-field class="example-full-width">
    <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto">
    <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayWith()">
      <mat-option *ngFor="let option of companies" [value]="option.code">
        {{option.name}}      </mat-option>
    </mat-autocomplete>
  </mat-form-field></form>

需要[displayWith]=”displayWithThis(this)” 这样特殊的使用方法。

为什么同样是用displayWith,不同的value中,实际写法差异却如此大呢?这个和JS的This作用于有关。

在不是用匿名函数的情况下,如:

displayFn(value) {    if (value) {        const arr=this.companies.filter(item=> item.code===value);        if (arr.length) {            return arr[0].name;
        }
    }    return undefined;
}

此时,如果将displayFn直接设置到mat-autocomplete中,此时的this表示mat-autocomplete对象,这样this.companies.filter就会抛出异常。


结论

在本文中,我们主要讲了MatAutocomplete在实际开发过程中,在不同的业务场景中,同样的组件我们用到的不同的实现方式。每一种语言都有它自己的特性,掌握了这些特性,会让我们工作时更加得心应手。