Go语言(Gin框架)实现传统文化读书软件

一、用GO语言实现

 

 

 

二、源代码公开,欢迎修改转载使用。

  1. https://pan.baidu.com/s/1NW_Am0WsjwrTtcfZKVTkOg 提取码: ptaw 

  2. 下载地址:https://files.cnblogs.com/files/javatiandi/DuRenZhou.rar

  3.  演示地址: http://106.12.161.76:8089/boat/index

 

  部分源代码如下:

package main

import (
	"DuRenZhou/src/conf"
	"DuRenZhou/src/controllers"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
)

func main() {

	router := gin.Default()
	router.StaticFS("/static", http.Dir("src/static"))
	router.LoadHTMLGlob("src/views/book/*")
	router.NoRoute(controllers.Handle404)
	router.StaticFile("/favicon.ico", "src/static/favicon/favicon.ico")

	//渡人舟
	boat := router.Group("/boat")
	{
		boat.GET("/index", controllers.BoatBookMainIndex)
		boat.POST("/clientInit", controllers.BoatClientInit)
		boat.POST("/client", controllers.BoatClient)
		boat.POST("/mark", controllers.BoatBookMark)
	}

	sererPort := conf.AppConf.ServerConfig.Port
	port := strconv.Itoa(sererPort)
	router.Run(":" + port)
}

  

package controllers

import (
	"DuRenZhou/src/conf"
	"fmt"
	"github.com/axgle/mahonia"
	"github.com/gin-gonic/gin"
	"io"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode/utf8"
)

type PageContent struct {
	BookId       string
	BookName     string
	BookRealPath string
	PWordsCount  int
	PContent     string
	ClientWidth  int
	ClientHeight int
	FontSize     int
	OriginalX    int
	OriginalY    int
	PageHeight   int
	LineSize     int
	MaxLetters   int
	PageNum      int
	TotalPage    int
	JumpMark     string
	ReadType     int
	JumpPageNum  int
}

type BookDetail struct {
	conf.Book
	JumpMark    string
	JumpPageNum int
	ReadType    int
}

var once sync.Once
var contentMap = make(map[string]string, 100)
var bookList = make([]conf.Book, 0)

func init() {
	once.Do(func() {
		initResources()
	})
}

func GetResourcesPath() (string, error) {
	getwd, err := os.Getwd()
	if err != nil {
		panic("初始化资源路径出错!")
		os.Exit(2)
	}
	resources := getwd + "\\src\\resources"
	//fmt.Println(resources)
	return resources, err
}

func listResources() []string {
	list := make([]string, 0)
	resourcesPath, err := GetResourcesPath()
	if err != nil {
		panic("初始化资源路径出错!")
		os.Exit(2)
	}
	dir, err := ioutil.ReadDir(resourcesPath)
	if err != nil {
		panic("初始化资源路径出错!")
		os.Exit(2)
	}
	for _, file := range dir {
		if file.IsDir() {
			continue
		} else {
			if strings.Contains(file.Name(), "jumpMark") {
				continue
			} else {
				list = append(list, file.Name())
			}
		}
	}
	return list
}
func initResources() {
	resourcesPath, err := GetResourcesPath()
	if err != nil {
		panic("初始化资源路径出错!")
		os.Exit(2)
	}
	resources := listResources()
	list := conf.AppConf.BookConfig.BookList
	for _, book := range list {
		for _, name := range resources {
			fmt.Println("书名:", name)
			if name == (book.BookName + ".txt") {
				book.RealPath = resourcesPath + "\\" + name
				fmt.Printf("书籍名称:%+v \n", book)
				bookList = append(bookList, book)
			}
		}
	}
}

func calcFontSize(deviceWidth int) int {
	/**
	321px <= device-width <= 375px,font-size:11px        --->  .item的width:34px
	376px <= device-width <= 414px,font-size:12px        --->  .item的width:37.4px
	415px <= device-width <= 639px,font-size:15px        --->  .item的width:40.8px
	640px <= device-width <= 719px,font-size:20px        --->  .item的width:51px
	720px <= device-width <= 749px,font-size:22.5px      --->  .item的width:76.5px
	750px <= device-width <= 799px,font-size:23.5px      --->  .item的width:79.8999999px
	800px <= device-width         ,font-size:25px        --->  .item的width:85px
	*/

	fontSize := 25
	switch {
	case deviceWidth <= 375:
		fontSize = 11
		break
	case deviceWidth <= 414:
		fontSize = 12
		break
	case deviceWidth <= 639:
		fontSize = 15
		break
	case deviceWidth <= 719:
		fontSize = 20
		break
	case deviceWidth <= 749:
		fontSize = 22
		break
	case deviceWidth <= 799:
		fontSize = 23
		break
	case deviceWidth > 799:
		fontSize = 25
		break
	default:
		fontSize = 25
	}
	fmt.Println(fontSize)
	return fontSize
}

