[yishen] 小慕读书web端学习笔记

本文档记录了使用Vue.js、epub.js和Node.js开发Web阅读器的过程,包括环境搭建、项目配置、样式处理、epubjs原理及应用、Vue组件和插件的使用,以及项目中遇到的问题和解决方案。详细介绍了Vue CLI、Vue-i18n、Vuex、sass和nvm等工具的使用,同时涵盖了字体引入、Nginx服务器配置和跨域问题的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

课程常用链接

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

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

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

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

【md格式 源文档下载】下载链接 在线阅读可能会乱码(我知道是编码问题,但在七牛云里不知道怎么修改),请下载后查看

阅读器原理课程-免费课

概览

知识点脑图

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

    <transition name="slide-down">
      <!-- 下面就是要展示动画的部分 -->
          <div
            class="title-wrapper"
            v-show="isTitleAndMenuShow"
          >
            <div class="left">
              <span class="iconfont iconbackarrow"></span>
            </div>
            <div class="right">
              <div class="icon-wrapper">
                <span class="iconfont iconbooks"></span>
              </div>
              <div class="icon-wrapper">
                <span class="iconfont iconiconfontcart-copy"></span>
              </div>
              <div class="icon-wrapper">
                <span class="iconfont iconi-more"></span>
              </div>
            </div>
          </div>
        </transition>
    
  
* 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'])
  }
}

  • 使用混入对象
<script>
import { ebookMixin } from '../../utils/mixin'
export default {
  mixins: [ebookMixin]
}
</script>
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'
    ])
  }
}

具体页面内使用

<script>
import { ebookMixin } from '../../utils/mixin'
export default {
  methods: {
    hideTitleAndMenu () {
      this.setSettingVisible(-1)	// 调用setSettingVisible方法将settingVisible的值修改为-1
      this.setMenuVisible(false)	// 可以直接this.调用
    }
  }
}
</script>
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
<template>
  <div>
    <component :is="currentTab === 1 ? content : bookmart"></component>
  </div>
</template>
<script>
data () {
    return {
      currentTab: 1, // 通过改变currentTab的值,就能分别渲染、切换不同组件
      content: EbookSlideContents, // 这是组件,记得引入,这里省略引入
      bookmart: EbookSlideBookMark
    }
  },
</script>
事件修饰符
.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. r e f s . s c r o l l . refs.scroll. 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)
<script>
// 省略了很多无关代码
export default {
  // 必须有name,否则create-api无法使用此组件
  name: 'Toast',
  props: {
    text: [String, Number],
    timeout: {
      type: Number,
      default: 1500
    }
  },
  methods:{
    show(){
      this.visible = true
    },
    hide(){
      this.visible = false
    }
  }
}
</script>
// 其他组件的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中

  • 解决方法,新加一个外层()使用vfor或者vif

参考链接-vue.js

<template v-for="(item, index) in categoryList">
  <div
       class="dialog-list-item"
       :class="{'is-add': item.edit  ? item.edit === 1 : false}"
       :key="index"
       @click="onGroupClick(item)"
       v-if="(item.edit === 2 && isInGroup) || item.edit !== 2 || !item.edit"
       >
  </div>
</template>

样式问题以及scss

样式
绝对定位子元素居中父元素
  • 父元素css
position: relative;
  • 子元素css
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);  // 向左向上偏移自身的50%
超出部分用省略号代替
// 超出的部分用省略号...代替
text-overflow: ellipsis;
// 超出的部分隐藏
overflow: hidden;
// 不换行
white-space: nowrap;
显示两行,多余省略号
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
white-space: normal;
text-overflow: ellipsis;
// 允许打断单词进行换行
word-break: break-all;
// 不允许打断单词,进行换行
word-break: keep-all;
Position:absolute的其他用法

标题起的可能不合适

image-20200628180122278

  • 还可以做居中效果

    @mixin absCenter {
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      margin: auto;
    }
    
  • 效果演示

image-20200628210815624

scss语法
@mixin:混入
  • 定义
// 定义一段垂直居中,水平居中的代码段
@mixin center() {
  display: flex;
  justify-content: center;
  align-items: center;
}
// 混入允许带参数
@mixin ellipsis2($line) {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  // 要显示多少行
  -webkit-line-clamp: $line;
  white-space: normal;
  text-overflow: ellipsis;
  // 允许打断单词进行换行
  word-break: break-all;
}
  • 使用
