商业级web阅读器项目(上)

1.技术难点分析

  • 阅读器开发:
  • 分页算法、全文搜索算法、引入web字体、主题设计
  • 离线存储机制设计:LocalStorage+IndexDB
  • 实现各种复杂手势+交互动画,如何兼容手势+鼠标操作
  • 利用vuex+mixin实现组件解耦+复用 科大讯飞web在线语音合成API开发

2.项目准备

  • 字体图标
  • 项目依赖包+项目配置
  • 准备web字体
  • viewport配置
  • rem设置+自适应布局实现思路
  • global.scss和reset.scss
  • 搭建静态资源服务器Nginx

2.1.字体图标准备

在main.js中引入

$ import "./assets/styles/icon.css"

2.2.安装epubjs依赖包

$ npm install --save epubjs

2.3.准备web字体

商业级web阅读器项目(上)_第1张图片
然后再在index.html中引入即可,


**在这里需要注意的是,有两种方法引入文本字体,一种是上述所示的方法,另一种是把fonts放到assets中,这时,就不能在index.html中引入,而应该在main 中以import的形式引入
**

2.4.viewport配置

在index.html中


2.5.rem配置

在App.vue中编写script代码


2.6. global.scss和reset.scss设置

商业级web阅读器项目(上)_第2张图片
在main中引入
import “./assets/styles/global.scss”

2.7.搭建nginx静态资源服务器

下载方法,及使用方法可参考 博客一文
,主要的命令 就是结束 nginx -s stop;接下来,在D盘的根目录下创建资源文件夹 resource
,然后修改 nginx.conf文件
添加这段代码,自起一个服务端口

server {
		listen 8081;
		server_name resource;
		root D:/resource;
		autoindex on;
		location / {
		  add_header Access-Control-Allow-Origin *;
		}
		add_header Cache-Control "no-cache,must-revalidate";
	}

商业级web阅读器项目(上)_第3张图片

当出现这个,说明成功了
商业级web阅读器项目(上)_第4张图片

3.阅读器–标题菜单、字号字体及主题设置功能开发

3.1.需求分析图

看看需求分析图
商业级web阅读器项目(上)_第5张图片

主要技术难点

  • epubjs
  • vuex+mixin
  • vue-i18n
  • 动态切换主题+书签手势操作

3.2.阅读器解析和渲染

把epub文件夹(放有所有epub电子书的文件夹)放到resource当中

由于访问资源每一本书的url是 http://localhost:8080/#/ebook/education/202yalishanda
这种方式,可见要用动态路由的方式来访问资源
什么是动态路由?听我说来
商业级web阅读器项目(上)_第6张图片
这种就是动态路由
,然后我们在浏览器上输入
http://localhost:8080/#/ebook/asasd
在EBook Reader中


接收到了参数
商业级web阅读器项目(上)_第7张图片
这就是动态路由

到了这这里想要获取nginx上的电子书时出现了跨域问题,暂时解决不了,先用 本地资源

3.3.翻页功能的实现

当屏幕向左划时,向上翻页,反之翻下一页。

//监听滑动翻页事件
                this.rendition.on("touchstart",event=>{
                    this.touchStartX = event.changedTouches[0].clientX;
                    this.touchStartTime = event.timeStamp;
                });
                this.rendition.on("touchend",event=>{
                    const offsetX = event.changedTouches[0].clientX-this.touchStartX;
                    const time = event.timeStamp - this.touchStartTime;
                    if(time<500 && offsetX>40){
                        this.prevPage();
                    }else if (time<500 && offsetX<-40) {
                        this.nextPage();
                    }else{
                        this.toggleTitleAndMenu()
                    }
                })
            prevPage(){
                if (this.rendition){
                   this.rendition.prev();
                   // this.$store.dispatch("setMenuVisible",false)
                    this.setMenuVisible(false);
               }
            },
            nextPage(){
              if (this.rendition){
                  this.rendition.next();
                  // this.$store.dispatch("setMenuVisible",false)
                  this.setMenuVisible(false);
              }
            },

在这里,值得注意的一点,需要修改epubjs的版本,修改为 “epubjs”: “0.3.71”,因为0.38以上的都不支持滑动

3.4.标题栏和菜单栏实现

先创建标题栏和菜单栏组件

//EbookTitle
<template>
    <transition name="slide-down">
        <div class="title-wrapper" v-show="menuVisible">
            <div class="left">
                <span class="icon-back">span>
            div>
            <div class="right">
                <div class="icon-wrapper">
                    <span class="icon-book">span>
                div>
                <div class="icon-wrapper">
                    <span class="icon-cart">span>
                div>
                <div class="icon-wrapper">
                    <span class="icon-more">span>
                div>
            div>
        div>
    transition>