func calcLineSize(pageHeight int, fontSize int) int {
	lineSize := 0
	tmp1 := float64(fontSize) * 1.5
	tmp2 := float64(pageHeight) / tmp1
	lineSize = int(math.Floor(tmp2))
	return lineSize
}

func BoatBookMainIndex(c *gin.Context) {
	list := bookList
	bookDetailList := make([]BookDetail, 0)
	for _, d := range list {
		//fmt.Println(d)
		bookDetail := BookDetail{}
		bookDetail.BookId = d.BookId
		bookDetail.BookName = d.BookName
		bookDetail.RealPath = d.RealPath
		//加载书签文件
		jumpMarkPath := strings.ReplaceAll(bookDetail.RealPath, ".txt", "_jumpMark.txt")
		b := exists(jumpMarkPath)
		if b {
			jumpMarkGBK := ReadFile(jumpMarkPath)
			dec := mahonia.NewDecoder("gbk")
			jumpMarkUTF8 := dec.ConvertString(jumpMarkGBK)
			//	fmt.Println("jumpMarkUTF8:", jumpMarkUTF8)
			bookDetail.JumpMark = jumpMarkUTF8
		}
		bookDetailList = append(bookDetailList, bookDetail)
	}
	data := make(map[string][]BookDetail, 1)
	data["bookDetailList"] = bookDetailList
	c.HTML(200, "mainIndex.tmpl", data)
}

// 判断所给路径文件/文件夹是否存在
func exists(path string) bool {
	_, err := os.Stat(path) //os.Stat获取文件信息
	if err != nil {
		if os.IsExist(err) {
			return true
		}
		return false
	}
	return true
}

func BoatClientInit(c *gin.Context) {
	bookId := c.PostForm("bookId")
	readTypeStr := c.PostForm("readType")
	bookName := c.PostForm("bookName")
	jumpMark := c.PostForm("jumpMark")
	jumpPageNumStr := c.PostForm("jumpPageNum")
	fmt.Println("书名:", bookName, "BookId:", bookId, "readType:", readTypeStr)
	//fmt.Println("书签:", jumpMark)
	//fmt.Println("跳转到:", jumpPageNumStr)
	book := conf.Book{}
	list := bookList
	for _, d := range list {
		if string(d.BookName) == bookName {
			book.BookId = d.BookId
			book.BookName = d.BookName
			book.RealPath = d.RealPath
			break
		}
	}
	bookDetail := BookDetail{}
	bookDetail.BookId = book.BookId
	bookDetail.BookName = book.BookName
	bookDetail.RealPath = book.RealPath
	bookDetail.JumpMark = jumpMark
	jumpPageNum, err := strconv.Atoi(jumpPageNumStr)
	if err != nil {
		jumpPageNum = 1
	}
	bookDetail.JumpPageNum = jumpPageNum
	bookDetail.JumpMark = jumpMark
	readType, err := strconv.Atoi(readTypeStr)
	if err != nil {
		readType = 0
	}
	bookDetail.ReadType = readType
	c.HTML(200, "client.tmpl", bookDetail)
}

func BoatClient(c *gin.Context) {
	pageContent := initPageContent(c)
	//fmt.Printf("pageContent %+v\n", *pageContent)
	var bookContentUTF8 string = ""
	if _, ok := contentMap[pageContent.BookName]; ok {
		bookContentUTF8 = contentMap[pageContent.BookName]
	} else {
		bookContentGBK := ReadFile(pageContent.BookRealPath)
		dec := mahonia.NewDecoder("gbk")
		bookContentUTF8 = dec.ConvertString(bookContentGBK)
		//去除任意的空白符
		//reg := regexp.MustCompile(`[\s]+`)
		//bookContentUTF8 = reg.ReplaceAllString(bookContentUTF8, "")
		contentMap[pageContent.BookName] = bookContentUTF8
	}

	if pageContent.ReadType == 1 {
		//jumpMark 处开始阅读
		index := findZhongWenSubStrIndex(bookContentUTF8, pageContent.JumpMark)
		if index == -1 {
			pageContent.PageNum = 1
		} else {
			//根据index 设置PageNum
			if index%pageContent.MaxLetters == 0 {
				pageNum := index / pageContent.MaxLetters
				if pageNum == 0 {
					pageContent.PageNum = 1
				} else {
					pageContent.PageNum = pageNum
				}
			} else {
				pageContent.PageNum = index/pageContent.MaxLetters + 1
			}
		}
	} else if pageContent.ReadType == 2 {
		//jumpPageNumStr 跳转到第 jumpPageNum页开始阅读
		pageContent.PageNum = pageContent.JumpPageNum
	} else if pageContent.ReadType == 3 {
		//readType 3   直接从头开始阅读
		pageContent.PageNum = 1
	}

	totalWords := utf8.RuneCountInString(bookContentUTF8)
	if totalWords%pageContent.MaxLetters == 0 {
		pageContent.TotalPage = totalWords / pageContent.MaxLetters
	} else {
		pageContent.TotalPage = totalWords/pageContent.MaxLetters + 1
	}
	if pageContent.PageNum > pageContent.TotalPage {
		c.HTML(http.StatusOK, "pagefinish.tmpl", nil)
		return
	}
	firstIndex := (pageContent.PageNum - 1) * pageContent.MaxLetters
	lastIndex := pageContent.PageNum * pageContent.MaxLetters
	if lastIndex+1 >= totalWords {
		lastIndex = totalWords - 1
	}
	runes := []rune(bookContentUTF8)
	content := string(runes[firstIndex:lastIndex])
	pageContent.PContent = content
	//fmt.Println("本页长度:", utf8.RuneCountInString(pageContent.PContent), "完整切割:本页内容")
	//fmt.Println(pageContent.PContent)
	c.HTML(http.StatusOK, "page.tmpl", pageContent)
}

