vue项目预热 Vue CLI 的使用 安装 nodejshttps://cli.vuejs.org/zh/guide/creating-a-project.html
1 2 3 4 5 6 7 8 npm install -g @vue /cli yarn global add @vue /cli npm install -g @vue /cli-init # `vue init ` 的运行效果将会跟 `vue-cli@ 2. x` 相同 vue init webpack my-project
1、使用图形化界面 使用 vue ui 命令进行图形界面化创建项目 按照界面提示创建
2、Vue CLI 2.x 模板 (旧版本) vue init webpack travel project name travel …… runtime + compiler 选择第一个 install vue-router Y esLint npm 包管理 sudo npm install –verbose 查看详细安装信息
3、新版Vue Cli vue create –help vue create hello-world
4、如果 vue init webpack travel卡主 安装的最后一步npm 和 yarn 都不选择,选择手动安装 然后 cd travel, 最后 npm install –registry=https://registry.npm.taobao.org
npm set registry https://registry.npm.taobao.org npm set registry https://registry.npmjs.org/
npm ERR! code CERT_HAS_EXPIRED:解决证书过期问题 清除npm缓存 npm cache clean –force 取消ssl验证: npm config set strict-ssl false
5、npm run dev 运行项目 npm run serve
6、浏览器vue调试插件 vue.js devtools
vue项目结构 config 各种配置
dev.env.js
index.js
prod.env.js
index.html 生成的首页面
package.json 项目依赖,基础配置
src 目录
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false new Vue({ el: '#app' , router, components: { App }, template: '<App/>' })
App.vue 根组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <div id ="app" > <img src ="./assets/logo.png" > <router-view /> //显示路由组件,显示的是当前路由所对应的地址,/ </div > </template > <script > export default { name: 'App' } </script > <style > #app { } </style >
router文件夹 路由管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' Vue.use(Router) export default new Router({ routes: [ { path : '/' , name : 'HelloWorld' , component: HelloWorld } ] })
components 自定义组件目录
HelloWorld.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template > </template > <script > export default { name : 'HelloWorld' , data () { return { msg : 'Welcome to Your Vue.js App' } } } </script > /*样式只对这个组件有用*/ <style scoped > </style >
assets
static
项目开发总结 1、路由配置放在 src/router/index.js 路由根路径也就是下面的 HelloWorld 显示的是当前路由所对应的地址
入口文件 main.js 文件中 vue实例中 router, //路由 { 键值对一个名字,只写一个 } router文件夹中的 index.js 文件
1 2 3 4 5 6 7 8 9 export default new Router({ routes: [ { path : '/' , name : 'HelloWorld' , component: HelloWorld } ] })
2、路由使用 src目录下创建页面 pages/home/Home.vue pages/list/List.vue router文件夹中修改 index.js
路由配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import Vue from 'vue' import Router from 'vue-router' import Home from '@/pages/home/Home' import List from '@/pages/list/List' // @ 表示src 目录 Vue.use(Router) export default new Router({ routes: [ { path : '/' , name : 'Home' , component: Home }, { path : '/list' , name : 'List' , component: List } ] })
vue单页应用与多页应用 项目初始化
在assets 文件件下创建 styles文件夹 将reset.css,border.css基础样式拷贝进去。 main.js 中引入 import ‘./assets/styles/reset.css’ import ‘./assets/styles/border.css’
在index.html中加入如下,适配移动端
1 2 <meta name ="viewport" content ="width=device-width,initial-scale=1.0,minimum-scale=1.0, maximum-scale=1.0,user-scalable=no" >
安装依赖库 npm set registry https://registry.npm.taobao.org npm install fastclik –save npm install stylus@0.54.8 –save npm install stylus-loader –save npm install stylus-loader@3.0.2 –save
也可以修改 package.json 然后 npm install
iconfont 图标使用 将图标加入购物车 ttf 与 css 文件放入 /assets/iconfont/
页面文件夹中创建 components目录 存放组件 如:src/pages/home/components/Header.vue 在 Home.vue 文件中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template lang ="" > <div > <HomeHeader > </HomeHeader > </div > </template > <script > import HomeHeader from './components/Header' export default { name :'Home' , components : { HomeHeader } } </script > <style lang ="stylus" scoped > </style >
UI布局, stylus 语法 先写骨架,再写样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <template > <div class ="header" > <div class ="header-left" > <div class ="iconfont back-icon" >  </div > </div > <div class ="header-input" > <span class ="iconfont" >  </span > 输入城市/景点/游玩主题 </div > <router-link to ="/city" > <div class ="header-right" > {{this.$store.state.city }} <span class ="iconfont arrow-icon" >  </span > </div > </router-link > </div > </template > <script > export default { name : 'HomeHeader' , props : { } } </script > <style lang ="stylus" scoped > @import '~styles/theme.styl' /*引入全局样式*/ .header display : flex line-height : .86rem background : $bgColor color : #fff .header-left width : .64rem float : left .back-icon text-align : center font-size : .4rem .header-input flex : 1 background : #fff border-radius : .1rem height : .64rem line-height : .64rem margin-top : .12rem margin-left : .2rem padding-left : .2rem color : #ccc .header-right min-width : 1.04rem padding : 0 .1rem float : right text-align : center color : #fff .arrow-icon font-size : .24rem </style >
使用外部ui库 比如: https://github.com/surmon-china/vue-awesome-swiper npm install vue-awesome-swiper@2.6.7 –save main.js 文件中引入 全局引入 import VueAwesomeSwiper from ‘vue-awesome-swiper’ Vue.use(VueAwesomeSwiper)
Swipe.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <template > <div class ="wrapper"> <swiper :options ="swiperOption" v-if ="showSwiper"> <swiper-slide v-for ="item of list" :key="item.id"> <img class ="swiper-img" :src="item.imgUrl"> </swiper-slide> <div class ="swiper-pagination" slot="pagination"></div> </swiper> </div> </template > <script> export default { name : 'HomeSwiper' , props: { list: [] }, data () { return { swiperOption: { autoplay: 3500 , pagination: '.swiper-pagination' , paginationClickable: true , loop : true } } }, computed: { showSwiper () { return this.list.length } } } </script> <style lang="stylus" scoped> //>>>穿透样式 .wrapper >>> .swiper-pagination-bullet-active background: #fff .wrapper overflow: hidden width: 100 % height: 0 padding-bottom: 55 % // padding-bottom: 57.4 % 高比宽 background: #ccc .swiper-img width: 100 % .swiper-pagination position: absolute z-index : 20 bottom: 20 % width: 100 % text -align: center </style>
Header.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <template > <div class ="header" > <div class ="header-left" > <div class ="iconfont back-icon" >  </div > </div > <div class ="header-input" > <span class ="iconfont" >  </span > 输入城市/景点/游玩主题 </div > <router-link to ="/city" > <div class ="header-right" > {{this.$store.state.city }} <span class ="iconfont arrow-icon" >  </span > </div > </router-link > </div > </template > <script > export default { name : 'HomeHeader' , props : { } } </script > <style lang ="stylus" scoped > @import '~styles/theme.styl' .header display : flex line-height : .86rem background : $bgColor color : #fff .header-left width : .64rem float : left .back-icon text-align : center font-size : .4rem .header-input flex : 1 background : #fff border-radius : .1rem height : .64rem line-height : .64rem margin-top : .12rem margin-left : .2rem padding-left : .2rem color : #ccc .header-right min-width : 1.04rem padding : 0 .1rem float : right text-align : center color : #fff .arrow-icon font-size : .24rem </style >
Home.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 <template > <div > <home-header > </home-header > <home-swiper :list ="swiperList" > </home-swiper > <home-icons :list ="iconList" > </home-icons > <home-recommend :list ="recommendList" > </home-recommend > <home-weekend :list ="weekendList" > </home-weekend > </div > </template > <script > import HomeHeader from './components/Header.vue' import HomeSwiper from './components/Swiper.vue' import HomeIcons from './components/Icons.vue' import HomeRecommend from './components/Recommend.vue' import HomeWeekend from './components/Weekend.vue' import axios from 'axios' import { mapState } from 'vuex' export default { name : 'Home' , components : { HomeHeader , HomeSwiper , HomeIcons , HomeRecommend , HomeWeekend }, data () { return { lastCity : '' , swiperList : [], iconList : [], recommendList : [], weekendList : [] } }, methods : { getHomeInfo () { axios.get ('/api/index.json' ) .then (this .getHomeInfoSucc ) }, getHomeInfoSucc (res) { res = res.data if (res.ret && res.data ) { const data = res.data this .swiperList = data.swiperList this .iconList = data.iconList this .recommendList = data.recommendList this .weekendList = data.weekendList } } }, mounted () { this .getHomeInfo () }, computed : { ...mapState (['city' ]) }, activated () { if (this .lastCity !== this .city ) { this .lastCity = this .city this .getHomeInfo () } } } </script > <style > .home { font-size : 50px ; } </style >
Icons.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 <template > <div class ="icons" > <swiper :options ="swiperOption2" > <swiper-slide v-for ="(page,index) of pages" :key ="index" > <div class ="icon" v-for ="item of page" :key ="item.id" > <div class ="icon-img" > <img class ="icon-imgcontent" :src ="item.imgUrl" /> </div > <p class ="icon-desc" > {{ item.desc }} </p > </div > </swiper-slide > </swiper > </div > </template > <script > function groupList (list, itemsPerPage ) { var groupedLists = [] var currentPage = [] for (var i = 0 ; i < list.length ; i++) { currentPage.push (list[i]) if (currentPage.length === itemsPerPage || i === list.length - 1 ) { groupedLists.push (currentPage) currentPage = [] } } return groupedLists } export default { name : 'HomeIcons' , props : { list : Array }, data () { return { swiperOption2 : { loop : false } } }, computed : { pages () { var pages = groupList (this .list , 8 ) return pages } } } </script > <style lang ="stylus" scoped > @import '~styles/mixins.styl' ;//>>>:这是Stylus中的一个深度选择器,用于选择当前元素下的所有后代元素。 .icons >>> .swiper-container //overflow hidden //隐藏溢出:当子元素的尺寸超出容器的尺寸时,超出的部分将被隐藏,并不会在页面上显示 height 0 padding-bottom : 50% //高宽比 1 /2 底部填充为50% .icon position relative //相对于自己在正常文档流中的位置进行微调,并不会影响其他元素的布局 overflow hidden float left //元素在容器内的水平对齐方式 width: 25% height: 0 padding-bottom: 25% //底部填充为25% .icon-img position: absolute //相当于根布局的位置 相对于其最近的已定位(非static)祖先元素进行定位 top: 0 //将该元素的上边缘与其父元素的上边缘对齐 left: 0 right: 0 bottom: .44rem //该元素的下边缘与其父元素的底部边缘保持一定的距离 box-sizing: border-box //元素的盒模型为"border-box" 模式,这意味着元素的尺寸包括了内边距和边框 padding: .1rem .icon-imgcontent height 100% display: block //元素显示为块级元素。块级元素会独占一行,并且可以设置宽度、高度以及外边距等属性 margin: 0 auto //左右外边距设置为自动,即水平居中对齐。这样设置后,该元素的左右外边距会自动调整,使其在父元素中水平居中显示 .icon-desc position: absolute //相当于根布局的位置 相对于其最近的已定位(非static)祖先元素进行定位 left: 0 right: 0 bottom: 0 height: .44rem line-height: .44rem //行高是指元素中文本行的高度,用于控制行间距和垂直居中等效果 text-align: center color: #333 ellipsis () //文字超出显示三个点 </style >
Recommend.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <template > <div > <div class ="title" > 热销推荐</div > <ul > <router-link class ="item border-bottom" v-for ="item of list" :key ="item.id" tag ="li" :to ="'/detail/' + item.id" > <img class ="item-img" :src ="item.imgUrl" /> <div class ="item-info" > <p class ="item-title" > {{ item.title }} </p > <p class ="item-desc" > {{ item.desc }} </p > <button class ="item-button" > 查看详情</button > </div > </router-link > </ul > </div > </template > <script > export default { name : 'HomeRecommend' , props : { list : Array } } </script > <style lang ="stylus" scoped > @import '~styles/mixins.styl' ; .title margin-top : .2rem line-height: .8rem background: #eee text-indent: .2rem //文本的首行缩进 .item overflow hidden display: flex height: 1.9rem .item-img width: 1.7rem height: 1.7rem padding: .1rem .item-info flex: 1 padding: .1rem min-width 0 .item-title line-height: .54rem font-size: .32rem .item-desc line-height: .4rem color: #ccc ellipsis () //文字超出显示三个点 .item-button line-height: .44rem margin-top: .16rem background: #ff9300 padding: 0 .2rem border-radius: .06rem color: #fff </style >
Weekend.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <template > <div > <div class ="title" > 周末去哪儿</div > <ul > <li class ="item border-bottom" v-for ="item of list" :key ="item.id" > <div class ="item-img-wrapper" > <img class ="item-img" :src ="item.imgUrl" /> </div > <div class ="item-info" > <p class ="item-title" > {{ item.title }} </p > <p class ="item-desc" > {{ item.desc }} </p > </div > </li > </ul > </div > </template > <script > export default { name : 'HomeWeekend' , props : { list : Array } } </script > <style lang ="stylus" scoped > @import '~styles/mixins.styl' ; .title line-height : .8rem background: #eee text-indent: .2rem //文本的首行缩进 .item-img-wrapper overflow hidden height: 0 padding-bottom: 33.9% .item-img width: 100% .item-info padding: .1rem .item-title line-height: .54rem font-size: .32rem ellipsis () //文字超出显示三个点 .item-desc line-height: .4rem color: #ccc ellipsis () //文字超出显示三个点 </style >
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import Vue from 'vue' import App from './App' import router from './router' import store from './store' import VueAwesomeSwiper from 'vue-awesome-swiper' import 'styles/reset.css' import 'styles/border.css' import 'styles/iconfont.css' import 'swiper/dist/css/swiper.css' Vue.config.productionTip = false Vue.use(VueAwesomeSwiper) new Vue({ el: '#app' , router, store, components: { App }, template: '<App/>' })
项目开发要点 静态后端数据 static目录下创建mock目录,json放里面 项目的config/index.js文件 配置
1 2 3 4 5 6 7 8 9 10 11 assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: { '/api': { target: 'http://localhost:8081 ', pathRewrite: { '^/api': '/static/mock' } } },
axios 访问 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 methods: { getDetailInfo () { axios.get ('/api/detail.json?id=' , { params: { id: this .$route.params.id } }).then(this .detailInfoSucc) }, detailInfoSucc (res) { res = res.data if (res.ret && res.data ) { const data = res.data this .sightName = data .sightName this .bannerImg = data .bannerImg this .gallaryImgs = data .gallaryImgs this .categoryList = data .categoryList console.log(data ) } } }
异步组件配置 router/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/' , name: 'Home' , component: () => import ('@/pages/home/Home' ) // 异步组件 }, { path: '/city' , name: 'City' , component: () => import ('@/pages/city/City' ) }, { path: '/detail/:id' , name: 'Detail' , component: () => import ('@/pages/detail/Detail' ) } ], scrollBehavior (to, from , savedPosition) { // return 每次进入新页面期望滚动到哪个的位置 return {x: 0 , y: 0 } } })
路由跳转
1 2 3 4 5 6 <div class ="header" > <router-link to ="/" > <div class ="iconfont header-back" >& </router-link> 城市选择 </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 <template > <div class ="list" ref ="wrapper" > <div > <div class ="area" > <div class ="title border-topbottom" > 当前城市</div > <div class ="button-list" > <div class ="button-wrapper" > <div class ="button" > {{ city }} </div > </div > </div > </div > <div class ="area" > <div class ="title border-topbottom" > 热门城市</div > <div class ="button-list" > <div class ="button-wrapper" v-for ="city of hotCities" :key ="city.id" @click ="handleCityClick(city.name)" > <div class ="button" > {{ city.name }} </div > </div > </div > </div > <div class ="area" v-for ="(item,key) of cities" :key ="key" :ref ="key" > <div class ="title border-topbottom" > {{ key }} </div > <div class ="item-list" > <div class ="item border-topbottom" v-for ="innerItem of item" :key ="innerItem.id" @click ="handleCityClick(innerItem.name)" > {{ innerItem.name }} </div > </div > </div > </div > </div > </template > <script > import BScroll from 'better-scroll' import { mapState } from 'vuex' export default { name : 'CityList' , props : { cities : Object , hotCities : Array , letter : String }, methods : { handleCityClick (city) { this .$store .commit ('changeCity' , city) this .$router .push ('/' ) } }, mounted () { this .scroll = new BScroll (this .$refs .wrapper , {click : true }) }, watch : { letter () { console .log ('变化了' + this .letter ) const element = this .$refs [this .letter ][0 ] this .scroll .scrollToElement (element) } }, computed : { ...mapState (['city' ]) } } </script > <style lang ="stylus" scoped > @import '~styles/theme.styl' .border-topbottom &:before border-color : #ccc &:after border-color : #ccc .border-bottom &:before border-color : #ccc .list overflow hidden position : absolute top : 1.58rem left 0 right : 0 bottom : 0 .title line-height : .54rem background : #eee padding-left : .2rem color #666 font-size : .26rem .button-list padding : .1rem .6rem .1rem .2rem //上右下左 overflow hidden .button-wrapper width 33.33% float left .button text-align : center margin : .1rem padding .1rem 0 border : .02rem solid #ccc border-radius : .06rem .item-list .item line-height : .76rem padding-left : .2rem </style >
兄弟组件传值 借助父组件
节流函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 <template > <div > <div class ="search" > <input type ="text" class ="search-input" placeholder ="输入城市名或拼音" v-model ="keyWord" /> </div > <div class ="search-content" ref ="search" v-show ="keyWord" > <ul > <li class ="search-item border-bottom" v-for ="item of list" :key ="item.id" @click ="handleCityClick(item.name)" > {{ item.name }} </li > <li class ="search-item border-bottom" v-show ="hasNoData" > 没有搜索到任何数据 </li > </ul > </div > </div > </template > <script > import BScroll from 'better-scroll' export default { name : 'CitySearch' , props : { cities : Object }, data () { return { keyWord : '' , list : [], timer : null } }, methods : { handleCityClick (city) { this .$store .commit ('changeCity' , city) this .$router .push ('/' ) } }, computed : { hasNoData () { return !this .list .length } }, watch : { keyWord () { if (this .timer ) { clearTimeout (this .timer ) } if (!this .keyWord ) { this .list = [] return } this .timer = setTimeout (() => { const result = [] for (let i in this .cities ) { this .cities [i].forEach (value => { if (value.spell .indexOf (this .keyWord ) > -1 || value.name .indexOf (this .keyWord ) > -1 ) { result.push (value) } }) } this .list = result }, 100 ) } }, mounted () { this .scroll = new BScroll (this .$refs .search ) } } </script > <style lang ="stylus" scoped > @import '~styles/theme.styl' .search background : $bgColor height : .72rem padding : 0 .2rem .search-input width 100% height .62rem line-height : .62rem text-align : center border-radius : .06rem color : #666 box-sizing : border-box padding 0 .1rem .search-content z-index 1 overflow hidden position : absolute top : 1.58rem left 0 right 0 bottom 0 background : #eee .search-item line-height : .62rem padding-left : .2rem color : #666 background : #fff </style >
数据共享 VueX npm install vuex –savehttps://vuex.vuejs.org/zh/
新建store文件夹 创建index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import Vue from 'vue' import Vuex from 'vuex' Vue .use (Vuex )let defaultCity = '北京' try { if (localStorage .city ) { defaultCity = localStorage .city } } catch (e) {} export default new Vuex .Store ({ state : { city : defaultCity }, mutations : { changeCity (state, city) { state.city = city try { localStorage .city = city } catch (e) { } } } })
main.js
1 2 3 4 5 6 7 8 import store from './store' new Vue({ el: '#app' , router, store, components: { App }, template : '<App/>' })
1 2 {{this.$store.state.city }}
1 2 3 // this.$store .dispatch('changeCity' , city)// 也可以直接commit 不用去store实现actionthis.$store .commit('changeCity' , city)
可以将 state 和 mutations的代码拆分成 state.js 和 mutations.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 在组件中: import { mapState } from 'vuex' ...... computed: { ...mapState(['city' ]) }, ...... this .city 直接调用 this .$store.commit('changeCity' , city) 替换在组件中: import { mapState,mapMutations } from 'vuex' ...... methods: { ...mapMutations([changeCity]) } this . changeCity(city) 直接调用
使用 keep-alive 优化页面 路由发生切换的时候,页面都会重新获取数据并重新渲染。
App.vue 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template > <div id ="app" > <keep-alive exclude ="Detail" > <router-view /> </keep-alive > </div > </template > <script > export default { name : 'App' } </script > <style > </style >
如果使用了keep-alive,则页面重新显示的时候会执行activated方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 activated () { if (this .lastCity !== this .city) { this .lastCity = this .city this .getHomeInfo() } } deactivated() { } mounted () { window.addEventListener('scroll' , this .handleScroll) }, destroyed () { window.removeEventListener('scroll' , this .handleScroll) }
动态路由 Recommend.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <div class ="title" > 热销推荐</div > <ul > <router-link class ="item border-bottom" v-for ="item of list" :key ="item.id" tag ="li" :to ="'/detail/' + item.id" //跳转传参 > <img class ="item-img" :src ="item.imgUrl" /> <div class ="item-info" > <p class ="item-title" > {{ item.title }} </p > <p class ="item-desc" > {{ item.desc }} </p > <button class ="item-button" > 查看详情</button > </div > </router-link > </ul > </div >
接受参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 methods: { getDetailInfo () { axios.get ('/api/detail.json?id=' , { params: { id: this .$route.params.id } }).then(this .detailInfoSucc) }, detailInfoSucc (res) { res = res.data if (res.ret && res.data ) { const data = res.data this .sightName = data .sightName this .bannerImg = data .bannerImg this .gallaryImgs = data .gallaryImgs this .categoryList = data .categoryList console.log(data ) } } }, mounted () { this .getDetailInfo() }
路由路径
1 2 3 4 5 { path: '/detail/:id' , name: 'Detail' , component: () => import ('@/pages/detail/Detail' ) }
动态修改样式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 <template > <div > <router-link tag ="div" to ="/" class ="header-abs" v-show ="showAbs" > <div class ="iconfont header-abs-back" >  </div > </router-link > <div class ="header-fixed" v-show ="!showAbs" :style ="opacityStyle" > 景点详情 <router-link to ="/" > <div class ="iconfont header-fixed-back" >  </div > </router-link > </div > </div > </template > <script > export default { name : 'DetailHeader' , data () { return { showAbs : true , opacityStyle : { opacity : 0 } } }, methods : { handleScroll () { console .log ('scroll' ) const top = document .documentElement .scrollTop || document .body .scrollTop || window .pageYOffset if (top > 60 ) { let opacity = top / 140 opacity = opacity > 1 ? 1 : opacity this .opacityStyle = { opacity } this .showAbs = false } else { this .showAbs = true } } }, mounted () { window .addEventListener ('scroll' , this .handleScroll ) }, destroyed () { window .removeEventListener ('scroll' , this .handleScroll ) } } </script > <style lang ="stylus" scoped > @import '~styles/theme.styl' .header-abs position : absolute top : .2rem left : .2rem width .8rem height .8rem line-height : .8rem border-radius : .4rem text-align : center background : rgba(0 ,0 ,0 ,.8 ) .header-abs-back color #fff font-size : .4rem .header-fixed position fixed z-index 2 top : 0 left 0 right 0 overflow hidden height : .86rem line-height : .86rem color : #fff text-align : center background : $bgColor font-size : .32rem .header-fixed-back position : absolute top : 0 left : 0 width : .64rem text-align : center font-size : .4rem color : #fff </style >
递归组件使用 组件自身调用自身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <template > <div > <div class ="item" v-for ="item of list" :key ="item.title" > <div class ="item-title border-bottom" > <span class ="item-title-icon" > </span > {{ item.title }} </div > <div v-if ="item.children" class ="item-child" > <detail-list :list ="item.children" /> </div > </div > </div > </template > <script > export default { name : 'DetailList' , props : { list : { type : Array } } } </script > <style lang ="stylus" scoped > .item-title line-height : .8rem font-size: .32rem padding: 0 .2rem .item-child padding: 0 .2rem </style >
动画组件封装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <transition > <slot > </slot > </transition > </template > <script > export default { name : 'Fade' } </script > <style lang ="stylus" scoped > .v-enter ,.v-leave-to opacity : 0 .v-enter-active,.v-leave-active transition: opacity .5s </style >
使用
1 2 3 4 5 6 7 8 9 <fade > <common-gallary :imgs ="gallaryImgs" v-show ="showGallary" @close ="gallaryClose" > </common-gallary > </fade >
服务器联调 真机测试 package.json 配置:
1 2 3 "scripts" : { "dev" : "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js" , "start" : "npm run dev" ,
ip + 端口访问
打包项目 npm run build
生成dist目录,放入服务器
配置打包目录 config/index.js
1 2 3 4 5 6 7 8 build: { index: path .resolve (__dirname, '../dist/index.html' ), assetsRoot: path .resolve (__dirname, '../dist' ), assetsSubDirectory: 'static' , assetsPublicPath: './' , 配置打包目录
异步组件优化代码 按需加载组件,路由中配置
vue插件资源 vue cli 升级4.0 卸载老版本 npm uninstall vue-cli -g npm install @vue/cli -g vue create travel , 手动选择特性
配置路径 vue.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // const { defineConfig } = require ('@vue/cli-service' ) // module .exports = defineConfig({ // transpileDependencies: true // }) const path = require ('path' ); module .exports = { devServer: { proxy: { '/api' : { target: 'http://localhost:8081' , pathRewrite: { '^/api' :'/mock' } } } }, configureWebpack: { resolve: { alias: { '@' : path .resolve(__dirname, 'src' ), // 将@符号指向src目录 'styles' : path .resolve(__dirname, 'src/assets/styles' ), 'common' : path .resolve(__dirname, 'src/common' ) // 'assets' : path .resolve(__dirname, 'src/assets' ), // 将assets指向src/assets目录 // 'components' : path .resolve(__dirname, 'src/components' ) // 将components指向src/components目录 } } } };
static 换成 public
项目升级3.0 vue-cli 4