template>

<script>
    // import {mapGetters} from "vuex"
    import { ebookMixin } from "./../../utils/mixin"
    export default {
        name: "EbookTitle",
        mixins:[ebookMixin],
        // computed:{
        //     ...mapGetters(["menuVisible"])
        // }
    }
script>


<style lang='scss' scoped>
    @import './../../assets/styles/global';
    .title-wrapper {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 101;
        display: flex;
        width: 100%;
        height: px2rem(48);
        background: white;
        box-shadow: 0 px2rem(8) px2rem(8) rgba(0, 0, 0, .15);
        font-size: px2rem(20);
        .left {
            flex: 0 0 px2rem(60);
            @include center;
        }
        .right {
            flex: 1;
            display: flex;
            justify-content: flex-end;
            .icon-wrapper {
                flex: 0 0 px2rem(40);
                @include center;
                .icon-cart {
                    font-size: px2rem(22);
                }
            }
        }
    }
style>
//EbookMenu
<template>
    <div>
        <transition name="slide-up">
            <div class="menu-wrapper" :class="{'hide-box-shadow':!menuVisible}" v-show="menuVisible">
                <div class="icon-wrapper">
                    <span class="icon-menu icon" @click="showSetting(3)">span>
                div>
                <div class="icon-wrapper">
                    <span class="icon-progress icon" @click="showSetting(2)">span>
                div>
                <div class="icon-wrapper">
                    <span class="icon-bright icon" @click="showSetting(1)">span>
                div>
                <div class="icon-wrapper">
                    <span class="icon-a icon" @click="showSetting(0)">Aspan>
                div>
            div>
        transition>
    div>
template>

<script>
    // import {mapGetters} from "vuex";
    import { ebookMixin } from "../../utils/mixin.js"
    export default {
        name: "EbookMenu",
        mixins:[ebookMixin],
        // computed:{
        //     ...mapGetters(["menuVisible"])
        // },
        methods:{
            showSetting(){

            }
        }

    }
script>


<style lang='scss' scoped>
    @import './../../assets/styles/global';
    .menu-wrapper {
            position: absolute;
            bottom: 0;
            left: 0;
            z-index: 102;
            display: flex;
            width: 100%;
            height: px2rem(48);
            background: white;
            box-shadow: 0 px2rem(-8) px2rem(8) rgba(0, 0, 0, .15);
            font-size: px2rem(20);
            &.hide-box-shadow {
                box-shadow: none;
            }
            .icon-wrapper {
                flex: 1;
                @include center;
                .icon-progress {
                    font-size: px2rem(28);
                }
                .icon-bright {
                    font-size: px2rem(24);
                }
            }
        }

style>

用 menuvisible来控制标题栏和菜单栏是否显示


到这里,容易发现

 methods:{
          ...mapActions(['setMenuVisible','setFileName']),
      }

这里再很多组件中都是共有的,这样,可以把这些共有的放在一起,在通过vue的一个勾子mixins 调用出来

商业级web阅读器项目(上)_第8张图片
商业级web阅读器项目(上)_第9张图片

3.5.字号UI的设置

商业级web阅读器项目(上)_第10张图片
当点击菜单栏中的A 的时候,出现字号设置UI,

<template>

        <transition name="slide-up">
            <div class="setting-wrapper" v-show="menuVisible && settingVisible===0">
                <div class="setting-font-size">
                    <div class="preview" :style="{fontSize: fontSizeList[0].fontSize + 'px'}">Adiv>
                    <div class="select">
                        <div class="select-wrapper" v-for="(item, index) in fontSizeList" :key="index" @click="setFontSize(item.fontSize)">
                            <div class="line">div>
                            <div class="point-wrapper">
                                <div class="point" v-show="defaultFontSize === item.fontSize">
                                    <div class="small-point">div>
                                div>
                            div>
                            <div class="line">div>
                        div>
                    div>
                    <div class="preview" :style="{fontSize: fontSizeList[fontSizeList.length - 1].fontSize + 'px'}">Adiv>
                div>

            div>
        transition>

template>

<script>
    import {ebookMixin} from "./../../utils/mixin"
    import {FONT_SIZE_LIST} from "./../../utils/book"
    export default {
        name: "EbookSettingFont",
        mixins:[ebookMixin],
        data(){
            return{
                fontSizeList:FONT_SIZE_LIST,
            }
        },
        methods:{
            setFontSize(fontSize){
                this.setDefaultFontSize(fontSize);
                this.currentBook.rendition.themes.fontSize(fontSize);
            }
        }
    }
