2023年2月

在我们开发业务的时候,一般数据库表都有相关的关系,除了单独表外,一般还包括一对多、多对多等常见的关系,在实际开发过程中,需要结合系统框架做对应的处理,本篇随笔介绍基于ABP框架对EF实体、DTO关系的处理,以及提供对应的接口进行相关的数据保存更新操作,这篇介绍多对多关系下的ABP框架的处理。

上篇随笔《
ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)
》介绍了一对多关系下的主从表数据处理,包括ABP框架对EF实体、DTO等关系处理,以及应用层基类接口的调整和Apicaller调用层的封装,最后介绍了基于代码生成工具快速生成所需ABP框架代码和Winform界面代码的过程。

本篇基于ABP框架的基础上,继续介绍多对多关系的数据库设计、框架代码生成和调整,以实现常见多对多关系的数据处理。

1、多对多关系的数据库设计和界面关系

一般多对多的关系是指两个业务表之间存在关联关系,它们通过中间表(包含两个表的外键关系)建立多对多的关系,ABP框架除了两个外键关系外,一般还会增加几个系统字段,如下所示。

角色包含菜单资源也是多对多的关系,一般在角色新增或者编辑界面中进行维护。

或者

功能界面设计的时候,就需要考虑和这些表之间的关系维护,如商品类型中,基本信息里面和品牌关系进行绑定。

不管上面的树形列表,还是很后面的复选框组,都是先请求关联主表的数据,然后再请求对应角色或者商品类型下的关系数据,绑定到界面上。

如对于上面的树形列表,通过设置树列表的数据,以及选中的记录就可以实现对应关系的绑定。

    <el-treeref="tree"class="filter-tree"style="padding-top:10px":data="treedata"node-key="id"icon-class="el-icon-price-tag"default-expand-all
highlight-current
:show-checkbox
="showcheck":filter-node-method="filterNode":default-checked-keys="checkedList" >

因此,在树形列表绑定的时候,需要请求原有的全部菜单数据,以及属于该角色下的菜单数据,两相整合就可以实现复选框选中已有菜单的效果了。

    async getlist() { //树列表数据获取
      //获取全部功能列表
      var param = { SkipCount: 0, MaxResultCount: 1000, Tag: 'web'}var treeList = [] //所有功能列表
      await menu.GetAll(param).then(data =>{
treeList
=data.result.items
})
//console.log(treeList) //获取角色菜单列表 var grantedList =[]if (this.roleId && typeof (this.roleId) !== 'undefined') {
param
= { RoleId: this.roleId, MaxResultCount: 1000, MenuTag: 'web'}
await role.GetMenusInRole(param).then(data
=>{
grantedList
=data.result.items
})
}
//console.log(grantedList)

当然我们也可以把角色包含菜单数据放在角色对象的DTO里面,然后一次性就可以获得菜单集合了,如我这里介绍的商品类型中的包含的品牌列表做法一样。

2、ABP后端对于多对多关系的处理

多对多关系,是我们业务表常见的一种关系,如果是只读的展示,我们直接通过关联关系获得记录展示即可;如果是进行编辑的处理,那么需要获取关联主表的全部记录进行展示,然后根据关联关系,显示复选框勾中的记录展示。

刚才说到,我们商品类型中对于多对多的关系,可以通过后端直接返回对应的数据记录集合的,这种做法可以避免细粒度API的请求过程,不过对于太大的数据集合,建议还是通过单独的API进行获取。

我们为了在商品类型中返回相关品牌信息,那么需要定义一个简单的对象用来承载品牌信息,如下DTO所示。

    /// <summary>
    ///品牌简单信息/// </summary>
    public classBrandItemDto
{
/// <summary> ///品牌ID/// </summary> public virtual long Id { get; set; }/// <summary> ///品牌编码/// </summary> public virtual string BrandCode { get; set; }/// <summary> ///品牌名称/// </summary> public virtual string BrandName { get; set; }
}

这个DTO是我们自定义的,我们需要映射常规的品牌DTO对象到这个自定义的DTO里面,那么我们可以通过映射文件中加入对应的映射关系来处理,避免属性的一一复制,如下所示。

然后,就是我们在商品类型中使用这个DTO的集合了,如下所示。

我们知道,我们所有业务对象提供服务,都是通过对应的应用层服务接口提供,而商品类型这里对应的应用服务层对象是ProductTypeAppService,它继承自MyAsyncServiceBase基类对象,MyAsyncServiceBase基类对象重写了一些常规的方法,以便提供更方便的服务接口。

其中为了数据对象的转换方便,我们重写了Get和GetAll的方法,并提供一个通用的模板方法用来修改对象DTO的关系,如下代码所示。