func BoatBookMark(c *gin.Context) {
	jumpMark := c.PostForm("jumpMark")
	bookId := c.PostForm("bookId")
	book := conf.Book{}
	list := bookList
	for _, d := range list {
		if string(d.BookId) == bookId {
			book.BookId = d.BookId
			book.BookName = d.BookName
			book.RealPath = d.RealPath
			break
		}
	}
	//加载书签文件
	jumpMarkPath := strings.ReplaceAll(book.RealPath, ".txt", "_jumpMark.txt")
	WriteFileGBK(jumpMarkPath, jumpMark)
	c.JSON(200, gin.H{"message": "保留书签成功!"})
}

func WriteFileGBK(filePath string, content string) {
	/*
	   O_RDONLY int = syscall.O_RDONLY // 只读打开文件和os.Open()同义
	   O_WRONLY int = syscall.O_WRONLY // 只写打开文件
	   O_RDWR   int = syscall.O_RDWR   // 读写方式打开文件
	   O_APPEND int = syscall.O_APPEND // 当写的时候使用追加模式到文件末尾
	   O_CREATE int = syscall.O_CREAT  // 如果文件不存在,此案创建
	   O_EXCL   int = syscall.O_EXCL   // 和O_CREATE一起使用, 只有当文件不存在时才创建
	   O_SYNC   int = syscall.O_SYNC   // 以同步I/O方式打开文件,直接写入硬盘.
	   O_TRUNC  int = syscall.O_TRUNC  // 如果可以的话,当打开文件时先清空文件
	*/

	_, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	enc := mahonia.NewEncoder("gbk")
	convertString := enc.ConvertString(content)
	bytes := []byte(convertString)
	ioutil.WriteFile(filePath, bytes, 0644)
}

func findZhongWenSubStrIndex(str string, substr string) int {
	if strings.Contains(str, substr) {
		runes := []rune(str)
		firstIndex := 0
		totalWords := utf8.RuneCountInString(str)
		for {
			lastIndex := firstIndex + utf8.RuneCountInString(substr)
			content := string(runes[firstIndex:lastIndex])
			if content == substr {
				break
			}
			if lastIndex > totalWords-1 {
				break
			}
			firstIndex++
		}
		return firstIndex
	} else {
		return -1
	}
}

func ReadFile(filePath string) string {
	strBuilder := strings.Builder{}
	f, err := os.Open(filePath)
	if err != nil {
		fmt.Println("can't opened this file")
	}
	defer f.Close()
	b := make([]byte, 1024)
	var counter int = 0
	for {
		read, err := f.Read(b)
		if err != nil && err != io.EOF {
			log.Println("读文章出错!", err)
			strBuilder.WriteString("")
			break
		}
		if err == io.EOF {
			strBuilder.WriteString("")
			break
		}
		s := string(b[0:read])
		strBuilder.WriteString(s)
		counter++
	}
	//解决文件读取比较慢,获取内容跟不上下面的分页程序的问题
	var str string
	var spinningCounter int = 0
	for {
		time.Sleep(200 * time.Millisecond)
		if counter == 0 {
			str = ""
			break
		}
		str = strBuilder.String()
		if len(str) > 1 {
			break
		}
		if spinningCounter > 200 {
			break
		}
		spinningCounter++
	}
	return str
}

