第1章 六何分析法分析uniApp?
一、什么是uniApp(What)
-
uni-app
是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。 -
uni-app
在手,做啥都不愁。即使不跨端,uni-app
也是更好的小程序开发框架(详见)、更好的App跨平台框架、更方便的H5开发框架。不管领导安排什么样的项目,你都可以快速交付,不需要转换开发思维、不需要更改开发习惯。 -
官网地址:https://uniapp.dcloud.net.cn/
二、uniApp的诞生历程(When)
- 很多人以为小程序是微信先推出的,其实,DCloud才是这个行业的开创者。
- DCloud于2012年开始研发小程序技术,优化webview的功能和性能,并加入W3C和HTML5中国产业联盟,推出了HBuilder开发工具,为后续产业化做准备。
- 2015年,DCloud正式商用了自己的小程序,产品名为“流应用”,它不是
B/S
模式的轻应用,而是能接近原生功能、性能的动态App,并且即点即用。 - 在2015年9月,DCloud推进微信团队开展小程序业务,演示了流应用的秒开应用、扫码获取应用、分享链接获取应用等众多场景案例,以及分享了webview体验优化的经验。
- 微信团队经过分析,于2016年初决定上线小程序业务,但其没有接入联盟标准,而是订制了自己的标准。
- 到目前已经经历9年多的发展时间。
三、uniApp与“谁”相关(Who)
- 华为
- 字节跳动
- 美团
- 快手
- 腾讯
- vivo官方商城
- 中华英才网
- 开源中国
- …
- 移动开发工程师
- webApp开发工程师
- 小程序开发工程师
四、uniApp的应用场景有哪些(Where)
五、为什么选择uniApp(Why)
- DCloud 国产
- vue语法,学习成本低,上手速度快,只要之前你做过vue的项目,那么就能很快上手,其实是vue和微信小程序的结合体,一半vue,一半微信小程序。
- 长期维护,之前做微信小程序的时候,选择了美团的mpvue,但是后面发现长期不维护了,提了Issues也没人理,随之就放弃了,而uni-app长期在维护,这样看出了开发团队的用心。
- 跨平台的能力,uni-app能够跨多个终端,H5,安卓,Ios,微信小程序,百度小程序,头条小程序,支付宝小程序,真正实现了一套代码,多端运行,而且很好适应了我国的市场。
- 日益丰富的插件市场,uni的插件市场也在日益强大,能够基本上满足我们平时的开发需求。
- 支持原生代码混写和原生sdk集成
- 开发成本低,不止开发成本,招聘、管理、测试各方面成本都大幅下降。
六、如何学习uniApp(How)
- 掌握Vue相关知识体系内容
- Vue基础语法结构
- 组件化开发模式
- Vuex的概念与应用
- 掌握微信小程序原生开发内容
- 小程序原生组件
- 小程序配置相关
- 小程序路由设置与跳转
- 小程序样式的单位尺寸rpx->upx(用法一样)
- 了解移动端开发的基础知识
- 逐步掌握uniApp开发技巧
七、严选项目的目标分析
- 网站地址: http://m.you.163.com/
- 不像网易云音乐,没有单独的数据接口,所以需要爬取数据处理自己定义的接口操作
八、hbuilder开发工具的安装准备
第2章 uniapp项目文件介绍
一、硅谷商城的运行
-
硅谷商城后台的运行
- npm start,地址:http://localhost:3002
-
硅谷商城小程序项目的运行
- 修改小程序的AppID,manifest.json中的“微信小程序配置”->“微信小程序AppID”
- 修改微信开发者工具,“设置”->“安全设置”->“服务端口”开启
- 修改小程序的AppID,manifest.json中的“微信小程序配置”->“微信小程序AppID”
-
Hbuilder开发工具,“运行”->“运行到小程序模拟器”->“运行时设置”->“微信开发者工具路径”
-
Hbuilder开发工具,“运行”->“运行到小程序模拟器”->“微信开发者工具”
硅谷商城小程序项目的介绍
- 首页
- 分类
- 商品详情页
- 购物车
- 个人中心
二、创建与分析uniApp项目
- 创建与运行uniapp小程序项目,创建的是uni-app默认模板项目,项目名称可以是guigushop
- 修改小程序的AppID,manifest.json中的“微信小程序配置”->“微信小程序AppID”
- 确认微信开发者工具服务端口开启
- 设置过一次,微信开发者工具路径应该默认设置了
- 运行到小程序模拟器需要将之前的项目停止运行,因为只支持一个项目的运行
Hbuilder中的开发项目与微信开发者工具中的运行项目的差异
-
Hbuilder中的开发项目是App.vue、main.js、pages.json、uni.scss以及manifest.json等文件内容,显然小程序并不支持
-
通过Hbuilder的编译将会产生一个unpackage目录,目录结构如下,该目录的内容才是微信开发者工具打开的项目目录内容
3. 微信开发者工具中的代码内容是Hbuilder编译以后的代码内容,代码内容都已经通过webpack前端自动化构建工具操作处理过,所以不可以修改
-
项目根目录下的pages.json配置文件与小程序原生开发的配置文件的差异
- globalStyle与原来的window一致
- pages的从单一路径数组元素变成了数组对象,而对象的第一个参数对应的是小程序原来的pages路由地址,而第二个style则是将小程序的页面参数设置提取到了pages这个公共的统一设置页中配置。在页面中将不再包含原生小程序单一页面的配置内容。
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}
manifest.json的微信小程序配置,安全域名默认是不检查,原生小程序默认是检查的
5. 更多详情配置,官网:https://uniapp.dcloud.net.cn/collocation/pages
-
uni.scss如果进行的自定义UI界面的开发,一般用不到
-
main.js
import Vue from 'vue'
import App from './App' // App组件很特殊,代表的是整个应用,相当于原生中的app.js里创建的App应用实例
Vue.config.productionTip = false // 产品化的时候去除提示信息等内容
// 声明当前组件的类型是 application
App.mpType = 'app'
// 下面的代码必须通过实例挂载的方式进行加载
const app = new Vue({
...App
})
app.$mount()
// 不能使用Vue的渲染模式,因为Vue是DOM渲染,小程序中没有DOM对象
/*
new Vue({
render: h=>h(App)
})
*/
-
App.vue,相当于原生小程序中的app.js+app.wxss
-
pages/index/index.vue,相当于原生小程序中的index.js+index.wxml+index.wxss,index.json已经被迁移到了pages.json进行统一配置
-
index.vue中既有Vue的语法结构,也可以使用小程序的生命周期等内容
第3章 index头部搭建
一、index头部搭建
-
新建页面及自动注册pages.json路由页面相关配置
-
删除pages/index目录,包括index.vue的页面内容
-
删除根目录下pages.json中的pages整个节点信息,只剩下globalStyle节点信息
{ "globalStyle" : { "navigationBarTextStyle" : "black", "navigationBarTitleText" : "uni-app", "navigationBarBackgroundColor" : "#F8F8F8", "backgroundColor" : "#F8F8F8" } }
-
重新新建页面,需注意:包括页面名称、同名目录、文件类型、样式类型以及在pages.json中注册
-
pages.json将自动注册路由地址及页面设置等信息
{ "globalStyle" : { "navigationBarTextStyle" : "black", "navigationBarTitleText" : "uni-app", "navigationBarBackgroundColor" : "#F8F8F8", "backgroundColor" : "#F8F8F8" }, "pages" : [ { "path" : "pages/index/index", "style" : { "navigationBarTitleText" : "", "enablePullDownRefresh" : false } } ] }
-
设置navigationBarTitleText的属性为“首页”
-
将原来的项目static静态资源目录复制到当前项目当中,主要包括images、iconfont等内容,项目中需要使用它们
-
设置头部的结构内容
<template> <view class="indexContainer"> <view class="header"> <image src=""></image> <view class="search"> <input type="text" placeholder="搜索商品" /> </view> <button>子心</button> </view> </view> </template>
-
因为样式使用的是stylus,而stylus在vue项目中需要配置stylus-loader加载器进行webpack解析转化操作
-
在Hbuilder中需要在工具->插件安装中进行插件的安装,插件安装需要有dcloud的帐号,所以需要注册与登录
HBuilder的编译转化及格式化等插件主要集中在“HBuilderX”栏目目录下
找到插件以后可以“使用HBuilderX导入插件”进行插件安装
-
dcloud插件市场除了HbuilderX的插件内容,还包含了其它众多的插件或者是模板内容,可以进行直接应用操作
-
在项目创建的时候,还可以使用模板方式进行创建,可以新建并进行运行尝试
-
给头部内容添加class及图片等内容,image的图片以及logo样式类,还有input的placeholader-class属性等
<view class="indexContainer"> <view class="header"> <image src="/static/images/logo.png" class="logo"></image> <view class="search"> <input type="text" placeholder="搜索商品" placeholder-class="placeholder"/> </view> <button>子心</button> </view> </view>
-
给头部内容设置stylus样式
.indexContainer .header display flex padding 10rpx .logo width 140rpx height 40rpx margin 10rpx 30rpx button /* button 按钮会自带内置的样式内容,比如padding等,需要覆盖设置 */ width 144rpx height 60rpx border none /* 去除边框不起作用 */ color #bb2c08 text-align center line-height 60rpx font-size 24rpx padding 0 6rpx &:after /* 去除边框需要设置伪类after,并将border内容去除 */ border none .search flex 1 /* flex 设置自动伸缩 */ border 1px solid #eee height 60rpx margin 0 20rpx input width 100% height 100% .placeholder font-size 24rpx text-align center
-
导航区域布局设置,enable-flex不起作用,需要利用样式设置navItem的display为inline-block模式
<scroll-view scroll-x="true" class="navScroll"> <view class="navItem">居家生活</view> ... </scroll-view>
-
导航样式设置
.navScroll /* display flex 去除flex布局 */ white-space nowrap .navItem display inline-block /* display flex 修改为inline-block */ width 140rpx height 80rpx text-align center line-height 80rpx font-size 26rpx
-
第4章 前后端通信实现
一、前后端通信实现
-
request功能的封装
-
新建utils目录,并在该目录下新建config.js与request.js两个文件
-
config.js地址参数配置
export default { host:'http://localhost:3002' }
-
request功能的封装,利用的是uni.request
import config from './config' export default (url,data={},method="GET")=>{ return new Promise((reslove,reject)=>{ uni.request({ url:config.host + url, data, method, success: (res) => { reslove(res.data) },fail: (err) => { reject(err) } }) }) }
-
-
利用Koa构建后台接口服务器
-
新建后端项目目录guigushop_server
-
npm init项目初始化
-
npm i koa koa-router --save,安装koa以及koa路由模块
-
新建servre.js接口服务文件
let Koa = require('koa'); let KoaRouter = require('koa-router'); // 实例化koa以及router路由 const app = new Koa(); const router = new KoaRouter(); // 利用ctx进行内容返回操作 router.get('/', (ctx, next) => { ctx.body = 'hello koa' }) // 主页数据 let indexData = require('./datas/index.json'); router.get('/getIndexData', (ctx, next) => { ctx.body = indexData }); // app实例使用router路由及限制allowedMethods请求方式的允许内容 app .use(router.routes()) .use(router.allowedMethods()) app.listen('3002', () => { console.log('服务器启动'); console.log('服务器地址: http://localhost:3002') })
-
修改package.json的scripts节点信息,设置start启动方式
{ "name": "wangyi_server", "version": "1.0.0", "dependencies": { "koa": "^2.8.2", "koa-router": "^7.4.0", "nodemon": "^1.19.4" }, "scripts": { "start": "nodemon server.js" } }
-
运行npm start启动接口服务
-
-
在页面中进行数据请求
<script> import request from '../../utils/request.js' export default { data() { return {}; }, // 生命周期钩子函数中一般不使用async,所以可以将获取数据操作单独封装于methods中 mounted() { this.getIndexData() }, methods:{ async getIndexData(){ let result = await request('/getIndexData') console.log(result) } } }; </script>
第5章 Vuex的使用
一、Vuex
-
为什么需要使用Vuex,它的作用是什么
- 集中管理状态数据
- 用于给多个组件共享数据
-
Vuex的基本概念
- store 数据仓库是store
- state: 设置状态state ->mapState
- getters:获取数据getters 根据已有的状态数据计算得到新的状态数据,等同于Vue中的computed -> mapGetters
- mutations: 修改数据mutations(同步修改)->mapMutations
- actions: 异步操作actions(异步获取异步数据,同步触发mutation,将异步数据交给mutation)->mapActions
- modules: 模块拆分modules(用于模块化管理)
- commit: 用于触发mutation
- dispatch: 用于分发action
-
vuex模块的安装与使用
-
安装,利用项目右键,使用命令行窗口打开所在目录
-
npm install vuex --save,安装vuex
-
在根目录下创建store目录,新建立index.js
import Vue from 'vue' import Vuex from 'vuex' import indexModule from './modules/index' // index模块 Vue.use(Vuex) export default new Vuex.Store({ modules: { indexModule, }, })
-
在store目录下新建modules模块目录,并新建index.js首页模块,需要注意namespaced的设置,默认值是false,需要设置为true,并设置initData初始数据以便测试
import request from '../../utils/request.js' const state = { initData: '初始化数据' } const mutations = {} const actions = {} const getters = {} export default { namespaced:true, // 支持命名空间 state, mutations, actions, getters }
-
在首页vue文件中调出state状态值,注意几种情况:
- 不使用命名空间的取值,需要利用对象及箭头函数通过state获取状态值,并且需要注意模块名称
- 利用命名空间的方式进行状态值的获取,第一个参数是模块名称,第二个参数是数组,可以设置多个状态值
<script> import request from '../../utils/request.js' import { mapState } from 'vuex' export default { ... computed:{ /* // 1.不使用命名空间的state取值法 ...mapState({ initData: state => state.indexModule.initData }) */ ...mapState('indexModule',['initData']) // 2.mapState可以使用命名空间内容 } }; </script>
-
-
设置首页数据
-
默认数据的设置
-
mutation修改数据的处理,payload是载荷,也就是参数
-
actions异步请求数据的操作,commit到mutation进行同步数据修改
import request from '../../utils/request.js' const state = { initData: '初始化数据', indexData:{} } const mutations = { // 第一个参数是state状态,第二个参数是payload载荷 // 因为mutation只支持2个参数,如果需要传递多个参数,则payload是对象或数组 changeIndexDataMutation(state,payload){ state.indexData = payload } } const actions = { async getIndexDataAction({commit}){ let result = await request('/getIndexData'); commit('changeIndexDataMutation',result) } } const getters = {} export default { namespaced:true, state, mutations, actions, getters }
-
-
调用action方法
-
直接dispatch方法,需要传递命名空间路径
-
mapActions映射数组,映射时需要写明全路径参数,利用this[ ‘全路径’ ]()方式进行调用,注意()的调用
-
mapActions映射,第一个参数是模块路径,第二个参数是action方法名称,在调用的时候只需要this.actionName()即可
<script> import request from '../../utils/request.js' import { mapState,mapActions } from 'vuex' export default { data() { return {}; }, computed:{ ...mapState('indexModule',['initData','indexData']) }, mounted() { // this.$store.dispatch('indexModule/getIndexDataAction') // 第一种 // this['indexModule/getIndexDataAction']() // 第二种 this.getIndexDataAction(); // 第三种 }, methods:{ ...mapActions('indexModule',[ 'getIndexDataAction' ]) // 第三种 // ...mapActions(['indexModule/getIndexDataAction']) // 第二种 } }; </script>
-
第6章 导航数据动态显示
一、导航数据动态显示
-
导航数据来源
- index.json首页数据中的kingKongModule.kingKongList属性节点
-
循环列表显示导航,注意key的设值
<scroll-view scroll-x="true" class="navScroll"> <view class="navItem" v-for="(navItem,index) in indexData.kingKongModule.kingKongList" :key="navItem.L1Id"> {{navItem.text}} </view> </scroll-view>
-
添加第一个navItem元素“推荐”
<scroll-view scroll-x="true" class="navScroll"> <view class="navItem">推荐</view> ... </scroll-view>
-
要进行导航切换,需要给navItem对象添加事件,注意下标的操作处理
<scroll-view scroll-x="true" class="navScroll"> <!-- changeNav(0) --> <view class="navItem" @click="changeNav(0)">推荐</view> <!-- changeNav(index+1) 下标+1 --> <view @click="changeNav(index+1)" class="navItem" v-for="(navItem,index) in indexData.kingKongModule.kingKongList" :key="navItem.L1Id"> {{navItem.text}} </view> </scroll-view>
-
添加changeNav的事件回调方法,切换导航修改navIndex下标
<script> ... export default { data() { return { navIndex:0 // navIndex的下标初始化 }; }, ... methods:{ ... changeNav(navIndex){ this.navIndex = navIndex // 切换导航修改navIndex下标 } } }; </script>
-
设置导航高亮样式activeClass
.navScroll white-space nowrap .navItem ... .activeClass border-bottom 2rpx solid #bb2c08
-
模板中利用条件判断实现样式类的动态添加
<scroll-view scroll-x="true" class="navScroll"> <view class="navItem" :class="{activeClass:navIndex === 0 }" @click="changeNav(0)">推荐</view> <view :class="{activeClass:navIndex === index+1 }" ...> {{navItem.text}} </view> </scroll-view>
第7章 H5跨域调试
一、跨域问题的出现
-
小程序不存在跨域的问题,但如果当前的项目发布成H5模式,则会出现跨域
-
- 以H5项目进行当前项目的运行
- 如果没有配置web服务器内容,则会出现无法运行的情况
- 需要配置浏览器的安装路径
-
-
跨域问题的解决方式
-
最为快速简单的方式是安装客户端浏览器调试插件,只需要利用CORS unblock插件,开启该插件,就可以直接进行本地跨域测试操作
-
利用vue.config.js设置跨域代理操作,在项目根目录新建
-
module.exports = {
devServer: {
proxy: {
'/api': {
// 客户端和脚手架代理服务器约定的接头暗号
target: 'http://localhost:3002',
// 需要代理的目标地址
ws: true,
// 代理websockets,
changeOrigin: true,
// 是否跨域
pathRewrite:{
// 路径重写
'^/api':''
// 接头暗号已经确认,就不再需要暗号信息了
}
},
}
}
}
现在测试H5页面仍旧出现跨域问题,说明请求的地址并不正确
-
修改请求地址,在request.js中请求的是全路径地址,但是在vue.config.js中已经设置了target地址为http://localhost:3002开头,所以在此应该设置的是一个相对地址
-
H5页面不再出现跨域错误,但是再看小程序运行结果,会发现也显示不了数据信息,因为小程序不存在跨域问题,它需要访问的是全路径地址,这就出现多端地址不相同的情况,需利用uniApp不同平台的条件编译来进行多端兼容代码处理,修改utils/config.js的host主机信息。https://uniapp.dcloud.net.cn/platform?id=%e8%b7%a8%e7%ab%af%e5%85%bc%e5%ae%b9
export default { // #ifdef H5 host: '/api', // 用于H5 // #endif // #ifdef MP-WEIXIN host: 'http://localhost:3002', // #endif }
-
现在小程序以及H5都可以进行正常数据访问,但在H5上直接报以属性未定义错误,小程序上是没有这些报错内容的,最终是能够被解析成功渲染的
-
如果有后端人员进行跨域协助操作,那么前端人员将不需要考虑跨域问题,但是后端代码也需要进行cors操作
第8章 vif和vfor的优先级
一、产生属性节点不存在,报以错误
-
为什么会产生属性节点不存在,是因为数据请求过程是异步请求,而页面在渲染过程中先进行了数组对象的遍历操作,在数据请求以后数据内容是被后赋值的,所以就出现了属性节点不存在的情况
-
需要给循环部分内容添加条件判断,需判断先有循环对象以及对应的属性节点,然后再进行循环遍历,但需考虑vif与vfor的层级
-
在同一级别以vfor优先,就意味着vif并不起作用,所以vif与vfor需要进行拆分编写
-
添加vif条件判断
<scroll-view scroll-x="true" class="navScroll" v-if="indexData.kingKongModule"> ... </scroll-view>
第9章 recommend组件swiper完成
一、新建recommend组件
-
根目录新建components组件目录
-
在该目录下右键点击创建组件,只有在该目录下会出现新建组件的功能菜单,其它目录不会出现该菜单项,可以利用该菜单项快速创建组件文件
-
创建组件的设置项
-
组件内容的初始化
<view class="recommendContainer"> recommend </view>
-
组件的引入与调用
-
轮播图的创建与图片设置,swiper设置样式类banners,每个item项的图片可以从严选网站复制图片地址,可以设置图片的mode模式属性
<swiper class="banners" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000"> <swiper-item> <view class="swiper-item"> <image mode="aspectFill" src="https://yanxuan.nosdn.127.net/bfb2d2897fae876df46fe1339496ef48.jpg?type=webp&imageView&quality=75&thumbnail=750x0"></image> </view> </swiper-item> ... </swiper>
-
设置轮播样式,如果只设置图片的宽高,而没有设置swipe-item的宽高,那么图片将不会显示,因为swipe-item是默认样式类,而image继承的宽高不对,所以不显示图
.recommendContainer .banners width 100% height 350rpx image height 100% width 100%
-
要设置swipe-item样式,image是它的子类
.recommendContainer .banners width 100% height 350rpx .swiper-item /* swipe-item宽高设置 */ height 100% width 100% image height 100% width 100%
第10章 主页布局完成
一、政策图标及分类图标
-
三个横向图标的渲染
<!-- policyDescList 三个横向图标 --> <div class='policyDescList' v-if="indexData.policyDescList"> <div class='policyDescItem' v-for='(item, index) in indexData.policyDescList' :key='index'> <img :src="item.icon"> <span>{{item.desc}}</span> </div> </div>
-
三个横向图标的样式操作
.policyDescList display flex .policyDescItem flex 1 text-align center img width 32rpx height 32rpx vertical-align middle /* 垂直 */ margin-right 6rpx span font-size 24rpx vertical-align middle /* 垂直 */
-
十个分类图标的渲染
<!-- kingKongList 10个图标列表 --> <div class="kingKongList" v-if='indexData.kingKongModule'> <div class="kingKongItem" v-for='(item, index) in indexData.kingKongModule.kingKongList' :key='index'> <img :src="item.picUrl"></img> <span>{{item.text}}</span> </div> </div>
-
十个分类图标的样式操作
.kingKongList display flex flex-wrap wrap margin 20rpx 0 .kingKongItem width 20% display flex flex-direction column align-items center img width 110rpx height 110rpx span font-size 24rpx line-height 50rpx
-
产品列表布局
<!-- 产品列表 --> <div class="proList"> <div class="proItem" v-for='(proItem, index) in indexData.categoryModule' :key='index'> <image class="proBigImg" :src="proItem.titlePicUrl"></image> <scroll-view scroll-x="true" class="proScroll"> <view class="scorllItem" v-for="(item,index) in proItem.itemList" :key="item.id"> <image :src="item.primaryPicUrl"></image> <view>{{item.name}}</view> </view> </scroll-view> </div> </div>
-
产品列表的样式操作
.proList .proItem margin-top 20rpx .proBigImg width 100% height 370rpx .proScroll white-space nowrap /* 不换行 */ .scorllItem display inline-block /* uniApp中利用inline-block 进行scroll-view的横向滚动 */ width 200rpx margin-left 20rpx vertical-align top /* 垂直以顶排齐,不然图片下沉 */ image width 200rpx height 200rpx background #ededed view font-size 24rpx /* 多行文本溢出 */ white-space pre-wrap /* 规定段落中的文本不进行换行 */ overflow hidden display -webkit-box -webkit-box-orient vertical -webkit-line-clamp 2
-
最后一个更多内容的设置
<scroll-view scroll-x="true" class="proScroll"> ... <view class="scorllItem more"> 查看更多 </view> </scroll-view>
-
最后一个更多内容的样式处理
.proList ... .scorllItem ... vertical-align top /* 通过&来找到父元素 */ &.more width 200rpx height 200rpx line-height 200rpx background #ededed text-align center font-size 26rpx ...
第11章 省市区三级联动以及无限分类
一、 省市区三级联动 https://github.com/uiwjs/province-city-china
-
三级联动数据结构
[ { "code": "420000", "name": "湖北省", "province": "42", "children": [ { "code": "420100", "name": "武汉市", "province": "42", "city": "01", "children": [ { "code": "420102", "name": "江岸区", "province": "42", "city": "01", "area": "02" }, // ... ] } // ... ] } // ... ]
-
数据结构属于嵌套结构,所以归属关系十分的清晰
二、无限分类的实现原理及算法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>利用递归处理无限分类</title>
</head>
<body>
<script>
const treeData = [
{ id: 1, name: "湖北省", parentId: null },
{ id: 2, name: "武汉市", parentId: 1 },
{ id: 3, name: "江岸区", parentId: 2 },
{ id: 4, name: "江汉区", parentId: 2 },
{ id: 5, name: "硚口区", parentId: 2 },
{ id: 6, name: "黄石市", parentId: 1 },
{ id: 7, name: "黄石港区", parentId: 6 },
{ id: 8, name: "西塞山区", parentId: 6 },
{ id: 9, name: "山西省", parentId: null },
{ id: 10, name: "太原市", parentId: 9 },
{ id: 11, name: "小店区", parentId: 10 },
{ id: 12, name: "迎泽区", parentId: 10 },
{ id: 13, name: "大同市", parentId: 9 },
{ id: 14, name: "新荣区", parentId: 13 },
{ id: 15, name: "平城区", parentId: 13 },
{ id: 16, name: "云冈区", parentId: 13 },
];
function reverseTree(data, pid) {
var result = [],
temp;
for (var i in data) {
if (data[i].parentId === pid) {
result.push(data[i]);
temp = reverseTree(data, data[i].id);
// 递归,并且把递归返回的内容设置成temp的值
if (temp.length > 0) {
data[i].children = temp;
}
}
}
return result;
}
const treeJsonData = reverseTree(treeData, null);
console.log(treeJsonData);
</script>
</body>
</html>