其中ConvertDto方法就是我们给子类重写,以便实现数据转换关系的。例如,我们在子类ProductTypeAppService里面重写了ConvertDto方法。

        /// <summary>
        ///对记录进行转义/// </summary>
        /// <param name="item">dto数据对象</param>
        /// <returns></returns>
        protected override voidConvertDto(ProductTypeDto item)
{
//重写ConvertDto方法,返回其他关系数据 var bindedBrands =GetBindedBrands(item.Id).Result.Items;//获取关联品牌的ID列表 var brandIds = bindedBrands.Select(s =>s.Id).ToArray();//获取关联品牌的对象列表 var brandDtos = bindedBrands.Select(ObjectMapper.Map<BrandItemDto>).ToList();

item.BindBrands
= brandIds; //纯ID集合 item.BindBrandItems = brandDtos;//ID,BrandName,BrandCode 信息集合 }

弄好了这些,我们测试接口,可以正确获得对应的记录列表了。

这样我们就可以在列表或者编辑界面里都展示对应的关系了。

在列表展示界面中绑定已有关系代码如下所示。

  <el-table-columnalign="center"label="绑定品牌列表">
    <templateslot-scope="scope">
      <el-tagv-for="opt in scope.row.bindBrandItems":key="opt.id"type="primary":disable-transitions="false"
      >{{ opt.brandName }}</el-tag>
    </template>
  </el-table-column>

在编辑界面中绑定已有关系代码如下所示。

  <el-form-itemlabel="品牌关联"prop="bindBrands">
    <el-checkbox-groupv-model="editForm.bindBrands"style="padding:10px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)">
      <el-checkboxv-for="(item, i) in brandList":key="i":label="item.id">{{ item.brandName }}</el-checkbox>
    </el-checkbox-group>
  </el-form-item>

其中editForm.bindBrands是我们包含的关系,而brandList这是所有品牌列表,这个需要在页面创建的时候,单独获取。

最后,需要介绍一下数据提交的时候,我们需要根据绑定列表关系,修改数据库已有的关联记录,这样实现关联关系的更新。

我们来看看创建商品类型和更新商品类型的时候,对关系数据的处理。

        /// <summary>
        ///重写创建操作,写入额外的信息/// </summary>
        /// <param name="input">商品类型对象DTO</param>
        /// <returns></returns>
        public override async Task<ProductTypeDto>CreateAsync(CreateProductTypeDto input)
{
CheckCreatePermission();var entity =MapToEntity(input);awaitRepository.InsertAsync(entity);awaitCurrentUnitOfWork.SaveChangesAsync();//写入中间表关系 if (input.BindBrands != null)
{
foreach (var brandId ininput.BindBrands)
{
//增加新增的 await _brandTypeRepository.InsertAsync(newBrandType(AbpSession.TenantId, brandId, entity.Id));
}
}
returnMapToEntityDto(entity);
}
/// <summary> ///重写更新操作,更新新的关系数据/// </summary> /// <param name="input">商品类型对象DTO</param> /// <returns></returns> public override async Task<ProductTypeDto>UpdateAsync(ProductTypeDto input)
{
//保存主记录 var dto = await base.UpdateAsync(input);//写入中间表关系 if (input.BindBrands != null)
{
var brandsDto = new BrandsToProductTypeDto() { BrandIds = input.BindBrands, ProductTypeId =input.Id };awaitAddBrandToType(brandsDto);
}
returndto;
}

其中 AddBrandToType 就是修改已有的品牌关系,在介绍这个函数开始前,先来看看商品类型应用服务层的定义,引入了商品类型、品牌、商品类型和品牌关系表三者的仓储对象作为参数的。

    /// <summary>
    ///商品类型,应用层服务接口实现/// </summary>
[AbpAuthorize]public class ProductTypeAppService : MyAsyncServiceBase<ProductType, ProductTypeDto, long, ProductTypePagedDto, CreateProductTypeDto, ProductTypeDto>, IProductTypeAppService
{
private readonly IRepository<ProductType, long> _repository;//业务对象仓储对象 private readonly IRepository<User, long> _userRepository;//用户信息仓储对象 private readonly IRepository<BrandType, long> _brandTypeRepository;//品牌分类中间表对象仓储对象 private readonly IRepository<Brand, long> _brandRepository;//业务对象仓储对象 public ProductTypeAppService(IRepository<ProductType, long> repository, IRepository<BrandType, long> brandTypeRepository, IRepository<Brand, long> brandRepository, IRepository<User, long> userRepository) : base(repository)
{
_repository
=repository;
_brandTypeRepository
=brandTypeRepository;
_brandRepository
=brandRepository;
_userRepository
=userRepository;
}

其中 AddBrandToType 需要修改关系,那么它的逻辑就是:如果不在新列表中的,移除数据库中的关系;如果新列表记录已在数据库中存在则跳过,否则写入关系。

详细代码如下所示,这个也是我们处理中间表之间关系的常见处理逻辑了。

        /// <summary>
        ///添加品牌到分类/// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public asyncTask AddBrandToType(BrandsToProductTypeDto input)
{
var typeInfo =Repository.GetAsync(input.ProductTypeId);if (typeInfo != null)
{
//获取与中间表联合的查询表达式 var query = from cb in_brandTypeRepository.GetAll()
join b
in_brandRepository.GetAll() on cb.Brand_ID equals b.Idwhere cb.ProductType_ID ==input.ProductTypeIdselectb;var oldNotInNewList = query.Where(p => !input.BrandIds.Contains(p.Id)).ToList();foreach (var info inoldNotInNewList)
{
//移除已有,但不在添加列表中的 await _brandTypeRepository.DeleteAsync(m => m.ProductType_ID == input.ProductTypeId && m.Brand_ID ==info.Id);
}
if (input.BrandIds != null)
{
//获取已有绑定列表 var currentBrands =query.ToList();foreach (var brandid ininput.BrandIds)
{
if (currentBrands.Any(cr => cr.Id ==brandid))
{
continue; //已有重复的跳过 }//否则增加新增的 await _brandTypeRepository.InsertAsync(newBrandType(AbpSession.TenantId, brandid, input.ProductTypeId));
}
}
}
}

这样我们在商品类型编辑界面中可以随时变更关联关系了。

以上就是关于中间表的常见处理操作,希望对你学习ABP框架或者Element前端界面有所帮助。

为了方便读者理解,我列出一下前面几篇随笔的连接,供参考:

循序渐进VUE+Element 前端应用开发(1)--- 开发环境的准备工作

循序渐进VUE+Element 前端应用开发(2)--- Vuex中的API、Store和View的使用

循序渐进VUE+Element 前端应用开发(3)--- 动态菜单和路由的关联处理

循序渐进VUE+Element 前端应用开发(4)--- 获取后端数据及产品信息页面的处理

循序渐进VUE+Element 前端应用开发(5)--- 表格列表页面的查询,列表展示和字段转义处理


循序渐进VUE+Element 前端应用开发(6)--- 常规Element 界面组件的使用

循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数

循序渐进VUE+Element 前端应用开发(8)--- 树列表组件的使用


循序渐进VUE+Element 前端应用开发(9)--- 界面语言国际化的处理

循序渐进VUE+Element 前端应用开发(10)--- 基于vue-echarts处理各种图表展示

循序渐进VUE+Element 前端应用开发(11)--- 图标的维护和使用


循序渐进VUE+Element 前端应用开发(12)--- 整合ABP框架的前端登录处理


循序渐进VUE+Element 前端应用开发(13)--- 前端API接口的封装处理

循序渐进VUE+Element 前端应用开发(14)--- 根据ABP后端接口实现前端界面展示

循序渐进VUE+Element 前端应用开发(15)--- 用户管理模块的处理

循序渐进VUE+Element 前端应用开发(16)--- 组织机构和角色管理模块的处理

循序渐进VUE+Element 前端应用开发(17)--- 菜单管理

循序渐进VUE+Element 前端应用开发(18)--- 功能点管理及权限控制

循序渐进VUE+Element 前端应用开发(19)--- 后端查询接口和Vue前端的整合

循序渐进VUE+Element 前端应用开发(20)--- 使用组件封装简化界面代码

循序渐进VUE+Element 前端应用开发(21)--- 省市区县联动处理的组件使用

循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中

循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理

循序渐进VUE+Element 前端应用开发(24)--- 修改密码的前端界面和ABP后端设置处理

循序渐进VUE+Element 前端应用开发(25)--- 各种界面组件的使用(1)

循序渐进VUE+Element 前端应用开发(26)--- 各种界面组件的使用(2)

ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)

电商商品数据库的设计和功能界面的处理

ABP框架中一对多,多对多关系的处理以及功能界面的处理(2)

在我们一些系统里面,有时候会需要一些让用户自定义的数据信息,一般这些可以使用扩展JSON进行存储,不过每个业务表的显示项目可能不一样,因此需要根据不同的表单进行设计,然后进行对应的数据存储。本篇随笔结合基于Vue+Element实现动态表单的设计、数据录入存储的相关操作。

1、动态表单的设计

动态表单的设计一般是基于某个能够动态设计界面的方式实现的,界面上定义对应的说明以及录入数据的方式,如标签后面加上文本,或者多文本等方式。由于我们后台是强类型的数据实体,后端一般不采用动态修改数据库字段的方式构建,而是采用扩展JSON结构的方式来定义整个动态表单的结构,动态扩展的JSON结构比较弹性,不用可以随时移除,也可随时增加,非常方便,这是一样常见的处理数据结构方式。

表单设计器有很多组件可以使用

不过我倾向于使用前者
vue-form-making
,因为它提供一个设计器的组件,可以集成在项目中使用,而
form-generator
好像没有找到,只有在线的设计器。不过两者的概念都差不多。

vue-form-making
的设计器界面如下:


form-generator
设计器界面如下,和前者不同的是,这个没有提供组件可以集成在项目里面。

有了工具,我们就要考虑如何处理我们具体项目里面表的扩展表单界面的设计工作了。

我们设计一个表单用来存储对应的业务表的结构设计,然后在具体表的查看、新增、编辑界面里面,根据键值标识获取对应的动态表单界面,整合到我们实际的界面里面,给用户查看或者录入、编辑等。

因此我们在系统模块里面增加一个动态表单的功能入口,以便设计系统所需业务表单的界面结构。

这里存储的信息不多,主要就是一个用来区分表单名称的键和说明信息,以及JSON界面结构信息即可 。

这里【立即编辑】功能就是前面说到的整合表单设计界面的入口。

其中使用making-form的代码如下所示,自定义了保存的操作功能。

  <el-dialogtitle="创建表单":close-on-click-modal="false":append-to-body="true":visible="isShowForm"fullscreen @close="isShowForm=false">
    <fm-making-formref="makingform"style="height: calc(100vh - 84px);"preview
generate-code
generate-json
upload
clearable
> <templateslot="action"> <el-buttontype="primary"icon="el-icon-upload"size="mini"@click="saveCode()">保存代码</el-button> </template> </fm-making-form>

由于这段代码是在自己定义的组件makingform.vue中处理的,那么获取到JSON信息后,需要抛出一个事件来告诉外部即可。

    saveCode() { //保存代码
      const json = this.$refs.makingform.getJSON();//getHtml()
      //console.log(json)
      this.$emit('save', JSON.stringify(json)); //触发事件,返回内容
      this.isShowForm = false}

在刚才的界面中,使用自定义的makingform组件代码如下所示。

<makingformref="makingform"@save="saveCode" />

当然这里保存的操作就是把新的JSON代码存储到界面的textarea组件上了,这样就实现了我们动态表单界面结构JSON的更新了。

saveCode(json) {if (this.isAdd) {this.addForm.content =json
}
else if (this.isEdit) {this.editForm.content =json
}
},

当然,我们打开makingform组件的时候,如果已经有了JSON信息,那么也是需要加载它已有的界面结果的。

在主体调用界面上,我们打开设计界面的时候,就需要传入对应的JSON数据。

showMaking() {if (this.isAdd) {this.$refs.makingform.show()//显示窗口
      } else if (this.isEdit) {this.$refs.makingform.show(this.editForm.content)//显示窗口
}
},

而在组件上,我们根据JSON赋值给设计器控件即可。

    show(json) { //显示窗口并加载数据
      this.isShowForm = true
      if (!this.isEmpty(json)) { //表单结构
        this.jsonData =JSON.parse(json)//console.log(this.jsonData)
        this.$nextTick(() =>{this.$refs.makingform.setJSON(this.jsonData);
})
}
},

有了这些动态表单界面数据的准备,我们就可以在具体表单里面,整合这些设计的界面,从而实现动态表单的展示了。

为了比较直观显示我们对应设计的表单,我们也在列表中提供了一个预览的界面,用于预览生成的表单界面效果。

单击预览按钮,可以查看具体设计的表单效果,表单的呈现是通过其中的fm-generate-form 来呈现效果的。

2、动态表单的数据存储

这里根据上面的动态表单设计的界面,整合并存储对应界面控件的值,从而实现了动态表单和动态表单数据的整合显示了。

为了有效管理动态表单的数据和是否展示的处理,我们在业务表单的data属性集合中增加了两个变量,如下所示。

      hasDynamicForm: false, //是否有动态表单
      dynamicFormJson: '', //动态表单的JSON数据

这样我们在业务表单列表界面呈现的时候,也同时获取对应的动态界面结构JSON,如下代码所示。

created() {this.getlist() //获取并显示列表

    //处理动态表单
    var param = { name: 'testuser'}
dynamicForm.FindByName(param).then(data
=>{var result =data.resultif (result && !this.isEmpty(result.content)) {this.dynamicFormJson = result.content //表单数据 this.hasDynamicForm = true //是否有 }
})
},

这些属性,可以在查看、编辑、新增界面中使用,为了独立性考虑,我们添加一个选项卡用来显示动态表单的设计,如果对应的记录中存在了动态表单结构,就显示,否则不显示即可。

界面代码如下所示。

其中动态表单数据主要存储在extensionData字段里面的。

其中的generateform 组件,是我们自定义整合fm-generate-form 组件的,完整的自定义组件generateform代码如下所示。

主要就是定义了两个prop属性,一个是json,用来存储结构数据,一个是edit,用来存储界面组件的JSON数据信息的。

<template>
  <divclass="app-container">
    <divclass="fm-container">
      <fm-generate-formref="generateForm":data="jsonData":remote="remoteFuncs":value="editData" />
    </div>
  </div>
</template>

<script>import Vue from'vue'import VueEditor from'vue2-editor'Vue.use(VueEditor)
import FormMaking from
'form-making'import'form-making/dist/FormMaking.css'Vue.use(FormMaking)

export
default{
props: {
json: {
type: String,
default:''},
edit: {
type: String,
default:''}
},
data() {
return{
jsonData: {},
editData: {},
remoteFuncs: {}
}
},
created() {
this.show(this.json,this.edit)
},
methods: {
clear() {
this.$nextTick(()=>{this.$refs.generateForm.reset()
})
},
show(json, edit) {
//显示窗口并加载数据 //console.log(json) //console.log(edit) if(!this.isEmpty(json)) {//表单结构 this.jsonData=JSON.parse(json)
}
if(!this.isEmpty(edit)) {//表单结构 this.editData=JSON.parse(edit)
}
},
getData() {
//获取动态表单数据并转换JSON return this.$refs.generateForm.getData()
}
}
}
</script> <stylelang="scss"scoped>.app-container,.fm-container{height:calc(100vh - 84px); } </style> <style>#app .app-container{padding:0 !important; } </style>

3、后端ABP框架的扩展数据处理

前面说到的显示动态表单及其数据的内容,其中动态表单数据主要存储在extensionData字段里面的。

这个需要我们后端提供数据存储的处理,在设计表中增加一个ntext类型的字段ExtensionData,如下所示。

那样ABP后端的Entity实体,和DTO数据对象里面,都添加这个字段信息了

        /// <summary>
        ///扩展JSON数据/// </summary>
        public string ExtensionData { get; set; }

这样ABP就可以通过不同的前端实现数据的存储处理了。

为了方便读者理解,我列出一下前面几篇随笔的连接,供参考:

循序渐进VUE+Element 前端应用开发(1)--- 开发环境的准备工作

循序渐进VUE+Element 前端应用开发(2)--- Vuex中的API、Store和View的使用

循序渐进VUE+Element 前端应用开发(3)--- 动态菜单和路由的关联处理

循序渐进VUE+Element 前端应用开发(4)--- 获取后端数据及产品信息页面的处理

循序渐进VUE+Element 前端应用开发(5)--- 表格列表页面的查询,列表展示和字段转义处理


循序渐进VUE+Element 前端应用开发(6)--- 常规Element 界面组件的使用

循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数

循序渐进VUE+Element 前端应用开发(8)--- 树列表组件的使用


循序渐进VUE+Element 前端应用开发(9)--- 界面语言国际化的处理

循序渐进VUE+Element 前端应用开发(10)--- 基于vue-echarts处理各种图表展示

循序渐进VUE+Element 前端应用开发(11)--- 图标的维护和使用


循序渐进VUE+Element 前端应用开发(12)--- 整合ABP框架的前端登录处理


循序渐进VUE+Element 前端应用开发(13)--- 前端API接口的封装处理

循序渐进VUE+Element 前端应用开发(14)--- 根据ABP后端接口实现前端界面展示

循序渐进VUE+Element 前端应用开发(15)--- 用户管理模块的处理

循序渐进VUE+Element 前端应用开发(16)--- 组织机构和角色管理模块的处理

循序渐进VUE+Element 前端应用开发(17)--- 菜单管理

循序渐进VUE+Element 前端应用开发(18)--- 功能点管理及权限控制

VUE+Element 前端应用开发框架功能介绍

循序渐进VUE+Element 前端应用开发(19)--- 后端查询接口和Vue前端的整合

使用代码生成工具快速生成基于ABP框架的Vue+Element的前端界面

循序渐进VUE+Element 前端应用开发(20)--- 使用组件封装简化界面代码

循序渐进VUE+Element 前端应用开发(21)--- 省市区县联动处理的组件使用

循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中

循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理

循序渐进VUE+Element 前端应用开发(24)--- 修改密码的前端界面和ABP后端设置处理

循序渐进VUE+Element 前端应用开发(25)--- 各种界面组件的使用(1)

循序渐进VUE+Element 前端应用开发(26)--- 各种界面组件的使用(2)

电商商品数据库的设计和功能界面的处理

循序渐进VUE+Element 前端应用开发(27)--- 数据表的动态表单设计和数据存储

循序渐进VUE+Element 前端应用开发(28)--- 附件内容的管理

循序渐进VUE+Element 前端应用开发(29)--- 高级查询条件的界面设计

部署基于.netcore5.0的ABP框架后台Api服务端,以及使用Nginx部署Vue+Element前端应用

循序渐进VUE+Element 前端应用开发(30)--- ABP后端和Vue+Element前端结合的分页排序处理

循序渐进VUE+Element 前端应用开发(31)--- 系统的日志管理,包括登录日志、接口访问日志、实体变化历史日志

循序渐进VUE+Element 前端应用开发(32)--- 手机短信动态码登陆处理

循序渐进VUE+Element 前端应用开发(33)--- 邮件参数配置和模板邮件发送处理

使用代码生成工具快速开发ABP框架项目

使用Vue-TreeSelect组件实现公司-部门-人员级联下拉列表的处理

使用Vue-TreeSelect组件的时候,用watch变量方式解决弹出编辑对话框界面无法触发更新的问题

在我们很多模块里面,都需要使用到一些诸如图片、Excel文件、PDF文件等附件的管理,一般我们倾向于把它独立为一个公用的附件管理模块,这样可以有效的统一管理附件的信息。本篇随笔介绍附件内容的管理,包括可以对图片进行预览,对其他文件实现信息的查看和下载等操作,以及后端提供对应的附件上传等处理。

1、附件管理的界面

和其他模块一样,我们可以对附件记录表里面的信息进行管理,一般情况下,我们管理的附件都是诸如图片、Excel文件、PDF文件等附件的管理。

附件表是一个综合管理这些文件的记录表,虽然附件一般是独立上传到服务器端的文件系统里面,不过也需要记录这些文件的名称、类别名称、大小、后缀名、创建时间、创建人等信息。

数据库设计表如下所示。

记录明细大概如下所示。

为了管理好这些文件信息,我们在界面提供一些条件供查询,如下是管理界面。

为了快速的进行检索,我们提供了两个树形列表进行查询,可以按照文件类型,以及按照类别名称查询,类别是我们在上传的时候指定的一个附件的类别名称。

按文件类型分类如下所示

按类别名称分类如下所示。

而树形列表的信息展示,我们使用了自定义的树列表控件,非常方便,并极大减少了界面代码,界面代码如下所示。

<el-tabsvalue="treeType"type="border-card">
  <el-tab-panename="treeType"label="按文件类型">
    <myTree:data="treeType"icon-class="el-icon-price-tag"@nodeClick="nodeTypeClick" />
  </el-tab-pane>
  <el-tab-panename="treeCategory"label="按类别名称">
    <myTree:data="treeCategory"icon-class="el-icon-price-tag"@nodeClick="nodeCategoryClick" />
  </el-tab-pane>
</el-tabs>

而树形列表的类别名称,我们是从数据库中动态获取的,因此需要特殊的API封装调用。

在ABP框架的后端,应用服务类FileUploadAppService中定义一个获取类别的列表接口

        /// <summary>
        ///获取所有类别(Distinct)/// </summary>
        /// <returns></returns>
        public virtual async Task<List<string>>GetAllCategory()
{
var query = Repository.GetAll().Where(s=> s.Category != null).OrderBy(s =>s.Category);var list = query.Select(s =>s.Category).Distinct().ToList();return awaitTask.FromResult(list);
}

在客户端的API调用类中同时增加一个API处理接口,如下所示。

而Element的前端调用后端的ABP接口,前面很多博客也介绍的很多了,如下是它们的处理过程图示。

前端根据ABP后端的接口进行前端JS端的类的封装处理,引入了ES6类的概念实现业务基类接口的统一封装,简化代码。

大多数模块我们涉及到常规增删改查等业务接口,那么这些类继承BaseApi,就会具有相关的接口了,如下所示继承关系。

其中JS类的BaseApi具有常规的增删改查接口,如下所示。

在整合ABP后端接口的时候,我们为了方便,一般使用ES6的方式定义一个客户端的Api调用类,基础接口封装在BaseApi类里面,扩展自定义接口放在子类定义,因此前端API封装类fileupload.js的类关系如下所示。

我们再次回到管理界面,在列表中展示附件信息外,如果是图片提供预览,如果是文件则提供下载链接,方便处理。

或者

预览查看图片文件的时候,我们也需要在明细中列出附件的一些信息,如下界面所示。

以上就是附件管理的设计表,以及管理界面,其中前端主要使用了Vue + Element进行开发,后端还是用ABP的框架提供相关的API接口。

2、附件上传的处理

在之前随笔《
循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理
》中已经比较详细的介绍了对附件的上传处理,我们ABP后端提供了一些API接口给前端界面控件进行调用即可上传对应的附件

在附件上传处理的时候,我们就可以通过这样获得请求的文件对象了,如下代码所示。

我们上传到后端ABP应用服务器的文件,一般情况是不能访问目录的,如果需要特别放行,那么需要在ABP服务的Host应用里面,设置静态文件,允许前端访问我们的文件路径。

一般在Host项目的启动入口设置即可。

        public voidConfigure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
............

app.UseStaticFiles();
//指定特定的目录作为静态文件目录,如UploadFiles//是否可以访问静态文件 app.UseStaticFiles(newStaticFileOptions
{
FileProvider
= new PhysicalFileProvider(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "UploadFiles")),
RequestPath
= "/UploadFiles"});

