用 Vue 写个移动 SPA 应用

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

内容简介:最近看了 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!现在就完成了


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

查看所有标签

猜你喜欢:

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

Host Your Web Site In The Cloud

Host Your Web Site In The Cloud

Jeff Barr / SitePoint / 2010-9-28 / USD 39.95

Host Your Web Site On The Cloud is the OFFICIAL step-by-step guide to this revolutionary approach to hosting and managing your websites and applications, authored by Amazon's very own Jeffrey Barr. "H......一起来看看 《Host Your Web Site In The Cloud》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具