script>

<style lang="scss" rel="stylesheet/scss" scoped>
    @import "../../assets/styles/global";
    div{
        /*width: 100%;*/
        /*height: 200px;*/
        /*margin-top: 100px;*/
        /*margin-bottom: 0px;*/
        /*z-index: 999;*/
        /*background-color: red;*/
    }
    .setting-wrapper {
        background-color: #fff;
        position: absolute;
        bottom: px2rem(48);
        left: 0;
        z-index: 190;
        display: flex;
        flex-direction: column;
        width: 100%;
        height: px2rem(90);
        box-shadow: 0 px2rem(-8) px2rem(8) rgba(0, 0, 0, .15);
        .setting-font-size {
            flex: 2;
            display: flex;
            height: 100%;
            .preview {
                flex: 0 0 px2rem(40);
                @include center;
            }
            .select {
                display: flex;
                flex: 1;
                .select-wrapper {
                    flex: 1;
                    display: flex;
                    align-items: center;
                    &:first-child {
                        .line {
                            &:first-child {
                                border-top: none;
                            }
                        }
                    }
                    &:last-child {
                        .line {
                            &:last-child {
                                border-top: none;
                            }
                        }
                    }
                    .line {
                        flex: 1;
                        height: 0;
                        border-bottom: 1px solid #000;
                    }
                    .point-wrapper {
                        position: relative;
                        flex: 0 0 0;
                        width: 0;
                        height: px2rem(7);
                        .point {
                            background-color: lightpink;
                            position: absolute;
                            top: px2rem(-8);
                            left: px2rem(-10);
                            width: px2rem(20);
                            height: px2rem(20);
                            border-radius: 50%;
                            box-shadow: 0 px2rem(4) px2rem(4) rgba(0, 0, 0, .15);
                            @include center;
                            .small-point {
                                width: px2rem(5);
                                height: px2rem(5);
                                border-radius: 50%;
                            }
                        }
                    }
                }
            }
        }
        .setting-font-family {
            flex: 1;
            font-size: px2rem(14);
            @include center;
            .setting-font-family-text-wrapper {
                @include center;
            }
            .setting-font-family-icon-wrapper {
                @include center;
            }
        }
    }
style>

3.6.字号设置功能实现

通过更改 defautFontSize来更改字体大小
defautFontSize 选

export const FONT_SIZE_LIST =[
    {fontSize:12},
    {fontSize:14},
    {fontSize:16},
    {fontSize:18},
    {fontSize:20},
    {fontSize:22},
    {fontSize:24},
];

之中的一个值

设置电子书的字号如下实现

 setFontSize(fontSize){
                this.setDefaultFontSize(fontSize);
                this.currentBook.rendition.themes.fontSize(fontSize);
            }

3.7.字体设置功能实现

商业级web阅读器项目(上)_第11张图片

在字号设置界面当中,我们把 这整个wrapper的布局设置为flex,上面flex:2,下面flex:1

商业级web阅读器项目(上)_第12张图片
结构如此,简单明了

3.8.字体设置弹窗UI实现

商业级web阅读器项目(上)_第13张图片

分为两部分,这部分较大的难题,就是使选中部分高亮+

<template>
   <transition name="popup-slide-up">
       <div class="ebook-popup-list" v-show="fontFamilyVisible">
           <div class="ebook-popup-title">
               <div class="ebook-popup-title-icon">
                   <span class="icon-down2">span>
               div>
               <span class="ebook-popup-title-text">选择字体span>
           div>
           <div class="ebook-popup-list-wrapper">
               <div class="ebook-popup-item" v-for="(item,index) in fontFamilyList" :key="index">
                   <div class="ebook-popup-item-text" :class="{'selected':isSelected(item)}">{{item.font}}div>
                   <div class="ebook-popup-item-check" v-if="isSelected(item)">
                       <span class="icon-check">span>
                   div>
               div>
           div>
       div>
   transition>
template>

<script>
    import {ebookMixin} from "./../../utils/mixin"
    import {FONT_FAMILY} from "./../../utils/book"
    export default {
        name: "EbookSettingFontPopup",
        mixins:[ebookMixin],
        data(){
            return{
                fontFamilyList:FONT_FAMILY
            }
        },
        methods:{
            isSelected(item){
                return this.defaultFontFamily===item.font;
            }
        }
    }
script>


