福利:关于左侧二维码可以 看《斗罗大陆》和《斗破苍穹》漫画啦
由于《斗罗大陆》和《斗破苍穹》动漫的热播,让我深深沉迷其中,因为动画更新的太慢了,搞得我只能去找小说,找漫画去过瘾,来来回回也刷了不少遍了。
在这个过程中,由于热爱,我甚至把他们的漫画全都下载了下来,想随时随地的看,啊哈哈哈哈哈哈哈哈嗝
然后下载下来图片,在电脑上一张一张翻着看并不方便,所以便萌生了自己搭建一个小说网站的想法,真正的在手机上随时随地的看。
如何搭建一个漫画小说网站?
PS:小说和漫画都有版权,本文主要交流技术,请勿用做商用盈利
如果自己搭建一个自己的私服漫画小说网站,放着自己喜欢的内容,是一件很酷的事情
涉及到的技术:
java,mysql, mybatis, 服务器,oss,uniapp,vue 等
看起来简简单单的一个小网站,需要用的的知识点还不少呢,大家可以先收藏,慢慢看
1.找资源
想做内容网站,内容是必不可少的,因为本人最喜欢的小说是《斗罗大陆》和《斗破苍穹》,而小说随手可得,这次我把矛头放在漫画上。
市面上已经有了很多漫画网站,随便找一下当一下资源就行了
下面以《斗破苍穹》为例,我用的技术是java,根据图片的url规律下载,没有用python等高大尚工具,比较接地气
因为这种漫画的周期很长了,url经过了很多次改变,而且估计是因为安全等因素考虑,前期的内容url是没有随机数的,后面的加了随机数,所以后面可能得每个章节都手动改随机数下载了,这是url下载方式的弊端,如果用爬虫就没这种困扰了。
package com.downloadImg.controller;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Created by 12143 on 2019/9/21.
*/
public class DouPoCangQiong {
/**
* 从网络Url中下载文件
* @param urlStr
* @param fileName
* @param savePath
* @throws IOException
*/
public static int downLoadFromUrl(String urlStr,String fileName,String savePath){
try{
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
//设置超时间为3秒
conn.setConnectTimeout(3*1000);
//防止屏蔽程序抓取而返回403错误
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
//得到输入流
InputStream inputStream = conn.getInputStream();
//获取自己数组
byte[] getData = readInputStream(inputStream);
//文件保存位置
File saveDir = new File(savePath);
if(!saveDir.exists()){
saveDir.mkdir();
}
File file = new File(saveDir+File.separator+fileName);
FileOutputStream fos = new FileOutputStream(file);
fos.write(getData);
if(fos!=null){
fos.close();
}
if(inputStream!=null){
inputStream.close();
}
System.out.println("info:"+url+" download success");
return 0;
}catch (Exception e){
System.out.println("err:download failed"+e);
return -1;
}
//System.out.println("info:"+url+" download success");
}
//斗破苍穹1-765下载地址,到了766就换地址了
/*public static void downLoad(){
//开始章节
int startPage=1;
//结束章节
int endPage=765;
//开始图片
int startImg=1;
//结束图片,因为每章都不超过100张图片,所以相当于搞了一个100的图片池
int endImg=100;
//循环章节
for(int i=startPage;i<=endPage;i++){
//每一章的图片都是从1开始命名的
int imgNum=1;
for(int j=startImg;j<=endImg;j++){
int r=downLoadFromUrl("http://mhpic.xiaomingtaiji.net/comic/D%2F%E6%96%97%E7%A0%B4%E8%8B%8D%E7%A9%B9%E6%8B%86%E5%88%86%E7%89%88%2F"+i+"%E8%AF%9DGQ%2F"+j+".jpg-zymk.middle.webp",
""+ imgNum++ +".webp","d:/zymk/斗破苍穹/第"+i+"话");
if(r==-1){
//返回-1时,说明此章已经下载完毕,可以终止循环图片,去循环下一章节
//imgNum--;
break;
}
}
}
System.out.println("下载完成");
}*/
//斗破苍穹从766章节换地址且地址带了随机码,前部门随机码是递增的,后面的是随机的, 766-870 到870之前都是换地址的,到了871又换回去了
/*public static void downLoad(){
int startPage=871;
int endPage=871;
int startNum=292018;
int startImg=1;
int endImg=100;
for(int i=startPage;i<=endPage;i++){
int imgNum=1;
for(int j=startImg;j<=endImg;j++){
int r=downLoadFromUrl("https://mhpic.xiaomingtaiji.net/comic/D%2F%E6%96%97%E7%A0%B4%E8%8B%8D%E7%A9%B9%2F%E7%AC%AC"+i+"%E8%AF%9DF0_"+startNum+"%2F"+j+".jpg-zymk.middle.webp",
""+ imgNum++ +".webp","d:/zymk/斗破苍穹/第"+i+"话");
if(r==-1){
//imgNum--;
break;
}
}
startNum++;
}
System.out.println("下载完成");
}*/
//870+ 因为地址总变,没啥规律,所以得随机应变
public static void downLoad(){
int startPage=876;
int endPage=876;
int startNum=298032;
int startImg=1;
int endImg=100;
for(int i=startPage;i<=endPage;i++){
int imgNum=1;
for(int j=startImg;j<=endImg;j++){
int r=downLoadFromUrl("https://mhpic.zymkcdn.com/comic/D%2F%E6%96%97%E7%A0%B4%E8%8B%8D%E7%A9%B9%2F%E7%AC%AC"+i+"%E8%AF%9DF0_"+startNum+"%2F"+j+".jpg-zymk.middle.webp",
""+ imgNum++ +".webp","d:/zymk/斗破苍穹/第"+i+"话");
if(r==-1){
//imgNum--;
break;
}
}
startNum++;
}
System.out.println("下载完成");
}
/**
* 从输入流中获取字节数组
* @param inputStream
* @return
* @throws IOException
*/
public static byte[] readInputStream(InputStream inputStream) throws IOException {
byte[] buffer = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((len = inputStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.close();
return bos.toByteArray();
}
public static void main(String[] args) {
try{
downLoad();
}catch (Exception e) {
// TODO: handle exception
}
}
}
这样就把整本书的图片都下载到本地了,
2.资源上传服务器
有了资源,想要在线观赏,就需要把图片放到服务器上。
如何购买服务器?看这篇文章:总结一下购买阿里云服务器的经验
有了服务器,但服务器的内存都比较小,加硬盘很贵,像图片这种很占用硬盘的资源,直接放在服务器上肯定是不合适的。
这就需要使用阿里推出的oss存储空间,个人用的感觉不错,上传流量免费,下行流量收费,但是可以用服务器esc的内网访问oss,走服务器的宽带流量,做到下行流量也免费。尤其是支持纯前端上传和下载,上传的图片还能自动修改样式,大小,加水印等,用起来得心应手。
关于oss我以前也介绍过:阿里云存储对象OSS使用讲解一:OSS的购买和配置
阿里云OSS通过服务器ESC内网流量访问文件(使用免费带宽,不使用收费流量)
好了,现在有了服务器和oss存储空间了。我们需要把图片都上传上去了。
但是一部漫画有上万张图片,如果选择线上手动上传,有点坑爹的是一次最多支持100个文件同时上传,那我们岂不是得做重复工作几百次,这是只是一部,做网站要上传很多部,作为程序员,一切需要人手动重复工作的都是拒绝的
所以我选择写java程序让电脑帮我去上传。
思路:1.按 小说-》章节-》图片 的结构,在oss上自动创建各个章节文件夹并自动上传图片。
2.上传的同时,在mysql数据库中记录小说名,章节名,图片url等信息,因为前端到时候需要掉接口获取小说数据,所以要把这些文件数据转化成数据库数据。
先建个表,随便写点字段:
首先,创建oss上传工具类,配置自己的各种账号,里面主要用到的就是创建文件夹和上传文件而已
package com.bomc.recordLife.util;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import org.apache.log4j.Logger;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.Bucket;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.beans.factory.annotation.Value;
/**
* @class:AliyunOSSClientUtil
* @descript:java使用阿里云OSS存储对象上传图片
*
*/
public class AliyunOSSClientUtil {
//log日志
private static Logger logger = Logger.getLogger(AliyunOSSClientUtil.class);
//阿里云API的内或外网域名
private static String ENDPOINT="你的endPoint";
//阿里云API的密钥Access Key ID
private static String ACCESS_KEY_ID="你的accessKeyId";
//阿里云API的密钥Access Key Secret
private static String ACCESS_KEY_SECRET="你的accessKeySecret";
//阿里云API的bucket名称
private static String BACKET_NAME="你的bucket名";
//阿里云API的文件夹名称
//private static String FOLDER;
/**
* 获取阿里云OSS客户端对象
* @return ossClient
*/
public static OSSClient getOSSClient(){
return new OSSClient(ENDPOINT,ACCESS_KEY_ID, ACCESS_KEY_SECRET);
}
/**
* 创建存储空间
* @param ossClient OSS连接
* @param bucketName 存储空间
* @return
*/
public static String createBucketName(OSSClient ossClient,String bucketName){
//存储空间
final String bucketNames=bucketName;
if(!ossClient.doesBucketExist(bucketName)){
//创建存储空间
Bucket bucket=ossClient.createBucket(bucketName);
logger.info("创建存储空间成功");
return bucket.getName();
}
return bucketNames;
}
/**
* 删除存储空间buckName
* @param ossClient oss对象
* @param bucketName 存储空间
*/
public static void deleteBucket(OSSClient ossClient, String bucketName){
ossClient.deleteBucket(bucketName);
logger.info("删除" + bucketName + "Bucket成功");
}
/**
* 创建模拟文件夹
* @param ossClient oss连接
* @param bucketName 存储空间
* @param folder 模拟文件夹名如"qj_nanjing/"
* @return 文件夹名
*/
public static String createFolder(OSSClient ossClient,String folder){
//文件夹名
final String keySuffixWithSlash =folder;
//判断文件夹是否存在,不存在则创建
if(!ossClient.doesObjectExist(BACKET_NAME, keySuffixWithSlash)){
//创建文件夹
ossClient.putObject(BACKET_NAME, keySuffixWithSlash, new ByteArrayInputStream(new byte[0]));
logger.info("创建文件夹成功");
//得到文件夹名
OSSObject object = ossClient.getObject(BACKET_NAME, keySuffixWithSlash);
String fileDir=object.getKey();
return fileDir;
}
return keySuffixWithSlash;
}
/**
* 根据key删除OSS服务器上的文件
* @param ossClient oss连接
* @param bucketName 存储空间
* @param folder 模拟文件夹名 如"qj_nanjing/"
* @param key Bucket下的文件的路径名+文件名 如:"upload/cake.jpg"
*/
public static void deleteFile(OSSClient ossClient, String bucketName, String folder, String key){
ossClient.deleteObject(bucketName, folder + key);
logger.info("删除" + bucketName + "下的文件" + folder + key + "成功");
}
/**
* 上传图片至OSS
* @param ossClient oss连接
* @param file 上传文件(文件全路径如:D:\\image\\cake.jpg)
* @param bucketName 存储空间
* @param folder 模拟文件夹名 如"qj_nanjing/"
* @return String 返回的唯一MD5数字签名
* */
public static String uploadObject2OSS(OSSClient ossClient, File file,String folder) {
String resultStr = null;
try {
//以输入流的形式上传文件
InputStream is = new FileInputStream(file);
//文件名
String fileName = file.getName();
//文件大小
Long fileSize = file.length();
//创建上传Object的Metadata
ObjectMetadata metadata = new ObjectMetadata();
//上传的文件的长度
metadata.setContentLength(is.available());
//指定该Object被下载时的网页的缓存行为
metadata.setCacheControl("no-cache");
//指定该Object下设置Header
metadata.setHeader("Pragma", "no-cache");
//指定该Object被下载时的内容编码格式
metadata.setContentEncoding("utf-8");
//文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成,
//如果没有扩展名则填默认值application/octet-stream
metadata.setContentType(getContentType(fileName));
//指定该Object被下载时的名称(指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称)
metadata.setContentDisposition("filename/filesize=" + fileName + "/" + fileSize + "Byte.");
//上传文件 (上传文件流的形式)
PutObjectResult putResult = ossClient.putObject(BACKET_NAME, folder + fileName, is, metadata);
//解析结果
resultStr = putResult.getETag();
} catch (Exception e) {
e.printStackTrace();
logger.error("上传阿里云OSS服务器异常." + e.getMessage(), e);
}
return resultStr;
}
/**
* 通过文件名判断并获取OSS服务文件上传时文件的contentType
* @param fileName 文件名
* @return 文件的contentType
*/
public static String getContentType(String fileName){
//文件的后缀名
String fileExtension = fileName.substring(fileName.lastIndexOf("."));
if(".bmp".equalsIgnoreCase(fileExtension)) {
return "image/bmp";
}
if(".gif".equalsIgnoreCase(fileExtension)) {
return "image/gif";
}
if(".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension) || ".png".equalsIgnoreCase(fileExtension) ) {
return "image/jpeg";
}
if(".html".equalsIgnoreCase(fileExtension)) {
return "text/html";
}
if(".txt".equalsIgnoreCase(fileExtension)) {
return "text/plain";
}
if(".vsd".equalsIgnoreCase(fileExtension)) {
return "application/vnd.visio";
}
if(".ppt".equalsIgnoreCase(fileExtension) || "pptx".equalsIgnoreCase(fileExtension)) {
return "application/vnd.ms-powerpoint";
}
if(".doc".equalsIgnoreCase(fileExtension) || "docx".equalsIgnoreCase(fileExtension)) {
return "application/msword";
}
if(".xml".equalsIgnoreCase(fileExtension)) {
return "text/xml";
}
//默认返回类型
return "image/jpeg";
}
}
再写程序上传,不必要的包自行删除,主要知识点就是java 文件目录的遍历
package com.bomc.recordLife.controller;
import com.alibaba.fastjson.JSON;
import com.aliyun.oss.OSSClient;
import com.bomc.recordLife.entry.SysUser;
import com.bomc.recordLife.service.AttachmentService;
import com.bomc.recordLife.service.NovelService;
import com.bomc.recordLife.util.AliyunOSSClientUtil;
import com.bomc.recordLife.util.OSSUtil;
import com.bomc.recordLife.util.RetBase;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
/**
* @author 作者 zlz
* @version 创建时间 2018/9/20
*/
@Controller
@RequestMapping(value = "webmana/attachment")
public class AttachmentOssController extends BaseController{
@Resource
private AttachmentService attachmentService;
@Resource
private NovelService novelService;
@Autowired
private OSSUtil ossUtil;
@Value("${imgFolder}")
private String imgFolder;
@Value("${videoFolder}")
private String videoFolder;
@Value("${coverFolder}")
private String coverFolder;
@Value("${ossHost}")
private String ossHost;
private OSSClient ossClient;
//获取小说列表
@ResponseBody
@RequestMapping(value = "getNovelList")
public Object getNovelList(@RequestParam Map<String,Object> params) {
RetBase ret=new RetBase();
List<Map<String,Object>> data=novelService.getNovelList(params);
ret.setData(data);
ret.setSuccess(true);
return ret;
}
//循环章节目录和图片,分别创建目录和上传图片,并分别记录到数据库中
private void func(File file,Integer parentId,String folder){
File[] fs = file.listFiles();
Map<String,Object> params = new HashMap<>();
for(File f:fs){
System.out.println(f.getName());
params.put("name",f.getName());
if(f.isDirectory()){
//若是目录,则递归打印该目录下的文件
params.put("type","2");
params.put("parentId",parentId);
novelService.insertNovelInfo(params);
AliyunOSSClientUtil.createFolder(ossClient, folder+f.getName()+"/");
func(f,Integer.valueOf(params.get("id").toString()),folder+f.getName()+"/");
}
if(f.isFile()){
params.put("type","3");
params.put("parentId",parentId);
params.put("url",ossHost+"/"+folder+f.getName());
novelService.insertNovelInfo(params);
AliyunOSSClientUtil.uploadObject2OSS(ossClient, f, folder);
} //若是文件,直接打印
}
}
//上传小说的入口,设置好需要上传的本地路径和oss路径,一切就让他自己遍历上传去吧
@RequestMapping(value = "getNovel")
@ResponseBody
private void getNovel(){
String path = "D:\\zymk\\斗破苍穹"; //要遍历的本地路径
File file = new File(path); //获取其file对象
Map<String,Object> params = new HashMap<>();
params.put("name",file.getName());
params.put("type","1");
params.put("parentId",0);
params.put("kind","1");
int key=novelService.insertNovelInfo(params);
ossClient= AliyunOSSClientUtil.getOSSClient();
//上传的oss路径
String md5key = AliyunOSSClientUtil.createFolder(ossClient, "files/漫画/斗破苍穹/");
System.out.println(md5key);
func(file,Integer.valueOf(params.get("id").toString()),"files/漫画/斗破苍穹/");
}
//把新的漫画放在一个新的文件中上传
@RequestMapping(value = "updateNovel")
@ResponseBody
private void updateNovel(){
String path = "D:\\zymk\\斗罗大陆2"; //要遍历的路径
File file = new File(path); //获取其file对象
Map<String,Object> params = new HashMap<>();
ossClient= AliyunOSSClientUtil.getOSSClient();
int novelId=33;
func(file,novelId,"files/漫画/斗罗大陆/");
}
}
package com.bomc.recordLife.service;
import com.bomc.recordLife.entry.AttachmentComment;
import com.bomc.recordLife.mapper.AttachmentMapper;
import com.bomc.recordLife.mapper.NovelMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Transactional
@Service
public class NovelService {
@Resource
private NovelMapper novelMapper;
public List<Map<String,Object>> getNovelList(Map<String,Object> params){
return novelMapper.getNovelList(params);
}
public int insertNovelInfo(Map<String,Object> params){
return novelMapper.insertNovelInfo(params);
}
}
关于mybatis的sql还有点知识点,分别是自动递增id和返回insert后的id,因为我插入后立刻就需要用这个id当parentId去传递给后面的递归中
关于这点我以前也记录过:Mybatis怎么怎么获取刚insert后生成的ID(探索selectKey)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.bomc.recordLife.mapper.NovelMapper">
<select id="getNovelList" resultType="map">
select *,CONVERT(SUBSTR(t.name,2,(LENGTH(t.name)-6)), UNSIGNED INTEGER) sort,
CONVERT(SUBSTR(t.name,1,(LENGTH(t.name)-5)), UNSIGNED INTEGER) sort1
from novel t
where 1=1
<if test="type !=null and type !=''">
and t.type = #{type,jdbcType=VARCHAR}
</if>
<if test="parentId !=null and parentId !=''">
and t.parent_id = #{parentId,jdbcType=VARCHAR}
</if>
<if test="type !=null and type !='' and type=='2'.toString()">
order by sort
</if>
<if test="type !=null and type !='' and type=='3'.toString()">
order by sort1
</if>
</select>
<!--userGeneratedKey自动递增id,再用selectKey把刚插入的id返回-->
<insert id="insertNovelInfo" useGeneratedKeys="true" keyProperty="ID1">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into novel
(
id,parent_id,type,classify,kind,name,url,create_time
)
VALUES
(
#{ID1},#{parentId,jdbcType=INTEGER},#{type,jdbcType=VARCHAR},
#{classify,jdbcType=VARCHAR},#{kind,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR},
#{url,jdbcType=VARCHAR},now()
)
</insert>
</mapper>
这样,所有的图片就自动传上去了。
然后记得把程序部署到服务器上去,这里建议使用jenkins自动化部署,也就是你在git或者svn上传代码,然后上服务器点一键构建就完成部署了,非常方便
关于这个,我也总结过:jenkins自动部署springboot项目
关于服务器,因为现在大部分服务器都是linux,作为小白直接操作linux还是比较费劲的,推荐服务器安装界面化工具:宝塔
宝塔可以监控服务器使用情况,图形化操作软件,配置站点,配置安全设置,便利上传文件等功能,让你像操作windows一样方便
3.搭建前端网站
现在我们有了资源,有了接口,剩下的就是完成前端界面展示漫画了。
现在看漫画最多的人是用手机看的,所以我们先定位为手机端。
然后现在最热门的移动端框架是uni-app,写一套代码可以比较完美的运行在H5,小程序,APP中,虽然坑比较多,但用了这么久,该能实现的功能还是能实现的。最主要的它基于vue和小程序语法开发,省去了很多学习的成本,甚至还能在工程里用npm下载包并完美兼容,属于学习成本低,并越用越顺手的框架。
去插件市场随便找个插件改改:https://ext.dcloud.net.cn/plugin?id=1116
这个插件是看小说用的,我改了改变成适合看漫画的
然后加入了uni-more-loading,vue-lazyload(好处就在这,可以使用所有vue的资源),历史记录,章节自定滚动定位等小功能
随意展示一个主页面源码吧,需要整套源码的加我左侧二维码留言找我要就行了。
<template>
<view class="container">
<scroll-view :style="{'background-color': currentTheme.backgroundColor}"
@click="contentTapHandler" @scroll="scrollChange" class="scroll" scroll-y="true"
@scrolltoupper="loadBefore"
@scrolltolower="loadNext"
>
<!-- <view class="title-section">
<text class="title" :style="{'color': currentTheme.color}">{{chapterTitle}}</text>
</view> -->
<view class="content-section" style="padding: 10px 0;">
<!-- <text class="content" :style="{'font-size': fontSize, 'color': currentTheme.color}">{{content}}</text> -->
<!-- <u-parse :content="content"/> -->
<img v-lazy="item.URL" class="novel-img" v-for="item in novelImgList"
:lazy-load="true"
mode="widthFix"
:key="item.ID"></img>
</view>
<!-- <view class="bottom-section">
<button @click="bottomBtnHandler('pre')" class="bottom-btn" :class="{'bottom-night-btn': isNight}" type="primary">上一章</button>
<button @click="bottomBtnHandler('chapters')" class="bottom-btn" :class="{'bottom-night-btn': isNight}" type="primary">目录</button>
<button @click="bottomBtnHandler('next')" class="bottom-btn" :class="{'bottom-night-btn': isNight}" type="primary">下一章</button>
</view> -->
<uni-load-more :status="loadingType"></uni-load-more>
</scroll-view>
<view @click="maskHandler" class="slider-mask" :class="{'slider-show-mask' :sliderShow}">
<view @click="sliderHandler" class="slider-section" :class="{'slider-show-section' :sliderShow}">
<ss-sliderbar :isNightTheme="isNight" @chapterItemTap="chapterItemHandler" :currentChapter="currentChapter" :chapterList="chapterList"></ss-sliderbar>
</view>
</view>
<view class="tool-section" :class="{'tool-show-section' :toolShow}">
<ss-toolbar @toolSliderChange="toolSliderChangeHandler" :chapter="chapter" @themeTap="themeHandler" @functionBtnTap="functionBtnHandler" @chapterBtnTap="chapterBtnHandler"></ss-toolbar>
</view>
<view class="set-section" :class="{'set-show-section' :setShow}">
<ss-setbar :isNightTheme="isNight" @brightChange="brightChangeHandler" @fontSizeChange="fontSizeChangeHandler"></ss-setbar>
</view>
</view>
</template>
<script>
import ssSliderbar from '@/components/ss-sliderbar/ss-sliderbar.vue'
import ssToolbar from '@/components/ss-toolbar/ss-toolbar.vue'
import Json from '@/Json'
import uParse from '@/components/gaoyia-parse/parse.vue'
import ssSetbar from '@/components/ss-setbar/ss-setbar.vue'
import uniLoadMore from '@/components/uni-load-more/uni-load-more.vue';
export default {
components: {
ssSliderbar,
ssToolbar,
uParse,
ssSetbar,
uniLoadMore
},
data () {
return {
novelId:0,
chapterId:0,
chapterList: [],
chapter: {},
sliderShow: false,
toolShow: false,
setShow: false,
screenWidth: 0,
screenHeight: 0,
content: '',
novelImgList:[],
chapterTitle: '',
currentChapter: 0, // 当前的章节数 默认从0开始
fontSize: '17px',
nightTheme: {
backgroundColor: '#161616',
color: '#4f5050'
},
dayTheme: {
backgroundColor: '#f7f7f7',
color: '#333'
},
// 默认采用白天的主题样式
currentTheme: {
backgroundColor: '#f7f7f7',
color: '#333'
},
// 是否为夜间模式
// 默认值为false
isNight: false,
lastScrollY: 0 ,// 用来判断电子书的滑动方向
loadingType:'loading'
}
},
onLoad (options) {
var novelId=options.novelId;
//this.novelId=11976;
this.novelId=novelId;
//记录历史记录,看过的小说和章节最新记录
var his=uni.getStorageSync("history")||"";
if(his.length>0){
console.log(his)
his.forEach(item=>{
if(item.novelId==this.novelId){
this.currentChapter=item.currentChapter
}
})
this.getEbookChapterList();
this.calculateScreenSize();
}else{
this.getEbookChapterList();
this.calculateScreenSize();
}
},
methods: {
calculateScreenSize () {
var info = uni.getSystemInfoSync();
console.log('相關信息:' + JSON.stringify(info));
this.screenHeight = info.safeArea.height;
this.screenWidth = info.safeArea.width;
},
loadBefore(){
},
loadNext(e){
console.log(e)
this.getChapterContent()
},
getEbookChapterList () {
let self=this;
var params={
data:{
type:'2',
parentId:this.novelId
}
}
this.$request.get('webmana/attachment/getNovelList',params).then(res => {
//console.log('获取的章节内容' + JSON.stringify(res.data));
//this.content = res.data.content;
this.chapterList = res.data.data;
var chapter = this.chapterList[0];
this.chapter = chapter;
this.chapterTitle = chapter.NAME;
this.getChapterContent(this.currentChapter);
})
},
getChapterContent () {
var params={
data:{
type:'3',
parentId:this.chapterList[this.currentChapter].ID
}
}
if(this.currentChapter>=this.chapterList.length){
this.loadingType='noMore'
return false;
}
this.loadingType='loading'
this.$request.get('webmana/attachment/getNovelList',params).then(res => {
//console.log('获取的章节内容' + JSON.stringify(res.data));
//this.content = res.data.content;
var his=uni.getStorageSync("history")||"";
if(his.length>0){
//更新登陆状态
var flag=false;
his.forEach(item=>{
if(item.novelId==this.novelId){
flag=true;
item.currentChapter=this.currentChapter
}
})
if(!flag){
his.push({
novelId:this.novelId,
currentChapter:this.currentChapter
})
}
uni.setStorageSync("history",his)
}else{
var obj={
novelId:this.novelId,
currentChapter:this.currentChapter
}
uni.setStorageSync("history",[obj])
}
this.chapter=this.chapterList[this.currentChapter];
this.novelImgList=[...this.novelImgList,...res.data.data];
this.currentChapter++
})
//this.content = Json.chapterContent.content;
},
chapterItemHandler (index) {
this.chapter=this.chapterList[index];
this.currentChapter=index;
this.novelImgList=[];
this.getChapterContent()
//this.$api.msg('章节ID:' + chapter.number);
},
chapterBtnHandler (btnType) {
switch (btnType) {
case 'pre':
this.$api.msg('上一章按钮');
break;
case 'next':
this.$api.msg('下一章按钮');
break;
}
},
contentTapHandler (e) {
var that = this;
let xMid = this.screenWidth / 2;
let yMid = this.screenHeight / 2;
let x = e.detail.x;
let y = e.detail.y;
// 这里设置屏幕中心200区域为可点区域
// 点击可以弹出底部工具栏
if ((x > xMid - 200 && x < xMid + 200) && (y < yMid + 200 && y > yMid - 200)) {
this.toolShow = !this.toolShow;
this.setShow = false;
}
},
maskHandler () {
this.sliderShow = false;
},
sliderHandler () {},
functionBtnHandler (type) {
var that = this;
switch (type){
case 'chapters':
this.toolShow = false;
setTimeout(function() {
that.sliderShow = true;
}, 0.7);
break;
case 'set':
this.toolShow = false;
setTimeout(function() {
that.setShow = true;
}, 0.7);
break;
}
},
/**
* 主题变换按钮
* 点击事件
*/
themeHandler (isNight) {
this.isNight = isNight;
if (isNight) {
// 夜间主题模式
uni.setNavigationBarColor({
frontColor: '#fff',
backgroundColor: this.nightTheme.backgroundColor,
animation: {
duration: 400,
timingFunc: 'easeIn'
},
fail: function (res) {
console.log('导航栏颜色失败:' + JSON.stringify(res));
}
});
this.currentTheme = this.nightTheme;
} else {
// 白天主题模式
uni.setNavigationBarColor({
frontColor: '#000',
backgroundColor: this.dayTheme.backgroundColor,
animation: {
duration: 400,
timingFunc: 'easeIn'
},
fail: function (res) {
console.log('导航栏颜色失败:' + JSON.stringify(res));
}
});
this.currentTheme = this.dayTheme;
}
},
bottomBtnHandler (type) {
switch (type){
case 'pre':
this.$api.msg('点击了上一章');
break;
case 'chapters':
this.sliderShow = true;
break;
case 'next':
this.$api.msg('点击了下一章');
break;
}
},
toolSliderChangeHandler (value) {
console.log('我获取的值为:' + value);
},
// 监听电子书滑动事件
scrollChange (e) {
let laterScrollTop = e.detail.scrollTop;
let ty = laterScrollTop - this.lastScrollY;
if (ty < 0) {
console.log('向上滑动');
} else {
this.toolShow = false;
}
this.lastScrollY = laterScrollTop;
},
/**
* 电子书阅读页面监听
* 字体变化
*/
fontSizeChangeHandler (fontSize) {
this.fontSize = fontSize + 'px';
console.log('fontSize变化了:' + this.fontSize)
},
/**
* 电子书阅读页面监听
* 屏幕亮度变化
*/
brightChangeHandler (bright) {
let value = bright / 100;
uni.setScreenBrightness({
value: value,
success: function () {
}
});
}
}
}
</script>
<style lang="scss">
.container {
position: absolute;
left: 0upx;
bottom: 0upx;
right: 0upx;
top: 0upx;
.scroll {
position: absolute;
left: 0upx;
bottom: 0upx;
right: 0upx;
top: 0upx;
.content-section {
padding: 0upx 40upx;
.novel-img{
width:100%;
}
}
.title-section {
padding: 20upx 40upx;
font-size: 24px;
font-weight: 600;
}
.bottom-section {
margin-top: 50upx;
display: flex;
justify-content: space-around;
align-items: center;
padding-bottom: 20upx;
.bottom-btn {
width: 200upx;
height: 70upx;
color: #000;
line-height: 70upx;
border-radius: 10upx;
background-color: #fff;
font-size: $uni-font-size-lg;
}
.bottom-night-btn {
background-color: #333;
color: #808080;
}
}
}
.slider-mask {
position: fixed;
top: 0upx;
left: 0upx;
bottom: 0upx;
right: 0upx;
display: flex;
transition: .7s;
background-color: rgba(0, 0, 0, 0);
visibility: hidden;
animation-delay: .2s;
.slider-section {
height: 100%;
width: 80%;
transform: translateX(-100%);
transition: .7s;
}
.slider-show-section {
transform: translateX(0);
}
}
.slider-show-mask {
visibility: visible;
background-color: rgba(0, 0, 0, 0.5);
}
.set-section {
position: fixed;
height: 200upx;
left: 0upx;
bottom: 0upx;
right: 0upx;
transform: translateY(100%);
transition: .7s;
}
.set-show-section {
transform: translateY(0);
}
.tool-section {
position: fixed;
height: 300upx;
left: 0upx;
bottom: 0upx;
right: 0upx;
transform: translateY(100%);
transition: .7s;
}
.tool-show-section {
transform: translateY(0);
}
}
</style>
uniapp打包生成h5网站
我们只需要把h5中的源码放到服务器上就行了,跟vue包部署的方式一毛一样
4.前端页面的部署
vue包必须放在服务器中才能正常运行,一般用的服务器有tomcat,apache,nginx等,像这种简单网站,用nginx是最方便的,启动服务并且可以随意调整域名端口啥的
正好宝塔的站点服务就是用nginx配置的
使用宝塔登录服务器,在放入站点文件夹中
然后在网站中加入一个站点就行了,推荐你起个二级域名,监听80端口,通过二级域名就可以访问你的网站了(nginx的好处就是可以随意监听不同域名的80端口,很方便)
到这里,网站的所有工作就做完啦,可以打开自己的域名,享受自己的劳动成果啦~~~
也可以直接看看我的劳动成果 :斗破苍穹漫画
扫描左侧二维码也可以直接看,有问题的童鞋直接关注公众号并发消息即可,我看到就会回复,欢迎来撩~