.........

这样上传的文件,在对应目录里面,就可以通过URL地址访问了。

另外,前面我们看到的数据记录里面,没有绝对的URL地址,一般是为了适应性方便,不需要绝对的地址。

但是前端为了方便,服务器后端返回的接口中,我们一般增加一个绝对的地址信息供查看或者下载文件,那么我们可以在后端对应附件记录的转义函数里面增加一个对相对地址转换为绝对访问的URL地址的转换即可。

        /// <summary>
        ///对记录进行转义/// </summary>
        /// <param name="item">dto数据对象</param>
        /// <returns></returns>
        protected override voidConvertDto(FileUploadDto item)
{
//转义相对地址为绝对地址 item.FileUrl =GetFileUrl(item.BasePath, item.SavePath);
}
        /// <summary>
        ///根据记录的basePath和savePath,以及HttpContext上下文确定绝对路径/// </summary>
        /// <param name="basePath">附件的基础路径</param>
        /// <param name="savePath">附件的保存路径</param>
        /// <returns></returns>
        private string GetFileUrl(string basePath, stringsavePath)
{
var httpContext =_httpContext.HttpContext;string serverRealPath =basePath.UriCombine(savePath);if (!Path.IsPathRooted(basePath) && !basePath.StartsWith("http://") && !basePath.StartsWith("https://"))
{
//如果是相对目录,加上当前程序的目录才能定位文件地址 var url = string.Format("{0}://{1}", httpContext.Request.Scheme, httpContext.Request.Host.Value);
serverRealPath
= url.UriCombine(serverRealPath).Replace('\\', '/');
}
returnserverRealPath;
}