<style lang="scss" rel="stylesheet/scss" scoped>
    @import "../../assets/styles/global";
    .ebook-popup-list{
        position: absolute;
        bottom: 0;
        left: 0;
        z-index: 300;
        width: 100%;
        /*background-color: red;*/
        background-color: white;
        box-shadow: 0 px2rem(-4) px2rem(6) rgba(0,0,0,1);
        .ebook-popup-title{
            position: relative;
            padding: px2rem(15);
            box-sizing: border-box;
            border-bottom: px2rem(1) solid #b8b9bb;
            text-align: center;
            @include center;
            .ebook-popup-title-icon{
                position: absolute;
                left: px2rem(15);
                top: 0;
                height: 100%;
                @include center;
                font-size: px2rem(16);
            }
            .ebook-popup-title-text{
                font-size: px2rem(14);
                font-weight: bold;
            }

        }
        .ebook-popup-list-wrapper{

            .ebook-popup-item{
                display: flex;
                padding: px2rem(15);
                text-align: center;
                /*justify-content: space-between;*/
                .ebook-popup-item-text{
                    flex: 1;
                    font-size: px2rem(14);
                    text-align: left;
                   &.selected{
                        color: #346c69;
                       font-weight: bold;

                   }
                }
                .ebook-popup-item-check{
                    flex: 1;
                    font-size: px2rem(14);
                    font-weight: bold;
                    .icon-check{
                        color: #346c69;
                    }
                }
            }
        }
    }
style>

看代码,很巧妙,用一行就解决了这个问题

 
{{item.font}}

如何判断选中呢?其实就是靠defaultFontFamily这个值来判断,看这个方法的实现!粗俗易懂

isSelected(item){
                return this.defaultFontFamily===item.font;
            }

3.9.字体设置弹窗功能实现

通过上面一节,容易发现,只要改变defaultFontFamily就能改变选中高亮的选项
因此,可以绑定一个方法

 setFontFamily(font){
                this.setDefaultFontFamily(font);
            }

接着也要记得改变电子书的字体,语句和改变电子书字号类似

setFontFamily(font){
                this.setDefaultFontFamily(font);
                this.currentBook.rendition.themes.font(font);
            }

但发现这样并不能实现,这是虽然,我们把样式文件虽然 放到项目当中了,但是并不能直接加载到电子书,因此,我们需要在电子书初次被渲染的时候就要把样式加载给电子书,这样在后面才有了选择的权力

在电子书.display()之后

  //加载字体样式到电子书
                this.rendition.hooks.content.register(contents=>{
                    contents.addStylesheet("./../../assets/fonts/daysOne.css")
                })


但这并不难成功达到目的,因为
商业级web阅读器项目(上)_第14张图片
这里传入的必须是个url,而不是一个路径,所以把font样式资源放到 搭建好的nginx服务器上

改成这样:

 //加载字体样式到电子书
                this.rendition.hooks.content.register(contents=>{
                    contents.addStylesheet("http://localhost:8081/fonts/daysOne.css")
                })

商业级web阅读器项目(上)_第15张图片

发现功能已经实现

接着把所有资源都加载
商业级web阅读器项目(上)_第16张图片

3.10.字号和字体设置离线存储

为了方便,引入依赖 web-storage-cache

这个插件的用处是可以把传进来的json转化为对象存到 本地,读取时 将对象转化为json

为了方便管理本地存储 把关于ebook相关的都放进book的对象当中,这样以后扩展其他功能是,例如添加音乐时,可以把音乐相关的都放进music对象

商业级web阅读器项目(上)_第17张图片
创建这个 来管理本地存储

import Storage from "web-storage-cache";
const localStorage = new Storage();
export function setLocalStorage(key,value) {
    return localStorage.set(key,value);
}
export function getLocalStorage(key) {
    return localStorage.get(key);
}
export function removeLocalStorage(key) {
    return localStorage.delete(key);
}

export function clearLocalStorage() {
    return localStorage.clear();
}

export function setBookObject(fileName,key,value) {
    console.log(fileName);
    let book = getLocalStorage(`${fileName}-info`);
    if(!book){
        book = {}
    }
    book[key] = value;
    setLocalStorage(`${fileName}-info`,book)
}
export function getBookObject(fileName,key) {
    let book = getLocalStorage(`${fileName}-info`)
    if(book){
        return book[key]
    }else {
        return null
    }
}
export function getFontFamily(fileName) {
    return getBookObject(fileName,'fontFamily')
}

export function saveFontFamily(fileName,font) {
    return setBookObject(fileName,'fontFamily',font);
}

