| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- {% extends "base.html" %}
- {% block title %}逐鹿导航 - 导入预览{% endblock %}
- {% block head %}
- {{ super() }}
- <!-- 确保 jQuery 和 Bootstrap JS 正确加载 -->
- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
- {% endblock %}
- {% block content %}
- <div class="row">
- <div class="col-md-12">
- <div class="card">
- <div class="card-header d-flex justify-content-between align-items-center">
- <h4>书签导入预览</h4>
- <div>
- <span class="badge bg-primary">总计: {{ preview_data.total_count }} 个书签</span>
- </div>
- </div>
- <div class="card-body">
- <!-- 统计信息 -->
- <div class="row mb-4">
- <div class="col-md-3">
- <div class="card text-white bg-success">
- <div class="card-body text-center">
- <h4>{{ preview_data.folders | sum(attribute='new_count') }}</h4>
- <p>新书签</p>
- </div>
- </div>
- </div>
- <div class="col-md-3">
- <div class="card text-white bg-warning">
- <div class="card-body text-center">
- <h4>{{ preview_data.folders | sum(attribute='duplicate_count') }}</h4>
- <p>重复书签</p>
- </div>
- </div>
- </div>
- <div class="col-md-3">
- <div class="card text-white bg-info">
- <div class="card-body text-center">
- <h4>{{ preview_data.folders | length }}</h4>
- <p>分类数量</p>
- </div>
- </div>
- </div>
- <div class="col-md-3">
- <div class="card">
- <div class="card-body text-center">
- <div class="form-check form-switch">
- <input class="form-check-input" type="checkbox" id="selectAll" checked>
- <label class="form-check-label" for="selectAll">全选</label>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 书签预览 -->
- <form id="previewForm">
- {% for folder in preview_data.folders %}
- <div class="card mb-3">
- <div class="card-header">
- <h5 class="mb-0">
- <span class="badge bg-secondary me-2">{{ folder.bookmarks | length }}</span>
- {{ folder.name }}
- <small class="text-muted">
- (新: {{ folder.new_count }}, 重复: {{ folder.duplicate_count }})
- </small>
- </h5>
- </div>
- <div class="card-body">
- <div class="table-responsive">
- <table class="table table-hover">
- <thead>
- <tr>
- <th width="30">
- <input type="checkbox" class="folder-select"
- data-folder="{{ folder.name }}" checked>
- </th>
- <th width="40%">名称</th>
- <th width="40%">网址</th>
- <th width="20%">分类</th>
- <th width="80">状态</th>
- </tr>
- </thead>
- <tbody>
- {% for bookmark in folder.bookmarks %}
- <tr class="{% if bookmark.is_duplicate %}table-warning{% endif %}">
- <td>
- <input type="checkbox" name="selected_bookmarks"
- value="{{ loop.index0 }}"
- data-folder="{{ folder.name }}"
- {% if bookmark.selected %}checked{% endif %}
- class="bookmark-check">
- </td>
- <td>
- <input type="text" class="form-control form-control-sm bookmark-name"
- value="{{ bookmark.name }}"
- data-original="{{ bookmark.name }}">
- </td>
- <td>
- <input type="text" class="form-control form-control-sm bookmark-url"
- value="{{ bookmark.url }}"
- data-original="{{ bookmark.url }}">
- {% if bookmark.description %}
- <small class="text-muted">{{ bookmark.description }}</small>
- {% endif %}
- </td>
- <td>
- <select class="form-select form-select-sm bookmark-category">
- <option value="{{ folder.name }}">{{ folder.name }}</option>
- {% for category in user_categories %}
- <option value="{{ category.name }}">{{ category.name }}</option>
- {% endfor %}
- <option value="_custom">自定义...</option>
- </select>
- <input type="text" class="form-control form-control-sm mt-1 custom-category"
- placeholder="输入分类名称" style="display: none;">
- </td>
- <td>
- {% if bookmark.is_duplicate %}
- <span class="badge bg-warning">重复</span>
- {% else %}
- <span class="badge bg-success">新</span>
- {% endif %}
- </td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- </div>
- </div>
- </div>
- {% endfor %}
- </form>
- <!-- 操作按钮 -->
- <div class="d-flex justify-content-between mt-4">
- <a href="{{ url_for('import_bookmarks') }}" class="btn btn-secondary">
- <i class="fas fa-arrow-left"></i> 重新上传
- </a>
- <button type="button" id="startImport" class="btn btn-success">
- <i class="fas fa-download"></i> 开始导入
- </button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 导入进度模态框 -->
- <div class="modal fade" id="importProgressModal" tabindex="-1">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">正在导入书签</h5>
- </div>
- <div class="modal-body">
- <div class="progress mb-3">
- <div class="progress-bar progress-bar-striped progress-bar-animated"
- role="progressbar" style="width: 0%"></div>
- </div>
- <div class="import-status">
- <p>准备开始导入...</p>
- </div>
- <div class="import-results" style="display: none;">
- <h6>导入结果:</h6>
- <ul class="list-unstyled"></ul>
- </div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="cancelBtn" style="display: none;">取消</button>
- <button type="button" class="btn btn-primary" data-bs-dismiss="modal" id="completeBtn" style="display: none;">完成</button>
- </div>
- </div>
- </div>
- </div>
- {% endblock %}
- {% block scripts %}
- <script>
- // 检查 jQuery 和 Bootstrap 是否已加载
- function checkDependencies() {
- if (typeof jQuery === 'undefined') {
- console.error('jQuery 未加载,正在动态加载...');
- // 动态加载 jQuery
- var script = document.createElement('script');
- script.src = 'https://code.jquery.com/jquery-3.6.0.min.js';
- script.onload = function() {
- console.log('jQuery 动态加载完成');
- // 加载 Bootstrap JS
- loadBootstrap();
- };
- document.head.appendChild(script);
- } else {
- console.log('jQuery 已加载');
- // 检查 Bootstrap
- if (typeof bootstrap === 'undefined') {
- loadBootstrap();
- } else {
- console.log('Bootstrap 已加载');
- initializePage();
- }
- }
- }
- function loadBootstrap() {
- console.log('正在加载 Bootstrap JS...');
- var script = document.createElement('script');
- script.src = 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js';
- script.onload = function() {
- console.log('Bootstrap JS 动态加载完成');
- initializePage();
- };
- script.onerror = function() {
- console.error('Bootstrap JS 加载失败');
- // 即使 Bootstrap 加载失败,也尝试初始化页面
- initializePage();
- };
- document.head.appendChild(script);
- }
- function initializePage() {
- console.log('页面初始化开始');
- // 检查 Bootstrap modal 是否可用
- if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
- console.log('Bootstrap Modal 可用');
- } else {
- console.warn('Bootstrap Modal 不可用,将使用备用方案');
- }
- // 全选/全不选
- $('#selectAll').change(function() {
- $('.bookmark-check').prop('checked', this.checked);
- updateSelectionCount();
- });
- // 文件夹选择
- $('.folder-select').change(function() {
- const folder = $(this).data('folder');
- $(`.bookmark-check[data-folder="${folder}"]`).prop('checked', this.checked);
- updateSelectionCount();
- });
- // 单个书签选择
- $('.bookmark-check').change(updateSelectionCount);
- // 分类选择
- $('.bookmark-category').change(function() {
- const customInput = $(this).closest('td').find('.custom-category');
- if ($(this).val() === '_custom') {
- customInput.show();
- } else {
- customInput.hide();
- }
- });
- // 开始导入
- $('#startImport').click(function() {
- console.log('开始导入按钮被点击');
- const selectedBookmarks = prepareImportData();
- console.log('选中的书签:', selectedBookmarks);
- if (selectedBookmarks.length === 0) {
- alert('请至少选择一个书签进行导入');
- return;
- }
- // 使用 Bootstrap Modal 或备用方案
- try {
- // 尝试使用 Bootstrap Modal
- if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
- var modalElement = document.getElementById('importProgressModal');
- var modal = new bootstrap.Modal(modalElement);
- modal.show();
- } else {
- // 备用方案:直接显示模态框
- $('#importProgressModal').show();
- $('body').addClass('modal-open');
- }
- } catch (error) {
- console.error('显示模态框失败:', error);
- // 备用方案:直接显示模态框
- $('#importProgressModal').show();
- $('body').addClass('modal-open');
- }
- startImport(selectedBookmarks);
- });
- function updateSelectionCount() {
- const selectedCount = $('.bookmark-check:checked').length;
- const totalCount = $('.bookmark-check').length;
- $('#selectAll').prop('checked', selectedCount === totalCount);
- console.log('已选择书签数量:', selectedCount, '/', totalCount);
- }
- function prepareImportData() {
- const selectedBookmarks = [];
- $('.bookmark-check:checked').each(function() {
- const row = $(this).closest('tr');
- const folder = $(this).data('folder');
- const name = row.find('.bookmark-name').val();
- const url = row.find('.bookmark-url').val();
- const categorySelect = row.find('.bookmark-category');
- let category = categorySelect.val();
- if (category === '_custom') {
- category = row.find('.custom-category').val() || folder;
- }
- selectedBookmarks.push({
- name: name,
- url: url,
- folder: folder,
- custom_category: category !== folder ? category : null
- });
- });
- return selectedBookmarks;
- }
- function startImport(bookmarks) {
- const importData = {
- selected_bookmarks: bookmarks,
- import_as_public: {{ preview_data.import_as_public | tojson }}
- };
- console.log('开始导入,数据:', importData);
- // 开始导入
- $.ajax({
- url: '/api/import-bookmarks',
- method: 'POST',
- contentType: 'application/json',
- data: JSON.stringify(importData),
- success: function(response) {
- console.log('导入启动响应:', response);
- if (response.status === 'started') {
- monitorProgress(response.import_id);
- } else {
- console.error('导入启动失败:', response);
- updateProgress('导入启动失败: ' + (response.error || '未知错误'), 0, true);
- }
- },
- error: function(xhr, status, error) {
- console.error('AJAX错误:', status, error, xhr.responseText);
- updateProgress('导入启动失败: ' + error, 0, true);
- }
- });
- }
- function monitorProgress(importId) {
- console.log('开始监控进度:', importId);
- const progressBar = $('.progress-bar');
- const statusText = $('.import-status');
- const resultsDiv = $('.import-results');
- const resultsList = $('.import-results ul');
- const cancelBtn = $('#cancelBtn');
- const completeBtn = $('#completeBtn');
- let checkCount = 0;
- const maxChecks = 300;
- function checkProgress() {
- if (checkCount >= maxChecks) {
- console.log('进度检查超时');
- updateProgress('导入超时', 0, true);
- return;
- }
- const progressUrl = '/api/import-progress/' + importId;
- console.log('查询进度:', progressUrl);
- $.get(progressUrl)
- .done(function(data) {
- console.log('进度响应:', data);
- if (data.error) {
- console.error('进度查询错误:', data.error);
- updateProgress('导入任务不存在: ' + data.error, 0, true);
- return;
- }
- const percent = data.total > 0 ? Math.round((data.processed / data.total) * 100) : 0;
- progressBar.css('width', percent + '%');
- progressBar.text(percent + '%');
- statusText.html(`
- <p>进度: ${data.processed}/${data.total} (${percent}%)</p>
- <p>状态: ${data.status}</p>
- <p>成功: ${data.results.success} | 失败: ${data.results.failed}</p>
- `);
- if (data.status === 'completed' || data.status === 'failed') {
- console.log('导入完成,状态:', data.status);
- // 显示结果
- resultsDiv.show();
- resultsList.empty();
- if (data.results.errors && data.results.errors.length > 0) {
- data.results.errors.forEach(error => {
- resultsList.append(`<li class="text-danger">${error}</li>`);
- });
- }
- if (data.results.success > 0) {
- resultsList.prepend(`<li class="text-success">成功导入 ${data.results.success} 个书签</li>`);
- }
- cancelBtn.hide();
- completeBtn.show();
- progressBar.removeClass('progress-bar-animated');
- if (data.status === 'completed') {
- progressBar.removeClass('bg-warning').addClass('bg-success');
- // 3秒后自动跳转
- setTimeout(function() {
- window.location.href = "/dashboard";
- }, 3000);
- } else {
- progressBar.removeClass('bg-success').addClass('bg-danger');
- }
- } else {
- // 继续检查进度
- setTimeout(checkProgress, 1000);
- checkCount++;
- }
- })
- .fail(function(xhr, status, error) {
- console.error('进度查询失败:', status, error, xhr.responseText);
- updateProgress('进度查询失败: ' + error, 0, true);
- });
- }
- cancelBtn.show().off('click').click(function() {
- console.log('用户取消导入');
- try {
- if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
- var modalElement = document.getElementById('importProgressModal');
- var modal = bootstrap.Modal.getInstance(modalElement);
- modal.hide();
- } else {
- $('#importProgressModal').hide();
- $('body').removeClass('modal-open');
- }
- } catch (error) {
- $('#importProgressModal').hide();
- $('body').removeClass('modal-open');
- }
- });
- completeBtn.click(function() {
- console.log('用户确认完成');
- window.location.href = "/dashboard";
- });
- checkProgress();
- }
- function updateProgress(message, percent, isError) {
- const progressBar = $('.progress-bar');
- const statusText = $('.import-status');
- progressBar.css('width', percent + '%').text(percent + '%');
- statusText.html(`<p class="${isError ? 'text-danger' : ''}">${message}</p>`);
- if (isError) {
- progressBar.removeClass('bg-success progress-bar-animated').addClass('bg-danger');
- $('#completeBtn').show();
- $('#cancelBtn').hide();
- }
- }
- updateSelectionCount();
- console.log('页面初始化完成');
- }
- // 页面加载完成后检查依赖并初始化
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', checkDependencies);
- } else {
- checkDependencies();
- }
- </script>
- {% endblock %}
|