From 75c691c183b27abc65653b9f3b1c9528109de48d Mon Sep 17 00:00:00 2001 From: wzj <244142824@qq.com> Date: Wed, 9 Jul 2025 00:35:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=EF=BC=8C=E4=BC=98=E5=8C=96=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- app.py | 42 + data/attachments.json | 20 + data/categories.json | 5 + data/settings.json | 6 +- templates/attachments.html | 179 +++-- templates/categories.html | 364 +++++++-- templates/index.html | 1478 +++++++++++++++++++----------------- templates/manage.html | 173 +++-- 9 files changed, 1395 insertions(+), 878 deletions(-) diff --git a/README.md b/README.md index d985e5f..a72473d 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ ## 待开发功能 ⏳ ### 界面优化 1. logo图标设置区分明亮和暗黑模式 -2. 私有应用在首页添加标识 +2. 私有应用在首页添加标识 - 已完成 3. 首页卡片右键菜单 4. 应用支持配置多个URL,左键打开默认URL,右键可选择URL进行复制地址或者打开或者编辑应用 5. 新增应用界面便捷增加分类 @@ -37,10 +37,10 @@ 2. 书签收藏工具 ### BUG修复 -1. 游客通过接口能查看私有应用; +1. 游客通过接口能查看私有应用 - 已修复 ### 移动端适配 -1. 后台页面适配 +1. 后台页面适配 - 已完成 ### 批量操作 1. 应用批量选择功能: diff --git a/app.py b/app.py index af3555b..3694355 100644 --- a/app.py +++ b/app.py @@ -1033,6 +1033,48 @@ def change_password(username, new_password): else: print("错误:只能修改管理员 (admin) 的密码") +@app.route('/app/add_from_category', methods=['POST']) +@login_required +def add_app_from_category(): + categories = load_categories() + icons = load_icons() + settings = load_settings() + attachments = load_attachments() + + if request.method == 'POST': + title = request.form['title'] + url = request.form['url'] + icon = request.form['icon'] + description = request.form.get('description', '') + private = 'private' in request.form + main_category = request.form['main_category'] + sub_category = request.form['sub_category'] + + new_app = { + "title": title, + "url": url, + "icon": icon, + "description": description, + "private": private, + "category": { + "main": main_category, + "sub": sub_category + } + } + + apps = load_apps() + apps.append(new_app) + save_apps(apps) + + flash('应用添加成功', 'success') + return redirect(url_for('manage_categories')) + + return render_template('add_app.html', + categories=categories, + settings=settings, + icons=load_icons(), + attachments=attachments) + if __name__ == '__main__': parser = argparse.ArgumentParser(description='导航系统管理工具') diff --git a/data/attachments.json b/data/attachments.json index 09656c9..642781c 100644 --- a/data/attachments.json +++ b/data/attachments.json @@ -63,5 +63,25 @@ "filename": "c77f6bbbb335.png", "type": "icon", "upload_time": "2025-07-06 20:22:28" + }, + { + "filename": "hpCMDHrCJVq3.png", + "type": "logo", + "upload_time": "2025-07-08 21:54:48" + }, + { + "filename": "ksDMQ8aEwEph.png", + "type": "logo", + "upload_time": "2025-07-08 21:55:09" + }, + { + "filename": "gfpWMYOvTTuI.png", + "type": "logo", + "upload_time": "2025-07-08 21:55:20" + }, + { + "filename": "G31hd5Kzu6t8.png", + "type": "logo", + "upload_time": "2025-07-08 21:55:29" } ] \ No newline at end of file diff --git a/data/categories.json b/data/categories.json index bae2580..96e33db 100644 --- a/data/categories.json +++ b/data/categories.json @@ -155,6 +155,11 @@ "name": "营养", "color": "#4d908e", "weight": 2 + }, + "swim": { + "name": "游泳", + "color": "#4895ef", + "weight": 4 } }, "weight": 4, diff --git a/data/settings.json b/data/settings.json index 547d941..401e53a 100644 --- a/data/settings.json +++ b/data/settings.json @@ -1,15 +1,15 @@ { "card_style": "compact", "search_history": [], - "theme": "light", + "theme": "dark", "bg_image": "/upload/background/5dd4f5d3cd7b.png", "dark_bg_image": "/upload/background/d078c01de3be.png", "site_title": "应用导航中心", "show_logo": true, "logo_type": "image", "logo_icon": "fa-solid fa-th-list", - "logo_image": "/upload/logo/f40e2eb965b2.png", + "logo_image": "/upload/logo/hpCMDHrCJVq3.png", "dark_bg_rotate": false, "admin_password_hash": "pbkdf2:sha256:260000$ig0vNC2QdipLHgAy$6d734e5112bbdf94bc5cb49a38ca4764376ebb51c877a416d1a8f27d5fb5c415", - "footer_html": "
\r\n
\r\n © 2023 AIDaohang. All rights reserved.\r\n |\r\n Powered by AIDaohang\r\n
\r\n
" + "footer_html": "
\r\n
\r\n © 2023 AIDaohang. All rights reserved.\r\n |\r\n Powered by AIDaohang\r\n
\r\n
" } \ No newline at end of file diff --git a/templates/attachments.html b/templates/attachments.html index e6d8979..dc1c551 100644 --- a/templates/attachments.html +++ b/templates/attachments.html @@ -34,6 +34,8 @@
附件列表
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + + +
添加主分类
-
- - - - -
- - + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+ +
-
添加子分类
-
- - - - - -
- - + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+ +
-
已存在的分类
-
- {% for main_id, cat in categories.items() %} -
-
-
- {{ cat.name }}(ID: {{ main_id }}) - {{ cat.color }} - {% if cat.get('private', False) %} - 私有 - {% endif %} -
-
- - 编辑 - - - 删除 - + +
+
+ {% for main_id, cat in categories.items()|sort(attribute='1.weight', reverse=True) %} +
+
+
+ {{ cat.name }}(ID: {{ main_id }}) + {{ cat.get('weight', 0) }} + {% if cat.get('private', False) %} + 私有 + {% endif %} +
+
+ + + 编辑 + + + 删除 + +
+
    + {% for sub_id, subData in cat.sub.items()|sort(attribute='1.weight', reverse=True) %} +
  • + + {{ subData.name }}(ID: {{ sub_id }}) + {{ subData.get('weight', 0) }} + {% if cat.get('sub_private', {}).get(sub_id, False) %} + 私有 + {% endif %} + +
    + + + 编辑 + + + 删除 + +
    +
  • + {% endfor %} +
-
    - {% for sub_id, subData in cat.sub.items() %} -
  • - - {{ subData.name }}(ID: {{ sub_id }}) - {{ subData.color }} - {% if cat.get('sub_private', {}).get(sub_id, False) %} - 私有 - {% endif %} - - +
