一 : 简介与说明
1.1 简介
这是小编写的第一款全栈程序,可能有些许不足,还望海涵. 写这款程序的主要意图是使自己能更加熟悉以及掌握vue,node,等相关的知识以及发现自己的不足并改进.这篇文章将会从爬虫说起,到后端的简介以及重点讲解前端.
项目与开源至github 地址:
下面是阅读界面的动图.
1.2项目的基本要求
会使用vue,node,mysql,html,css3,vant,并对其有一定的基本认识.
1.3 核心插件
epubjs:对epub格式小说的解析
axios:发起网络请求
puppeteer:谷歌开源的无头浏览器,可用来做测试与爬虫
cheerio:对html内容进行解析
stylus: 更加方便书写css样式
vant:vue移动端ui框架
better-scroll : 移动端实现流畅的滚动
koa2:开源node框架
koa2-cors: 实现跨域,
mysql : 连接mysql数据库
1.4项目的基本配置
准备三个文件夹分别对应爬虫,node后端,vue前端,并在对应的文件夹下安装好相应的插件
二: 爬虫
目标网站:sobooks.cc/
首先我们先引入模块
let puppeteer = require("puppeteer")
let $ = require('cheerio')
let url = require('url')
let write = require('../06demo-fs/index.js')
声明目标地址变量
let httpurl = "https://sobooks.cc/shenghuoshishang"
这里用sobooks网站的一个生活时尚分类作为模块,其他模块只要修改一下地址就行
无头浏览器的配置
let brower = await puppeteer.launch({
headless: true
})
headless为true则代表不打开浏览器,false则打开
async function getAllnum() {
let page = await brower.newPage() //打开一个新的页面
await page.setRequestInterception(true) //拦截打开
await page.on('request', Interception => {
let urlobj = url.parse(Interception.url())
if (urlobj.hostname == 'googleads.g.doubleclick.net') {
Interception.abort() // 对谷歌广告进行拦截,提高爬取速率
} else {
Interception.continue()
}
})
await page.goto(httpurl) //前往目标地址
let pageNum = await page.$eval('.pagination li:last-child span', element => {
let text = element.innerHTML.substring(1, element.innerHTML.length - 2).trim()
return text
}) // 使用page的$eval方法选中指定元素,并对其操作,获得总页数
page.close()
return pageNum;
}
创建一个读取总页数的函数,代码具体作用请观看注释
async function pageList(num) {
let pagelisturl = await "https://sobooks.cc/shenghuoshishang/page/" + num
let page = await brower.newPage()
await page.setRequestInterception(true)
await page.on('request', Interception => {
let urlobj = url.parse(Interception.url())
if (urlobj.hostname == 'googleads.g.doubleclick.net') {
Interception.abort()
} else {
Interception.continue()
}
})
await page.goto(pagelisturl)
let arrpage = await page.$$eval(".card .card-item .thumb-img>a", elements => {
let arr = []
elements.forEach((item, i) => {
let href = item.getAttribute('href')
arr.push(href)
})
return arr
})
// console.log(arrpage);
page.close()
return arrpage
}
创建一个函数获取当前页面的所有href,前面的函数获取到总页数,对所有页数进行遍历即可得到每项的href,然后我们可以通过这个href去往小说详情页面,然后对其进行爬取
注:?eval与$eval的区别:
相当于?eval 选中所有符合要求的元素,$eval 只选中一个
后面操作类似,无非就是选中元素,并对所选元素进行加工处理,然后通过node 的write模块将其写入到本地json文件
github源码地址 github.com/1131446340a…
代码写入json的时候为了方便,将json内容格式写入为'{},{},'请人为改成[{},{}]这种格式
json文件如图:
其中urls 关键字为下载地址的关键网址
然后再对下载网址进行爬取,github源码地址 github.com/1131446340a…
其中对爬取小说做了拦截,比如下载需时超过10分钟的就拦截了.基本没什么难度,通过流式下载即可.
最后将json文件通过mysql存入数据库就行.
三 : 后端接口
首先先连接mysql数据库
var mysql = require('mysql')
var config = require('./defaultConfig')
// 创建线程池
var pool = mysql.createPool({
host: config.database.HOST,
user: config.database.USERNAME,
password: config.database.PASSWORD,
database: config.database.DATABASE,
port: config.database.PORT
})
// 统一连接数据库的方法
let allServies = {
query: function (sql, values) {
return new Promise((resolve, reject) => {
pool.getConnection(function (err, connection) {
if (err) {
reject(err)
} else {
connection.query(sql, values, (err, rows) => {
if (err) {
reject(err)
} else {
resolve(rows)
}
connection.release()
})
}
})
})
}
}
然后在该文件下写入mysql语句,部分如下
其次解决koa2跨域问题,只要在app.js设置一下代码即可var cors = require('koa2-cors');
var app = new Koa();
app.use(cors());
然后在routes下建立一个book的js文件
const router = require('koa-router')() 实例化koa路由
const booksServies = require('../controllers/mySqlConfig')引入mysql
router.prefix('/books') //设置路由前缀
最后记得在APP.js中加上这段代码
app.use(book.routes(), book.allowedMethods())
然后通过router.get或者post方法定义后端接口即可.
形式如下
router.post('/bookinfo', async (ctx, next) => {
let bookid = await ctx.request.body.params.bookid
await booksServies.querybookinfo(bookid).then(res => {
ctx.body = {
bookinfo: res
}
})
})
其中ctx.request.body.params.bookid为前端发送参数
ctx.body内容为向前端展示的数据
做好以上操作,前端只需输入 http://localhost:3001/books/bookinfo 即可获取book的详情,当然,要携带参数bookid 注:其中端口号3001由自己在koa中设置
其他接口大多类似,不做详解
四 : vue前端
4.1 axios 的配置
import Vue from 'vue'
import axios from 'axios'
const vue = new Vue
Vue.config.devtools = true
axios.defaults.timeout = 5000; // 默认5s超时
// axios.defaults.baseURL = 'http://localhost:3000';
let urls = "http://localhost:3001/books"
Vue.prototype.$http = axios
// Vue.config.productionTip = false
function fetch(methods,url, param) {
return new Promise((resolve, reject) => {
axios[methods](urls + url, {
params: param
})
.then(response => {
resolve(response.data)
}, err => {
vue.$notify("网络出错或链接过期");
reject(err)
})
.catch((error => {
// console.log(this);
reject(error)
}))
})
}
首先封装一个fetch方法
export function fetchGet(url,param){
return fetch('get',url,param)
}
export function fetchPost(url,param){
return fetch('post',url,param)
}
然后再拆分成get,和post两个方法,方便调用.
export function booksrore(fn) {
return fetchGet("/bookstore").then(fn)
}
然后导出,这样其他页面只要引入然后直接booksrore(fn)就能调用,其中fn为回调函数
4.2路由的配置
首先引入
import Vue from 'vue'
import Router from 'vue-router'
然后引入组件
import index from '@/components/index'
import bookinfo from '@/components/common/bookinfo'
import reader from '@/components/common/reader'
import search from '@/components/search/search'
import my from '@/components/my/my'
import login from '@/components/my/login'
import zhuche from '@/components/my/zhuche'
import book from '@/components/book/book'
import bookstore from '@/components/bookstore/bookstore'
import readerHis from '@/components/book/readerHis'
import morebook from '@/components/bookstore/morebook'
import recommed from '@/components/recommed/recommed'
再使用路由
Vue.use(Router)
最后导出路由即可
{
path: '/',
name: 'index',
component: index,
children: [
{
path: '/', // 子路由重定向
redirect: 'bookstore'
},
{
path: 'bookstore',
name: 'bookstore',
component: bookstore,
meta:{
requireAuth:true,
keepAlive:true,
isBack:true
}
},
{
path: '/recommed',
name: 'recommed',
component: recommed,
meta:{
requireAuth:true,
keepAlive:true,
isBack:true
}
},
{
path: 'my',
name: 'my',
component: my,
meta:{
requireAuth:true,
keepAlive:true,
isBack:true
}
},
{
path: 'book',
name: 'book',
component: book,
meta:{
requireAuth:true,
keepAlive:true,
isBack:true
}
},
],
meta:{
requireAuth:true,
keepAlive:true,
isBack:true
}
},
其中path为路由路径, component为引入的组件 ,children为子路由,meta为路由参数.keepAlive为一个参数,主要用来控制路由前进刷新页面,后退不刷新页面的控制条件.默认为true.
在app.vue下配置
<keep-alive >
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
用keep-alive组件控制router活性,被keep-alive包裹的路由是不会刷新页面的,如果让某页面刷新在路由跳转的时候调用生命周期beforeRouteLeave方法即可.调用如下
beforeRouteLeave(to, from, next) {
if(to.path=="/reader") {to.meta.keepAlive = false;
}
else{to.meta.keepAlive = true;}
next();
},
其中to,为要去的页面,from为当前页面,next为跳转函数.
例如上述代码代表的意思就是如果前往的路由接口是reader,则刷新页面,否则则保持活性( 因为这个页面只会前往reader路由,其他路由为后退路由,则做到了前进刷新页面,后退不刷新页面 )
4.3 index页面的搭建
效果图如下
顶部一个搜索框,底部一个导航栏,中间是四个二级路由分别代表的是书城,书架,精选,我的,四个页面
其中顶部的搜索框使用vant ui框架(vant的使用请观看vant官网)
中间部分稍后详说.
底部navbar代码如下
<template>
<div class="main">
<van-tabbar active-color="#000">
<van-tabbar-item to="/bookstore">
<div class="icon">
<i
class="iconfont icon-shucheng fontcolor"
:class="{fontcoloractive:Route==='bookstore'}"
></i>
</div>
<div :class="{fontcoloractive:Route==='bookstore'}">书城</div>
</van-tabbar-item>
<van-tabbar-item to="/recommed">
<div class="icon">
<i
class="iconfont icon-classify2-o fontcolor"
:class="{fontcoloractive:Route==='recommed'}"
></i>
</div>
<div :class="{fontcoloractive:Route==='recommed'}">精选</div>
</van-tabbar-item>
<van-tabbar-item to="/book">
<div class="icon">
<i class="iconfont icon-shujia fontcolor" :class="{fontcoloractive:Route==='book'}"></i>
</div>
<div :class="{fontcoloractive:Route==='book'}">书架</div>
</van-tabbar-item>
<van-tabbar-item to="/my">
<div class="icon">
<i class="iconfont icon-sself fontcolor" :class="{fontcoloractive:Route==='my'}"></i>
</div>
<div :class="{fontcoloractive:Route==='my'}">我的</div>
</van-tabbar-item>
</van-tabbar>
</div>
</template>
<script>
export default {
name: "navbar",
data() {
return {
Route: ""
};
},
mounted() {
this.Route = this.$route.name;
},
watch: {
$route(route) {
this.Route = route.name;
console.log(this.Route);
}
}
};
</script>
开始我是使用页面索引(即1,2,3,4)作为判断条件fontcoloractive是否为真,后来发现这个页面有二级路由,如果从其他页面返回,二级路由不会刷新,但是一级路由会刷新,即顶部与底部navbar,导致返回之后页面的索引重新初始化,即navbar的活跃图片为第一个.后来使用watch监听$route,用route作为判断,即可完美解决返回navbar刷新导致底部活跃图标为第一个的bug.
注:其中icon图标来源于iconfont.
4.3.1 二级路由bookstore页面的搭建
主要代码
import { booksrore } from "../../network/index";
import scroll from "../common/scroll";
getBook() {
booksrore(res => {
// console.log(
res.content = res.content.sort(() => {
return Math.random() - 0.5;
});
let xiaoshuowenxue = res.content.slice(0, 6);
res.history = res.history.sort(() => {
return Math.random() - 0.5;
});
let history = res.history.slice(0, 6);
res.cglz = res.cglz.sort(() => {
return Math.random() - 0.5;
});
let cglz = res.cglz.slice(0, 6);
res.hightStar_select = res.hightStar_select.sort(() => {
return Math.random() - 0.5;
});
let hightStar_select = res.hightStar_select.slice(0, 6);
this.noval = [xiaoshuowenxue, history, cglz, hightStar_select];
// this.data.xiaoshuowenxue = res.content.slice(0,6);
});
}
引入axios下的bookstore方法进行调用,可获得后端提供数据,将得到的数据进行随机排序,这样就可以做到下拉刷新页面.
效果如下
点击更多按钮会去往更多界面,点击单个小说会去往小说详情页面.
4.3.2 精选页面
同样通过bookstore方法获取数据,然后对其进行页面渲染,css没碰到什么技术痛点,所以展示关键代码
getBook() {
booksrore(res => {
let xiaoshuowenxue = res.content.slice(0, 3);
xiaoshuowenxue.forEach(item => {
item.novel_content = JSON.parse(item.novel_content);
});
let history = res.history.slice(0, 3);
history.forEach(item => {
item.novel_content = JSON.parse(item.novel_content);
});
let cglz = res.cglz.slice(0, 3);
cglz.forEach(item => {
item.novel_content = JSON.parse(item.novel_content);
});
let shenghuo = res.shenghuo.slice(0, 3);
shenghuo.forEach(item => {
item.novel_content = JSON.parse(item.novel_content);
});
let renwen = res.renwen.slice(0, 3);
renwen.forEach(item => {
item.novel_content = JSON.parse(item.novel_content);
});
this.noval = [xiaoshuowenxue, history, cglz, renwen, shenghuo];
// this.data.xiaoshuowenxue = res.content.slice(0,6);
});
},
获取数据并加工代码.
具体效果如下:
其中底部轮播页面是使用vant框架,所以也没什么难度,手写的话,起原理基本和轮播图的实现差不多.
4.3.3 书架页面
无登录和无收藏书籍的状态
登录状态基本也是使用vant. 因为这些页面无过多js操作,不做详说. 唯一要注意的是,如果处于无登录状态,不能调用和登录有关的接口即可.
关键代码:
showcollect() {
if (localStorage.book_user) {
sqlcollection(
res => {
this.book = res.data;
// console.log(res.data);
},
{
user: localStorage.book_user
}
);
}
sqlcollection(
res => {
this.book = res.data;
// console.log(res.data);
},
{
user: localStorage.book_user
}
);
},
效果动态图:
4.3.4 我的页面
登录与无登录状态图
登录之后会在本地保存一个book_user字段,根据此字段控制是否展示与隐藏某些内容.
4.4 登录与注册
注册于登录极其类似,因此只讲注册
methods: {
tologin() {
this.$router.push({ path: "/login" });
},
zhuche() {
if (!this.user) {
this.$toast("手机号不能为空");
} else {
if (!/^1[3456789]\d{9}$/.test(this.user)) {
this.$toast("手机号出错");
return false;
} else {
if (this.pass.length < 6) this.$toast("密码至少6位");
else {
zhuche(
res => {
if (res.status == "200") {
this.$toast(res.msg);
localStorage.book_user = this.user;
this.$router.push({ path: "/my" });
}
if (res.status == "500") {
this.$toast(res.msg);
}
},
{ user: this.user, pass: this.pass }
);
}
}
}
}
},
首先判断手机号是否为空(手机号就是用户名)如果为空,则提示手机号为空,否则对输入的内容进行正则匹配.是否为手机号,如果不是手机号,提示手机号格式错误,最后检验当前号码是否已经被注册,如果已经注册提示用户已经被注册,否则进行注册,并调转到/my页面.
ui界面如下:
4.5 更多书籍页面
部分主要代码
if (this.$route.query.catogry == "小说文学 精选好书") {
if (20 * this.page < res.content.length)
this.book = res.content.slice(0, 20 * this.page);
else this.book = res.content;
this.book.forEach(item => {
item.novel_content = JSON.parse(item.novel_content);
});
// this.book.novel_content=JSON.parse(this.book.novel_content)
}
数据源增加一个page字段,每次调用函数,page+1,以此控制当前页面显示内容的多少.
ui界面如下:
4.6搜索功能
ui界面如图:
历史记录采用行内块布局,默认显示一行,超出隐藏,点击向下icon图标即可打开,然后就会展示向上icon图标,点击则会关闭.点击删除icon则会删除所有历史记录.另外无登录状态没有历史记录,因为历史记录我设计的后端和用户挂钩.热搜则采用垂直风格布局.点击历史记录和热搜里面的内容可直接搜索,输入框内容搜索有一个400ms的防抖,当输入框有值时隐藏历史记录和热搜榜,展示搜索到的数据.点击搜索到的选项进行页面跳转,跳转到书籍详情页面,同时增加搜索记录,内容为输入框的值,如果存在则更新.不存在则插入.
动态效果展示图:
部分代码如下,防抖函数的实现原理就是设置一个定时器,如果再次执行函数则会清除定时器重新计时
debounce(func, wait) {
let timeout = null;
return function() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
func.apply(this, arguments);
}, wait);
};
},
tobookdetail(bookid) {
if (bookid) {
if (this.value) {
if (localStorage.book_user) {
insertHS(
res => {
console.log(res);
},
{ text: this.value, user: localStorage.book_user }
);
}
}
this.$router.push({
path: "/bookinfo",
query: { bookid: bookid }
});
}
},
4.7 书籍详情页面的开发
界面:
顶部一个返回栏是一个主键,其中间和右边内容为props传值,右边点击事件为emit发送.背景色为书籍图片的虚化.中间为书籍界面的简单布局.简介有内容简介和作者简介.内容简介通过点击按钮进行打开,打开后点击内容会进行收藏,另外还有一个目录通过for循环遍历展示.点击麻烦阅读按钮会去往读书界面,同时阅读历史增加一条数据.进入页面的时候回进行数据库查询本数据是否已收藏,根据查询结果显示不同的内容.
由于处于开发阶段,没有存放过多小说,小说内容只有一本,是写死的.意思就是不敢进入哪本小说都是固定的那一本小说.
import Epub from "epubjs";
this.book = new Epub("../../../static/巴别塔之犬.epub");
this.book.ready
.then(() => {
// 生成目录
this.navigation = this.book.navigation;
// console.log(this.navigation);
// 生成Locations对象
return this.book.locations.generate();
})
通过引入epubjs模块,然后通过调用ready()函数生成一个promise模块,然后通过this.book.navigation可生成目录信息,然后将其保存然后进行页面展示即可
4.8 阅读界面的开发
功能: 点击页面左侧向前翻页.点击页面右侧向后翻页.点击中间则打开设置. 设置可以修改字号,背景色,并对其进行记录,下次进入依旧生效.用户可以通过目录进行页面跳转,对于已经观看了的小说,会记录阅读进度.
页面效果开始已经上传了,这里就不上传了.
mounted() {
// console.log(this.$route.query.href);
if (localStorage.book_user) {
sqlCll(
res => {
// console.log(res);
console.log(res);
if (res.status === "200") {
this.right = "已加入书架";
}
if (res.status === "500") {
this.right = "加入书架";
}
},
{
user: localStorage.book_user,
bookid: this.$route.query.bookid
}
);
sqlprogress(
res => {
if (res.status == "200") {
this.progress = res.data[0].progress;
}
},
{
user: localStorage.book_user,
bookid: this.$route.query.bookid
}
);
getBookInfo(
res => {
this.Bookinfo = res.bookinfo;
},
{ bookid: this.$route.query.bookid }
);
}
this.bookid = this.$route.query.bookid;
this.getbook();
},
首先在进入页面的时候,判断该书是否已经加入书架,然后通过不同的判断条件进行不同的页面显示, 然后读取book信息.同时还有个判断这本书籍用户是否看过,如果看过这将观看进度重新赋值. 最后调用getbook函数
getbook() {
// let title = this.Bookinfo[0].title;
// let url = "../../../static/" + title + ".epub";
// this.book = new Epub(url);
this.book = new Epub("../../../static/巴别塔之犬.epub");
this.rendition = this.book.renderTo("read", {
width: window.innerWidth,
height: window.innerHeight,
method: "default"
});
// this.rendition.display();
this.theme = this.rendition.themes;
this.registerTheme();
this.rendition.hooks.content.register(function(contents, view) {
var elements = contents.document.querySelector("body");
elements.style.lineHeight = "40px";
});
if(localStorage.book_user){sqlset(
res => {
if (localStorage.book_user) {
if (res.status == "200") {
this.bgc = res.data[0].bgcolor;
let color = "";
if (this.bgc == "#fff") {
color = "white";
this.actived = 0;
this.$refs.main.style.color = "black";
}
if (this.bgc == "#FFFFF0") {
color = "yello";
this.actived = 1;
this.$refs.main.style.color = "black";
}
if (this.bgc == "#E0FFFF") {
color = "blue";
this.actived = 3;
this.$refs.main.style.color = "black";
}
if (this.bgc == "#000") {
color = "black";
this.actived = 4;
this.$refs.main.style.color = "white";
}
if (this.bgc == "rgba(152, 251, 152, 0.8)") {
color = "green";
this.actived = 2;
this.$refs.main.style.color = "black";
}
this.fontsize = res.data[0].fontsize;
this.theme.select(color);
this.theme.fontSize(this.fontsize + "px");
}
} else {
this.actived = 1;
this.theme.select("yello");
}
},
{ user: localStorage.book_user }
);}
this.book.ready
.then(() => {
// 生成目录
this.navigation = this.book.navigation;
// console.log(this.navigation);
// 生成Locations对象
return this.book.locations.generate();
})
.then(result => {
// 保存locations对象
this.locations = this.book.locations;
// 标记电子书为解析完毕状态
console.log(this.locations);
this.bookAvailable = true;
if (this.$route.query.href) {
this.rendition.display(this.$route.query.href);
} else {
this.rendition.display(
this.locations.cfiFromPercentage(this.progress)
);
}
});
// this.rendition.display();
},
通过this.book.renderTo方法设置book显示的大小,read为所挂载的id,直接将显示宽高设置为屏幕宽高即可.
this.rendition.hooks.content.register方法可以对书籍页面的标签进行选择,这里选择body标签,将行高设置为40px.
然后调用用户设置接口,根据用户的设置信息展示用户设置好的信息.使用this.book.locations对小说进行解析. bookAvailable字段默认为false,解析完毕设置为true.然后通过this.rendition.display()方法进行页面展示. this.locations.cfiFromPercentage()方法接受一个数字,其值为0-1,可以定位到小说的百分比内容.
给整个页面添加一个mask盒子,进行绝对定位,其大小为这个屏幕,用弹性布局对其进行切分成三块,分别对应三个事件,前进后退以及打开设置.
下面对进行下一页进行说明
tolast() {
if (this.rendition) {
if (localStorage.book_user) this.saveprogress();
this.rendition.next();
this.isShow = false;
}
}
this.rendition有一个next方法,调用次方法则可进行下一页.同时将设置界面关闭.
同时如果用户登录了,则调用saveprogreess方法对当前进度进行保存
saveprogress() {
var currentLocation = this.rendition.currentLocation();
var progress =
Math.floor(
this.locations
.percentageFromCfi(currentLocation.start.cfi)
.toFixed(5) * 10000
) / 10000;
this.progress = progress;
Progress(
res => {
console.log(res);
},
{
user: localStorage.book_user,
bookid: this.$route.query.bookid,
progress: progress
}
);
},
this.locations .percentageFromCfi(currentLocation.start.cfi).toFixed(5) * 10000可以获取到当前页面所在进度. 然后调用接口将其存入后端即可.
registerTheme() {
this.themeList.forEach(theme => {
this.theme.register(theme.name, theme.style);
});
},
通过registerTheme方法对主题进行注册,其中themeList为一个数组,结构如下:
themeList: [
{
name: "white",
style: {
body: {
background: "#fff",
color: "#000"
}
}
},
{
name: "black",
style: {
body: {
background: "#000",
color: "#fff"
}
}
},
{
name: "yello",
style: {
body: {
background: "#FFFFF0",
color: "#000"
}
}
},
{
name: "green",
style: {
body: {
background: "rgba(152, 251, 152, 0.8)",
color: "#000"
}
}
},
{
name: "blue",
style: {
body: {
background: "#E0FFFF",
color: "#000"
}
}
}
],
然后t通过 this.theme.select(color)方法进行选择主题,其中color参数为注册主题的name字段, 当改变主题的时候进行设置
change_Color(index, color) {
this.actived = index;
this.theme.select(color);
if (color == "yello") {
this.bgc = "#FFFFF0";
this.$refs.main.style.color = "black";
}
if (color == "white") {
this.bgc = "#fff";
this.$refs.main.style.color = "black";
}
if (color == "black") {
this.$refs.main.style.color = "white";
this.bgc = "#000";
}
if (color == "green") {
this.bgc = "rgba(152, 251, 152, 0.8)";
this.$refs.main.style.color = "black";
}
if (color == "blue") {
this.bgc = "#E0FFFF";
this.$refs.main.style.color = "black";
}
this.saveset();
this.show = false;
this.isShow = false;
},
当然不要忘记了修改设置选项的背景色和文字的颜色. 然后设置完了对其进行接口请求,将其存入数据库中即可