用 Vue 写个移动 SPA 应用

栏目: JavaScript · 发布时间: 5年前

内容简介:最近看了 Vue 的文档,想着应该写点什么加深下印象,能力有限,就照着葫芦画下吧:joy:,这次的葫芦是图灵社区 移动端页面Github:预览:

最近看了 Vue 的文档,想着应该写点什么加深下印象,能力有限,就照着葫芦画下吧:joy:,这次的葫芦是图灵社区 移动端页面

Github: github.com/Jimzjy/itur…

预览: jimzjy.github.io/ituring-mob…

前端新手,CSS / TS / JS 写的很烂,望见谅

用 Vue 写个移动 SPA 应用

准备工作

使用 vue-cli 创建项目,我的配置是

vue-router
vuex
dart-sass
babel
typescript
eslint
复制代码

删除自动创建的 HelloWorld,About 等组件、页面以及路由

页面结构

剖析页面

我们看下要做的部分的大体导航结构

用 Vue 写个移动 SPA 应用

概括为(上面的图少 more-books 的页面)

- pages
    - [pages-content]
        - header-bar
        - home / book / article
    - [user-info]
    - bottom-navbar
- login
- more-books
复制代码

创建基本结构组件

新建以下文件

views/
    Pages.vue
    PgaesContent.vue
    Login.vue
    MoreBooks.vue
    NotFound.vue
    pagesContents/
        Home.vue
        Book.vue
        Article.vue
        User.vue    // 对应上文结构中的 user-info, 按照结构其实应该直接放在 views/ 目录下
components/
    HeaderMenu.vue
    HeaderNav.vue   // 两个 Header 组件一起对应 header-bar
    BottomNavbar.vue
复制代码

添加 route

routePageNames = ['home', 'book', 'article', 'user']

{
  path: '/',
  component: Pages,
  children: [
    {
      path: '',
      component: PagesContent,
      children: [
        {
          path: '',
          component: Home,
          name: routePageNames[0]
        },
        {
          path: 'book',
          component: Book,
          name: routePageNames[1],
        },
        {
          path: 'article',
          component: Article,
          name: routePageNames[2],
        }
      ]
    },
    {
      path: 'user',
      component: User,
      name: routePageNames[3],
    }
  ]
},
{
  path: '/login',
  component: Login,
  name: 'login',
},
{
  path: '/more-books',
  component: MoreBooks,
  name: 'more-books',
}
{
  path: '*',
  component: NotFound,
  name: 'not-found'
}
复制代码

OK,现在文件结构已经满足了上面结构,接下来开始填充结构内容

开始填充架构

准备工作

MockJS 来模拟数据

npm install mockjs
npm install @types/mockjs -D
复制代码

新建文件

src/
    mock/
        index.ts    // mock 数据
    service/
        index.ts    // 获取数据的 Url
复制代码

头部

头部由两个组件组成 HeaderMenu 和 HeaderNav, 代码可以在最上面的链接中找到

HeaderMenu

用 Vue 写个移动 SPA 应用

HeaderNav有两种样子

在 Home 页面的

用 Vue 写个移动 SPA 应用

在 Book 和 Article 页面的

用 Vue 写个移动 SPA 应用
我们使用 BottomNavbar 来切换页面,但是 HeaderNav 和 BottomNavbar 既不是父子也不是兄弟,那么我们不能直接传参,那么我们可以通过 Vuex 设置一个判断 Home 和 其它 两种状态的 state,但是这样需要在 route 上设置守卫或者在 Home 和 Article 组件上操作,或者还可以通过监听 $route

,让 HeaderNav 自己去处理,这里我选择后者

service/index.ts
const topicsUrl = api + '/topics'

mock/index.ts
Mock.mock(`${topicsUrl}/${routePageNames[0]}`, shuffle(topicsHome).concat('最新上线', '每周特价'))
Mock.mock(`${topicsUrl}/${routePageNames[1]}`, topicsBook)
Mock.mock(`${topicsUrl}/${routePageNames[2]}`, topicsArticle)
// 内容都可以在源码中找到,篇幅有限就只列重点了,下同

HeaderNav.vue

@Watch('$route')
updateTopics () {
    this.isHome = this.$route.name === routePageNames[0] // 通过 isHome 来判断是否是 Home,routePageNames 是之前在 route 里写的
    this.$http.get(`${topicsUrl}/${this.$route.name}`).then((resposne: any) => {
        this.topicsData = resposne.data
    })
}