调用其中的方法就可以了,是不是超鸡方便

不信可以看看使用,
我们在已渲染完电子书后中就获取本地 fontFamily,如果不存在,则,默认添加为defaut

this.rendition.display().then(()=>{

                    let font = getFontFamily(this.fileName);
                    if(!font){
                        saveFontFamily(this.fileName,this.defaultFontFamily)
                    }else{
                        this.rendition.themes.font(font);
                        this.setDefaultFontFamily(getFontFamily(this.fileName));
                    }

                });

每次更改完后也要存储到本地

商业级web阅读器项目(上)_第18张图片
字号本地化过程类似,在此,就不再遨述

3.11. 字体设置标题国际化

为了跟上时代,必须使用国际化,这国际化以后搬到别处应用也是可以装一把。
创建这几个文件
商业级web阅读器项目(上)_第19张图片
中文包

const messages = {
  home: {
    title: '书城',
    hint: '计算机科学和软件工程',
    guessYouLike: '猜你喜欢',
    change: '换一批',
    clear: '清空',
    hotSearch: '热门搜索',
    historySearch: '搜索历史',
    sameAuthor: '与$1同作者',
    sameReader: '对$1感兴趣的人也在读',
    readPercent: '阅读$2的人,$1都在读',
    recommend: '热门推荐',
    seeAll: '查看全部',
    readers: '$1人同时在读',
    featured: '精选',
    category: '分类',
    books: '本书',
    readNow: '立即阅读',
    allBook: '共 $1 本图书'
  },
  category: {
    computerScience: '计算机科学',
    socialSciences: '社会科学',
    economics: '经济学',
    education: '教育学',
    engineering: '工程学',
    environment: '环境学',
    geography: '地理学',
    history: '历史学',
    laws: '法学',
    lifeSciences: '生命科学',
    literature: '文学',
    biomedicine: '生物医学',
    businessandManagement: '工商管理',
    earthSciences: '地球科学',
    materialsScience: '材料科学',
    mathematics: '数学',
    medicineAndPublicHealth: '公共卫生',
    philosophy: '哲学',
    physics: '物理',
    politicalScienceAndInternationalRelations: '国际关系',
    psychology: '心理学',
    statistics: '统计学'
  },
  shelf: {
    title: '书架',
    edit: '编辑',
    cancel: '取消',
    search: '搜索',
    private: '私密阅读',
    noPrivate: '关闭私密阅读',
    download: '开启离线',
    move: '移动到...',
    remove: '移出书架',
    setPrivateTitle: '开启后,所选书籍的阅读记录将不会对外公开',
    open: '开启',
    closePrivateTitle: '是否关闭所选书籍的私密阅读?',
    close: '关闭',
    setPrivateSuccess: '已开启私密阅读
阅读记录将不再公开'
, closePrivateSuccess: '已关闭私密阅读', setDownloadTitle: '开启后,将自动缓存所选书籍内容', setDownloadSuccess: '已开启,将自动离线已购内容', setDownloadError: '离线下载异常,请重新尝试', removeDownloadTitle: '确认后,将删除所选书籍的离线内容', removeDownloadSuccess: '已选书籍的离线内容已删除', delete: '删除', clearCache: '清除缓存', clearCacheSuccess: '缓存已清空', removeBookTitle: '是否将$1移出书架?', removeBook: '移出', selectedBooks: '所选书籍', default: '默认', progress: '按进度', purchase: '按购买', bought: '已购买', notPurchased: '未购买', selectBook: '选择书籍', haveSelectedBook: '已选择$1本', haveSelectedBooks: '已选择$1本', moveBook: '移动书籍', newGroup: '新建分组', groupOut: '移出分组', editGroup: '修改分组', editGroupName: '修改分组名', deleteGroup: '删除分组', deleteGroupTitle: '删除分组后,分组内的书籍将会自动移出分组', groupNone: '当前分组暂无书籍', groupName: '分组名', confirm: '确定', moveBookInSuccess: '成功移入$1', moveBookOutSuccess: '成功移出分组', statistic: '$1本公开阅读 • $2本私密阅读', startDownload: '开始下载...', progressDownload: '正在下载:$1', downloadFirst: '请先缓存图书', welcome: '欢迎访问慕课网
学习《实战微信读书——媲美原生APP的企业级Web书城》
-------- 作者:Sam --------'
, find: '去找书', changeLanguage: '切换语言', studyNow: '去慕课网学习' }, detail: { copyright: '版权', navigation: '目录', publisher: '出版社', category: '分类', ISBN: 'ISBN', trial: '试读', lang: '语言', loading: '加载中...', read: '阅读', listen: '听书', addOrRemoveShelf: '加入书架', isAddedToShelf: '已加入书架' }, speak: { voice: '语音朗读', read: '查看原文', settings: '设置', timing: '定时', current: '当前章节', requestFailed: '请求失败!', apply: '语义解析核心技术由科大讯飞提供' }, book: { pulldownAddMark: '下拉添加书签', releaseAddMark: '松手添加书签', pulldownDeleteMark: '下拉删除书签', releaseDeleteMark: '松手删除书签', selectFont: '选择字体', haveRead: '已读$1分钟', themeDefault: '默认', themeGold: '雅致', themeEye: '护眼', themeNight: '夜间', loading: '加载中...', navigation: '目录', bookmark: '书签', searchHint: '搜索全书内容', haveRead2: '已读', minutes: '分钟', cancel: '取消' } } export default messages