+ + +
+
+ {% for main_id, cat in categories.items()|sort(attribute='1.weight', reverse=True) %} +
+
+
+ {{ cat.name }} (ID: {{ main_id }}) + {{ cat.get('weight', 0) }} + {% if cat.get('private', False) %} + 私有 + {% endif %} +
+
+ +
+
+
+ - {% endfor %} + +
子分类列表
+ {% for sub_id, subData in cat.sub.items()|sort(attribute='1.weight', reverse=True) %} +
+
+
+
+ {{ subData.name }} (ID: {{ sub_id }}) + {{ subData.get('weight', 0) }} + {% if cat.get('sub_private', {}).get(sub_id, False) %} + 私有 + {% endif %} +
+
+
+ + + 编辑 + + + 删除 + +
+
+
+ {% endfor %} +
+
+
+ {% endfor %} +
@@ -106,4 +317,15 @@
+ + {% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index edbe4d2..6e5eef1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,7 +2,7 @@ - + 应用导航 {% if settings and settings.logo_type == 'image' and settings.logo_image %} @@ -25,6 +25,9 @@ --bg-image: none; --dark-bg-image: none; } + * { + box-sizing: border-box; + } body { font-family: 'Segoe UI', system-ui, sans-serif; max-width: 1400px; @@ -33,6 +36,8 @@ background-color: #f8f9fa; color: #333; transition: background-color 0.3s ease, color 0.3s ease; + overflow-x: hidden; + width: 100%; } /* 新增私有卡片标记样式 */ @@ -45,7 +50,7 @@ border-style: solid; border-width: 0 30px 30px 0; border-color: transparent #f8961e transparent transparent; - border-radius: 0 12px 0 0; /* 只圆角右上角 */ + border-radius: 0 12px 0 0; } .private-badge::after { @@ -152,6 +157,8 @@ margin: 20px auto; max-width: 600px; position: relative; + width: 100%; + padding: 0 15px; } .search-input { width: 100%; @@ -164,7 +171,7 @@ } .search-results { position: absolute; - width: 100%; + width: calc(100% - 30px); background: white; border-radius: 0 0 10px 10px; box-shadow: 0 5px 15px rgba(0,0,0,0.1); @@ -237,9 +244,24 @@ /* 暗黑模式下的按钮样式 */ body.dark-theme .floating-btn { background: #444; - color: #eee; + color: #e0e0e0; } +.floating-btn#addBtn { + background-color: var(--primary-color); + color: white; + display: none; +} + +.floating-btn#addBtn:hover { + background-color: var(--hover-color); +} + +body.dark-theme .floating-btn#addBtn { + background-color: #444; + color: #e0e0e0; +} + /* 简洁模式卡片样式 */ .app-item.compact { padding: 10px 15px; @@ -330,7 +352,7 @@ color: #aaa !important; } - /* 修改后的筛选容器样式 - 支持横向滚动 */ + /* 修改后的筛选容器样式 */ .filter-container { display: flex; flex-direction: column; @@ -346,9 +368,14 @@ padding: 15px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); transition: background-color 0.3s ease, color 0.3s ease; + margin-left: -15px; + margin-right: -15px; + padding-left: 20px; + padding-right: 20px; + width: calc(100% + 30px); } - /* 修改后的筛选行样式 - 支持横向滚动 */ + /* 修改后的筛选行样式 */ .filter-row { display: flex; flex-wrap: nowrap; @@ -357,10 +384,13 @@ padding-bottom: 10px; scrollbar-width: thin; scrollbar-color: #c2c3c9d6; - justify-content: center; /* 默认居中 */ - padding: 0 20px; /* 添加内边距 */ + justify-content: center; + padding: 0 15px; scroll-behavior: smooth; position: relative; + margin-left: -10px; + margin-right: -10px; + width: calc(100% + 20px); } /* 当内容超出时靠左 */ @@ -417,8 +447,8 @@ body.dark-theme .filter-row::after { font-size: 14px; display: flex; align-items: center; - white-space: nowrap; /* 禁止按钮内文字换行 */ - flex-shrink: 0; /* 禁止按钮缩小 */ + white-space: nowrap; + flex-shrink: 0; } .filter-btn:hover { transform: translateY(-2px); @@ -463,7 +493,7 @@ body.dark-theme .filter-row::after { transform: rotate(90deg); } - /* 修改后的二级筛选容器样式 - 支持横向滚动 */ + /* 修改后的二级筛选容器样式 */ .secondary-filters-container { width: 100%; display: none; @@ -473,10 +503,13 @@ body.dark-theme .filter-row::after { padding-bottom: 10px; scrollbar-width: thin; scrollbar-color: #c2c3c9d6; - justify-content: center; /* 默认居中 */ - padding: 0 20px; /* 添加内边距 */ + justify-content: center; + padding: 0 15px; scroll-behavior: smooth; position: relative; + margin-left: -10px; + margin-right: -10px; + width: calc(100% + 20px); } /* 私有主分类 */ @@ -542,6 +575,7 @@ body.dark-theme .secondary-filters-container::after { .app-list-container { margin-top: 20px; position: relative; + width: 100%; } .category-title { font-size: 20px; @@ -558,11 +592,13 @@ body.dark-theme .secondary-filters-container::after { } .app-group { margin-bottom: 30px; + width: 100%; } .app-list { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; + width: 100%; } @media (max-width: 1200px) { .app-list { @@ -578,11 +614,34 @@ body.dark-theme .secondary-filters-container::after { .app-list { grid-template-columns: repeat(2, 1fr); } + body { + padding: 15px; + } + .filter-container { + margin-left: -15px; + margin-right: -15px; + padding-left: 20px; + padding-right: 20px; + width: calc(100% + 30px); + } + .floating-btn::after { + display: none; + } } @media (max-width: 576px) { .app-list { grid-template-columns: 1fr; } + body { + padding: 10px; + } + .filter-container { + margin-left: -10px; + margin-right: -10px; + padding-left: 15px; + padding-right: 15px; + width: calc(100% + 20px); + } } .app-item { background-color: #ffffff69; @@ -597,6 +656,7 @@ body.dark-theme .secondary-filters-container::after { text-decoration: none; color: inherit; position: relative; + width: 100%; } .app-item:hover { transform: translateY(-5px); @@ -737,6 +797,7 @@ body.dark-theme .secondary-filters-container::after { border-top: 1px solid #eee; text-align: center; color: #777; + width: 100%; } @@ -783,10 +844,12 @@ body.dark-theme .secondary-filters-container::after {
- - + + + +
@@ -805,10 +868,12 @@ body.dark-theme .secondary-filters-container::after { const loadingSpinner = document.getElementById('loadingSpinner'); const loginBtn = document.getElementById('loginBtn'); const adminBtn = document.getElementById('adminBtn'); + const backToTopBtn = document.getElementById('backToTopBtn'); let currentPrimaryFilter = 'all'; let currentSecondaryFilter = 'all'; let currentExpandedPrimary = null; + let activeSecondaryFilter = null; // 新增:当前激活的二级筛选 // 从后端加载数据 async function loadData() { @@ -931,733 +996,776 @@ body.dark-theme .secondary-filters-container::after { cat.sub = Object.fromEntries( Object.entries(cat.sub).sort((a, b) => { const weightA = a[1].weight || 0; - const weightB = b[1].weight || 0; - return weightB - weightA; // 降序排列 - }) - ); -} -}); + const weightB = b[1].weight || 0; + return weightB - weightA; // 降序排列 + }) + ); + } + }); -// 渲染界面 -renderFilters(); -renderApps(); -loadingSpinner.style.display = 'none'; -primaryFilters.querySelector('[data-filter="all"]').textContent = '全部'; - } catch (error) { - console.error('加载数据失败:', error); - appListContainer.innerHTML = ` -
- 加载数据失败,请刷新页面重试 -
- `; - } -} - -// 更新登录按钮状态 -function updateLoginButton() { -if (isLoggedIn) { - loginBtn.innerHTML = ''; - loginBtn.href = '/logout'; - loginBtn.title = '退出登录'; - adminBtn.href = '/manage'; // 已登录时直接跳转到管理页面 -} else { - loginBtn.innerHTML = ''; - loginBtn.href = '/login'; - loginBtn.title = '登录'; - adminBtn.href = '/login?next=/manage'; // 未登录时跳转到登录页面 -} -} - -// 根据分类ID获取对应的颜色类 -function getColorForCategory(categoryId) { -return categoryId; -} - -// 生成筛选按钮 -function renderFilters() { -// 生成一级标签 -primaryFilters.innerHTML = ''; -const allPrimaryBtn = document.createElement('button'); -allPrimaryBtn.className = 'filter-btn active'; -allPrimaryBtn.textContent = '全部'; -allPrimaryBtn.dataset.level = '1'; -allPrimaryBtn.dataset.filter = 'all'; -allPrimaryBtn.addEventListener('click', () => { - setFilter('all', 'all'); - showAllSecondaryFilters(); - setAllPrimaryCaretDown(true); -}); -primaryFilters.appendChild(allPrimaryBtn); - -// 添加一级标签按钮(从分类数据生成) -Object.entries(categories).forEach(([catId, catData]) => { - const btn = document.createElement('button'); - btn.className = `filter-btn ${catId}`; - btn.innerHTML = ` ${catData.name}`; - btn.dataset.level = '1'; - btn.dataset.filter = catId; - // 确保有颜色时才设置背景色 - if (catData.color) { - btn.style.backgroundColor = catData.color; - btn.style.color = getContrastColor(catData.color); - } - btn.addEventListener('click', (e) => { - if (currentExpandedPrimary === catId) { - // 如果点击的是已展开的一级分类,则显示所有二级分类 - showAllSecondaryFilters(); - setFilter('all', 'all'); - setAllPrimaryCaretDown(true); - } else { - // 否则展开该一级分类下的二级分类 - expandSecondary(catId); - setFilter(catId, 'all'); - setAllPrimaryCaretDown(false); - setPrimaryCaretDown(catId, true); - } - }); - primaryFilters.appendChild(btn); -}); - -// 初始加载时显示所有二级标签 -showAllSecondaryFilters(); -} - -// 设置所有一级标签箭头的展开/收起状态 -function setAllPrimaryCaretDown(shouldDown) { -const primaryBtns = primaryFilters.querySelectorAll('[data-level="1"]'); -primaryBtns.forEach(btn => { - if (btn.dataset.filter !== 'all') { - const caret = btn.querySelector('.caret'); - if (caret) { - if (shouldDown) { - caret.classList.add('down'); - } else { - caret.classList.remove('down'); - } - } - } -}); -} - -// 设置特定一级标签箭头的展开/收起状态 -function setPrimaryCaretDown(primaryId, shouldDown) { -const btn = primaryFilters.querySelector(`[data-filter="${primaryId}"]`); -if (btn) { - const caret = btn.querySelector('.caret'); - if (caret) { - if (shouldDown) { - caret.classList.add('down'); - } else { - caret.classList.remove('down'); - } - } -} -} - -// 显示所有二级标签 -function showAllSecondaryFilters() { -collapseAllSecondary(); - -const container = document.createElement('div'); -container.className = 'secondary-filters-container show'; -container.id = 'secondary-all'; - -const allSecondaryBtn = document.createElement('button'); -allSecondaryBtn.className = 'filter-btn active'; -allSecondaryBtn.textContent = '全部'; -allSecondaryBtn.dataset.level = '2'; -allSecondaryBtn.dataset.filter = 'all'; -allSecondaryBtn.addEventListener('click', () => { - setFilter('all', 'all'); -}); -container.appendChild(allSecondaryBtn); - -// 添加所有二级标签按钮(从分类数据生成) -Object.entries(categories).forEach(([mainCatId, mainCatData]) => { - Object.entries(mainCatData.sub).forEach(([subCatId, subCatData]) => { - const btn = document.createElement('button'); - btn.className = `filter-btn ${mainCatId}`; - btn.textContent = subCatData.name; - btn.dataset.level = '2'; - btn.dataset.filter = subCatId; - btn.style.backgroundColor = subCatData.color; - btn.style.color = getContrastColor(subCatData.color); - btn.addEventListener('click', () => { - setFilter('all', subCatId); - }); - container.appendChild(btn); - }); -}); - -secondaryFiltersWrapper.innerHTML = ''; -secondaryFiltersWrapper.appendChild(container); -currentExpandedPrimary = null; - -// 为新创建的二级筛选容器添加滚轮事件 -addWheelEventToContainer(container); -} - -// 展开二级标签 -function expandSecondary(primaryFilterId) { -collapseAllSecondary(); - -const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilterId}"]`); -if (primaryBtn && categories[primaryFilterId]) { - const container = document.createElement('div'); - container.className = 'secondary-filters-container show'; - container.id = `secondary-${primaryFilterId}`; - - const allSecondaryBtn = document.createElement('button'); - allSecondaryBtn.className = 'filter-btn active'; - allSecondaryBtn.textContent = '全部'; - allSecondaryBtn.dataset.level = '2'; - allSecondaryBtn.dataset.filter = 'all'; - allSecondaryBtn.addEventListener('click', () => { - setFilter(primaryFilterId, 'all'); - }); - container.appendChild(allSecondaryBtn); - - Object.entries(categories[primaryFilterId].sub).forEach(([subCatId, subCatData]) => { - const btn = document.createElement('button'); - btn.className = `filter-btn ${primaryFilterId}`; - btn.textContent = subCatData.name; - btn.dataset.level = '2'; - btn.dataset.filter = subCatId; - btn.style.backgroundColor = subCatData.color; - btn.style.color = getContrastColor(subCatData.color); - btn.addEventListener('click', () => { - setFilter(primaryFilterId, subCatId); - }); - container.appendChild(btn); - }); - - secondaryFiltersWrapper.innerHTML = ''; - secondaryFiltersWrapper.appendChild(container); - currentExpandedPrimary = primaryFilterId; - - // 为新创建的二级筛选容器添加滚轮事件 - addWheelEventToContainer(container); -} -} - -// 收起所有二级标签 -function collapseAllSecondary() { -secondaryFiltersWrapper.innerHTML = ''; -currentExpandedPrimary = null; -} - -// 为滚动容器添加滚轮事件 -function addWheelEventToContainer(container) { -container.addEventListener('wheel', (e) => { - if (e.deltaY !== 0) { - e.preventDefault(); - container.scrollLeft += e.deltaY; - } -}); -} - -// 设置当前筛选条件 -function setFilter(primaryFilter, secondaryFilter) { -currentPrimaryFilter = primaryFilter; -currentSecondaryFilter = secondaryFilter; - -// 更新按钮激活状态 -document.querySelectorAll('.filter-btn').forEach(btn => { - btn.classList.remove('active'); -}); - -// 激活一级标签按钮 -if (primaryFilter === 'all') { - primaryFilters.querySelector('[data-filter="all"]').classList.add('active'); -} else { - const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilter}"]`); - if (primaryBtn) primaryBtn.classList.add('active'); -} - -// 激活二级标签按钮 -if (secondaryFilter === 'all') { - const container = document.querySelector('.secondary-filters-container'); - if (container) { - const allBtn = container.querySelector('[data-filter="all"]'); - if (allBtn) allBtn.classList.add('active'); - } -} else { - const secondaryBtn = document.querySelector(`[data-filter="${secondaryFilter}"]`); - if (secondaryBtn) secondaryBtn.classList.add('active'); -} - -// 重新渲染应用列表 -renderApps(); -} - -// 渲染应用列表 -function renderApps() { - appListContainer.innerHTML = ''; - - // 按一级标签分组 - Object.entries(categories).forEach(([mainCatId, mainCatData]) => { - const groupApps = apps.filter(app => app.category.main === mainCatId); - - // 应用筛选条件 - let filteredGroupApps = groupApps; - - // 一级标签筛选 - if (currentPrimaryFilter !== 'all' && currentPrimaryFilter !== mainCatId) { - return; // 跳过不匹配的一级标签组 - } - - // 二级标签筛选 - if (currentSecondaryFilter !== 'all') { - filteredGroupApps = filteredGroupApps.filter(app => - app.category.sub === currentSecondaryFilter - ); - - if (filteredGroupApps.length === 0) { - return; - } - } - - // 对应用按权重排序(权重越大越靠前) - filteredGroupApps.sort((a, b) => { - const weightA = a.weight || 0; - const weightB = b.weight || 0; - return weightB - weightA; // 降序排列 - }); - - // 获取该分类下第一个应用的图标(如果有应用的话) - const firstAppIcon = filteredGroupApps.length > 0 ? - filteredGroupApps[0].icon : - 'fa-cube'; // 默认图标 - - // 创建分类组 - const groupDiv = document.createElement('div'); - groupDiv.className = 'app-group'; - - // 检查该主分类是否为私有分类且用户已登录 - const isPrivateCategory = mainCatData.private && isLoggedIn; - - // 添加分类标题(使用第一个应用的图标) - const titleDiv = document.createElement('div'); - titleDiv.className = 'category-title'; - titleDiv.innerHTML = ` ${mainCatData.name}`; - - // 如果是私有分类且已登录,添加私有标记 - if (isPrivateCategory) { - const privateBadge = document.createElement('span'); - privateBadge.className = 'badge bg-warning text-dark ms-2'; - privateBadge.textContent = '私有'; - titleDiv.appendChild(privateBadge); - } - - groupDiv.appendChild(titleDiv); - - // 创建应用列表 - const appList = document.createElement('div'); - appList.className = 'app-list'; - - // 添加应用卡片 - filteredGroupApps.forEach(app => { - const subCatName = categories[app.category.main]?.sub[app.category.sub]?.name || app.category.sub; - const subCatColor = categories[app.category.main]?.sub[app.category.sub]?.color || mainCatData.color; - - const appItem = document.createElement('a'); - appItem.className = `app-item ${settings.card_style === 'compact' ? 'compact' : ''}`; - appItem.href = app.url; - appItem.target = '_blank'; - - // 如果是私有应用且已登录,添加私有标记 - if (app.private && isLoggedIn) { - const privateBadge = document.createElement('div'); - privateBadge.className = 'private-badge'; - appItem.appendChild(privateBadge); - } - - // 添加聊天气泡描述提示框 - const descriptionDiv = document.createElement('div'); - descriptionDiv.className = 'app-description'; - descriptionDiv.textContent = app.description || '暂无描述'; - - // 判断是Font Awesome图标还是自定义图片 - const iconHtml = app.icon.startsWith('/') || app.icon.startsWith('http') ? - `图标` : - ``; - - appItem.innerHTML += ` -
- ${iconHtml} -
-
-
${app.title}
-
${new URL(app.url).hostname}
-
- ${subCatName} -
+ // 渲染界面 + renderFilters(); + renderApps(); + loadingSpinner.style.display = 'none'; + primaryFilters.querySelector('[data-filter="all"]').textContent = '全部'; + } catch (error) { + console.error('加载数据失败:', error); + appListContainer.innerHTML = ` +
+ 加载数据失败,请刷新页面重试
`; + } + } - appItem.prepend(descriptionDiv); - appList.appendChild(appItem); + // 更新登录按钮状态 + function updateLoginButton() { + if (isLoggedIn) { + loginBtn.innerHTML = ''; + loginBtn.href = '/logout'; + loginBtn.title = '退出登录'; + adminBtn.href = '/manage'; // 已登录时直接跳转到管理页面 + document.getElementById('addBtn').style.display = 'flex'; // 显示添加按钮 + } else { + loginBtn.innerHTML = ''; + loginBtn.href = '/login'; + loginBtn.title = '登录'; + adminBtn.href = '/login?next=/manage'; // 未登录时跳转到登录页面 + document.getElementById('addBtn').style.display = 'none'; // 隐藏添加按钮 + } + } + + // 根据分类ID获取对应的颜色类 + function getColorForCategory(categoryId) { + return categoryId; + } + + // 生成筛选按钮 + function renderFilters() { + // 生成一级标签 + primaryFilters.innerHTML = ''; + const allPrimaryBtn = document.createElement('button'); + allPrimaryBtn.className = 'filter-btn active'; + allPrimaryBtn.textContent = '全部'; + allPrimaryBtn.dataset.level = '1'; + allPrimaryBtn.dataset.filter = 'all'; + allPrimaryBtn.addEventListener('click', () => { + setFilter('all', 'all'); + showAllSecondaryFilters(); + setAllPrimaryCaretDown(true); + activeSecondaryFilter = null; // 重置二级筛选状态 + }); + primaryFilters.appendChild(allPrimaryBtn); + + // 添加一级标签按钮(从分类数据生成) + Object.entries(categories).forEach(([catId, catData]) => { + const btn = document.createElement('button'); + btn.className = `filter-btn ${catId}`; + btn.innerHTML = ` ${catData.name}`; + btn.dataset.level = '1'; + btn.dataset.filter = catId; + // 确保有颜色时才设置背景色 + if (catData.color) { + btn.style.backgroundColor = catData.color; + btn.style.color = getContrastColor(catData.color); + } + btn.addEventListener('click', (e) => { + if (currentExpandedPrimary === catId) { + // 如果点击的是已展开的一级分类,则显示所有二级分类 + showAllSecondaryFilters(); + setFilter('all', 'all'); + setAllPrimaryCaretDown(true); + activeSecondaryFilter = null; // 重置二级筛选状态 + } else { + // 否则展开该一级分类下的二级分类 + expandSecondary(catId); + setFilter(catId, 'all'); + setAllPrimaryCaretDown(false); + setPrimaryCaretDown(catId, true); + activeSecondaryFilter = null; // 重置二级筛选状态 + } + }); + primaryFilters.appendChild(btn); }); - groupDiv.appendChild(appList); - appListContainer.appendChild(groupDiv); - }); - - // 如果没有显示任何分类组,显示无结果提示 - if (appListContainer.children.length === 0) { - const noResults = document.createElement('div'); - noResults.className = 'no-results'; - noResults.textContent = '没有找到匹配的应用'; - appListContainer.appendChild(noResults); + // 初始加载时显示所有二级标签 + showAllSecondaryFilters(); } -} -// 辅助函数:计算对比色 -function getContrastColor(hexColor) { -if (!hexColor) return '#ffffff'; + // 设置所有一级标签箭头的展开/收起状态 + function setAllPrimaryCaretDown(shouldDown) { + const primaryBtns = primaryFilters.querySelectorAll('[data-level="1"]'); + primaryBtns.forEach(btn => { + if (btn.dataset.filter !== 'all') { + const caret = btn.querySelector('.caret'); + if (caret) { + if (shouldDown) { + caret.classList.add('down'); + } else { + caret.classList.remove('down'); + } + } + } + }); + } -// 转换hex颜色为RGB -const r = parseInt(hexColor.substr(1, 2), 16); -const g = parseInt(hexColor.substr(3, 2), 16); -const b = parseInt(hexColor.substr(5, 2), 16); + // 设置特定一级标签箭头的展开/收起状态 + function setPrimaryCaretDown(primaryId, shouldDown) { + const btn = primaryFilters.querySelector(`[data-filter="${primaryId}"]`); + if (btn) { + const caret = btn.querySelector('.caret'); + if (caret) { + if (shouldDown) { + caret.classList.add('down'); + } else { + caret.classList.remove('down'); + } + } + } + } -// 计算亮度 -const brightness = (r * 299 + g * 587 + b * 114) / 1000; + // 显示所有二级标签 + function showAllSecondaryFilters() { + collapseAllSecondary(); -// 根据亮度返回黑色或白色 -return brightness > 128 ? '#000000' : '#ffffff'; -} + const container = document.createElement('div'); + container.className = 'secondary-filters-container show'; + container.id = 'secondary-all'; -// 全局设置 -let settings = { -theme: 'auto', -card_style: 'normal', -search_history: [] -}; + const allSecondaryBtn = document.createElement('button'); + allSecondaryBtn.className = 'filter-btn active'; + allSecondaryBtn.textContent = '全部'; + allSecondaryBtn.dataset.level = '2'; + allSecondaryBtn.dataset.filter = 'all'; + allSecondaryBtn.addEventListener('click', () => { + setFilter('all', 'all'); + activeSecondaryFilter = null; // 重置二级筛选状态 + }); + container.appendChild(allSecondaryBtn); -// 加载设置 -async function loadSettings() { -try { - // 检查是否登录 - isLoggedIn = await checkLoginStatus(); + // 添加所有二级标签按钮(从分类数据生成) + Object.entries(categories).forEach(([mainCatId, mainCatData]) => { + Object.entries(mainCatData.sub).forEach(([subCatId, subCatData]) => { + const btn = document.createElement('button'); + btn.className = `filter-btn ${mainCatId}`; + btn.textContent = subCatData.name; + btn.dataset.level = '2'; + btn.dataset.filter = subCatId; + btn.style.backgroundColor = subCatData.color; + btn.style.color = getContrastColor(subCatData.color); + btn.addEventListener('click', () => { + // 如果点击的是当前激活的二级筛选,则取消筛选 + if (activeSecondaryFilter === subCatId) { + setFilter('all', 'all'); + activeSecondaryFilter = null; + } else { + setFilter('all', subCatId); + activeSecondaryFilter = subCatId; + } + }); + container.appendChild(btn); + }); + }); - // 获取游客默认背景设置 - const guestResponse = await fetch('/api/guest_settings'); - const guestSettings = await guestResponse.json(); + secondaryFiltersWrapper.innerHTML = ''; + secondaryFiltersWrapper.appendChild(container); + currentExpandedPrimary = null; - if (isLoggedIn) { - // 如果已登录,从服务器获取系统设置 - const response = await fetch('/api/settings'); - settings = await response.json(); + // 为新创建的二级筛选容器添加滚轮事件 + addWheelEventToContainer(container); + } - // 设置背景图片 - const lightBg = settings.bg_image === 'none' ? 'none' : - (settings.bg_image || '/static/background_light.jpg'); - const darkBg = settings.dark_bg_image === 'none' ? 'none' : - (settings.dark_bg_image || '/static/background_dark.jpg'); + // 展开二级标签 + function expandSecondary(primaryFilterId) { + collapseAllSecondary(); - setBackgroundImages(lightBg, darkBg); - } else { - // 如果未登录,先检查本地存储 - const localSettings = localStorage.getItem('navSettings'); - if (localSettings) { - const localSettingsObj = JSON.parse(localSettings); - // 从本地存储加载主题和卡片样式 - settings.theme = localSettingsObj.theme || guestSettings.theme; - settings.card_style = localSettingsObj.card_style || guestSettings.card_style; - settings.search_history = localSettingsObj.search_history || []; + const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilterId}"]`); + if (primaryBtn && categories[primaryFilterId]) { + const container = document.createElement('div'); + container.className = 'secondary-filters-container show'; + container.id = `secondary-${primaryFilterId}`; + + const allSecondaryBtn = document.createElement('button'); + allSecondaryBtn.className = 'filter-btn active'; + allSecondaryBtn.textContent = '全部'; + allSecondaryBtn.dataset.level = '2'; + allSecondaryBtn.dataset.filter = 'all'; + allSecondaryBtn.addEventListener('click', () => { + setFilter(primaryFilterId, 'all'); + activeSecondaryFilter = null; // 重置二级筛选状态 + }); + container.appendChild(allSecondaryBtn); + + Object.entries(categories[primaryFilterId].sub).forEach(([subCatId, subCatData]) => { + const btn = document.createElement('button'); + btn.className = `filter-btn ${primaryFilterId}`; + btn.textContent = subCatData.name; + btn.dataset.level = '2'; + btn.dataset.filter = subCatId; + btn.style.backgroundColor = subCatData.color; + btn.style.color = getContrastColor(subCatData.color); + btn.addEventListener('click', () => { + // 如果点击的是当前激活的二级筛选,则取消筛选 + if (activeSecondaryFilter === subCatId) { + setFilter(primaryFilterId, 'all'); + activeSecondaryFilter = null; + } else { + setFilter(primaryFilterId, subCatId); + activeSecondaryFilter = subCatId; + } + }); + container.appendChild(btn); + }); + + secondaryFiltersWrapper.innerHTML = ''; + secondaryFiltersWrapper.appendChild(container); + currentExpandedPrimary = primaryFilterId; + + // 为新创建的二级筛选容器添加滚轮事件 + addWheelEventToContainer(container); + } + } + + // 收起所有二级标签 + function collapseAllSecondary() { + secondaryFiltersWrapper.innerHTML = ''; + currentExpandedPrimary = null; + } + + // 为滚动容器添加滚轮事件 + function addWheelEventToContainer(container) { + container.addEventListener('wheel', (e) => { + if (e.deltaY !== 0) { + e.preventDefault(); + container.scrollLeft += e.deltaY; + } + }); + } + + // 设置当前筛选条件 + function setFilter(primaryFilter, secondaryFilter) { + currentPrimaryFilter = primaryFilter; + currentSecondaryFilter = secondaryFilter; + + // 更新按钮激活状态 + document.querySelectorAll('.filter-btn').forEach(btn => { + btn.classList.remove('active'); + }); + + // 激活一级标签按钮 + if (primaryFilter === 'all') { + primaryFilters.querySelector('[data-filter="all"]').classList.add('active'); } else { - // 首次访问,使用接口返回的默认配置 - settings.theme = guestSettings.theme; - settings.card_style = guestSettings.card_style; + const primaryBtn = primaryFilters.querySelector(`[data-filter="${primaryFilter}"]`); + if (primaryBtn) primaryBtn.classList.add('active'); } - // 设置背景图片(始终使用服务器配置) - const lightBg = guestSettings.bg_image === 'none' ? 'none' : - (guestSettings.bg_image || '/static/background_light.jpg'); - const darkBg = guestSettings.dark_bg_image === 'none' ? 'none' : - (guestSettings.dark_bg_image || '/static/background_dark.jpg'); + // 激活二级标签按钮 + if (secondaryFilter === 'all') { + const container = document.querySelector('.secondary-filters-container'); + if (container) { + const allBtn = container.querySelector('[data-filter="all"]'); + if (allBtn) allBtn.classList.add('active'); + } + } else { + const secondaryBtn = document.querySelector(`[data-filter="${secondaryFilter}"]`); + if (secondaryBtn) secondaryBtn.classList.add('active'); + } - setBackgroundImages(lightBg, darkBg); + // 重新渲染应用列表 + renderApps(); } - applySettings(); -} catch (error) { - console.error('加载设置失败:', error); -} -} + // 渲染应用列表 + function renderApps() { + appListContainer.innerHTML = ''; -// 设置背景图片 -function setBackgroundImages(lightImage, darkImage) { -// 移除现有的视频背景 -document.querySelectorAll('.video-bg-container').forEach(el => el.remove()); + // 按一级标签分组 + Object.entries(categories).forEach(([mainCatId, mainCatData]) => { + const groupApps = apps.filter(app => app.category.main === mainCatId); -// 处理明亮模式背景 -if (lightImage && lightImage.endsWith('.mp4')) { - const lightVideoContainer = document.createElement('div'); - lightVideoContainer.className = 'video-bg-container bg-light'; - lightVideoContainer.style.display = document.body.classList.contains('dark-theme') ? 'none' : 'block'; + // 应用筛选条件 + let filteredGroupApps = groupApps; - const lightVideo = document.createElement('video'); - lightVideo.className = 'video-bg'; - lightVideo.src = lightImage; - lightVideo.autoplay = true; - lightVideo.loop = true; - lightVideo.muted = true; - lightVideo.playsInline = true; + // 一级标签筛选 + if (currentPrimaryFilter !== 'all' && currentPrimaryFilter !== mainCatId) { + return; // 跳过不匹配的一级标签组 + } - lightVideoContainer.appendChild(lightVideo); - document.body.appendChild(lightVideoContainer); -} else { - document.documentElement.style.setProperty('--bg-image', lightImage === 'none' ? 'none' : `url('${lightImage}')`); -} + // 二级标签筛选 + if (currentSecondaryFilter !== 'all') { + filteredGroupApps = filteredGroupApps.filter(app => + app.category.sub === currentSecondaryFilter + ); -// 处理暗黑模式背景 -if (darkImage && darkImage.endsWith('.mp4')) { - const darkVideoContainer = document.createElement('div'); - darkVideoContainer.className = 'video-bg-container bg-dark'; - darkVideoContainer.style.display = document.body.classList.contains('dark-theme') ? 'block' : 'none'; + if (filteredGroupApps.length === 0) { + return; + } + } - const darkVideo = document.createElement('video'); - darkVideo.className = 'video-bg'; - darkVideo.src = darkImage; - darkVideo.autoplay = true; - darkVideo.loop = true; - darkVideo.muted = true; - darkVideo.playsInline = true; + // 对应用按权重排序(权重越大越靠前) + filteredGroupApps.sort((a, b) => { + const weightA = a.weight || 0; + const weightB = b.weight || 0; + return weightB - weightA; // 降序排列 + }); - darkVideoContainer.appendChild(darkVideo); - document.body.appendChild(darkVideoContainer); -} else { - document.documentElement.style.setProperty('--dark-bg-image', darkImage === 'none' ? 'none' : `url('${darkImage}')`); -} + // 获取该分类下第一个应用的图标(如果有应用的话) + const firstAppIcon = filteredGroupApps.length > 0 ? + filteredGroupApps[0].icon : + 'fa-cube'; // 默认图标 -// 添加背景加载完成的类 -setTimeout(() => { - document.body.classList.add('bg-loaded'); -}, 100); -} + // 创建分类组 + const groupDiv = document.createElement('div'); + groupDiv.className = 'app-group'; -// 检查登录状态 -async function checkLoginStatus() { -try { - const response = await fetch('/api/check_login'); - const data = await response.json(); - return data.logged_in; -} catch (error) { - console.error('检查登录状态失败:', error); - return false; -} -} + // 检查该主分类是否为私有分类且用户已登录 + const isPrivateCategory = mainCatData.private && isLoggedIn; -// 应用设置 -function applySettings() { -// 应用主题 -document.body.classList.remove('dark-theme', 'light-theme'); -if (settings.theme === 'dark' || - (settings.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) { - document.body.classList.add('dark-theme'); + // 添加分类标题(使用第一个应用的图标) + const titleDiv = document.createElement('div'); + titleDiv.className = 'category-title'; + titleDiv.innerHTML = ` ${mainCatData.name}`; - // 显示/隐藏视频背景 - document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'none'); - document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'block'); -} else if (settings.theme === 'light') { - document.body.classList.add('light-theme'); + // 如果是私有分类且已登录,添加私有标记 + if (isPrivateCategory) { + const privateBadge = document.createElement('span'); + privateBadge.className = 'badge bg-warning text-dark ms-2'; + privateBadge.textContent = '私有'; + titleDiv.appendChild(privateBadge); + } - // 显示/隐藏视频背景 - document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'block'); - document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'none'); -} + groupDiv.appendChild(titleDiv); -// 应用卡片样式 -document.querySelectorAll('.app-item').forEach(item => { - item.classList.toggle('compact', settings.card_style === 'compact'); -}); + // 创建应用列表 + const appList = document.createElement('div'); + appList.className = 'app-list'; -// 更新主题按钮状态 -updateThemeButtonIcon(); -} + // 添加应用卡片 + filteredGroupApps.forEach(app => { + const subCatName = categories[app.category.main]?.sub[app.category.sub]?.name || app.category.sub; + const subCatColor = categories[app.category.main]?.sub[app.category.sub]?.color || mainCatData.color; -// 更新主题按钮图标 -function updateThemeButtonIcon() { -const themeToggle = document.getElementById('themeToggle'); -if (document.body.classList.contains('dark-theme')) { - themeToggle.textContent = '☀️'; - themeToggle.title = '切换至明亮模式'; -} else { - themeToggle.textContent = '🌙'; - themeToggle.title = '切换至暗黑模式'; -} -} + const appItem = document.createElement('a'); + appItem.className = `app-item ${settings.card_style === 'compact' ? 'compact' : ''}`; + appItem.href = app.url; + appItem.target = '_blank'; -// 保存设置 -function saveSettings() { -if (isLoggedIn) { - fetch('/api/settings', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(settings) - }); -} else { - // 游客只保存主题和卡片样式到本地 - const settingsToSave = { - theme: settings.theme, - card_style: settings.card_style, - search_history: settings.search_history + // 如果是私有应用且已登录,添加私有标记 + if (app.private && isLoggedIn) { + const privateBadge = document.createElement('div'); + privateBadge.className = 'private-badge'; + appItem.appendChild(privateBadge); + } + + // 添加聊天气泡描述提示框 + const descriptionDiv = document.createElement('div'); + descriptionDiv.className = 'app-description'; + descriptionDiv.textContent = app.description || '暂无描述'; + + // 判断是Font Awesome图标还是自定义图片 + const iconHtml = app.icon.startsWith('/') || app.icon.startsWith('http') ? + `图标` : + ``; + + appItem.innerHTML += ` +
+ ${iconHtml} +
+
+
${app.title}
+
${new URL(app.url).hostname}
+
+ ${subCatName} +
+
+ `; + + appItem.prepend(descriptionDiv); + appList.appendChild(appItem); + }); + + groupDiv.appendChild(appList); + appListContainer.appendChild(groupDiv); + }); + + // 如果没有显示任何分类组,显示无结果提示 + if (appListContainer.children.length === 0) { + const noResults = document.createElement('div'); + noResults.className = 'no-results'; + noResults.textContent = '没有找到匹配的应用'; + appListContainer.appendChild(noResults); + } + } + + // 辅助函数:计算对比色 + function getContrastColor(hexColor) { + if (!hexColor) return '#ffffff'; + + // 转换hex颜色为RGB + const r = parseInt(hexColor.substr(1, 2), 16); + const g = parseInt(hexColor.substr(3, 2), 16); + const b = parseInt(hexColor.substr(5, 2), 16); + + // 计算亮度 + const brightness = (r * 299 + g * 587 + b * 114) / 1000; + + // 根据亮度返回黑色或白色 + return brightness > 128 ? '#000000' : '#ffffff'; + } + + // 全局设置 + let settings = { + theme: 'auto', + card_style: 'normal', + search_history: [] }; - localStorage.setItem('navSettings', JSON.stringify(settingsToSave)); -} -} -// 搜索功能 -function setupSearch() { -const searchInput = document.getElementById('searchInput'); -const searchResults = document.getElementById('searchResults'); -let searchTimeout; + // 加载设置 + async function loadSettings() { + try { + // 检查是否登录 + isLoggedIn = await checkLoginStatus(); -searchInput.addEventListener('input', () => { - clearTimeout(searchTimeout); - searchTimeout = setTimeout(() => { - const keyword = searchInput.value.trim(); - if (keyword.length > 0) { - fetch('/api/search', { + // 获取游客默认背景设置 + const guestResponse = await fetch('/api/guest_settings'); + const guestSettings = await guestResponse.json(); + + if (isLoggedIn) { + // 如果已登录,从服务器获取系统设置 + const response = await fetch('/api/settings'); + settings = await response.json(); + + // 设置背景图片 + const lightBg = settings.bg_image === 'none' ? 'none' : + (settings.bg_image || '/static/background_light.jpg'); + const darkBg = settings.dark_bg_image === 'none' ? 'none' : + (settings.dark_bg_image || '/static/background_dark.jpg'); + + setBackgroundImages(lightBg, darkBg); + } else { + // 如果未登录,先检查本地存储 + const localSettings = localStorage.getItem('navSettings'); + if (localSettings) { + const localSettingsObj = JSON.parse(localSettings); + // 从本地存储加载主题和卡片样式 + settings.theme = localSettingsObj.theme || guestSettings.theme; + settings.card_style = localSettingsObj.card_style || guestSettings.card_style; + settings.search_history = localSettingsObj.search_history || []; + } else { + // 首次访问,使用接口返回的默认配置 + settings.theme = guestSettings.theme; + settings.card_style = guestSettings.card_style; + } + + // 设置背景图片(始终使用服务器配置) + const lightBg = guestSettings.bg_image === 'none' ? 'none' : + (guestSettings.bg_image || '/static/background_light.jpg'); + const darkBg = guestSettings.dark_bg_image === 'none' ? 'none' : + (guestSettings.dark_bg_image || '/static/background_dark.jpg'); + + setBackgroundImages(lightBg, darkBg); + } + + applySettings(); + } catch (error) { + console.error('加载设置失败:', error); + } + } + + // 设置背景图片 + function setBackgroundImages(lightImage, darkImage) { + // 移除现有的视频背景 + document.querySelectorAll('.video-bg-container').forEach(el => el.remove()); + + // 处理明亮模式背景 + if (lightImage && lightImage.endsWith('.mp4')) { + const lightVideoContainer = document.createElement('div'); + lightVideoContainer.className = 'video-bg-container bg-light'; + lightVideoContainer.style.display = document.body.classList.contains('dark-theme') ? 'none' : 'block'; + + const lightVideo = document.createElement('video'); + lightVideo.className = 'video-bg'; + lightVideo.src = lightImage; + lightVideo.autoplay = true; + lightVideo.loop = true; + lightVideo.muted = true; + lightVideo.playsInline = true; + + lightVideoContainer.appendChild(lightVideo); + document.body.appendChild(lightVideoContainer); + } else { + document.documentElement.style.setProperty('--bg-image', lightImage === 'none' ? 'none' : `url('${lightImage}')`); + } + + // 处理暗黑模式背景 + if (darkImage && darkImage.endsWith('.mp4')) { + const darkVideoContainer = document.createElement('div'); + darkVideoContainer.className = 'video-bg-container bg-dark'; + darkVideoContainer.style.display = document.body.classList.contains('dark-theme') ? 'block' : 'none'; + + const darkVideo = document.createElement('video'); + darkVideo.className = 'video-bg'; + darkVideo.src = darkImage; + darkVideo.autoplay = true; + darkVideo.loop = true; + darkVideo.muted = true; + darkVideo.playsInline = true; + + darkVideoContainer.appendChild(darkVideo); + document.body.appendChild(darkVideoContainer); + } else { + document.documentElement.style.setProperty('--dark-bg-image', darkImage === 'none' ? 'none' : `url('${darkImage}')`); + } + + // 添加背景加载完成的类 + setTimeout(() => { + document.body.classList.add('bg-loaded'); + }, 100); + } + + // 检查登录状态 + async function checkLoginStatus() { + try { + const response = await fetch('/api/check_login'); + const data = await response.json(); + return data.logged_in; + } catch (error) { + console.error('检查登录状态失败:', error); + return false; + } + } + + // 应用设置 + function applySettings() { + // 应用主题 + document.body.classList.remove('dark-theme', 'light-theme'); + if (settings.theme === 'dark' || + (settings.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) { + document.body.classList.add('dark-theme'); + + // 显示/隐藏视频背景 + document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'none'); + document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'block'); + } else if (settings.theme === 'light') { + document.body.classList.add('light-theme'); + + // 显示/隐藏视频背景 + document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'block'); + document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'none'); + } + + // 应用卡片样式 + document.querySelectorAll('.app-item').forEach(item => { + item.classList.toggle('compact', settings.card_style === 'compact'); + }); + + // 更新主题按钮状态 + updateThemeButtonIcon(); + } + + // 更新主题按钮图标 + function updateThemeButtonIcon() { + const themeToggle = document.getElementById('themeToggle'); + if (document.body.classList.contains('dark-theme')) { + themeToggle.textContent = '☀️'; + themeToggle.title = '切换至明亮模式'; + } else { + themeToggle.textContent = '🌙'; + themeToggle.title = '切换至暗黑模式'; + } + } + + // 保存设置 + function saveSettings() { + if (isLoggedIn) { + fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ keyword }) - }) - .then(response => response.json()) - .then(results => { - searchResults.innerHTML = ''; - if (results.length > 0) { - results.forEach(app => { - const item = document.createElement('div'); - item.className = 'search-result-item'; - item.innerHTML = ` -
${app.title}
-
${app.url}
- `; - item.addEventListener('click', () => { - window.open(app.url, '_blank'); - }); - searchResults.appendChild(item); - }); - searchResults.style.display = 'block'; - } else { - searchResults.innerHTML = '
无搜索结果
'; - searchResults.style.display = 'block'; - } + body: JSON.stringify(settings) }); } else { - searchResults.style.display = 'none'; - } - }, 300); -}); - -document.addEventListener('click', (e) => { - if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { - searchResults.style.display = 'none'; - } -}); -} - -// 主题切换功能 -function setupThemeSwitcher() { -const themeToggle = document.getElementById('themeToggle'); - -themeToggle.addEventListener('click', () => { - // 只在明亮和暗黑之间切换 - if (document.body.classList.contains('dark-theme')) { - settings.theme = 'light'; - document.body.classList.remove('dark-theme'); - - // 更新视频背景显示状态 - document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'block'); - document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'none'); - } else { - settings.theme = 'dark'; - document.body.classList.add('dark-theme'); - - // 更新视频背景显示状态 - document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'none'); - document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'block'); - } - - updateThemeButtonIcon(); - saveSettings(); -}); -} - -// 简洁模式切换 -function setupCompactToggle() { -const compactToggle = document.getElementById('compactToggle'); -compactToggle.addEventListener('click', () => { - settings.card_style = settings.card_style === 'compact' ? 'normal' : 'compact'; - applySettings(); - saveSettings(); -}); -} - -// 后台管理按钮点击事件 -function setupAdminButton() { -adminBtn.addEventListener('click', async (e) => { - if (!isLoggedIn) { - e.preventDefault(); - const loginCheck = await fetch('/api/check_login'); - const loginData = await loginCheck.json(); - - if (!loginData.logged_in) { - window.location.href = '/login?next=/manage'; - } else { - window.location.href = '/manage'; + // 游客只保存主题和卡片样式到本地 + const settingsToSave = { + theme: settings.theme, + card_style: settings.card_style, + search_history: settings.search_history + }; + localStorage.setItem('navSettings', JSON.stringify(settingsToSave)); } } -}); -} -// 页面加载完成后初始化 -document.addEventListener('DOMContentLoaded', () => { -loadData(); -loadSettings(); -setupSearch(); -setupThemeSwitcher(); -setupCompactToggle(); -setupAdminButton(); + // 搜索功能 + function setupSearch() { + const searchInput = document.getElementById('searchInput'); + const searchResults = document.getElementById('searchResults'); + let searchTimeout; -// 为静态的一级筛选容器添加滚轮事件 -primaryFilters.addEventListener('wheel', (e) => { - if (e.deltaY !== 0) { - e.preventDefault(); - primaryFilters.scrollLeft += e.deltaY; + searchInput.addEventListener('input', () => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + const keyword = searchInput.value.trim(); + if (keyword.length > 0) { + fetch('/api/search', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ keyword }) + }) + .then(response => response.json()) + .then(results => { + searchResults.innerHTML = ''; + if (results.length > 0) { + results.forEach(app => { + const item = document.createElement('div'); + item.className = 'search-result-item'; + item.innerHTML = ` +
${app.title}
+
${app.url}
+ `; + item.addEventListener('click', () => { + window.open(app.url, '_blank'); + }); + searchResults.appendChild(item); + }); + searchResults.style.display = 'block'; + } else { + searchResults.innerHTML = '
无搜索结果
'; + searchResults.style.display = 'block'; + } + }); + } else { + searchResults.style.display = 'none'; + } + }, 300); + }); + + document.addEventListener('click', (e) => { + if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { + searchResults.style.display = 'none'; + } + }); } -}); -}); -function checkScrollable() { -document.querySelectorAll('.filter-row, .secondary-filters-container').forEach(container => { - // 检查内容宽度是否大于容器宽度 - const isScrollable = container.scrollWidth > container.clientWidth; + // 主题切换功能 + function setupThemeSwitcher() { + const themeToggle = document.getElementById('themeToggle'); - if(isScrollable) { - container.classList.add('scrollable'); - } else { - container.classList.remove('scrollable'); + themeToggle.addEventListener('click', () => { + // 只在明亮和暗黑之间切换 + if (document.body.classList.contains('dark-theme')) { + settings.theme = 'light'; + document.body.classList.remove('dark-theme'); + + // 更新视频背景显示状态 + document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'block'); + document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'none'); + } else { + settings.theme = 'dark'; + document.body.classList.add('dark-theme'); + + // 更新视频背景显示状态 + document.querySelectorAll('.video-bg-container.bg-light').forEach(el => el.style.display = 'none'); + document.querySelectorAll('.video-bg-container.bg-dark').forEach(el => el.style.display = 'block'); + } + + updateThemeButtonIcon(); + saveSettings(); + }); } -}); -} -// 初始化时检查 -window.addEventListener('load', checkScrollable); -// 窗口大小改变时检查 -window.addEventListener('resize', checkScrollable); -// 内容变化时检查(如筛选器更新后) -new MutationObserver(checkScrollable).observe(document.body, { -childList: true, -subtree: true -}); + // 简洁模式切换 + function setupCompactToggle() { + const compactToggle = document.getElementById('compactToggle'); + compactToggle.addEventListener('click', () => { + settings.card_style = settings.card_style === 'compact' ? 'normal' : 'compact'; + applySettings(); + saveSettings(); + }); + } + + // 后台管理按钮点击事件 + function setupAdminButton() { + adminBtn.addEventListener('click', async (e) => { + if (!isLoggedIn) { + e.preventDefault(); + const loginCheck = await fetch('/api/check_login'); + const loginData = await loginCheck.json(); + + if (!loginData.logged_in) { + window.location.href = '/login?next=/manage'; + } else { + window.location.href = '/manage'; + } + } + }); + } + + // 返回顶部功能 + function setupBackToTop() { + // 监听滚动事件 + window.addEventListener('scroll', () => { + if (window.pageYOffset > 300) { + backToTopBtn.style.display = 'flex'; + } else { + backToTopBtn.style.display = 'none'; + } + }); + + // 点击返回顶部按钮 + backToTopBtn.addEventListener('click', () => { + // 平滑滚动到顶部 + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }); + } + + // 页面加载完成后初始化 + document.addEventListener('DOMContentLoaded', () => { + loadData(); + loadSettings(); + setupSearch(); + setupThemeSwitcher(); + setupCompactToggle(); + setupAdminButton(); + setupBackToTop(); // 初始化返回顶部功能 + + // 为静态的一级筛选容器添加滚轮事件 + primaryFilters.addEventListener('wheel', (e) => { + if (e.deltaY !== 0) { + e.preventDefault(); + primaryFilters.scrollLeft += e.deltaY; + } + }); + }); + + function checkScrollable() { + document.querySelectorAll('.filter-row, .secondary-filters-container').forEach(container => { + // 检查内容宽度是否大于容器宽度 + const isScrollable = container.scrollWidth > container.clientWidth; + + if(isScrollable) { + container.classList.add('scrollable'); + } else { + container.classList.remove('scrollable'); + } + }); + } + + // 初始化时检查 + window.addEventListener('load', checkScrollable); + // 窗口大小改变时检查 + window.addEventListener('resize', checkScrollable); + // 内容变化时检查(如筛选器更新后) + new MutationObserver(checkScrollable).observe(document.body, { + childList: true, + subtree: true + }); \ No newline at end of file diff --git a/templates/manage.html b/templates/manage.html index 961f210..be0ca0f 100644 --- a/templates/manage.html +++ b/templates/manage.html @@ -47,64 +47,125 @@
-
- - - - - - - - - - - - {% for app in apps %} - - - - - - - - {% else %} - - - - {% endfor %} - -
图标标题分类/权重URL操作
- {% if app.icon.startswith('/upload/icon/') %} - - {% else %} - - {% endif %} - - {{ app.title }} - {% if app.get('private', False) %} - 私有 - {% endif %} - -
- - {{ categories[app.category.main].name }} - {{ categories[app.category.main].weight }} - - {% if app.category.sub %} - - {{ categories[app.category.main].sub[app.category.sub].name }} - {{ categories[app.category.main].sub[app.category.sub].weight }} - + +
+
+ + + + + + + + + + + + {% for app in apps %} + + + + + + + + {% else %} + + + + {% endfor %} + +
图标标题分类/权重URL操作
+ {% if app.icon.startswith('/upload/icon/') %} + + {% else %} + {% endif %} + + {{ app.title }} + {% if app.get('private', False) %} + 私有 + {% endif %} + +
+ + {{ categories[app.category.main].name }} + {{ categories[app.category.main].weight }} + + {% if app.category.sub %} + + {{ categories[app.category.main].sub[app.category.sub].name }} + {{ categories[app.category.main].sub[app.category.sub].weight }} + + {% endif %} +
+
{{ app.url[:30] }}... + + 编辑 + + + 删除 + +
没有找到匹配的应用
+
+
+ + +
+
+ {% for app in apps %} +
+
+
+
+
+ {% if app.icon.startswith('/upload/icon/') %} + + {% else %} + + {% endif %} +
+
+
+ {{ app.title }} + {% if app.get('private', False) %} + 私有 + {% endif %} +
+
+ + {{ categories[app.category.main].name }} + {{ categories[app.category.main].weight }} + + {% if app.category.sub %} + + {{ categories[app.category.main].sub[app.category.sub].name }} + {{ categories[app.category.main].sub[app.category.sub].weight }} + + {% endif %} +
+

+ {{ app.url }} +

+ +
-
{{ app.url[:30] }}... - - 编辑 - - - 删除 - -
没有找到匹配的应用
+
+
+
+ {% else %} +
+
没有找到匹配的应用
+
+ {% endfor %} +