当前位置:   article > 正文

Vue项目实战篇二:实现一个完整的新闻WebApp客户端(带前端源码下载)_新闻资讯vue项目

新闻资讯vue项目

系列文章目录

Vue基础篇一:编写第一个Vue程序
Vue基础篇二:Vue组件的核心概念
Vue基础篇三:Vue的计算属性与侦听器
Vue基础篇四:Vue的生命周期(秒杀案例实战)
Vue基础篇五:Vue的指令
Vue基础篇六:Vue使用JSX进行动态渲染
Vue提高篇一:使用Vuex进行状态管理
Vue提高篇二:使用vue-router实现静态路由
Vue提高篇三:使用vue-router实现动态路由
Vue提高篇四:使用Element UI组件库
Vue提高篇五:使用Jest进行单元测试
Vue提高篇六: 使用Vetur+ESLint+Prettier插件提升开发效率
Vue实战篇一: 使用Vue搭建注册登录界面
Vue实战篇二: 实现邮件验证码发送
Vue实战篇三:实现用户注册
Vue实战篇四:创建多步骤表单
Vue实战篇五:实现文件上传
Vue实战篇六:表格渲染动态数据
Vue实战篇七:表单校验
Vue实战篇八:实现弹出对话框进行交互
Vue实战篇九:使用省市区级联选择插件
Vue实战篇十:响应式布局
Vue实战篇十一:父组件获取子组件数据的常规方法
Vue实战篇十二:多项选择器的实际运用
Vue实战篇十三:实战分页组件
Vue实战篇十四:前端excel组件实现数据导入
Vue实战篇十五:表格数据多选在实际项目中的技巧
Vue实战篇十六:导航菜单
Vue实战篇十七:用树型组件实现一个知识目录
Vue实战篇十八:搭建一个知识库框架
Vue实战篇十九:使用printjs打印表单
Vue实战篇二十:自定义表格合计
Vue实战篇二十一:实战Prop的双向绑定
Vue实战篇二十二:生成二维码
Vue实战篇二十三:卡片风格与列表风格的切换
Vue实战篇二十四:分页显示
Vue实战篇二十五:使用ECharts绘制疫情折线图
Vue实战篇二十六:创建动态仪表盘
Vue实战篇二十七:实现走马灯效果的商品轮播图
Vue实战篇二十八:实现一个手机版的购物车
Vue实战篇二十九:模拟一个简易留言板
Vue项目实战篇一:实现一个完整的留言板(带前后端源码下载)
Vue实战篇三十:实现一个简易版的头条新闻
Vue实战篇三十一:实现一个改进版的头条新闻
Vue实战篇三十二:实现新闻的无限加载
Vue实战篇三十三:实现新闻的浏览历史
ue实战篇三十四:给新闻WebApp加入模拟注册登录功能


一、背景

  • 这次我们将以项目实战的方式实现一个完整的新闻WebApp客户端,以下是该项目需要实现的功能:
    新闻频道:在主页面上显示新闻频道,用户可选择不同的频道。
    新闻列表:根据用户选择的频道加载对应的新闻,并以列表的方式显示新闻概要。
    新闻阅读:用户点击新闻列表中的项目, 显示具体的新闻明细。
    注册登录:模拟用户注册及登录
    浏览历史:存储用户每次阅读新闻的历史
    我的收藏:存储用户感兴趣的新闻(可模仿浏览历史的功能自行实现)
    在这里插入图片描述

  • 主要技术栈如下:

  1. vue-cli脚手架
  2. vue-router路由
  3. element组件库
  4. vscode编辑器
  5. vetur+eSLint+prettier插件

二、准备新闻数据接口

  • 极速数据网站上申请一个免费的API,并编写获取新闻频道获取新闻的API接口。

2.1 获取新闻频道

在这里插入图片描述

2.2 获取新闻

在这里插入图片描述

  • 注意:为了让前端直接可以访问以上的API接口,需要配置 vue.config.js 解决跨域问题
  // vue.config.js
  ...
  devServer: {
    port: port,
    open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    proxy: {
       // axios请求中带有/apis的url,就会触发代理机制
      '/apis': {
        target: 'http://api.jisuapi.com',
        secure: false,
        changeOrigin: true,
        pathRewrite: { '^/apis': '' }
      }
    }
  },
  ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.3 编写前端Api接口

  • 根据以上接口,在前端工程中定义api,供各页面组件调用。
    在这里插入图片描述
import axios from 'axios'

axios.defaults.baseURL = '/apis'

// 向极速数据免费新闻接口获取新闻频道
export function getNewChannel() {
  return new Promise((resolve, reject) => {
    axios.get('/news/channel?appkey=自己在极速数据上申请的appkey')
      .then(res => {
        resolve(res)
      }).catch(error => { reject(error) })
  })
}