英文包

const messages = {
  home: {
    title: 'Book Store',
    hint: 'Computer Science And Software Engineering',
    guessYouLike: 'Guess You Like',
    change: 'Change',
    clear: 'Clear',
    hotSearch: 'Hot Search',
    historySearch: 'History Search',
    sameAuthor: 'Same author with $1',
    sameReader: 'Same reader with $1',
    readPercent: '$1 is reading $2',
    recommend: 'Recommend',
    seeAll: 'See all',
    readers: '$1 is reading',
    featured: 'Featured',
    category: 'Category',
    books: 'books',
    readNow: 'Read Now',
    allBook: '$1 books'
  },
  category: {
    computerScience: 'Computer Science',
    socialSciences: 'Social Sciences',
    economics: 'Economics',
    education: 'Eductation',
    engineering: 'Engineering',
    environment: 'Environment',
    geography: 'Geography',
    history: 'History',
    laws: 'Laws',
    lifeSciences: 'LifeSciences',
    literature: 'Literature',
    biomedicine: 'Biomedicine',
    businessandManagement: 'Business and Management',
    earthSciences: 'Earth Sciences',
    materialsScience: 'Materials Science',
    mathematics: 'Mathematics',
    medicineAndPublicHealth: 'Medicine And Public Health',
    philosophy: 'Philosophy',
    physics: 'Physics',
    politicalScienceAndInternationalRelations: 'Political Science And International Relations',
    psychology: 'Psychology',
    statistics: 'Statistics'
  },
  shelf: {
    title: 'Book Shelf',
    edit: 'Edit',
    cancel: 'Cancel',
    search: 'Search',
    private: 'Private',
    noPrivate: 'Close Private',
    download: 'Download',
    move: 'Move...',
    remove: 'Remove',
    setPrivateTitle: 'When opened, the reading history of selected books will not be made public',
    open: 'Open',
    closePrivateTitle: 'Whether to close the private reading of selected books?',
    close: 'Close',
    setPrivateSuccess: 'Private reading has been open and reading history will no longer be published',
    closePrivateSuccess: 'Private reading has been closed',
    setDownloadTitle: 'When opened, selected books will automatically download',
    setDownloadSuccess: 'Opened, will automatically download purchased books',
    setDownloadError: 'Offline download exception, please try again',
    removeDownloadTitle: 'Once confirmed, the offline books of selected will be remove',
    removeDownloadSuccess: 'Offline books of selected has been remove',
    delete: 'Remove',
    clearCache: 'Clear Cache',
    clearCacheSuccess: 'Clear cache successfully, cache is empty',
    removeBookTitle: 'Whether to remove $1 out of the bookshelf?',
    removeBook: 'Remove',
    selectedBooks: 'selected books',
    default: 'Default',
    progress: 'By Progress',
    purchase: 'By Purchase',
    bought: 'Bought',
    notPurchased: 'Not Purchased',
    selectBook: 'Select Book',
    haveSelectedBook: '$1 book has been selected',
    haveSelectedBooks: '$1 books have been selected',
    moveBook: 'Move Book',
    newGroup: 'New Group',
    groupOut: 'Move Out of Group',
    editGroup: 'Edit Group',
    editGroupName: 'Edit Group Name',
    deleteGroup: 'Delete Group',
    deleteGroupTitle: 'After deleting a group, the books in the group will be automatically moved out of the group',
    groupNone: 'There are no books in the current group',
    groupName: 'Group Name',
    confirm: 'Confirm',
    moveBookInSuccess: 'Move book(s) into $1 successfully',
    moveBookOutSuccess: 'Move book(s) out of the group successfully',
    statistic: '$1 public reading • $2 private reading',
    startDownload: 'Start download...',
    progressDownload: 'Downloading:$1',
    downloadFirst: 'Please download book first',
    welcome: 'Welcome to visit iMooc
Learning "Practical WeChat Reading - Enterprise Web Book Store of Amami Native APP"
-------- Author: Sam --------'
, find: 'Go to book store', changeLanguage: 'Change Language', studyNow: 'Learn on imooc.com' }, detail: { copyright: 'Copyright', navigation: 'Table of Contents', publisher: 'Publisher', category: 'Category', ISBN: 'ISBN', trial: 'Trial Reading', lang: 'Language', loading: 'Loading...', read: 'Read', listen: 'Listen', addOrRemoveShelf: 'Add to Book Shelf', isAddedToShelf: 'Added to BookShelf' }, speak: { voice: 'Voice Reading', read: 'Read Originial', settings: 'Settings', timing: 'Timing', current: 'Current Section', requestFailed: 'Request failed!', apply: 'The core technology of semantic analysis is provided by iFLY TEK' }, book: { pulldownAddMark: 'Pull down to add bookmark', releaseAddMark: 'Release to add bookmark', pulldownDeleteMark: 'Pull down to delete bookmark', releaseDeleteMark: 'Release to add bookmark', selectFont: 'Select Font', haveRead: 'Already read $1 minutes', themeDefault: 'Default', themeGold: 'Grace', themeEye: 'Eye', themeNight: 'Night', loading: 'Loading...', navigation: 'Contents', bookmark: 'Bookmark', searchHint: 'Search from the entire book', haveRead2: 'already read', minutes: 'minutes', cancel: 'Cancel' } } export default messages

