html,php,ajax写的一个小说阅读器

纯阅读版本:

<?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>

增加笔记版本。

<?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>

后端api:

实时保存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' => '无效的请求方法']);
}
© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容