// 是left类,垂直居中,水平居中
.left{
  @include center;
}
.text{
  // 带参数的形式
  @include ellipsis2(2);
}
&符号
  • 作用:引用父元素
.dashboard {
  &-container {
    margin: 30px;
  }
  &-text {
    font-size: 30px;
    line-height: 46px;
  }
}
  • 编译为css
.dashboard-container {
  margin: 30px;
}
.dashboard-text {
  font-size: 300px;
  line-height: 46px;
}
animation

动画属性,将动画绑定到一个div元素,css提供的属性,非scss

代码演示src/components/home/FlapCard.vue

.flap-card-bg {
  	// scale:缩放
    transform: scale(0);
    opacity: 0;
    &.animation {
      // both 是动画完成后,保持在100%的效果(替代上面两行)
      animation: flap-card-move 0.3s ease-in both;
    }
    // 刚开始进入翻转加载的动画效果
    @keyframes flap-card-move {
      0% {
        transform: scale(0);
        opacity: 0;
      }
      50% {
        transform: scale(1.2);
        opacity: 1;
      }
      70% {
        transform: scale(0.9);
        opacity: 1;
      }
      100% {
        transform: scale(1);
        opacity: 1;
      }
    }

epubjs

epubjs原理及常用方法
原理图

image-20200525200847273

常用方法

api地址(网站比较慢,开全局代理)

this.rendition.prev()				// 后退翻页
this.rendition.next()				// 下一页
this.rendition.themes.fontSize(16px)	// 设置字号

// 主题
this.themeList.forEach(theme => {
    // 注册主题
    this.rendition.themes.register(theme.name, theme.style)
  })
this.rendition.themes.select(theme.name)			// 选择主题

项目中的问题

HTML5、input实现进度条效果
  • 成品展示

image-20200528093323806

  • 代码实现
<input
            class="progress"
            type="range"
            max="100"
            min="0"
            step="1"
            @change="onProgressChange($event.target.value)"
            @input="onProgressChange($event.target.value)"
            :value="progress"
            :disabled="!bookAvailable"
            ref="progress"
          >
.progress {
        width: 100%;
        // 清除默认样式
        -webkit-appearance: none;
        height: px2rem(2);
        background: -webkit-linear-gradient(#53575d, #53575d) no-repeat, #abacae;
  			// 下面代表已读 总的。前45%background:#53575d,其余#abacae
        background-size: 45% 100%;
        &:focus {
          outline: none;
        }
        &::-webkit-slider-thumb {
          // 滑块的样式
          -webkit-appearance: none;
          height: px2rem(15);
          width: px2rem(15);
          border-radius: 50%;
          background: #fff;
          box-shadow: 0 4px 4px 0 rgba($color: #000000, $alpha: 0.15);
          border: px2rem(1) solid #ddd;
        }
      }
项目中设置字体
  • 引入选好的字体API api地址 (此api是英文字体,对中文不起作用,没找到中文字体的Api)

由于epub解析图书,是依赖于iframe的,外层的字体文件无法直接传递到内层的iframe(大概是这个意思,不太懂),所以需要使用epub提供的一个register()方法,来使内层iframe获取到字体样式

src/components/ebook/EbookReader.vue

this.rendition.hooks.content.register(contents => {
        Promise.all([
          contents.addStylesheet(
            'https://fonts.font.im/css?family=Hanalei+Fill|Kirang+Haerang|Merriweather|MedievalSharp|Ranga')
        ]).then(() => {
          // console.log('字体全部加载完毕。。。')
        })
      })

src/components/ebook/EbookSettingFontPopup.vue

  • 调用themes下的font()方法设置字体
setFontFamily (font) {
  		// font 是字体名称
      this.setDefaultFontFamily(font)
      this.currentBook.rendition.themes.font(font)
    }
缓存问题,相当于小程序中的storage
  • 安装包
npm i --save web-storage-cache
  • 使用,封装常用函数
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 (key) {
  return localStorage.deleteAllExpires(key)
}
  • 可在以下位置查看缓存,可以手动修改

image-20200611102843226

cssText设置属性!important
  • 说明:需要为一个属性设置!important

src/components/ebook/EbookSettingProgress.vue

updateProgressBg () {
      // 背景色变化,样式已经写好,更改background-size即可
      // ASK 标记,浪费了老子贼多时间,搞这个问题,不知道哪里冲突不加!important就是不行
      this.$refs.progress.style.cssText = `background-size:${this.progress}% 100% !important;`
      // this.$refs.progress.style.backgroundSize = `${this.progress}% 100%`
    },

常用js操作css方法

// 方法一
var obj = document.getElementById('no');
function setStyle(obj, css) {
for(var attr in obj){
obj.style[attr] = css[attr];
}
}
setStyle(obj,{width:"400px",height:"300px"});
// 方法二,个人感觉此方法简单,还可以设置!important,方法一不知道可不可以
var obj = document.getElementById('no');
obj.style.cssText = "width:400px; height:300px;";
font-size=0消除空行
.slide-contents-book-progress {
  font-size: 0;
  .progress {
    font-size: px2rem(14);
  }
  .progress-text {
    font-size: px2rem(12);
  }
}

image-20200612221721493

其他知识点

blob链接

业务代码:src/components/ebook/EbookReader.vue

// 解析电子书内容,获取封面,标题,作者等信息
parseBook () {
  // 获取cover
  this.book.loaded.cover.then(cover => {
    this.book.archive.createUrl(cover).then(url => {
      // url----blob:http://localhost:8080/ecf9934c-1313-4b3d-9647-caa7acafb152
      this.setCover(url)
    })
  })
}
js语法二维数组变一维数组.concat.apply用法
// .concat()用法:连接两个数组
a=[1,2,3,4,5]
b=[2,3,4,5,6,7]
[].concat(a,b)     --> [1, 2, 3, 4, 5, 2, 3, 4, 5, 6, 7]
// .apply()用法,使用apply将他们([],c)作为形参传入,将数组中的对象逐一的传入到concat中(老师原话,不太理解)
c=[[1,2,3],[9,9,9]]
[].concat.apply([],c)   -->[1, 2, 3, 9, 9, 9]

apply()用法:能改变this的指向(个人理解),参考链接-w3school菜鸟教程

数组常用方法
filter()

创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素;
不会对空数组进行检测;
不会改变原始数组;

项目实例:src/components/ebook/EbookBookmark.vue

removeBookmark () {
      console.log('删除书签')
      const currentLocation = this.currentBook.rendition.currentLocation()
      const cfi = currentLocation.start.cfi
      this.bookmark = getBookmark(this.fileName)
      if (this.bookmark) {
        // 保留缓存中,cfi项与要删除的cfi不相同的----> 删除与cif相同的
        saveBookmark(this.fileName, this.bookmark.filter(item => item.cfi !== cfi))
      }
    }

参考链接 菜鸟教程

Array.some()

检测数组中国的元素是否满足指定条件

有一个满足条件的元素,即返回true,剩余的元素不在执行检测

Array.every()

every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。

every() 方法使用指定函数检测数组中的所有元素:

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true。
Array.map()

map()方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值

map()方法按照原始数组元素顺序依次处理元素

注意: map() 不会对空数组进行检测。

注意: map() 不会改变原始数组

mock.js–生成随机数据,拦截Ajax请求

官网地址

  • 安装npm i mockjs --D/--save-dev,安装axios-发送网络请求用的npm - axios --save

indexDB

image-20200706173841117

localforage
  • 安装 npm i -S localforage
  • 对indexDB进行操作的一个库

项目构建、发布

修改到线上地址

根目录的.env.production文件是线上版本的一些接口路径

​ .env.development 是开发版本的一些接口路径

.env.production文件内容

# nginx跟地址,不知道能不能加注释,反正没报错
VUE_APP_RES_URL=http://127.0.0.1:9001
# nginxepub目录
# VUE_APP_EPUB_URL=http://127.0.0.1:9001/epub
VUE_APP_EPUB_URL=http://47.99.166.157/epub
# api请求的根地址
VUE_APP_BASE_URL=http://localhost:8080
# 线上的api接口地址(老师的)
VUE_APP_BOOK_URL=http://47.99.166.157:3000
# 所有的电子书,相当于nginx的资源目录
VUE_APP_EPUB_OPF_URL=http://47.99.166.157/epub2
# 老师的封装好的讯飞文字转语音api地址
VUE_APP_VOICE_URL=http://47.99.166.157:3000
构建生产版本

npm run build

构建过程中警告处理
文件大小超出

image-20200713075757240

// vue.config.js文件内,与devServer同级,扩大资源限制到512 * 1024 (根据自己的项目来)
configureWebpack: {
    performance: {
      hints: 'warning',
      maxAssetSize: 524288,
      maxEntrypointSize: 524288
    }
  }
  • 将引用的一些库,替换成线上cdn版本

image-20200713082902625

例如:将本项目中占用资源最大的epubjs换成线上cdn版本(引用的版本应与本地版本一致,不要盲目最新版)

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>

添加到public/index.html的内部

console.log警告
  • 少的化,搜索全部注释或者删除
  • 多的化,https://blog.youkuaiyun.com/u013611033/article/details/104152300

需要注意的地方

驼峰命名&’-'命名

vue html部分,类名尽量使用"-",不要使用驼峰命名法(好像是因为不识别大小写的原因)

在有些地方,驼峰和’-'好像是一样的,例如fileName 和 file-name是表示同一个东西;

css中font-size、js操作css属性可以写作fontSize;

项目中各组件z-index

read阅读器页面
  • #slide-content-wrapper,目录的侧边栏300
  • #title-wrapper,上面的菜单栏201
  • #menu-wrapper,下面的菜单栏201
    • EbookSettingProgress中的#setting-wrapper,阅读进度200
    • EbookSettingTheme中的#setting-wrapper,设置主题200
    • EbookSettingFont中的#setting-wrapper,设置字体字号200
  • #ebook-reader-mask,主页的蒙版(用来触发点击事件)150
StoreHome页面
  • searchBar组件

    • .search-bar ,标题行,和输入框行 z-index:150
    • .title-icon-back-wrapper ,左上方的返回图标,要大于.search-bar。z-index:200
  • FlapCard组件

    • .flap-card-wrapper,用来显示推荐图书(伴有动画),浮在最上层:z-index:1000
    • class=“flap-card”,展示动画变化用的,是动态变化的,范围是100-96
      • image-20200628212204009
    • .read-btn,推荐图书的立即阅读按钮,要在.flap-card-wrapper(1000)之上,zIndex:1100
StoreShelf页面
  • shelfTitle组件

    • .shelf-title,zindex-130,要在当前页面的最上层
  • 主页面内

    • .store-shelf-scroll-wrapper,zindex:101
  • shelfFooter组件

    • .shelf-footer,编辑状态下,下面的菜单栏,zindex:120
    • .popup,点击下方的tab弹出的菜单框,zindex:2080

git的使用

首次使用,配置公钥。。。
  • 留白,以后补充 //TODO
初始化,首次push
git init
touch README.md
git add README.md
git commit -m "提交显示的信息"
git remote add origin git地址
git push -u origin master
第二次push
git add .
git commit "第二次push"
git push
git表状态字符
字符状态

A: 工作区新增的文件

C: 文件的一个新拷贝

D: 你本地删除的文件,服务器上还在

M: 文件的内容或者mode被修改

R: 文件名被修改了

T: 文件的类型被修改了

U: 文件没有被合并,需要完成合并才能进行提交

X: 未知状态

颜色状态

绿色:新增文件

黑色:别删除文件

蓝色:之前已经存在,被修改文件

红色:新增,但是没有增加到git中

为git设置代理
  • 修改 ~/.gitconfig 文件

image-20200602215734179

  • 注意:ssr走的是socks5

参考链接 参考链接

已知的BUG&优化

已知的BUG

  • 当使用主题时,章节翻页时,会闪一下白色(老师的也会闪一下,但底色与主题颜色相同-背景色修改为了灰色不是白色不那么显眼-算是解决了吧)
  • 点击屏幕中央,呼出上下菜单栏,会出现滚动条(老师的不会)(猜测:动画过渡导致页面宽高发生变化)–已解决

image-20200526223235946

// 解决办法,隐藏进度条
html::-webkit-scrollbar{
  width:0px
}
  • 图书主题切换,短时间切换过多次,会没反应(主题已经调过来了,前端没有渲染)
  • 切换不同的菜单选项(下面的)没有过度动画**—已解决**
  • 点击目录蒙版区域隐藏目录,下面的菜单条没有被隐藏**—已解决**
  • 目录跳转有问题**—已解决**
  • 切换字体,有时会没有效果
  • 清完缓存,第一次进入,字体大小会没效果(缓存中有)

未命名4

​ 原因:第一次初始化,这两个值没有定义(刷新一下就有了,定位不到出错位置)

image-20200608123139318

  • 上下章,能跳转的比目录上的多,有的图书会导致章节的选中效果选择了错误的章节**—严重性BUG**
    • 应该验证章节跳转的逻辑,是依据什么进行跳转的(猜测是section的值有问题)
    • this.navigation有问题

image-20200614083329248

  • Scroll组件没有滚动条,当内容有很多屏时,不知道滑到哪里了(对 目录非常多的书 不友好)

    • 理想效果:高度小于两屏时,隐藏滚动条,高于两屏时显示滚动条
    • src/components/common/Scroll.vue----webkit-scrollbar
  • 高度异常:很多地方都高度异常,可能是scroll组件问题 http://localhost:8080/#/store/home,滚动部分的高度没有变化,初试时,mounted中调用了refresh重新计算高度,没有问题,后面高度没有变化了

image-20200627205914017

  • toast组件还是有问题,当切换页面时,toast不会自动消失

    • 例如:删除分组时
  • store/detail页面底部加入书架:已解决

    假设一本书已经在书架中,进入图书详情页detail底部的加入书架会变成已加入书架

    初次进入底部的加入书架,会变成 已加入书架,但刷新一下,就变成了 加入书架

    computed中inBookShelf变量的问题

    解决方法:刷新进入bookdatail页面是,shelfList中值为空,重新获取即可

可以优化的地方

  • http://localhost:8080/#/store/shelf页面

    • image-20200711110128311

    • pc端可以监听esc按键,调用取消的方法,移动端暂时不知道

校友系统不只是一套软件系统,而是一整套“互联网+校友”的解决方案。校友系统通过帮助院校搭建校友互动平台和校友管理系统,拓展院校在校友服务方面的效率和范围,帮助院校提升校友工作信息化水平。 设计任务说明 按软件工程规范描述管理员需求,细化用例规约,合理设计数据库,实现以下功能: 后台管理模块功能 校友数据管理 校友增删改 校友快速搜索、高级搜索 校友分组的增删改 为校友重置登陆密码 提供以下10项校友统计方式:毕业年份、教育阶段、年龄、性别、民族、籍贯、政治面貌、单位性质、行业、所在省份。 校友活动管理 发起活动 修改、删除活动 管理活动报名情况(增删) 导出活动报名表 审核校友发起的活动 新闻、公告管理 新闻分类管理(增删改、排序) 新闻管理(增删改、查询) 公告管理(增删改、查询) 校友资料管理 校友文档分类管理(增删改、排序) 校友文档管理(增删改、查询) 校友相册管理(增删改、排序) 校友视频分类管理(增删查改、排序) 所有资料均可设置查看权限 所有类型资料均支持校友上传,后台审核 前台模块功能 网站首页 能够显示出最新网站通知公告、校友新闻和学校活动,双击每个新闻通告的标题,可看到新闻内容,点击“更多”则进入该类的标题列表项。 显示 Flash,可切换图片新闻,点击进入新闻内容。 显示友情链接、网站帮助版权、信息和校庆专栏。 校友新闻 显示最新若干条校友新闻标题,点击后可进入详细内容,点击“更多”将显示该标题下的所有新闻列表。 校友风采 显示知名校友的姓名、简介和照片,点击“详细介绍”可了解该校友的详细情况。 校友相册:对于每个注册成功的校友,可显示其名称、创建和更新日期和相册简介等。对于相册个人登录的情形,可进行新建和修改操作;查看他人相册,也可以发表评论。 校友捐赠 校友捐赠:显示最新若干条捐赠情况记录,点击“更多”进入所有列表,最新的若干条捐赠项目显示出来。向下滚动显示最新捐赠的捐赠校友的姓名、金额、捐赠日期等,并显示部分捐赠图片。 校友服务 学院简介及一些校内的相关部门链接。 用户注册和登陆 在某一页面左侧栏点击“登录校友网“即可进入登录页面,输入用户名、密码和验证码后点击“登录”,若信息正确则显示“登录成功”,同时异步更新页面的个人信息,显示个人头像、姓名、控制面板等信息;若看不清楚验证码,可点击验证码更换图片)。 调查问卷功能: 投票:阅读并填写调查问卷,点击投票按钮将投票信息上传至后台。 查看结果:查看所有参与本次问卷调查校友的投票数据统计结果。 站内搜索功能 项目介绍 本项目使用 Java 语言编写,后框架 Spring + Springmvc + Hibernate ,前框架 Jquery + Bootstrap ,数据库 mysql 未完待续……
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值