在学习仿美团项目时,对于vue结构、组件拆分布局,路由结构划分、提取公共的url接口上又有了新的收获,进行一些总结

一、技术选型

"axios": "^0.18.0",
"muse-ui": "^3.0.2",   //按需引用
"node-sass": "^4.11.0",
"vant": "^1.6.7",    //全局使用
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuex": "^3.1.0",
"less": "^3.9.0",
"less-loader": "^4.1.0",
1
2
3
4
5
6
7
8
9

项目地址

二、src的项目结构

|--src
|  |-- api //存放公共的url和封装axios获取数据方法
|  |-- components  //存放公共的组件
|  |-- pages  //存放单个组件的页面
|  |-- Museui.js  //按需引入museui的组件
|  |-- store.js   //进行数据管理
1
2
3
4
5
6

三、vant和muse-ui的引入

main.js

1、全局引入vant
import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);
1
2
3
2、按需引入muse-ui

import Vue from 'vue';
import 'muse-ui/lib/styles/base.less';
import {
  AppBar,
  Avatar,
  Badge,
  BottomNav,
  Button,
  TextField,
  theme
} from 'muse-ui';
import 'muse-ui/lib/styles/theme.less';

Vue.use(Icon);
Vue.use(Button);
Vue.use(AppBar);
Vue.use(BottomNav);
Vue.use(Paper);
Vue.use(TextField);
Vue.use(Badge);
Vue.use(Avatar);
Vue.use(theme);
//自定义主题
theme.add('teal', {
  primary: '#009688',
  secondary: '#ff4081',
  success: '#4caf50',
  warning: '#ffeb3b',
}, 'light');

theme.use('teal');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

四、tabbar组件实现及路由跳转实现

  1. 底部导航菜单栏使用muse-ui的tabbar组件,菜单页面路由通过Main.vue在底部菜单之上实现。
<template>
  <div>
    <router-view/>
    <mu-paper class="g-footer">
      <mu-bottom-nav :value="bottomNav">
        <mu-bottom-nav-item to="/home" value="/home" title="首页" icon="home"></mu-bottom-nav-item>
        <mu-bottom-nav-item to="/recent" value="/recent" title="附近" icon="pin_drop"></mu-bottom-nav-item>
        <mu-bottom-nav-item to="/guang" value="/guang" title="逛一逛" icon="language"></mu-bottom-nav-item>
        <mu-bottom-nav-item to="/order" value="/order" title="订单" icon="assignment"></mu-bottom-nav-item>
        <mu-bottom-nav-item to="/me" value="/me" title="我的" icon="account_circle"></mu-bottom-nav-item>
      </mu-bottom-nav>
    </mu-paper>
  </div>
</template>

<script>
export default {
  data() {
    return {
      bottomNav: "/home"
    };
  },
  methods: {},
  mounted() {
    //当每次重新挂载(重新刷新)时,重新匹配路由,使导航和路由路径相匹配
    // console.log(this.$route.path);
    let path = this.$route.path;
    if(path=="/")path="/home";
    this.bottomNav = path;
  }
};
</script>
<style lang="less" >
//TODO:由于museui字体图标无法显示,直接引入cdn库
@import "http://cdn.bootcss.com/material-design-icons/3.0.1/iconfont/material-icons.css";