<div v-if="isHome">...</div>
<div v-if="!isHome">...</div>
复制代码

在 PagesContent.vue 添加 HeaderNav 和 HeaderMenu

<header>
  <header-menu></header-menu>
  <header-nav></header-nav>
</header>
<router-view/>  // Home / Book / Article
<div id="bottom-space"></div>   // 为 BottomNavbar 留底部空间
复制代码

底部

BottomNavbar 用到了 vue-awesome 这个 icon 库

npm install vue-awesome
复制代码

在这个项目中我用的都是自定义icon,可以在 src/asstes/icon/customIcons.ts 中找到

通过 fill 和 .router-link-active 一起配合,可以很轻松的实现根据不同路由改变 icon 的颜色

.fa-icon {
    ...

    fill: #C8CDD4;
}

.navbar-tab {
    ...

    &.router-link-active {
        color: $primary-color;

        .fa-icon {
            fill: $primary-color;
        }
    }
}
复制代码

在 Pages.vue 中添加 BottomNavbar

<router-view/>  // PagesContent
<bottom-navbar></bottom-navbar>
复制代码

用户和登录

登录状态可以用 Vuex 来设置一个 loginStatus(true为登录)

state: {
  loginStatus: false
},
mutations: {
  login (state) {
    state.loginStatus = true
  },
  logout (state) {
    state.loginStatus = false
  }
},
复制代码

当用户登录状态为 false 时,不允许用户进入 /user ,所以我们在 /user 上添加一个路由守卫

{
  path: 'user',
  component: User,
  name: routePageNames[3],
  + beforeEnter: authGuard
}

function authGuard (to: Route, from: Route, next: Function) {
  if ($store.state.loginStatus) {
    next()
    return
  }

  next({ name: 'login', query: { to: to.name } })
}
复制代码

在上面的代码中给到 login 的路由加了 query: { to: to.name } } ,之后我们在 Login 组件中就可以获取到本来用户想要去的路由,在登录后就可以跳转到用户原来想要取得路由,我们可以通过 $route.query 获取到信息,或者可以使用

route.ts
{
  path: '/login',
  component: Login,
  name: 'login',
  + props: (route) => ({ to: route.query.to })
}

Login.vue
@Prop({ default: 'home' }) readonly to!: string
复制代码

在路由中添加 props 将 query.to 变成添加给 Login 的属性

在 Login 中添加 login 方法,在 User 中添加 logout 方法

Login.vue
onLoginClick () {
    this.$store.commit('login')
    this.$router.push({ name: this.to })
}

User.vue
onLogoutClick () {
    this.$store.commit('logout')
    this.$router.push('/')
}
复制代码

开始填充内部

开始填充 PagesContent 内部,MoreBooks 中的组件会复用内部用过的,所以会和内部一起说明

Home

Swiper

Home 中的第一个组件是一个 Swiper,使用 vue-swipe

npm install vue-swipe
复制代码

再新建 components/Swiper.vue 再封装下 vue-swipe

Swiper.vue

<swipe :showIndicators=false :speed=3000>
  <swipe-item v-for="(item, index) in data" :key="index" class="swipe-item">
    <p class="title">{{ item.title }}</p>
    <p class="content">{{ item.content }}</p>
  </swipe-item>
</swipe>

@Prop() readonly data!: Array<InfoSwipe>
复制代码

在 Home 中通过 Mock 获取数据,传给 Swiper

created () {
    this.updateData()
}

updateData () {
    this.$http.get(homeDataUrl).then((resposne: any) => {
        this.data = resposne.data
    })
}

<swiper :data="data.infoSwipe"></swiper>
复制代码

SpecialView

在 Home 中有两种组件有着同样的外组件

书的列表

用 Vue 写个移动 SPA 应用
文章的列表
用 Vue 写个移动 SPA 应用

新建 components/SpecialView.vue

SpecialView.vue
<div class="title">
  <div class="title-content">
    ...
  </div>
  <router-link class="more" v-if="more" :to="{ name: 'more-books', query: { title } }">更多</router-link>
</div>
<slot></slot>

Home.vue
<special-view :title="item.title" :more="item.books.length > 4">
  ...
</special-view>
复制代码

这里我偷了下懒,更多按钮只去 /more-books

BookListView

书的列表也有两种样子

