| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- {% extends "base.html" %}
- {% block title %}逐鹿导航 - 我的导航{% endblock %}
- {% block scripts %}
- <script>
- // 按创建时间升序排序站点(旧站点在前,新站点在后)
- function sortSitesByCreatedAt(sites) {
- return sites.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
- }
- function showCategoryModal(action, categoryId = null) {
- const modal = new bootstrap.Modal(document.getElementById('categoryModal'));
- const form = document.getElementById('categoryForm');
- const modalTitle = document.getElementById('categoryModalLabel');
- if (action === 'add') {
- modalTitle.textContent = '新增分类';
- form.reset();
- document.getElementById('categoryId').value = '';
- } else if (action === 'edit') {
- modalTitle.textContent = '编辑分类';
- fetch(`/api/category/${categoryId}`)
- .then(response => response.json())
- .then(data => {
- document.getElementById('categoryId').value = data.id;
- document.getElementById('categoryName').value = data.name;
- document.getElementById('isPublic').checked = data.is_public;
- });
- }
- modal.show();
- }
- function submitCategoryForm() {
- const form = document.getElementById('categoryForm');
- const formData = new FormData(form);
- const categoryId = formData.get('category_id');
- const url = categoryId ? `/api/category/${categoryId}` : '/api/category';
- const method = categoryId ? 'PUT' : 'POST';
- // 将 FormData 转换为 JSON 对象
- const jsonData = {
- name: formData.get('name'),
- is_public: formData.get('is_public') === 'on' // 将复选框值转换为布尔值
- };
- if (categoryId) {
- jsonData.id = categoryId; // 添加分类 ID(仅用于编辑)
- }
- fetch(url, {
- method: method,
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json'
- },
- body: JSON.stringify(jsonData)
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- location.reload();
- } else {
- alert(data.message || '操作失败');
- }
- })
- .catch(error => {
- console.error('Error:', error);
- alert('操作失败');
- });
- }
- </script>
- {% endblock %}
- {% block content %}
- <h2>我的导航</h2>
- <div class="row mb-4">
- <div class="col-md-12">
- <a href="{{ url_for('add_site') }}" class="btn btn-primary me-2">添加站点</a>
- <button type="button" class="btn btn-secondary me-2" onclick="showCategoryModal('add')">添加分类</button>
- <button type="button" class="btn btn-info me-2" data-bs-toggle="modal" data-bs-target="#manageCategoriesModal">
- 管理分类
- </button>
- <a href="{{ url_for('import_bookmarks') }}" class="btn btn-success">导入书签</a>
- </div>
- </div>
- <!-- 分类管理弹窗 -->
- <div class="modal fade" id="manageCategoriesModal" tabindex="-1" aria-labelledby="manageCategoriesModalLabel" aria-hidden="true">
- <div class="modal-dialog modal-lg">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="manageCategoriesModalLabel">管理分类</h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body">
- {% if categories %}
- <div class="list-group">
- {% for category in categories %}
- <div class="d-flex justify-content-between align-items-center mb-2 border rounded p-2">
- <div>
- <h6 class="mb-1">{{ category.name }}</h6>
- <small class="text-muted">{{ category.sites|length }} 个站点</small>
- </div>
- <div>
- <button type="button" class="btn btn-sm btn-outline-primary me-1" onclick="showCategoryModal('edit', {{ category.id }})">编辑</button>
- <form method="POST" action="{{ url_for('delete_category', category_id=category.id) }}"
- class="d-inline"
- onsubmit="return confirm('确定要删除分类 {{ category.name }} 吗?这将同时删除该分类下的所有站点。');">
- <button type="submit" class="btn btn-sm btn-outline-danger">删除</button>
- </form>
- </div>
- </div>
- {% endfor %}
- </div>
- {% else %}
- <p class="text-muted text-center py-3">您还没有创建任何分类,点击上方"添加分类"按钮创建第一个分类吧!</p>
- {% endif %}
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 编辑/新增分类弹窗(放到 body 直接下方,脱离父 modal) -->
- <div class="modal fade" id="categoryModal" tabindex="-1" aria-labelledby="categoryModalLabel" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="categoryModalLabel">编辑分类</h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body">
- <form id="categoryForm">
- <input type="hidden" id="categoryId" name="category_id">
- <div class="mb-3">
- <label for="categoryName" class="form-label">分类名称</label>
- <input type="text" class="form-control" id="categoryName" name="name" required>
- </div>
- <div class="mb-3 form-check">
- <input type="checkbox" class="form-check-input" id="isPublic" name="is_public">
- <label class="form-check-label" for="isPublic">公开分类</label>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
- <button type="button" class="btn btn-primary" onclick="submitCategoryForm()">保存</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 按分类显示站点(保持原样) -->
- <div class="card">
- <div class="card-header">
- <h5>我的站点</h5>
- </div>
- <div class="card-body">
- {% if sites %}
- {% set categorized_sites = {} %}
- {% for site in sites %}
- {% if site.category %}
- {% if site.category.id not in categorized_sites %}
- {% set _ = categorized_sites.update({site.category.id: {'category': site.category, 'sites': [site]}}) %}
- {% else %}
- {% set _ = categorized_sites[site.category.id].sites.append(site) %}
- {% endif %}
- {% endif %}
- {% endfor %}
- {% for category_id, data in categorized_sites.items() %}
- <div class="mb-4">
- <h6 class="text-primary fw-bold border-bottom pb-2 mb-3">
- 📁 {{ data.category.name }} ({{ data.sites|length }})
- </h6>
- <div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-3">
- {% for site in data.sites %}
- <div class="col">
- <div class="card h-100">
- <div class="card-body py-2">
- <div class="row align-items-center">
- <div class="col-auto">
- {% if site.custom_icon %}
- <img src="{{ site.custom_icon }}" alt="图标"
- style="width:40px; height:40px; border-radius:8px; margin-right:8px;"
- onerror="this.src='{{ url_for('static', filename='images/default-icon.png') }}'">
- {% elif site.icon %}
- <img src="{{ site.icon }}" alt="图标"
- style="width:40px; height:40px; border-radius:8px; margin-right:8px;"
- onerror="this.src='{{ url_for('static', filename='images/default-icon.png') }}'">
- {% else %}
- <img src="{{ url_for('static', filename='images/default-icon.png') }}" alt="图标"
- style="width:40px; height:40px; border-radius:8px; margin-right:8px;">
- {% endif %}
- </div>
- <div class="col">
- <div class="d-flex justify-content-between align-items-center">
- <div>
- <h6 class="mb-1">
- <a href="{{ url_for('site_detail', site_id=site.id) }}"
- class="text-decoration-none text-dark">{{ site.name }}</a>
- </h6>
- <small class="text-muted">{{ site.url }}</small>
- </div>
- <div>
- <span class="badge bg-secondary bg-opacity-75 small">
- {{ '公开' if site.is_public else '私有' }}
- </span>
- </div>
- </div>
- {% if site.description %}
- <small class="text-muted d-block mt-1">{{ site.description }}</small>
- {% endif %}
- <div class="mt-1">
- <a href="{{ url_for('edit_site', site_id=site.id) }}" class="btn btn-sm btn-outline-primary me-1">编辑</a>
- <a href="{{ url_for('site_detail', site_id=site.id) }}" class="btn btn-sm btn-outline-secondary me-1">查看</a>
- <form method="POST" action="{{ url_for('delete_site', site_id=site.id) }}"
- class="d-inline"
- onsubmit="return confirm('确定要删除站点 {{ site.name }} 吗?');">
- <button type="submit" class="btn btn-sm btn-outline-danger">删除</button>
- </form>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- {% endfor %}
- </div>
- </div>
- {% endfor %}
- {% set uncategorized_sites = sites | selectattr('category', 'equalto', none) | list %}
- {% if uncategorized_sites %}
- <div class="mb-4">
- <h6 class="text-muted fw-bold border-bottom pb-2 mb-3">
- 📋 未分类 ({{ uncategorized_sites|length }})
- </h6>
- <div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-3">
- {% for site in uncategorized_sites %}
- <div class="col">
- <div class="card h-100">
- <div class="card-body py-2">
- <div class="row align-items-center">
- <div class="col-auto">
- {% if site.custom_icon %}
- <img src="{{ site.custom_icon }}" alt="图标"
- style="width:40px; height:40px; border-radius:8px; margin-right:8px;"
- onerror="this.src='{{ url_for('static', filename='images/default-icon.png') }}'">
- {% elif site.icon %}
- <img src="{{ site.icon }}" alt="图标"
- style="width:40px; height:40px; border-radius:8px; margin-right:8px;"
- onerror="this.src='{{ url_for('static', filename='images/default-icon.png') }}'">
- {% else %}
- <img src="{{ url_for('static', filename='images/default-icon.png') }}" alt="图标"
- style="width:40px; height:40px; border-radius:8px; margin-right:8px;">
- {% endif %}
- </div>
- <div class="col">
- <div class="d-flex justify-content-between align-items-center">
- <div>
- <h6 class="mb-1">
- <a href="{{ url_for('site_detail', site_id=site.id) }}"
- class="text-decoration-none text-dark">{{ site.name }}</a>
- </h6>
- <small class="text-muted">{{ site.url }}</small>
- </div>
- <div>
- <span class="badge bg-secondary bg-opacity-75 small">
- {{ '公开' if site.is_public else '私有' }}
- </span>
- </div>
- </div>
- {% if site.description %}
- <small class="text-muted d-block mt-1">{{ site.description }}</small>
- {% endif %}
- <div class="mt-1">
- <a href="{{ url_for('edit_site', site_id=site.id) }}" class="btn btn-sm btn-outline-primary me-1">编辑</a>
- <a href="{{ url_for('site_detail', site_id=site.id) }}" class="btn btn-sm btn-outline-secondary me-1">查看</a>
- <form method="POST" action="{{ url_for('delete_site', site_id=site.id) }}"
- class="d-inline"
- onsubmit="return confirm('确定要删除站点 {{ site.name }} 吗?');">
- <button type="submit" class="btn btn-sm btn-outline-danger">删除</button>
- </form>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- {% endfor %}
- </div>
- </div>
- {% endif %}
- {% else %}
- <p class="text-muted">您还没有添加任何站点,点击上方按钮添加第一个站点吧!</p>
- {% endif %}
- </div>
- </div>
- {% endblock %}
|