课程常用链接

【前奏-课程】快速入门Web阅读器开发

【小慕读书web端】Vue 实战商业级读书Web APP 全面提升技能

【epub图书免费下载站点 · 中文书】http://www.ziliaoh.com/epub.html

【项目代码gitee】https://gitee.com/yishen_yishen/vue-ebook 如果对你有帮助欢迎点个 star

阅读器原理课程-免费课

概览

知识点脑图

image-20200524211349799

阅读器工作原理

image-20200524211537414

环境搭建

vue-cli环境

  • 环境准备
iMac-Pro:code yishen$ node -v
v14.0.0
iMac-Pro:code yishen$ npm -v
6.14.5
iMac-Pro:code yishen$ vue -V
2.9.6
  • 离线版安装

    • GitHub下载webpack 下载到桌面,然后解压
    cd ~
    cd .vue-templates/
    cp -R ~/Desktop/webpack-develop webpack # -R 是将目录下所有文件复制到新目录webpack下
    cd ~/Desktop             # 项目路径,后面会在此路径下新建项目文件夹
    vue init webpack --offline ebook-read # 新建ebook-read项目,并初始化
  • 启动vue项目

npm run dev

sass支持

npm install node-sass sass-loader --save-dev

epubjs扩展

npm install epubjs --save

项目配置

viewport配置

  • viewport用来设置用户在手机上的可视区域
  • width=device-width:指定viewport宽度为设备宽度;inital-scale=1.0:指定默认缩放比例为1:1
  • 通过maximum-scale和minimun-scale限定屏幕缩放比例为1:1,通过user-scalable限制用户对屏幕进行缩放
  • 项目根目录Index.html文件,内部
<meta name="viewport" content="width=device-width,initial-scale=1.0,
    maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">

rem配置

  • rem是css3新增的一个相对长度单位

  • rem相当于根元素font-size值的倍数

    • 2rem = 根元素font-size *2
  • DOMCotentLoaded事件动态设置根元素font-size

    html.style.fontSize = window.innerWidth / 10 + 'px'
  • /src/App.vue文件,<script>内部

    document.addEventListener('DOMContentLoaded', () => {
      const html = document.querySelector('html')
      let fontSize = window.innerWidth / 10
      fontSize = fontSize > 50 ? 50 : fontSize
      html.style.fontSize = fontSize + 'px'
    })

reset.scss和global.scss

  • Reset.scss是为了消除不同浏览器默认样式的不一致性
  • Global.scss规定整个站点的公共样式,公共方法和公共参数
  • 实现px2rem方法,将px转化为rem
  • Reset.scss代码 参考链接
/* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
    display: block;
}
body {
    line-height: 1;
}
ol, ul {
    list-style: none;
}
blockquote, q {
    quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
    content: '';
    content: none;
}
table {
    border-collapse: collapse;
    border-spacing: 0;
}
html,body{
  width: 100%;
  height: 100%;
  font-family: 'PingFangSC-Light','PingFang SC','StheitiSC-Light',
    'Helvetica-Light','Arial','sans-serif';
}
  • global.scss代码
@import 'reset';

// 1rem = fontSize px
// 1px = (1 / fontSize)rem
$fontSize:37.5;

@function px2rem($px) {
  @return ($px / $fontSize)+rem;
}

@mixin center() {
  display: flex;
  justify-content: center;
  align-items: center;
}

web端小慕读书-付费课

环境搭建

Node.js环境

nvm工具的使用
  • nvm作用:node version manger,node版本管理工具

  • nvm github地址https://github.com/nvm-sh/nvm

  • nvm安装

    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
nvm常用命令
  • 换淘宝源
export NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node
  • 其他
nvm install node // 安装最新版nodejs
nvm install 10.10.0 // 安装指定版本
nvm use 10.10.0         // 切换到指定版本

Vue CLI 3.0环境搭建

  • 卸载老版本npm unistall vue-cli -g
  • 安装新版本 npm install -g @vue/cli
  • 原型开发 npm install -g@vue/cli-service-global
  • npm i -g @vue/cli-service-global
vue.config.js文件配置
module.exports = {
  publicPath: process.env.NODE_ENV === 'production'
    ? './'
    : '/'
}
Vue-remote-devtools调试工具

此工具与Chrome插件功能相同,一个是浏览器扩展,一个独立分离出来,可以不安装

GitHub地址

  • 安装 npm install -g @vue/devtools 添加 --verbose可以查看安装进度,以及请求地址

    npm 配置

Electron,安装上述工具时,可能会需要安装这个Electron,不过由于网络的原因,会下载失败。

修改 ~/.npmrc 文件,添加一行ELECTRON_MIRROR=”https://cdn.npm.taobao.org/dist/electron/"

  • 添加<script src="http://localhost:8098"></script> 到/public/index.html
    • 项目上线时,要删除掉这句话

epubjs扩展

npm i --save epubjs

sass扩展

npm i --save-dev node-sass sass-loader
sass报错:this.getResolve is not a function
  • 版本过高引起的,或其他低版本的不适用高版本sass

  • 可降低sass版本解决

    npm uninstall sass-loader
    npm i -D sass-loader@7.3.1

web字体引入

谷歌字体api

使用方法

image-20200529111040856

[三方汉化]谷歌字体api
  • 谷歌中文字体api(第三方汉化):地址

image-20200606140320118

font-family: 'Hanalei Fill', cursive;
font-family: 'Kirang Haerang', cursive;
font-family: 'Merriweather', serif;
font-family: 'MedievalSharp', cursive;
font-family: 'Ranga', cursive;

Nignx搭建静态服务器

Nginx.org

  • mac上安装Nignx需要先安装brew

    • /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
    • 一般来说会由于网络原因,安装失败(挂代理也不能使用:git默认不走代理,即使能正常访问GitHub,clone仓库时也非常慢,所以需要为git配置代理)

    • 国内下载方式/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"

    • 参考链接,知乎 , gitee

  • brew 安装nginx

brew install nginx
  • 运行nginxsudo nginx

    配置文件地址:/usr/local/etc/nginx/nginx.conf

  • 停止运行 sudo nginx -s stop

  • 重新加载 sudo nginx -s reload

外链配置

项目根目录新建.env.development文件和 .env.production 文件

VUE_APP_RES_URL=http://127.0.0.1:9001/
  • 外部引用举例

  • initGlobalStyle () {
          console.log(this.defaultTheme)
          addCss(`${process.env.VUE_APP_RES_URL}/themes/theme_eye.css`)
        },
nginx配置相关
  • 需将 autoindex on; 才可以访问目录

image-20200721231734769

nodejs环境接口搭建、相关知识点

  • 新建node-imooc-ebook文件夹,app.js入口文件

  • npm init初始化npm项目,生成package.json文件

  • 安装expressnpm i express -Sapi参考手册

  • 安装mysql操作库 npm i mysql -S

  • Nodemon 插件

    • 每次修改完文件,需要重新node app.js 才能执行

      Nodemon app.js 在项目保存后自动重新运行项目

  • cors-跨域问题

image-20200720160110196

什么是跨域:链接

npm i -S cors

app.js中

const cors = require('cors')
app.use(cors())

知识点

vue

vue引用中的@符号

地址中的@符
  • 当引用文件时import '@/assets/styles/global.scss'

  • @代表/src

    • 可以在 build/webpack.base.conf.js中设置

    image-20200525145712597

import前面的@符

script中的import是js的语法, 是在js中去引用css文件

style中的@import是stylus的语法,是在css中引用css文件

参考链接:详解vue中常用的几种import引入方式

备用链接

transition动画原理

  • 使用v-show动态显示或隐藏元素时,会触发过渡动画
  • transition需要指定name,并包裹一个包含v-show的div
  • vue会为transition包裹的div动态添加class,公6种

image-20200526111211545

transition
  • 代码实现

  • html部分。src/Ebook.vue,外围使用包裹,带上name属性,被包裹的部分要有vshow或者vif

    
      
          

* css 部分

  ~~~scss
  .slide-down-enter-to,
  .slide-down-leave {
    transform: translate3d(0, 0, 0);
  }
  .slide-down-enter-active,
  .slide-down-leave-active {
    transition: all 0.3s linear;
  }
  .slide-down-enter,
  .slide-down-leave-to {
    transform: translate3d(0, -100%, 0);
  }
transition-group

参考链接-vue.js

作为多个元素/组件的过渡效果。 渲染一个真实的 DOM 元素(通过tag指定)

<template>
  <div class="shelf-list">
    <transition-group
      name="list"
      tag="div"   // tag='div',这该元素渲染文div
      id="shelf-list"
    >
      <div
        class="shelf-list-item"
        v-for="(item, index) in shelfList"
        :key="index"
      >
        ......
      </div>
    </transition-group>
  </div>
</template>

image-20200709135236951

  • :key引发的bug

bug描述:动画没有效果,动画的类没有加到dom上面

// 原始代码,没有任何效果,dom上没有添加.list-move,.list-leave-active等类名
<transition-group
      name="list"
      tag="div"
      id="shelf-list"
    >
      <div
        class="shelf-list-item"
        v-for="(item, index) in shelfList"
        :key="index"
      >
        ......
      </div>
</transition-group>

// 修改后代码,效果正常
<transition-group
      name="list"
      tag="div"
      id="shelf-list"
    >
      <div
        class="shelf-list-item"
        v-for="(item) in shelfList"
        :key="item.id" // 修改了这里
      >
        ......
      </div>
</transition-group>

只是把:key由原先的index修改为了item.id,我也不知道什么原因

image-20200709175903436

image-20200709180117835

官网中关于key的说明:内部元素总是需要提供唯一的 key attribute 值 index不唯一吗?

搜到了一个比较靠谱的答案

交换位置后(对应到我的项目,就是删除某个),元素的key发生了变化

解决:给key值设置一个不会因为位置变化而变化的值

vue中的mixin混入

代码举例:

  • 定义混入对象
import { mapGetters } from 'vuex'
export const ebookMixin = {
  computed: {
    ...mapGetters(['fileName', 'menuVisible'])
  }
}
  • 使用混入对象

Vuex(仅代表个人理解)

概念
  • 一句话简介:解决组件非常多时,组件之间传参的问题(个人理解) 官方地址

  • 核心概念:state、mutations、getters、actions、module

  • State:相当于父组件中的props:{},定义一些子组件需要使用的变量(布尔、数组、对象、数值、字符串等)

    • props: {  
          isTitleAndMenuShow: {
            type: Boolean,
            default: false
          },
          fontSizeList: Array,
          defaultFontSize: Number,
          defaultTheme: Number,
          themesList: Array,
          // 图书是否加载完毕
          bookAvailable: Boolean,
          navigation: Object
        },
使用举例

src/store/modules/book.js

const book = {
  state: {
    fileName: '',
    menuVisible: false,
    // 底部的菜单项,倒数第二条,-1:不显示,0:字号,1:主题、2:进度条、3:目录
    settingVisible: -1,
  },
  mutations: {
    SET_FILENAME: (state, fileName) => {
      state.fileName = fileName
    },
    SET_MENU_VISIBLE: (state, visible) => {
      state.menuVisible = visible
    },
    SET_SETTING_VISIBLE: (state, visible) => {
      state.settingVisible = visible
    }
  }
}
export default book

src/store/getters.js

const getters = {
  fileName: state => state.book.fileName,
  menuVisible: state => state.book.menuVisible,
  settingVisible: state => state.book.settingVisible
}
export default getters

src/store/actions.js

const actions = {
  setFontFamilyVisible: ({ commit }, visible) => {
    return commit('SET_FONT_FAMILY_VISIBLE', visible)
  },
  setDefaultFontFamily: ({ commit }, font) => {
    return commit('SET_DEFAULT_FONT_FAMILY', font)
  },
  setDefaultFontSize: ({ commit }, fontSize) => {
    return commit('SET_DEFAULT_FONT_SIZE', fontSize)
  }
}
export default actions

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import book from './modules/book'
import getters from './getters'
import actions from './actions'

Vue.use(Vuex)
export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions,
  getters,
  modules: {
    book
  }
})
使用

使用了混入技术…mapGetters作用

src/utils/mixin.js

import { mapGetters, mapActions } from 'vuex'

export const ebookMixin = {
  computed: {
    ...mapGetters([
      'fileName',
      'menuVisible',
      'settingVisible'
    ])
  },
  methods: {
    // 下面的方法用来设置上面变量的值
    ...mapActions([
      'setMenuVisible',
      'setFileName',
      'setSettingVisible'
    ])
  }
}

具体页面内使用

vue-i18n实现国际化

vue-i18n说明文档

  • 安装插件
npm i --save vue-i18n
  • 初始化i18n对象

src/lang/index.js

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

Vue.use(VueI18N)

const messages = {
  en, cn
}
// 通过读取缓存获取,默认语言,缓存中无数据,默认设置为cn
let locale = getLocale()
if (!locale) {
  locale = 'cn'
  setLocale('locale', locale)
}

const i18n = new VueI18N({
  locale,
  messages,
  // 下面这项,是为了消除过多的警告
  silentFallbackWarn: true
})
export default i18n

silentFallbackWarn: true的作用

  • vue-html中解析对应文本
<span>{{$t('book.selectFont')}}</span>

image-20200608211409781

vue中alias别名的使用

<div class="setting-theme">
  <div
       class="setting-theme-item"
       v-for="(item, index) in themesList"
       :key="index"
       @click="setTheme(index)"
       >
          <div
            class="preview"
            :style="{background: item.style.body.background}"
            :class="{'no-border':item.style.body.background !== '#fff'}"
          ></div>
          <div
            class="text"
            :class="{'seleted':item.name===defaultTheme}"
          >{{item.alias}}</div>
  </div>
</div>
export function themeList (vue) {
  return [
    {
      alias: vue.$t('book.themeDefault'),
      name: 'Default',
      style: {
        body: {
          color: '#000', background: '#fff'
        }
      }
    }
  ]

Vue中的updated钩子

由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。无论是组件本身的数据变更,还是从父组件接收到的 props 或者从vuex里面拿到的数据有变更,都会触发虚拟 DOM重新渲染和打补丁,并在之后调用 updated。官网介绍

updateProgress(){
                this.$refs.progress.style.backgroundSize = `${this.progress}% 100%`;
            },

动态组件component


事件修饰符

.passive
<div
    class="scroll-wrapper"
    :class="{'no-scroll':ifNoScroll}"
    @scroll.passive="handleScroll()"
    ref="scrollWrapper"
  >
    <slot></slot>
  </div>
.stop,阻止点击事件向下冒泡
  • 代码描述 [src/components/shelf/ShelfItem.vue](# TODO)

  • 描述:避免上层的点击事件触发后,还会触发下层的点击事件

OnItemSelected触发之后不会在触发onItemClick

<template>
  <div
    class="shelf-item shelf-item-shadow"
    :class="{'hide-shadow':data.type===3}"
    @click="onItemClick"
  >
    <component
      :is="item"
      :data="data"
    ></component>
    <div
      class="shelf-item-selected"
      v-show="isEditMode && data.type ===1"
      @click.stop="OnItemSelected"
    >
      <span class="iconfont iconselected"></span>
    </div>
  </div>
</template>

插槽slot标签

单插槽
<template>
  <div
    class="scroll-wrapper"
    :class="{'no-scroll':ifNoScroll}"
    @scroll.passive="handleScroll"
    ref="scrollWrapper"
  >
    <!-- 预留插槽,用来替换传递过来的内容 -->
    <slot></slot>
  </div>
</template>

参考链接-官网插槽最基础深入了解插槽

<template>
    <Scroll
      class="slide-search-list"
      :top="66"
      :bottom="49"
      v-show="searchVisible"
    >
      <!-- 这中间的内容都会被子组件中<slot>标签替换 -->
      <div>
        我是内容
      </div>
    </Scroll>
</template>
多插槽
<div class="dialog-wrapper">
  <div class="dialog-title-wrapper">
    <span class="dialog-title-text">{{title}}</span>
  </div>
  <slot>
    <!-- 插槽一 -->
  </slot>
  <div class="dialog-btn-wrapper">
    <slot name="btn">
      <!-- 插槽二,中间有默认内容,父组件不传递内容,就使用默认内容 -->
      <div
           class="dialog-btn"
           @click="hide"
           >{{$t('shelf.cancel')}}</div>
      <div class="dialog-btn">{{$t('shelf.confirm')}}</div>
    </slot>
  </div>
</div>
<ebook-dialog
    :title="title"
    ref="dialog"
  >
    <!-- 这里是插槽一的内容,省略了,没粘进来 -->
      <!-- 下方div有slot="btn"与子组件<slot name="btn">匹配 -->
    <div
      slot="btn"
      class="group-dialog-btn-wrapper"
    >
      ......
    </div>
</ebook-dialog>

ref指向性问题

// 指向dom元素
<div ref="div"></div>

// 指向组件对象
<Button ref="btn"></Button>

// this.refs.btnList是一个列表,列表里的每一项是一个组件对象
<Button v-for="xx in xx" ref="btnList">

当指向dom元素时获取样式:this.$refs.scroll.style.height

当执行组件对象,获取样式:this.$refs.scroll.$el.style.height

参考链接

$nextTick

  • 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
  • 我理解的太浅,不懂vue的dom更新,以及响应式序列啥的

代码示例 src/components/home/SearchBar.vue

showHotSearch () {
      if (this.hotSearchOffsetY === 0) {
        this.hideShadow()
      } else if (this.hotSearchOffsetY > 0) {
        this.showShadow()
      }
      this.hideTitle()
      this.hotSearchVisible = true
      // TODO 重置阅读进度
      this.$nextTick(() => {
        this.$refs.hotSearch.reset()
      })
    }

参考链接

代码示例 src/views/store/StoreShelf.vue

watch: {
    isEditMode (isEditMode) {
      this.scrollBottom = isEditMode ? 48 : 0
      this.$nextTick(() => {
        // 等所有dom结构更新完后,在计算scroll的高度
        this.$refs.scroll.refresh()
      })
    }
  },

vue-create-api

  • 一个能够让Vue组件通过API方式调用的插件
    • 可以大幅度降低引用组件的代码
    • 传统父组件使用子组件,需要js中引入,components中声明,html中写入DOM
    • 使用此插件,可以很简单的简化
    • 此插件是在body下创建dom,与vue在#app下不同,通常全屏的消息提示框,选择框使用它
    • image-20200705142751560
  • GitHub文档地址
  • 安装 npm i -S vue-create-api
  • 代码演示
    • 初始化、create-api.js(mian.js中需要引入这个文件)
import CreateAPI from 'vue-create-api'
import Vue from 'vue'
import Toast from '../components/common/Toast.vue'

Vue.use(CreateAPI)
Vue.createAPI(Toast, true)
// 其他组件的methods中使用
this.$createToast({
  $props: {
    // 这里就相当于Toase组件中的props
    text: 'hello imooc'
  }
}).show()
  • 更一步的简化

create-api.js文件

// 新增混入全局方法
Vue.mixin({
  methods: {
    toast (settings) {
      return this.$createToast({
        $props: settings
      })
    }
  }
})
  • 使用
this.toast({ text: 'hello' }).show()

v-for和v-if一起使用

可能会报错:The ‘undefined’ variable inside ‘v-for’ directive should be replaced with a computed property that returns filtered array instead. You should not mix ‘v-for’ with ‘v-if’

原因:v-for的优先级会高于v-if,因此v-if会重复运行在每个v-for中

  • 解决方法,新加一个外层(