// 向极速数据免费新闻接口获取新闻列表
export function getNewList(type) {
  return new Promise((resolve, reject) => {
    axios.get('/news/get?channel=' + type + '&start=0&num=30&appkey=自己在极速数据上申请的appkey')
      .then(res => {
        resolve(res)
      }).catch(error => { reject(error) })
  })
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

三、WebApp设计

3.1 新闻频道栏组件

  • 我们需要单独编写一个频道组件,主要包含以下这些功能:
    1、横向展示频道列表
    在这里插入图片描述
    2、可左右滑动
    在这里插入图片描述
    3、选择频道后,获取频道对应的新闻列表
    在这里插入图片描述

  • 以下是频道组件的完整代码
    在这里插入图片描述

<template>
  <div class="channel-box">
    <div class="channel-list">
      <ul ref="channelList" :style="{ left: -nowLeft + 'px' }">
        <li v-for="(item, index) in list" :key="index" :ref="'li' + index">
          <span class="channel" :class="{ 'channel-active': id === item.id }" @click="selectChannel(item)">
            {{ item.tag_name }}
          </span>
        </li>
      </ul>
    </div>
    <div class="icon-lf">
      <i v-show="showLf" class="el-icon-arrow-left" @click="handleLeft" />
    </div>
    <div class="icon-rt">
      <i v-show="showRt" class="el-icon-arrow-right" @click="handleRight" />
    </div>
  </div>
</template>

<script>
import { getNewChannel, getNewList } from '@/api/news'
export default {
  data() {
    return {
      list: [],
      id: 0,
      fixedWidth: 200,
      nowNum: 0,
      showLf: false,
      showRt: false,
      allWidth: 0,
      nowLeft: 0,
      nowIndex: 0 }
  },
  mounted() {
    this.getChannel().then(res => {
      console.log('channel', res)
      if (res && res.status === 200 && res.data.status === 0) {
        this.list = []
        res.data.result.forEach((element, index) => {
          this.list.push({ 'id': index, 'tag_name': element })
        })
        this.$nextTick(() => {
          this.allWidth = this.$refs.channelList.offsetWidth
          if (this.allWidth > this.fixedWidth) {
            this.showRt = true
          }
          this.selectChannel({ id: 0, tag_name: '头条' })
        })
      }
    })
  },
  methods: {
    // 异步获取频道
    async getChannel() {
      const data = await getNewChannel()
      return data
    },
    // 选择频道,根据频道获取新闻
    selectChannel(item) {
      this.$store.commit('SET_LOADING', true)
      this.$store.commit('SET_CHANNEL', item.tag_name)
      this.$store.commit('SET_START', 0)
      this.getNews(item.tag_name).then(res => {
        console.log('news', res)
        if (res) {
          scrollTo(0, 0)
          this.$store.commit('SET_NEWS', res.data.result.list)
          this.id = item.id
        }
        this.$store.commit('SET_LOADING', false)
      })
    },
    // 异步获取新闻
    async getNews(channel) {
      const data = await getNewList(channel, this.$store.state.news.start, this.$store.state.news.num)
      return data
    },
    // 频道列表左移
    handleLeft() {
      if (this.nowLeft > 0) {
        this.nowNum--
        this.showRt = true
        if (this.nowNum > 0) {
          let nw = 0
          for (let j = this.list.length; j >= 0; j--) {
            if (j < this.nowIndex) {
              nw += this.$refs['li' + j][0].offsetWidth
              if (nw >= this.fixedWidth) {
                nw -= this.$refs['li' + j][0].offsetWidth
                this.nowLeft -= nw
                this.nowIndex = j + 1
                break
              }
            }
          }
        } else {
          this.nowLeft = 0
          this.nowIndex = 0
          this.showLf = false
        }
      }
    },
    // 频道列表右移
    handleRight() {
      if (this.nowLeft + this.fixedWidth < this.allWidth) {
        this.nowNum++
        this.showLf = true
        let nw = 0
        for (let i = 0; i < this.list.length; i++) {
          if (i >= this.nowIndex) {
            nw += this.$refs['li' + i][0].offsetWidth
            if (nw > this.fixedWidth) {
              nw -= this.$refs['li' + i][0].offsetWidth
              this.nowLeft += nw
              this.nowIndex = i
              break
            }
          }
        }
        if (this.nowLeft + this.fixedWidth >= this.allWidth) {
          this.showRt = false
        }
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.channel-box {
  width: 100%;
  padding: 0 20px;
  height: 46px;
  position: fixed;
  align-items: center;
  top: 1.2rem;
  font-size: 18px;
  letter-spacing: 3px;
  background-color: rgb(252, 248, 248);
  .channel-list {
    width: 100%;
    height: 100%;
    overflow: hidden;
    position: relative;
    margin-top: 0.2rem;
    ul {
      transition-duration: 0.3s;
      position: absolute;
      top: 0px;
      left: 0px;
      margin: 0;
      padding: 0;
      display: flex;
      flex-wrap: nowrap;
      li {
        white-space: nowrap;
        display: inline-block;
        white-space: nowrap;
        padding: 0 10px;
      }
      li:first-child {
        padding-left: 0;
      }
      li:last-child {
        padding-right: 0;
      }
    }
    .channel {
      cursor: pointer;
      display: inline-block;
      height: 28px;
      line-height: 28px;
      transition: border-color 0.2s;
      &:hover {
        color: #e72521;
      }
    }
    .channel-active {
      color: #e72521;
    }
  }
  .icon-lf {
    cursor: pointer;
    line-height: 30px;
    position: absolute;
    left: 5px;
    top: 6px;
  }
  .icon-rt {
    line-height: 30px;
    cursor: pointer;
    position: absolute;
    right: 5px;
    top: 6px;
  }
}
</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
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 注意,我们需要将用户当前选择的频道、加载的新闻列表及加载状态放入公共状态管理器中存储,供其它组件调用。
    在这里插入图片描述
  • 以下是公共状态存储管理器的源码
    在这里插入图片描述
// 公共状态存储管理器
const news = {
  state: {
    // 用户选择的频道
    channel: '',
    // 起始位置
    start: 0,
    // 一次向接口接取新闻的条数
    num: 10,
    // 存放拉取下来的新闻
    newsData: [],
    // 当前正在查看的新闻
    newsIndex: -1,
    // 是否加载状态
    loading: false
  },

  mutations: {

    SET_CHANNEL: (state, channel) => {
      state.channel = channel
    },
    SET_START: (state, start) => {
      state.start = start
    },
    SET_NUM: (state, num) => {
      state.num = num
    },

    SET_NEWS: (state, news) => {
      state.newsData = news
    },

    SET_NEWS_INDEX: (state, newsIndex) => {
      state.newsIndex = newsIndex
    },
    SET_LOADING: (state, loading) => {
      state.loading = loading
    }

  },

  actions: {
    setChannel({ commit }, channel) {
      return new Promise(resolve => {
        commit('SET_CHANNEL', channel)
      })
    },

    setStart({ commit }, start) {
      return new Promise(resolve => {
        commit('SET_START', start)
      })
    },

    setNum({ commit }, num) {
      return new Promise(resolve => {
        commit('SET_NUM', num)
      })
    },

    setNews({ commit }, news) {
      return new Promise(resolve => {
        commit('SET_NEWS', news)
      })
    },
    setNewsIndex({ commit }, newsIndex) {
      return new Promise(resolve => {
        commit('SET_NEWS_INDEX', newsIndex)
      })
    },
    setLoading({ commit }, loading) {
      return new Promise(resolve => {
        commit('SET_LOADING', loading)
      })
    }
  }
}

export default news

  • 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

3.2 底部导航条组件

  • 在制作主页面组件前,我们需要先实现底部导航条;
  • 底部导航条中包含首页我的两个菜单。

3.2.1 封装底层的TabBarItem组件

– 设置标题、激活/未激活的图片插槽;设置路由地址传值参数;设置click点击行为,根据路由地址跳转。

在这里插入图片描述
在这里插入图片描述

– TabBarItem组件实现代码

<template>
  <div class="tab-bar-item" @click="itemClick">
    <div v-if="!isActive">
      <slot name="item-icon" />
    </div>
    <div v-else>
      <slot name="item-icon-active" />
    </div>
    <div><slot name="item-text" /></div>
  </div>
</template>

<script>
export default {
  name: 'TabBarItem',
  props: {
    path: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
    }
  },
  computed: {
    // 判断当前条目是否被选中
    isActive() {
      return !this.$route.path.indexOf(this.path)
    }
  },
  methods: {
    // 跳转路由
    itemClick() {
      this.$router.replace(this.path)
    }
  }

}
</script>

<style>
.tab-bar-item {
  display: flex;
  flex: 1;
  justify-content: center;
  text-align: center;
  height: 49px;
  line-height: 49px;
  cursor: pointer;
}
</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

3.2.2 实现底部导航条TabBar

– 通过css设置将导航条固定在底部
– 调用TabBarItem进行组合

<template>

  <div id="tab-bar">
    <tab-bar-item path="/home">
      <i slot="item-icon-active" class="el-icon-message-solid" />
      <i slot="item-icon" class="el-icon-bell" />
      <div slot="item-text">首页</div>
    </tab-bar-item>

    <tab-bar-item path="/my">
      <i slot="item-icon-active" class="el-icon-user-solid" />
      <i slot="item-icon" class="el-icon-user" />
      <div slot="item-text">我的</div>
    </tab-bar-item>

  </div>

</template>

<script>
import TabBarItem from '@/components/TabBar/tabBarItem'
export default {
  name: 'TabBar',
  components: { TabBarItem },
  data() {
    return {

    }
  },
  methods: {

  }
}

</script>

<style>
#tab-bar {
  display: flex;
  background-color: #f6f6f6;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  box-shadow: 0 -1px 1px rgba(100, 100, 100, 0.08);
}
</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

3.2.3 在主组件App.vue中加入底部导航条

<template>
  <div id="app">
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive" />
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive" />
    <tab-bar />
  </div>
</template>

<script>
import TabBar from '@/components/TabBar/tabBar'
export default {
  name: 'App',
  components: { TabBar }
}
</script>

<style>
img {
  width: 100%;
}
html, body {
  overflow: hidden;
  height:calc(100vh - 49px);
}

</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

3.3 设计新闻列表页面

3.3.1 页面组成

  • 页面由标题栏新闻频道栏组件列表页组成。

在这里插入图片描述

  • 当用户滚动新闻列表到达底部时,需要自动加载下一页新闻。
    在这里插入图片描述

3.3.2 引入无限滚动组件

  • 为了实现列表滚动到底部时,自动加载下一页新闻,我们需要引入element-uiInfiniteScroll组件,该组件可以判断容器的垂直滚动条滚动至底部时,自动执行加载方法。

在这里插入图片描述

  • 基础代码
<template>
  <ul class="infinite-list" v-infinite-scroll="load" style="overflow:auto">
    <li v-for="i in count" class="infinite-list-item">{{ i }}</li>
  </ul>
</template>

<script>
  export default {
    data () {
      return {
        count: 0
      }
    },
    methods: {
      load () {
        this.count += 2
      }
    }
  }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

3.3.3 动态加载新闻事件

  • 在极数数据接口中,我们观察到请求参数中,有个start参数,可以通过传入偏移值offset,来获取下一页的新闻。
  • 即当前浏览的是第1页的新闻,当滚动条到达底部触发自动加载方法时,需要给start参数传值为2,即加载第2页的新闻,依次类推。

在这里插入图片描述

  • 编写加载方法
  methods: {
    load() {
      // 如果状态管理器中的新闻列表是空的,说明刚初始化数据,不需要判断是否到达底部
      if (this.newData.length === 0) {
        return
      }
      // 获取下一页新闻
      console.log('已到达底部,自动触发加载方法')
      let start = this.$store.state.news.start
      start++
      if (start < 400) {
        this.getNews(this.$store.state.news.channel, start, this.$store.state.news.num).then(res => {
          console.log('加载下一页新闻列表', res)
          if (res && res.data.result) {
            const newsData = this.$store.state.news.newsData
            newsData.push.apply(newsData, res.data.result.list)
            this.$store.commit('SET_NEWS', newsData)
            this.$store.commit('SET_START', start)
          }
        })
      }
    },
    // 异步获取新闻
    async getNews(channel, start, num) {
      const data = await getNewList(channel, start, num)
      return data
    },

  }
  • 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

在这里插入图片描述

3.3.4 新闻列表页面源码与效果演示

  • 完成了自动无限加载的关键一步,我们就可以来编写完整的页面了。
<template>
  <div>
    <!-- 标题栏 -->
    <div class="header">
      <span />
      <span>新闻</span>
      <span />
    </div>
    <channel />
    <div ref="container" class="nav-content">
      <!-- 在新闻列表中引入无限滚动加载功能 -->
      <div v-if="loading == false" ref="scroll" v-infinite-scroll="load" class="news-list">
        <div
          v-for="(item, index) in newData"
          :key="index"
          class="section"
          @click="toNews(index)"
        >
          <div class="news">
            <div class="news-left">
              <img :src="item.pic" alt="">
            </div>
            <div class="news-right">
              <div class="newsTitle">{{ item.title }}</div>
              <div class="newsMessage">
                <span>{{ item.time }}</span>
                <span>{{ item.src }}</span>
              </div>
            </div>
          </div>
        </div>
        <!-- 在底部放入加载条 -->
        <div class="loading-more">正在努力加载</div>
      </div>
      <el-main
        v-else
        v-loading="loading"
        class="load"
        element-loading-background="rgba(0,0,0,0)"
        element-loading-text="正在加载中"
      />
    </div>
  </div>
</template>

<script>
import Channel from './channel'
import { getNewList } from '@/api/news'
export default {
  name: 'Home',
  components: { Channel },
  beforeRouteLeave(to, from, next) {
    this.scroll = this.$refs.scroll.scrollTop
    next()
  },
  data() {
    return {
      scrollTop: 0
    }
  },
  computed: {
    newData() {
      return this.$store.state.news.newsData
    },
    loading() {
      return this.$store.state.news.loading
    }
  },
  activated() {
    this.$refs.scroll.scrollTop = this.scroll
  },
  methods: {
    load() {
      // 如果状态管理器中的新闻列表是空的,说明刚初始化数据,不需要判断是否到达底部
      if (this.newData.length === 0) {
        return
      }
      // 获取下一页新闻
      console.log('已到达底部,自动触发加载方法')
      let start = this.$store.state.news.start
      start++
      if (start < 400) {
        this.getNews(this.$store.state.news.channel, start, this.$store.state.news.num).then(res => {
          console.log('加载下一页新闻列表', res)
          if (res && res.data.result) {
            const newsData = this.$store.state.news.newsData
            newsData.push.apply(newsData, res.data.result.list)
            this.$store.commit('SET_NEWS', newsData)
            this.$store.commit('SET_START', start)
          }
        })
      }
    },
    // 异步获取新闻
    async getNews(channel, start, num) {
      const data = await getNewList(channel, start, num)
      return data
    },
    // 打开新闻阅读
    toNews(index) {
      // 存储浏览历史
      this.$store.commit('SET_HISTROY', this.newData[index])
      // 打开明细
      this.$store.commit('SET_NEWS_INDEX', index)
      this.$router.push('/news')
    }
  }

}
</script>

<style lang="scss"  scoped>
.header {
  width: 100%;
  height: 1.2rem;
  background-color: #d43d3d;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: #fff;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 3px;
  z-index: 99;
  position: fixed;
  top: 0;
  img {
    width: 0.67rem;
    height: 0.67rem;
    cursor: pointer;
  }
}

.nav-content {
  margin-top: 2.4rem;
}

.news-list {
  position: relative;
  height:calc(100vh - 2.4rem - 49px);
  overflow-y:auto;
  width: 100%;
}

.section {
  width: 100%;
  height: 2.5rem;
  border-bottom: 1px solid #ccc;
}

.news {
  height: 2.25rem;
  box-sizing: border-box;
  margin: 10px 10px;
  display: flex;
}
.news-left {
  height: 100%;
  width: 2.8rem;
  display: inline-block;
}
.news-left img {
  width: 100%;
  height: 100%;
}
.news-right {
  flex: 1;
  padding-left: 10px;
}
.newsTitle {
  width: 100%;
  height: 62%;
  color: #404040;
  font-size: 17px;
  overflow: hidden;
}
.newsMessage {
  width: 100%;
  height: 38%;
  display: flex;
  align-items: flex-end;
  color: #888;
  justify-content: space-between;
}
.load {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
 .loading-more {
  margin-top: 5px;
  width: 100%;
  height: 20px;
  text-align: center;
 }
</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
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197

在这里插入图片描述

3.4 新闻阅读组件

  • 用户在列表页面上点击新闻,展示新闻详情进行阅读
    在这里插入图片描述
    在这里插入图片描述
<template lang="html">
  <div>
    <div class="header">
      <img src="@/assets/images/back.png" @click="back">
      <span>{{ $store.state.news.channel }}新闻</span>
      <span />
    </div>
    <div class="content">
      <div ref="container" class="container">
        <div class="title">{{ newsData && newsData.title }}</div>
        <div class="message">
          <span>{{ newsData && newsData.time }}</span>
        </div>
        <img :src="newsData && newsData.pic">
        <div class="newsContent" v-html="newsData && newsData.content" />
      </div>
    </div>
  </div>
</template>

<script>
export default {
  computed: {
    newsData() {
      return this.$store.state.news.newsData[this.$store.state.news.newsIndex]
    }
  },
  methods: {
    back() {
      this.$router.back()
    }
  }
}
</script>

<style lang="css" scoped>
.header {
  width: 100%;
  height: 1.33rem;
  background-color: #d43d3d;
  color: #fff;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 3px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: fixed;
  top: 0;
}
.header img {
    width: 0.67rem;
    height: 0.67rem;
    cursor: pointer;
}
.content {
  position: relative;
  top: 1.33rem;
  height:calc(100vh - 1.33rem - 49px);
  overflow-y:auto;
}
.container {
  margin: 20px;
}
.title {
  font-size: 24px;
  font-weight: bold;
  text-align: center;
}
.message {
  text-align: center;
  margin: 20px 0;
  color: #888;
}
.message span:last-child {
  margin-left: 10px;
}
.container img {
  width: 100%;
  margin-bottom: 20px;
}
.newsContent {
  font-size: 18px;
  line-height: 30px;
}
</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

3.5 我的页面制作

  • 在我的页面中有一个是否登录状态,如果状态为已登录,则显示浏览历史、收藏等信息,如果状态为未登录,则显示登录页面。
  • 注意,本文仅模拟登录的功能,没有进行后台验证(后续将提供完整的前后端功能)

在这里插入图片描述

3.5.1 添加登录状态的状态管理器

  • 为了记录该登录状态,我们需要在状态管理器源码中,加入登录状态的管理
    在这里插入图片描述

const my = {
  state: {
	...
    // 是否已登录
    logined: false
  },

  mutations: {

    ...
    SET_LOGIN: (state, login) => {
      state.logined = login
    }

  },

  actions: {
    ...
    setLogin({ commit }, login) {
      return new Promise(resolve => {
        commit('SET_LOGIN', login)
      })
    }
  }
}

export default my

  • 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

3.5.2 编写登录表单

在这里插入图片描述

  • 以下是改造后完整源码
<template>
   <!-- 已登录状态显示我的信息 -->
  <div v-if="logined == true" class="content">
    <div class="header">
      <div class="user">
        <img class="avatar" src="@/assets/images/avatar.png">
        <p class="user-name">{{ loginForm.username }}</p>
        <img class="right" src="@/assets/images/right.png">
      </div>
      <div class="info">
        <div class="histroy" @click="toHistroy()">
          <span class="histroy-count">{{ histroryCount }}</span>
          <span class="histroy-text">{{ '浏览历史' }}</span>
        </div>
        <div class="fav">
          <span class="fav-count">{{ favCount }}</span>
          <span class="fav-text">{{ '我的收藏' }}</span>
        </div>
      </div>
    </div>
    <div class="logout">
      <el-button
        size="medium"
        type="danger"
        style="width: 90%"
        @click.native.prevent="logout"
      >
        <span>退 出 登 录</span>
      </el-button>
    </div>
  </div>
  <!-- 未登录状态显示登录页面 -->
  <div v-else>
    <el-form
      ref="loginForm"
      :model="loginForm"
      :rules="loginRules"
      label-position="left"
      label-width="0px"
      class="login-form"
    >
      <h2 class="title">欢迎使用</h2>
      <el-form-item prop="username">
        <el-input
          v-model="loginForm.username"
          type="text"
          auto-complete="off"
          placeholder="账号"
        >
          <svg-icon
            slot="prefix"
            icon-class="user"
            class="el-input__icon input-icon"
          />
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          v-model="loginForm.password"
          type="password"
          auto-complete="off"
          placeholder="密码"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon
            slot="prefix"
            icon-class="password"
            class="el-input__icon input-icon"
          />
        </el-input>
      </el-form-item>
      <el-form-item style="width: 100%">
        <el-button
          :loading="loading"
          size="medium"
          type="danger"
          style="width: 100%"
          @click.native.prevent="handleLogin"
        >
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
      </el-form-item>

      <p class="register">
        <!-- <span class="memo">请使用Chrome,Firefox,IE 10+  </span> -->
        还没有帐号?
        <a href="/register" type="primary">立即注册</a>
      </p>
    </el-form>
  </div>
</template>

<script>

export default {
  data() {
    return {
      loginForm: {
        username: '',
        password: ''
      },
      loginRules: {
        username: [
          { required: true, trigger: 'blur', message: '用户名不能为空' }
        ],
        password: [
          { required: true, trigger: 'blur', message: '密码不能为空' }
        ]
      },
      loading: false
    }
  },
  computed: {
    histroryCount() {
      return this.$store.state.my.histroy.length
    },
    favCount() {
      return this.$store.state.my.favourite.length
    },
    // 从状态管理器中获取登录状态
    logined() {
      return this.$store.state.my.logined
    }
  },
  methods: {
    // 模拟登录成功
    handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.loading = true
          this.$store.commit('SET_LOGIN', true)
          this.loading = false
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    // 模拟注销登录
    logout() {
      this.$store.commit('SET_LOGIN', false)
      this.loginForm.username = ''
      this.loginForm.password = ''
    },
    toHistroy() {
      if (this.histroryCount > 0) {
        this.$router.push('/list')
      }
    }
  }
}

</script>

<style lang="scss"  scoped>
.login-form {
  border-radius: 6px;
  background: #ffffff;
  width: 100%;
  padding: 25px 25px 5px 25px;
  margin-top: 25px;
  .el-input {
    height: 38px;
    input {
      height: 38px;
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
}

.title {
  margin: 0 auto 30px auto;
  text-align: center;
  color: #707070;
}

.register {
  float: right;
  font-size: 13px;
  // color: rgb(24, 144, 255);
}
a {
  color: #e72521;
  text-decoration: none;
  background-color: transparent;
  outline: none;
  cursor: pointer;
  transition: color 0.3s;
}
a:hover {
  color: #e72521;
}

.content {
  width: 100%;
  height: 100%;
  background-color: rgb(252, 248, 248);
}
.header {
  width: 100%;
  height: 5.33rem;
  background-color: #fff;
}

.user {
  margin-top: 0.5rem;
  overflow: hidden;
  padding: 0.5rem;
  height: 2.5rem;
  width: 100%;
}

.avatar {
  float: left;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
}

.user-name {
  float: left;
  margin-top: 0.6rem;
  margin-left: 0.5rem;
  color: #404040;
  font-size: 18px;
}

.right {
  float: right;
  width: 0.8rem;
  height: 0.8rem;
  margin-top: 0.6rem;
}

.info {
  float: left;
  padding: 1rem;
  height: 2.5rem;
  width: 100%;
}
.histroy {
  display: flex;
  float: left;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.histroy-count {
  color: #404040;
  font-size: 18px;
}

.histroy-text {
  margin-top: 0.1rem;
  color: #9b9191;
  font-size: 14px;
}

.fav {
  display: flex;
  float: right;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.fav-count {
  color: #404040;
  font-size: 18px;
}

.fav-text {
  margin-top: 0.1rem;
  color: #9b9191;
  font-size: 14px;
}

.logout {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 2rem;

}
</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
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294

3.5.3 添加新的注册页面

1、用户在登录前,需要向系统进行注册。
2、注册时输入用户名,密码(密码需两次输入一致)
3、本文只实现模拟注册(后续将提供完整的前后端功能)
在这里插入图片描述

  • 完整的注册页面源码:
<template>
  <div>
    <el-form
      ref="signupForm"
      :model="signupForm"
      :rules="signupRules"
      label-position="left"
      label-width="0px"
      class="login-form"
    >
      <h2 class="title">欢迎注册</h2>
      <el-form-item prop="username">
        <el-input
          v-model="signupForm.username"
          type="text"
          auto-complete="off"
          placeholder="账号"
        >
          <svg-icon
            slot="prefix"
            icon-class="user"
            class="el-input__icon input-icon"
          />
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          v-model="signupForm.password"
          type="password"
          auto-complete="off"
          placeholder="密码"
        >
          <svg-icon
            slot="prefix"
            icon-class="password"
            class="el-input__icon input-icon"
          />
        </el-input>
      </el-form-item>
      <el-form-item prop="password2">
        <el-input
          v-model="signupForm.password2"
          type="password"
          auto-complete="off"
          placeholder="确认密码"
          @keyup.enter.native="handleSignup"
        >
          <svg-icon
            slot="prefix"
            icon-class="password"
            class="el-input__icon input-icon"
          />
        </el-input>
      </el-form-item>
      <el-form-item style="width: 100%">
        <el-button
          :loading="loading"
          size="medium"
          type="danger"
          style="width: 100%"
          @click.native.prevent="handleSignup"
        >
          <span v-if="!loading">注 册</span>
          <span v-else>注 册 中...</span>
        </el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>

export default {
  data() {
    // 二次密码输入校验
    var checkpass = (rule, value, callback) => {
      console.log(value)
      if (value === '') {
        callback(new Error('请再次输入密码'))
      } else if (value !== this.signupForm.password) {
        callback(new Error('两次输入密码不一致'))
      } else {
        callback()
      }
    }
    return {
      signupForm: {
        username: '',
        password: '',
        password2: ''
      },
      signupRules: {
        username: [
          { required: true, trigger: 'blur', message: '用户名不能为空' }
        ],
        password: [
          { required: true, trigger: 'blur', message: '密码不能为空' }
        ],
        password2: [
          { trigger: 'blur', validator: checkpass }
        ]

      },
      loading: false
    }
  },
  methods: {
    // 模拟注册成功
    handleSignup() {
      this.$refs.signupForm.validate((valid) => {
        if (valid) {
          this.loading = true
          this.$store.commit('SET_LOGIN', true)
          this.loading = false
          this.$router.push('/my')
        } else {
          console.log('error signup!!')
          return false
        }
      })
    }
  }
}

</script>

<style lang="scss"  scoped>
.login-form {
  border-radius: 6px;
  background: #ffffff;
  width: 100%;
  padding: 25px 25px 5px 25px;
  margin-top: 25px;
  .el-input {
    height: 38px;
    input {
      height: 38px;
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
}

.title {
  margin: 0 auto 30px auto;
  text-align: center;
  color: #707070;
}

</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
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155

3.5.4 注册登录效果演示

在这里插入图片描述

3.6 实现浏览历史功能

3.6.1 添加浏览历史的状态管理器

在这里插入图片描述

  • 代码

const my = {
  state: {
    // 存储浏览历史
    histroy: [],
    // 存储我的收藏
    favourite: []
  },

  mutations: {

    SET_HISTROY: (state, histroy) => {
      state.histroy.unshift({ createTime: new Date(), histroy: histroy })
    },
    SET_FAVOURITE: (state, favourite) => {
      state.favourite.unshift(favourite)
    }

  },

  actions: {
    setHistroy({ commit }, histroy) {
      return new Promise(resolve => {
        commit('SET_HISTROY', histroy)
      })
    },

    setFavourite({ commit }, favourite) {
      return new Promise(resolve => {
        commit('SET_FAVOURITE', favourite)
      })
    }
  }
}

export default my

  • 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

3.6.2 浏览内容存储

在这里插入图片描述

  • 代码改造
<template>
  <div>
    <!-- 标题栏 -->
    <div class="header">
      <span />
      <span>新闻</span>
      <span />
    </div>
    <channel />
    <div ref="container" class="nav-content">
      <!-- 在新闻列表中引入无限滚动加载功能 -->
      <div v-if="loading == false" ref="scroll" v-infinite-scroll="load" class="news-list">
        <div
          v-for="(item, index) in newData"
          :key="index"
          class="section"
          @click="toNews(index)"
        >
          <div class="news">
            <div class="news-left">
              <img :src="item.pic" alt="">
            </div>
            <div class="news-right">
              <div class="newsTitle">{{ item.title }}</div>
              <div class="newsMessage">
                <span>{{ item.time }}</span>
                <span>{{ item.src }}</span>
              </div>
            </div>
          </div>
        </div>

      </div>

    </div>
  </div>
</template>

<script>

export default {
  ...
  methods: {
	...
    // 打开新闻阅读
    toNews(index) {
      // 存储浏览历史
      this.$store.commit('SET_HISTROY', this.newData[index])
      // 打开明细
      this.$store.commit('SET_NEWS_INDEX', index)
      this.$router.push('/news')
    }
  }

}
</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
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

3.6.3 在我的页面中添加浏览历史

  • 我们在我的页面中添加浏览历史,直接调取存储管理器的计数,进行显示。
    在这里插入图片描述
<template>
  <div class="content">
    <div class="header">
      <div class="user">
        <img class="avatar" src="@/assets/images/avatar.png">
        <p class="user-name">{{ 'zhuhuix' }}</p>
        <img class="right" src="@/assets/images/right.png">
      </div>
      <div class="info">
        <div class="histroy" @click="toHistroy()">
          <span class="histroy-count">{{ histroryCount }}</span>
          <span class="histroy-text">{{ '浏览历史' }}</span>
        </div>
        <div class="fav">
          <span class="fav-count">{{ favCount }}</span>
          <span class="fav-text">{{ '我的收藏' }}</span>
        </div>
      </div>
    </div>

  </div>
</template>

<script>

export default {
  computed: {
    // 调取浏览历史存储管理器中的计数
    histroryCount() {
      return this.$store.state.my.histroy.length
    },
    favCount() {
      return this.$store.state.my.favourite.length
    }
  },
  methods: {
    toHistroy() {
      if (this.histroryCount > 0) {
        this.$router.push('/list')
      }
    }
  }
}

</script>

<style lang="css"  scoped>
.content {
  width: 100%;
  height: 100%;
  background-color: rgb(252, 248, 248);
}
.header {
  width: 100%;
  height: 5.33rem;
  background-color: #fff;
}

.user {
  margin-top: 0.5rem;
  overflow: hidden;
  padding: 0.5rem;
  height: 2.5rem;
  width: 100%;
}

.avatar {
  float: left;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
}

.user-name {
  float: left;
  margin-top: 0.6rem;
  margin-left: 0.5rem;
  color: #404040;
  font-size: 18px;
}

.right {
  float: right;
  width: 0.8rem;
  height: 0.8rem;
  margin-top: 0.6rem;
}

.info {
  float: left;
  padding: 1rem;
  height: 2.5rem;
  width: 100%;

}
.histroy {
  display: flex;
  float: left;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.histroy-count {
   color: #404040;
  font-size: 18px;

}

.histroy-text {
  margin-top: 0.1rem;
  color: #9b9191;
  font-size: 14px;

}

.fav {
  display: flex;
  float: right;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.fav-count {
   color: #404040;
  font-size: 18px;
}

.fav-text {
  margin-top: 0.1rem;
   color: #9b9191;
  font-size: 14px;
}

</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
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139

3.6.4 添加新的浏览历史页面

1、增加一个新的浏览历史页面,该页面类似于新闻列表页面。
2、该页面按浏览时间从新到旧显示列表内容。

在这里插入图片描述

  • 完整的页面源码:
<template>
  <div>
    <!-- 标题栏 -->
    <div class="header">
      <img src="@/assets/images/back.png" @click="back">
      <span>浏览历史</span>
      <span />
    </div>
    <div ref="container" class="nav-content">
      <!-- 在新闻列表中引入无限滚动加载功能 -->
      <div ref="scroll" v-infinite-scroll="load" class="news-list">
        <div
          v-for="(item, index) in data"
          :key="index"
          class="section"
          @click="toNews(index)"
        >
          <div class="news">
            <div class="news-left">
              <img :src="item.histroy.pic" alt="">
            </div>
            <div class="news-right">
              <div class="newsTitle">{{ item.histroy.title }}</div>
              <div class="newsMessage">
                <span>{{ item.histroy.time }}</span>
                <span>{{ item.histroy.src }}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'List',
  beforeRouteLeave(to, from, next) {
    this.scroll = this.$refs.scroll.scrollTop
    next()
  },
  data() {
    return {
      data: [],
      scrollTop: 0,
      currentPage: 0
    }
  },
  computed: {
    histroy() {
      return this.$store.state.my.histroy
    }
  },
  activated() {
    this.$refs.scroll.scrollTop = this.scroll
  },
  methods: {
    // 获取下一页历史
    load() {
      // 判断当前已加载多少页
      console.log('currentPage', this.currentPage)
      const totalPage = Math.floor(this.$store.state.my.histroy.length / this.$store.state.news.num)
      console.log('totalPage', totalPage)
      if (this.currentPage <= totalPage) {
        // 当前加载的起始位置
        const start = this.currentPage * this.$store.state.news.num
        // 结束位置
        let end = start + this.$store.state.news.num
        // 如果剩余未加载的条数小于固定加载条数,则取剩余条数
        const banlance = this.histroy.length - this.currentPage * this.$store.state.news.num
        if (banlance < this.$store.state.news.num) {
          end = start + banlance
        }
        console.log(start)
        console.log(end)
        for (let i = start; i < end; i++) { this.data.push(this.histroy[i]) }
        console.log(this.data)
        this.currentPage++
      }
    },
    // 打开新闻阅读
    toNews(index) {
      // 打开明细
      this.$store.commit('SET_NEWS_INDEX', index)
      this.$router.push('/news')
    },
    back() {
      this.$router.back()
    }
  }

}
</script>

<style lang="scss"  scoped>
.header {
  width: 100%;
  height: 1.33rem;
  background-color: #d43d3d;
  color: #fff;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 3px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: fixed;
  top: 0;
}
.header img {
    width: 0.67rem;
    height: 0.67rem;
    cursor: pointer;
}

.nav-content {
  margin-top: 1.4rem;
}

.news-list {
  position: relative;
  height:calc(100vh - 1.4rem - 49px);
  overflow-y:auto;
  width: 100%;
}

.section {
  width: 100%;
  height: 2.5rem;
  border-bottom: 1px solid #ccc;
}

.news {
  height: 2.25rem;
  box-sizing: border-box;
  margin: 10px 10px;
  display: flex;
}
.news-left {
  height: 100%;
  width: 2.8rem;
  display: inline-block;
}
.news-left img {
  width: 100%;
  height: 100%;
}
.news-right {
  flex: 1;
  padding-left: 10px;
}
.newsTitle {
  width: 100%;
  height: 62%;
  color: #404040;
  font-size: 17px;
  overflow: hidden;
}
.newsMessage {
  width: 100%;
  height: 38%;
  display: flex;
  align-items: flex-end;
  color: #888;
  justify-content: space-between;
}
.load {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
 .loading-more {
  margin-top: 5px;
  width: 100%;
  height: 20px;
  text-align: center;
 }
</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
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181

3.6.5 浏览历史效果演示

在这里插入图片描述

四、源码地址

  • 请关注文末的微信公众号,回复“新闻客户端”。

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/396311
推荐阅读
相关标签
  

闽ICP备14008679号