mirror of
https://github.com/Kakune55/ComiPy.git
synced 2025-05-06 18:29:26 +08:00
Merge branch 'dev'
This commit is contained in:
commit
86a47a8aab
@ -15,3 +15,8 @@ path=./data/metadata.db
|
|||||||
inputdir=./input
|
inputdir=./input
|
||||||
storedir=./data/file
|
storedir=./data/file
|
||||||
tmpdir=./data/tmp
|
tmpdir=./data/tmp
|
||||||
|
|
||||||
|
[img]
|
||||||
|
encode=jpg
|
||||||
|
miniSize=400
|
||||||
|
fullSize=1000
|
97
db/comments.py
Normal file
97
db/comments.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import time
|
||||||
|
import db.util as util
|
||||||
|
|
||||||
|
|
||||||
|
# 查找评论
|
||||||
|
def getById(id: str):
|
||||||
|
"通过id查找所有评论"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
cursor = c.execute("SELECT * FROM Comments WHERE id = ?", (id,))
|
||||||
|
return cursor.fetchone()
|
||||||
|
|
||||||
|
# 查找评论
|
||||||
|
def listByBookid(id: str):
|
||||||
|
"通过bookid查找所有评论"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
cursor = c.execute("SELECT * FROM Comments WHERE bookid = ? ORDER BY time desc", (id,))
|
||||||
|
out = []
|
||||||
|
for row in cursor:
|
||||||
|
out.append(row)
|
||||||
|
conn.close()
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# 获取综合评分
|
||||||
|
def getScore(bookid: str):
|
||||||
|
"获取综合评分 返回一个字典 字典有两个key like和dislike分别记录不同评论的个数"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
cursor = c.execute("SELECT * FROM Comments WHERE bookid = ? ", (bookid,))
|
||||||
|
num={'like':0,'dislike':0}
|
||||||
|
for row in cursor:
|
||||||
|
if row[4] == "like":
|
||||||
|
num["like"]+=1
|
||||||
|
elif row[4] == "dislike":
|
||||||
|
num["dislike"]+=1
|
||||||
|
conn.close()
|
||||||
|
return num
|
||||||
|
|
||||||
|
|
||||||
|
# 查找评论
|
||||||
|
def searchByUid(uid: str):
|
||||||
|
"通过用户查找所有评论"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
cursor = c.execute("SELECT * FROM Comments WHERE from_uid = ? ORDER BY time desc", (uid,))
|
||||||
|
out = []
|
||||||
|
for row in cursor:
|
||||||
|
out.append(row)
|
||||||
|
conn.close()
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# 查找评论
|
||||||
|
def searchByAll(uid: str,bookid:str):
|
||||||
|
"通过用户和BookID查找所有评论"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
cursor = c.execute("SELECT * FROM Comments WHERE from_uid = ? AND bookid= ? ORDER BY time desc", (uid,bookid))
|
||||||
|
out = []
|
||||||
|
for row in cursor:
|
||||||
|
out.append(row)
|
||||||
|
conn.close()
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# 在数据库中添加一个新的文件记录
|
||||||
|
def new(bookid: str, from_uid: int, score: str, content=""):
|
||||||
|
"添加一条新评论 score字段可选值为[like,none,dislike] content字段非必填"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO Comments
|
||||||
|
(time, bookid, from_uid, score, content)
|
||||||
|
VALUES
|
||||||
|
(?, ?, ?, ?,?);
|
||||||
|
""",
|
||||||
|
(int(time.time()), bookid, from_uid, score, content),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 查找评论
|
||||||
|
def remove(id:int)->bool:
|
||||||
|
"通过id删除评论"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("DELETE FROM Comments WHERE id = ?", (id,))
|
||||||
|
conn.commit()
|
||||||
|
changes = conn.total_changes
|
||||||
|
conn.close()
|
||||||
|
if changes == 0:
|
||||||
|
return False
|
||||||
|
return True
|
20
db/user.py
20
db/user.py
@ -33,3 +33,23 @@ def check(username: str, password: int):
|
|||||||
if cursor.fetchone() is None:
|
if cursor.fetchone() is None:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def getUid(username: str):
|
||||||
|
"判断用户名是否存在 并获取用户uid 用户不存在则返回None"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
cursor = c.execute("SELECT * FROM User WHERE username = ?", (username,))
|
||||||
|
out = cursor.fetchone()
|
||||||
|
if out is not None:
|
||||||
|
return out[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getUsername(uid:str):
|
||||||
|
"判断Uid是否存在 并获取用户名 用户不存在则返回None"
|
||||||
|
conn = util.getConn()
|
||||||
|
c = conn.cursor()
|
||||||
|
cursor = c.execute("SELECT * FROM User WHERE uid = ?", (uid,))
|
||||||
|
out = cursor.fetchone()
|
||||||
|
if out is not None:
|
||||||
|
return out[1]
|
||||||
|
return None
|
24
db/util.py
24
db/util.py
@ -31,5 +31,29 @@ def init():
|
|||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
c.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS Comments (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
time INT NOT NULL,
|
||||||
|
bookid TEXT NOT NULL,
|
||||||
|
from_uid INTEGAR NOT NULL,
|
||||||
|
score INT NOT NULL,
|
||||||
|
content TEXT
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
c.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO User (username, password)
|
||||||
|
SELECT ?, ?
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM User WHERE username = ?);
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
conf.get("user", "username"),
|
||||||
|
conf.get("user", "password"),
|
||||||
|
conf.get("user", "username"),
|
||||||
|
),
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
48
file.py
48
file.py
@ -1,11 +1,12 @@
|
|||||||
import shutil, os, zipfile, io
|
import shutil, os, zipfile, io, cv2, numpy as np
|
||||||
|
|
||||||
import db.file, app_conf
|
import db.file, app_conf
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
app_conf = app_conf.conf()
|
app_conf = app_conf.conf()
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
paths = ("inputdir","storedir","tmpdir")
|
paths = ("inputdir", "storedir", "tmpdir")
|
||||||
for path in paths:
|
for path in paths:
|
||||||
try:
|
try:
|
||||||
os.makedirs(app_conf.get("file", path))
|
os.makedirs(app_conf.get("file", path))
|
||||||
@ -66,27 +67,20 @@ def raedZip(bookid: str, index: int):
|
|||||||
return str(e), ""
|
return str(e), ""
|
||||||
|
|
||||||
|
|
||||||
def thumbnail(input,size=(420,600)):
|
def thumbnail(input, minSize: int = 600, encode:str="webp"):
|
||||||
im = Image.open(io.BytesIO(input))
|
img = cv2.imdecode(np.frombuffer(input, np.uint8), cv2.IMREAD_COLOR)
|
||||||
del input
|
height = img.shape[0] # 图片高度
|
||||||
newimg = im.convert('RGB')
|
width = img.shape[1] # 图片宽度
|
||||||
im.close()
|
if minSize < np.amin((height,width)):
|
||||||
newimg.thumbnail(size)
|
if height > width:
|
||||||
output_io = io.BytesIO()
|
newshape = (minSize, int(minSize / width * height))
|
||||||
newimg.save(output_io,format='WEBP')
|
else:
|
||||||
newimg.close()
|
newshape = (int(minSize / height * width), minSize)
|
||||||
output_io.seek(0)
|
img = cv2.resize(img, newshape)
|
||||||
return output_io
|
if encode == "webp":
|
||||||
|
success, encoded_image = cv2.imencode(".webp", img, [cv2.IMWRITE_WEBP_QUALITY, 75])
|
||||||
def imageToWebP(input,size=(2100,3000)):
|
elif encode == "jpg" or "jpeg":
|
||||||
with Image.open(io.BytesIO(input)) as img:
|
success, encoded_image = cv2.imencode(".jpg", img, [cv2.IMWRITE_JPEG_QUALITY, 75])
|
||||||
newimg = img.convert('RGB')
|
else:
|
||||||
img.close()
|
return input
|
||||||
output_io = io.BytesIO()
|
return encoded_image.tobytes()
|
||||||
newimg.thumbnail(size)
|
|
||||||
newimg.save(output_io,format='WEBP')
|
|
||||||
newimg.close()
|
|
||||||
output_io.seek(0)
|
|
||||||
return output_io
|
|
||||||
|
|
||||||
|
|
||||||
|
2
main.py
2
main.py
@ -6,6 +6,7 @@ from flask import *
|
|||||||
from web.api_Img import api_Img_bp
|
from web.api_Img import api_Img_bp
|
||||||
from web.page import page_bp
|
from web.page import page_bp
|
||||||
from web.admin_page import admin_page_bp
|
from web.admin_page import admin_page_bp
|
||||||
|
from web.api_comment import comment_api_bp
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ def appinit():
|
|||||||
app.register_blueprint(api_Img_bp)
|
app.register_blueprint(api_Img_bp)
|
||||||
app.register_blueprint(page_bp)
|
app.register_blueprint(page_bp)
|
||||||
app.register_blueprint(admin_page_bp)
|
app.register_blueprint(admin_page_bp)
|
||||||
|
app.register_blueprint(comment_api_bp)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
appinit()
|
appinit()
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
shortuuid
|
shortuuid
|
||||||
flask
|
flask
|
||||||
Pillow
|
opencv-python
|
||||||
|
opencv-python-headless
|
5
static/js/popper.min.js
vendored
5
static/js/popper.min.js
vendored
File diff suppressed because one or more lines are too long
@ -72,6 +72,36 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="exampleModalLabel">评论</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="comment" action="/api/comment/upload" method="post">
|
||||||
|
<select class="form-select" aria-label="Default select example" name="score">
|
||||||
|
<option value="none">无评价</option>
|
||||||
|
<option value="like">赞👍</option>
|
||||||
|
<option value="dislike">踩👎</option>
|
||||||
|
</select>
|
||||||
|
<div class="input-group" style="margin-top: 10px;">
|
||||||
|
<span class="input-group-text">评论</span>
|
||||||
|
<textarea class="form-control" name="text" aria-label="With textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="bookid" value="{{data[0][1]}}" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="commentSubmit()">提交</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="movie-poster">
|
<div class="movie-poster">
|
||||||
@ -81,24 +111,75 @@
|
|||||||
<div class="movie-details">
|
<div class="movie-details">
|
||||||
<!-- 详细信息 -->
|
<!-- 详细信息 -->
|
||||||
<h1>{{ data[0][2] }}</h1>
|
<h1>{{ data[0][2] }}</h1>
|
||||||
<h2>时间: {{time}}</h2>
|
<h3>更新时间: {{time}}</h3>
|
||||||
<h2>暂无评价</h2>
|
<h2>👍{{socre["like"]}} 👎{{socre["dislike"]}}</h2>
|
||||||
<button class="btn btn-primary" onclick="window.location.href='/view/{{ id }}'">在线浏览</button>
|
<button class="btn btn-primary" onclick="window.location.href='/view/{{ id }}'">在线浏览</button>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">撰写评论</button>
|
||||||
|
{%if islogin == "admin"%}
|
||||||
|
<button class="btn btn-danger">删除资源</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="container">
|
||||||
<div class="comments-section">
|
<div class="comments-section">
|
||||||
<h2>评论区</h2>
|
<h2>评论区</h2>
|
||||||
<!-- 评论 -->
|
<!-- 评论 -->
|
||||||
|
{% if not comments%}
|
||||||
|
<p>暂无评论</p>
|
||||||
|
{%endif%}
|
||||||
|
{% for item in comments %}
|
||||||
<div class="comment">
|
<div class="comment">
|
||||||
<p>用户A:Lorem ipsum dolor sit amet consectetur adipisicing elit. Nobis, quam!</p>
|
<h3>{{item["from"]}}:
|
||||||
|
{%if item["socre"] == "like"%}
|
||||||
|
<small class="text-muted">觉得很赞👍</small>
|
||||||
|
{%endif%}
|
||||||
|
{%if item["socre"] == "dislike"%}
|
||||||
|
<small class="text-muted">点了个踩👎</small>
|
||||||
|
{%endif%}
|
||||||
|
</h3>
|
||||||
|
<h3>{{item["text"]}}</h3>
|
||||||
|
<small class="text-muted">id:{{item["id"]}} {{item["time"]}}</small>
|
||||||
|
{% if islogin == item["from"] %}
|
||||||
|
<button class="btn btn-danger" id="{{item['id']}}" onclick="delComment(id)">删除</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="comment">
|
{% endfor %}
|
||||||
<p>用户B:Lorem, ipsum dolor sit amet consectetur adipisicing elit. Aut sunt tempore architecto minus, cum
|
|
||||||
mollitia voluptatibus repellendus aliquid id reprehenderit.</p>
|
|
||||||
</div>
|
|
||||||
<!-- 在此添加更多评论 -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||||
|
<script src="/static/js/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const comment = document.getElementById("comment")
|
||||||
|
function commentSubmit() {
|
||||||
|
comment.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
function delComment(id) {
|
||||||
|
fetch("/api/comment/remove?id=" + id, {
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
// 首先检查HTTP状态码
|
||||||
|
if (response.ok) {
|
||||||
|
// 请求成功,刷新页面
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
// 请求失败,抛出一个错误
|
||||||
|
throw new Error('网络请求失败,状态码:' + response.status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// 处理任何在请求过程中发生的错误
|
||||||
|
alert('请求失败:' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -50,7 +50,7 @@
|
|||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
/* 模糊度可以根据需要调整 */
|
/* 模糊度可以根据需要调整 */
|
||||||
transition: display;
|
transition: display;
|
||||||
z-index: 1;
|
z-index: 10;
|
||||||
/* 保证遮罩在页面上方 */
|
/* 保证遮罩在页面上方 */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
/* 确保遮罩不影响下方元素的交互 */
|
/* 确保遮罩不影响下方元素的交互 */
|
||||||
@ -61,11 +61,21 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div style="display: flex;">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" id="search_text" placeholder="键入以搜索">
|
<input type="text" class="form-control" id="search_text" placeholder="键入以搜索">
|
||||||
<button class="btn btn-secondary" type="button"
|
<button class="btn btn-secondary" type="button"
|
||||||
onclick="window.location.href='/overview/1?search='+document.getElementById('search_text').value">Search</button>
|
onclick="window.location.href='/overview/1?search='+document.getElementById('search_text').value">Search</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dropdown" style="margin-left: 10px;">
|
||||||
|
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
{{username}}
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><button class="dropdown-item" href="#" onclick="logout()">Log out</button></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div id="gallery">
|
<div id="gallery">
|
||||||
{% for item in list %}
|
{% for item in list %}
|
||||||
@ -101,6 +111,9 @@
|
|||||||
|
|
||||||
<div id="global-blur" onclick="unshow_global_blur()"></div>
|
<div id="global-blur" onclick="unshow_global_blur()"></div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||||
|
<script src="/static/js/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function linkjump(url) {
|
function linkjump(url) {
|
||||||
window.open("/book/" + url)
|
window.open("/book/" + url)
|
||||||
@ -127,6 +140,11 @@
|
|||||||
global_blur.style.pointerEvents = "none";
|
global_blur.style.pointerEvents = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
document.cookie = "islogin=1; max-age=0"
|
||||||
|
//location.href = "/";
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="/static/js/jquery.min.js"></script>
|
<script src="/static/js/jquery.min.js"></script>
|
||||||
<script src="/static/js/popper.min.js"></script>
|
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||||
<script src="/static/js/bootstrap.min.js"></script>
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-cn">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>漫画详情页</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#comic-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comic-image {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 300px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#global-blur {
|
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
-webkit-backdrop-filter: blur(12px);
|
|
||||||
/* 模糊度可以根据需要调整 */
|
|
||||||
transition: display;
|
|
||||||
z-index: 1;
|
|
||||||
/* 保证遮罩在页面上方 */
|
|
||||||
pointer-events: none;
|
|
||||||
/* 确保遮罩不影响下方元素的交互 */
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.1s ease
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
{% for i in index %}
|
|
||||||
<img data-src="/api/img/{{ id }}/{{ i }}" loading="lazy" alt="{{ i }}" class="imgs">
|
|
||||||
{% endfor %}
|
|
||||||
<div style="display:flex;justify-content: center; align-items:center;">
|
|
||||||
<img src="/static/loading.gif" id="loadingGIF">
|
|
||||||
</div>
|
|
||||||
<div id="global-blur" onclick="unshow_global_blur()"></div>
|
|
||||||
<script>
|
|
||||||
var imgs = document.querySelectorAll('.imgs');
|
|
||||||
|
|
||||||
//offsetTop是元素与offsetParent的距离,循环获取直到页面顶部
|
|
||||||
function getTop(e) {
|
|
||||||
var T = e.offsetTop;
|
|
||||||
while (e = e.offsetParent) {
|
|
||||||
T += e.offsetTop;
|
|
||||||
}
|
|
||||||
return T;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lazyLoad(imgs) {
|
|
||||||
var H = document.documentElement.clientHeight;//获取可视区域高度
|
|
||||||
var S = document.documentElement.scrollTop || document.body.scrollTop;
|
|
||||||
for (var i = 0; i < imgs.length; i++) {
|
|
||||||
if (H + S > getTop(imgs[i])) {
|
|
||||||
imgs[i].src = imgs[i].getAttribute('data-src');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = window.onscroll = function () { //onscroll()在滚动条滚动的时候触发
|
|
||||||
lazyLoad(imgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', documentVisibilityChange)
|
|
||||||
global_blur = document.getElementById("global-blur")
|
|
||||||
function documentVisibilityChange() {
|
|
||||||
if (document.visibilityState === "hidden") {
|
|
||||||
global_blur.style.opacity = 1;
|
|
||||||
global_blur.style.pointerEvents = "auto";
|
|
||||||
}
|
|
||||||
if (document.visibilityState === "visible") {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function unshow_global_blur() {
|
|
||||||
global_blur.style.opacity = 0;
|
|
||||||
global_blur.style.pointerEvents = "none";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
181
templates/view.html.j2
Normal file
181
templates/view.html.j2
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-cn">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>漫画详情页</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comic-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comic-image {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pagination {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-button {
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 5px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-button:disabled {
|
||||||
|
background-color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ellipsis {
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 0 5px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-blur {
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="comic-container"></div>
|
||||||
|
<div id="pagination"></div>
|
||||||
|
<div id="global-blur" onclick="unshowGlobalBlur()"></div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const imgsData = [
|
||||||
|
{% for i in index %}
|
||||||
|
{ src: "/api/img/{{ id }}/{{ i }}", alt: "漫画页面 {{ i }}" },
|
||||||
|
{% endfor %}
|
||||||
|
];
|
||||||
|
const itemsPerPage = 25; // 每页显示的图片数量
|
||||||
|
let currentPage = 1;
|
||||||
|
|
||||||
|
function renderPage(page) {
|
||||||
|
const comicContainer = document.getElementById('comic-container');
|
||||||
|
comicContainer.innerHTML = ''; // 清空当前内容
|
||||||
|
|
||||||
|
const start = (page - 1) * itemsPerPage;
|
||||||
|
const end = start + itemsPerPage;
|
||||||
|
const pageItems = imgsData.slice(start, end);
|
||||||
|
|
||||||
|
pageItems.forEach(item => {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = 'imgs comic-image';
|
||||||
|
img.setAttribute('data-src', item.src);
|
||||||
|
img.setAttribute('alt', item.alt);
|
||||||
|
comicContainer.appendChild(img);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.scrollTo(0, 0); // 滚动到页面顶部
|
||||||
|
lazyLoad(); // 确保惰性加载生效
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination() {
|
||||||
|
const pagination = document.getElementById('pagination');
|
||||||
|
pagination.innerHTML = ''; // 清空当前内容
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(imgsData.length / itemsPerPage);
|
||||||
|
|
||||||
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'page-button';
|
||||||
|
button.innerText = i;
|
||||||
|
button.disabled = (i === currentPage);
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
currentPage = i;
|
||||||
|
renderPage(i);
|
||||||
|
renderPagination();
|
||||||
|
});
|
||||||
|
pagination.appendChild(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function lazyLoad() {
|
||||||
|
const imgs = document.querySelectorAll('.imgs');
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
const scrollY = window.scrollY || window.pageYOffset;
|
||||||
|
|
||||||
|
imgs.forEach(img => {
|
||||||
|
if (img.src) return; // 如果已经加载过了就跳过
|
||||||
|
|
||||||
|
const imgTop = img.getBoundingClientRect().top + scrollY;
|
||||||
|
if (windowHeight + scrollY > imgTop) {
|
||||||
|
img.src = img.getAttribute('data-src');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', lazyLoad);
|
||||||
|
window.addEventListener('resize', lazyLoad);
|
||||||
|
lazyLoad(); // 页面加载时初始化调用
|
||||||
|
|
||||||
|
const globalBlur = document.getElementById('global-blur');
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'hidden') {
|
||||||
|
globalBlur.style.opacity = 1;
|
||||||
|
globalBlur.style.pointerEvents = 'auto';
|
||||||
|
} else if (document.visibilityState === 'visible') {
|
||||||
|
globalBlur.style.opacity = 0;
|
||||||
|
globalBlur.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPage(currentPage);
|
||||||
|
renderPagination();
|
||||||
|
});
|
||||||
|
|
||||||
|
function unshowGlobalBlur() {
|
||||||
|
const globalBlur = document.getElementById('global-blur');
|
||||||
|
globalBlur.style.opacity = 0;
|
||||||
|
globalBlur.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -1,7 +1,8 @@
|
|||||||
from flask import *
|
from flask import *
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
import time
|
import time
|
||||||
import db.file, file , app_conf
|
import db.user
|
||||||
|
import db.file, file, app_conf
|
||||||
|
|
||||||
admin_page_bp = Blueprint("admin_page_bp", __name__)
|
admin_page_bp = Blueprint("admin_page_bp", __name__)
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ conf = app_conf.conf()
|
|||||||
|
|
||||||
# 管理页
|
# 管理页
|
||||||
|
|
||||||
|
|
||||||
@admin_page_bp.route("/", methods=["GET", "POST"])
|
@admin_page_bp.route("/", methods=["GET", "POST"])
|
||||||
def login(): # 登录页面
|
def login(): # 登录页面
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
@ -16,11 +18,10 @@ def login(): # 登录页面
|
|||||||
return redirect("/overview/1")
|
return redirect("/overview/1")
|
||||||
return render_template("login.html")
|
return render_template("login.html")
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
if request.form["username"] == conf.get("user", "username") and request.form[
|
if db.user.check(request.form["username"], request.form["password"]):
|
||||||
"password"
|
|
||||||
] == conf.get("user", "password"):
|
|
||||||
resp = make_response(redirect("/overview/1"))
|
resp = make_response(redirect("/overview/1"))
|
||||||
resp.set_cookie("islogin", "True")
|
resp.set_cookie("islogin", request.form["username"])
|
||||||
|
resp.set_cookie("uid", str(db.user.getUid(request.form["username"])))
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
return redirect("/")
|
return redirect("/")
|
@ -1,9 +1,13 @@
|
|||||||
from flask import *
|
from flask import *
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
import db.file , file, gc
|
import db.file , file, gc , app_conf
|
||||||
|
|
||||||
api_Img_bp = Blueprint("api_Img_bp", __name__)
|
api_Img_bp = Blueprint("api_Img_bp", __name__)
|
||||||
|
|
||||||
|
conf = app_conf.conf()
|
||||||
|
imgencode = conf.get("img", "encode")
|
||||||
|
miniSize = conf.getint("img", "miniSize")
|
||||||
|
fullSize = conf.getint("img", "fullSize")
|
||||||
|
|
||||||
@api_Img_bp.route("/api/img/<bookid>/<index>")
|
@api_Img_bp.route("/api/img/<bookid>/<index>")
|
||||||
def img(bookid, index): # 图片接口
|
def img(bookid, index): # 图片接口
|
||||||
@ -16,12 +20,12 @@ def img(bookid, index): # 图片接口
|
|||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
abort(404)
|
abort(404)
|
||||||
if request.args.get("mini") == "yes":
|
if request.args.get("mini") == "yes":
|
||||||
data = file.thumbnail(data)
|
data = file.thumbnail(data,miniSize,encode=imgencode)
|
||||||
else:
|
else:
|
||||||
data = file.imageToWebP(data)
|
data = file.thumbnail(data,fullSize,encode=imgencode)
|
||||||
response = make_response(data) # 读取文件
|
response = make_response(data) # 读取文件
|
||||||
del data
|
del data
|
||||||
response.headers.set("Content-Type", "image/Webp")
|
response.headers.set("Content-Type",f"image/{imgencode}")
|
||||||
response.headers.set("Content-Disposition", "inline", filename=filename)
|
response.headers.set("Content-Disposition", "inline", filename=filename)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
return response
|
return response
|
||||||
|
44
web/api_comment.py
Normal file
44
web/api_comment.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from flask import *
|
||||||
|
from flask import Blueprint
|
||||||
|
import time
|
||||||
|
import db.comments, db.file, app_conf
|
||||||
|
|
||||||
|
comment_api_bp = Blueprint("comment_api_bp", __name__)
|
||||||
|
|
||||||
|
conf = app_conf.conf()
|
||||||
|
|
||||||
|
|
||||||
|
@comment_api_bp.route("/api/comment/upload", methods=["POST"])
|
||||||
|
def comment_api(): # 概览
|
||||||
|
if request.cookies.get("islogin") is None: # 验证登录状态
|
||||||
|
return redirect("/")
|
||||||
|
if request.form["score"] != "none" and request.form["text"].isspace():
|
||||||
|
return "评论不能为空"
|
||||||
|
if len(request.form["text"]) > 200:
|
||||||
|
return "评论过长(需要小于200字)"
|
||||||
|
if db.comments.searchByAll(request.cookies.get("uid"), request.form["bookid"]):
|
||||||
|
return "你已经完成了评论 不可重复提交"
|
||||||
|
db.comments.new(
|
||||||
|
request.form["bookid"],
|
||||||
|
request.cookies.get("uid"),
|
||||||
|
request.form["score"],
|
||||||
|
request.form["text"],
|
||||||
|
)
|
||||||
|
return redirect("/book/" + request.form["bookid"])
|
||||||
|
|
||||||
|
@comment_api_bp.route("/api/comment/remove")
|
||||||
|
def remove(): # 删除api
|
||||||
|
if request.cookies.get("islogin") is None: # 验证登录状态
|
||||||
|
return abort(403)
|
||||||
|
try:
|
||||||
|
id = int(request.args.get("id"))
|
||||||
|
except:
|
||||||
|
return abort(400)
|
||||||
|
commentInfo = db.comments.getById(id)
|
||||||
|
if commentInfo is None:
|
||||||
|
return abort(404)
|
||||||
|
if int(request.cookies.get("uid")) == commentInfo[3]:
|
||||||
|
if db.comments.remove(id):
|
||||||
|
return "OK"
|
||||||
|
return abort(404)
|
||||||
|
return abort(400)
|
44
web/page.py
44
web/page.py
@ -1,20 +1,23 @@
|
|||||||
from flask import *
|
from flask import *
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
import time
|
import time
|
||||||
import db.file, file , app_conf
|
import db.comments, db.user, db.file, file, app_conf
|
||||||
|
|
||||||
page_bp = Blueprint("page_bp", __name__)
|
page_bp = Blueprint("page_bp", __name__)
|
||||||
|
|
||||||
conf = app_conf.conf()
|
conf = app_conf.conf()
|
||||||
|
|
||||||
|
|
||||||
@page_bp.route("/overview/<page>")
|
@page_bp.route("/overview/<page>")
|
||||||
def overview(page): # 概览
|
def overview(page): # 概览
|
||||||
page = int(page)
|
page = int(page)
|
||||||
if request.cookies.get("islogin") is None: #验证登录状态
|
if request.cookies.get("islogin") is None: # 验证登录状态
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
metaDataList = db.file.getMetadata((page - 1) * 20, page * 20, request.args.get("search"))
|
metaDataList = db.file.getMetadata(
|
||||||
|
(page - 1) * 20, page * 20, request.args.get("search")
|
||||||
|
)
|
||||||
for item in metaDataList:
|
for item in metaDataList:
|
||||||
item[2] = item[2][:-4] #去除文件扩展名
|
item[2] = item[2][:-4] # 去除文件扩展名
|
||||||
if page <= 3:
|
if page <= 3:
|
||||||
lastPageList = range(1, page)
|
lastPageList = range(1, page)
|
||||||
else:
|
else:
|
||||||
@ -26,13 +29,14 @@ def overview(page): # 概览
|
|||||||
lastPageList=lastPageList,
|
lastPageList=lastPageList,
|
||||||
pagenow=page,
|
pagenow=page,
|
||||||
nextPageList=nextPageList,
|
nextPageList=nextPageList,
|
||||||
aftertime=int(time.time())-3*86400
|
aftertime=int(time.time()) - 3 * 86400,
|
||||||
|
username=request.cookies.get("islogin"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@page_bp.route("/book/<bookid>")
|
@page_bp.route("/book/<bookid>")
|
||||||
def book(bookid): # 接口
|
def book(bookid): # 接口
|
||||||
if request.cookies.get("islogin") is None: #验证登录状态
|
if request.cookies.get("islogin") is None: # 验证登录状态
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
data = db.file.searchByid(bookid)
|
data = db.file.searchByid(bookid)
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
@ -41,27 +45,44 @@ def book(bookid): # 接口
|
|||||||
data[0][2] = data[0][2][0:-4] # 把文件扩展名去掉
|
data[0][2] = data[0][2][0:-4] # 把文件扩展名去掉
|
||||||
local_time = time.localtime(float(data[0][4]))
|
local_time = time.localtime(float(data[0][4]))
|
||||||
|
|
||||||
|
raw_com = db.comments.listByBookid(bookid)
|
||||||
|
comments = []
|
||||||
|
for i in raw_com:
|
||||||
|
print(request.cookies.get("islogin"))
|
||||||
|
comments.append(
|
||||||
|
{
|
||||||
|
"id": i[0],
|
||||||
|
"time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(float(i[1]))),
|
||||||
|
"from": db.user.getUsername(i[3]),
|
||||||
|
"socre":i[4],
|
||||||
|
"text":i[5]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"book.html",
|
"book.html",
|
||||||
id=bookid,
|
id=bookid,
|
||||||
data=data,
|
data=data,
|
||||||
time=time.strftime("%Y-%m-%d %H:%M:%S",local_time),
|
time=time.strftime("%Y-%m-%d %H:%M:%S", local_time),
|
||||||
|
socre=db.comments.getScore(bookid),
|
||||||
|
comments=comments,
|
||||||
|
islogin=request.cookies.get("islogin")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@page_bp.route("/view/<bookid>")
|
@page_bp.route("/view/<bookid>")
|
||||||
def view(bookid): # 接口
|
def view(bookid): # 接口
|
||||||
if request.cookies.get("islogin") is None: #验证登录状态
|
if request.cookies.get("islogin") is None: # 验证登录状态
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
data = db.file.searchByid(bookid)
|
data = db.file.searchByid(bookid)
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
return abort(404)
|
return abort(404)
|
||||||
return render_template("view.html", id=bookid, index=range(1, data[0][3]))
|
return render_template("view.html.j2", id=bookid, index=range(1, data[0][3]))
|
||||||
|
|
||||||
|
|
||||||
@page_bp.route("/upload", methods=["GET", "POST"]) # 文件上传
|
@page_bp.route("/upload", methods=["GET", "POST"]) # 文件上传
|
||||||
def upload_file():
|
def upload_file():
|
||||||
if request.cookies.get("islogin") is None: #验证登录状态
|
if request.cookies.get("islogin") is None: # 验证登录状态
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
return render_template("upload.html")
|
return render_template("upload.html")
|
||||||
@ -72,6 +93,3 @@ def upload_file():
|
|||||||
fileitem.save(conf.get("file", "inputdir") + "/" + fileitem.filename)
|
fileitem.save(conf.get("file", "inputdir") + "/" + fileitem.filename)
|
||||||
file.auotLoadFile()
|
file.auotLoadFile()
|
||||||
return "success"
|
return "success"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user