func initPageContent(c *gin.Context) *PageContent {
	cWidth := c.PostForm("cWidth")
	clientWidth, _ := strconv.Atoi(cWidth)

	cHeight := c.PostForm("cHeight")
	clientHeight, _ := strconv.Atoi(cHeight)

	fontSize := calcFontSize(clientWidth)
	pageHeight := clientHeight - fontSize*5
	lineSize := calcLineSize(pageHeight, fontSize)
	originalX := clientWidth - fontSize*4
	originalY := fontSize * 3

	bookId := c.PostForm("bookId")
	pageNumStr := c.PostForm("pageNum")
	pageNum, err := strconv.Atoi(pageNumStr)
	if err != nil {
		pageNum = 1
	}
	maxLettersStr := c.PostForm("maxLetters")
	maxLetters, err := strconv.Atoi(maxLettersStr)
	if err != nil {
		maxLetters = ((originalX - fontSize*2) / (fontSize * 2)) * lineSize
		maxLetters = (lineSize - maxLetters%lineSize) + maxLetters
	}
	book := findByBookId(bookId)
	pageContent := &PageContent{
		BookId:       bookId,
		BookName:     book.BookName,
		BookRealPath: book.RealPath,
		PWordsCount:  0,
		PContent:     "",
		ClientWidth:  clientWidth,
		ClientHeight: clientHeight,
		FontSize:     fontSize,
		OriginalX:    originalX,
		OriginalY:    originalY,
		PageHeight:   pageHeight,
		LineSize:     lineSize,
		MaxLetters:   maxLetters,
		PageNum:      pageNum,
		TotalPage:    0,
	}

	//处理阅读书签
	readTypeStr := c.PostForm("readType")
	jumpMark := c.PostForm("jumpMark")
	jumpPageNumStr := c.PostForm("jumpPageNum")
	readType, err := strconv.Atoi(readTypeStr)
	jumpPageNum, err := strconv.Atoi(jumpPageNumStr)
	pageContent.JumpMark = jumpMark
	if err != nil {
		readType = 0
	}
	pageContent.ReadType = readType
	if err != nil {
		jumpPageNum = 1
	}
	pageContent.JumpPageNum = jumpPageNum
	return pageContent
}

func findByBookId(bookId string) conf.Book {
	book := conf.Book{}
	list := bookList
	for _, d := range list {
		if string(d.BookId) == bookId {
			book.BookId = bookId
			book.BookName = d.BookName
			book.RealPath = d.RealPath
			break
		}
	}
	return book
}

/***
GBK 1个字占 2个字节 2byte
1MB = 1024 kb
1kb = 1024 b

所以 1MB 的书可以装多少字
1kb = 512 个字
1MB 可以装  1024 * 512 = 524,288  52 万字
100MB 可以装   52 * 100 = 5200 万字


*/

  

<!DOCTYPE >
<html>
	<head>
		<title>传统文化书单</title>
	</head>
	<style>
		html,
		body {
			width: 100%;
			height: 100%;
			margin: 0px;
			background: url(/static/book/img/page.png) repeat-x;
			
		}
		.main{
			padding-top: 100px;
			width: 100%;
			height: 100%;
			overflow: hidden;
			height: 1800px;
		}
		.row{
			padding-top: 20px;
			padding-left: 10%;
			width: 100%; 
			text-align: center;
			 
		}
		.book{
			background: url(/static/book/img/book.png) no-repeat center;
			margin: 10px;
			display: inline-block;
			width: 300px ;
			height: 180px;
			padding-top: 10px;
			font-size: 40px;
			float: left; 
			cursor:pointer;
			cursor: hand;
		}
		.clearFloat{
			clear: both;
		}
		.zi  {  	  

			margin-top: 30px;
			margin-left: 108px;
			line-height: 40px; 
			writing-mode: vertical-rl;
			-webkit-writing-mode: vertical-rl;
			-ms-writing-mode: vertical-rl; 
			font-size: 14px;
			font-family: '楷体';
			float: left; 
		}
		.menu{ 
			font-size:12px ;
			background: #DDDDDD ;
			opacity: 0.9;
			width: 200px;
			text-align: left;
			visibility: hidden;
		}
		.menu ul{
			padding:2px;
			line-height: 30px;
			list-style: none;
			cursor: hand;
			cursor: pointer;
		}
		ul li:hover {
			background: #32CD32;
		}
		.jump{
			width: 20px;
		}
		
	</style>
	<body>
		<div class="main">
			<div class="row">
              {{range .bookDetailList}}
                    <div class="book">
                        <div class="zi"  myBookId="{{.BookId}}"  myJumpMark="{{.JumpMark}}" >{{.BookName}}</div>
                    </div>
              {{end}}
			</div>
			<div class="clearFloat"></div>
			<div id="menu" class="menu"  >
				<ul>
					<li id="startReadLi" ><a>开始阅读</a></li>
					<li id="jumpMarkLi"  >跳转到标签:<a id="jumpMarkText">孝者天之经地之义也...</a></li>
					<li>跳转到第<input type="text" id="jumpInput"  onclick="null"  onbeforepaste="jumpPaste()"   onchange="jumpChange(this)"    class="jump" value="1">页<a id="jumpPageNumLi" >【GO】</a></li>
				</ul>
			</div>
			<div class="form">
				<form id="form1" method="post" action="/boat/clientInit">
					<input type="hidden" id="bookName" name="bookName" value="" />
					<input type="hidden" id="bookId"  name="bookId" value="" />
					<input type="hidden" id="jumpMark" name="jumpMark" value=""  />
					<input type="hidden" id="jumpPageNum"  name="jumpPageNum" value="1" />
					<input type="hidden" id="readType" name="readType"  value="3" />
				</form>
			</div>
		</div>

	</body>
	<script>
		var mousePosition = {};
		function mouseMove(ev) {
			Ev = ev || window.event;
			var mousePos = mouseCoords(ev); 
			mousePosition['x'] = mousePos.x;
			mousePosition['y'] = mousePos.y;
		} 
		function mouseCoords(ev) {
			if (ev.pageX || ev.pageY) {
				return {
					x: ev.pageX,
					y: ev.pageY
				};
			}
			return {
				x: ev.clientX + document.body.scrollLeft - document.body.clientLeft,
				y: ev.clientY + document.body.scrollTop - document.body.clientTop
			};
		}
		document.onmousemove = mouseMove;
	</script>


	<script>
		var timeout ;
		var menu={
		    bookId:"",
			bookName:"",
			jumpMark:"",
			jumpPageNum:0,
			readType:0
		};  
		function showMenu(objBook) {
		    var menuDiv =document.getElementById("menu");
			menuDiv.style.visibility = "visible";
		    menuDiv.style.left = mousePosition.x + 'px';  
			menuDiv.style.top = mousePosition.y + 'px';  
			menuDiv.style.position='absolute';
			var jumpMarkText = document.getElementById("jumpMarkText");

			var mark = objBook.jumpMark;
			mark = mark.replace(/\s*/g,"")
			jumpMarkText.innerText = mark;

			clearTimeout(timeout);
			hiddenTimeOut(menuDiv,20000);
            menu.bookId = objBook.bookId;
		    menu.bookName = objBook.bookName;
            menu.jumpMark = objBook.jumpMark;

		} 
		
		function hiddenTimeOut(obj,milliseconds){
		   timeout = window.setTimeout(function(){
				if(obj.style.visibility = "visible"){
					obj.style.visibility = "hidden"; 
				} 
			},milliseconds);
		}
		
		function submitRead(thisMenu){
		    document.getElementById("bookId").value = thisMenu.bookId;
			document.getElementById("bookName").value = thisMenu.bookName;
			document.getElementById("jumpMark").value = thisMenu.jumpMark;
			try{
              var thisJumpPageNum = parseInt(thisMenu.jumpPageNum);
              document.getElementById("jumpPageNum").value = thisJumpPageNum;
              if((thisJumpPageNum<=0) && (thisMenu.readType==2)){
                 alert("请输入正确的页码!");
                 return
              }
            }catch(e){
              alert("请输入正确的页码!");
            }
			document.getElementById("readType").value = thisMenu.readType;
			document.getElementById("form1").submit();
		}

		function jumpPaste(){
            window.clipboardData.setData('text',clipboardData.getData('text').replace(/^[0-9]*$/g,''));
        }


        function jumpChange(obj){
            if(!/^[0-9]*$/.test(obj.value)){
               obj.value='';
               alert('请输入正确的页码!');
           }
        }

		
		window.onload = function() { 
			document.getElementById("startReadLi").onclick = function(event){
				menu.readType = 3;
				submitRead(menu);
			    event.stopPropagation(); 
			} 
			document.getElementById("jumpMarkLi").onclick = function(event){
				menu.readType = 1;
				submitRead(menu);
			    event.stopPropagation(); 
			} 
			document.getElementById("jumpPageNumLi").onclick = function(event){
				menu.readType = 2;
                menu.jumpPageNum = document.getElementById("jumpInput").value;
				submitRead(menu);
			    event.stopPropagation(); 
			} 
			var books = document.getElementsByClassName("book"); 
			for (var i = 0; i < books.length; i++) {
				var book = books[i];
				book.onclick = function() {
				     var myBookId = this.children[0].getAttribute("myBookId");
				     var myBookName = this.children[0].innerText;
				     var myJumpMark = this.children[0].getAttribute("myJumpMark");
				     var bookQuery = {
				         bookId : myBookId,
				         bookName : myBookName,
				         jumpMark : myJumpMark
				     };

                     showMenu(bookQuery);
				}
			}

		}
	</script>

</html>

  

<!DOCTYPE html>
<html>
	<head lang="en">
		<style>
			html,
			body {
				width: 100%;
				height: 100%;
				margin: 0px;
				/* overflow: hidden; */
				/* background-image: linear-gradient(#BEBEBE,#778899) ; */
				background: url(/static/book/img/page.png) repeat-x;
			    cursor:pointer;
				cursor: hand;
			}
			.main{
				text-align: center;
				padding: 10px; 
			}
			.page{
				
			} 
			.pageFoot{
				width: 100%;
				padding-top: 10px;
				padding-bottom: 10px ;
				/* height: 40px; */
				/* border: 1px solid red;*/
				text-align: center;
			}
			.prev{
				border: 1px solid #008080; 
				background:#E6E6FA;
				padding-top: 5px;
			    padding-bottom: 5px;
                padding-left: 60px;
				padding-right: 60px;
			    border-radius: 2px;
				text-decoration: none;
				/* font-size: 20px; */
			}
			.next{
				border: 1px solid #008080;
				background: #F0F8FF;
				padding-top: 5px;
				padding-bottom: 5px;
				padding-left: 60px;
				padding-right: 60px;
				border-radius: 2px;  
				text-decoration: none;
				/* font-size: 20px; */
			}  
			.current {
				border: 1px solid #008080;
				background: #54FF9F;
				padding-top: 5px;
				padding-bottom: 5px;
				padding-left: 60px;
				padding-right: 60px;
				border-radius: 2px;
				text-decoration: none;
			}
			.helpWin{
                border: 1px solid #008080;
                background: #7FFF00;
                padding-top: 5px;
                padding-bottom: 5px;
                padding-left: 60px;
                padding-right: 60px;
                border-radius: 2px;
                text-decoration: none;
            }
            .mark{
                border: 1px solid #008080;
                background: #7FFF00;
                padding-top: 5px;
                padding-bottom: 5px;
                padding-left: 60px;
                padding-right: 60px;
                border-radius: 2px;
                text-decoration: none;
            }
            .backMain{
                border: 1px solid #008080;
                background: #7FFF00;
                padding-top: 5px;
                padding-bottom: 5px;
                padding-left: 60px;
                padding-right: 60px;
                border-radius: 2px;
                text-decoration: none;
            }
			a:hover{  
				background: limegreen;
				text-decoration: none;
			}
			.jumpBox{
                border: 1px solid #008080;
                background: #7FFF00;
                padding-left: 10px;
                padding-right: 10px;
                border-radius: 2px;
                padding-top: 5px;
                padding-bottom: 5px;
            }
            .jump{
                display: inline-block;
                border: 1px solid #008080;
                border-bottom: 1px solid #000000 ;
                height: 25px;
                margin-bottom: 20x;
                border-radius: 2px;
                width: 40px;
                text-align: center;
            }


			.searchTxt{
				margin: 0;
				padding: 0;
				height: 30px;
			}
			.searchTxt input{
				display: inline-block;
				height: 25px;
				width: 92%;
				float: left;
			}
             .searchTxt .booklogo{
                    display: inline-block;
                    height: 30px;
                    border-radius: 2px ;
                    float: left;

             }
             .searchTxt .searchlogo{
                 display: inline-block;
                 height: 30px;
                 background: #7FFF00;
                 border: 1px solid #DDDDDD;
                 border-radius: 2px ;
                 float: left;
             }
		</style>
		<meta charset="UTF-8">
		<title></title>

	</head>
	<body>
		<div class="main"  >
			<canvas id='canvas' class="page" ></canvas>
		</div>
        <div class="searchTxt">
            <img class="booklogo" src="/static/book/img/booklittle.png" />
            <input type="text" id="searchTxt"   name="searchTxt"   value="" />
            <img class="searchlogo"  onclick="doSearch()"   src="/static/book/img/search.png" />
        </div>
		<div class="pageFoot">
			<a class="prev" onclick="prev()" ><</a>
			<a class="current" onclick="current()" >刷新</a>
			<a class="next"  onclick="next()" >></a>
			<a class="helpWin"  onclick="doHelp()" href="javascript:void(0);">帮助</a>
			<a class="mark"  onclick="mark()" href="javascript:void(0);">保存书签</a>
			<a class="backMain"  onclick="backMain()" href="javascript:void(0);">返回主页</a>
			<a class="jumpBox" onclick="jump()" >共{{.TotalPage}}页,当前第{{.PageNum}}页,转到<input type="text" id="jumpPageNum"  onclick="null"  onbeforepaste="jumpPaste()"   onchange="jumpChange(this)"   class="jump" />页</a>
		</div>

        <div>
            <form id="form1" method="post" action="/boat/client" >
                <input type="hidden" id="bookId" name="bookId" value="{{.BookId}}" />
                <input type="hidden" id="pageNum" name="pageNum" value="{{.PageNum}}" />
                <input type="hidden" id="maxLetters" name="maxLetters" value="{{.MaxLetters}}" />
                <input type="hidden" id="cWidth" name="cWidth" value="{{.ClientWidth}}" />
                <input type="hidden" id="cHeight" name="cHeight" value="{{.ClientHeight}}" />
            </form>
        </div>

        <script>
      var mousePosition = {};
            function mouseMove(ev) {
                Ev = ev || window.event;
                var mousePos = mouseCoords(ev);
                mousePosition['x'] = mousePos.x;
                mousePosition['y'] = mousePos.y;
                inputSearchText(mousePos.x, mousePos.y);
            }

            function mouseCoords(ev) {
                if (ev.pageX || ev.pageY) {
                    return {
                        x: ev.pageX,
                        y: ev.pageY
                    };
                }
                return {
                    x: ev.clientX + document.body.scrollLeft - document.body.clientLeft,
                    y: ev.clientY + document.body.scrollTop - document.body.clientTop
                };
            }
            document.onmousemove = mouseMove;
        </script>

		<script>
		    var bookId = {{.BookId}}
		    var fontSize = {{.FontSize}};
			var orginalX = {{.OriginalX}};
			var orginalY = {{.OriginalY}};
			var pageHeight = {{.PageHeight}};
			var lineSize =  {{.LineSize}};
			var position = {
				x: orginalX,
				y: orginalY
			};
			var article = ' '+'{{.PContent}}';
			var canvas;
			var context;
            var maxLetters = {{.MaxLetters}} ;

			function init() {
				canvas = document.getElementById('canvas');
				context = canvas.getContext('2d');
				canvas.width =  {{.ClientWidth}};
				canvas.height = {{.ClientHeight}};
				drawWenZhang();
				window.onresize = function(ev) {
					var oEvent = event || ev;
					canvas.width =  {{.ClientWidth}};
					canvas.height = {{.ClientHeight}};
					 
				}
			}

        var contentAry = [];
            var positionAry = [];
            var index = 0;

            function inputSearchText(mouseX,mouseY) {
                if(inMaxMinArea(mouseX,mouseY)){
                    for (var i = 0; i < positionAry.length; i++) {
                        var p = positionAry[i];
                        var AryX = p.split("##");
                        if (mouseX > parseFloat(AryX[0]) && mouseX < parseFloat(AryX[1])) {
                            index = i+1;
                            break;
                        }
                    }
                 if(index<contentAry.length+1){
                      document.getElementById("searchTxt").value = contentAry[index];
                 }
                }
            }

            function inMaxMinArea(posX,posY){
                 var maxY = pageHeight;
                 if(posY>=parseFloat(maxY)){
                    return false;
                 }
                if(positionAry.length>1){
                    minX = positionAry[positionAry.length-1].split("##")[0];
                    maxX = positionAry[0].split("##")[1]
                    if(posX>=parseFloat(minX) && posX<=parseFloat(maxX)){
                        return true;
                    }
                }
                return false;
            }


        function doSearch(){
               var searchTxt = document.getElementById("searchTxt").value;
                 window.open("https://www.baidu.com/s?wd="+ searchTxt);
         }



           function drawWhiteSpace(){
              context.font = fontSize + "px Georgia";
              context.save();
              context.translate(position.x, position.y);
              context.fillText("  ", 0, 0);
              context.restore();
              position.y = position.y + fontSize * 1.5;
           }
            var strTemp = "";
            var lastStrTemp = "";
			function drawWenZhang() {
                //drawWhiteSpace();
                var cnt = 0;
				for (var i = 0; i < article.length; i++) {
					context.font = fontSize + "px Georgia";
                    context.save();
                    if(i==0){
                        context.translate(position.x,position.y+fontSize * 1.5);
                    }else{
                       context.translate(position.x,position.y);
                    }
                    var ch = undefined ;
                    try{
                      ch = article[i];
                      if(cnt>(article.length -  lineSize)){
                          lastStrTemp = lastStrTemp + article[i];
                      }
                    }catch(e){
                       context.restore();
                       break;
                    }
                    if(ch == undefined){
                       context.fillText(' ', 0, 0);
                       context.restore();
                       break;
                    }else{
                       strTemp = strTemp + ch;
                       context.fillText(ch, 0, 0);
                       context.restore();
                    }
                    if (cnt == article.length-1){
                        break;
                    }
                    if (cnt % lineSize == 0) {
                        contentAry.push(strTemp);
                        strTemp = "";
                        context.save();
                        var flag = 0;
                        if(cnt>0){
                           flag=1;
                        }
                        position.x = position.x - fontSize * 2*flag;
                        position.y = orginalY ;
                        context.moveTo(position.x - ((fontSize*2 - fontSize)/2.0) , orginalY);
                        context.lineTo(position.x - ((fontSize*2 - fontSize)/2.0) , orginalY+pageHeight);
                        var x1 = position.x + fontSize ;
                        var x2 = x1 + fontSize*2 ;
                        positionAry.push(x1 + "##" + x2);
                        context.stroke();
                        context.restore();
                     }
                    cnt++;
                    position.y = position.y + fontSize * 1.5;
				}
					contentAry.push(lastStrTemp);
			} 


            function jump(){
               var jumpPageNumStr =  document.getElementById("jumpPageNum").value;
               if(jumpPageNumStr!=""){
                   try{
                      var jumpPageNum = parseInt(jumpPageNumStr);
                      document.getElementById("pageNum").value=jumpPageNum;
                      if(jumpPageNum>0){
                         document.getElementById("form1").submit();
                      }else{
                         alert("请输入正确的页码!");
                      }
                   }catch(e){
                      alert("请输入正确的页码!");
                      return
                   }
               }
            }

            function jumpPaste(){
                window.clipboardData.setData('text',clipboardData.getData('text').replace(/^[0-9]*$/g,''));
            }


			function jumpChange(obj){
			    if(!/^[0-9]*$/.test(obj.value)){
			       obj.value='';
			       alert('输入数字!');
			   }
			}

            function urlencode (str) {
                        str = (str + '').toString();
                        return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').
                        replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
                }
            function doHelp() {
                var searchTxt = document.getElementById("searchTxt").value;
                if(searchTxt.length>1){
                    alert("条目太长,限一个生字");
                }else{
                    window.open("https://www.zdic.net/hans/"+urlencode(searchTxt));
                }

            }
			function prev(){
                var pageNum = document.getElementById("pageNum").value;
                if( pageNum == "" || pageNum == 1 ){
                    document.getElementById("pageNum").value = 1;
                }else{
                    document.getElementById("pageNum").value= parseInt(pageNum) - 1;
                }
                document.getElementById("form1").submit();
            }

            function current(){
                var pageNum = document.getElementById("pageNum").value;
                if( pageNum == "" || pageNum == 1 ){
                    document.getElementById("pageNum").value = 1;
                }
                document.getElementById("form1").submit();
            }

			function next(){
			    var pageNum = document.getElementById("pageNum").value;
			    if( pageNum == "" ){
                    document.getElementById("pageNum").value = 1;
			    }else{
			        document.getElementById("pageNum").value= parseInt(pageNum) + 1;
			    }
                document.getElementById("form1").submit();
			}


            function mark(){
                var content  =  article;
                var firstIndex = 1;
                var re = /[^'"“”?‘’《》○\s]+/;
                var mark = "";
                for(var i=0;i<content.length;i++){
                   var myArray = null ;
                   var lastIndex = firstIndex + ((content.length)/5) -1;
                   if (lastIndex<=1){
                      mark = "";
                      break;
                   }
                   if(lastIndex>content.length-1){
                      break;
                   }
                   var str1 = content.substring(firstIndex,lastIndex);
                   myArray = str1.match(re);
                   if((myArray==null) || (myArray=="") || (myArray.length==0)){
                       continue;
                   }

                   for(var j=0;j<myArray.length;j++){
                        var  markTemp = myArray[j];
                        if(markTemp.length>mark.length){
                           mark = markTemp;
                        }
                   }
                   firstIndex++;
                }

                if(mark.length>1){
                   if( mark.substring(0,1)==","){
                      mark = mark.substring(1);
                   }
                }
                alert(mark);
                send_request(mark,bookId);
            }

            function backMain(){
                window.location.href = "/boat/index";
            }
            function send_request( mark,bookId ){
                var xmlhttp = null;
                if (window.XMLHttpRequest){
                    xmlhttp=new XMLHttpRequest();
                }else if (window.ActiveXObject){
                    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
                }
                if (xmlhttp!=null){
                    var requrl = '/boat/mark';
                    xmlhttp.open( "POST",requrl,true);
                    xmlhttp.onreadystatechange=function(){
                        //异步需要指定回调函数
                        if (xmlhttp.readyState==4 && xmlhttp.status==200){
                           //readyState为4,表示ajax请求已经完成,status是目标url返回的http状态码,200表示服务器响应成功
                        var d= xmlhttp.responseText;
                        // 处理返回结果
                           alert(d);
                      }
                    }
                    //post需要设置Content-type,防止乱码
                    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
                    //post需要将参数放在send方法,当然参数放在url也还是可以的,但不好
                    var jumpMarkStr = mark;
                    xmlhttp.send("jumpMark="+jumpMarkStr+"&bookId="+ bookId);
                }else{
                    alert("您的浏览器不支持AJAX!");
                }
            }

			window.onload = function(){
				init();
			} 	
		</script>
	</body>
</html>

  2.效果如下:

 

 

源代码公开,欢迎修改转载使用。

 https://pan.baidu.com/s/1NW_Am0WsjwrTtcfZKVTkOg 提取码: ptaw 

下载地址:https://files.cnblogs.com/files/javatiandi/DuRenZhou.rar

 

posted @ 2020-02-03 16:22  和平鸽  阅读(787)  评论(0编辑  收藏  举报