而前端界面中,一般的图片和附件上传界面如下所示。

编辑界面下,附件上传界面,可以加载已有的记录展示,如下所示。

用图片列表控件的方式展示图片信息,如下所示。

如果我们用Element的上传组件,大概的界面代码如下所示,主要设置好上传的API地址,以及给它提供好对应的授权头部信息即可。

前端界面的代码如下所示。

    <el-form-itemlabel="封面图片">
      <el-uploadref="upload"action="/abp/services/app/FileUpload/PostUpload"list-type="picture-card":on-preview="handlePictureCardPreview":on-remove="handleRemove":on-success="onSuccess":on-error="onError"accept="image/jpeg,image/gif,image/png,image/bmp":headers="myHeaders":file-list="editForm.fileList"
      >
        <iclass="el-icon-plus" />
      </el-upload>
      <el-dialog:visible.sync="dialogVisible">
        <imgwidth="100%":src="dialogImageUrl"alt="">
      </el-dialog>
    </el-form-item> 

其中授权头部信息,是ABP约定的授权请求头部信息,如下属性设置

  myHeaders: { Authorization: 'Bearer ' + getToken() }, //用于上传文件的身份认证

这样就可以整合文件上传的管理操作了,而前端就只需要针对附件信息,做统一的管理即可。

