Vue - 基于vant的APP框架(navbar+tabbar+router)

demo 地址: https://github.com/iotjin/jh-vue-demo的app1项目

demo 地址: https://github.com/iotjin/jh-vue-demo的app2项目(带登录、推荐这个)

本篇主要介绍如何使用vant的navbartabbarvue-router 创建一个vue版的APP基础框架(适配刘海屏)

app的主界面一般由三部分构成:navbar + 主体内容 +tabbar,如下图:

在这里插入图片描述

所以需要创建navbar组件和tabbar组件,然后通过router-view映射组件,连通单个页面

1、首先要有Vue项目

2、vant安装

3、navbar组件实现

导航条一般由左侧item、中间的标题、右侧item组成,当页面跳转后左侧一般是个返回按钮,点击返回上一页面。左右item变动较大,所以需要设置slot,另外主页面一般就4、5个,所以设置左侧默认为返回按钮

在这里插入图片描述

3.1、对外暴露的属性 props

  props: {
    // 是否显示返回按钮,默认为true,优先级低于slot
    isBack: { type: [Boolean, String], default: true },
    // 默认返回按钮颜色
    backIconColor: { type: String, default: "white" },
    // 标题
    title: { type: String, default: "" },
    // 固定在顶部时,是否在标签位置生成一个等高的占位元素
    isPlaceholder: { type: Boolean, default: false },
  },

3.2、template

 <template>
  <div class="navBar">
    <van-nav-bar
      :title="title"
      fixed
      safe-area-inset-top
      @click-left="onClickLeft"
      @click-right="onClickRight"
    >
      <template v-if="$slots.left" slot="left">
        <slot name="left"></slot>
      </template>
      <template v-else-if="isBack" slot="left">
        <van-icon name="arrow-left" size="18" :color="backIconColor" />
      </template>
      <template slot="right">
        <slot name="right"></slot>
      </template>
    </van-nav-bar>
    <div class="nav-top-placeholder" v-if="isPlaceholder"></div>
  </div>
</template>

3.3、背景色、标题颜色和顶部占位

<style>
.van-nav-bar {
  background: white;
  background: #38bc9d;
}

.van-nav-bar__title {
  color: black;
  color: white;
}

.nav-top-placeholder {
  padding-top: constant(safe-area-inset-top);
  padding-top: env(safe-area-inset-top);
  height: 44px;
}
</style>

3.4、关于刘海屏的适配

vant的navbar有个属性placeholder:固定在顶部时,是否在标签位置生成一个等高的占位元素,默认值为false。本篇没有使用默认的,但是如果不加占位,刘海屏会往上偏移,所以自定义了一个占位div

3.5、用法


<BaseNavBar :title="title" :isBack="isBack" :isPlaceholder="true">
  <van-icon name="cross" size="18" slot="left" />
  <van-icon name="circle" size="18" slot="right" />
</BaseNavBar>

import BaseNavBar from "../../components/BaseNavBar.vue";
export default {
  components: {
    BaseNavBar,
  },
  data() {
    return {
      title: "标题",
      isBack: false,
    };
  },
  methods: {},
  created() {},
};

或者在main.js全局引用navbar组件

import BaseNavBar from "./components/BaseNavBar.vue";

Vue.component('BaseNavBar',BaseNavBar)

4、tabbar组件实现

tabbar组件的每个item一般由文字默认图片选中图片小红点构成。另外要和页面绑定,需要一个路径参数。item切换也需要能在外部监听,

在主页进行页面跳转时,如果路由没有使用children,tabbar需要处理隐藏显示。
在tabbar组件内添加:v-if="$route.meta.isShowTabBar",在路由配置里添加:meta: { isShowTabBar: true }

另一种方法是通过children处理,不在本篇展开,可到demoapp2查看

在这里插入图片描述

代码实现

<template>
  <div v-if="$route.meta.isShowTabBar">
    <van-tabbar
      v-model="currentSelected"
      :inactive-color="color"
      :active-color="selectedColor"
      @change="onChange"
      route
      placeholder
      safe-area-inset-bottom
    >
      <template v-for="item in tabBars">
        <van-tabbar-item
          :key="item.name"
          :to="item.name"
          :badge="item.badge"
          :dot="item.isShowRedDot"
          replace
        >
          <span>{{ item.text }}</span>
          <template #icon="props">
            <img :src="props.active ? item.selectedIconPath : item.iconPath" />
          </template>
        </van-tabbar-item>
      </template>
    </van-tabbar>
  </div>
</template>

<script>
import { Tabbar, TabbarItem } from "vant";

