app.vue页面

初始化数据,通过调用vuex mutation里定义的方法 调用保存到localstorage中的书架信息、搜索历史记录、字体大小和皮肤

并把这些数据保存的vuex state中

1.书架页 Myshelf

url:http://localhost:8080/myshelf

myshelf页面有 header、shelf-list、tabbar组件

重点:

1.从vuex state中获取已经保存的书架信息

2.处理时间,返回距离现在几小时的格式

<span class="time red">{{ book.updated | time}}</span>

import moment from 'moment';

    filters: {
time(updated) {
moment.locale('zh-cn');
return moment(updated).fromNow();
}
},

3.删除书架书籍提示是否确认删除

调用mint-ui的messagebox,MessageBox.confirm() 返回的是一个promise对象,点击确认可被then捕获,点击取消可被catch捕获

import { MessageBox } from 'mint-ui';

    showDialog(book_id){
MessageBox.confirm('确定要从书架中删除?', '温馨提示').then(action => { 
 if (action == 'confirm') {     //确认的回调
  console.log(1); 
this.deleteFromShelft(book_id)
 }
 }).catch(err => { 
  if (err == 'cancel') {     //取消的回调
  console.log('取消');
  } 
})
}

调用写在mutation中的deleteFromShelft,通过filter筛选出对象,再重新保存到localstrage覆盖之前的保存信息

  // 删除特定书从书架
deleteFromShelft(state,book_id){
state.shelf_book_list = state.shelf_book_list.filter(value => {
return !book_id.includes(value.id);
});
// 重现覆盖localstorage
setStore('SHEFL_BOOK_LIST', state.shelf_book_list);
},
// 删除全部书从书架
deleteAllShelft(state){
state.shelf_book_list.clear();
removeStore('SHEFL_BOOK_LIST')
},

补充:数组includes用法

语法:arr.includes(searchElement)
   arr.includes(searchElement, fromIndex)

includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。

2.主页面home

吸顶效果

    <div class="tabs-warp" :class="searchBarFixed == true ? 'isFixed' :''">
<div ref="tabsContent" class="tabs-content">
<!-- 导航 -->
</div>
<div v-if="searchBarFixed" class="replace-tab"></div>

定义函数 当滑动距离超过tab栏到顶部距离的时候,应用isFixed样式。

    handleScroll () {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
if(this.offsetTop==0){
this.offsetTop=document.querySelector('.tabs-warp').offsetTop-document.querySelector('.header').offsetHeight
}
if (scrollTop > this.offsetTop) {
this.searchBarFixed = true
} else {
this.searchBarFixed = false
}
},

在mounted阶段,监听滚动,当滚动的时候实现上面的函数

  mounted(){
window.addEventListener('scroll', this.handleScroll)
},

当离开组件的时候需要移除

  destroyed () {
window.removeEventListener('scroll', this.handleScroll)
},

滑动内容带动tab栏滑动

    <div class="tabs-warp" :class="searchBarFixed == true ? 'isFixed' :''">
<div ref="tabsContent" class="tabs-content">
<div style="display: inline-block"> <!--PC端运行,加上这个div可修复tab-bar错位的问题 -->
<ul class="tabs" ref="tabs">
<li class="tab" v-for="(tab,i) in tabs" :class="{active: i===curIndex}" :key="i" @click="changeTab(i)">{{tab.name}}</li>
</ul>
<div class="tab-bar" :style="{left: barLeft}"></div>
</div>
</div>
</div>
<!-- -->
<div v-if="searchBarFixed" class="replace-tab"></div>
<!--轮播-->
<swiper ref="mySwiper" :options="swiperOption">
<swiper-slide>
<!-- 精选 -->
<div v-for="(feature_item,index) in feature_channel" :key="index">
<v-home-list :channel="feature_item"></v-home-list>
</div>
</swiper-slide>
<swiper-slide>
<!-- 男频 -->
<div v-for="(male_item,index) in male_channel" :key="index">
<v-home-list :channel="male_item"></v-home-list>
</div>
</swiper-slide>
<swiper-slide>
<!-- 女频 -->
<div v-for="(female_item,index) in female_channel" :key="index">
<v-home-list :channel="female_item"></v-home-list>
</div>
</swiper-slide>
<swiper-slide>
<!-- 限免 -->
<div v-for="(free_item,index) in free_channel" :key="index">
<v-home-list :channel="free_item"></v-home-list>
</div>
</swiper-slide>
<swiper-slide>
<!-- 出版 -->
<div v-for="(publish_item,index) in publish_channel" :key="index">
<v-home-list :channel="publish_item"></v-home-list>
</div>
</swiper-slide>
</swiper>