最后把这两个引入index

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import en from './en'
import cn from './cn'
import { getLocale, saveLocale } from '../utils/localStorage'

Vue.use(VueI18n)

const messages = {
  en, cn
}

let locale = getLocale()
if (!locale) {
  locale = 'cn';
  saveLocale(locale)
}

const i18n = new VueI18n({
  locale,
  messages
})

export default i18n

同样,记得把index 引入main中

商业级web阅读器项目(上)_第20张图片

在这里插入图片描述

通过这种方法使用,就能实现国际化了

3.12.阅读器主题设置功能实现

首先,理所当然的是,我们得先创建一个主题设置得组件
商业级web阅读器项目(上)_第21张图片

<template>
    <transition name="slide-up">
        <div class="setting-wrapper" v-show="menuVisible && settingVisible ===1">
            <div class="setting-theme">
                <div class="setting-theme-item" v-for="(item, index) in themeList" :key="index" @click="setTheme(index,item)">
                    <div class="preview" :style="{background: item.style.body.background}" :class="{'no-border': item.style.body.background !== '#fff'}">div>
                    <div class="text" :class="{'selected': item.name === defaultTheme}">{{item.alias}}div>
                div>
            div>
        div>
    transition>
template>

<script>
    import {ebookMixin} from "./../../utils/mixin"
    // import {themeList} from "./../../utils/book"
    export default {
        name: "EbookSettingTheme",
        mixins:[ebookMixin],
        computed:{
        },
        data(){
            return{
                
            }
        },
        methods:{
            setTheme(index,item){
                const theme = this.themeList[index];
                this.setDefaultTheme(theme.name).then(()=>{
                    this.currentBook.rendition.themes.select(this.defaultTheme)
                });

            },
        }

    }
script>

<style lang="scss" rel="stylesheet/scss" scoped>
    @import "../../assets/styles/global";

    .setting-wrapper {
        color: gray;
        background-color: white;
        position: absolute;
        bottom: px2rem(48);
        left: 0;
        z-index: 190;
        width: 100%;
        height: px2rem(90);
        box-shadow: 0 px2rem(-8) px2rem(8) rgba(0, 0, 0, .15);
        .setting-theme {
            height: 100%;
            display: flex;
            .setting-theme-item {
                flex: 1;
                display: flex;
                flex-direction: column;
                padding: px2rem(5);
                box-sizing: border-box;
                .preview {
                    flex: 1;
                    /*border: px2rem(1) solid #ccc;*/
                    box-sizing: border-box;
                    border: none;

                }
                .text {
                    flex: 0 0 px2rem(20);
                    font-size: px2rem(14);
                    @include center;
                    &.selected {
                        /*box-shadow: 0 px2rem(4) px2rem(6) 0 rgba(0, 0, 0, .1)!important;*/
                        /*border: px2rem(2) solid #5e6369;*/
                        font-weight: bolder;
                        /*color: black!important;*/
                    }
                }
            }
        }
    }