如下是统一的附件管理界面入口。

为了方便读者理解,我列出一下前面几篇随笔的连接,供参考:

循序渐进VUE+Element 前端应用开发(1)--- 开发环境的准备工作

循序渐进VUE+Element 前端应用开发(2)--- Vuex中的API、Store和View的使用

循序渐进VUE+Element 前端应用开发(3)--- 动态菜单和路由的关联处理

循序渐进VUE+Element 前端应用开发(4)--- 获取后端数据及产品信息页面的处理

循序渐进VUE+Element 前端应用开发(5)--- 表格列表页面的查询,列表展示和字段转义处理


循序渐进VUE+Element 前端应用开发(6)--- 常规Element 界面组件的使用

循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数

循序渐进VUE+Element 前端应用开发(8)--- 树列表组件的使用


循序渐进VUE+Element 前端应用开发(9)--- 界面语言国际化的处理

循序渐进VUE+Element 前端应用开发(10)--- 基于vue-echarts处理各种图表展示

循序渐进VUE+Element 前端应用开发(11)--- 图标的维护和使用


循序渐进VUE+Element 前端应用开发(12)--- 整合ABP框架的前端登录处理


循序渐进VUE+Element 前端应用开发(13)--- 前端API接口的封装处理

循序渐进VUE+Element 前端应用开发(14)--- 根据ABP后端接口实现前端界面展示