引入插件

import { swiper, swiperSlide } from 'vue-awesome-swiper'

设置swiper

  data () {
return {
curIndex: 0, // 当前tab的下标
tabScrollLeft: 0, // 菜单滚动条的位置
swiperOption: { // 轮播配置
on: {
transitionEnd: () => {
this.changeTab(this.swiper.activeIndex)
}
}
},
isMounted:false,
searchBarFixed:false,
offsetTop:0, //吸顶
}
},

监听轮播

  computed: {
swiper () { // 轮播对象
return this.$refs.mySwiper.swiper
},
barLeft () { // 红线的位置
// 需要等dom加载完才能获取到dom
if(this.isMounted){
var tabWidth=document.getElementsByClassName('tab')[0].offsetWidth
var barWidth=document.getElementsByClassName('tab-bar')[0].offsetWidth
return (tabWidth * this.curIndex + (tabWidth - barWidth) / 2) + 'px'
}
}
},

当轮播切换的时候执行changeTab,tabIndex为轮播的索引

  methods: {
// 切换菜单
changeTab (tabIndex) {
if (this.curIndex === tabIndex) return; // 避免重复调用
let curTab = this.tabs[this.curIndex];// 当前列表
let newTab = this.tabs[tabIndex];// 新转换的列表
this.curIndex = tabIndex; // 切换菜单
curTab.mescroll
this.swiper.slideTo(tabIndex);
let tabsContent = this.$refs.tabsContent;
let tabDom = tabsContent.getElementsByClassName('tab')[tabIndex];
let star = tabsContent.scrollLeft;// 当前位置
let end = tabDom.offsetLeft + tabDom.clientWidth / 2 - document.body.clientWidth / 2; // 居中
this.tabScrollLeft = end;
tabsContent.scrollLeft = end;
},
},
}

3.分类页面

1.排行榜页面rank.vue

url:http://localhost:8080/rank

rank.vue里只有通用booklist组件

1.create

请求数据,并保持数据到data,并默认加载全部

2.watch

监听rank.id,rank.id对应左边标签,当rank.id改变的时候重新请求数据

3.组件

父组件通过prop 传递bookList数据给booklist组件

<book-list :book-list="bookList" v-if="bookList.length > 0"></book-list>

补充:图片资源的路径和数据请求的路径不同,需要区分

通用组件

1.booklist组件

1.router-link跳转
<router-link :to="{ name: 'book', params: {id: book._id} }">
通过命名路由跳转到子组件

2.computed 计算处理data

3.filter用于处理data数据的返回特定格式

   如 filters: {
        time(update) {
            return moment(update).format('YYYY-MM-DD');
        }
    },

2.图书详情页book.vue

url:http://localhost:8080/book/55eef8b27445ad27755670b9

book里有bookinfo review recommend bookbar

1.create

判断该书是否保存在书架

1.如果当前书籍已存在书架中,则书架中的书籍设置为当前书籍

2.如果不在 调用vuex中的mutation 保存自定义的book对象,并保持 this.$route.params.id

            this.SET_CUR_BOOK({
id: this.$route.params.id, //书籍id
title: '', //书名
cover: '', //封面
author: '', //作者
lastChapter: '', //已更新的最新章节
updated: '', //更新时间
readChapter: '', //已读章节
isInShelf: false, //是否已在书架中,false:不在,true:在
sort: false //目录顺序,false:正序, true:倒序
});

2.组件

1.bookinfo组件

create请求数据    调用vuex 参数为父级保存的 id: this.$route.params.id

2.revew组件

。。。

3.阅读页面 read.vue

1.create

保存id this.bookId = this.$route.params.id;
判断是否从图书详情页点击目录过来,是则显示章节目录,否默认显示阅读页面
请求章节列表数据 
 
