mirror of
https://github.com/KingHWJ/WeReadBoard.git
synced 2026-11-03 18:24:58 +08:00
添加书架刷新按钮 + 后端服务保活方案
前端: - 书架筛选栏新增「↻ 刷新」按钮,点击重新加载分类和书架数据 - 新增 refreshShelf() 函数,带 loading 状态反馈 - 新增 .shelf-refresh-btn CSS 样式 后端保活: - 新增 start_server.sh 启动脚本 (start/daemon/stop/restart/status) - 新增 com.weread-dashboard.plist launchd 服务配置 - KeepAlive=true: 崩溃自动重启 - RunAtLoad=true: 开机自动启动 - 日志输出到 /tmp/weread_server.log
This commit is contained in:
1 parent
7d983e38da
commit
8333230f77
6 files changed
+161
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
49192
|
||||||
@@ -87,3 +87,10 @@
|
|||||||
- app.js 中 shelfSortMode 默认值从 "default" 改为 "category"
|
- app.js 中 shelfSortMode 默认值从 "default" 改为 "category"
|
||||||
- 删除 toggleShelfSort 函数和 .shelf-sort-btn CSS 样式
|
- 删除 toggleShelfSort 函数和 .shelf-sort-btn CSS 样式
|
||||||
- 书架列表始终按分类分组渲染,无需手动切换
|
- 书架列表始终按分类分组渲染,无需手动切换
|
||||||
|
|
||||||
|
## 12:32 书架恢复横向网格展示,移除分类分组标题
|
||||||
|
|
||||||
|
- 用户反馈:不需要展示分类分组标题(如"历史-历史读物 3本"),恢复简单横向书架
|
||||||
|
- 删除 renderBooksGroupedByCategory 函数,loadShelf 始终用简单网格渲染
|
||||||
|
- 后端 sort=category 仍生效(按分类排序),但前端不显示分组
|
||||||
|
- 分类筛选标签(可折叠层级)保留不变
|
||||||
Executable
+108
@@ -0,0 +1,108 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 微信读书数据看板 - 服务启动脚本
|
||||||
|
# 用法:
|
||||||
|
# ./start_server.sh 启动服务(前台运行)
|
||||||
|
# ./start_server.sh daemon 后台守护进程方式启动
|
||||||
|
# ./start_server.sh stop 停止服务
|
||||||
|
# ./start_server.sh restart 重启服务
|
||||||
|
# ./start_server.sh status 查看状态
|
||||||
|
|
||||||
|
PROJECT_DIR="/Users/hwj/WorkBuddy/2026-07-17-09-48-20"
|
||||||
|
PYTHON="/Users/hwj/.workbuddy/binaries/python/envs/default/bin/python"
|
||||||
|
PID_FILE="$PROJECT_DIR/.server.pid"
|
||||||
|
LOG_FILE="/tmp/weread_server.log"
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
|
cd "$PROJECT_DIR" || exit 1
|
||||||
|
|
||||||
|
is_running() {
|
||||||
|
if [ -f "$PID_FILE" ]; then
|
||||||
|
local pid=$(cat "$PID_FILE")
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# 也检查端口是否被占用
|
||||||
|
if lsof -i :$PORT -t >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_pid() {
|
||||||
|
if [ -f "$PID_FILE" ]; then
|
||||||
|
local pid=$(cat "$PID_FILE")
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
echo "$pid"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
lsof -i :$PORT -t 2>/dev/null | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_server() {
|
||||||
|
if is_running; then
|
||||||
|
local pid=$(get_pid)
|
||||||
|
echo "停止服务 (PID: $pid)..."
|
||||||
|
kill "$pid" 2>/dev/null
|
||||||
|
sleep 1
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
kill -9 "$pid" 2>/dev/null
|
||||||
|
fi
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
echo "已停止"
|
||||||
|
else
|
||||||
|
echo "服务未运行"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_foreground() {
|
||||||
|
if is_running; then
|
||||||
|
echo "服务已在运行 (PID: $(get_pid))"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "启动服务(前台模式)..."
|
||||||
|
exec "$PYTHON" app.py
|
||||||
|
}
|
||||||
|
|
||||||
|
start_daemon() {
|
||||||
|
if is_running; then
|
||||||
|
echo "服务已在运行 (PID: $(get_pid))"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "启动服务(后台守护模式)..."
|
||||||
|
nohup "$PYTHON" app.py > "$LOG_FILE" 2>&1 &
|
||||||
|
local pid=$!
|
||||||
|
echo "$pid" > "$PID_FILE"
|
||||||
|
sleep 2
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
echo "服务已启动 (PID: $pid)"
|
||||||
|
echo "访问: http://127.0.0.1:$PORT"
|
||||||
|
echo "日志: $LOG_FILE"
|
||||||
|
else
|
||||||
|
echo "启动失败,请查看日志: $LOG_FILE"
|
||||||
|
cat "$LOG_FILE"
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
if is_running; then
|
||||||
|
local pid=$(get_pid)
|
||||||
|
echo "✅ 服务运行中 (PID: $pid)"
|
||||||
|
echo " 端口: $PORT"
|
||||||
|
echo " 日志: $LOG_FILE"
|
||||||
|
else
|
||||||
|
echo "❌ 服务未运行"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-start}" in
|
||||||
|
start) start_foreground ;;
|
||||||
|
daemon) start_daemon ;;
|
||||||
|
stop) stop_server ;;
|
||||||
|
restart) stop_server; sleep 1; start_daemon ;;
|
||||||
|
status) show_status ;;
|
||||||
|
*) echo "用法: $0 {start|daemon|stop|restart|status}"; exit 1 ;;
|
||||||
|
esac
|
||||||
@@ -766,6 +766,33 @@ body {
|
|||||||
box-shadow: 0 0 0 3px var(--accent-bg);
|
box-shadow: 0 0 0 3px var(--accent-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shelf-refresh-btn {
|
||||||
|
padding: 7px 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: var(--bg-card);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shelf-refresh-btn:hover:not(:disabled) {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shelf-refresh-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shelf-refresh-btn.loading {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
.filter-checkbox {
|
.filter-checkbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -318,6 +318,23 @@ function filterByCategory(fullCategory) {
|
|||||||
loadShelf(1);
|
loadShelf(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 书架刷新 =====
|
||||||
|
|
||||||
|
async function refreshShelf() {
|
||||||
|
const btn = document.getElementById("shelfRefreshBtn");
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.classList.add("loading");
|
||||||
|
btn.textContent = "↻ 刷新中...";
|
||||||
|
try {
|
||||||
|
await loadShelfCategories();
|
||||||
|
await loadShelf(1);
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.classList.remove("loading");
|
||||||
|
btn.textContent = "↻ 刷新";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 初始化 =====
|
// ===== 初始化 =====
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
|||||||
@@ -125,6 +125,7 @@
|
|||||||
<label class="filter-checkbox">
|
<label class="filter-checkbox">
|
||||||
<input type="checkbox" id="finishedOnly"> 仅看已读完
|
<input type="checkbox" id="finishedOnly"> 仅看已读完
|
||||||
</label>
|
</label>
|
||||||
|
<button class="shelf-refresh-btn" id="shelfRefreshBtn" onclick="refreshShelf()" title="重新加载书架数据">↻ 刷新</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="shelf-info" id="shelfInfo"></div>
|
<div class="shelf-info" id="shelfInfo"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user