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
部分源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 | 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 万字 */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | <!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> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 | <!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
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 10亿数据,如何做迁移?
· 推荐几款开源且免费的 .NET MAUI 组件库
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· c# 半导体/led行业 晶圆片WaferMap实现 map图实现入门篇
· 易语言 —— 开山篇