2.method
点击下一章节
选择章节
 
3.beforeRouteLeave

路由退出
判断是否添加到书架
    beforeRouteLeave(to, from, next) {
//当书籍不在书籍并且不是从目录过来
if (!this.curBook.isInShelf && !this.isFromMenu) {
//显示提示框
this.showDialog = true;
//调用子组件dialog的confirm方法
this.$refs.dialog.confirm().then(() => {
this.showDialog = false;
let book = this.curBook;
//添加到书架保存
book.isInShelf = true;
this.SET_CUR_BOOK(book);
this.ADD_TO_SHELF(book);
next();
}).catch(() => {
this.showDialog = false;
next();
})
} else {
next()
}
}
 分割文章
"诺德王国一年只有三个月的时间会是温暖的,别的时间这里似乎总是被风雪和寒冷笼罩,这里的冬天是如此地寒冷以至于晨曦之神能带给这里晨光却无法带来温暖,农业女神的信徒们总是努力地想方设法在那些肥沃可是终年寒冷的土地上播种耐寒的作物,如小麦、大麦、油菜等,一年一熟的农业勉强供给着这个国家的口粮所需。↵↵  夜晚,位于诺德领北方的乌兰镇静悄悄,又是一年的隆冬时节,天空一如既往地飘起了鹅毛大雪,落在家家户户的屋顶,就连道路上也落得厚厚一层积雪,小镇的空气静谧无声,房屋的烟囱冒出浓浓黑烟,天上的星辰带给地面微弱的星光,大雪却把一切掩埋。↵↵  这个季节,太阳下山的时间很早,天黑之后,快一分回到城镇就多一分安全,慢一分回到城镇就多一分危险,日落之后的野外什么都有,强盗、野兽人、不时来劫掠的北方蛮族、甚至是可怕的绿皮或者亡灵都时有出没,小镇的居民们基本不会选择在这个时候外出,只有家里的房门和温暖的壁炉能带给他们安全感。↵↵  一个银装素裹的世界,除了偶尔经过的巡逻卫兵,小镇内一片黑暗,像一个死城,隆冬的寒风不断地呼啸而过,雪花在天际间飘洒,在这个一年最寒冷的季节,小镇的居民们都只能躲在自己的家里,盼望着能够早日等来开春的一刻。↵↵  一支黑色的皮靴踩在城门口的台阶上"

1.str = str.replace(/↵/g,"<br/>");
2.str=str.replace(/\n/g,"<br/>")
3.array=array.split('\n')
具体哪一种需要看编辑器的换行符,不一定生效
 

功能

1.页面弹出设置栏效果

    <div class="config">
<div :class="['config-top','bg-black', { 'config-enter': show_config }]">顶部栏</div>
<div :class="['config-right','bg-black', { 'config-enter': show_config }]">加入书架</div>
<div :class="['config-bottom','bg-black', { 'config-enter': show_config }]">
<div class="config-bootom-item">目录</div>
<div class="config-bootom-item" @click="showNightMode()">夜间模式</div>
<div class="config-bootom-item">设置</div>
</div>
</div>
showConfig(){
 if(!this.show_config){
   this.show_config=true
 }else{
  this.show_config=false
 }
},
  .bg-black{
color: #fff;
background-color: rgba(0, 0, 0, 0.9);
transition: transform 0.15s ease 0s;
text-align: center;
}
.config-top{
position: fixed;
width: 100%;
height: .8rem;
top:0;
left:0;
transform: translateY(-100%);
}
.config-right{
position: fixed;
right: 0;
top:20%;
width: 1.6rem;
height: .6rem;
line-height: .6rem;
transform: translateX(100%);
border-top-left-radius: .3rem;
border-bottom-left-radius: .3rem;
}
.config-bottom{
position: fixed;
display: flex;
width: 100%;
height: 1rem;
bottom: 0;
left: 0;
justify-content: space-between;
transform: translateY(100%);
&>.config-bootom-item{
flex:0 0 33.3%;
}
}
.config-enter{
transform: translate(0%, 0%);
}

首页设置transform偏移到屏幕外,并设置transition效果

2.页面只有点击中心区域弹出设置栏,点击其他区域不弹出

<div class="read-touch" @click="showConfig()"></div>
.read-touch{
position: fixed;
width: 60%;
height: 40%;
top: 30%;
left: 20%;
z-index: 1000;
}

3.目录排序,正序和倒序

目录是一个数组,可以通过数组自带reverse()来排序

4.点击目录弹出目录

重点:Read.vue包含两个组件  1.ReadContent书页面   和    2.Chapter目录页面

1.点击弹出目录的按钮在ReadContent组件,2目录显示在Chapter组件。

解决方法:通过$emit 将ReadContent(子组件)的信息传到Read.vue(父组件),再由父组件prop传递到 Chapter(子组件)

Read.vue组件

<template>
<section class="read">
<v-read-content :read-content="read_content" @show-chapter="showChapter()"></v-read-content>
<v-chapter :chapter-name="chapter_name" :chapter-show="chapter_show" @select-chapter="selectChapterData"></v-chapter>
</section>
</template> <script>
import http from '../http/api'
import {mapState,mapMutations} from 'vuex';
import ReadCotent from '../components/common/ReadContent'
import Chapter from '../components/common/Chapter'
export default {
name:'read',
components:{
'v-read-content':ReadCotent,
'v-chapter':Chapter
},
data(){
return{
book_id:'',
chapter_name:[],
chapter_show:false,
read_content:[],
read_index:0
}
},
watch:{
// 监听章节和阅读到第几章变化
chapter_name(){
this.getChapterData(this.chapter_name[this.read_index].id)
},
read_index(){ }
},
methods:{
// 获取所有章节名
getChapterName(book_id){
http.getChapters(book_id)
.then(data => {
this.chapter_name=data
})
},
// 获取特定章节内容
getChapterData(chapter_id){
http.getChapterContent(chapter_id)
.then(data => {
this.read_content.push({
content_title: data.title,
content_list: data.isVip ? ['vip章节,请到正版网站阅读'] : data.cpContent.split('\n') //换行符分割文章
});
var aa=data.cpContent.split('\n')
})
},
// ReadContent组件传出的是否显示章节
showChapter(){
this.chapter_show=true
},
selectChapterData(chapter_id){
// 先清空原有书籍
// this.readContent.splice(0, this.readContent.length);
this.read_content=[]
this.getChapterData(chapter_id)
this.chapter_show=false
}
},
created(){
this.book_id=this.$route.params.id;
this.getChapterName(this.book_id)
}
}
</script>

ReadContent.vue组件

<template>
<section :class="['read-content',skin_color,{'night-color':night_mode}]" v-if="readContent.length>0" >
<div class="config">
<div :class="['config-top','bg-black', { 'config-enter': show_config }]">顶部栏</div>
<div :class="['config-right','bg-black', { 'config-enter': show_config }]">加入书架</div>
<div :class="['config-bottom','bg-black', { 'config-enter': show_config }]">
<div class="config-bootom-item" @click="showChapter()">目录</div>
<div class="config-bootom-item" @click="showNightMode()">夜间模式</div>
<div class="config-bootom-item" @click="showConfigPop()">设置</div>
</div>
<!-- 设置字体颜色弹出层 -->
<div class='config-bootom-pop' v-show="show_config_pop">
<ul class="config-skin-color">
<li class="color-item" v-for="(skin,index) in skin_list" :key="index">
<span :class="['color-round-btn',skin,{'skin_color_active':skin==skin_color}]" @click="changeSkinColor(skin)"></span>
</li>
</ul>
<div class="config-control-fontsize">
<button @click="changeFontSize(false)">A-</button>
<button @click="changeFontSize(true)">A+</button>
</div>
</div>
</div> <div class="read-tag">民国谍影</div>
<h4 class="read-title">{{readContent[0].content_title}}</h4>
<div class="read-touch" @click="showConfig()"></div>
<ul >
<li :style="{ fontSize: font_size + 'px' }" v-for="(item,index) in readContent[0].content_list" :key="index">
{{item}}
</li>
</ul> </section>
</template> <script>
export default {
name:'readcontent',
props:{
readContent:Array
},
data(){
return{
show_config:false, //设置弹出层
show_config_pop:false, //设置皮肤字体弹出层
skin_color:'', //皮肤颜色
night_mode:false, //夜晚模式
skin_list:['skin-default', 'skin-blue', 'skin-green', 'skin-pink', 'skin-dark', 'skin-light'],
// skin_color_active:'',
font_size:14 }
},
methods:{
// 显示设置弹出层
showConfig(){
if(!this.show_config){
this.show_config=true
}else{
this.show_config=false
this.show_config_pop=false
}
},
// 夜间模式
showNightMode(){
this.skin_color=false
if(!this.night_mode){
this.night_mode=true
}else{
this.night_mode=false
}
},
// 显示弹出层皮肤和字体大小
showConfigPop(){
this.show_config_pop=true
},
changeSkinColor(skin){
this.night_mode=false
this.skin_color=skin
},
changeFontSize(isAdd){
console.log(this.font_size)
// if ((this.font_size >= 30 && isAdd) || (this.font_size <= 10 && !isAdd)) {
// return;
// }
let size = this.font_size;
isAdd ? size++ : size--
this.font_size=size
// this.SET_FONT_SIZE(size);
},
// 显示章节 $emit触发父组件
showChapter(){
// this.show_config=false
this.$emit('show-chapter'); }
} }
</script> <style lang="scss" scoped>
// 皮肤颜色和夜晚模式
.night-color{
color: rgba(255, 255, 255, .5);
background-color: #1a1a1a;
}
.skin-default {
background-color: #c4b395;
} .skin-blue {
background-color: #c3d4e6;
} .skin-green {
background-color: #c8e8c8;
} .skin-pink {
background-color: #F8C9C9;
} .skin-dark {
background-color: #3E4349;
} .skin-light {
background-color: #f6f7f9;
} .read-content{
padding:0 .2rem;
// 设置
.bg-black{
color: #fff;
background-color: #13120F;
transition: transform 0.15s ease 0s;
text-align: center;
}
.config-top{
position: fixed;
width: 100%;
height: .8rem;
top:0;
left:0;
transform: translateY(-100%);
}
}
</style>

Chapter.vue组件

<template>
<section :class="['chapter',{'chapter-show':chapterShow}]">
<div class="chapter-title">返回</div>
<div class="chapter-head">
<span class="chapter-total">共{{chapterName.length}}章</span>
<span class="chapter-sort" @click="chapterSort()">
<span v-if="sort">倒序</span>
<span v-else>正序</span>
</span>
</div>
<div class="chapter-list-section">
<div class="chapter-top">正文卷</div>
<ul class="chapter-list">
<li class="chapter-list-item" v-for=" chapter in chapterName" :key="chapter.id" @click="selectChapter(chapter.id)">
<span>{{chapter.title}}</span>
<span v-if="chapter.isVip">vip</span>
</li>
</ul>
</div>
</section>
</template> <script>
export default {
name:'chapter',
props: {
chapterName: Array,
chapterShow:Boolean
},
data(){
return{
sort:true
}
},
methods:{
// 排序
chapterSort(){
this.sort=!this.sort
this.chapterName.reverse(); //数组reverse()
},
// 选择章节
selectChapter(chapter_id){
this.$emit('select-chapter', chapter_id);
}
}
}
</script> <style lang="scss" scoped>
.chapter-title{
height: .8rem;
} .chapter-show{
transform: translateX(0) !important;
}
.chapter{
position: fixed;
top:0;
left:0;
bottom:0;
right:0;
overflow: hidden;
z-index: 9999;
background-color: #fff;
transform: translateX(-100%);
transition: transform .15s; } </style>