style>

既然实现额国际化,那肯定出现的文字也要已国际化的格式出现,看上述代码,用到的主题列表themeList,其实是放到util/book中的

export function themeList(vue) {
    return [
        {
            alias: vue.$t('book.themeDefault'),
            name: 'Default',
            style: {
                body: {
                    'color': '#4c5059',
                    'background': '#cecece',
                    // 'padding-top': `${realPx(48)}px!important`,
                    // 'padding-bottom': `${realPx(48)}px!important`
                },
                img: {
                    'width': '100%'
                },
                '.epubjs-hl': {
                    'fill': 'red', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'
                }
            }
        },
        {
            alias: vue.$t('book.themeGold'),
            name: 'Gold',
            style: {
                body: {
                    'color': '#5c5b56',
                    'background': '#c6c2b6',
                    // 'padding-top': `${realPx(48)}px!important`,
                    // 'padding-bottom': `${realPx(48)}px!important`
                },
                img: {
                    'width': '100%'
                },
                '.epubjs-hl': {
                    'fill': 'red', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'
                }
            }
        },
        {
            alias: vue.$t('book.themeEye'),
            name: 'Eye',
            style: {
                body: {
                    'color': '#404c42',
                    'background': '#a9c1a9',
                    // 'padding-top': `${realPx(48)}px!important`,
                    // 'padding-bottom': `${realPx(48)}px!important`
                },
                img: {
                    'width': '100%'
                },
                '.epubjs-hl': {
                    'fill': 'red', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'
                }
            }
        },
        {
            alias: vue.$t('book.themeNight'),
            name: 'Night',
            style: {
                body: {
                    'color': '#cecece',
                    'background': '#000000',
                    // 'padding-top': `${realPx(48)}px!important`,
                    // 'padding-bottom': `${realPx(48)}px!important`
                },
                img: {
                    'width': '100%'
                },
                '.epubjs-hl': {
                    'fill': 'red', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'
                }
            }
        }
    ]
}

setTime就是切换主题的方法了。不过有一点,我们必须注意,就是,这主题啊,是我们自己创建出来的,而不是电子书本身带有的,就像字体一样,需要电子书初始化时,加载
商业级web阅读器项目(上)_第22张图片

3.13.全局主题设置功能实现(这是一个难点,方法采用动态添加样式,方法很巧妙)

动态添加的方法如下所示

export function addCss(href) {
    const link = document.createElement('link');
    link.setAttribute('rel','stylesheet')
    link.setAttribute('type','text/css')
    link.setAttribute('href',href)
    document.getElementsByTagName('head')[0].appendChild(link);
}

商业级web阅读器项目(上)_第23张图片
通过这句就可以设置全局样式了,很巧妙,有没有
,里面那个地址是搭好的nginx服务器里的资源

接下来就要通过获取当前电子书的主题,来反应到全局主题

setGlobalTheme(theme) {
            // removeAllCss()
            switch (theme) {
                case 'Default':
                    addCss('http://localhost:8081/theme/theme_default.css')
                    break
                case 'Eye':
                    addCss('http://localhost:8081/theme/theme_eye.css')
                    break
                case 'Gold':
                    addCss('http://localhost:8081/theme/theme_gold.css')
                    break
                case 'Night':
                    addCss('http://localhost:8081/theme/theme_night.css')
                    break
                default:
                    this.setDefaultTheme('Default')
                    addCss('http://localhost:8081/theme/theme_default.css')
                    break
            }
        },

这真是一个漂亮的方法,
当是发现一个很大的问题
商业级web阅读器项目(上)_第24张图片
当我们频繁切换主题的时候,引入的链接会添加到dom树上,而我们需要的只有最后一条,所以非常有必要把之前的移除掉
因此,我们创建了一个方法,在每次切换全局主题之前,也就是引入link之前,先把之前的link删了

export function removeCss(href) {
    const links = document.getElementsByTagName('link');
    for (let i = links.length;i>=0;i--){
        const link = links[i];
        if(link && link.getAttribute('href')&&link.getAttribute('href')===href){
            link.parentNode.removeChild(link);
        }
    } 
}

export function removeAllCss() {
    removeCss('http://localhost:8081/theme/theme_default.css')
    removeCss('http://localhost:8081/theme/theme_eye.css')
    removeCss('http://localhost:8081/theme/theme_gold.css')
    removeCss('http://localhost:8081/theme/theme_night.css')

}

你可能感兴趣的:(全栈,前端,vue)