最近前后端分离大行其道,苦了后端人员,需要学习的东西还不少。于是到网上看了看前端的教程。没想到前端发展到今天变得如此复杂。前端也包括权限和路由的东西。不过整体看上去似曾相识,只是需要熟悉些新的语法。昨天晚上试用了一下element ui。感觉这个框架还是不错的。学了vue,再也不想用jQuery了。不再直接操作dom,而是跟数据打交道。今后打算好好学习下vue,网上做出来的后端框架还是不少的。下面就记录以下element做前端,asp.net core做后端实现的一个树控件。
后端
- 首先涉及到树型结构,先上Model,这里以材料类型为例,类型可以嵌套,有的有父类。类型为Material:
using Newtonsoft.Json;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
namespace TreeDemo.Models
{
/// <summary>
/// 材料类型
/// </summary>
public class MaterialType
{
/// <summary>
/// id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 材料类型名称
/// </summary>
[StringLength(50)]
public string Title { get; set; }
/// <summary>
/// 父类Id
/// </summary>
public int? ParentId { get; set; }
[JsonIgnore]
[ForeignKey(nameof(ParentId))]
public virtual MaterialType ParentType { get; set; }
public virtual ICollection<MaterialType> MaterialTypes { get; set; }
public static List<MaterialType> GetData(List<MaterialType> data)
{
var nodes = data.Where(x => x.ParentId == null).Select(x => new MaterialType
{
Id = x.Id,
ParentId = x.ParentId,
Title = x.Title
}).ToList();
foreach (var item in nodes)
{
item.MaterialTypes = GetChildrens(item, data);
}
return nodes;
}
private static List<MaterialType> GetChildrens(MaterialType item, List<MaterialType> data)
{
var children = data.Where(x=>x.ParentId==item.Id).Select(x=> new MaterialType
{
Id = x.Id,
ParentId = x.ParentId,
Title = x.Title
}).ToList();
foreach (var child in children)
{
child.MaterialTypes = GetChildrens(child, data);
}
return children;
}
}
}
- asp.net core比较省事,有了模型就可以直接生成增删改查页面和api。在visual studio中的Controllers文件夹上右键,选择添加->“新搭建的基架项目",选择如下图,再选择MaterialType作为模型,添加TreeDemoContext,做数据库上下文类。就可以直接生成增删改查页面。
- 数据库ORM用的是Entity Framework Core。这里需要再TreeDemoContext类中,配置以下表关系:
(树形结构的关系配置可以参考我之前写的一篇文章:
Entity Framework Core树状结构增删改查)
using Microsoft.EntityFrameworkCore;
using TreeDemo.Models;
public class TreeDemoContext : DbContext
{
public TreeDemoContext(DbContextOptions<TreeDemoContext> options)
: base(options)
{
}
public DbSet<MaterialType> MaterialType { get; set; }
//配置树状结构表关系
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MaterialType>()
.HasMany(x => x.MaterialTypes)
.WithOne(x => x.ParentType)
.HasForeignKey(x => x.ParentId)
.OnDelete(DeleteBehavior.ClientSetNull);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
}
- 生成的创建页面(create.cshtml)需要稍微修改以下,因为默认select选项为第一个,修改的目的是让select一开始为空:
@model TreeDemo.Models.MaterialType
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>MaterialType</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ParentId" class="control-label"></label>
<select asp-for="ParentId" class="form-control" asp-items="ViewBag.ParentId">
<option value="">选择父类型</option>
</select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
- 增删改查页面好了,通过页面添加几个示例数据:
- 在按照步骤二,生成MaterialType的增删改查api。
- 替换System.Text.Json包,这里通过nuget安Newtonsoft.Json和Microsoft.AspNetCore.Mvc.NewtonsoftJson包,在Startup中配置json格式。为啥配置:因为System.Text.Json不支持循环引用,树中的children在序列化时,会引发循环引用错误。
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
//在ConfigureServices中添加
services.AddControllersWithViews().AddNewtonsoftJson(options=>
{
//忽略循环引用
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//不使用驼峰样式的key
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
//设置时间格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd";
});
- 配置跨域配置:
(1). 在appsettings.json中添加
"AppCores": "http://localhost:8080,http://localhost:8081",
(2) 在startup类的ConfigureService方法中配置跨域:
services.AddCors(options => options.AddPolicy("AllowAll", policy => policy.WithOrigins(cores).AllowAnyMethod().AllowAnyHeader().AllowCredentials()));
(3) 还需要再api的方法中启用跨域:
[EnableCors("AllowAll")]
[HttpGet]
public async Task<ActionResult<IEnumerable<MaterialType>>> GetMaterialType()
{
return await _context.MaterialType.ToListAsync();
}
后端到此结束
前端
1.创建vue项目
vue create treedemo
- 安装element 和axios
npm install element-ui -S
npm install axios --save
- 再main.js中引用element ui和axios
import Vue from "vue"
import App from "./App.vue"
import ElementUI from "element-ui"
import "element-ui/lib/theme-chalk/index.css"
import axios from "axios"
axios.defaults.baseURL = "https://localhost:44356"
Vue.use(ElementUI)
Vue.prototype.$http = axios
Vue.config.productionTip = false
new Vue({
render: h => h(App)
}).$mount("#app")
- 在App.vue中编写页面,替换原来的内容:
<template>
<div id="app">
<el-tree
:data="materialTypesData"
:props="{ children: 'MaterialTypes', label: 'Title' }"
@node-click="handleMaterialTypeNodeClick"
></el-tree>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
materialTypesData: []
}
},
mounted() {
this.getMaterialTypes()
},
methods: {
//远程获取数据
getMaterialTypes() {
this.$http
.get("https://localhost:44356/api/materialtypes/tree")
.then(res => {
console.log(res)
this.materialTypesData = res.data
})
},
//树节点点击事件
handleMaterialTypeNodeClick(data) {
this.$message("test" + data)
}
}
}
</script>
<style></style>
- 运行:后端在visual studio中按ctrl+F5运行,前端在vscode终端中输入
npm run serve
运行,运行效果: