Vue 项目实战系列 (三)

  我们继续前两节的开发。本节教程实现的效果如下:

  效果很简单,但是实现起来却要用到Vue的很多知识,下面我们将一步一步的实现这个效果。

  首先这些城市的信息都是从后台的server里面获取的,所以我们需要一个后台,后台的代码可以从

https://github.com/EzrealDeng/Taopiaopiao里面的server 文件夹获取,这个server端具体怎么实现的我们暂时不用关心,只需要知道这是一个可以返回我们需要的数据的后台服务即可。

下载完后进入文件夹执行:

npm run start //启动后台服务

即可启动服务,(如果启动过程中出错,可以使用npm run start:strict 启动,或者升级node版本,默认的是9090端口,可以手动进行修改)。

  成功启动的话我们就有了一个可以使用的数据后台了。那么Vue如何访问这个接口的呢,我们这里使用vue-resource(类似于Jquery里的ajax的功能)进行访问后台接口。vue-resource的使用方式类似下面的例子:

this.$http.get(‘/movie/swiper‘).then(function(res){ //返回的是promise对象,res为返回的对象
    console.log(res);
    this.images = res.body.data.data.returnValue;
    console.log(this.images);
})

  有了这个我们就可以和后台进行数据交互了,但是还有一个问题,我们的开发是vue脚手架自动搭建的一个基于Express的服务,我们的前端代码实际上直接访问的都是这个前端项目的后台,想要直接访问我们刚才搭建的后台会有跨域问题的,怎么办呢?幸好有一个叫做http-proxy的东西,可以帮我们实现代理,将我们要访问的接口都映射到真正的服务器上,后端进行跨域问题的解决。而且Vue脚手架也帮我们集成了这个插件,只要在配置文件里修改即可。这个文件在:config/index.js里,修改这个文件里的proxyTable如下

.....//省略
dev: {
    env: require(‘./dev.env‘),
    port: 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: ‘static‘,
    assetsPublicPath: ‘/‘,
    proxyTable: {
        ‘/movie/coming‘: ‘http://localhost:9090‘,
        ‘/movie/hot‘: ‘http://localhost:9090‘,
        ‘/movie/info‘: ‘http://localhost:9090‘,
        ‘/movie/evaluation‘: ‘http://localhost:9090‘,
        ‘/movie/cinema‘: ‘http://localhost:9090‘,
        ‘/movie/cinema_detail‘: ‘http://localhost:9090‘,
        ‘/movie/swiper‘: ‘http://localhost:9090‘,
        ‘/movie/city‘: ‘http://localhost:9090‘
    },
...//省略

到现在为止,接口的访问问题已经解决了。

  还有最后一个准备知识需要介绍下,那就是Vuex,这是个啥呢?就是个状态管理机,由于Vue好多个组件可能会共用同一个状态信息,例如我们的淘票票,选择城市的这个组件需要知道当前的城市信息,而每个城市会有当前不同的上映的电影,这个反应当前热映电影的组件也需要知道这个信息,如何保持数据的同步,光靠组件之间的通信将会十分复杂,所以有了Vuex这个东西,具体如何使用可以去看https://vuex.vuejs.org/zh-cn/官网的介绍。本例中用到的地方我会写上注释。接下来开始正式的编码吧。

  上面我们介绍了这么多的东西,使用起来当然得先一一的安装一遍。

  

npm install vuex --save //vuex
npm install vue-resource --save //vue-resource

另外我们选择城市的组件用到了mint-ui的Vue组件库,所以要也要安装。

npm install mint-ui //mint-ui

接下来新建文件和目录如下:

主要是新建立home/city.vue和store文件夹。city.vue是我们的选择城市的组件,store存放的是Vuex状态管理文件。

依次修改city.vue如下:

<template>
    <section ref="city" id="select-city" class="pf fadeInDown" v-if="$store.state.city.show">
        <header class="city-header mint-1px-b pr">
            <span class="fb">选择城市</span>
            <span class="close-city pa" @click="cancelCityList">×</span>
        </header>
        <div ref="city" @click="selectCity">
            <mt-index-list>
              <mt-index-section :index="city.sort" v-for="city in cityList" key="city.id">
                <mt-cell :title="name.regionName" v-for="name in city.data" key="name.id"></mt-cell>
              </mt-index-section>
            </mt-index-list>
        </div>
    </section>
</template>

<script>//mapActions,mapMutations可以获取我们在store里面的所有actions,mutations方法,
import { mapActions, mapMutations } from ‘vuex‘

export default{
    data () {
        return {
            showCityList: true,
            cityList: []
        }
    },
    methods: {
        ...mapActions([
            ‘updateCityAsync‘
        ]),
        ...mapMutations([
              ‘pushLoadStack‘,
              ‘completeLoad‘
        ]),     //封装了一下vue-resource的请求方法
        requestData (url, fn) {
              this.pushLoadStack()
              this.$http.get(url).then(fn).then(this.completeLoad)
        },
        changeCityData (data) {
            this.pushLoadStack()
            this.$refs.city.className = "pf fadeOutTop"
            this.$store.dispatch(‘updateCityAsync‘, data).then(this.completeLoad)
        },
        matchCityStr (str) {
            let randomList = [‘bj‘, ‘sh‘, ‘gz‘]
            let randomCity = randomList[Math.floor(3*Math.random())]
            switch (str) {
                case ‘北京‘: return ‘bj‘
                case ‘上海‘: return ‘sh‘
                case ‘广州‘: return ‘gz‘
                default: return randomCity
            }
        },    //选择城市事件
        selectCity (event) {
            let ele = event.target
            let className = ele.className
            let name = ‘‘
            if (className === "mint-indexsection-index" || className ==="mint-indexlist-nav" || className === "mint-indexlist-navitem") {
                name = ‘‘
            } else if (className === ‘mint-cell-wrapper‘) {
                name = ele.children[0].children[0].innerHTML
            } else if (className === "mint-cell-title") {
                name = ele.children[0].innerHTML
            } else {
                name = ele.innerHTML
            }
            if (name) {
                this.changeCityData({
                    city: {
                        name: name,
                        rN: this.matchCityStr(name)
                    }
                })
            } else {
                return false
            }
        },
        cancelCityList () {
            this.changeCityData({city: {}})
        }
    },
    created () {
        //this.$store.dispatch(‘updateCityAsync‘, {city: {}})
        this.requestData(‘/movie/city‘, (response) => {
            // let data = JSON.parse(response.data)
            let data = response.data
            let cityObj = data.data.data.returnValue
            let citySort = Object.keys(cityObj)
            this.cityList.push({ //先push进去三个热门城市
                sort: ‘热门‘,
                data: [{
                    regionName: ‘北京‘,
                    id: 1,
                    rN: ‘bj‘
                }, {
                    regionName: ‘上海‘,
                    id: 2,
                    rN: ‘sh‘
                }, {
                    regionName: ‘广州‘,
                    id: 3,
                    rN: ‘gz‘
                }]
            })
            citySort.forEach((item) => { //获取后台的城市信息并且按分类信息进行排序
                this.cityList.push({
                    sort: item,
                    data: cityObj[item]
                })
            })
        })
    }
}
</script>

<style>
.mint-indicator-wrapper {
    z-index: 1000
}
#select-city {
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 9999999;
    background-color: #fff;
}
.city-header {
    height: 46px;
    line-height: 46px;
    text-align: center;
    color: #000;
    font-size: 16px;
    background-color: #fff;
}
.close-city {
    font-size: 28px;
    color: #666;
    display: inline-block;
    height: 46px;
    width: 50px;
    line-height: 38px;
    text-align: center;
    right: 0px;
}
@-webkit-keyframes fadeInDown {
  0% {
    opacity: .7;
    -webkit-transform: translateY(-50px);
    transform: translateY(-50px)
  }
  100% {
    opacity: 1;
    -webkit-transform: translateY(0);
    transform: translateY(0)
  }
}
@keyframes fadeInDown {
  0% {
    opacity: .7;
    -webkit-transform: translateY(-50px);
    -ms-transform: translateY(-50px);
    transform: translateY(-50px)
  }
  100% {
    opacity: 1;
    -webkit-transform: translateY(0);
    -ms-transform: translateY(0);
    transform: translateY(0)
  }
}
.fadeInDown {
  -webkit-animation: fadeInDown .3s;
  animation: fadeInDown .3s;
}
@-webkit-keyframes fadeOutTop {
  0% {
    opacity: 1;
    -webkit-transform: translateY(0);
    transform: translateY(0)
  }
  100% {
        opacity: 0;
        -webkit-transform: translateY(-50px);
        transform: translateY(-50px)
  }
}
@keyframes fadeOutTop {
  0% {
    opacity: 1;
    -webkit-transform: translateY(0);
    -ms-transform: translateY(0);
    transform: translateY(0)
  }
  100% {
      opacity: 0;
      -webkit-transform: translateY(-50px);
      -ms-transform: translateY(-50px);
      transform: translateY(-50px)
  }
}
.fadeOutTop {
  -webkit-animation: fadeOutTop .35s;
  animation: fadeOutTop .35s;
}
</style>

store/city/actions.js如下:

import Vue from ‘vue‘
export default {  //异步的更新城市信息
  updateCityAsync ({ commit, state }, {city}) { //commit对象可以用来触发mutations里面的同步更新城市方法
      if (!city.name) {
          city.name = state.name
          city.rN = state.rN
      }
    return  Vue.http.get(`/movie/hot/?city=${city.rN}`).then((response) => {
      let data = response.data
      let lists = data.data.data.returnValue
      //模拟索引数据的id号
      lists.forEach((item, index) => {
        item.mID = index
      })
          city.data  = lists
          commit(‘UPDATE‘, { city }) // 更新城市信息
      })
  }
}

mutations.js如下:

export default{
    UPDATE (state , { city }){ //根据传入的city对象来改变状态
        if(city.name){
            state.name = city.name;
            state.data = city.data;
            state.rN = city.rN;
        }
        state.show = false;
    },
    showCityList (state) { //显示城市选择
      state.show = true
  }
}

store/loading/mutations.js如下:

//loading组件import { Indicator } from ‘mint-ui‘;
export default {
  pushLoadStack (state) {
    Indicator.open({
      text: ‘loading...‘,
      spinnerType: ‘snake‘
    });
      state.stack.push(1)
  },
  completeLoad (state) { //完成加载
      let stack = state.stack
    stack.pop()
    if (!stack.length) {
      //延时为了更好显示loading效果
      setTimeout(() => {
        Indicator.close()
      }, 500)
    }
  }
}

然后再修改store下的index.js,这个文件是所有的mutations和actions的总出口。

import Vue from ‘vue‘
import cityMutations from ‘./city/mutations‘
import cityAcions from ‘./city/actions‘
import loadingMutations from ‘./loading/mutations‘
import Vuex from ‘vuex‘
Vue.use(Vuex) //vue插件只需要在这里use一次

const cityGetters = {
    movies: state => state.data,
    cityName: state => state.name
}

const city = {
    state: {
        name: ‘北京‘,
        show: true,
        rN: ‘bj‘,
        data: []
    },
    actions: cityAcions,
    mutations: cityMutations,
    getters: cityGetters
}

const loading = {
    state: {
        stack: []
    },
    mutations: loadingMutations
}

export default new Vuex.Store({
     modules: {
         city,
         loading
     }
})

然后再修改src下的main.js如下:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from ‘vue‘
import App from ‘./App‘
import router from ‘./router‘
import Mint from ‘mint-ui‘;
import store from ‘./store‘
import VueResource from ‘vue-resource‘
import ‘mint-ui/lib/style.css‘
Vue.config.productionTip = false

Vue.use(Mint)
Vue.use(VueResource)

/* eslint-disable no-new */
new Vue({
  el: ‘#app‘,
  router,
  store,
  template: ‘<App/>‘,
  components: { App }
})

然后再使用我们刚才建立的city组件,修改views/movie.vue如下:

<template>
    <div>
        <header class="home-header border-bottom">
            <city></city>
            <div class="top-section">
                <div class="" @click="showCityList">
                    {{ $store.state.city.name }}
                    <i class="icon-arrow"></i>
                </div>
                <div>
                    正在热映
                </div>
                <div>
                    即将上映
                </div>
            </div>

        </header>
    </div>
</template>
<script>
import city from ‘../components/home/city.vue‘
import { mapMutations } from ‘vuex‘
export default{
    data(){
        return {

        }
    },
    components:{
        city
    },
    methods:{
        ...mapMutations([
        ‘showCityList‘
      ])
    }
}
</script>
<style>
    .top-section{
        display: flex;
        justify-content: space-around;
    }
    .icon-arrow{
        height: 12px;
        display: inline-block;
    }
    .icon-arrow:after{
        content: "";
    display: block;
    width: 6px;
    height: 6px;
    border: 1px solid #50505a;
    border-top: 0 none;
    border-left: 0 none;
    margin-left: 2px;
    transform: rotate(45deg);
    }
</style>

所有文件修改完成之后重新启动项目,不出意外的话应该就会完成我们开头的效果了。

这里面具体的city.vue的实现可能有些地方看不太清楚,没关系,再后面的章节后我们会一步步自己实现简单的类似的组件。

谢谢阅读,如果有问题欢迎在评论区一起讨论。

注:本文出自博客园 https://home.cnblogs.com/u/mdengcc/ ,转载请注明出处。

时间: 2024-11-05 00:35:53

Vue 项目实战系列 (三)的相关文章

从零开始Vue项目实战(三)-项目结构

现在在浏览器中输入http://localhost:8083,可以看到初始的"Welcome to Your Vue.js App"页面了 目录结构 ├── README.md 项目介绍 ├── index.html 入口页面 ├── build 构建脚本目录 │ ├── build-server.js 运行本地构建服务器,可以访问构建后的页面 │ ├── build.js 生产环境构建脚本 │ ├── dev-client.js 开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷

Vue2+VueRouter2+webpack 构建项目实战(三):配置路由,运行页面

制作.vue模板文件 通过前面的两篇博文的学习,我们已经建立好了一个项目.问题是,我们还没有开始制作页面.下面,我们要来做页面了. 我们还是利用 http://cnodejs.org/api 这里公开的api来做项目.不过本章节不涉及调用接口等内容.这里,我们假设我们的项目是做俩页面,一个列表页面,一个内容页面.列表页面有分页等,内容页面展示. 因此,我们需要两个模板文件. 我们在src/page目录下面新建两个文件,分别是index.vue和content.vue index.vue代码: <

(转载)Android项目实战(三十二):圆角对话框Dialog

Android项目实战(三十二):圆角对话框Dialog 前言: 项目中多处用到对话框,用系统对话框太难看,就自己写一个自定义对话框. 对话框包括:1.圆角 2.app图标 , 提示文本,关闭对话框的"确定"按钮 难点:1.对话框边框圆角显示 2.考虑到提示文本字数不确定,在不影响美观的情况下,需要在一行内显示提示的文字信息   3.设置对话框的宽和高 技术储备: 1.安卓开发_使用AlertDialog实现对话框    知道AlertDialog有setView(view) ,Dia

Android项目实战(三):实现第一次进入软件的引导页

原文:Android项目实战(三):实现第一次进入软件的引导页 最近做的APP接近尾声了,就是些优化工作了, 我们都知道现在的APP都会有引导页,就是安装之后第一次打开才显示的引导页面(介绍这个软件的几张可以切换的图) 自己做了一下,结合之前学过的 慕课网_ViewPager切换动画(3.0版本以上有效果) 思路很简单,APP的主界面还是作为主Activity,只要新添加一个类来判断是不是第一次打开APP 设主activity 名字为:MainActivity.java   判断是不是第一次打开

Android项目实战(三十四):蓝牙4.0 BLE 多设备连接

原文:Android项目实战(三十四):蓝牙4.0 BLE 多设备连接 最近项目有个需求,手机设备连接多个蓝牙4.0 设备 并获取这些设备的数据. 查询了很多资料终于实现,现进行总结. -------------------------------------------------------------------------------------------------------------------------------------------------------------

Android项目实战(三十七):Activity管理及BaseActivity的实现

原文:Android项目实战(三十七):Activity管理及BaseActivity的实现 Ps:7-10月 完成公司两个app项目上架.漏掉的总结 开始慢慢补上. 一.写一个Activity的管理类 1.单例模式,以栈(先进后出)的形式存储Activity对象 public class AppManager { private static Stack<Activity> activityStack; // Activity栈 , 先进后出 private static AppManage

Android项目实战(三十二):圆角对话框Dialog

原文:Android项目实战(三十二):圆角对话框Dialog 前言: 项目中多处用到对话框,用系统对话框太难看,就自己写一个自定义对话框. 对话框包括:1.圆角 2.app图标 , 提示文本,关闭对话框的"确定"按钮 难点:1.对话框边框圆角显示 2.考虑到提示文本字数不确定,在不影响美观的情况下,需要在一行内显示提示的文字信息   3.设置对话框的宽和高 技术储备: 1.安卓开发_使用AlertDialog实现对话框    知道AlertDialog有setView(view) ,

Android项目实战(三十一):异步下载apk文件并安装(非静默安装)

原文:Android项目实战(三十一):异步下载apk文件并安装(非静默安装) 前言: 实现异步下载apk文件 并 安装.(进度条对话框显示下载进度的展现方式) 涉及技术点: 1.ProgressDialog   进度条对话框  用于显示下载进度 2.AsyncTask         异步任务的使用    耗时操作不能再主线程中进行      安卓开发_浅谈AsyncTask 3.File                   文件相关操作    将文件的字节数据生成文件 4.自动打开安装应用操

Android项目实战(三十六):给背景加上阴影效果

原文:Android项目实战(三十六):给背景加上阴影效果 圆角背景大家应该经常用: 一个drawable资源文件  里面控制corner圆角 和solid填充色 <shape xmlns:android="http://schemas.android.com/apk/res/android"> <corners android:radius="@dimen/dp_2"></corners> <solid android:c