循序渐进VUE+Element 前端应用开发(15)--- 用户管理模块的处理

循序渐进VUE+Element 前端应用开发(16)--- 组织机构和角色管理模块的处理

循序渐进VUE+Element 前端应用开发(17)--- 菜单管理

循序渐进VUE+Element 前端应用开发(18)--- 功能点管理及权限控制

VUE+Element 前端应用开发框架功能介绍

循序渐进VUE+Element 前端应用开发(19)--- 后端查询接口和Vue前端的整合

使用代码生成工具快速生成基于ABP框架的Vue+Element的前端界面

循序渐进VUE+Element 前端应用开发(20)--- 使用组件封装简化界面代码

循序渐进VUE+Element 前端应用开发(21)--- 省市区县联动处理的组件使用

循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中

循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理

循序渐进VUE+Element 前端应用开发(24)--- 修改密码的前端界面和ABP后端设置处理

循序渐进VUE+Element 前端应用开发(25)--- 各种界面组件的使用(1)

循序渐进VUE+Element 前端应用开发(26)--- 各种界面组件的使用(2)

电商商品数据库的设计和功能界面的处理

循序渐进VUE+Element 前端应用开发(27)--- 数据表的动态表单设计和数据存储

循序渐进VUE+Element 前端应用开发(28)--- 附件内容的管理

循序渐进VUE+Element 前端应用开发(29)--- 高级查询条件的界面设计

部署基于.netcore5.0的ABP框架后台Api服务端,以及使用Nginx部署Vue+Element前端应用

循序渐进VUE+Element 前端应用开发(30)--- ABP后端和Vue+Element前端结合的分页排序处理

循序渐进VUE+Element 前端应用开发(31)--- 系统的日志管理,包括登录日志、接口访问日志、实体变化历史日志

循序渐进VUE+Element 前端应用开发(32)--- 手机短信动态码登陆处理

循序渐进VUE+Element 前端应用开发(33)--- 邮件参数配置和模板邮件发送处理

使用代码生成工具快速开发ABP框架项目

使用Vue-TreeSelect组件实现公司-部门-人员级联下拉列表的处理

使用Vue-TreeSelect组件的时候,用watch变量方式解决弹出编辑对话框界面无法触发更新的问题

在系统模块中的业务列表展示里面,一般我们都会在列表中放置一些查询条件,如果是表字段不多,大多数情况下,放置的条件有十个八个就可以了,如果是字段很多,而这些条件信息也很关键的时候,就可能放置很多条件,但是界面空间比较有限,而常规的查询一般就那么几个常用条件,如果每次都占用很多版面,好像不是很合理和友好。本篇随笔探讨Vue+Element前端界面中处理高级查询模块的界面设计,提供了两种处理的思路供参考借鉴。

1、弹出框的高级查询条件的界面设计

如常规的列表界面如下所示。

单击【高级查询】弹出一个新对话框窗口,里面可以查看到所有的查询条件

我们来一段动画效果,操作界面的动态效果如下所示。

这里我为了降低单页面的代码量,把高级查询模块的代码抽取到一个独立的文件中,然后在主体页面中引入使用。

    <testproduct-advanceref="advancesearch"@search="advanceSearch"
    />

我们在弹出高级查询对话框中,执行查询的时候,会获得条件对象,然后通过事件的方式给调用页面

    async handleSearch() { //表单提交
      this.isVisible = false;this.$emit('search', this.searchForm)
},

在主页面里面,会对高级查询的几个事件进行处理,如打开窗口,确定高级查询后触发查询。

