本项目已经为你生成了一个完整的开发框架,下面是整个项目的目录结构。
接下来你可以修改代码进行业务开发了,本项目内建了典型业务模板、模拟数据、状态管理、国际化、全局路由等等各种实用的功能来辅助开发
#项目打包npmrunbuild:xxx#自动创建npmrunnew#规范Git提交npmrungit-cz#生成CHANGELOGnpmrungenlog分层架构目前前端的一个开发趋势是以搭建单页应用(SPA)为主的。当应用体系比较大,或者你的应用业务逻辑足够复杂的时候,会遇到各种各样的问题,我们随便提两点:
针对上面所遇到的问题,我们以下面这张架构图做讲解:
其中视图层/View是大家接触最多的,想必大家都很了解,就不在这里介绍了,重点介绍其他几个层的含义:
Services层是用来对底层技术进行操作的,例如封装AJAX请求,操作浏览器Cookie、LocaStorage、IndexedDB,操作Native提供的能力(如调用摄像头等),以及建立Websocket与后端进行交互等。
Axios封装
.....exportdefaultasyncfunction(options){const{url}=optionsconstrequestOptions=Object.assign({},options)try{const{data,data:{errno,errmsg}}=awaitinstance.request(requestOptions)if(errno){errorReport(url,errmsg,requestOptions,data)thrownewError(errmsg)}returndata}catch(err){errorReport(url,err,requestOptions)throwerr}}IndexedDB
...exportclassDBRequest{instancestaticgetInstance(){if(!this.instance){this.instance=newDBRequest()}returnthis.instance}asynccreate(options={}){const{name,data}=optionsconstdb=awaitindexDB(name)returnawaitdb.add(name,data)}...}.......
实体Entity是领域驱动设计的核心概念,它是领域服务的载体,它定义了业务中某个个体的属性和方法。区分一个对象是否是实体,主要是看他是否有唯一的标志符(例如id)
通过上面的代码可以看到,这里主要是以实体本身的属性以及派生属性为主,当然实体本身也可以具有方法,用于实现属于实体自身的业务逻辑。
并不是所有的实体都应该按上面那样封装成一个类,如果某个实体本身业务逻辑很简单,就没有必要进行封装,例如本模板中的Test只是做个演示。
Interactors层是负责处理业务逻辑的层,主要是由业务用例组成
import{Request}from'@/utils/request'import{CARDS}from'@/constants/api/test'classTestHttpInteractor{serviceconstructor(service){this.service=service}asyncgetTest(){try{constoptions={url:CARDS}returnawaitthis.service.get(options)}catch(error){throwerror}}asynccreateTest(data){try{constoptons={url:CARDS,data}awaitthis.service.post(optons)}catch(error){throwerror}}...}consttestHttpInteractor=newTestHttpInteractor(Request.getInstance())exportdefaulttestHttpInteractor通过上面的代码可以看到,Sevices层提供的类的实例主要是通过Interactors层的类的构造函数获取到,这样就可以达到两层之间解耦,实现快速切换service的目的了,当然这个和依赖注入DI还是有些差距的,不过已经满足了我们的需求。
当然这种分层架构并不是银弹,其主要适用的场景是:实体关系复杂,而交互相对模式化,例如企业软件领域。相反实体关系简单而交互复杂多变就不适合这种分层架构了。
然后需要明确的是,架构和项目文件结构并不是等同的,文件结构是你从视觉上分离应用程序各部分的方式,而架构是从概念上分离应用程序的方式。你可以在很好地保持相同架构的同时,选择不同的文件结构方式。没有完美的文件结构,因此请根据项目的不同选择适合你的文件结构。
页面整体布局是一个产品最外层的框架结构,这里使用了vue-router路由嵌套,所以一般情况下,你增加或者修改页面只会影响app-main这个主体区域。其它配置在layout中的内容如:底部导航都是不会随着你主体页面变化而变化的。
/foo/bar+------------------++-----------------+|layout||layout||+--------------+||+-------------+|||foo.vue||+------------>||bar.vue|||||||||||+--------------+||+-------------+|+------------------++-----------------+这里在app-main外部包了一层keep-alive主要是为了缓存的,如不需要可自行去除。
在样式开发过程中,有两个问题比较突出:
/*编译前*/.example{color:red;}/*编译后*/.example[_v-f3f3eg9]{color:red;}只要加上stylescoped这样css就只会作用在当前组件内了。
TIP
使用scoped后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的scopedCSS和子组件的scopedCSS的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。
vue-h5-template所有全局样式都在@/src/styles目录下设置
├──styles│├──_animation#全局动画│├──index.scss#全局通用样式│├──_mixin.scss#全局mixin│├──_transition.scss#过渡效果│└──_variables.scss#全局变量一次完整的与服务器端交互在vue-h5-template中,一个完整的前端UI交互到服务端处理流程是这样的:
...exportclassRequest{instancestaticgetInstance(){if(!this.instance){this.instance=newRequest()}returnthis.instance}asyncpost(options={}){const{data}=awaitservice({method:'post',...options})returndata}...}例子定义接口地址统一管理src/constants/api/test.js
exportconstCARDS='/admin/cards'请求方法src/core/interactors/test-interactor.js
asyncgetTest(){try{constoptions={url:CARDS}returnawaitthis.service.get(options)}catch(error){throwerror}}请求方式src/utils/request.js
asyncget(options={}){const{data}=awaitservice({method:'get',...options})returndata}TIP
目录结构不要纠结,个人习惯而定
页面使用src/pages/test/index.vue
#生命周期asynccreated(){if(this.id){awaitthis.handleGetTest()}}#请求asynchandleGetTest(){try{consttest=awaittestInteractor.getTest(this.id)this.addressInfo=Object.assign({},test)}catch(error){console.log(error)}}可能大家会觉得很繁琐,这么多文件容易搞混,重复编写代码等等,不要着急,本模板配置了自动生成文件,上述除了视图/View层这块需要你手动去编写代码,其他的我们都会去一键生成,接下来我们就来讲讲使用方法。
在开发过程中,无论我们添加页面也好还是添加组件等等。都需要不停地新建.vue文件(或者其他框架或者html/js/css文件)
本项目中我一个配置了5项,他们分别代表着什么呢?
module.exports=function(plop){plop.setGenerator('page',pageGenerator)//Pageplop.setGenerator('component',componentGenerator)//组件plop.setGenerator('store',storeGenerator)//vuexplop.setGenerator('interactor',interactorGenerator)//业务逻辑plop.setGenerator('db-interactor',dbInteractorGenerator)//db业务逻辑}TIP
创建模板指令是npmrunnew,记得属于目录或文件名称哦
代码风格在.editorconfig,大家可以按照个人喜欢个性化修改。
首先,搜索并找到你需要的图标,将它采集到你的购物车里,在购物车里,你可以将选中的图标添加到项目中(没有的话,新建一个),后续生成的资源/代码都是以项目为维度的。
现在本项目支持和推荐单独导出svg的引入使用方式。下载方式如下图:
下载完成之后将下载好的.svg文件放入@/icons/svg文件夹下之后就会自动导入。
由于本项目ui框架使用了VantUI,所以国际化的同时也要将其国际化。同时将当前lang语言存在cookie之中,为了下次打开页面能记住上次的语言设置。
exportconstVueVantLocales=(lang=getLocale())=>{switch(lang){case'zh':Locale.use('zh-CN',vantZhLocale)breakcase'en':Locale.use('en-US',vantEnLocale)break}}exportdefaultnewVueI18n({locale:getLocale(),fallbackLocale:getLocale(),messages})使用Html中使用:
//$t是vue-i18n提供的全局方法$t('heoll')Js中使用:
在调试方面,本项目使用vconsole作为手机端调试面板,功能相当于打开PC控制台,可以很方便地查看Console,Network,Element、Storage等关键调试信息。
npmrungenlog就会出现类似与这种的文件格式:
vue最新脚手架中集成了pwa的插件,将pwa的实现变得更加的简单,只需要在vue.config.js文件中配置pwa属性就可以自动生成对应的service-worker.js配置文件,在这不做过多介绍。