export default {
  components: {
    [Tabbar.name]: Tabbar,
    [TabbarItem.name]: TabbarItem,
  },
  props: {
    // 选中tabbar
    selected: { type: Number, default: 0 },
    // 默认颜色
    color: { type: String, default: "#7d7e80" },
    // 选中颜色
    selectedColor: { type: String, default: "#38BC9D" },
    //item数组
    tabBars: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      //是否选中
      active: 0,
      //当前选中
      currentSelected: this.selected,
    };
  },
  methods: {
    onChange(index) {
      // console.log("内部-切换到tabbar:" + index);
      this.$emit("onChange", index); //往外传值
    },
  },
  // 初始化页面选中状态
  created() {
    if (this.$route.path === "/Main" && this.tabBars.length) {
      this.$router.push(this.tabBars[this.currentSelected].name);
    }
  },
  mounted() {},
};
</script>

<style></style>

5、vue-router实现

5.1、安装

npm install vue-router

5.2、router配置

import Vue from "vue";
import VueRouter from "vue-router";
import Root from "../views/root/index.vue";
import Main from "../views/root/main.vue";
import Module1 from "../views/module1/index.vue";
import Module2 from "../views/module2/index.vue";
import Module3 from "../views/module3/index.vue";
import Module4 from "../views/module4/index.vue";

import DemoList from "../views/module2/DemoList";


Vue.use(VueRouter);

const routes = [
  { path: "/", name: "Root", component: Root },
  { path: "/Main", name: "Main", component: Main },
  { path: "/Module1", name: "Module1", component: Module1, meta: { isShowTabBar: true } },
  { path: "/Module2", name: "Module2", component: Module2, meta: { isShowTabBar: true } },
  { path: "/Module3", name: "Module3", component: Module3, meta: { isShowTabBar: true } },
  { path: "/Module4", name: "Module4", component: Module4, meta: { isShowTabBar: true } },
  { path: "/Module2/DemoList", name: "DemoList", component: DemoList, },
];

const router = new VueRouter({
  routes,
});

export default router;

6、app主界面实现

app主界面由三部分构成:navbar + 主体内容 +tabbar
其中navbar 在主页面使用一次,如果单个页面需要自定义的话,在各个模块再使用一次
主体内容<router-view></router-view>
底部为tabbar,主页面跳转隐藏tabbar在路由配置里设置meta: { isShowTabBar: true }

项目结构


在这里插入图片描述

6.1、main代码实现(主页面)

<template>
  <div class="bg">
    <BaseNavBar :title="title" :isBack="isBack" :isPlaceholder="true">
    </BaseNavBar>
    <router-view></router-view>
    <BaseTabBar
      :selected="selected"
      :tabBars="tabBars"
      @onChange="onChange"
    ></BaseTabBar>
  </div>
</template>

<script>
import BaseNavBar from "../../components/BaseNavBar.vue";
import BaseTabBar from "../../components/BaseTabBar.vue";
export default {
  components: {
    BaseNavBar,
    BaseTabBar,
  },
  data() {
    return {
      title: "标题",
      isBack: false,
      selected: 1,
      tabBars: [
        {
          name: "/Module1",
          isShowRedDot: false,
          badge: "",
          text: "首页",
          iconPath: require("@assets/tab/tab1.png"),
          selectedIconPath: require("@assets/tab/tab1_select.png"),
        },
        {
          name: "/Module2",
          isShowRedDot: false,
          badge: "",
          text: "Demo",
          iconPath: require("@assets/tab/tab2.png"),
          selectedIconPath: require("@assets/tab/tab2_select.png"),
        },
        {
          name: "/Module3",
          isShowRedDot: true,
          badge: "",
          text: "我的",
          iconPath: require("@assets/tab/tab3.png"),
          selectedIconPath: require("@assets/tab/tab3_select.png"),
        },
      ],
    };
  },
  methods: {
    onChange(index) {
      console.log("外部-切换到tabbar:" + index);
      this.tabBars[index].isShowRedDot = this.tabBars[index].isShowRedDot
        ? false
        : false;
    },
  },
  created() {},
};
</script>

<style></style>


6.2、module2代码实现(单页面)

<template>
  <div class="bg2" v-bind:class="{ bg10: isActive }">
    <BaseNavBar :title="title" :isBack="false"> </BaseNavBar>
    模块2
    <div class="button" @click="onClick">按钮</div>
  </div>
</template>

<script>
export default {
  components: {},
  data() {
    return {
      title: "模块2",
      isActive: false,
    };
  },
  methods: {
    onClick() {
      console.log("点击按钮");
      this.$router.push({ name: "DemoList", params: { setid: 111222 } });
    },
    created() {
      console.log("模块2");
    },
  },
};
</script>

<style>
.bg2 {
  width: auto;
  height: 100vh;
  /* background: red; */
}
.button {
  width: 200px;
  height: 200px;
  background: palevioletred;
}
.bg10 {
  background: pink;
}
</style>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容