methods: {
advanceSearch(searchObj) {
//高级查询 //console.log(searchObj) this.advanceSearchForm =searchObj;this.msgSuccess('已选择高级查询条件进行查询了')this.isAdvanceSeach = false //关闭高级查询窗口 this.getlist()
},
onResetAdvance(searchObj) {
//重置高级查询条件 //console.log(searchObj) this.advanceSearchForm =searchObj;
},
showAdvanceForm() {
//显示自定义的高级查询对话框 this.$refs.advancesearch.show()
},

在getlist函数里面,需要对高级查询对象进行转换处理

    getlist() { //列表数据获取
      //如果高级查询对象非空,则采用高级查询条件
       var form = this.isEmpty(this.advanceSearchForm) ? this.searchForm : this.advanceSearchForm;var param = { //构造常规的分页查询条件
        //分页条件
        SkipCount: (this.pageinfo.pageindex - 1) * this.pageinfo.pagesize,
MaxResultCount:
this.pageinfo.pagesize,//查询过滤条件 ProductNo: form.productNo,
BarCode: form.barCode,
MaterialCode: form.materialCode,
ProductType: form.productType,
ProductName: form.productName,
Status: form.status
}

.............

不过,由于高级查询和普通的查询界面代码有很多重复的地方,因此这样做感觉也相对比较啰嗦。

下面的思路就折叠的方案进行讨论。

2、折叠式的高级查询条件的界面设计

这个思路来自于AntDesign的查询条件展示,默认它是基于常规条件的展示,如果展开则展示更多的条件。

单击【展开】则展开更多的条件,以供查询。

借鉴了这个方式,我们也可以使用这样的折叠方式来隐藏更多的查询条件,从而也使得不常用的条件默认隐藏起来,提高界面的友好性。

这样折叠的条件和不折叠的条件在一个表单里面,只是通过一个状态的切换隐藏部分条件而已,虽然界面代码多增加一些,不过处理却变得简单一些,不需要单独编写一个高级查询的条件组件页面。

界面代码大致布局如下所示。

这样我们通过一个条件按钮来切换它的状态即可实现常用条件、高级查询条件的切换显示了。

<el-button:icon="expandMore ?'el-icon-arrow-up':'el-icon-arrow-down'"type="text"@click="expandMore =!expandMore">{{ expandMore ? '收起':'展开' }}</el-button>

这样查询处理,并不需要变化什么特殊的操作了,和常规操作一样,只不过多一些条件而已。

    getlist() { //列表数据获取
      var param = { //构造常规的分页查询条件
        //分页条件
        SkipCount: (this.pageinfo.pageindex - 1) * this.pageinfo.pagesize,
MaxResultCount:
this.pageinfo.pagesize,//查询过滤条件 Name: this.searchForm.name,
Sex:
this.searchForm.sex,
.............

State:
this.searchForm.state
};
//使用日期范围选择控件,在查询对象增加开始日期CreationTimeStart、结束日期CreationTimeEnd this.addDateRange(param, this.searchForm.creationTime)//获取列表,绑定到模型上,并修改分页数量 this.listLoading = truetestUser.GetAll(param).then(data=>{this.list =data.result.itemsthis.pageinfo.total =data.result.totalCountthis.listLoading = false})
},

以上就是两种不同高级查询条件的界面设计,一般来说,我倾向于使用后者来实现,这样界面效果也比较完整统一。

为了方便读者理解,我列出一下前面几篇随笔的连接,供参考:

循序渐进VUE+Element 前端应用开发(1)--- 开发环境的准备工作

循序渐进VUE+Element 前端应用开发(2)--- Vuex中的API、Store和View的使用

循序渐进VUE+Element 前端应用开发(3)--- 动态菜单和路由的关联处理

循序渐进VUE+Element 前端应用开发(4)--- 获取后端数据及产品信息页面的处理

循序渐进VUE+Element 前端应用开发(5)--- 表格列表页面的查询,列表展示和字段转义处理


循序渐进VUE+Element 前端应用开发(6)--- 常规Element 界面组件的使用

循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数

循序渐进VUE+Element 前端应用开发(8)--- 树列表组件的使用


循序渐进VUE+Element 前端应用开发(9)--- 界面语言国际化的处理

循序渐进VUE+Element 前端应用开发(10)--- 基于vue-echarts处理各种图表展示

循序渐进VUE+Element 前端应用开发(11)--- 图标的维护和使用


循序渐进VUE+Element 前端应用开发(12)--- 整合ABP框架的前端登录处理


循序渐进VUE+Element 前端应用开发(13)--- 前端API接口的封装处理

循序渐进VUE+Element 前端应用开发(14)--- 根据ABP后端接口实现前端界面展示

循序渐进VUE+Element 前端应用开发(15)--- 用户管理模块的处理

循序渐进VUE+Element 前端应用开发(16)--- 组织机构和角色管理模块的处理

循序渐进VUE+Element 前端应用开发(17)--- 菜单管理

循序渐进VUE+Element 前端应用开发(18)--- 功能点管理及权限控制

VUE+Element 前端应用开发框架功能介绍

循序渐进VUE+Element 前端应用开发(19)--- 后端查询接口和Vue前端的整合

使用代码生成工具快速生成基于ABP框架的Vue+Element的前端界面

循序渐进VUE+Element 前端应用开发(20)--- 使用组件封装简化界面代码

循序渐进VUE+Element 前端应用开发(21)--- 省市区县联动处理的组件使用

循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中

循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理

循序渐进VUE+Element 前端应用开发(24)--- 修改密码的前端界面和ABP后端设置处理

循序渐进VUE+Element 前端应用开发(25)--- 各种界面组件的使用(1)

循序渐进VUE+Element 前端应用开发(26)--- 各种界面组件的使用(2)

电商商品数据库的设计和功能界面的处理

循序渐进VUE+Element 前端应用开发(27)--- 数据表的动态表单设计和数据存储

循序渐进VUE+Element 前端应用开发(28)--- 附件内容的管理

循序渐进VUE+Element 前端应用开发(29)--- 高级查询条件的界面设计

部署基于.netcore5.0的ABP框架后台Api服务端,以及使用Nginx部署Vue+Element前端应用

循序渐进VUE+Element 前端应用开发(30)--- ABP后端和Vue+Element前端结合的分页排序处理

循序渐进VUE+Element 前端应用开发(31)--- 系统的日志管理,包括登录日志、接口访问日志、实体变化历史日志

循序渐进VUE+Element 前端应用开发(32)--- 手机短信动态码登陆处理

循序渐进VUE+Element 前端应用开发(33)--- 邮件参数配置和模板邮件发送处理

使用代码生成工具快速开发ABP框架项目

使用Vue-TreeSelect组件实现公司-部门-人员级联下拉列表的处理

使用Vue-TreeSelect组件的时候,用watch变量方式解决弹出编辑对话框界面无法触发更新的问题

ABP默认的数据库是SQLServer,不过ABP框架底层是EF框架,因此也是很容易支持其他类型的数据库的,本篇随笔介绍在ABP框架使用Mysql数据库,以及基于SQLServer创建MySql数据库的架构和数据的处理。

如果需要其他方式数据库的支持,那么需要引入相应的支持程序集。Nuget中安装MySql相关包,Oracle公司提供的MySql 包试了很多次,都还是用起来有问题,所以现在都是用的第三方的MySql包,这个包也是ABP官方文档中推荐使用的。

pomelo
.entityframeworkcore
.mysql

pomelo
.entityframeworkcore
.mysql
.design

目前使用的是基于正式版的.NETCore5.0,那么Microsoft.EntityFrameworkCore.Relational对应版本是5.0.0。因此,对应这些版本的驱动如下图所示。

在EntityFrameworkCore层中找到XXDbContextConfigurer,修改DbContext中数据库配置,默认使用的SqlServer,修改为mysql。

namespaceMyProject.EntityFrameworkCore
{
/// <summary> ///配置使用特定数据库的处理/// </summary> public static classMyProjectDbContextConfigurer
{
public static void Configure(DbContextOptionsBuilder<MyProjectDbContext> builder, stringconnectionString)
{
//builder.UseSqlServer(connectionString);//默认SQlServer builder.UseMySql(connectionString, new MySqlServerVersion(new Version(5, 7)));//MySQL//builder.UseNpgsql(connectionString);//PostgreSQL//builder.UseOracle(connectionString);//Oracle }public static void Configure(DbContextOptionsBuilder<MyProjectDbContext>builder, DbConnection connection)
{
//builder.UseSqlServer(connection);//默认SQlServer builder.UseMySql(connection, new MySqlServerVersion(new Version(5, 7)));//MySQL//builder.UseNpgsql(connection);//PostgreSQL//builder.UseOracle(connection);//Oracle }
}
}

其中UseMySql需要用到第二个版本参数,我Mysql用的是Mysql5.7,因此使用代码构建版本参数。

new MySqlServerVersion(new Version(5, 7)

我们在Host启动项目里面的appsettings.json里面定义了不同数据库的连接信息,如下所示。

{"ConnectionStrings": {"Default": "Server=.\\SQL2014; Database=MyProjectDb; Trusted_Connection=True;","Oracle": "User Id=orcl;Password=orcl;Data Source=orcl;Persist Security Info=True;","MySql": "Server=localhost;Database=myprojectdb;Uid=root;Pwd=123456;","PostgreSQL": "Server=localhost;Port=5432;Database=myprojectdb;User Id=postgres;Password=123456"},"RedisCache": {"ConnectionString": "localhost","DatabaseId": "-1"},

...............

而我们系统不同的数据库连接就就是来自ConnectionStrings里面的键值对象,我们ABP框架里面,使用引用常数来配置对应的数据库连接。

    public class MyProjectDbContextFactory : IDesignTimeDbContextFactory<MyProjectDbContext>{public MyProjectDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<MyProjectDbContext>();//获取配置文件信息 var configuration =AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder());//通过连接字符串切换对应的数据库,ConnectionStringName默认为Default MyProjectDbContextConfigurer.Configure(builder, configuration.GetConnectionString(Constants.ConnectionStringName));return newMyProjectDbContext(builder.Options);
}
}

那么我们修改其中对应的常数定义,切换为我们所需要的Mysql数据库即可,如下所示。

至此,代码上修改就完成了,那么我们现有运行的SQLServer数据库,如何迁移到Mysql环境中去呢?

2、基于SQLServer创建Mysql数据库的架构和数据

上面小节介绍了切换到其他数据库Mysql数据库的时候,代码上的调整修改,但是我们还需要把已有SQLServer上运行的数据库迁移到Mysql上去,如何处理呢。

有些人希望通过ABP带有的Migrator项目进行项目数据库的重构,不过我不建议使用这个,因为我们实际开发的时候,变动了很多数据库,而变动了再映射回到代码里面,比较麻烦,不如在数据库基础上进行迁移来的快捷完整。

我这里通过工具的方式,把SQLServer数据库导入到Mysql数据库里面,然后在导出脚本进行一定的调整就可以完成。

我这里说到的工具就是Navicat Premium,由于我的数据库用的是SQLServer2014,因此建议使用Navicat Premium15或更高版本来处理数据库的迁移,我测试过Navicat Premium·11是不行的。

在Navicat Premium工具上连接好Mysql数据库和SQLServer数据库,然后在Mysql里面创建一个同名的数据库,以Utf-8编码创建数据库即可,如下所示。

然后在工具上选择数据库传输

在弹出的界面中选择源数据库和目标数据库,如下所示。

然后一步步处理即可完成数据库结构和数据的导入,这样Mysql数据库里面就有对应的数据库结构和数据了。

不过完成这部还是不够十分完美,因为从SQLServer钟导入到Mysql数据库里面,数据库的自增长列全部被取消了,如果我们在管理界面里面修改,则无法修改带有约束关系的主键为自增长。

因此我们需要再次调整一下,也就是把Mysql数据库导出为SQL脚本,然后在脚本上进行一定的调整即可。

用Notepad++打开导出的数据库脚本,对自增长的表字段进行添加一个关键字AUTO_INCREMENT,标注为自增长即可,如下所示。

然后删除已有的Mysql数据库,然后重新创建对应名称的数据库,重新导入Mysql脚本进行运行生成新的表和数据即可完成。

运行ABP后台项目,启动Swagger界面,如下所示。

然后启动Vue+Element的客户端进行测试Mysql数据库的运行情况,前端正常。

以上就是介绍ABP框架使用Mysql数据库的代码调整,以及基于SQLServer创建Mysql数据库的架构和数据的操作过程