.g-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
}
.mu-bottom-item {
  min-width: 0.62rem;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  1. 页面路由结构
routes: [
    {
      path: '/',
      component: Main,
      children: [  //TODO: 设置子路由
        {
          path: '',  //设置空路由,父级匹配一个子路由
          name: 'main',
          component: Home
        },
        {
          path: '/home',
          name: 'home',
          component: Home
        },
        {
          path: '/recent',
          name: 'recent',
          component: Recent
        },
        {
          path: '/guang',
          name: 'guang',
          component: Guang
        },
        {
          path: '/order',
          name: 'order',
          component: Order
        },
        {
          path: '/me',
          name: 'me',
          component: Me
        }
      ]
    },
    {
      path: '/meishi',  //单页面路由,不匹配在tabbar上
      name: 'meishi',
      component: MeShi
    }
  ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  • 由于页面刷新时,底部菜单索引自动匹配第一个路由页面,导致页面与底部菜单活跃状态不符,所以页面重新挂载时,获取此时路由的值,重新给底部菜单活跃项赋值。

五、页面拆分成组件

  • 所有的页面中一部分都拆分成单个组件去进行渲染
  • 示例:

六、api接口的封装

  1. --api--config.js,存放公共的url地址
var url = " https://www.easy-mock.com/mock/5c7d08e7b7a08d1246bef9ff/api/"
export {url};
1
2
  1. ---api--shop.js,存放获取商品列表的axios方法
import axios from 'axios'
import { url } from './config'
let getShopList = (page) => {
  return axios({
    method: "post",
    url: url + "shopList",
    data: {
      page: page,
      pageSize: 10
    }
  })
}
export {
  getShopList
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. api接口的使用--pages--home-HomeShop.vue
<template>
  <div>
    <van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
      <van-card v-for="(item,index) in list" :key="index">
        <div slot="title">
          <strong>{{item.desc}}</strong>
          <div class="distance">< 500m</div>
        </div>
        <div slot="desc">{{item.pos}}{{item.id}}</div>
        <div slot="price">
          <span>¥{{item.price}}</span>
          <van-tag size="medium" type="danger">劲爆</van-tag>
          <div class="workoff">已售{{item.sales}}</div>
        </div>
        <div slot="thumb">
          <img :src="'/static/shoplog/'+ item.imgurl" alt>
        </div>
      </van-card>
    </van-list>
  </div>
</template>
<script>
import { getShopList } from "@/api/shop";  //引入获取商品列表分页数据的方法
export default {
  data() {
    return {
       list: [],
      loading: false,
      finished: false
       page: 0
    };
  },
  mounted() {},
  methods: {
    onLoad() {
      this.page++

      // 数据全部加载完成
      setTimeout(() => {
        getShopList(this.page)
          .then(res => {
            if (res.data.success) {
              //遍历获取的数据添加到商品列表数组中进行渲染
               res.data.shop.forEach(item => {
                 this.list.push(item);
               });
              
              // 加载状态结束
              this.loading = false;
            }
          })
          .catch(function(err) {
            alert("接口错误:" + err);
          });
      }, 2000);
      //如果获取数据长度超过了70,则不再获取数据
      if (this.list.length >= 70) {
        this.finished = true;
      }
    }
  }
};
</script>

<style lang="less" scoped>
.van-list {
  //深度作用选择器: 如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符,使用预编译器则使用 "/deep/ "
  /deep/ .van-card {   
    background: #fff;
  }
}
.van-card__price {
  margin-top: 5px;
  width: 100%;
}
.van-card__content {
  strong {
    font-size: 14px;
  }
}
.distance {
  float: right;
  width: 0.6rem;
  text-align: right;
  font-weight: 500;
  color: #000;
}
.workoff {
  float: right;
  width: 0.6rem;
  text-align: right;
  font-weight: 500;
  color: #000;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

七、使用vuex管理数据

  • 创建store.js对商品列表的分页数据进行管理
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

var state = {
  shop: {
    num: 1,
    list: [],
    page: 0
  }
}

const mutations = {
  setshoplist(state, data) {  //设置商品列表数据
    state.shop.list = state.shop.list.concat(data)  //拼接数组
  },
  nextpage(state){  //下一页
    state.shop.page++;
  }
}

export default new Vuex.Store({
  state,
  mutations
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • 在HomeShop.vue页面使用vuex接收数据
<script> 
import { mapState } from "vuex";  //引入vuex数据mapState辅助函数
import { getShopList } from "@/api/shop";  //引入获取商品列表分页数据的方法

export default {
  data() {
    return {
      // list: [],
      loading: false,
      finished: false
      // page: 0
    };
  },
  computed: {
    ...mapState({
      list: state => state.shop.list,  //获取vuex中的list数据
      page: state => state.shop.page   //获取vuex中的分页数据
    })
  },
  mounted() {},
  methods: {
    onLoad() {
      //每次加载刷新,页数加1
      this.$store.commit("nextpage");  //提交页数突变的方法

      // 数据全部加载完成
      setTimeout(() => {
        getShopList(this.page)
          .then(res => {
            if (res.data.success) {
              this.$store.commit("setshoplist", res.data.shop);  //提交商品数据突变方法
              // 加载状态结束
              this.loading = false;
            }
          })
          .catch(function(err) {
            alert("接口错误:" + err);
          });
      }, 2000);
      //如果获取数据长度超过了70,则不再获取数据
      if (this.list.length >= 70) {
        this.finished = true;
      }
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

八、制作svg图标,引入自定义图标

  1. 使用Ai制作svg图标,保存为svg格式
  2. 进入制作字体图标官网
  3. 导入svg图标 导入svg图标
  4. 也可以选择观望已有的图标,选择之后下方有显示,
  5. 点击下方Generate Font F按钮对选择的图标进行设置,然后下载 点击preferences进行个性化设置
  6. 得到解压包以后,把解压的文件放在assets文件夹中,此文件夹会被打包编译,
  7. 解决样式冲突问题:
  • 由于项目引入了其他ui框架,本身有字体图标,会与自定义图标冲突,需要解决
  • 编写--style.scss文件
//TODO: 解决字体图标库与框架的字体图标库的冲突问题
//以"icon-mt-"打头,包含" icon-mt-"的,css3选择器
i[class^="icon-mt-"],i[class*=" icon-mt-"] {
  font-family: '#{$icomoon-font-family}' !important;
  speak: none;
  font-style: normal;
  font-weight: normal;
  font-variant: normal;
  text-transform: none;
  line-height: 1;

  /* Better Font Rendering =========== */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 如果想添加样式直接在--style.scss文件添加
.icon-mt-hezuo {
  &:before {
    content: $icon-mt-hezuo;     
    color: $icon-mt-color;
  }
}
//默认没有状态样式,自定义添加
.icon-mt-hezuo:active {
  &:before {
    content: $icon-mt-hezuo;     
    color: red;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13