最近前后端分离大行其道,苦了后端人员,需要学习的东西还不少。于是到网上看了看前端的教程。没想到前端发展到今天变得如此复杂。前端也包括权限和路由的东西。不过整体看上去似曾相识,只是需要熟悉些新的语法。昨天晚上试用了一下element ui。感觉这个框架还是不错的。学了vue,再也不想用jQuery了。不再直接操作dom,而是跟数据打交道。今后打算好好学习下vue,网上做出来的后端框架还是不少的。下面就记录以下element做前端,asp.net core做后端实现的一个树控件。

后端

  1. 首先涉及到树型结构,先上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;
        }
    }
}
  1. asp.net core比较省事,有了模型就可以直接生成增删改查页面和api。在visual studio中的Controllers文件夹上右键,选择添加->“新搭建的基架项目",选择如下图,再选择MaterialType作为模型,添加TreeDemoContext,做数据库上下文类。就可以直接生成增删改查页面。
  2. 数据库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);
    }

}
  1. 生成的创建页面(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");}
}
  1. 增删改查页面好了,通过页面添加几个示例数据:
  2. element 树形控件怎么加按钮_ios

  3. 在按照步骤二,生成MaterialType的增删改查api。
  4. 替换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. 配置跨域配置:
    (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
  1. 安装element 和axios
npm install element-ui -S
npm install axios --save
  1. 再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")
  1. 在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>
  1. 运行:后端在visual studio中按ctrl+F5运行,前端在vscode终端中输入npm run serve运行,运行效果: