Hongmu Notes
Home Language Notes A novel reader written in html, php, ajax
Language Notes PHP CSS JavaScript Bootstrap

A novel reader written in html, php, ajax

A novel reader written in html, php, ajax

Pure Reading Version:

<?php
// index.php
session_start();
$uploadDir = 'books/';
$booksFile = $uploadDir . 'books.json';

// 初始化书籍存储目录
if (!file_exists($uploadDir)) mkdir($uploadDir, 0755, true);
if (!file_exists($booksFile)) file_put_contents($booksFile, '[]');

// 处理文件上传
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    header('Content-Type: application/json');

    // 验证文件
    if (!isset($_FILES['book']) || $_FILES['book']['error'] !== UPLOAD_ERR_OK) {
        die(json_encode(['status' => 'error', 'message' => '文件上传失败']));
    }

    // 生成安全目录名
    $bookName = preg_replace('/[^\w\x{4e00}-\x{9fa5}]/u', '', pathinfo($_FILES['book']['name'], PATHINFO_FILENAME));
    $safeDir = $bookName . '_' . bin2hex(random_bytes(4));
    $targetDir = $uploadDir . $safeDir . '/';

    // 创建目录
    if (!mkdir($targetDir, 0755, true)) {
        die(json_encode(['status' => 'error', 'message' => '目录创建失败']));
    }

    // 移动文件
    move_uploaded_file($_FILES['book']['tmp_name'], $targetDir . 'source.txt');

    // 处理章节分割
    $content = mb_convert_encoding(file_get_contents($targetDir . 'source.txt'), 'UTF-8', 'auto');
    $chapters = preg_split('/(^(?:第[零一二三四五六七八九十百千0-9]+章|第\d+章|卷\d+).*$)/mu', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
    
    $chapterList = [];
    for ($i = 1; $i < count($chapters); $i += 2) {
        if (empty(trim($chapters[$i]))) continue;
        
        $chapterTitle = trim(strip_tags($chapters[$i]));
        $chapterContent = nl2br(htmlspecialchars(trim($chapters[$i+1] ?? '')));
        
        // $chapterContent = str_replace('<br />','<textarea></textarea>',$chapterContent);
        
        $fileName = "chapter_" . (count($chapterList)+1) . ".html";
        file_put_contents($targetDir . $fileName, 
            "<div class='chapter-content'>
                <h3 class='chapter-title'>{$chapterTitle}</h3>
                <div class='content-text'>{$chapterContent}</div>
            </div>");
        
        $chapterList[] = [
            'title' => $chapterTitle,
            'file' => $fileName
        ];
    }

    // 保存章节信息
    file_put_contents($targetDir . 'chapters.json', json_encode($chapterList, JSON_UNESCAPED_UNICODE));

    // 更新书籍列表
    $books = json_decode(file_get_contents($booksFile), true);
    $books[] = [
        'id' => $safeDir,
        'title' => $bookName,
        'upload_time' => date('Y-m-d H:i:s'),
        'chapter_count' => count($chapterList)
    ];
    file_put_contents($booksFile, json_encode($books, JSON_UNESCAPED_UNICODE));

    die(json_encode(['status' => 'success', 'book' => $safeDir]));
}

// 获取当前书籍
$currentBook = isset($_GET['book']) ? basename($_GET['book']) : '';
if ($currentBook) $_SESSION['current_book'] = $currentBook;
$currentBook = $_SESSION['current_book'] ?? '';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>多书阅读器</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
    <style>
        :root {
            --primary-color: #2c3e50;
            --accent-color: #3498db;
        }

        body {
            background: #f8f9fa;
            min-height: 100vh;
        }

        #sidebar {
            background: white;
            height: 100vh;
            box-shadow: 2px 0 8px rgba(0,0,0,0.1);
            transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        #chapterList {
            overflow: auto;
            height: 60vh;
        }
        textarea {
          resize: vertical;
          display: block;
          width: 100%;
          height: 2em;
        }

        .book-item {
            padding: 12px;
            margin: 8px 0;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
            border: 1px solid #eee;
        }

        .book-item:hover {
            transform: translateX(3px);
            box-shadow: 0 2px 6px rgba(0,0,0,0.1);
        }

        .book-item.active {
            border-left: 4px solid var(--accent-color);
            background: #f8f9fa;
        }

        .chapter-item {
            padding: 10px 15px;
            border-bottom: 1px solid #eee;
            cursor: pointer;
            transition: background 0.2s;
        }

        .chapter-item:hover {
            background: #f8f9fa;
        }

        .chapter-item.active {
            color: var(--accent-color);
            font-weight: 500;
        }

        #contentArea {
            padding: 2rem;
            line-height: 1.8;
            font-size: 1.1rem;
            background: white;
            height: 100vh;
            overflow: auto;
        }

        /* 移动端样式 */
        #mobileMenuBtn {
            display: none;
            position: fixed;
            top: 10px;
            left: 10px;
            z-index: 1000;
            padding: 8px 12px;
            border: none;
            border-radius: 5px;
            background: var(--accent-color);
            color: white;
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
            transition: transform 0.2s;
        }

        .sidebar-overlay {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0,0,0,0.5);
            z-index: 998;
        }

        @media (max-width: 768px) {
            #mobileMenuBtn {
                display: block;
            }

            #sidebar {
                position: fixed;
                top: 0;
                left: 0;
                width: 280px;
                height: 100vh;
                transform: translateX(-100%);
                z-index: 999;
            }

            .sidebar-mobile-show {
                transform: translateX(0) !important;
            }

            #contentArea {
                width: 100%;
                padding: 60px 15px 15px;
            }
        }
        /* 保持原有样式,新增以下内容 */
        .note-section {
            margin: 1rem 0;
            border-left: 3px solid #3498db;
            padding-left: 1rem;
        }
        .note-input {
            width: 100%;
            padding: 0.5rem;
            border: 1px solid #ddd;
            border-radius: 4px;
            resize: vertical;
            min-height: 60px;
        }
        #copyNotesBtn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 1000;
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
        }
    </style>
</head>
<body>
<button id="mobileMenuBtn">☰ 菜单</button>
<div class="sidebar-overlay"></div>

<div class="container-fluid">
    <div class="row">
        <!-- 左侧边栏 -->
        <div class="col-lg-3 col-md-4" id="sidebar">
            <div class="p-3">
                <!-- 上传表单 -->
                <form id="uploadForm" class="mb-4">
                    <input type="file" id="bookFile" name="book" accept=".txt" hidden>
                    <label for="bookFile" class="btn btn-primary w-100">
                        <i class="bi bi-upload me-2"></i>上传小说
                    </label>
                    <div class="progress mt-2" style="height: 4px; display: none;">
                        <div class="progress-bar" role="progressbar" style="width: 0%"></div>
                    </div>
                </form>

                <!-- 书籍列表 -->
                <h5 class="text-muted mb-3">我的书架</h5>
                <div id="bookList">
                    <?php foreach(json_decode(file_get_contents($booksFile), true) as $book): ?>
                    <div class="book-item <?= $book['id'] === $currentBook ? 'active' : '' ?>" 
                         data-id="<?= htmlspecialchars($book['id']) ?>">
                        <div class="fw-bold"><?= htmlspecialchars($book['title']) ?></div>
                        <small class="text-muted">
                            共<?= $book['chapter_count'] ?>章 · 
                            <?= date('m/d H:i', strtotime($book['upload_time'])) ?>
                        </small>
                    </div>
                    <?php endforeach; ?>
                </div>

                <!-- 章节目录 -->
                <h5 class="text-muted mt-4 mb-3">章节列表</h5>
                <div id="chapterList">
                    <?php if ($currentBook && file_exists($uploadDir . $currentBook . '/chapters.json')): ?>
                    <?php $chapters = json_decode(file_get_contents($uploadDir . $currentBook . '/chapters.json'), true); ?>
                    <?php foreach ($chapters as $chapter): ?>
                    <div class="chapter-item" data-file="<?= htmlspecialchars($chapter['file']) ?>">
                        <?= htmlspecialchars($chapter['title']) ?>
                    </div>
                    <?php endforeach; ?>
                    <?php endif; ?>
                </div>
            </div>
        </div>

        <!-- 阅读区域 -->
        <div class="col-lg-9 col-md-8">
            <div id="contentArea">
                <?php if ($currentBook): ?>
                    <div class="text-center text-muted mt-5">请选择章节开始阅读</div>
                <?php else: ?>
                    <div class="text-center mt-5">
                        <h3 class="text-muted">欢迎使用阅读器</h3>
                        <p class="text-secondary">请上传或选择书籍开始阅读</p>
                    </div>
                <?php endif; ?>
            </div>
        </div>
    </div>
</div>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(function() {
    // 移动端侧边栏控制
    const $sidebar = $('#sidebar');
    const $overlay = $('.sidebar-overlay');
    const $menuBtn = $('#mobileMenuBtn');

    function isMobile() {
        return window.innerWidth <= 768;
    }

    function initMobileLayout() {
        if(isMobile()) {
            $menuBtn.show();
            $overlay.hide();
            $sidebar.removeClass('sidebar-mobile-show');
        } else {
            $menuBtn.hide();
            $overlay.hide();
            $sidebar.removeClass('sidebar-mobile-show');
        }
    }

    $menuBtn.on('click', function(e) {
        e.stopPropagation();
        $sidebar.addClass('sidebar-mobile-show');
        $overlay.fadeIn(200);
        $('body').css('overflow', 'hidden');
    });

    $overlay.on('click', function() {
        $sidebar.removeClass('sidebar-mobile-show');
        $overlay.fadeOut(200);
        $('body').css('overflow', 'auto');
    });

    $(document).on('click', '.chapter-item', function() {
        if(isMobile()) {
            $sidebar.removeClass('sidebar-mobile-show');
            $overlay.fadeOut(200);
            $('body').css('overflow', 'auto');
        }
    });

    $(window).on('resize', initMobileLayout);
    initMobileLayout();

    // 文件上传处理
    $('#bookFile').on('change', function() {
        const formData = new FormData();
        formData.append('book', this.files[0]);

        $('.progress').show().find('.progress-bar').css('width', '0%');

        $.ajax({
            url: '',
            type: 'POST',
            data: formData,
            contentType: false,
            processData: false,
            xhr: function() {
                const xhr = new XMLHttpRequest();
                xhr.upload.addEventListener('progress', function(e) {
                    if(e.lengthComputable) {
                        const percent = Math.round((e.loaded / e.total) * 100);
                        $('.progress-bar').css('width', percent + '%');
                    }
                }, false);
                return xhr;
            },
            success: function(res) {
                if(res.status === 'success') {
                    location.href = '?book=' + encodeURIComponent(res.book);
                } else {
                    alert(res.message || '上传失败');
                }
            },
            complete: function() {
                $('.progress').hide();
            }
        });
    });

    // 书籍和章节控制
    $(document).on('click', '.book-item', function() {
        window.location.href = '?book=' + encodeURIComponent($(this).data('id'));
    });

    // 修改章节点击事件处理代码
$(document).on('click', '.chapter-item', function() {
    $('.chapter-item').removeClass('active');
    $(this).addClass('active');
    
    const bookDir = 'books/' + getCurrentBookId();
    const chapterFile = $(this).data('file');
    
    $('#contentArea').load(bookDir + '/' + chapterFile, function() {
        // 新增以下两行代码
        $('#contentArea').scrollTop(0);  // 重置内容区域滚动条
        $('html, body').animate({ scrollTop: 0 }, 0);  // 重置页面滚动
        
        if(isMobile()) {
            $('html, body').animate({
                scrollTop: $('#contentArea').offset().top
            }, 500);
        }
    });
});

    function getCurrentBookId() {
        return new URLSearchParams(location.search).get('book') || '';
    }
});
</script>
</body>
</html>

Add a new note version.

<?php
function deleteDirectory($dirPath) {
    if (!is_dir($dirPath)) return false;
    $files = array_diff(scandir($dirPath), ['.', '..']);
    foreach ($files as $file) {
        $path = "$dirPath/$file";
        is_dir($path) ? deleteDirectory($path) : unlink($path);
    }
    return rmdir($dirPath);
}
// index.php
session_start();
$uploadDir = 'books/';
$booksFile = $uploadDir . 'books.json';

// 初始化书籍存储目录
if (!file_exists($uploadDir)) mkdir($uploadDir, 0755, true);
if (!file_exists($booksFile)) file_put_contents($booksFile, '[]');

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'deleteBook') {
    header('Content-Type: application/json');
    session_start();
    
    // 验证书籍ID
    $bookId = basename($_POST['bookId'] ?? '');
    if (!$bookId) {
        die(json_encode(['status' => 'error', 'message' => '无效书籍ID']));
    }

    // 加载书籍列表
    $books = json_decode(file_get_contents($booksFile), true);
    $index = array_search($bookId, array_column($books, 'id'));
    
    if ($index === false) {
        die(json_encode(['status' => 'error', 'message' => '书籍不存在']));
    }

    // 删除目录
    $bookDir = $uploadDir . $bookId;
    if (is_dir($bookDir)) {
        if (!deleteDirectory($bookDir)) {
            die(json_encode(['status' => 'error', 'message' => '目录删除失败']));
        }
    }

    // 更新书籍列表
    array_splice($books, $index, 1);
    file_put_contents($booksFile, json_encode($books, JSON_UNESCAPED_UNICODE));

    die(json_encode(['status' => 'success']));
}

// 处理文件上传
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    header('Content-Type: application/json');

    // 验证文件
    if (!isset($_FILES['book']) || $_FILES['book']['error'] !== UPLOAD_ERR_OK) {
        die(json_encode(['status' => 'error', 'message' => '文件上传失败']));
    }

    // 生成安全目录名
    $bookName = preg_replace('/[^\w\x{4e00}-\x{9fa5}]/u', '', pathinfo($_FILES['book']['name'], PATHINFO_FILENAME));
    $safeDir = $bookName . '_' . bin2hex(random_bytes(4));
    $targetDir = $uploadDir . $safeDir . '/';

    // 创建目录
    if (!mkdir($targetDir, 0755, true)) {
        die(json_encode(['status' => 'error', 'message' => '目录创建失败']));
    }

    // 移动文件
    move_uploaded_file($_FILES['book']['tmp_name'], $targetDir . 'source.txt');

    // 处理章节分割
    $content = mb_convert_encoding(file_get_contents($targetDir . 'source.txt'), 'UTF-8', 'auto');
    $chapters = preg_split('/(^(?:第[零一二三四五六七八九十百千0-9]+章|第\d+章|卷\d+).*$)/mu', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
    
    $chapterList = [];
    for ($i = 1; $i < count($chapters); $i += 2) {
        if (empty(trim($chapters[$i]))) continue;
        
        $chapterTitle = trim(strip_tags($chapters[$i]));
        
       // 修改章节内容处理部分(约在index.php第45行附近)
        $chapterContent = nl2br(htmlspecialchars(trim($chapters[$i+1] ?? '')));
        $index = 0;
        // 修改章节内容处理部分
        $chapterContent = preg_replace_callback('/<br\s*\/?>/i', function($matches) use (&$index) {
$index++;
return '</div>
<div class="paragraph-wrapper" data-index="'.$index.'">'.
    '<div class="paragraph-content"></div>'.
    '<textarea class="note-textarea" data-index="'.$index.'" style="display:none"></textarea>';
    }, $chapterContent);

    $fileName = "chapter_" . (count($chapterList)+1) . ".html";
    file_put_contents($targetDir . $fileName,
    "<div class='chapter-content'>
        <h3 class='chapter-title'>{$chapterTitle}</h3>
        <div class='content-text'>{$chapterContent}</div>
    </div>");

    $chapterList[] = [
    'title' => $chapterTitle,
    'file' => $fileName
    ];
    }

    // 保存章节信息
    file_put_contents($targetDir . 'chapters.json', json_encode($chapterList, JSON_UNESCAPED_UNICODE));

    // 更新书籍列表
    $books = json_decode(file_get_contents($booksFile), true);
    $books[] = [
    'id' => $safeDir,
    'title' => $bookName,
    'upload_time' => date('Y-m-d H:i:s'),
    'chapter_count' => count($chapterList)
    ];
    file_put_contents($booksFile, json_encode($books, JSON_UNESCAPED_UNICODE));

    die(json_encode(['status' => 'success', 'book' => $safeDir]));
    }

    // 获取当前书籍
    $currentBook = isset($_GET['book']) ? basename($_GET['book']) : '';
    if ($currentBook) $_SESSION['current_book'] = $currentBook;
    $currentBook = $_SESSION['current_book'] ?? '';
    ?>
    <!DOCTYPE html>
    <html lang="zh-CN">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <title>多书阅读器 - 小清新护眼版</title>
        <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
        <style>
            :root {
                --primary-color: #6b8e6b;
                --accent-color: #8db596;
                --background-color: #f8fbf8;
                --sidebar-bg: #ffffff;
                --text-primary: #2c3e2c;
                --text-secondary: #5a735a;
                --text-muted: #7a8c7a;
                --border-color: #e1eae1;
                --hover-color: #f0f7f0;
                --success-color: #7bb17b;
                
                /* 新增护眼模式变量 */
                --reading-bg: #f8fbf8;
                --reading-text: #2c3e2c;
                --reading-font-size: 1.1rem;
                --reading-line-height: 1.8;
                --reading-font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
            }
            
            /* 夜间模式 */
            .night-mode {
                --primary-color: #8db596;
                --accent-color: #a8c6a8;
                --background-color: #1a2f1a;
                --sidebar-bg: #243824;
                --text-primary: #e8f5e8;
                --text-secondary: #c8dcc8;
                --text-muted: #9ab39a;
                --border-color: #2d4a2d;
                --hover-color: #2d4a2d;
                --success-color: #8db596;
                --reading-bg: #1a2f1a;
                --reading-text: #e8f5e8;
            }
            
            /* 纸张模式 */
            .paper-mode {
                --reading-bg: #fefcf0;
                --reading-text: #5c4b37;
                --background-color: #fefcf0;
            }
            
            /* 羊皮纸模式 */
            .parchment-mode {
                --reading-bg: #f5f0e6;
                --reading-text: #4a3c2a;
                --background-color: #f5f0e6;
            }
            
            /* 深色护眼模式 */
            .dark-eye-mode {
                --reading-bg: #1e3a1e;
                --reading-text: #c8e6c8;
                --background-color: #1e3a1e;
            }
            
            * {
                box-sizing: border-box;
            }
            
            body {
                background: var(--background-color);
                min-height: 100vh;
                font-family: var(--reading-font-family);
                color: var(--text-primary);
                line-height: 1.6;
                transition: all 0.3s ease;
            }
            
            #sidebar {
                background: var(--sidebar-bg);
                height: 100vh;
                box-shadow: 2px 0 12px rgba(107, 142, 107, 0.08);
                transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                border-right: 1px solid var(--border-color);
            }
            
            #chapterList {
                overflow: auto;
                height: 60vh;
            }
            
            textarea {
                resize: vertical;
                display: block;
                width: 100%;
                font-family: var(--reading-font-family);
            }
            
            .btn-primary {
                background: var(--primary-color);
                border: none;
                border-radius: 8px;
                padding: 10px 16px;
                font-weight: 500;
                transition: all 0.3s ease;
            }
            
            .btn-primary:hover {
                background: #5a7a5a;
                transform: translateY(-2px);
                box-shadow: 0 4px 12px rgba(107, 142, 107, 0.2);
            }
            
            .book-item {
                padding: 14px;
                margin: 10px 0;
                border-radius: 12px;
                cursor: pointer;
                transition: all 0.3s ease;
                border: 1px solid var(--border-color);
                background: var(--sidebar-bg);
                position: relative;
                overflow: hidden;
            }
            
            .book-item::before {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                width: 4px;
                height: 100%;
                background: var(--accent-color);
                opacity: 0;
                transition: opacity 0.3s;
            }
            
            .book-item:hover {
                transform: translateY(-3px);
                box-shadow: 0 6px 16px rgba(107, 142, 107, 0.1);
                border-color: var(--accent-color);
            }
            
            .book-item:hover::before {
                opacity: 1;
            }
            
            .book-item.active {
                border-left: 4px solid var(--accent-color);
                background: var(--hover-color);
            }
            
            .chapter-item {
                padding: 12px 16px;
                border-bottom: 1px solid var(--border-color);
                cursor: pointer;
                transition: all 0.2s;
                border-radius: 6px;
                margin: 4px 0;
            }
            
            .chapter-item:hover {
                background: var(--hover-color);
                transform: translateX(4px);
            }
            
            .chapter-item.active {
                color: var(--primary-color);
                font-weight: 600;
                background: var(--hover-color);
                border-left: 3px solid var(--accent-color);
            }
            
            #contentArea {
                padding: 2.5rem;
                line-height: var(--reading-line-height);
                font-size: var(--reading-font-size);
                background: var(--reading-bg);
                color: var(--reading-text);
                height: 100vh;
                overflow: auto;
                border-radius: 12px;
                margin: 1rem;
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
                transition: all 0.3s ease;
            }
            
            .chapter-title {
                color: var(--primary-color);
                font-weight: 600;
                margin-bottom: 1.5rem;
                padding-bottom: 0.5rem;
                border-bottom: 2px solid var(--accent-color);
                text-align: center;
                font-size: 1.5rem;
            }
            
            /* 移动端样式 */
            #mobileMenuBtn {
                display: none;
                position: fixed;
                top: 15px;
                left: 15px;
                z-index: 1000;
                padding: 10px 14px;
                border: none;
                border-radius: 8px;
                background: var(--primary-color);
                color: white;
                box-shadow: 0 4px 12px rgba(107, 142, 107, 0.3);
                transition: all 0.3s;
                font-weight: 500;
            }
            
            #mobileMenuBtn:hover {
                transform: translateY(-2px);
                box-shadow: 0 6px 16px rgba(107, 142, 107, 0.4);
            }
            
            .sidebar-overlay {
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0, 0, 0, 0.4);
                z-index: 998;
                backdrop-filter: blur(2px);
            }
            
            @media (max-width: 768px) {
                #mobileMenuBtn {
                    display: block;
                }
            
                #sidebar {
                    position: fixed;
                    top: 0;
                    left: 0;
                    width: 300px;
                    height: 100vh;
                    transform: translateX(-100%);
                    z-index: 999;
                    border-radius: 0 12px 12px 0;
                }
            
                .sidebar-mobile-show {
                    transform: translateX(0) !important;
                }
            
                #contentArea {
                    width: 100%;
                    padding: 70px 20px 20px;
                    margin: 0;
                    border-radius: 0;
                }
            }
            
            /* 笔记相关样式 */
            .note-section {
                margin: 1.5rem 0;
                border-left: 4px solid var(--accent-color);
                padding-left: 1.2rem;
                background: color-mix(in srgb, var(--reading-bg) 95%, var(--accent-color));
                border-radius: 0 8px 8px 0;
                padding: 1rem 1.2rem;
            }
            
            .note-input {
                width: 100%;
                padding: 0.8rem;
                border: 1px solid var(--border-color);
                border-radius: 8px;
                resize: vertical;
                min-height: 80px;
                background: color-mix(in srgb, var(--reading-bg) 98%, var(--accent-color));
                transition: all 0.3s;
            }
            
            .note-input:focus {
                border-color: var(--accent-color);
                box-shadow: 0 0 0 3px rgba(141, 181, 150, 0.1);
                outline: none;
            }
            
            #copyNotesBtn {
                position: fixed;
                bottom: 25px;
                right: 25px;
                z-index: 1000;
                box-shadow: 0 4px 16px rgba(107, 142, 107, 0.3);
                border-radius: 50px;
                padding: 12px 20px;
                background: var(--primary-color);
                border: none;
                color: white;
                font-weight: 500;
                transition: all 0.3s;
            }
            
            #copyNotesBtn:hover {
                transform: translateY(-3px);
                box-shadow: 0 6px 20px rgba(107, 142, 107, 0.4);
                background: #5a7a5a;
            }
            
            /* 保存提示 */
            .save-notification {
                position: fixed;
                bottom: 25px;
                left: 50%;
                transform: translateX(-50%);
                background: var(--success-color);
                color: white;
                padding: 14px 28px;
                border-radius: 30px;
                box-shadow: 0 6px 20px rgba(123, 177, 123, 0.4);
                opacity: 0;
                transition: opacity 0.3s, transform 0.3s;
                z-index: 9999;
                pointer-events: none;
                font-weight: 500;
            }
            
            /* 段落交互样式 */
            .paragraph-wrapper {
                position: relative;
                margin: 1.2rem 0;
                padding: 8px;
                border-radius: 6px;
                transition: background 0.2s;
            }
            
            .paragraph-content {
                cursor: pointer;
                padding: 10px;
                border-radius: 6px;
                transition: all 0.2s;
                line-height: var(--reading-line-height);
            }
            
            .paragraph-content:hover {
                background: var(--hover-color);
            }
            
            .note-textarea.active {
                display: block !important;
                margin-top: 10px;
                animation: fadeIn 0.3s ease;
                border: 1px solid var(--border-color);
                border-radius: 8px;
                padding: 12px;
                background: color-mix(in srgb, var(--reading-bg) 98%, var(--accent-color));
                min-height: 80px;
            }
            
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(-8px); }
                to { opacity: 1; transform: translateY(0); }
            }
            
            .save-notification.show {
                opacity: 1;
                transform: translateX(-50%) translateY(-10px);
            }
            
            /* 字数统计面板 */
            .stats-panel {
                position: fixed;
                top: 25px;
                right: 25px;
                background: var(--sidebar-bg);
                padding: 20px;
                border-radius: 12px;
                box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
                z-index: 1000;
                min-width: 220px;
                border: 1px solid var(--border-color);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            }
            
            .stats-item {
                margin: 10px 0;
                font-size: 0.95em;
            }
            
            .stats-label {
                color: var(--text-muted);
                margin-right: 10px;
            }
            
            .stats-value {
                color: var(--primary-color);
                font-weight: 600;
            }
            
            /* 复制按钮样式 */
            .copy-btn {
                width: 100%;
                padding: 10px 14px;
                background: var(--accent-color);
                color: white;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                transition: all 0.3s;
                margin-top: 12px;
                font-weight: 500;
            }
            
            .copy-btn:hover {
                background: #7da585;
                transform: translateY(-2px);
                box-shadow: 0 4px 12px rgba(141, 181, 150, 0.3);
            }
            
            .copy-btn:active {
                transform: translateY(0);
            }
            
            .paragraph-content.has-note {
                border-left: 3px solid var(--accent-color);
                padding-left: 12px;
                background: color-mix(in srgb, var(--reading-bg) 95%, var(--accent-color));
            }
            
            /* 笔记文本框样式 */
            .note-textarea {
                min-height: 50px;
                overflow-y: hidden;
                transition: height 0.2s ease;
                font-family: var(--reading-font-family);
                line-height: 1.5;
            }
            
            /* 折叠按钮样式 */
            .toggle-button {
                position: absolute;
                top: 15px;
                right: 15px;
                width: 32px;
                height: 32px;
                border-radius: 50%;
                background: var(--accent-color);
                color: white;
                border: none;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
                font-size: 12px;
                transition: transform 0.3s, background 0.3s;
                z-index: 1001;
            }
            
            .toggle-button:hover {
                background: #7da585;
                transform: scale(1.1);
            }
            
            /* 折叠动画 */
            .stats-panel {
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            }
            
            .stats-panel.collapsed {
                transform: translateX(100%);
                opacity: 0;
                visibility: hidden;
            }
            
            .delete-btn {
                padding: 4px 10px;
                font-size: 12px;
                transition: all 0.3s;
                border-radius: 6px;
                background: color-mix(in srgb, var(--sidebar-bg) 90%, #ff4444);
                color: #a55;
                border: 1px solid color-mix(in srgb, var(--border-color) 80%, #ff4444);
            }
            
            .delete-btn:hover {
                background: color-mix(in srgb, var(--sidebar-bg) 80%, #ff4444);
                color: #933;
                transform: scale(1.05);
            }
            
            .book-item:hover .delete-btn {
                opacity: 1;
            }
            
            .book-item:not(:hover) .delete-btn {
                opacity: 0.7;
            }
            
            /* 进度条样式 */
            .progress {
                height: 6px;
                border-radius: 3px;
                background: var(--border-color);
                overflow: hidden;
            }
            
            .progress-bar {
                background: var(--accent-color);
                transition: width 0.3s ease;
            }
            
            /* 标题样式优化 */
            h5.text-muted {
                color: var(--text-secondary) !important;
                font-weight: 600;
                margin-bottom: 1rem;
                padding-bottom: 0.5rem;
                border-bottom: 1px solid var(--border-color);
            }
            
            /* 滚动条样式 */
            ::-webkit-scrollbar {
                width: 6px;
            }
            
            ::-webkit-scrollbar-track {
                background: color-mix(in srgb, var(--background-color) 90%, #000);
            }
            
            ::-webkit-scrollbar-thumb {
                background: var(--accent-color);
                border-radius: 3px;
            }
            
            ::-webkit-scrollbar-thumb:hover {
                background: #7da585;
            }
            
            /* 新增护眼控制面板 */
            .eye-care-panel {
                position: fixed;
                top: 25px;
                left: 25px;
                background: var(--sidebar-bg);
                padding: 20px;
                border-radius: 12px;
                box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
                z-index: 1000;
                min-width: 200px;
                border: 1px solid var(--border-color);
                transition: all 0.3s;
            }
            
            .eye-care-panel.collapsed {
                transform: translateX(-100%);
                opacity: 0;
                visibility: hidden;
            }
            
            .eye-care-control {
                margin: 12px 0;
            }
            
            .eye-care-label {
                display: block;
                color: var(--text-secondary);
                font-size: 0.9em;
                margin-bottom: 6px;
                font-weight: 500;
            }
            
            .mode-selector {
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 8px;
                margin-bottom: 12px;
            }
            
            .mode-btn {
                padding: 8px 12px;
                border: 1px solid var(--border-color);
                background: var(--sidebar-bg);
                color: var(--text-primary);
                border-radius: 6px;
                cursor: pointer;
                transition: all 0.2s;
                font-size: 0.85em;
                text-align: center;
            }
            
            .mode-btn:hover {
                background: var(--hover-color);
            }
            
            .mode-btn.active {
                background: var(--accent-color);
                color: white;
                border-color: var(--accent-color);
            }
            
            .font-controls {
                display: flex;
                align-items: center;
                gap: 10px;
                margin: 12px 0;
            }
            
            .font-btn {
                width: 36px;
                height: 36px;
                border: 1px solid var(--border-color);
                background: var(--sidebar-bg);
                color: var(--text-primary);
                border-radius: 6px;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.2s;
            }
            
            .font-btn:hover {
                background: var(--hover-color);
            }
            
            .font-btn.active {
                background: var(--accent-color);
                color: white;
                border-color: var(--accent-color);
            }
            
            .line-height-control {
                display: flex;
                align-items: center;
                gap: 10px;
                margin: 12px 0;
            }
            
            .line-height-btn {
                flex: 1;
                padding: 8px;
                border: 1px solid var(--border-color);
                background: var(--sidebar-bg);
                color: var(--text-primary);
                border-radius: 6px;
                cursor: pointer;
                transition: all 0.2s;
                text-align: center;
                font-size: 0.85em;
            }
            
            .line-height-btn:hover {
                background: var(--hover-color);
            }
            
            .line-height-btn.active {
                background: var(--accent-color);
                color: white;
                border-color: var(--accent-color);
            }
            
            .toggle-eye-care {
                position: fixed;
                top: 15px;
                left: 15px;
                width: 32px;
                height: 32px;
                border-radius: 50%;
                background: var(--accent-color);
                color: white;
                border: none;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
                font-size: 14px;
                transition: transform 0.3s, background 0.3s;
                z-index: 1001;
            }
            
            .toggle-eye-care:hover {
                background: #7da585;
                transform: scale(1.1);
            }
            
            /* 专注阅读模式 */
            .focus-mode .chapter-title {
                font-size: 1.8rem;
                margin-bottom: 2rem;
            }
            
            .focus-mode .paragraph-content {
                font-size: 1.2rem;
                line-height: 2;
                margin: 1.5rem 0;
            }
            
            /* 阅读进度指示器 */
            .reading-progress {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 3px;
                background: var(--border-color);
                z-index: 1002;
            }
            
            .reading-progress-bar {
                height: 100%;
                background: var(--accent-color);
                width: 0%;
                transition: width 0.1s ease;
            }
            
            /* 呼吸动画效果 */
            @keyframes breathe {
                0%, 100% { opacity: 1; }
                50% { opacity: 0.8; }
            }
            
            .breathe-mode .paragraph-content {
                animation: breathe 6s ease-in-out infinite;
            }
        </style>
    </head>

    <body>
        <!-- 阅读进度条 -->
        <div class="reading-progress">
            <div class="reading-progress-bar" id="readingProgress"></div>
        </div>
        
        <!-- 保存提示 -->
        <div id="saveNotification" class="save-notification"></div>
        
        <!-- 护眼控制面板 -->
        <button id="toggleEyeCare" class="toggle-eye-care">️</button>
        <div class="eye-care-panel" id="eyeCarePanel">
            <h6 style="color: var(--text-primary); margin-bottom: 15px;">护眼设置</h6>
            
            <div class="eye-care-control">
                <span class="eye-care-label">阅读模式</span>
                <div class="mode-selector">
                    <button class="mode-btn active" data-mode="default">默认</button>
                    <button class="mode-btn" data-mode="paper">纸张</button>
                    <button class="mode-btn" data-mode="parchment">羊皮纸</button>
                    <button class="mode-btn" data-mode="dark-eye">深色护眼</button>
                    <button class="mode-btn" data-mode="night">夜间</button>
                    <button class="mode-btn" data-mode="focus">专注模式</button>
                </div>
            </div>
            
            <div class="eye-care-control">
                <span class="eye-care-label">字体大小</span>
                <div class="font-controls">
                    <button class="font-btn" data-size="0.9">小</button>
                    <button class="font-btn active" data-size="1.1">中</button>
                    <button class="font-btn" data-size="1.3">大</button>
                    <button class="font-btn" data-size="1.5">特大</button>
                </div>
            </div>
            
            <div class="eye-care-control">
                <span class="eye-care-label">行高</span>
                <div class="line-height-control">
                    <button class="line-height-btn" data-height="1.5">紧凑</button>
                    <button class="line-height-btn active" data-height="1.8">舒适</button>
                    <button class="line-height-btn" data-height="2.2">宽松</button>
                </div>
            </div>
            
            <div class="eye-care-control">
                <span class="eye-care-label">特殊效果</span>
                <div style="display: flex; gap: 8px;">
                    <button class="mode-btn" id="toggleBreathe">呼吸模式</button>
                    <button class="mode-btn" id="toggleBlur">背景模糊</button>
                </div>
            </div>
        </div>
        
        <!-- 字数统计 -->
        <button id="toggleStats" class="toggle-button">▼</button>
        <div class="stats-panel">
            <div class="stats-item">
                <span class="stats-label">字数状态</span>
                <span class="stats-value" id="notesCount">0</span>/<span class="stats-value" id="originalCount">0</span>
            </div>
            <div class="stats-item">
                <button id="copyAllNotes" class="copy-btn"><i class="fas fa-copy me-2"></i>复制全部笔记</button>
            </div>
        </div>
        
        <button id="mobileMenuBtn"><i class="fas fa-bars me-2"></i>菜单</button>
        <div class="sidebar-overlay"></div>

        <div class="container-fluid">
            <div class="row">
                <!-- 左侧边栏 -->
                <div class="col-lg-3 col-md-4" id="sidebar">
                    <div class="p-4">
                        <!-- 上传表单 -->
                        <form id="uploadForm" class="mb-4">
                            <input type="file" id="bookFile" name="book" accept=".txt" hidden>
                            <label for="bookFile" class="btn btn-primary w-100">
                                <i class="fas fa-upload me-2"></i>上传小说
                            </label>
                            <div class="progress mt-3" style="display: none;">
                                <div class="progress-bar" role="progressbar" style="width: 0%"></div>
                            </div>
                        </form>

                        <!-- 书籍列表 -->
                        <h5 class="mb-3"><i class="fas fa-book me-2"></i>我的书架</h5>
                        <div id="bookList">
                            <?php foreach(json_decode(file_get_contents($booksFile), true) as $book): ?>
                            <div class="book-item <?= $book['id'] === $currentBook ? 'active' : '' ?>" data-id="<?= htmlspecialchars($book['id']) ?>">
                                <div class="d-flex justify-content-between align-items-center">
                                    <div class="fw-bold"><?= htmlspecialchars($book['title']) ?></div>
                                    <button class="btn btn-sm delete-btn"><i class="fas fa-trash"></i></button>
                                </div>
                                <small class="text-muted">
                                    <i class="fas fa-list me-1"></i>共<?= $book['chapter_count'] ?>章 ·
                                    <i class="far fa-clock me-1"></i><?= date('m/d H:i', strtotime($book['upload_time'])) ?>
                                </small>
                            </div>
                            <?php endforeach; ?>
                        </div>

                        <!-- 章节目录 -->
                        <h5 class="mt-4 mb-3"><i class="fas fa-list-ul me-2"></i>章节列表</h5>
                        <div id="chapterList">
                            <?php if ($currentBook && file_exists($uploadDir . $currentBook . '/chapters.json')): ?>
                            <?php $chapters = json_decode(file_get_contents($uploadDir . $currentBook . '/chapters.json'), true); ?>
                            <?php foreach ($chapters as $chapter): ?>
                            <div class="chapter-item" data-file="<?= htmlspecialchars($chapter['file']) ?>">
                                <i class="far fa-file-alt me-2"></i><?= htmlspecialchars($chapter['title']) ?>
                            </div>
                            <?php endforeach; ?>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>

                <!-- 阅读区域 -->
                <div class="col-lg-9 col-md-8">
                    <div id="contentArea">
                        <?php if ($currentBook): ?>
                        <div class="text-center text-muted mt-5 py-5">
                            <i class="fas fa-book-open fa-3x mb-3" style="color: var(--accent-color);"></i>
                            <h4>请选择章节开始阅读</h4>
                        </div>
                        <?php else: ?>
                        <div class="text-center mt-5 py-5">
                            <i class="fas fa-book-reader fa-4x mb-4" style="color: var(--accent-color);"></i>
                            <h3 class="text-muted">欢迎使用阅读器</h3>
                            <p class="text-secondary">请上传或选择书籍开始阅读</p>
                        </div>
                        <?php endif; ?>
                    </div>
                </div>
            </div>
        </div>

        <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
        <script>
        // 删除书籍功能
$(document).on('click', '.delete-btn', function(e) {
    e.stopPropagation();
    const $bookItem = $(this).closest('.book-item');
    const bookId = $bookItem.data('id');
    
    if (!confirm(`确定要永久删除《${$bookItem.find('.fw-bold').text()}》吗?此操作不可恢复!`)) return;

    $.post({
        url: '',
        data: {
            action: 'deleteBook',
            bookId: bookId
        },
        dataType: 'json'
    }).done(function(res) {
        if (res.status === 'success') {
            $bookItem.slideUp(300, () => $(this).remove());
            // 如果当前正在阅读被删除的书籍
            if ('<?= $currentBook ?>' === bookId) {
                window.location.href = './';
            }
        } else {
            alert('删除失败:' + (res.message || '未知错误'));
        }
    }).fail(function() {
        alert('网络请求失败,请检查连接');
    });
});

// 护眼功能管理器
class EyeCareManager {
    constructor() {
        this.settings = {
            mode: 'default',
            fontSize: '1.1',
            lineHeight: '1.8',
            breatheMode: false,
            blurMode: false,
            focusMode: false
        };
        this.init();
    }

    init() {
        this.loadSettings();
        this.bindEvents();
        this.applySettings();
    }

    loadSettings() {
        const saved = localStorage.getItem('eyeCareSettings');
        if (saved) {
            this.settings = { ...this.settings, ...JSON.parse(saved) };
        }
    }

    saveSettings() {
        localStorage.setItem('eyeCareSettings', JSON.stringify(this.settings));
    }

    bindEvents() {
        // 模式切换
        $('.mode-btn').on('click', (e) => {
            const mode = $(e.target).data('mode');
            if (mode) {
                this.setMode(mode);
            }
        });

        // 字体大小
        $('.font-btn').on('click', (e) => {
            const size = $(e.target).data('size');
            this.setFontSize(size);
        });

        // 行高
        $('.line-height-btn').on('click', (e) => {
            const height = $(e.target).data('height');
            this.setLineHeight(height);
        });

        // 特殊效果
        $('#toggleBreathe').on('click', () => {
            this.toggleBreatheMode();
        });

        $('#toggleBlur').on('click', () => {
            this.toggleBlurMode();
        });

        // 阅读进度
        $(window).on('scroll', this.updateReadingProgress.bind(this));
    }

    setMode(mode) {
        $('body').removeClass('night-mode paper-mode parchment-mode dark-eye-mode focus-mode');
        if (mode !== 'default') {
            $('body').addClass(`${mode}-mode`);
        }
        
        if (mode === 'focus') {
            this.settings.focusMode = true;
            this.setFontSize('1.2');
            this.setLineHeight('2');
        } else {
            this.settings.focusMode = false;
        }
        
        this.settings.mode = mode;
        this.updateActiveButtons('.mode-btn', mode);
        this.saveSettings();
    }

    setFontSize(size) {
        document.documentElement.style.setProperty('--reading-font-size', size + 'rem');
        this.settings.fontSize = size;
        this.updateActiveButtons('.font-btn', size);
        this.saveSettings();
    }

    setLineHeight(height) {
        document.documentElement.style.setProperty('--reading-line-height', height);
        this.settings.lineHeight = height;
        this.updateActiveButtons('.line-height-btn', height);
        this.saveSettings();
    }

    toggleBreatheMode() {
        this.settings.breatheMode = !this.settings.breatheMode;
        $('body').toggleClass('breathe-mode', this.settings.breatheMode);
        $('#toggleBreathe').toggleClass('active', this.settings.breatheMode);
        this.saveSettings();
    }

    toggleBlurMode() {
        this.settings.blurMode = !this.settings.blurMode;
        $('#contentArea').css('backdrop-filter', this.settings.blurMode ? 'blur(2px)' : 'none');
        $('#toggleBlur').toggleClass('active', this.settings.blurMode);
        this.saveSettings();
    }

    updateActiveButtons(selector, value) {
        $(selector).removeClass('active');
        $(`${selector}[data-${selector.includes('font') ? 'size' : selector.includes('line-height') ? 'height' : 'mode'}="${value}"]`).addClass('active');
    }

    applySettings() {
        this.setMode(this.settings.mode);
        this.setFontSize(this.settings.fontSize);
        this.setLineHeight(this.settings.lineHeight);
        if (this.settings.breatheMode) this.toggleBreatheMode();
        if (this.settings.blurMode) this.toggleBlurMode();
    }

    updateReadingProgress() {
        const winHeight = $(window).height();
        const docHeight = $(document).height();
        const scrollTop = $(window).scrollTop();
        const progress = (scrollTop / (docHeight - winHeight)) * 100;
        $('#readingProgress').css('width', progress + '%');
    }
}

// 初始化护眼管理器
let eyeCareManager;

$(function() {
    eyeCareManager = new EyeCareManager();
    
    // 护眼面板控制
    $('#toggleEyeCare').on('click', function() {
        const panel = $('#eyeCarePanel');
        const isCollapsed = panel.hasClass('collapsed');
        
        panel.toggleClass('collapsed', !isCollapsed);
        $(this).text(isCollapsed ? '️' : '⚙️');
        
        if (isCollapsed) {
            $(this).css('transform', 'translateX(220px)');
        } else {
            $(this).css('transform', 'translateX(0)');
        }
    });

    // 移动端侧边栏控制
    const $sidebar = $('#sidebar');
    const $overlay = $('.sidebar-overlay');
    const $menuBtn = $('#mobileMenuBtn');
    
    // 自动调整高度函数
    function autoResizeTextarea() {
        const $this = $(this);
        const currentVal = $this.val();
        const lastContent = $this.data('lastContent') || '';
        
        const hasNewLine = currentVal.includes('\n') && 
                          !lastContent.includes('\n') &&
                          currentVal !== lastContent;
     
        const needsAutoResize = this.scrollHeight > $this.height();
     
        if (hasNewLine || needsAutoResize) {
            clearTimeout($this.data('resizeTimer'));
            $this.data('resizeTimer', setTimeout(() => {
                $this.css('height', '0').height(this.scrollHeight);
                $this.data('lastContent', currentVal);
            }, 50));
        }
    }

    // 初始化已有textarea
    $('.note-textarea').each(function() {
        $(this).data('lastContent', $(this).val()).trigger('input');
    });

    // 修改输入事件绑定 - 只在回车时换行
    $(document).off('input', '.note-textarea').on('keydown', '.note-textarea', function(e) {
        if (e.key === 'Enter' || e.keyCode === 13) {
            e.preventDefault();
            
            const textarea = this;
            const start = textarea.selectionStart;
            const end = textarea.selectionEnd;
            const value = textarea.value;
            
            textarea.value = value.substring(0, start) + '\n' + value.substring(end);
            textarea.selectionStart = textarea.selectionEnd = start + 1;
            
            autoResizeTextarea.call(textarea);
            $(this).trigger('input');
        }
    });

    function isMobile() {
        return window.innerWidth <= 768;
    }

    function initMobileLayout() {
        if(isMobile()) {
            $menuBtn.show();
            $overlay.hide();
            $sidebar.removeClass('sidebar-mobile-show');
        } else {
            $menuBtn.hide();
            $overlay.hide();
            $sidebar.removeClass('sidebar-mobile-show');
        }
    }

    $menuBtn.on('click', function(e) {
        e.stopPropagation();
        $sidebar.addClass('sidebar-mobile-show');
        $overlay.fadeIn(200);
        $('body').css('overflow', 'hidden');
    });

    $overlay.on('click', function() {
        $sidebar.removeClass('sidebar-mobile-show');
        $overlay.fadeOut(200);
        $('body').css('overflow', 'auto');
    });

    $(document).on('click', '.chapter-item', function() {
        if(isMobile()) {
            $sidebar.removeClass('sidebar-mobile-show');
            $overlay.fadeOut(200);
            $('body').css('overflow', 'auto');
        }
    });

    $(window).on('resize', initMobileLayout);
    initMobileLayout();

    // 文件上传处理
    $('#bookFile').on('change', function() {
        const formData = new FormData();
        formData.append('book', this.files[0]);

        $('.progress').show().find('.progress-bar').css('width', '0%');

        $.ajax({
            url: '',
            type: 'POST',
            data: formData,
            contentType: false,
            processData: false,
            xhr: function() {
                const xhr = new XMLHttpRequest();
                xhr.upload.addEventListener('progress', function(e) {
                    if(e.lengthComputable) {
                        const percent = Math.round((e.loaded / e.total) * 100);
                        $('.progress-bar').css('width', percent + '%');
                    }
                }, false);
                return xhr;
            },
            success: function(res) {
                if(res.status === 'success') {
                    location.href = '?book=' + encodeURIComponent(res.book);
                } else {
                    alert(res.message || '上传失败');
                }
            },
            complete: function() {
                $('.progress').hide();
            }
        });
    });

    // 书籍和章节控制
    $(document).on('click', '.book-item', function() {
        window.location.href = '?book=' + encodeURIComponent($(this).data('id'));
    });

    // 字数统计面板折叠
    $('#toggleStats').on('click', function() {
        const panel = $('.stats-panel');
        const isCollapsed = panel.hasClass('collapsed');
        
        panel.toggleClass('collapsed', !isCollapsed);
        this.textContent = isCollapsed ? '▼' : '▶';
        
        if (isCollapsed) {
            $(this).css('transform', 'translateX(0)');
        } else {
            $(this).css('transform', 'translateX(-100%)');
        }
    });

    // 添加去抖动函数
    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            const context = this;
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(context, args), wait);
        };
    }

    // 在复制功能中添加兼容性处理
    function fallbackCopy(text) {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.select();
        
        try {
            document.execCommand('copy');
            return true;
        } catch (err) {
            return false;
        } finally {
            document.body.removeChild(textarea);
        }
    }

    // 修改后的复制功能代码
    $('#copyAllNotes').click(async function() {
        try {
            // 收集所有笔记
            let notes = [];
            $('.note-textarea').each(function(index) {
                const content = $(this).val().trim();
                if (content) {
                    notes.push(`\n${content}`);
                }
            });
    
            if (notes.length === 0) {
                showSaveNotification('暂无笔记可复制');
                return;
            }
    
            // 格式化文本(必须先定义)
            const textToCopy = notes.join('');
    
            // 执行复制操作
            if (navigator.clipboard) {
                await navigator.clipboard.writeText(textToCopy);
            } else {
                if (!fallbackCopy(textToCopy)) {
                    throw new Error('Clipboard API not available');
                }
            }
    
            showSaveNotification(`已复制${notes.length}条笔记`);
            
            // 视觉反馈
            $(this).css({
                'background': 'var(--success-color)',
                'color': 'white'
            }).html('<i class="fas fa-check me-2"></i>复制成功');
            
            setTimeout(() => {
                $(this).css({
                    'background': 'var(--accent-color)',
                    'color': 'white'
                }).html('<i class="fas fa-copy me-2"></i>复制全部笔记');
            }, 2000);
        } catch (err) {
            console.error('复制失败:', err);
            showSaveNotification('复制失败,请手动选择文本');
            $(this).css('background', '#e74c3c').html('<i class="fas fa-exclamation-triangle me-2"></i>复制失败');
            setTimeout(() => {
                $(this).css('background', 'var(--accent-color)').html('<i class="fas fa-copy me-2"></i>复制全部笔记');
            }, 2000);
        }
    });
    
    // 增强保存功能
    function bindNoteEvents() {
        $('#contentArea').off('input', '.note-textarea').on('input', '.note-textarea', debounce(function() {
            const $textarea = $(this);
            const bookId = '<?= $currentBook ?>';
            const chapterFile = $('.chapter-item.active').data('file');
            const paragraphIndex = $textarea.data('index');
            const content = $textarea.val().trim();
    
            // 实时更新字数统计
            calculateWordCount();
            
            // 自动显示/隐藏逻辑
            $textarea.toggleClass('active', content.length > 0)
                     .closest('.paragraph-wrapper')
                     .find('.paragraph-content')
                     .toggleClass('has-note', content.length > 0);
    
            // 发送保存请求
            $.post('api.php', {
                action: 'saveNote',
                bookId: bookId,
                chapter: chapterFile,
                index: paragraphIndex,
                content: content
            }).done(function(response) {
                if (response.status === 'success') {
                    showSaveNotification(`保存成功 (${content.length}字)`);
                } else {
                    showSaveNotification('保存失败: ' + (response.message || '未知错误'));
                }
            }).fail(function() {
                showSaveNotification('网络连接失败');
            });
        }, 1000));
    }
    
    // 修改后的loadNotes函数
    function loadNotes() {
        const bookId = '<?= $currentBook ?>';
        const chapterFile = $('.chapter-item.active').data('file');
        
        if (!bookId || !chapterFile) {
            return $.Deferred().resolve().promise(); // 返回空Promise
        }
        
        // 返回jQuery的ajax对象
        return $.post('api.php', {
            action: 'loadNotes',
            bookId: bookId,
            chapter: chapterFile
        }, function(response) {
            if (response.status === 'success') {
                Object.entries(response.notes).forEach(([index, content]) => {
                    const $textarea = $(`textarea[data-index="${index}"]`);
                    $textarea.val(content);
                    autoResizeTextarea.call($textarea[0]); // 新增
                    if (content.trim().length > 0) {
                        $textarea.addClass('active')
                                 .closest('.paragraph-wrapper')
                                 .find('.paragraph-content')
                                 .addClass('has-note');
                    }
                });
            }
        });
    }
    
    // 在全局作用域添加这些函数
    function showSaveNotification(message) {
        const $note = $('#saveNotification');
        $note.text(message).addClass('show');
        setTimeout(() => $note.removeClass('show'), 2000);
    }
    
    function calculateWordCount() {
        let total = 0;
        $('.note-textarea').each(function() {
            const text = $(this).val();
            total += professionalWordCount(text);
        });
        $('#notesCount').text(total);
    }
    
    function calculateOriginalCount() {
        const originalText = $('#contentArea').clone().find('.note-textarea').remove().end().text();
        $('#originalCount').text(professionalWordCount(originalText));
    }
    
    // 行业标准字数统计
    function professionalWordCount(text) {
        // 中文及中文标点计数(每个字符计1字)
        const chineseRegex = /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/g;
        const chineseMatches = text.match(chineseRegex) || [];
        const chineseCount = chineseMatches.length;
    
        // 处理非中文部分
        const nonChinese = text
            .replace(chineseRegex, '')  // 移除已统计的中文
            .replace(/\s+/g, ' ')       // 合并连续空格
            .trim();
    
        // 统计英文单词和数据单词(连续字母数字序列计1字)
        const westernWords = nonChinese ? nonChinese.split(/\s+/) : [];
        
        return chineseCount + westernWords.length;
    }
    
    // 新增DOM处理函数
    function processParagraphs() {
        $('.content-text').each(function() {
            const $container = $(this);
            let html = $container.html();
            
            // 转换原始结构
            html = html.replace(/<\/div><div class="paragraph-wrapper/g, '<div class="paragraph-wrapper')
                      .replace(/^<div class="paragraph-wrapper/, '</div><div class="paragraph-wrapper');
            $container.html(html);
            
            // 提取原始文本到段落容器
            $container.find('.paragraph-wrapper').each(function() {
                const $wrapper = $(this);
                const $content = $wrapper.find('.paragraph-content');
                const $textarea = $wrapper.find('textarea');
                
                // 将原始文本移动到段落容器
                const rawHtml = $wrapper.contents().not($content).not($textarea).text();
                $content.html(rawHtml);
                $wrapper.contents().not($content).not($textarea).remove();
            });
        });
    
        // 绑定双击事件(移动到这里)
        $('.paragraph-content').off('dblclick').on('dblclick', function() {
            const $wrapper = $(this).closest('.paragraph-wrapper');
            const $textarea = $wrapper.find('.note-textarea');
            
            $textarea.addClass('active')
                     .focus()
                     .off('blur')
                     .on('blur', function() {
                         if ($(this).val().trim() === '') {
                             $(this).removeClass('active');
                         }
                     });
        });
    }
    
    // 修改章节点击事件处理代码
    $(document).on('click', '.chapter-item', function() {
        $('.chapter-item').removeClass('active');
        $(this).addClass('active');
        
        const bookDir = 'books/' + getCurrentBookId();
        const chapterFile = $(this).data('file');
        
        $('#contentArea').load(bookDir + '/' + chapterFile, function() {
            processParagraphs();
            
            // 修改后的调用方式
            loadNotes()
                .always(function() {
                    bindNoteEvents();
                    calculateWordCount();
                    calculateOriginalCount();
                })
                .fail(function() {
                    showSaveNotification('笔记加载失败');
                });
        });
    });
    
    function getCurrentBookId() {
        return new URLSearchParams(location.search).get('book') || '';
    }
});

// 初始化时保持展开状态
document.querySelector('.stats-panel').classList.remove('collapsed');
</script>
    </body>

    </html>

after endapi:

Real-time save API:

<?php
// api.php
session_start();
header('Content-Type: application/json');

$uploadDir = 'books/';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        $action = $_POST['action'] ?? '';
        $bookId = $_POST['bookId'] ?? '';
        $chapter = basename($_POST['chapter'] ?? '');
        $index = (int)($_POST['index'] ?? 0);
        $content = $_POST['content'] ?? '';

        // 验证参数
       if (empty($bookId) || !preg_match('/^[\w\x{4e00}-\x{9fa5}-]+$/u', $bookId)) {
            throw new Exception('无效的书籍ID');
        }

        $bookPath = $uploadDir . $bookId;
        $notesPath = $bookPath . '/notes';
        
        // 确保目录存在
        if (!file_exists($notesPath)) {
            mkdir($notesPath, 0755, true);
        }

        $noteFile = $notesPath . '/' . pathinfo($chapter, PATHINFO_FILENAME) . '.json';

        if ($action === 'saveNote') {
            // 读取现有笔记
            $notes = file_exists($noteFile) ? json_decode(file_get_contents($noteFile), true) : [];
            
            // 更新笔记内容
            if ($index > 0) {
                $notes[$index] = $content;
                file_put_contents($noteFile, json_encode($notes, JSON_UNESCAPED_UNICODE));
            }
            
            echo json_encode(['status' => 'success']);
        }
        elseif ($action === 'loadNotes') {
            // 读取笔记文件
            $notes = file_exists($noteFile) ? json_decode(file_get_contents($noteFile), true) : [];
            echo json_encode(['status' => 'success', 'notes' => $notes]);
        }
        else {
            throw new Exception('无效的操作');
        }
    } catch (Exception $e) {
        echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
    }
} else {
    echo json_encode(['status' => 'error', 'message' => '无效的请求方法']);
}
微信赞赏

WeChat

支付宝赞赏

Alipay

✍️ Author: Hong Mu

webmaster · Thanks for reading, stay tuned for more exciting content

Author homepage View home page →

Related articles

PHP ob function record

PHP ob function record Language Notes PHP

Usage of the following three functions ob_get_contents(); ob_end_clean(); ob_start(); You can use these functions to buffer local files and execute local script code. Use ob_start() to save the output code into the buffer, and the page will not be displayed; then use ob_get_contents to get the data in the buffer. o…
👁 140
PHP preg

PHP preg Language Notes PHP

The preg_match_all function is used to perform a global regular expression match. preg_match_all() Syntax int preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG…
👁 169

Recommended reading

(Adaptive Mobile Version) LED Lighting – pbootCMS Website Template; Download LED Green Lighting Website Source Code – 0547

(Adaptive Mobile Version) LED Lighting – pbootCMS Website Template; Download LED Green Lighting Website Source Code – 0547 Practical Collection pbootcms Template

An LED lighting and green lighting PbootCMS website template supporting mobile access. Featuring a modern, energy-efficient design, it is ideal for LED lighting manufacturers and lighting engineering companies to showcase their products. This template helps lighting enterprises present their brands online and attract engineering and international trade clients. Template Display | Installation Instructions | Website Backend: /admin.php | Username: admin | Password: admin | Extraction Password: www.4s5.cn...
👁 52
Knowledge-based paid video platform template – 11194 0556

Knowledge-based paid video platform template – 11194 0556 Practical Collection Yiyou template

An EyouCMS professional template designed for knowledge-based paid-content platforms and video streaming websites. Its knowledge-oriented design enables the display of paid videos, course categories, membership services, and online payment functionality, facilitating educational institutions or content creators in building online video-based paid-content platforms. Template Demo | Installation Instructions | Website Backend: /login.php | Username: admin | Password: admin | Related Articles: Summary of Common Installation Issues for EyouCMS | EyouCMS...
👁 37
(Adaptive mobile version) SEO-optimized blog website template – Download source code for personal blog and information websites – 1131

(Adaptive mobile version) SEO-optimized blog website template – Download source code for personal blog and information websites – 1131 Practical Collection pbootcms Template

An SEO-optimized blog and personal blog website template using PbootCMS, compatible with both PC and WAP devices. The clean, informative design makes it ideal for SEO bloggers to share optimization techniques, industry trends, and case studies. It helps SEO professionals build their personal brand and knowledge influence online. Template Preview | Installation Instructions | Website Admin Panel: /admin.php | Username: admin | Password: admin | Extraction Password: www...
👁 45
Responsive Financial Planning Service Management Website Template 1175

Responsive Financial Planning Service Management Website Template 1175 Practical Collection Yiyou template

An EyouCMS responsive website template designed for financial wealth management service management. Its professional financial design enables effective presentation of wealth management products, investment services, wealth management solutions, and success stories. This template helps financial institutions showcase their brand online and attract investors. Template Overview | Installation Instructions | Website Backend: /login.php | Username: admin | Password: admin | Related Articles: Summary of Common Installation Issues for EyouCMS | EyouCMS (E...
👁 41
(PC + WAP) pbootCMS – Eco-friendly, versatile corporate website template; Download source code for general construction industry websites – 0548

(PC + WAP) pbootCMS – Eco-friendly, versatile corporate website template; Download source code for general construction industry websites – 0548 Practical Collection pbootcms Template

A eco-friendly PbootCMS universal corporate website template ideal for the construction, engineering, and environmental protection industries, compatible with both PC and mobile devices. Its minimalist yet sophisticated design aligns perfectly with the brand identity of companies operating in the construction and environmental sectors. The template includes a comprehensive corporate showcase module, enabling businesses to quickly launch their branded websites and effectively build customer trust. Template Display | Installation Instructions | Website Backend: /admin.php | Username: admin | Password:...
👁 42