├── README.md ├── image ├── animate.png ├── animate1.png ├── chuantong.png ├── lifecycle.png ├── mvvm.png └── qunar.gif ├── project ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ └── prod.env.js ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── assets │ │ └── styles │ │ │ ├── border.css │ │ │ ├── iconfont.css │ │ │ ├── iconfont │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ │ │ ├── mixins.styl │ │ │ ├── reset.css │ │ │ └── varibles.styl │ ├── base │ │ ├── fade │ │ │ └── fadeAnimation.vue │ │ ├── gallary │ │ │ └── Gallary.vue │ │ ├── icon │ │ │ └── Icon.vue │ │ └── index.js │ ├── components │ │ └── HelloWorld.vue │ ├── main.js │ ├── pages │ │ ├── city │ │ │ ├── City.vue │ │ │ └── components │ │ │ │ ├── Alphabet.vue │ │ │ │ ├── Header.vue │ │ │ │ ├── List.vue │ │ │ │ └── Search.vue │ │ ├── detail │ │ │ ├── Detail.vue │ │ │ └── components │ │ │ │ ├── Banner.vue │ │ │ │ ├── Header.vue │ │ │ │ └── List.vue │ │ └── home │ │ │ ├── Home.vue │ │ │ └── components │ │ │ ├── Header.vue │ │ │ ├── Icons.vue │ │ │ ├── Recommend.vue │ │ │ ├── Swiper.vue │ │ │ └── Weekend.vue │ ├── router │ │ └── index.js │ └── store │ │ ├── index.js │ │ ├── mutations.js │ │ └── state.js └── static │ └── .gitkeep ├── setting_up ├── 01 │ ├── index.html │ └── vue.js ├── 02 │ ├── index.html │ └── vue.js ├── 03mvvm │ └── jquery.html ├── 04组件间传值 │ └── index.html ├── 05生命周期 │ └── index.html ├── 06模板语法 │ └── index.html ├── 07计算属性,方法与侦听器 │ └── index.html ├── 08计算属性的 getter和setter │ └── index.html ├── 09Vue中的样式绑定 │ └── index.html ├── 10Vue中的条件渲染 │ ├── index.html │ └── key.html ├── 11Vue中的列表渲染 │ └── index.html ├── 12Vue中的set方法 │ └── index.html ├── 13使用组件的细节点 │ ├── componentref.html │ ├── index.html │ ├── index1.html │ └── refs.html ├── 14父子组件间的数据传递 │ └── 01.html ├── 15组件参数校验与非props特性 │ └── index.html ├── 16给组件绑定原生事件 │ └── index.html ├── 17非父子组件间的传值 │ └── index.html ├── 18在Vue中使用插槽 │ └── index.html ├── 19作用域插槽 │ └── index.html ├── 20动态组件与v-once指令 │ └── index.html ├── 21Vue中CSS动画原理 │ └── index.html ├── 22在Vue中使用animate.css库 │ └── index.html ├── 23在Vue中同时使用过渡和动画 │ └── index.html ├── 24Vue中的 Js 动画与Velocity.js的结合 │ └── index.html ├── 25Vue中多个元素或组件的过渡 │ └── index.html ├── 26Vue中的列表过渡 │ └── index.html └── 27Vue中的动画封装 │ └── index.html └── vue.js /README.md: -------------------------------------------------------------------------------- 1 | # 项目简介 2 | 公司项目一直采用的是React全家桶开发的。近期公司让做一款项目,由于不是很急打算用Vue开发试试尝尝鲜。由于接口、UI都还没完事,自己先借这个git项目先熟悉熟悉Vue,把Vue中常用的知识点总结下方便自己的同时也方便他人。本项目从Vue基础语法入手,逐层递进,让您更好的掌握Vue各个基础知识点。包括Vue中的CSS动画原理、Vue中使用animate.css库、Vue中同时使用过渡与动画效果、Vue中使用Velocity.js库、Vue中多组件和列表过度 、Vue中动画的封装、vuex等等 3 | 4 | ### 下载源码 5 | ``` 6 | git clone https://github.com/fangfeiyue/vue-qu-na.git 7 | ``` 8 | ### 运行项目 9 | ``` 10 | npm install 11 | npm start 12 | 浏览器输入localhost:8080回车即可 13 | ``` 14 | ### 技术选型 15 | - 版本控制:git 16 | - 开发工具:vscode 17 | - 前端:Vue2.5 18 | - 模块化方案:ES6 + Webpack 19 | - 前后端分离方式:完全分离,纯静态方式 20 | ### 项目文件目录大概说明 21 | - image文件主要放了一些readme文件需要的图片 22 | - setting_up梳理项目前期的一些知识点,方便初学Vue的同学学习 23 | - project是项目文件 24 | ### 项目截图 25 | ![项目截图](https://github.com/fangfeiyue/vue-qu-na/blob/master/image/qunar.gif) 26 | ## 项目前期知识准备 27 | ### MVVM模式 28 | 29 | 传统开发中前端通常使用MVP设计模式 30 | 31 | ![MVP](https://github.com/fangfeiyue/vue-qu-na/blob/master/image/chuantong.png) 32 | 33 | 在这种设计模式下我们通常把代码分为三层,M层是数据层,V层是是视图层一般指的是数据展示,P层是控制层,下面是一个简单的MVP模式对应的前端代码 34 | 35 | ``` 36 | 37 | 38 | 39 | 40 | 41 | 42 | TodoList 43 | 44 | 45 | 46 |
47 | 48 | 49 | 51 |
52 | 75 | 76 | 77 | ``` 78 | 这个例子因为没有请求接口,所以弱化了M数据层。div里面是V层,script标签里的操作Dom的是P层,当我们点击页面按钮提交时会执行P层的代码,更新页面。P层相当于M和V层的中转站,P层中的很大一份代码都是在做DOM的操作。而前端存在大量操作DOM的行为会严重影响页面性能。 79 | 80 | 下图是vue中MVVM模式的示意图 81 | 82 | ![MVP](https://github.com/fangfeiyue/vue-qu-na/blob/master/image/mvvm.png) 83 | 84 | MVVM模式中也有视图层V和数据层M,但是没有P层取而代之的ViewModel层,从上图我们也可以看出VM层是vue自己带的,由此可以看出在使用MVVM进行开发Dom的操作被极大的简化了代码量会得到显著的减少,我们只需要关注M层和V层,更多是关注的M层就可以了。 85 | 86 | ### 组件初识 87 | 88 | Vue中组件简单来说可以分为以下两种 89 | - 全局组件 90 | - 局部组件。 91 | 92 | 下面我们就来写了个小demo来看看这两种组件如何使用。 93 | 94 | 先来用Vue写一个阉割版todolist,这个只能实现添加功能,代码如下: 95 | ``` 96 | 97 | 98 | 99 | 100 | 101 | 102 | TodoList 103 | 104 | 105 | 106 |
107 | 108 | 109 | 112 |
113 | 128 | 129 | 130 | ``` 131 | 运行上述代码就实现了只带添加功能的TodoList,从运行结果看,添加的内容我们可以分装成一个组件也就是`ul`中的`li`标签。 132 | 133 | 全局组件的运用,在`script`标签中添加如下代码 134 | ``` 135 | Vue.component("TodoItem", { 136 | props: ['content'], 137 | template: "
  • {{content}}
  • " 138 | }); 139 | ``` 140 | 上述代码中我们定义了一个Vue的全局组件`TodoItem`,下一步``替换`
  • `,注意我们定义的组件在使用的时候,字母要全部小写,大写字母转为小写后要用`-`连接,如`TodoItem`在使用时写为`todo-item`。 141 | 142 | 上述为全局组件的使用,下面我们试试局部组件。 143 | 144 | 在`script`标签中添加如下代码 145 | ``` 146 | var TodoItem = { 147 | props: ['content'], 148 | template: "
  • {{content}}
  • " 149 | }; 150 | ``` 151 | 注意,此时我们还不能直接使用局部组件,局部组件要在new Vue()中通过components注册后方可使用,代码如下: 152 | ``` 153 | var app = new Vue({ 154 | ... 155 | components: { 156 | TodoItem: TodoItem 157 | } 158 | ... 159 | }); 160 | ``` 161 | 注册完成后就可以像全局组件一样使用局部组件了。 162 | 163 | `注意:v-bind可以缩写为:,v-on可以缩写为@。` 164 | 165 | ### 子组件向父组件传值初探 166 | 167 | 上面我们只实现了简单的添加todo,如果我们已经完成了某了todo,点击对应的todo需要删掉它,该怎么实现呢?首先我们用的是Vue,就不需要特别关注dom的操作,我们只需要处理数据即可,当我们点击某项todo,只要删除list数组中选中的项即可,数组中的数据发生了变化,页面也会跟随着变化。 168 | 169 | 首先我们分别修改html部分和js部分的代码 170 | ``` 171 | // html 172 | 176 | 177 | 178 | // js 179 | var TodoList = { 180 | props: ['content', 'index'], 181 | template: "
  • {{content}}
  • ", 182 | methods: { 183 | handleItemClick: function(){ 184 | this.$emit("delete", this.index); 185 | } 186 | } 187 | }; 188 | 189 | var app = new Vue({ 190 | ... 191 | methods: { 192 | ... 193 | handleItemDelete: function(index){ 194 | // 删除数组中指定索引的元素 195 | this.list.splice(index, 1); 196 | } 197 | } 198 | }); 199 | ``` 200 | 201 | `$emit`方法可以向外触发事件。 202 | ### 生命周期 203 | 204 | 生命周期函数就是Vue实例在某一个时间点会自动执行的函数. 205 | 206 | ![生命周期图示](https://github.com/fangfeiyue/vue-qu-na/blob/master/image/lifecycle.png) 207 | 208 | ### Vue的模板语法初探 209 | v-text、v-html、{{}}都可以现实变量中的内容,区别在于,如果变量内容是被hmtl元素包裹的话,v-html会输出html语法中的样式,v-text和{{}}只输出普通的文本. 210 | ``` 211 | 212 | 213 | 214 | 215 | 216 | 217 | Document 218 | 219 | 220 |
    221 | {{message}} 222 |
    223 |
    224 |
    225 | 226 | 234 | 235 | 236 | ``` 237 | 238 | ### 计算属性,方法与侦听器 239 | 240 | - 计算属性 `computed` 这个属性有一个重要的特性就是内置缓存,当它里面用于计算的属性的值都没有发生变化的时候,这个属性执行一次就不再执行了,除非参与计算的属性的值发生了变化。 241 | 242 | - 方法属性 `methods` 243 | - 监听属性 `watch` 244 | 245 | 下面我们分别用这三个属性计算出一个完整的名字显示到页面上 246 | ``` 247 | 248 | 249 | 250 | 251 | 252 | 253 | Document 254 | 255 | 256 |
    257 | {{fullName}} 258 |
    259 | 260 | 291 | 292 | 293 | ``` 294 | 综合对比三种方法,如果用methods的话,每个数据变化都会执行一遍,没有这个必要,我们只需要firtName和lastName变化的时候执行即可,如果用监听器的话虽然firstName和lastName的值在程序运行过程中不变的话里面的方法执行一次就不再执行了,但代码明显多很多,还要监听多个变量,由此看来使用计算属性的话更能适合这种方式。 295 | 296 | ### 计算属性的 getter和setter 297 | 298 | 还是上面的计算全名的例子,我们将代码修改如下 299 | ``` 300 | 301 | 302 | 303 | 304 | 305 | 306 | Document 307 | 308 | 309 |
    310 | {{fullName}} 311 |
    312 | 313 | 334 | 335 | 336 | ``` 337 | 对比过代码后,我们发现在`computed`属性中我们把之前fullName方法改成了对象,并且在这个对象中新增了两个方法一个是get一个是set。当我们获取fullName值时,会执行get方法,set方法在我们改变fullNmae值的时候回执行,将fullName改变前的值当做传入set方法中。这个例子中,我们在set方法中又重新操作了firstName和lastName,这两种值的变化会重新引起computed执行,从而更新fullName的值。 338 | 339 | ### Vue中的样式绑定 340 | 341 | 1. 类的使用 342 | 343 | 方式一,对象方式: 344 | ``` 345 | // html文件 346 |
    350 | hello world 351 |
    352 | 353 | // js文件 354 | var app = new Vue({ 355 | el: '#app', 356 | data: { 357 | isActivated: false, 358 | }, 359 | methods: { 360 | handleDivClick: function(){ 361 | this.isActivated = !this.isActivated; 362 | } 363 | } 364 | }); 365 | ``` 366 | 367 | 方式二,数组方式: 368 | ``` 369 | // html 370 |
    374 | hello world 375 |
    376 | 377 | // js文件 378 | var app = new Vue({ 379 | el: '#app', 380 | data: { 381 | activated: '' 382 | }, 383 | methods: { 384 | handleDivClick: function(){ 385 | this.activated = this.activated ? '' : 'activated'; 386 | } 387 | } 388 | }); 389 | ``` 390 | 既然是class既然给的是一个数组,数组中肯定可以包含多个元素,即元素可以有多个类名 391 | 392 | 2. style的使用 393 | 394 | 对象方式 395 | ``` 396 | // html 397 |
    401 | hello world 402 |
    403 | 404 | // js 405 | var app = new Vue({ 406 | el: '#app', 407 | data: { 408 | styleObj: { 409 | color: '' 410 | } 411 | }, 412 | methods: { 413 | handleDivClick: function(){ 414 | this.styleObj.color = this.styleObj.color ? '' : 'red'; 415 | } 416 | } 417 | }); 418 | ``` 419 | 420 | 数组方式 421 | ``` 422 |
    426 | hello world 427 |
    428 | ``` 429 | ### Vue中的条件渲染 430 | 431 | 1. v-show和v-if都能控制页面元素的显示,v-if对应的值为false时元素不会被渲染到Dom结构中,v-show对应的值为false时,元素会被渲染到Dom结构中只不过它的display为none。当我们频繁的去操作一个元素显示和隐藏时用v-show更合适,它不会频繁的将这个元素从Dom中删除再添加,性能要高些。 432 | ``` 433 |
    434 |
    {{message}}
    435 |
    {{message}}
    436 |
    437 | 438 | 447 | ``` 448 | 449 | 2. v-if、v-else的使用 450 | ``` 451 |
    452 |
    {{message}}
    453 |
    I love doudou
    454 |
    455 | 456 | 465 | ``` 466 | 467 | `注意:v-if和v-else一起使用时中间不能有其他元素阻隔他俩,一定要在一起写` 468 | 469 | 3. v-if、v-else的使用 470 | ``` 471 |
    472 |
    if
    473 |
    else if
    474 |
    else
    475 |
    476 | 477 | 486 | ``` 487 | 488 | `注意:v-if、v-else-if、v-else一起使用时中间不能有其他元素阻隔他们,一定要在一起写` 489 | 490 | ### key值初始 491 | ``` 492 |
    493 |
    494 | 姓名 495 |
    496 |
    497 | 密码 498 |
    499 |
    500 | 501 | 509 | ``` 510 | 511 | 上述代码如果去掉input中对应的key值,在密码框输入数字,当改变show值时,input的标题已经变成姓名,但输入框中依然是之前用的密码的输入框,原因是vue会尽可能的复用页面上的元素,当给input赋值不同的key后页面就会显示正常,这里涉及到了diff算法,后期专门讲解。 512 | ### Vue中的列表渲染 513 | 514 | 遍历数组和对象 515 | ``` 516 |
    517 |
    518 | {{index}}=>{{item}} 519 |
    520 |
    521 | {{index}}=>{{key}}=>{{item}} 522 |
    523 |
    524 | 525 | 546 | ``` 547 | 548 | ### Vue中的set方法 549 | 上述代码,当我们改变数组中的元素引起页面重新渲染可以使用pop、push、shift等数组方法,或者给数组变变量重新赋值新数组来改变它的引用。当我们改变对象中的属性来引起页面重新渲染,只能通过改变它的引用。 550 | 551 | 我们还可以用Vue中的set属性来改变数组或对象的值,来引起页面的重新渲染。 552 | ``` 553 |
    554 | 555 |
    {{item}}
    556 |
    557 |
    {{item}}
    558 |
    559 |
    560 | 561 | 583 | ``` 584 | ### 使用组件的细节点 585 | 1. is 属性 586 | ``` 587 |
    588 | 589 | 590 | 591 | 592 | 593 | 594 |
    595 |
    596 | 597 | 609 | ``` 610 | 上述代码当我们成功运行后,表面上看浏览器是呈现了三个3item,好像一切正常,但当我们审查元素会发现tr标签跑道table标签外面显示了,这显然不对,主要是tbody标签中必须放tr标签,tbody不能识别我们定义的组件row,浏览解析的时候就会出问题。 611 | 612 | 这个时候我们可以用is来解决这种问题,将tbody中的``换成``,此时再刷新页面审查元素发现Dom结构正确了。 613 | 614 | 2. 在子组件中定义data,data必须是一个函数并且发挥一个对象 615 | ``` 616 |
    617 | 618 | 619 | 620 | 621 | 622 | 623 |
    624 |
    625 | 626 | 647 | ``` 648 | 因为子组件可能会被调用多次,所以每个子组件都应该有自己的数据,不应该共享一套数据, 通过一个函数来返回一个对象的目的就是为了让每个子组件都有自己的独立存储,这样的话不会出现多个子组件互相影响的情况。 649 | 650 | 3. refs引用 651 | - ref 应用在dom元素上 652 | 653 | 在某些特殊情况下我们可能是需要操作Dom元素,这是我们可以使用ref 654 | ``` 655 |
    656 |
    hello world
    657 |
    658 | 659 | 669 | ``` 670 | 上述代码我们通过给div元素添加ref属性,在js中用this.$refs.hello获取到了div元素 671 | 672 | - ref应用在子组件上 673 | ``` 674 |
    675 | 676 | 677 |
    {{total}}
    678 |
    679 | 680 | 707 | ``` 708 | ### 父子组件间的数据传递 709 | 710 | ``` 711 |
    712 | 713 | 714 | {{total}} 715 |
    716 | 717 | 748 | ``` 749 | 750 | 父组件向子组件传值通过props即可,子组件向父组件传值需要通过事件,注意,vue是单向数据流,子组件不能直接修改父组件传递过来的值,实在要修改的话可以用一个变量承接,修改这个副本。 751 | 752 | ### 组件参数校验与非props特性 753 | 754 | 1.校验 755 | 756 | 父组件可以向子组件传递属性值,子组件也可以通过props接收父组件传递过来的值,同时我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。 757 | ``` 758 | var child = { 759 | props: { 760 | content: [String, Number], // 类型的校验,多个类型要放在数组里 761 | content: { 762 | type: String, // 类型的校验 763 | required: true, // 是否必须 764 | default: 'doudou', // 默认值 765 | validator: function(value){ // 自定义校验器 766 | return value.length > 5; 767 | } 768 | } 769 | }, 770 | template:"
    {{content}}
    " 771 | }; 772 | ``` 773 | 774 | 2. 非 Prop 特性 775 | 776 | 父组件向子组件传递了值,但子组件不通过prop进行接收,这是就不能直接使用父组件传递过来的值。 同时,父组件传递过来的属性值会显示在子组件最外层html标签上 777 | ``` 778 | // html 779 | ... 780 |
    781 | 782 |
    783 | 784 | // js 785 | var child = { 786 | template:"
    {{content}}
    " 787 | }; 788 | ... 789 | ``` 790 | 成功运行上述代码后,打开浏览器控制台审查元素就会看到`
    `div上显示了父组件传递过来的属性 791 | 792 | ### 给组件绑定原生事件 793 | 794 | ``` 795 | // html 796 |
    797 | 798 |
    799 | 800 | // js 801 | var child = { 802 | template:"
    child
    ", 803 | }; 804 | 805 | var app = new Vue({ 806 | el: '#app', 807 | components: { 808 | child: child 809 | }, 810 | methods: { 811 | handleClick: function(){ 812 | alert('click'); 813 | } 814 | } 815 | }); 816 | ``` 817 | 818 | 如上代码,当我们直接点击child的组件浏览器并不会弹出click,要想弹出click必修跟child组件中的div绑定事件,然后在child组件中的methods属性中定义对应的方法,通过this.$emit触发外层的handleClick方法,从而弹出click 819 | ``` 820 |
    821 | 822 |
    823 | 824 | 847 | ``` 848 | 这也未免操作有点麻烦,我们可以通过给父组件传递的@click后加一个native属性实现click的弹出 849 | ``` 850 |
    851 | 852 |
    853 | 854 | 872 | ``` 873 | 这样代码就会简洁很多。 874 | 875 | ### 在Vue中使用插槽 876 | 877 | 1.简单的插槽使用 878 | 879 | 当我们想要给子组件中传递dom元素时,我们可以怎么做呢? 880 | 881 | 首先根据前面的知识我们一定能想到用父组件给子组件传值的方法,本例以传递一个`

    hello world

    `为例 882 | ``` 883 |
    884 | 885 | 886 | 887 |
    888 | 889 | 904 | ``` 905 | 运行上述代码,浏览器页面就会出现hello world。但是当我审查元素的时候就会发现hello world的p元素外面包裹了一层div,而我们只想显示p里面的hello world,这不是我们想要的结果,并且如果我们传入的dom元素多的时候,代码看起来就会很混乱。这时我们就需要使用到插槽了,更改上述代码如下。 906 | ``` 907 |
    908 | 909 |

    hell world

    910 |

    hell world

    911 |
    912 |
    913 | 914 | 929 | ``` 930 | 我们在引用child组件里直接输入了`

    hello world

    `,然后在组件里直接用``来接收,slot就是我们所说的插槽。 931 | 932 | slot标签还可以写默认内容。上述代码,如果我们不让child包裹任何dom元素,子组件的slot中改写为`default value`运行代码页面就会显示出default value。 933 | 934 | 2. 具名插槽 935 | 936 | 上述代码我们看出slot代表的是child包裹的所有dom元素,但当我们想查分使用传入的元素改怎么办呢? 937 | ``` 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | Document 946 | 947 | 948 |
    949 | 950 |

    header

    951 |

    footer

    952 |
    953 |
    954 | 955 | 971 | 972 | 973 | 974 | ``` 975 | 上述代码们想将footer和header分别插在test的上面和下面,运行代码后看到并不是我们想要的结果,原因是slot代表所有child插入的dom元素。如果想实现这种效果改怎么办呢?这时我们就需要使用到具名插槽了,分别给header和footer添加属性 slot="header"和slot="footer",然后再在child子组件中给slot赋值name属性,运行代码就会得到我们想要的结果 976 | ``` 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | Document 985 | 986 | 987 |
    988 | 989 |

    header

    990 |

    footer

    991 |
    992 |
    993 | 994 | 1010 | 1011 | 1012 | 1013 | ``` 1014 | ### 作用域插槽 1015 | ``` 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | Document 1023 | 1024 | 1025 |
    1026 | 1027 | 1028 |
    1029 | 1030 | 1050 | 1051 | 1052 | ``` 1053 | 上述代码我们调用了child组件显示child组件中list数组中的内容,运行代码,结果显示正确。但是child组件可能在很多地方会被调用,我们希望根据不同的调用情况来控制子组件显示的内容和样式该怎么实现呢? 1054 | ``` 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | Document 1062 | 1063 | 1064 |
    1065 | 1066 | 1071 | 1072 |
    1073 | 1074 | 1094 | 1095 | 1096 | ``` 1097 | 上述代码我们将child组件中的li标签用slot代替了,父组件中我们向子组件传递了 1098 | ``` 1099 | 1104 | ``` 1105 | 注意,一定要用`template`包裹想传给子组件的元素,`slot-scope`接收子组件传递过来的数据。`slot-scrope`的值不必非要是props可以根据自己需求来。 1106 | 1107 | ### 动态组件与v-once指令 1108 | ``` 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | Document 1116 | 1117 | 1118 |
    1119 | 1120 | 1121 | 1122 |
    1123 | 1124 | 1149 | 1150 | 1151 | ``` 1152 | 上述代码当我点击change按钮的时候,实现了child-one和child-two来回切换,但如果我们需要切换的组件比较多的时候这样操作就比较麻烦,这是我们可以使用Vue提供的动态组件来实现这个功能。通过使用保留的 `` 元素,并对其 `is` 特性进行动态绑定,就可以在同一个挂载点动态切换多个组件了。上述代码中的 1153 | ``` 1154 | 1155 | 1156 | ``` 1157 | 替换为 1158 | ``` 1159 | 1160 | ``` 1161 | 即可动态切换多个组件。完整代码如下 1162 | ``` 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | Document 1170 | 1171 | 1172 |
    1173 | 1174 | 1176 | 1177 |
    1178 | 1179 | 1208 | 1209 | 1210 | ``` 1211 | 但是上述代码还是有问题的,上述代码在组件来回切换的时候步骤大概为当一个组件不显示了就销毁需要显示的时候再创建,如果切换的组件较多这样就会比较耗费性能。我们可以给每个组件添加个`v-once`指令,这个指令只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。 1212 | 1213 | ### Vue中CSS动画原理 1214 | Vue 的过渡系统有了彻底的改变,现在通过使用 `` 和 `` 来包裹元素实现过渡效果,而不再使用 `transition` 属性 1215 | ![css](https://github.com/fangfeiyue/vue-qu-na/blob/master/image/animate.png) 1216 | ![css](https://github.com/fangfeiyue/vue-qu-na/blob/master/image/animate1.png) 1217 | ``` 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | Document 1225 | 1233 | 1234 | 1235 | 1236 |
    1237 | 1238 |
    hello world
    1239 |
    1240 | 1241 |
    1242 | 1255 | 1256 | 1257 | ``` 1258 | transition中name为string类型,用于自动生成 CSS 过渡类名。例如:name: 'fade' 将自动拓展为.fade-enter,.fade-enter-active等。默认类名为 "v" 1259 | ### 在Vue中使用animate.css库 1260 | 上面我们简单的实现了一些动画效果,但是用的自带的,如果我们自己想定义一些动画,或者使用第三方动画库改怎么办呢?下面我们以animate.css为例。 1261 | 1262 | ``` 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | Document 1270 | 1273 | 1274 | 1275 | 1276 |
    1277 | 1281 |
    hello world
    1282 |
    1283 | 1284 |
    1285 | 1286 | 1299 | 1300 | 1301 | ``` 1302 | 首先给`transition`添加`enter-active-class`和`leave-active-class`两个属性,这个两个属性分别赋值我们想给的类名(自己写的也可以哦)即可。 1303 | 1304 | ### 在Vue中同时使用过渡和动画 1305 | ``` 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | Document 1313 | 1314 | 1324 | 1325 | 1326 |
    1327 | 1333 |
    hello world
    1334 |
    1335 | 1336 |
    1337 | 1338 | 1351 | 1352 | 1353 | ``` 1354 | 上面的代码,当页面初次加载的时候并没有动画效果,下面我们给`transition`标签分别添加`appear`和`appear-active-class`两个属性。appear为bool类型,作用是是否在初始渲染时使用过渡。默认为 false。具体代码如下: 1355 | ``` 1356 | 1363 |
    hello world
    1364 |
    1365 | ``` 1366 | 这个时候当页面首次加载hello world也就显示动画了。 1367 | 1368 | 上面代码中我们还给transiton添加了自定义类`fade-enter-active`和`fade-leave-active`.我们自定义类的动画过度时间为3s,而annimate.css的动画过渡时间为1s,这个时候动画时间应该以谁为准呢?transation为我们提供type属性。type的值为字符串,指定过渡事件类型,侦听过渡何时结束。有效值为 "transition" 和 "animation"。默认 Vue.js 将自动检测出持续时间长的为过渡事件类型。由此可见我们可以自己决定动画的时长为transition的时长还是为animation的时长。我们这里用transition的时长 1369 | ``` 1370 | 1375 |
    hello world
    1376 |
    1377 | ``` 1378 | 通过`:duration`我们还可以自己定义动画时间的总时长,单位为毫秒 1379 | ``` 1380 | 1385 |
    hello world
    1386 |
    1387 | ``` 1388 | 我们还可以将时长定义的复杂下,比如将入场动画和出场动画时间定义的不一致。 1389 | ``` 1390 | 1395 |
    hello world
    1396 |
    1397 | ``` 1398 | ### Vue中的列表过渡 1399 | 上面的代码我们只是给单个dom元素添加了动画效果,如果我们想给一组dom添加动画该怎么办呢?Vue为我们提供了`transition-group`,大体用法和`transition`一样,demo如下 1400 | ``` 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | Document 1408 | 1416 | 1417 | 1418 |
    1419 | 1420 |
    {{item.title}}
    1421 |
    1422 | 1423 |
    1424 | 1425 | 1442 | 1443 | 1444 | ``` 1445 | ### Vue中的动画封装 1446 | 1447 | 如果我们需要很多类似的动画每次都用`transition`来操作未免太过重复,繁琐,这个时候我们可以把动画效果一样的代码进行封装,以后使用的时候直接调用封装的代码即可。如下代码是个简单的demo,demo很简单就是讲动画封装成了一个组件,组件中的显示的元素用`slot`传递。 1448 | ``` 1449 | 1450 | 1451 | 1452 | 1453 | 1454 | 1455 | Document 1456 | 1457 | 1458 |
    1459 | 1460 |
    hello world
    1461 |
    1462 | 1463 |

    hello world

    1464 |
    1465 | 1466 |
    1467 | 1468 | 1503 | 1504 | 1505 | ``` 1506 | 1507 | ### 项目总结 1508 | 1509 | 1. 多页应用:当每次用户浏览不同的页面,服务器都会返回不同的html文件。 1510 | 1511 | 多页应用的特点: 1512 | 1513 | - 优点 1514 | - 首屏时间快 1515 | - seo效果好 1516 | - 缺点 1517 | - 页面切换慢 1518 | 1519 | 2. 单页应用在页面跳转的时候并不是每次都请求一个新的html文件,而是通过js动态的来更换页面需要显示的数据并渲染dom元素, 1520 | 1521 | 单页应用的特点: 1522 | - 优点 1523 | - 页面切换快 1524 | - 缺点 1525 | - 首屏时间稍慢 1526 | - SEO差 1527 | 1528 | 3. Vue中页面跳转可以使用`router-link`跳转,功能相当于`a`标签 1529 | ``` 1530 | 列表页 1531 | ``` 1532 | 1533 | 4. 在vue单个组件文件中的`template`只能返回一个dom标签,如果有多个标签需要返回,外面需要包裹一层html标签 1534 | ``` 1535 | 1541 | ``` 1542 | 1543 | 5. 在vue组件中写css样式时,为了防止组件中的css影响到其他组件中的样式可以给`style`标签添加`scoped`属性,将css样式限制到特定的组件中 1544 | ``` 1545 | 1548 | ``` 1549 | 1550 | 6. 但是在style中使用scoped属性需要注意一点,添加scoped之后,实际上vue在背后做的工作是将当前组件的节点添加一个像data-v-1233这样唯一属性的标识,当然也会给当前style的所有样式添加[data-v-1233]这样的话,就可以使得当前样式只作用于当前组件的节点。但是我们需要注意的是如果我们添加了子组件,同样的,如果子组件也用scoped标识了,那么在父组件中是不能设置子组件中的节点的。若父组件有scoped,子组件没有设置,同样,也是不能在父组件中设置子组件的节点的样式的,因为父组件用了scoped,那么父组件中style设置的样式都是唯一的了,不会作用与其他的组件样式。如果我们想要在组件中修改其他组件的样式可以用`>>>`来达到组件样式穿透的效果,进而修改子组件中的样式,如下代码是我们修改HomeSwiper组件中滑动小圆点样式的代码,swiper组件中小圆点默认为蓝色,我们透过`>>>`将其修改为白色 1551 | ``` 1552 | 1564 | 1565 | 1577 | 1578 | 1582 | 1583 | ``` 1584 | 1585 | 7. 在项目中当文字过长我们希望显示三个省略号,这个css样式可能在很多地方都会用的到,这个时候我们可以将他抽离出来,利用stylus实现,单独新建一个文件命名为mixins.styl然后将显示省略的代码写到这个文件中 1586 | ``` 1587 | ellipsis() 1588 | overflow: hidden 1589 | white-space: nowrap 1590 | text-overflow: ellipsis 1591 | ``` 1592 | 当我们需要使用这个功能时,可以用`import`引入这个文件,在对应的`stylus`代码中引入`ellipsis()`即可 1593 | ``` 1594 | @import '~styles/mixins.styl' 1595 | .icon-desc 1596 | ellipsis() 1597 | ``` 1598 | 1599 | 8. 因为项目没有直接调用线上的接口,数据是本地模拟的,如果我们像下面这样直接请求在开发的过程中是可以的 1600 | ``` 1601 | axios.get('/static/mock/index.json').then(this.getHomeInfoSuccess) 1602 | ``` 1603 | 但如果当我们发布上线因为本地的模拟的数据不发布,就会找不到index.json, 1604 | ``` 1605 | axios.get('/api/index.json').then(this.getHomeInfoSuccess) 1606 | ``` 1607 | 我们需要对webpack-dev-server进行配置,让它帮我们将请求转发到本地的static/mock文件下 1608 | ``` 1609 | proxyTable: { 1610 | '/api': { 1611 | target: 'http://localhost:8080', 1612 | pathRewrite: { 1613 | '^/api': '/static/mock' 1614 | } 1615 | } 1616 | } 1617 | ``` 1618 | 1619 | 9. 在给旅游景点布局的时候,用到了flex:1,当旅游景点的标题过长需要出现省略号,当我直接给放标题的元素设置 1620 | ``` 1621 | overflow: hidden 1622 | white-space: nowrap 1623 | text-overflow: ellipsis 1624 | ``` 1625 | 并没有出现省略号,改怎么实现呢?可以给父标签设置`min-height: 0`试试 1626 | ``` 1627 | .item-info 1628 | flex 1 1629 | padding .1rem 1630 | min-width 0 1631 | .item-title 1632 | line-height .54rem 1633 | font-size .32rem 1634 | overflow: hidden 1635 | white-space: nowrap 1636 | text-overflow: ellipsis 1637 | ``` 1638 | 1639 | 10. 首页的轮播图如果我们通过接口获取数据的话,因为接口请求需要一定的时间,这时我们为了防止报错,给轮播图的数据默认赋值了空数组,刚开始这个数组没有值,当有值的时候轮播图默认显示到最后一个了。解决办法是加一个条件判断,只有这个数组有值的时候才渲染轮播图组件 1640 | ``` 1641 | 1642 | ... 1643 | 1644 | 1645 | computed: { 1646 | showSwiper () { 1647 | return this.swiperList.length 1648 | } 1649 | } 1650 | ``` 1651 | 1652 | 11. 因为项目我们用的webpack-dev-server当服务器启动的,所以不支持将localhost换成ip地址访问项目,这个时候我们需要对package.json文件进行配置,在package.json文件中找到如下代码 1653 | ``` 1654 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js" 1655 | ``` 1656 | 然后再--inline前面加上--host 0.0.0.0修改完成后的代码如下 1657 | ``` 1658 | "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js" 1659 | ``` 1660 | 1661 | 12. 当我们在手机上拖动城市右侧字母表的时候,会出现整个页面都在跟着拖动 1662 | 1663 | 解决办法,打开Alphabet.vue文件在`touchstart`后加`prevent`属性禁止掉touchstart的默认事件, 1664 | 1665 | ``` 1666 | @touchstart.prevent="handleTouchStart" 1667 | ``` 1668 | 1669 | 13. 某些低版本手机中打开项目会出现白屏的现象,造成白屏的原因一般有两个。 1670 | 1671 | - 低版本手机中的浏览器不支持promise,解决办法:安装`babel-polyfill` 1672 | ``` 1673 | npm install babel-polyfill --save 1674 | ``` 1675 | 并在main.js文件中引入这个包 1676 | ``` 1677 | import 'babel-polyfill' 1678 | ``` 1679 | - 可能是webpack-dev-server的问题 1680 | 1681 | 14. 当我们在项目前后端联调的时候,有时可能接口会出现跨域的情况,这个时候我们无需使用charles做代理,直接配置webpack-dev-server即可,将target配置成服务器接口的地址就行 1682 | ``` 1683 | proxyTable: { 1684 | '/api': { 1685 | target: 'http://localhost:8080' 1686 | } 1687 | } 1688 | ``` 1689 | 1690 | 15. 项目打包:用命令行打开项目,然后在命令行中输入`npm run build`将生成的dist文件下的内容放到服务器的根目录即可,如果想放到特定的目录中运行,例如qunar,我们需要打包的时候对webpack进行配置,打开config文件下的index.js,找到`assetsPublicPath: '/'`修改为` assetsPublicPath: '/qunar'`然后再运行npm run build,将dist目录修改为qunar放到服务器根目录即可 1691 | 1692 | 16. 当我们打包后会生成三个js文件,其中app.js是我们所有的业务代码,当我们在浏览器中开发首页的时候,发现加载app.js文件,这显示是有问题的,因为app.js是我们所有的业务代码,我们只访问了首页,他不应该把city里面的js也加载出来。这个时候我们就需要使用异步组件实现按需加载了。打开router文件夹下index.js文件,修改如下 1693 | ``` 1694 | export default new Router({ 1695 | routes: [ 1696 | { 1697 | path: '/', 1698 | name: 'Home', 1699 | component: () => import('@/pages/home/Home') 1700 | }, { 1701 | path: '/city', 1702 | name: 'City', 1703 | component: () => import('@/pages/city/City') 1704 | } 1705 | ] 1706 | }) 1707 | ``` 1708 | 然后再运行代码,发现每次进入新的页面都会请求对应页面的js文件。注意,除了我们可以在router文件中使用异步组件,在其他组件中我们也可以使用异步组件,例如home文件中我们可以对header使用异步加载的方式 1709 | ``` 1710 | export default { 1711 | name: 'Home', 1712 | components: { 1713 | HomeHeader: () => import('./components/Header') 1714 | } 1715 | } 1716 | ``` 1717 | 虽然按照上述操作帮我们实现了按需加载,但不是任何情况下都需要这样操作的,按需加载在减少了首屏加载的文件大小的同时也增加了http请求的次数,因为当你在访问其他页面的时候会重新其他页面的js逻辑代码。如果我们的app.js文件特别小的时候,就没必要用按需加载了,因为多发一次http请求比首页多加载一点js代码的代价要高 1718 | 1719 | 17. 使用keep-alive优化网页性能 1720 | 当我们从首页进入城市选择页再返回首页,每次都会重新加载对应页面的json文件,这样对性能并不好,我们可以使用`keep-alive`来优化性能。打开App.vue文件用keep-alive包裹router-view标签 1721 | ``` 1722 | 1723 | 1724 | 1725 | ``` 1726 | 重新刷新页面,依次进入首页和城市选择页,对应页面的json文件加载一次,以后再次访问就会从内存中读取了 1727 | 1728 | 但是我们又遇到了新的问题,当我们从城市选择页选择一个页面返回首页的时候,首页数据应该重新请求选中城市的数据。可是当我们使用keep-alive的使用,首页数据也不再重新请求了,这个时候该怎么办呢? 1729 | 1730 | 当我们使用keep-alive的时候会多出一个``方法供我们使用 1731 | 1732 | 18. 项目中当我们点击热销推荐列表中的条目时,应该跳转到对应的详情页,这时我们可以用router-link包裹对应的li标签,但是此时li里面的文字会默认变成蓝色,这并不是我们想要的。我们可以将li标签替换成router-link,具体代码如下 1733 | ``` 1734 | 1735 | .... 1736 | 1737 | ``` 1738 | 1739 | 19. 当我们点击热门推荐中的条目时需要传递条目的id跳转到详情页,详情页根据id的不同显示不同的详情内容。这个时候我们就需要使用到动态路由了,在path属性中添加'/detail/:id',作用是跳转到详情页,详情页根据id的不同显示不同的详情页。 1740 | ``` 1741 | export default new Router({ 1742 | routes: [ 1743 | { 1744 | path: '/detail/:id', 1745 | name: 'Detail', 1746 | component: Detail 1747 | } 1748 | ] 1749 | }) 1750 | ``` 1751 | 1752 | 20. 项目中的icon我封装了一个小小的Icon组件,以后使用icon图片的时候可以在对应组件中如下使用 1753 | ``` 1754 | import Icon from 'base' 1755 | 1756 | 1761 | ``` 1762 | 在封装这个小小的组件的时候,阿里的矢量图库使用的时候要给标签增加两个类,分别为iconfont和icon-图标名称,因为icon的名字是从父组件传递的,所以是动态给第二个类名赋值的,刚开始使用了做react项目中的方法``iconfont icon-${name}``并没有任何效果,查了vue的官方文档才知道要绑定下才行,具体代码如下 1763 | ``` 1764 | 1767 | ``` 1768 | 1769 | 21. 点击详情页banner需要弹出画廊组件,画廊组件使用swiper做的,设计图上分页显示器是数字并且显示在文字的下方,这个时候查看分页器的样式,发现bottom是10px,我把它改成-50px发现看不到分页器了,查看swiper的dom元素发现swiper-container设置了overflow:hidden,然后我将overflow改为overlow:inherit就可以了 1770 | ``` 1771 | .container >>> .swiper-container 1772 | overflow inherit 1773 | 1774 | 1775 | .swiper-pagination 1776 | color #fff 1777 | bottom -1rem 1778 | ``` 1779 | 1780 | 22. 点击详情页banner后需要弹出画廊组件,当拖动画廊组件图片时发现拖动的图片移动出错,这就奇怪了!这是为啥呢???查找的了资料才发现需要设置`observeParents`和`observer`来解决这个问题 1781 | ``` 1782 | swiperOption: { 1783 | // 将observe应用于Swiper的父元素。当Swiper的父元素变化时,例如window.resize,Swiper更新。 1784 | observeParents: true, 1785 | // 启动动态检查器(OB/观众/观看者),当改变swiper的样式(例如隐藏/显示)或者修改swiper的子元素时,自动初始化swiper。默认false,不开启,可以使用update()方法更新。 1786 | observer: true 1787 | } 1788 | ``` 1789 | 1790 | 23. 在做详情页header随着详情页滚动渐隐渐现时给window对象添加了scroll事件,因为使用了keep-alive所以给window对象添加事件、移除事件都需要在keep-alive对应生命周期函数中操作,具体代码如下 1791 | ``` 1792 | activated () { 1793 | window.addEventListener('scroll', this.handleScroll) 1794 | }, 1795 | deactivated () { 1796 | window.removeEventListener('scroll', this.handleScroll) 1797 | } 1798 | ``` 1799 | 1800 | 24. 怎么获取详情页路由中的id呢 1801 | ``` 1802 | this.$route.params.id 1803 | ``` 1804 | 1805 | 25. 当我们在首页向上滚动热销列表时,然后再点击热销列表中查看详情会发现,一进入详情页详情页面发生了滚动,解决办法需要router文件中给router添加`scrollBehavior`属性,具体代码如下 1806 | ``` 1807 | export default new Router({ 1808 | routes: [ 1809 | { 1810 | path: '/', 1811 | name: 'Home', 1812 | component: Home 1813 | } 1814 | ], 1815 | scrollBehavior: function (to, from, savedPosition) { 1816 | return savedPosition || { x: 0, y: 0 } 1817 | } 1818 | }) 1819 | ``` 1820 | 1821 | 26. 因为我们使用了`keep-alive`,进入详情页的时候只会请求一次数据,但详情页的数据应该随着不同的入口而发生变化,也就是每次进入详情页应该去请求数据,我们可以给APP.vue组件添加属性`exclude`属性,该属性值为字符串或正则表达式,意思为任何匹配的组件都不会被缓存。具体代码如下 1822 | ``` 1823 | 1824 | 1825 | 1826 | ``` 1827 | 意思是详情页组件不被缓存,每次进入详情页都要执行mounted函数 1828 | ## 彩蛋 1829 | - vscode stylus插件language-stylus 1830 | - 滑动组件 better-scroll 1831 | ### 联系方式 1832 | 1833 | 坐标:北京 1834 | 1835 | QQ:294925572 1836 | 1837 | QQ技术交流群:678941904 ( 欢迎加入此群,与志同道合的朋友一起进步 ) 1838 | -------------------------------------------------------------------------------- /image/animate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/image/animate.png -------------------------------------------------------------------------------- /image/animate1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/image/animate1.png -------------------------------------------------------------------------------- /image/chuantong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/image/chuantong.png -------------------------------------------------------------------------------- /image/lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/image/lifecycle.png -------------------------------------------------------------------------------- /image/mvvm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/image/mvvm.png -------------------------------------------------------------------------------- /image/qunar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/image/qunar.gif -------------------------------------------------------------------------------- /project/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /project/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /project/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /project/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /project/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | static/mock 8 | 9 | # Editor directories and files 10 | .idea 11 | .vscode 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | -------------------------------------------------------------------------------- /project/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /project/README.md: -------------------------------------------------------------------------------- 1 | # travel 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /project/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /project/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /project/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/project/build/logo.png -------------------------------------------------------------------------------- /project/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /project/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /project/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | 'styles': resolve('src/assets/styles'), 40 | 'base': resolve('src/base/index.js') 41 | } 42 | }, 43 | module: { 44 | rules: [ 45 | ...(config.dev.useEslint ? [createLintingRule()] : []), 46 | { 47 | test: /\.vue$/, 48 | loader: 'vue-loader', 49 | options: vueLoaderConfig 50 | }, 51 | { 52 | test: /\.js$/, 53 | loader: 'babel-loader', 54 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 55 | }, 56 | { 57 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 58 | loader: 'url-loader', 59 | options: { 60 | limit: 10000, 61 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 62 | } 63 | }, 64 | { 65 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 66 | loader: 'url-loader', 67 | options: { 68 | limit: 10000, 69 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 70 | } 71 | }, 72 | { 73 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 74 | loader: 'url-loader', 75 | options: { 76 | limit: 10000, 77 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 78 | } 79 | } 80 | ] 81 | }, 82 | node: { 83 | // prevent webpack from injecting useless setImmediate polyfill because Vue 84 | // source contains it (although only uses it if it's native). 85 | setImmediate: false, 86 | // prevent webpack from injecting mocks to Node native modules 87 | // that does not make sense for the client 88 | dgram: 'empty', 89 | fs: 'empty', 90 | net: 'empty', 91 | tls: 'empty', 92 | child_process: 'empty' 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /project/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /project/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /project/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /project/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/api': { 15 | target: 'http://localhost:8080', 16 | pathRewrite: { 17 | '^/api': '/static/mock' 18 | } 19 | } 20 | }, 21 | 22 | // Various Dev Server settings 23 | host: 'localhost', // can be overwritten by process.env.HOST 24 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 25 | autoOpenBrowser: false, 26 | errorOverlay: true, 27 | notifyOnErrors: true, 28 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 29 | 30 | // Use Eslint Loader? 31 | // If true, your code will be linted during bundling and 32 | // linting errors and warnings will be shown in the console. 33 | useEslint: true, 34 | // If true, eslint errors and warnings will also be shown in the error overlay 35 | // in the browser. 36 | showEslintErrorsInOverlay: false, 37 | 38 | /** 39 | * Source Maps 40 | */ 41 | 42 | // https://webpack.js.org/configuration/devtool/#development 43 | devtool: 'cheap-module-eval-source-map', 44 | 45 | // If you have problems debugging vue-files in devtools, 46 | // set this to false - it *may* help 47 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 48 | cacheBusting: true, 49 | 50 | cssSourceMap: true 51 | }, 52 | 53 | build: { 54 | // Template for index.html 55 | index: path.resolve(__dirname, '../dist/index.html'), 56 | 57 | // Paths 58 | assetsRoot: path.resolve(__dirname, '../dist'), 59 | assetsSubDirectory: 'static', 60 | assetsPublicPath: '/', 61 | 62 | /** 63 | * Source Maps 64 | */ 65 | 66 | productionSourceMap: true, 67 | // https://webpack.js.org/configuration/devtool/#production 68 | devtool: '#source-map', 69 | 70 | // Gzip off by default as many popular static hosts such as 71 | // Surge or Netlify already gzip all static assets for you. 72 | // Before setting to `true`, make sure to: 73 | // npm install --save-dev compression-webpack-plugin 74 | productionGzip: false, 75 | productionGzipExtensions: ['js', 'css'], 76 | 77 | // Run the build command with an extra argument to 78 | // View the bundle analyzer report after build finishes: 79 | // `npm run build --report` 80 | // Set to `true` or `false` to always turn it on or off 81 | bundleAnalyzerReport: process.env.npm_config_report 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /project/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /project/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | travel 7 | 8 | 9 |
    10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "travel", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "FangFeiyue <18766131540@163.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "babel-polyfill": "^6.26.0", 16 | "better-scroll": "^1.10.2", 17 | "fastclick": "^1.0.6", 18 | "stylus": "^0.54.5", 19 | "stylus-loader": "^3.0.2", 20 | "vue": "^2.5.2", 21 | "vue-awesome-swiper": "^2.6.7", 22 | "vue-router": "^3.0.1", 23 | "vuex": "^3.0.1" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "^7.1.2", 27 | "babel-core": "^6.22.1", 28 | "babel-eslint": "^8.2.1", 29 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 30 | "babel-loader": "^7.1.1", 31 | "babel-plugin-syntax-jsx": "^6.18.0", 32 | "babel-plugin-transform-runtime": "^6.22.0", 33 | "babel-plugin-transform-vue-jsx": "^3.5.0", 34 | "babel-preset-env": "^1.3.2", 35 | "babel-preset-stage-2": "^6.22.0", 36 | "chalk": "^2.0.1", 37 | "copy-webpack-plugin": "^4.0.1", 38 | "css-loader": "^0.28.0", 39 | "eslint": "^4.15.0", 40 | "eslint-config-standard": "^10.2.1", 41 | "eslint-friendly-formatter": "^3.0.0", 42 | "eslint-loader": "^1.7.1", 43 | "eslint-plugin-import": "^2.7.0", 44 | "eslint-plugin-node": "^5.2.0", 45 | "eslint-plugin-promise": "^3.4.0", 46 | "eslint-plugin-standard": "^3.0.1", 47 | "eslint-plugin-vue": "^4.0.0", 48 | "extract-text-webpack-plugin": "^3.0.0", 49 | "file-loader": "^1.1.4", 50 | "friendly-errors-webpack-plugin": "^1.6.1", 51 | "html-webpack-plugin": "^2.30.1", 52 | "node-notifier": "^5.1.2", 53 | "optimize-css-assets-webpack-plugin": "^3.2.0", 54 | "ora": "^1.2.0", 55 | "portfinder": "^1.0.13", 56 | "postcss-import": "^11.0.0", 57 | "postcss-loader": "^2.0.8", 58 | "postcss-url": "^7.2.1", 59 | "rimraf": "^2.6.0", 60 | "semver": "^5.3.0", 61 | "shelljs": "^0.7.6", 62 | "uglifyjs-webpack-plugin": "^1.1.1", 63 | "url-loader": "^0.5.8", 64 | "vue-loader": "^13.3.0", 65 | "vue-style-loader": "^3.0.1", 66 | "vue-template-compiler": "^2.5.2", 67 | "webpack": "^3.6.0", 68 | "webpack-bundle-analyzer": "^2.9.0", 69 | "webpack-dev-server": "^2.9.1", 70 | "webpack-merge": "^4.1.0" 71 | }, 72 | "engines": { 73 | "node": ">= 6.0.0", 74 | "npm": ">= 3.0.0" 75 | }, 76 | "browserslist": [ 77 | "> 1%", 78 | "last 2 versions", 79 | "not ie <= 8" 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /project/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 17 | -------------------------------------------------------------------------------- /project/src/assets/styles/border.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | .border, 3 | .border-top, 4 | .border-right, 5 | .border-bottom, 6 | .border-left, 7 | .border-topbottom, 8 | .border-rightleft, 9 | .border-topleft, 10 | .border-rightbottom, 11 | .border-topright, 12 | .border-bottomleft { 13 | position: relative; 14 | } 15 | .border::before, 16 | .border-top::before, 17 | .border-right::before, 18 | .border-bottom::before, 19 | .border-left::before, 20 | .border-topbottom::before, 21 | .border-topbottom::after, 22 | .border-rightleft::before, 23 | .border-rightleft::after, 24 | .border-topleft::before, 25 | .border-topleft::after, 26 | .border-rightbottom::before, 27 | .border-rightbottom::after, 28 | .border-topright::before, 29 | .border-topright::after, 30 | .border-bottomleft::before, 31 | .border-bottomleft::after { 32 | content: "\0020"; 33 | overflow: hidden; 34 | position: absolute; 35 | } 36 | /* border 37 | * 因,边框是由伪元素区域遮盖在父级 38 | * 故,子级若有交互,需要对子级设置 39 | * 定位 及 z轴 40 | */ 41 | .border::before { 42 | box-sizing: border-box; 43 | top: 0; 44 | left: 0; 45 | height: 100%; 46 | width: 100%; 47 | border: 1px solid #eaeaea; 48 | transform-origin: 0 0; 49 | } 50 | .border-top::before, 51 | .border-bottom::before, 52 | .border-topbottom::before, 53 | .border-topbottom::after, 54 | .border-topleft::before, 55 | .border-rightbottom::after, 56 | .border-topright::before, 57 | .border-bottomleft::before { 58 | left: 0; 59 | width: 100%; 60 | height: 1px; 61 | } 62 | .border-right::before, 63 | .border-left::before, 64 | .border-rightleft::before, 65 | .border-rightleft::after, 66 | .border-topleft::after, 67 | .border-rightbottom::before, 68 | .border-topright::after, 69 | .border-bottomleft::after { 70 | top: 0; 71 | width: 1px; 72 | height: 100%; 73 | } 74 | .border-top::before, 75 | .border-topbottom::before, 76 | .border-topleft::before, 77 | .border-topright::before { 78 | border-top: 1px solid #eaeaea; 79 | transform-origin: 0 0; 80 | } 81 | .border-right::before, 82 | .border-rightbottom::before, 83 | .border-rightleft::before, 84 | .border-topright::after { 85 | border-right: 1px solid #eaeaea; 86 | transform-origin: 100% 0; 87 | } 88 | .border-bottom::before, 89 | .border-topbottom::after, 90 | .border-rightbottom::after, 91 | .border-bottomleft::before { 92 | border-bottom: 1px solid #eaeaea; 93 | transform-origin: 0 100%; 94 | } 95 | .border-left::before, 96 | .border-topleft::after, 97 | .border-rightleft::after, 98 | .border-bottomleft::after { 99 | border-left: 1px solid #eaeaea; 100 | transform-origin: 0 0; 101 | } 102 | .border-top::before, 103 | .border-topbottom::before, 104 | .border-topleft::before, 105 | .border-topright::before { 106 | top: 0; 107 | } 108 | .border-right::before, 109 | .border-rightleft::after, 110 | .border-rightbottom::before, 111 | .border-topright::after { 112 | right: 0; 113 | } 114 | .border-bottom::before, 115 | .border-topbottom::after, 116 | .border-rightbottom::after, 117 | .border-bottomleft::after { 118 | bottom: 0; 119 | } 120 | .border-left::before, 121 | .border-rightleft::before, 122 | .border-topleft::after, 123 | .border-bottomleft::before { 124 | left: 0; 125 | } 126 | @media (max--moz-device-pixel-ratio: 1.49), (-webkit-max-device-pixel-ratio: 1.49), (max-device-pixel-ratio: 1.49), (max-resolution: 143dpi), (max-resolution: 1.49dppx) { 127 | /* 默认值,无需重置 */ 128 | } 129 | @media (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49), (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49), (min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49), (min-resolution: 144dpi) and (max-resolution: 239dpi), (min-resolution: 1.5dppx) and (max-resolution: 2.49dppx) { 130 | .border::before { 131 | width: 200%; 132 | height: 200%; 133 | transform: scale(.5); 134 | } 135 | .border-top::before, 136 | .border-bottom::before, 137 | .border-topbottom::before, 138 | .border-topbottom::after, 139 | .border-topleft::before, 140 | .border-rightbottom::after, 141 | .border-topright::before, 142 | .border-bottomleft::before { 143 | transform: scaleY(.5); 144 | } 145 | .border-right::before, 146 | .border-left::before, 147 | .border-rightleft::before, 148 | .border-rightleft::after, 149 | .border-topleft::after, 150 | .border-rightbottom::before, 151 | .border-topright::after, 152 | .border-bottomleft::after { 153 | transform: scaleX(.5); 154 | } 155 | } 156 | @media (min--moz-device-pixel-ratio: 2.5), (-webkit-min-device-pixel-ratio: 2.5), (min-device-pixel-ratio: 2.5), (min-resolution: 240dpi), (min-resolution: 2.5dppx) { 157 | .border::before { 158 | width: 300%; 159 | height: 300%; 160 | transform: scale(.33333); 161 | } 162 | .border-top::before, 163 | .border-bottom::before, 164 | .border-topbottom::before, 165 | .border-topbottom::after, 166 | .border-topleft::before, 167 | .border-rightbottom::after, 168 | .border-topright::before, 169 | .border-bottomleft::before { 170 | transform: scaleY(.33333); 171 | } 172 | .border-right::before, 173 | .border-left::before, 174 | .border-rightleft::before, 175 | .border-rightleft::after, 176 | .border-topleft::after, 177 | .border-rightbottom::before, 178 | .border-topright::after, 179 | .border-bottomleft::after { 180 | transform: scaleX(.33333); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /project/src/assets/styles/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('./iconfont/iconfont.eot?t=1526544059248'); /* IE9*/ 4 | src: url('./iconfont/iconfont.eot?t=1526544059248#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAYoAAsAAAAACMQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kmAY21hcAAAAYAAAAB4AAAByJyx06pnbHlmAAAB+AAAAgYAAAI0G3DEeWhlYWQAAAQAAAAALwAAADYRZfNzaGhlYQAABDAAAAAeAAAAJAfeA4pobXR4AAAEUAAAABYAAAAYF+wAAGxvY2EAAARoAAAADgAAAA4CYgGabWF4cAAABHgAAAAfAAAAIAEXAF1uYW1lAAAEmAAAAUUAAAJtPlT+fXBvc3QAAAXgAAAARwAAAFmL/0dmeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/ss4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDyfyNzwv4EhhrmBoQEozAiSAwAv0g0DeJzFkcEJhTAQRN9q/IiIldiBHcivRzzYhE2uZehsogcrcMILmWHJhizQALUYRQLbMEKLUst5TZfzxCzf01LpvDo++HTs56n07R6Zqp8VrtFdVe7y4zPZd63f6vP+v13MYb3RE51C5D4UYk4+FfSPHHuBdAEj3hq3eJw1UU1rE2EQnnnf/ci2ySa72Y9k03zsbrNvpG2K2zQ9pCYWVKp4UETBYz0pGC+iuQhGW0HQg4deW0UErxoQPFQI/gFTf0BR9CJ48Sa2+9aNxWGeOTzzzPAMAyLA4Ve6Q3OQhRoch1NwAQClGfRUUkSXNepkBk1XNG1Dpcxnrux7dXoCbU8yrLDZCGxJltKoYgkX3LDJ6oThYqNNWhhaRcR8wbmkV6d0+gwncqz0iJ8jL9Es+1Pp9hw/O9sxwko20Uvqel7XnyYkUUwQIqRV7NqWIioTEn8lph1zp3yMlDGZZ875q6lKQV973LhVrNoKYr+P2UJFfd3RHC3Oe46V1fNyJpXIOSl/2sDe98lcNlkMvkEc8rjQIfkFCVBABwscKIEX3xuAHEiy6aLfaIaWIXkBaq5GXc2VY7AYdHqNjzKrl5F0+I3TtRZjrRr/TfvNg5/USEUS+bMQ3SSb+OY5/+E+uGtcw4ulWMMYZqIK+RJdIXcmoxchHsQWyNgH+UQiEEECUFBW0Ea8j2dqfBN7AX+LT/gHhtfxdsDfxfb++V4hK+PJrItD0udDcpL3j3Ydvqcf6SqY4AJU27hYR6aiXEI7bC7Z4x+xaky1cSmmVCSffUZ3t7ZHgjDaXu7O72lznhbtbwwoHWysDwRhEM13l4/aW7u0Wt7T/Fn9oTBY/y/5CzYrd3QAAHicY2BkYGAA4gdaZb7x/DZfGbhZGEDgunL4bgT9fwcLA7MHkMvBwAQSBQAXNQmmAHicY2BkYGBu+N/AEMPCzAAELAwMjAyogA0AR1cCcgAAeJxjYWBgYH7JwMDCgISZGRgAEqgBBAAAAAAAAAB2ALoA0gDgARoAAHicY2BkYGBgYwhkYGcAASYg5gJCBob/YD4DABF2AXUAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicbcExDoAwCAVQflVMnLyIhyLRGBzAlBJ7fAdX36NCn4X+MQoGjJjAmAmdW94qxvVoWW0NsUvFu9q57f4Yh2ekE71O5A8uAA==') format('woff'), 6 | url('./iconfont/iconfont.ttf?t=1526544059248') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('./iconfont/iconfont.svg?t=1526544059248#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-tupian:before { content: "\e63b"; } 19 | 20 | .icon-return:before { content: "\e60e"; } 21 | 22 | .icon-sanjiaoxing-down:before { content: "\e791"; } 23 | 24 | .icon-sousuo:before { content: "\e600"; } 25 | 26 | -------------------------------------------------------------------------------- /project/src/assets/styles/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/project/src/assets/styles/iconfont/iconfont.eot -------------------------------------------------------------------------------- /project/src/assets/styles/iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /project/src/assets/styles/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/project/src/assets/styles/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /project/src/assets/styles/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/project/src/assets/styles/iconfont/iconfont.woff -------------------------------------------------------------------------------- /project/src/assets/styles/mixins.styl: -------------------------------------------------------------------------------- 1 | ellipsis() 2 | overflow: hidden 3 | white-space: nowrap 4 | text-overflow: ellipsis 5 | -------------------------------------------------------------------------------- /project/src/assets/styles/reset.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8";html{background-color:#fff;color:#000;font-size:12px} 2 | body,ul,ol,dl,dd,h1,h2,h3,h4,h5,h6,figure,form,fieldset,legend,input,textarea,button,p,blockquote,th,td,pre,xmp{margin:0;padding:0} 3 | body,input,textarea,button,select,pre,xmp,tt,code,kbd,samp{line-height:1.5;font-family:tahoma,arial,"Hiragino Sans GB",simsun,sans-serif} 4 | h1,h2,h3,h4,h5,h6,small,big,input,textarea,button,select{font-size:100%} 5 | h1,h2,h3,h4,h5,h6{font-family:tahoma,arial,"Hiragino Sans GB","微软雅黑",simsun,sans-serif} 6 | h1,h2,h3,h4,h5,h6,b,strong{font-weight:normal} 7 | address,cite,dfn,em,i,optgroup,var{font-style:normal} 8 | table{border-collapse:collapse;border-spacing:0;text-align:left} 9 | caption,th{text-align:inherit} 10 | ul,ol,menu{list-style:none} 11 | fieldset,img{border:0} 12 | img,object,input,textarea,button,select{vertical-align:middle} 13 | article,aside,footer,header,section,nav,figure,figcaption,hgroup,details,menu{display:block} 14 | audio,canvas,video{display:inline-block;*display:inline;*zoom:1} 15 | blockquote:before,blockquote:after,q:before,q:after{content:"\0020"} 16 | textarea{overflow:auto;resize:vertical} 17 | input,textarea,button,select,a{outline:0 none;border: none;} 18 | button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0} 19 | mark{background-color:transparent} 20 | a,ins,s,u,del{text-decoration:none} 21 | sup,sub{vertical-align:baseline} 22 | html {overflow-x: hidden;height: 100%;font-size: 50px;-webkit-tap-highlight-color: transparent;} 23 | body {font-family: Arial, "Microsoft Yahei", "Helvetica Neue", Helvetica, sans-serif;color: #333;font-size: .28em;line-height: 1;-webkit-text-size-adjust: none;} 24 | hr {height: .02rem;margin: .1rem 0;border: medium none;border-top: .02rem solid #cacaca;} 25 | a {color: #25a4bb;text-decoration: none;} 26 | -------------------------------------------------------------------------------- /project/src/assets/styles/varibles.styl: -------------------------------------------------------------------------------- 1 | $bgColor = #00bcd4 2 | $darkTextColor = #333 3 | $headerHeight = .86rem 4 | -------------------------------------------------------------------------------- /project/src/base/fade/fadeAnimation.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /project/src/base/gallary/Gallary.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 44 | 45 | 69 | -------------------------------------------------------------------------------- /project/src/base/icon/Icon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /project/src/base/index.js: -------------------------------------------------------------------------------- 1 | import Icon from './icon/Icon' 2 | import CommonGallary from './gallary/Gallary' 3 | import FadeAnimation from './fade/fadeAnimation' 4 | export {Icon, FadeAnimation, CommonGallary} 5 | -------------------------------------------------------------------------------- /project/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 96 | 97 | 98 | 114 | -------------------------------------------------------------------------------- /project/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import 'styles/reset.css' 7 | import store from './store/index' 8 | import 'babel-polyfill' 9 | import 'styles/border.css' 10 | import 'styles/iconfont.css' 11 | import fastClick from 'fastclick' 12 | import VueAwesomeSwiper from 'vue-awesome-swiper' 13 | import 'swiper/dist/css/swiper.css' 14 | 15 | Vue.config.productionTip = false 16 | fastClick.attach(document.body) 17 | Vue.use(VueAwesomeSwiper) 18 | 19 | /* eslint-disable no-new */ 20 | new Vue({ 21 | el: '#app', 22 | router, 23 | store, 24 | components: { App }, 25 | template: '' 26 | }) 27 | -------------------------------------------------------------------------------- /project/src/pages/city/City.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 55 | 56 | 59 | -------------------------------------------------------------------------------- /project/src/pages/city/components/Alphabet.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 61 | 62 | 78 | -------------------------------------------------------------------------------- /project/src/pages/city/components/Header.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 35 | -------------------------------------------------------------------------------- /project/src/pages/city/components/List.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 64 | 65 | 125 | -------------------------------------------------------------------------------- /project/src/pages/city/components/Search.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 70 | 71 | 101 | -------------------------------------------------------------------------------- /project/src/pages/detail/Detail.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 54 | 55 | 59 | -------------------------------------------------------------------------------- /project/src/pages/detail/components/Banner.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 50 | 51 | 84 | -------------------------------------------------------------------------------- /project/src/pages/detail/components/Header.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 49 | 50 | 84 | -------------------------------------------------------------------------------- /project/src/pages/detail/components/List.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | 45 | -------------------------------------------------------------------------------- /project/src/pages/home/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 68 | 69 | 72 | -------------------------------------------------------------------------------- /project/src/pages/home/components/Header.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 33 | 34 | 66 | -------------------------------------------------------------------------------- /project/src/pages/home/components/Icons.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 47 | 48 | 86 | -------------------------------------------------------------------------------- /project/src/pages/home/components/Recommend.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | 26 | 61 | -------------------------------------------------------------------------------- /project/src/pages/home/components/Swiper.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | 37 | 48 | -------------------------------------------------------------------------------- /project/src/pages/home/components/Weekend.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 26 | 27 | 51 | -------------------------------------------------------------------------------- /project/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from '@/pages/home/Home' 4 | import City from '@/pages/city/City' 5 | import Detail from '@/pages/detail/Detail' 6 | 7 | Vue.use(Router) 8 | 9 | export default new Router({ 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'Home', 14 | component: Home 15 | }, { 16 | path: '/city', 17 | name: 'City', 18 | component: City 19 | }, { 20 | path: '/detail/:id', 21 | name: 'Detail', 22 | component: Detail 23 | } 24 | ], 25 | scrollBehavior: function (to, from, savedPosition) { 26 | return savedPosition || { x: 0, y: 0 } 27 | } 28 | }) 29 | // export default new Router({ 30 | // routes: [ 31 | // { 32 | // path: '/', 33 | // name: 'Home', 34 | // component: () => import('@/pages/home/Home') 35 | // }, { 36 | // path: '/city', 37 | // name: 'City', 38 | // component: () => import('@/pages/city/City') 39 | // } 40 | // ] 41 | // }) 42 | -------------------------------------------------------------------------------- /project/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import state from './state' 3 | import mutations from './mutations' 4 | import Vuex from 'vuex' 5 | 6 | Vue.use(Vuex) 7 | 8 | const store = new Vuex.Store({ 9 | state, 10 | actions: { 11 | changeCity (ctx, city) { 12 | ctx.commit('changeCity', city) 13 | } 14 | }, 15 | mutations 16 | }) 17 | 18 | export default store 19 | -------------------------------------------------------------------------------- /project/src/store/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | changeCity (state, city) { 3 | state.city = city 4 | try { 5 | localStorage.city = city 6 | } catch (error) { 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /project/src/store/state.js: -------------------------------------------------------------------------------- 1 | let defaultCity = '北京' 2 | 3 | try { 4 | if (localStorage.city) { 5 | defaultCity = localStorage.city 6 | } 7 | } catch (error) { 8 | } 9 | 10 | export default { 11 | city: defaultCity 12 | } 13 | -------------------------------------------------------------------------------- /project/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heidoua/vue-qu-na/f705ff56df2fe91a6966214fa248978e06a90f98/project/static/.gitkeep -------------------------------------------------------------------------------- /setting_up/01/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello World 8 | 9 | 10 | 11 |
    {{content}}
    12 | 24 | 25 | -------------------------------------------------------------------------------- /setting_up/02/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | TodoList 8 | 9 | 10 | 11 |
    12 | 13 | 14 |
      15 | 16 | 20 | 21 |
    22 |
    23 | 53 | 54 | -------------------------------------------------------------------------------- /setting_up/03mvvm/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | TodoList 8 | 9 | 10 | 11 |
    12 | 13 | 14 | 15 |
      16 |
    17 |
    18 | 50 | 51 | -------------------------------------------------------------------------------- /setting_up/04组件间传值/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 |
      13 | 19 | 20 |
    21 |
    22 | 23 | 53 | 54 | -------------------------------------------------------------------------------- /setting_up/05生命周期/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue实例生命周期函数 8 | 9 | 10 |
    hello world
    11 | 12 | 42 | 43 | -------------------------------------------------------------------------------- /setting_up/06模板语法/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | {{message}} 12 |
    13 |
    14 |
    15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /setting_up/07计算属性,方法与侦听器/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | {{fullName}} 12 |
    13 | 14 | 47 | 48 | -------------------------------------------------------------------------------- /setting_up/08计算属性的 getter和setter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | {{fullName}} 12 |
    13 | 14 | 35 | 36 | -------------------------------------------------------------------------------- /setting_up/09Vue中的样式绑定/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 13 | 14 | 15 |
    16 |
    20 | hello world 21 |
    22 |
    26 | hello world 27 |
    28 |
    32 | hello world 33 |
    34 |
    35 | 36 | 55 | 56 | -------------------------------------------------------------------------------- /setting_up/10Vue中的条件渲染/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 |
    if
    12 |
    else if
    13 |
    else
    14 |
    15 | 16 | 25 | 26 | -------------------------------------------------------------------------------- /setting_up/10Vue中的条件渲染/key.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 |
    12 | 姓名 13 |
    14 |
    15 | 密码 16 |
    17 |
    18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /setting_up/11Vue中的列表渲染/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 |
    12 | {{index}}=>{{item}} 13 |
    14 |
    15 | {{index}}=>{{key}}=>{{item}} 16 |
    17 |
    18 | 19 | 40 | 41 | -------------------------------------------------------------------------------- /setting_up/12Vue中的set方法/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 |
    {{item}}
    13 |
    14 |
    {{item}}
    15 |
    16 |
    17 | 18 | 40 | 41 | -------------------------------------------------------------------------------- /setting_up/13使用组件的细节点/componentref.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 |
    {{total}}
    14 |
    15 | 16 | 43 | 44 | -------------------------------------------------------------------------------- /setting_up/13使用组件的细节点/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 |
    19 | 20 | 32 | 33 | -------------------------------------------------------------------------------- /setting_up/13使用组件的细节点/index1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 |
    19 | 20 | 41 | 42 | -------------------------------------------------------------------------------- /setting_up/13使用组件的细节点/refs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 |
    hello world
    12 |
    13 | 14 | 24 | 25 | -------------------------------------------------------------------------------- /setting_up/14父子组件间的数据传递/01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 | {{total}} 14 |
    15 | 16 | 47 | 48 | -------------------------------------------------------------------------------- /setting_up/15组件参数校验与非props特性/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 |
    13 | 14 | 44 | 45 | -------------------------------------------------------------------------------- /setting_up/16给组件绑定原生事件/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 |
    13 | 14 | 32 | 33 | -------------------------------------------------------------------------------- /setting_up/17非父子组件间的传值/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 |
    14 | 15 | 47 | 48 | -------------------------------------------------------------------------------- /setting_up/18在Vue中使用插槽/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 |
    12 | 13 |

    header

    14 |

    footer

    15 |
    16 |
    17 | 18 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /setting_up/19作用域插槽/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 17 | 18 |
    19 | 20 | 40 | 41 | -------------------------------------------------------------------------------- /setting_up/20动态组件与v-once指令/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 14 | 15 |
    16 | 17 | 46 | 47 | -------------------------------------------------------------------------------- /setting_up/21Vue中CSS动画原理/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 | 19 |
    20 | 21 |
    hello world
    22 |
    23 | 24 |
    25 | 38 | 39 | -------------------------------------------------------------------------------- /setting_up/22在Vue中使用animate.css库/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 11 | 12 | 13 | 14 |
    15 | 19 |
    hello world
    20 |
    21 | 22 |
    23 | 24 | 37 | 38 | -------------------------------------------------------------------------------- /setting_up/23在Vue中同时使用过渡和动画/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 19 | 20 | 21 |
    22 | 30 |
    hello world
    31 |
    32 | 33 |
    34 | 35 | 48 | 49 | -------------------------------------------------------------------------------- /setting_up/24Vue中的 Js 动画与Velocity.js的结合/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 16 |
    hello world
    17 |
    18 | 19 |
    20 | 21 | 22 | 48 | 49 | -------------------------------------------------------------------------------- /setting_up/25Vue中多个元素或组件的过渡/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
    19 | 20 |
    hello world
    21 |
    by world
    22 |
    23 | 24 |
    25 | 26 | 39 | 40 | -------------------------------------------------------------------------------- /setting_up/26Vue中的列表过渡/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
    19 | 20 |
    {{item.title}}
    21 |
    22 | 23 |
    24 | 25 | 42 | 43 | -------------------------------------------------------------------------------- /setting_up/27Vue中的动画封装/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 |
    12 | 13 |
    hello world
    14 |
    15 | 16 |
    17 | 18 | 53 | 54 | --------------------------------------------------------------------------------