vue 追书神器的更多相关文章

  1. vue仿追书神器,vue小说项目源码

    vue-reader 一点阅读器!API源自追书神器,免费使用!目前已初步开发完成! Github项目地址:https://github.com/AntonySufer/vue-readle 欢迎is ...

  2. 基于vue-cli3和追书神器制作的移动端小说阅读网站,附接口和源码

    项目简介 基于node express+mysql+vue-cli3和追书神器接口制作的移动端小说阅读网站,**仅供参考学习!不用于任何商业用途!** 闲暇时间用vue练练手,就想写个小说网站来看看, ...

  3. React+Redux实现追书神器网页版

    引言 由于现在做的react-native项目没有使用到redux等框架,写了一段时间想深入学习react,有个想法想做个demo练手下,那时候其实还没想好要做哪一个类型的,也看了些动漫的,小说阅读, ...

  4. 追书神器API

    由于自己喜欢看小说,有的时候不方便手机看的时候希望在电脑上面看,但很多网站有广告啊,于是封装了套手机版的追书神器API 目前只做了搜索 详情 书评 换源 正文 调用方式: //搜索小说 var sea ...

  5. Vue小说阅读器(仿追书神器)

    一个vue阅读器项目,目前已升级到2.0,阅读器支持横向分页并滑动翻页(没有动画,需要动画的可以自己设置,增加transitionDuration即可) 技术栈 vue全家桶+mint-ui gith ...

  6. 通过Charles获取看书神器API

    Charles Charles是一个可以做HTTP代理/ HTTP监视器/反向代理的软件,使开发人员能够查看其机器和Internet之间的所有HTTP和SSL / HTTPS流量.包括请求,响应和HT ...

  7. Vue开发调试神器 vue-devtools

    Vue开发调试神器: vue-devtools 1. 下载Chrome扩展插件GitHub下载地址: https://github.com/vuejs/vue-devtools 建议使用npm淘宝镜像 ...

  8. svn 追责神器 blame vscode - SVN Gutter

    svn 追责神器 blame vscode - SVN Gutter

  9. vue 代码调试神器

    一.序 工欲善其事,必先利其器.作为一名资深程序员,相信必有一款调试神器相伴左右,帮助你快速发现问题,解决问题.作为前端开发,我还很年轻,也喜欢去捣鼓一些东西,借着文章的标题,先提一个问题:大家目前是 ...

随机推荐

  1. web服务器之nginx和apache的区别

    ① apache属于重量级的服务器,nginx属于轻量级的服务器; 区别在于对一些功能的支持,比如:  pathinfo,php模块方面 ② nginx抗高并发能力强. 由于nginx采用的是异步非阻 ...

  2. 浅论各种调试接口(SWD、JTAG、Jlink、Ulink、STlink)的区别

    JTAG协议 JTAG(Joint Test Action Group,联合测试行动小组)是一种国际标准测试协议(IEEE 1149.1兼容),主要用于芯片内部测试.现在多数的高级器件都支持JTAG协 ...

  3. javascript ES6 新特性之 扩展运算符 三个点 ...

    对于 ES6 新特性中的 ... 可以简单的理解为下面一句话就可以了: 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中. 作用类似于 Object.assign() ...

  4. ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目

    一.前言 这几年前端的发展速度就像坐上了火箭,各种的框架一个接一个的出现,需要学习的东西越来越多,分工也越来越细,作为一个 .NET Web 程序猿,多了解了解行业的发展,让自己扩展出新的技能树,对自 ...

  5. EF实体实现链接字符串加密

    1.加密解密方法 using System;using System.Security.Cryptography; using System.Text;namespace DBUtility{ /// ...

  6. .NET Core微服务之ASP.NET Core on Docker

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.Docker极简介绍 1.1 总体介绍 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源.D ...

  7. C#利用NPOI操作Excel文件

    NPOI作为开源免费的组件,功能强大,可用来读写Excel(兼容xls和xlsx两种版本).Word.PPT文件.可是要让我们记住所有的操作,这便有点困难了,至此,总结一些在开发中常用的针对Excel ...

  8. .NET CORE 中使用AutoMapper进行对象映射

    简介 AutoMapper uses a fluent configuration API to define an object-object mapping strategy. AutoMappe ...

  9. pwnable.tw applestore 分析

    此题第一步凑齐7174进入漏洞地点 然后可以把iphone8的结构体中的地址通过read修改为一个.got表地址,这样就能把libc中该函数地址打出来.这是因为read函数并不会在遇到\x00时截断( ...

  10. 能够玩转BKY皮肤的 geek,有一半最后都成为了前端大师

    By Conmajia March 9, 2018 剩下的那一半全部扑街了. 世纪之初,BKY那些花里胡哨的预设皮肤曾经让初识网络的懵懂学子雀跃不已. 然而以现在的审美眼光看来,这些带着一股子扑面而来 ...