横向滚动

用 Vue 写个移动 SPA 应用
wrap
用 Vue 写个移动 SPA 应用

通过 wrap 属性改变 class 来改变样式

components/BookListView.vue
<div :class="['book-list-view', wrap ? 'wrap-list' : '']">
    ...
</div>

@Prop() readonly books!: Array<Book>
@Prop({ default: false }) readonly wrap!: boolean

Home.vue
<div v-for="(item, index) in data.booksWithTitle" :key="index" >
  <special-view :title="item.title" :more="item.books.length > 4">
    <book-list-view :books="item.books"></book-list-view>
  </special-view>
  <sepline></sepline>
</div>

MoreBooks.vue
<book-list-view :books="books" :wrap=true></book-list-view>
复制代码

Book

TopicTabView

在 Book 和 Article 中都有一个 tabview

用 Vue 写个移动 SPA 应用

点击 tab 时要更新 Book 的内容,所以在 TopicTabView 上可以加一个监听

components/TopicTabView.vue
@Emit()
onTopicClick (n: number) {
    this.topicChecked = n
    return n
}

Book.vue
<topic-tab-view @on-topic-click="onTopicChange"></topic-tab-view>

onTopicChange (n: number) {
    this.updateData()
}

updateData () {
    this.$http.get(moreBooksUrl).then((response: any) => {
        this.books = response.data
    })
}
复制代码

内容

Book 的内容可以直接复用 BookListView 的 wrap 模式

Article

ArticleListView

Article 和 Home 中都有文章的列表,样式相差并不多,可以像之前一样通过属性改变 class

用 Vue 写个移动 SPA 应用
用 Vue 写个移动 SPA 应用
components/ArticleListView.vue
<div v-if="showTag">
  ...
</div>
<div v-if="!showTag">
  ...
</div>

Article.vue
<article-list-view :articles="articles" class="articles"></article-list-view>

Home.vue
<special-view title="推荐文章">
  <article-list-view :articles="data.articles" :showTag=false></article-list-view>
</special-view>
复制代码

内容更新

现在从样子的角度来说已经构建完了,但是还有一个问题,当在 Book 和 Article 中点击 HeaderNav 的选项不会有任何效果,因为 HeaderNav 和 Book 以及 Article 的 内容部分,既不是父子也不是兄弟,我们又不能直接传参或者监听了

那我们可以通过在 route 上加属性,HeaderNav 通过 route 上的属性切换,点击后改变 route 的属性刷新页面,嗯...,感觉消耗有点大

那我们还可以通过 Vuex

state: {
    + currentHeaderNav: 0
},
mutations: {
    + changeCurrentHeaderNav (state, n) {
      state.currentHeaderNav = n
    }
},
复制代码

我们添加了一个 currentHeaderNav 用于表示现在的 HeaderNav 序号

<div :class="[isCurrentNav(index) ? 'topic-content-foucused' : 'topic-content']" @click="onTopicClick(index)">{{ topic }}</div>

isCurrentNav (n: number): boolean {
    return this.currentHeaderNav === n
}

onTopicClick (n: number) {
    this.$store.commit('changeCurrentHeaderNav', n)
}
复制代码

在 Book 和 Article 中添加监听

created () {
    this.updateData()
    this.subscription = this.$store.subscribe(mutation => {
        if (mutation.type === 'changeCurrentHeaderNav') {
            this.updateData()
        }
    })
}

destroyed () {
    this.subscription()
}
复制代码

现在已经可以正常更新内容了,但是还有一个问题,在 Book 和 Article 中序号是共享的,所以我们要在进入 Book 和 Article 前初始化序号,不然的话序号就乱套了,添加路由守卫

{
  path: 'book',
  component: Book,
  name: routePageNames[1],
  + beforeEnter: refreshHeaderNav
},
{
  path: 'article',
  component: Article,
  name: routePageNames[2],
  + beforeEnter: refreshHeaderNav
}

function refreshHeaderNav (to: Route, from: Route, next: Function) {
  $store.commit('changeCurrentHeaderNav', 0)

  next()
}
复制代码

OK!现在就完成了


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构算法与应用

数据结构算法与应用

塞尼 / 机械工业出版社 / 1999-3 / 49.00元

数据结构、算法与应用—C++语言描述(英文版),ISBN:9787111070177,作者:(美)塞尼 著一起来看看 《数据结构算法与应用》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具