• Day12_搜索前端 Nuxt.js


    1 搜索前端技术需求

    1.1 需求描述

    采用vue.js开发搜索界面则SEO不友好,需要解决SEO的问题。

    1.2 了解SEO

    搜索引擎优化(英语:search engine optimization,缩写为SEO),是一种透过了解搜索引擎的运作规则来调整网站,以及提高目的网站在有关搜索引擎内排名的方式。由于不少研究发现,搜索引擎的用户往往只会留意搜索结果最前面的几个条目,所以不少网站都希望透过各种形式来影响搜索引擎的排序,让自己的网站可以有优秀的搜索排名。当中尤以各种依靠广告维生的网站为甚。

    总结:seo是网站为了提高自已的网站排名,获得更多的流量,对网站的结构及内容进行调整优化,以便搜索引擎 (百度,google等)更好抓取到更优质的网站的内容。

    下图是搜索引擎爬取网站页面的大概流程图:

    (搜索引擎的工作流程很复杂,下图只是简单概括)

    从上图可以看到SEO是网站自己为了方便spider抓取网页而作出的网页内容优化,常见的SEO方法比如:

    1)对url链接的规范化,多用restful风格的url,多用静态资源url;
    2) 注意title、keywords的设置。
    3)由于spider对javascript支持不好,对于网页跳转用href标签。
    

    1.3 服务端渲染和客户端渲染

    采用什么技术有利于SEO?要解答这个问题需要理解服务端渲染和客户端渲染。

    什么是服务端渲染?

    我们用传统的servlet开发来举例:浏览器请求servlet,servlet在服务端生成html响应给浏览器,浏览器展示html 的内容,这个过程就是服务端渲染,如下图:

    服务端渲染的特点:

    1)在服务端生成html网页的dom元素。
    2)客户端(浏览器)只负责显示dom元素内容。
    

    当初随着web2.0的到来,AJAX技术兴起,出现了客户端渲染:客户端(浏览器) 使用AJAX向服务端发起http请 求,获取到了想要的数据,客户端拿着数据开始渲染html网页,生成Dom元素,并最终将网页内容展示给用户,如下图:

    客户端渲染的特点:

    1)在服务端只是给客户端响应数据,而不是html网页
    2)客户端(浏览器)负责获取服务端的数据生成Dom元素
    

    两种方式各有什么优缺点?

    客户端渲染:

    1. 缺点

    不利于网站进行SEO,因为网站大量使用javascript技术,不利于spider抓取网页。

    1. 优点

    客户端负责渲染,用户体验性好,服务端只提供数据不用关心用户界面的内容,有利于提高服务端的开发效率。

    1. 适用场景

    对SEO没有要求的系统,比如后台管理类的系统,如电商后台管理,用户管理等。

    服务端渲染:

    1. 优点

    有利于SEO,网站通过href的url将spider直接引到服务端,服务端提供优质的网页内容给spider。

    1. 缺点

    服务端完成一部分客户端的工作,通常完成一个需求需要修改客户端和服务端的代码,开发效率低,不利于系统的 稳定性。

    1. 适用场景

    对SEO有要求的系统,比如:门户首页、商品详情页面等。

    2 Nuxt.js介绍

    2.1 Nuxt.js介绍

    移动互联网的兴起促进了web前后端分离开发模式的发展,服务端只专注业务,前端只专注用户体验,前端大量运 用的前端渲染技术,比如流行的vue.js、react框架都实现了功能强大的前端渲染。

    但是,对于有SEO需求的网页如果使用前端渲染技术去开发就不利于SEO了,有没有一种即使用vue.js、react的前 端技术也实现服务端渲染的技术呢?其实,对于服务端渲染的需求,vue.js、react这样流行的前端框架提供了服务端渲染的解决方案。

    从上图可以看到:

    react框架提供next.js实现服务端渲染。

    vue.js框架提供Nuxt.js实现服务端渲染。

    2.2 Nuxt.js工作原理

    下图展示了从客户端请求到Nuxt.js进行服务端渲染的整体的工作流程:

    1、用户打开浏览器,输入网址请求到Node.js
    2、部署在Node.js的应用Nuxt.js接收浏览器请求,并请求服务端获取数据
    3、Nuxt.js获取到数据后进行服务端渲染
    4、Nuxt.js将html网页响应给浏览器
    

    Nuxt.js使用了哪些技术?

    Nuxt.js使用Vue.js+webpack+Babel三大技术框架/组件,如下图:

    Babel是一个js的转码器,负责将ES6的代码转成浏览器识别的ES5代码。
    Webpack是一个前端工程打包工具。
    Vue.js是一个优秀的前端框架。
    

    Nuxt.js的特性有哪些?

    基于 Vue.js
    自动代码分层
    服务端渲染
    强大的路由功能,支持异步数据
    静态文件服务
    ES6/ES7 语法支持
    打包和压缩 JS 和 CSS
    HTML头部标签管理
    本地开发支持热加载
    集成ESLint
    支持各种样式预处理器: SASS、LESS、 Stylus等等
    

    3 Nuxt.js基本使用

    3.1 创建Nuxt工程

    nuxt.js有标准的目录结构,官方提供了模板工程,可以模板工程快速创建nuxt项目。

    模板工程地址:https://github.com/nuxt-community/starter-template/archive/master.zip

    本项目提供基于Nuxt.js的封装工程,既基于此封装工程开发搜索前端,见“资料”中xc-ui-pc-portal.zip,解压xc-ui-pc-portal.zip到本项目前端工程目录下。

    本前端工程属于门户的一部分,将承载一部分考虑SEO的非静态化页面。

    本工程基于Nuxt.js模板工程构建,Nuxt.js使用1.3版本,并加入了今后开发中所使用的依赖包,直接解压本工程即 可使用。

    3.2 目录结构

    本工程的目录结构如下:

    ‐ 资源目录
    资源目录 assets 用于组织未编译的静态资源如 LESS、SASS 或 JavaScript。
    
    ‐ 组件目录 
    组件目录 components 用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不 会像页面组件那样有 asyncData 方法的特性。
    
    ‐ 布局目录 
    布局目录 layouts 用于组织应用的布局组件。
    该目录名为Nuxt.js保留的,不可更改。
    
    ‐ 中间件目录 
    middleware 目录用于存放应用的中间件。 
    
    ‐ 页面目录 
    页面目录 pages 用于组织应用的路由及视图。
    Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。 
    该目录名为Nuxt.js保留的,不可更改。 
    
    ‐ 插件目录 
    插件目录 plugins 用于组织那些需要在根vue.js应用实例化之前需要运行的 Javascript 插件。 
    
    ‐ 静态文件目录 
    静态文件目录 static 用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。 
    服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。 
    举个例子: /static/logo.png 映射至 /logo.png 该目录名为Nuxt.js保留的,不可更改。 
    
    ‐ Store 目录 
    store 目录用于组织应用的 Vuex 状态树 文件。 
    Nuxt.js 框架集成了 Vuex 状态树的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置。 
    该目录名为Nuxt.js保留的,不可更改。
    
    ‐ nuxt.config.js 文件 
    nuxt.config.js 文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。 
    该文件名为Nuxt.js保留的,不可更改。
    
    ‐ package.json 文件
    package.json 文件用于描述应用的依赖关系和对外暴露的脚本接口。
    该文件名为Nuxt.js保留的,不可更改。
    

    nuxt.js 提供了目录的别名,方便在程序中引用:

    3.3 页面布局

    页面布局就是页面内容的整体结构,通过在layouts目录下添加布局文件来实现。在layouts 根目录下的所有文件都 属于个性化布局文件,可以在页面组件中利用 layout 属性来引用。

    运行时我这里报错,显示权限不够,故进入工程目录chmod -R u+x .

    举个例子:

    1、创建layouts/test.vue布局文件,如下:

    <template>
      <div>
        <div>这里是头</div>
        <nuxt/>
        <div>这里是尾</div>
      </div>
    </template>
    
    <script>
      export default {
    
      }
    </script>
    <style>
      
    </style>
    

    2、在pages目录创建user目录,并创建index.vue页面

    在 pages/user/index.vue 页面里, 可以指定页面组件使用 test 布局,代码如下:

    <template>
      <div> 测试页面</div>
    </template>
    <script>
      export default {
        layout: 'test'
      }
    </script>
    <style>
    
    </style>
    

    3、测试,请求:http://localhost:10000/user,如果如下:

    3.4 路由

    3.4.1 基础路由

    Nuxt.js根据pages目录结构自动生成vue-router模块的路由配置。

    Nuxt.js根据pages的目录结构及页面名称定义规范来生成路由,下边是一个基础路由的例子:

    假设 pages 的目录结构如下:

    pages/ 
    ‐‐| user/
    ‐‐‐‐‐| index.vue 
    ‐‐‐‐‐| one.vue
    

    那么,Nuxt.js 自动生成的路由配置如下:

    router: {
        routes: [
            { 
                name: 'user',
                path: '/user',
                component: 'pages/user/index.vue'
            },
            { 
                name: 'user‐one', 
                path: '/user/one', 
                component: 'pages/user/one.vue'
            }
        ]
    }
    

    index.vue 代码如下:

    <template>
      <div> 测试页面</div>
    </template>
    <script>
      export default {
        layout: 'test'
      }
    </script>
    <style>
    
    </style>
    

    one.vue代码如下:

    <template>
      <div> one页面</div>
    </template>
    <script>
      export default {
        layout: "test"
      }
    </script>
    <style>
    
    </style>
    

    分别访问如下链接进行测试:

    http://localhost:10000/user

    http://localhost:10000/user/one

    3.4.2 嵌套路由

    你可以通过 vue-router 的子路由创建 Nuxt.js 应用的嵌套路由。

    创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。

    别忘了在父级 Vue 文件内增加 用于显示子视图内容。

    假设文件结构如:

    pages/ 
    ‐‐| user/ 
    ‐‐‐‐‐| _id.vue 
    ‐‐‐‐‐| index.vue 
    ‐‐| user.vue
    

    Nuxt.js 自动生成的路由配置如下:

    router: {
        routes: [
            { 
                path: '/user', 
                component: 'pages/user.vue', 
                children: [
                    { 
                        path: '',
                        component: 'pages/user/index.vue',
                        name: 'user'
                    },
                    { 
                        path: ':id', 
                        component: 'pages/user/_id.vue', 
                        name: 'user‐id'
                    }
                ]
            }
        ]
    }
    

    将user.vue文件创建到与user目录的父目录下,即和user目录保持平级。

    <template>
      <div>
        用户管理导航,
        <nuxt-link :to="'/user/101'">修改</nuxt-link>
        <nuxt-child/>
      </div>
    </template>
    <script>
      export default {
        layout: "test"
      }
    </script>
    <style>
    
    </style>
    

    _id.vue页面实现了向页面传入id参数,页面内容如下:

    <template>
      <div> 修改用户信息{{id}}</div>
    </template>
    <script>
      export default {
        layout: "test", data() {
          return {
            id: ''
          }
        },
        mounted() {
          this.id = this.$route.params.id;
          console.log(this.id)
        }
      }
    </script>
    <style>
    
    </style>
    

    测试:http://localhost:10000/user

    点击修改:

    3.6 获取数据

    3.6.1 asyncData 方法

    Nuxt.js 扩展了 Vue.js,增加了一个叫 asyncData 的方法, asyncData 方法会在组件(限于页面组件)每次加载 之前被调用。它可以在服务端或路由更新之前被调用。 在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData 方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。

    注意:由于asynData方法方法是在组件初始化前被调用的,所以在方法内是没有办法通过this来引用组件的实例对象。

    例子:

    在上边例子中的user/_id.vue中添加,页面代码如下:

    <template>
      <div> 修改用户信息{{id}}, 名称{{name}}</div>
    </template>
    <script>
      export default {
        layout: "test",
        asyncData() {
          console.log("async方法")
          return {
            name: 'hello world'
          }
        },
        data(){
          return {
            id:''
          }
        },
        mounted() {
          this.id = this.$route.params.id;
          console.log(this.id)
        }
      }
    </script>
    <style>
    
    </style>
    

    此方法在服务端被执行,观察服务端控制台打印输出“async方法”。

    此方法返回data模型数据,在服务端被渲染,最后响应给前端,刷新此页面查看页面源代码可以看到name模型数 据已在页面源代码中显示。

    3.6.2 async /await方法

    使用 async 和 await 配合promise也可以实现同步调用,nuxt.js中使用async/await实现同步调用效果。

    1、先测试异步调用,增加a、b两个方法,并在mounted中调用。

    methods: {
          a() {
            return new Promise(function (resolve, reject) {
              setTimeout(function () {
                resolve(1)
              }, 2000)
            })
          },
          b() {
            return new Promise(function (resolve, reject) {
              setTimeout(function () {
                resolve(2)
              }, 1000)
            })
          }
        },
        mounted() {
          this.a().then(res => {
            alert(res)
            console.log(res)
          })
          this.b().then(res => {
            alert(res)
            console.log(res)
          })
          this.id = this.$route.params.id;
          console.log(this.id)
        }
    

    观察客户端,并没有按照方法执行的顺序输出,使用Promise实现了异步调用。

    2 、使用async/await完成同步调用

    async asyncData() {
          console.log("async方法")
    
          var a = await new Promise(function (resolve, reject) {
            setTimeout(function () {
              console.log("1")
              resolve(1)
            }, 2000)
          });
    
          var b = await new Promise(function (resolve, reject) {
            setTimeout(function () {
              console.log("2")
              resolve(2)
            }, 1000)
          });
    

    观察服务端控制台发现是按照a、b方法的调用顺序输出1、2,实现了使用async/await完成同步调用。

    3 搜索前端开发

    3.1 搜索页面

    3.1.1 需求分析

    上图是课程搜索前端的界面,用户通过前端向服务端发起搜索请求,搜索功能包括:

    1、界面默认查询所有课程,并分页显示
    2、通过一级分类和二分类搜索课程,选择一级分类后将显示下属的二级分类
    3、通过关键字搜索课程
    4、通过课程等级搜索课程
    

    3.1.2 页面布局

    nuxt.js将/layout/default.vue作为所有页面的默认布局,通常布局包括:页头、内容区、页尾

    default.vue内容如下:

    <template>
      <div>
        <Header />
        <nuxt/>
        <Footer />
      </div>
    </template>
    <script>
      import Footer from '../components/Footer.vue'
      import Header from '../components/Header.vue'
      export default {
        components: {
          Header,
          Footer
        }
      }
    </script>
    <style>
    
    </style>
    

    3.1.3 Nginx代理配置

    搜索页面中以/static开头的静态资源通过nginx解析,如下:

    /static/plugins:指向门户目录下的plugins目录
    /static/css:指向门户目录下的的css目录
    

    修改Nginx中www.xuecheng.com虚拟主机的配置,配置搜索Url,下图是Nginx搜索转发流程图::

    用户请求/course/search时Nginx将请求转发到nuxt.js服务,nginx在转发时根据每台nuxt服务的负载情况进行转 发,实现负载均衡。

    本教程开发环境Nuxt.js服务和www.xuecheng.com虚拟机主在同一台计算机,使用同一个 nginx,配置如下:

    		#前端门户课程搜索
    		location ^~ /course/search {
    			proxy_pass http://dynamic_portal_server_pool; 
    		}
    
    		#后端搜索服务
    		location /openapi/search/ {
    			proxy_pass http://search_server_pool/search/; 
    		}
    
    		#分类信息
    		location /static/category/ {
    			proxy_pass http://static_server_pool; 
    		}
    		
    		#开发环境webpack定时加载此文件 
    		location ^~ /__webpack_hmr/ { 
    			proxy_pass http://dynamic_portal_server_pool/__webpack_hmr; 
    		}
    
    		#开发环境nuxt访问_nuxt 
    		location ^~ /_nuxt/ { 
    			proxy_pass http://dynamic_portal_server_pool/_nuxt/; 
    		}
    

    dynamic_portal_server_pool配置如下 :

    	#前端动态门户 
    	upstream dynamic_portal_server_pool{
    		server 127.0.0.1:10000 weight=10;
    	}
    
    	#后台搜索(公开api) 
    	upstream search_server_pool{
    		server 127.0.0.1:40100 weight=10;
    	}
    

    3.1.4 搜索页面

    搜索页面如下:

    nuxt.js支持定义header,本页面我们在header中引入css样式并定义头部信息。

    3.1.5 测试

    重启Nginx,请求:http://www.xuecheng.com/course/search,页面效果如下:

    注意将IDEA中的SearchApplication服务启动,但是还是报Request failed with status code 404,查看WebStorm发现'http://www.xuecheng.com/static/category/category.json'这个404,所以是nginx忘记配置了

    3.2 查询全部

    3.2.1 需求分析

    初次进入页面,没有输入任何查询条件,默认查询全部课程,分页显示。

    3.2.2 API方法

    在api目录创建本工程所用的api方法类,api方法类使用了public.js等一些抽取类:

    /api/public.js-------------抽取axios 的基础方法
    /api/util.js-----------------工具类
    /config/sysConfig.js----系统配置类,配置了系统参数变量
    

    创建course.js,作为课程相关业务模块的api方法类。

    import http from './public'
    import qs from 'qs'
    let config = require('~/config/sysConfig')
    let apiURL = config.apiURL
    let staticURL = config.staticURL
    if (typeof window === 'undefined') {
      apiURL = config.backApiURL
      staticURL = config.backStaticURL
    }
    /*搜索*/
    export const search_course = (page,size,params) => {
      //let loginRequest = querystring.stringify(params)
      let querys = qs.stringify(params);
      return http.requestQuickGet(apiURL+"/search/course/list/"+page+"/"+size+"?"+querys);
    }
    /*获取分类*/
    export const sysres_category = () => {
      return http.requestQuickGet(staticURL+"/category/category.json");
    }
    

    3.2.3搜索方法

    实现思路如下:

    1、用户请求本页面到达node.js
    2、在asyncData方法中向服务端请求查询课程
    3、asyncData方法执行完成开始服务端渲染
    

    在asyncData(pages/course/search/index.vue)中执行搜索,代码如下:

    async asyncData({ store, route }) {
    //       console.log(route.query)
    
          let page = route.query.page;
           if(!page){
               page = 1;
           }
    //       console.log("page="+page)
          //搜索课程
          let course_data = await courseApi.search_course(page,12,route.query)
          console.log(course_data)
          //查询分类
          let category_data = await courseApi.sysres_category()
    //      console.log(category_data)
    //      console.log(course_data.hits.hits)
    
          if (course_data && course_data.hits &&  course_data.hits.hits && category_data) {
            //全部分类
            let category = category_data.category
            let first_category = category[0].children
            let keywords = ''
            let second_category=[]
            let mt=''
            let st=''
            let grade=''
            let keyword=''
            let total = course_data.hits.total
            if( route.query.mt){
              mt = route.query.mt
            }
            if( route.query.st){
              st = route.query.st
            }
            if( route.query.grade){
              grade = route.query.grade
            }
            if( route.query.keyword){
              keyword = route.query.keyword
            }
            //遍历一级分类
            for(var i in first_category){
              keywords+=first_category[i].name+' '
              if(mt!=''&& mt == first_category[i].id){
                //取出二级分类
                second_category = first_category[i].children;
                // console.log(second_category)
                break;
              }
            }
            //console.log(category[0].children)
            return {
              courselist: course_data.hits.hits,
              first_category: first_category,
              keywords:keywords,
              second_category: second_category,
              mt:mt,
              st:st,
              grade:grade,
              keyword:keyword,
              total:total,
              imgUrl:config.imgUrl
            }
          } else {
            return {
              courselist: {},
              first_category:{},
              second_category:{},
              mt:'',
              st:'',
              grade:'',
              keyword:'',
              total:0,
              imgUrl:config.imgUrl
            }
          }
    
        },
    

    3.2.4 页面

    在页面中展示课程列表。

    <div class="col-md-3 list-row-rit">
              <div class="list-cont-right">
                <!--精品推荐-->
                <!--#include virtual="/include/BestTop.html"-->
                <!--精品推荐结束-->
                <!--猜你喜欢开始-->
                <div class="right-box">
                  <div class="tit">猜你喜欢</div>
                  <div class="contList">
                    <div class="contList-titB">通过对ThinkPHP框架基础,带领大家由浅入深轻松掌握ThinkPHP的理论基础,更加全面的掌握ThinkPHP框架运行机制……</div>
                    <div class="contList-item">
                      <p>Think PHP 5.0 博客系统实战项目演练</p>
                      <li><span>高级</span> <em> · </em> 1125人在学习</li>
                    </div>
                    <div class="contList-item">
                      <p>Think PHP 5.0 博客系统实战项目演练</p>
                      <li><span>高级</span> <em> · </em> 1125人在学习</li>
                    </div>
                    <div class="contList-item">
                      <p>Think PHP 5.0 博客系统实战项目演练</p>
                      <li><span>高级</span> <em> · </em> 1125人在学习</li>
                    </div>
                    <div class="contList-item">
                      <p>Think PHP 5.0 博客系统实战项目演练</p>
                      <li><span>高级</span> <em> · </em> 1125人在学习</li>
                    </div>
                  </div>
                </div>
                <!--猜你喜欢结束-->
              </div>
            </div>
    

    3.3 分页查询

    3.3.1 前端代码

    使用Element-UI的el-pagination分页插件:

    <div style="text-align: center">
                <el-pagination
                  background
                  layout="prev, pager, next"
                  @current-change="handleCurrentChange"
                  :total="total"
                  :page-size="page_size"
                  prev-text="上一页"
                  next-text="下一页">
                </el-pagination>
    </div>
    

    定义分页触发方法:

    methods: {
          //分页触发
          handleCurrentChange(page) {
            this.page = page
            let query = Object.assign({}, this.$route.query);
            query.page = page
            let querys = querystring.stringify(query)
    //        console.log(querys)
            this.$router.push(`/course/search?`+querys)
          },
    	.......
    }
    

    3.3.2 后端代码

    			//设置分页参数
            if(page<=0){
                page = 1;
            }
            if(size<=0){
                size = 12;
            }
            //起始记录下标
            int from  = (page-1)*size;
            searchSourceBuilder.from(from);
            searchSourceBuilder.size(size);
    

    3.4 按分类搜索

    3.4.1 需求分析

    1、通过一级分类搜索
    2、选择一级分类后将显示下属的二级分类
    3、选择二分类进行搜索
    4、选择一级分类的全部则表示没有按照分类搜索
    5、选择一级分类的全部时二级分类不显示
    

    3.4.2 API方法

    课程分类将通过页面静态化的方式写入静态资源下,通过/category/category.json可访问,通过 www.xuecheng.com/static/category/category.json即可访问。

    category.json的内容如下:

    {
    category: [
    {
    children: [
    {
    children: [
    {
    id: "1-1-1",
    isleaf: "1",
    isshow: "1",
    label: "HTML/CSS",
    name: "HTML/CSS",
    orderby: 1,
    parentid: "1-1"
    },
    {
    id: "1-1-2",
    isleaf: "1",
    isshow: "1",
    label: "JavaScript",
    name: "JavaScript",
    orderby: 2,
    parentid: "1-1"
    },
    {
    id: "1-1-3",
    isleaf: "1",
    isshow: "1",
    label: "jQuery",
    name: "jQuery",
    orderby: 3,
    parentid: "1-1"
    },
    {
    id: "1-1-4",
    isleaf: "1",
    isshow: "1",
    label: "ExtJS",
    name: "ExtJS",
    orderby: 4,
    parentid: "1-1"
    },
    {
    id: "1-1-5",
    isleaf: "1",
    isshow: "1",
    label: "AngularJS",
    name: "AngularJS",
    orderby: 5,
    parentid: "1-1"
    },
    {
    id: "1-1-6",
    isleaf: "1",
    isshow: "1",
    label: "ReactJS",
    name: "ReactJS",
    orderby: 6,
    parentid: "1-1"
    },
    {
    id: "1-1-7",
    isleaf: "1",
    isshow: "1",
    label: "Bootstrap",
    name: "Bootstrap",
    orderby: 7,
    parentid: "1-1"
    },
    {
    id: "1-1-8",
    isleaf: "1",
    isshow: "1",
    label: "Node.js",
    name: "Node.js",
    orderby: 8,
    parentid: "1-1"
    },
    {
    id: "1-1-9",
    isleaf: "1",
    isshow: "1",
    label: "Vue",
    name: "Vue",
    orderby: 9,
    parentid: "1-1"
    },
    {
    id: "1-1-10",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 10,
    parentid: "1-1"
    }
    ],
    id: "1-1",
    isleaf: "0",
    isshow: "1",
    label: "前端开发",
    name: "前端开发",
    orderby: 1,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-2-1",
    isleaf: "1",
    isshow: "1",
    label: "微信开发",
    name: "微信开发",
    orderby: 1,
    parentid: "1-2"
    },
    {
    id: "1-2-2",
    isleaf: "1",
    isshow: "1",
    label: "iOS",
    name: "iOS",
    orderby: 2,
    parentid: "1-2"
    },
    {
    id: "1-2-3",
    isleaf: "1",
    isshow: "1",
    label: "手游开发",
    name: "手游开发",
    orderby: 3,
    parentid: "1-2"
    },
    {
    id: "1-2-4",
    isleaf: "1",
    isshow: "1",
    label: "Swift",
    name: "Swift",
    orderby: 4,
    parentid: "1-2"
    },
    {
    id: "1-2-5",
    isleaf: "1",
    isshow: "1",
    label: "Android",
    name: "Android",
    orderby: 5,
    parentid: "1-2"
    },
    {
    id: "1-2-6",
    isleaf: "1",
    isshow: "1",
    label: "ReactNative",
    name: "ReactNative",
    orderby: 6,
    parentid: "1-2"
    },
    {
    id: "1-2-7",
    isleaf: "1",
    isshow: "1",
    label: "Cordova",
    name: "Cordova",
    orderby: 7,
    parentid: "1-2"
    },
    {
    id: "1-2-8",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 8,
    parentid: "1-2"
    }
    ],
    id: "1-2",
    isleaf: "0",
    isshow: "1",
    label: "移动开发",
    name: "移动开发",
    orderby: 2,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-3-1",
    isleaf: "1",
    isshow: "1",
    label: "C/C++",
    name: "C/C++",
    orderby: 1,
    parentid: "1-3"
    },
    {
    id: "1-3-2",
    isleaf: "1",
    isshow: "1",
    label: "Java",
    name: "Java",
    orderby: 2,
    parentid: "1-3"
    },
    {
    id: "1-3-3",
    isleaf: "1",
    isshow: "1",
    label: ".NET",
    name: ".NET",
    orderby: 3,
    parentid: "1-3"
    },
    {
    id: "1-3-4",
    isleaf: "1",
    isshow: "1",
    label: "Objective-C",
    name: "Objective-C",
    orderby: 4,
    parentid: "1-3"
    },
    {
    id: "1-3-5",
    isleaf: "1",
    isshow: "1",
    label: "Go语言",
    name: "Go语言",
    orderby: 5,
    parentid: "1-3"
    },
    {
    id: "1-3-6",
    isleaf: "1",
    isshow: "1",
    label: "Python",
    name: "Python",
    orderby: 6,
    parentid: "1-3"
    },
    {
    id: "1-3-7",
    isleaf: "1",
    isshow: "1",
    label: "Ruby/Rails",
    name: "Ruby/Rails",
    orderby: 7,
    parentid: "1-3"
    },
    {
    id: "1-3-8",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 8,
    parentid: "1-3"
    }
    ],
    id: "1-3",
    isleaf: "0",
    isshow: "1",
    label: "编程开发",
    name: "编程开发",
    orderby: 3,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-4-1",
    isleaf: "1",
    isshow: "1",
    label: "Oracle",
    name: "Oracle",
    orderby: 1,
    parentid: "1-4"
    },
    {
    id: "1-4-2",
    isleaf: "1",
    isshow: "1",
    label: "MySQL",
    name: "MySQL",
    orderby: 2,
    parentid: "1-4"
    },
    {
    id: "1-4-3",
    isleaf: "1",
    isshow: "1",
    label: "SQL Server",
    name: "SQL Server",
    orderby: 3,
    parentid: "1-4"
    },
    {
    id: "1-4-4",
    isleaf: "1",
    isshow: "1",
    label: "DB2",
    name: "DB2",
    orderby: 4,
    parentid: "1-4"
    },
    {
    id: "1-4-5",
    isleaf: "1",
    isshow: "1",
    label: "NoSQL",
    name: "NoSQL",
    orderby: 5,
    parentid: "1-4"
    },
    {
    id: "1-4-6",
    isleaf: "1",
    isshow: "1",
    label: "Mongo DB",
    name: "Mongo DB",
    orderby: 6,
    parentid: "1-4"
    },
    {
    id: "1-4-7",
    isleaf: "1",
    isshow: "1",
    label: "Hbase",
    name: "Hbase",
    orderby: 7,
    parentid: "1-4"
    },
    {
    id: "1-4-8",
    isleaf: "1",
    isshow: "1",
    label: "数据仓库",
    name: "数据仓库",
    orderby: 8,
    parentid: "1-4"
    },
    {
    id: "1-4-9",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 9,
    parentid: "1-4"
    }
    ],
    id: "1-4",
    isleaf: "0",
    isshow: "1",
    label: "数据库",
    name: "数据库",
    orderby: 4,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-5-1",
    isleaf: "1",
    isshow: "1",
    label: "机器学习",
    name: "机器学习",
    orderby: 1,
    parentid: "1-5"
    },
    {
    id: "1-5-2",
    isleaf: "1",
    isshow: "1",
    label: "深度学习",
    name: "深度学习",
    orderby: 2,
    parentid: "1-5"
    },
    {
    id: "1-5-3",
    isleaf: "1",
    isshow: "1",
    label: "语音识别",
    name: "语音识别",
    orderby: 3,
    parentid: "1-5"
    },
    {
    id: "1-5-4",
    isleaf: "1",
    isshow: "1",
    label: "计算机视觉",
    name: "计算机视觉",
    orderby: 4,
    parentid: "1-5"
    },
    {
    id: "1-5-5",
    isleaf: "1",
    isshow: "1",
    label: "NLP",
    name: "NLP",
    orderby: 5,
    parentid: "1-5"
    },
    {
    id: "1-5-6",
    isleaf: "1",
    isshow: "1",
    label: "强化学习",
    name: "强化学习",
    orderby: 6,
    parentid: "1-5"
    },
    {
    id: "1-5-7",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 7,
    parentid: "1-5"
    }
    ],
    id: "1-5",
    isleaf: "0",
    isshow: "1",
    label: "人工智能",
    name: "人工智能",
    orderby: 5,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-6-1",
    isleaf: "1",
    isshow: "1",
    label: "Spark",
    name: "Spark",
    orderby: 1,
    parentid: "1-6"
    },
    {
    id: "1-6-2",
    isleaf: "1",
    isshow: "1",
    label: "Hadoop",
    name: "Hadoop",
    orderby: 2,
    parentid: "1-6"
    },
    {
    id: "1-6-3",
    isleaf: "1",
    isshow: "1",
    label: "OpenStack",
    name: "OpenStack",
    orderby: 3,
    parentid: "1-6"
    },
    {
    id: "1-6-4",
    isleaf: "1",
    isshow: "1",
    label: "Docker/K8S",
    name: "Docker/K8S",
    orderby: 4,
    parentid: "1-6"
    },
    {
    id: "1-6-5",
    isleaf: "1",
    isshow: "1",
    label: "云计算基础架构",
    name: "云计算基础架构",
    orderby: 5,
    parentid: "1-6"
    },
    {
    id: "1-6-6",
    isleaf: "1",
    isshow: "1",
    label: "虚拟化技术",
    name: "虚拟化技术",
    orderby: 6,
    parentid: "1-6"
    },
    {
    id: "1-6-7",
    isleaf: "1",
    isshow: "1",
    label: "云平台",
    name: "云平台",
    orderby: 7,
    parentid: "1-6"
    },
    {
    id: "1-6-8",
    isleaf: "1",
    isshow: "1",
    label: "ELK",
    name: "ELK",
    orderby: 8,
    parentid: "1-6"
    },
    {
    id: "1-6-9",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 9,
    parentid: "1-6"
    }
    ],
    id: "1-6",
    isleaf: "0",
    isshow: "1",
    label: "云计算/大数据",
    name: "云计算/大数据",
    orderby: 6,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-7-1",
    isleaf: "1",
    isshow: "1",
    label: "Photoshop",
    name: "Photoshop",
    orderby: 1,
    parentid: "1-7"
    },
    {
    id: "1-7-2",
    isleaf: "1",
    isshow: "1",
    label: "3Dmax",
    name: "3Dmax",
    orderby: 2,
    parentid: "1-7"
    },
    {
    id: "1-7-3",
    isleaf: "1",
    isshow: "1",
    label: "Illustrator",
    name: "Illustrator",
    orderby: 3,
    parentid: "1-7"
    },
    {
    id: "1-7-4",
    isleaf: "1",
    isshow: "1",
    label: "Flash",
    name: "Flash",
    orderby: 4,
    parentid: "1-7"
    },
    {
    id: "1-7-5",
    isleaf: "1",
    isshow: "1",
    label: "Maya",
    name: "Maya",
    orderby: 5,
    parentid: "1-7"
    },
    {
    id: "1-7-6",
    isleaf: "1",
    isshow: "1",
    label: "AUTOCAD",
    name: "AUTOCAD",
    orderby: 6,
    parentid: "1-7"
    },
    {
    id: "1-7-7",
    isleaf: "1",
    isshow: "1",
    label: "UG",
    name: "UG",
    orderby: 7,
    parentid: "1-7"
    },
    {
    id: "1-7-8",
    isleaf: "1",
    isshow: "1",
    label: "SolidWorks",
    name: "SolidWorks",
    orderby: 8,
    parentid: "1-7"
    },
    {
    id: "1-7-9",
    isleaf: "1",
    isshow: "1",
    label: "CorelDraw",
    name: "CorelDraw",
    orderby: 9,
    parentid: "1-7"
    },
    {
    id: "1-7-10",
    isleaf: "1",
    isshow: "1",
    label: "InDesign",
    name: "InDesign",
    orderby: 10,
    parentid: "1-7"
    },
    {
    id: "1-7-11",
    isleaf: "1",
    isshow: "1",
    label: "Pro/Engineer",
    name: "Pro/Engineer",
    orderby: 11,
    parentid: "1-7"
    },
    {
    id: "1-7-12",
    isleaf: "1",
    isshow: "1",
    label: "Cinema 4D",
    name: "Cinema 4D",
    orderby: 12,
    parentid: "1-7"
    },
    {
    id: "1-7-13",
    isleaf: "1",
    isshow: "1",
    label: "3D Studio",
    name: "3D Studio",
    orderby: 13,
    parentid: "1-7"
    },
    {
    id: "1-7-14",
    isleaf: "1",
    isshow: "1",
    label: "After Effects(AE)",
    name: "After Effects(AE)",
    orderby: 14,
    parentid: "1-7"
    },
    {
    id: "1-7-15",
    isleaf: "1",
    isshow: "1",
    label: "原画设计",
    name: "原画设计",
    orderby: 15,
    parentid: "1-7"
    },
    {
    id: "1-7-16",
    isleaf: "1",
    isshow: "1",
    label: "动画制作",
    name: "动画制作",
    orderby: 16,
    parentid: "1-7"
    },
    {
    id: "1-7-17",
    isleaf: "1",
    isshow: "1",
    label: "Dreamweaver",
    name: "Dreamweaver",
    orderby: 17,
    parentid: "1-7"
    },
    {
    id: "1-7-18",
    isleaf: "1",
    isshow: "1",
    label: "Axure",
    name: "Axure",
    orderby: 18,
    parentid: "1-7"
    },
    {
    id: "1-7-19",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 19,
    parentid: "1-7"
    }
    ],
    id: "1-7",
    isleaf: "0",
    isshow: "1",
    label: "UI设计",
    name: "UI设计",
    orderby: 7,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-8-1",
    isleaf: "1",
    isshow: "1",
    label: "Cocos",
    name: "Cocos",
    orderby: 1,
    parentid: "1-8"
    },
    {
    id: "1-8-2",
    isleaf: "1",
    isshow: "1",
    label: "Unity3D",
    name: "Unity3D",
    orderby: 2,
    parentid: "1-8"
    },
    {
    id: "1-8-3",
    isleaf: "1",
    isshow: "1",
    label: "Flash",
    name: "Flash",
    orderby: 3,
    parentid: "1-8"
    },
    {
    id: "1-8-4",
    isleaf: "1",
    isshow: "1",
    label: "SpriteKit 2D",
    name: "SpriteKit 2D",
    orderby: 4,
    parentid: "1-8"
    },
    {
    id: "1-8-5",
    isleaf: "1",
    isshow: "1",
    label: "Unreal",
    name: "Unreal",
    orderby: 5,
    parentid: "1-8"
    },
    {
    id: "1-8-6",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 6,
    parentid: "1-8"
    }
    ],
    id: "1-8",
    isleaf: "0",
    isshow: "1",
    label: "游戏开发",
    name: "游戏开发",
    orderby: 8,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-9-1",
    isleaf: "1",
    isshow: "1",
    label: "无线通信",
    name: "无线通信",
    orderby: 1,
    parentid: "1-9"
    },
    {
    id: "1-9-2",
    isleaf: "1",
    isshow: "1",
    label: "电子工程",
    name: "电子工程",
    orderby: 2,
    parentid: "1-9"
    },
    {
    id: "1-9-3",
    isleaf: "1",
    isshow: "1",
    label: "Arduino",
    name: "Arduino",
    orderby: 3,
    parentid: "1-9"
    },
    {
    id: "1-9-4",
    isleaf: "1",
    isshow: "1",
    label: "体感技术",
    name: "体感技术",
    orderby: 4,
    parentid: "1-9"
    },
    {
    id: "1-9-5",
    isleaf: "1",
    isshow: "1",
    label: "智能硬件",
    name: "智能硬件",
    orderby: 5,
    parentid: "1-9"
    },
    {
    id: "1-9-6",
    isleaf: "1",
    isshow: "1",
    label: "驱动/内核开发",
    name: "驱动/内核开发",
    orderby: 6,
    parentid: "1-9"
    },
    {
    id: "1-9-7",
    isleaf: "1",
    isshow: "1",
    label: "单片机/工控",
    name: "单片机/工控",
    orderby: 7,
    parentid: "1-9"
    },
    {
    id: "1-9-8",
    isleaf: "1",
    isshow: "1",
    label: "WinCE",
    name: "WinCE",
    orderby: 8,
    parentid: "1-9"
    },
    {
    id: "1-9-9",
    isleaf: "1",
    isshow: "1",
    label: "嵌入式",
    name: "嵌入式",
    orderby: 9,
    parentid: "1-9"
    },
    {
    id: "1-9-10",
    isleaf: "1",
    isshow: "1",
    label: "物联网技术",
    name: "物联网技术",
    orderby: 10,
    parentid: "1-9"
    },
    {
    id: "1-9-11",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 11,
    parentid: "1-9"
    }
    ],
    id: "1-9",
    isleaf: "0",
    isshow: "1",
    label: "智能硬件/物联网",
    name: "智能硬件/物联网",
    orderby: 9,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-10-1",
    isleaf: "1",
    isshow: "1",
    label: "敏捷开发",
    name: "敏捷开发",
    orderby: 1,
    parentid: "1-10"
    },
    {
    id: "1-10-2",
    isleaf: "1",
    isshow: "1",
    label: "软件设计",
    name: "软件设计",
    orderby: 2,
    parentid: "1-10"
    },
    {
    id: "1-10-3",
    isleaf: "1",
    isshow: "1",
    label: "软件测试",
    name: "软件测试",
    orderby: 3,
    parentid: "1-10"
    },
    {
    id: "1-10-4",
    isleaf: "1",
    isshow: "1",
    label: "研发管理",
    name: "研发管理",
    orderby: 4,
    parentid: "1-10"
    },
    {
    id: "1-10-5",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 5,
    parentid: "1-10"
    }
    ],
    id: "1-10",
    isleaf: "0",
    isshow: "1",
    label: "研发管理",
    name: "研发管理",
    orderby: 10,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-11-1",
    isleaf: "1",
    isshow: "1",
    label: "Linux",
    name: "Linux",
    orderby: 1,
    parentid: "1-11"
    },
    {
    id: "1-11-2",
    isleaf: "1",
    isshow: "1",
    label: "Windows",
    name: "Windows",
    orderby: 2,
    parentid: "1-11"
    },
    {
    id: "1-11-3",
    isleaf: "1",
    isshow: "1",
    label: "UNIX",
    name: "UNIX",
    orderby: 3,
    parentid: "1-11"
    },
    {
    id: "1-11-4",
    isleaf: "1",
    isshow: "1",
    label: "Mac OS",
    name: "Mac OS",
    orderby: 4,
    parentid: "1-11"
    },
    {
    id: "1-11-5",
    isleaf: "1",
    isshow: "1",
    label: "网络技术",
    name: "网络技术",
    orderby: 5,
    parentid: "1-11"
    },
    {
    id: "1-11-6",
    isleaf: "1",
    isshow: "1",
    label: "路由协议",
    name: "路由协议",
    orderby: 6,
    parentid: "1-11"
    },
    {
    id: "1-11-7",
    isleaf: "1",
    isshow: "1",
    label: "无线网络",
    name: "无线网络",
    orderby: 7,
    parentid: "1-11"
    },
    {
    id: "1-11-8",
    isleaf: "1",
    isshow: "1",
    label: "Ngnix",
    name: "Ngnix",
    orderby: 8,
    parentid: "1-11"
    },
    {
    id: "1-11-9",
    isleaf: "1",
    isshow: "1",
    label: "邮件服务器",
    name: "邮件服务器",
    orderby: 9,
    parentid: "1-11"
    },
    {
    id: "1-11-10",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 10,
    parentid: "1-11"
    }
    ],
    id: "1-11",
    isleaf: "0",
    isshow: "1",
    label: "系统运维",
    name: "系统运维",
    orderby: 11,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-12-1",
    isleaf: "1",
    isshow: "1",
    label: "交互设计",
    name: "交互设计",
    orderby: 1,
    parentid: "1-12"
    },
    {
    id: "1-12-2",
    isleaf: "1",
    isshow: "1",
    label: "产品设计",
    name: "产品设计",
    orderby: 2,
    parentid: "1-12"
    },
    {
    id: "1-12-3",
    isleaf: "1",
    isshow: "1",
    label: "原型设计",
    name: "原型设计",
    orderby: 3,
    parentid: "1-12"
    },
    {
    id: "1-12-4",
    isleaf: "1",
    isshow: "1",
    label: "用户体验",
    name: "用户体验",
    orderby: 4,
    parentid: "1-12"
    },
    {
    id: "1-12-5",
    isleaf: "1",
    isshow: "1",
    label: "需求分析",
    name: "需求分析",
    orderby: 5,
    parentid: "1-12"
    },
    {
    id: "1-12-6",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 6,
    parentid: "1-12"
    }
    ],
    id: "1-12",
    isleaf: "0",
    isshow: "1",
    label: "产品经理",
    name: "产品经理",
    orderby: 12,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-13-1",
    isleaf: "1",
    isshow: "1",
    label: "运营管理",
    name: "运营管理",
    orderby: 1,
    parentid: "1-13"
    },
    {
    id: "1-13-2",
    isleaf: "1",
    isshow: "1",
    label: "企业信息化",
    name: "企业信息化",
    orderby: 2,
    parentid: "1-13"
    },
    {
    id: "1-13-3",
    isleaf: "1",
    isshow: "1",
    label: "网络营销",
    name: "网络营销",
    orderby: 3,
    parentid: "1-13"
    },
    {
    id: "1-13-4",
    isleaf: "1",
    isshow: "1",
    label: "Office/WPS",
    name: "Office/WPS",
    orderby: 4,
    parentid: "1-13"
    },
    {
    id: "1-13-5",
    isleaf: "1",
    isshow: "1",
    label: "招聘/面试",
    name: "招聘/面试",
    orderby: 5,
    parentid: "1-13"
    },
    {
    id: "1-13-6",
    isleaf: "1",
    isshow: "1",
    label: "电子商务",
    name: "电子商务",
    orderby: 6,
    parentid: "1-13"
    },
    {
    id: "1-13-7",
    isleaf: "1",
    isshow: "1",
    label: "CRM",
    name: "CRM",
    orderby: 7,
    parentid: "1-13"
    },
    {
    id: "1-13-8",
    isleaf: "1",
    isshow: "1",
    label: "ERP",
    name: "ERP",
    orderby: 8,
    parentid: "1-13"
    },
    {
    id: "1-13-9",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 9,
    parentid: "1-13"
    }
    ],
    id: "1-13",
    isleaf: "0",
    isshow: "1",
    label: "企业/办公/职场",
    name: "企业/办公/职场",
    orderby: 13,
    parentid: "1"
    },
    {
    children: [
    {
    id: "1-14-1",
    isleaf: "1",
    isshow: "1",
    label: "密码学/加密/破解",
    name: "密码学/加密/破解",
    orderby: 1,
    parentid: "1-14"
    },
    {
    id: "1-14-2",
    isleaf: "1",
    isshow: "1",
    label: "渗透测试",
    name: "渗透测试",
    orderby: 2,
    parentid: "1-14"
    },
    {
    id: "1-14-3",
    isleaf: "1",
    isshow: "1",
    label: "社会工程",
    name: "社会工程",
    orderby: 3,
    parentid: "1-14"
    },
    {
    id: "1-14-4",
    isleaf: "1",
    isshow: "1",
    label: "漏洞挖掘与利用",
    name: "漏洞挖掘与利用",
    orderby: 4,
    parentid: "1-14"
    },
    {
    id: "1-14-5",
    isleaf: "1",
    isshow: "1",
    label: "云安全",
    name: "云安全",
    orderby: 5,
    parentid: "1-14"
    },
    {
    id: "1-14-6",
    isleaf: "1",
    isshow: "1",
    label: "防护加固",
    name: "防护加固",
    orderby: 6,
    parentid: "1-14"
    },
    {
    id: "1-14-7",
    isleaf: "1",
    isshow: "1",
    label: "代码审计",
    name: "代码审计",
    orderby: 7,
    parentid: "1-14"
    },
    {
    id: "1-14-8",
    isleaf: "1",
    isshow: "1",
    label: "移动安全",
    name: "移动安全",
    orderby: 8,
    parentid: "1-14"
    },
    {
    id: "1-14-9",
    isleaf: "1",
    isshow: "1",
    label: "病毒木马",
    name: "病毒木马",
    orderby: 9,
    parentid: "1-14"
    },
    {
    id: "1-14-10",
    isleaf: "1",
    isshow: "1",
    label: "其它",
    name: "其它",
    orderby: 10,
    parentid: "1-14"
    }
    ],
    id: "1-14",
    isleaf: "0",
    isshow: "1",
    label: "信息安全",
    name: "信息安全",
    orderby: 14,
    parentid: "1"
    }
    ],
    id: "1",
    isleaf: "0",
    isshow: "1",
    label: "根结点",
    name: "根结点",
    orderby: 1,
    parentid: "0"
    }
    ]
    }
    

    我们需要定义api方法获取所有的分类

    在/api/course.js中添加:

    /*获取分类*/
    export const sysres_category = () => {
      return http.requestQuickGet(staticURL+"/category/category.json");
    }
    

    项目已经添加

    3.4.3 在asyncData中查询分类

    进入搜索页面将默认显示所有一级分类,当前如果已选择一级分类则要显示所有一级分类及该一级分类下属的二级 分类。

    在asyncData方法中实现上边的需求,代码如下:

    async asyncData({ store, route }) {
    //       console.log(route.query)
    
          let page = route.query.page;
           if(!page){
               page = 1;
           }
    //       console.log("page="+page)
          //搜索课程
          let course_data = await courseApi.search_course(page,12,route.query)
          console.log(course_data)
          //查询分类
          let category_data = await courseApi.sysres_category()
    //      console.log(category_data)
    //      console.log(course_data.hits.hits)
    
          if (course_data && course_data.hits &&  course_data.hits.hits && category_data) {
            //全部分类
            let category = category_data.category
            let first_category = category[0].children
            let keywords = ''
            let second_category=[]
            let mt=''
            let st=''
            let grade=''
            let keyword=''
            let total = course_data.hits.total
            if( route.query.mt){
              mt = route.query.mt
            }
            if( route.query.st){
              st = route.query.st
            }
            if( route.query.grade){
              grade = route.query.grade
            }
            if( route.query.keyword){
              keyword = route.query.keyword
            }
            //遍历一级分类
            for(var i in first_category){
              keywords+=first_category[i].name+' '
              if(mt!=''&& mt == first_category[i].id){
                //取出二级分类
                second_category = first_category[i].children;
                // console.log(second_category)
                break;
              }
            }
            //console.log(category[0].children)
            return {
              courselist: course_data.hits.hits,
              first_category: first_category,
              keywords:keywords,
              second_category: second_category,
              mt:mt,
              st:st,
              grade:grade,
              keyword:keyword,
              total:total,
              imgUrl:config.imgUrl
            }
          } else {
            return {
              courselist: {},
              first_category:{},
              second_category:{},
              mt:'',
              st:'',
              grade:'',
              keyword:'',
              total:0,
              imgUrl:config.imgUrl
            }
          }
    
        },
    

    3.3.4 页面

    在页面显示一级分类及二级分类,需要根据当前是否选择一级分类、是否选择二分类显示页面内容。

    <ul>
              <li>一级分类:</li>
              <li v-if="mt!=''"><nuxt-link  class="title-link" :to="'/course/search?keyword='+keyword+'&grade='+grade">全部</nuxt-link></li>
              <li class="all" v-else>全部</li>
              <ol>
              <li v-for="category_v in first_category">
                <nuxt-link  class="title-link all" :to="'/course/search?keyword='+keyword+'&mt=' + category_v.id" v-if="category_v.id == mt">{{category_v.name}}</nuxt-link>
                <nuxt-link  class="title-link" :to="'/course/search?keyword='+keyword+'&mt=' + category_v.id" v-else>{{category_v.name}}</nuxt-link>
              </li>
              </ol>
              <!--<ol>
                <li>数据分析</li>
                <li>机器学习工程</li>
                <li>前端开发工程</li>
              </ol>-->
            </ul>
            <ul>
              <li>二级分类:</li>
              <li v-if="st!=''"><nuxt-link  class="title-link" :to="'/course/search?keyword='+keyword+'&mt='+mt+'&grade='+grade">全部</nuxt-link></li>
              <li class="all" v-else>全部</li>
              <ol v-if="second_category.length>0">
                <li v-for="category_v in second_category">
                  <nuxt-link  class="title-link all" :to="'/course/search?keyword='+keyword+'&mt='+mt+'&st=' + category_v.id" v-if="category_v.id == st">{{category_v.name}}</nuxt-link>
                  <nuxt-link  class="title-link" :to="'/course/search?keyword='+keyword+'&mt='+mt+'&st=' + category_v.id" v-else>{{category_v.name}}</nuxt-link>
                </li>
               <!-- <li>大数据</li>
                <li>云计算</li>-->
              </ol>
              <!--<a href="#" class="more">更多 ∨</a>-->
            </ul>
    

    3.3.5 立即搜索

    当用户点击分类时立即执行搜索,实现思路如下:

    1)点击分类立即更改路由。
    2)通过监听路由,路由更改则刷新页面。
    

    1)创建搜索方法(项目中代码已经写好)

    search(){ //刷新当前页面 
    	window.location.reload(); 
    }
    

    2)定义watch

    通过vue.js的watch可以实现监视某个变量,当变量值出现变化时执行某个方法。

    实现思路是:

    1、点击分类页面路由更改
    2、通过watch监视路由,路由更改触发search方法
    

    与methods并行定义watch:

    watch:{//路由发生变化立即搜索search表示search方法
          '$route':'search'
    },
    

    3.5 按难度等级搜索

    3.5.1 需求分析

    用户选择不同的课程难度等级去搜索课程。

    3.5.2 API方法

    使用 search_course 方法完成搜索。

    3.5.3页面

    按难度等级搜索思路如下:

    1)点击难度等级立即更改路由。
    2)通过监听路由,路由更改则立即执行search搜索方法。
    

    按难度等级搜索页面代码如下:

    <ul>
              <li>难度等级:</li>
              <li v-if="grade!=''">
                <nuxt-link  class="title-link" :to="'/course/search?keyword='+keyword+'&mt=' + mt+'&st='+st+'&grade='">全部
                </nuxt-link>
              </li>
              <li class="all" v-else>全部</li>
              <ol>
                <li v-if="grade=='200001'" class="all">初级</li>
                <li v-else><nuxt-link  class="title-link" :to="'/course/search?keyword='+keyword+'&mt=' + mt+'&st='+st+'&grade=200001'">初级</nuxt-link></li>
                <li v-if="grade=='200002'" class="all">中级</li>
                <li v-else><nuxt-link  class="title-link" :to="'/course/search?keyword='+keyword+'&mt=' + mt+'&st='+st+'&grade=200002'">中级</nuxt-link></li>
                <li v-if="grade=='200003'" class="all">高级</li>
                <li v-else><nuxt-link  class="title-link" :to="'/course/search?keyword='+keyword+'&mt=' + mt+'&st='+st+'&grade=200003'">高级</nuxt-link></li>
              </ol>
            </ul>
    

    3.6 高亮显示

    3.6.1 服务端代码

    修改service的搜索方法,添加高亮设置:

    				//定义高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.preTags("<font class='eslight'>");
            highlightBuilder.postTags("</font>");
            highlightBuilder.fields().add(new HighlightBuilder.Field("name"));
            searchSourceBuilder.highlighter(highlightBuilder);
            //解析高亮字段
            for (SearchHit hit : searchHits) {
                CoursePub coursePub = new CoursePub();
                //取出source
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                //课程id
                String id = (String) sourceAsMap.get("id");
                coursePub.setId(id);
                //取出名称
                String name = (String) sourceAsMap.get("name");
                //取出高亮字段
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                if (highlightFields.get("name") != null) {
                    HighlightField highlightField = highlightFields.get("name");
                    Text[] fragments = highlightField.fragments();
                    StringBuffer stringBuffer = new StringBuffer();
                    for (Text text : fragments) {
                        stringBuffer.append(text);
                    }
                    name = stringBuffer.toString();
                }
                coursePub.setName(name);
    

    完整代码:

    package com.xuecheng.search.service;
    
    import com.xuecheng.framework.domain.course.CoursePub;
    import com.xuecheng.framework.domain.search.CourseSearchParam;
    import com.xuecheng.framework.model.response.CommonCode;
    import com.xuecheng.framework.model.response.QueryResponseResult;
    import com.xuecheng.framework.model.response.QueryResult;
    import org.apache.commons.lang3.StringUtils;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.text.Text;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.MultiMatchQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author Administrator
     * @version 1.0
     **/
    @Service
    public class EsCourseService {
    
        @Value("${xuecheng.course.index}")
        private String index;
        @Value("${xuecheng.course.type}")
        private String type;
        @Value("${xuecheng.course.source_field}")
        private String source_field;
    
        @Autowired
        RestHighLevelClient restHighLevelClient;
    
        //课程搜索
        public QueryResponseResult<CoursePub> list(int page, int size, CourseSearchParam courseSearchParam) {
            if(courseSearchParam == null){
                courseSearchParam = new CourseSearchParam();
            }
            //创建搜索请求对象
            SearchRequest searchRequest = new SearchRequest(index);
            //设置搜索类型
            searchRequest.types(type);
    
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            //过虑源字段
            String[] source_field_array = source_field.split(",");
            searchSourceBuilder.fetchSource(source_field_array,new String[]{});
            //创建布尔查询对象
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            //搜索条件
            //根据关键字搜索
            if(StringUtils.isNotEmpty(courseSearchParam.getKeyword())){
                MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(courseSearchParam.getKeyword(), "name", "description", "teachplan")
                        .minimumShouldMatch("70%")
                        .field("name", 10);
                boolQueryBuilder.must(multiMatchQueryBuilder);
            }
            if(StringUtils.isNotEmpty(courseSearchParam.getMt())){
                //根据一级分类
                boolQueryBuilder.filter(QueryBuilders.termQuery("mt",courseSearchParam.getMt()));
            }
            if(StringUtils.isNotEmpty(courseSearchParam.getSt())){
                //根据二级分类
                boolQueryBuilder.filter(QueryBuilders.termQuery("st",courseSearchParam.getSt()));
            }
            if(StringUtils.isNotEmpty(courseSearchParam.getGrade())){
                //根据难度等级
                boolQueryBuilder.filter(QueryBuilders.termQuery("grade",courseSearchParam.getGrade()));
            }
    
            //设置boolQueryBuilder到searchSourceBuilder
            searchSourceBuilder.query(boolQueryBuilder);
            //设置分页参数
            if(page<=0){
                page = 1;
            }
            if(size<=0){
                size = 12;
            }
            //起始记录下标
            int from  = (page-1)*size;
            searchSourceBuilder.from(from);
            searchSourceBuilder.size(size);
    
            //设置高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.preTags("<font class='eslight'>");
            highlightBuilder.postTags("</font>");
            //设置高亮字段
            highlightBuilder.fields().add(new HighlightBuilder.Field("name"));
            searchSourceBuilder.highlighter(highlightBuilder);
    
            searchRequest.source(searchSourceBuilder);
    
            QueryResult<CoursePub> queryResult = new QueryResult();
            List<CoursePub> list = new ArrayList<>();
            try {
                //执行搜索
                SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
                //获取响应结果
                SearchHits hits = searchResponse.getHits();
                //匹配的总记录数
                long totalHits = hits.totalHits;
                queryResult.setTotal(totalHits);
                SearchHit[] searchHits = hits.getHits();
                for(SearchHit hit:searchHits){
                    CoursePub coursePub = new CoursePub();
                    //源文档
                    Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                    //取出id
                    String id = (String)sourceAsMap.get("id");
                    coursePub.setId(id);
                    //取出name
                    String name = (String) sourceAsMap.get("name");
                    //取出高亮字段name
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                    if(highlightFields!=null){
                        HighlightField highlightFieldName = highlightFields.get("name");
                        if(highlightFieldName!=null){
                            Text[] fragments = highlightFieldName.fragments();
                            StringBuffer stringBuffer = new StringBuffer();
                            for(Text text:fragments){
                                stringBuffer.append(text);
                            }
                            name = stringBuffer.toString();
                        }
    
                    }
                    coursePub.setName(name);
                    //图片
                    String pic = (String) sourceAsMap.get("pic");
                    coursePub.setPic(pic);
                    //价格
                    Double price = null;
                    try {
                        if(sourceAsMap.get("price")!=null ){
                            price = (Double) sourceAsMap.get("price");
                        }
    
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    coursePub.setPrice(price);
                    //旧价格
                    Float price_old = null;
                    try {
                        if(sourceAsMap.get("price_old")!=null ){
                            price_old = (Float) sourceAsMap.get("price_old");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    coursePub.setPrice_old(price_old);
                    //将coursePub对象放入list
                    list.add(coursePub);
                }
    
    
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            queryResult.setList(list);
            QueryResponseResult<CoursePub> queryResponseResult = new QueryResponseResult<CoursePub>(CommonCode.SUCCESS,queryResult);
    
            return queryResponseResult;
        }
    }
    
    

    3.6.2 前端代码

    在search/index.vue中定义eslight样式:

    <style>
    	.eslight{ color: red; } 
      ...
    </style>
    

    4 集成测试

    4.1 需求分析

    本次集成测试的目的如下:

    1、测试课程发布与CMS接口是否正常。
    2、测试课程发布与ES接口是否正常。
    3、测试课程从创建到发布的整个过程。
    

    4.2 准备环境

    1、启动MySQL、MongoDB
    2、启动ElasticSearch、RabbitMQ
    3、启动Eureka Server
    4、启动CMS、课程管理服务、搜索服务。
    5、启动Nginx、系统管理前端、教学管理前端、Nuxt.js。
    
  • 相关阅读:
    (转)ANT与RTS的结合
    (转)[Android] 利用 ant 脚本修改项目包名
    (转)MULTIPLE TARGETS FROM ONE ANDROID SOURCE (THE BETTER WAY)
    JS+CSS打造网站头部蓝色简约可自动显示/隐藏的导航菜单
    CSS打造很棒的黑色背景下的导航菜单
    老外JS实现的Infinite Menus
    【荐】JavaScript打造的无限级可刷新树型折叠菜单
    【荐】纯CSS打造超酷的圆角菜单,鼠标移过向上/向下扩张
    来自阿里巴巴网的滑动TAB导航特效
    适用于商城JS实现的可折叠的商品分类导航
  • 原文地址:https://www.cnblogs.com/artwalker/p/13569344.html
Copyright © 2020-2023  润新知