diff --git a/README.md b/README.md index 5e6cd07..d985e5f 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,55 @@ -# 项目待办事项 +# 项目待办事项管理系统 ## 已完成功能 ✅ +### 界面优化 - [x] 首页标题支持自定义 - [x] 背景图片设置支持从已上传图片中选择,选择时提供预览 - [x] 增加附件管理页面,管理上传的图片(包括应用图标和背景图片) - [x] 首页悬浮气泡内的文字改为居左 - [x] 背景支持视频 +- [x] 应用的图标支持上传图片自定义 +- [x] 自定义页脚 +- [x] 一级分类和二级分类固定宽度,超出宽度可左右滑动查看 +- [x] 气泡的小箭头靠左对齐 + +### 功能增强 - [x] 修复密码明文传输问题 -- [x] 不登录时一级分类和二级分类按钮没有颜色 +- [x] 应用管理页支持分页和按一级分类筛选 +- [x] 应用分页和附件分页功能 - [x] 部分页面游客跳转登录功能 +### BUG修复 +- [x] 不登录时一级分类和二级分类按钮没有颜色 +- [x] 应用编辑页面没有回显带入图片 + ## 待开发功能 ⏳ ### 界面优化 -1. 每行卡片数量支持自定义(4个、5个、6个、8个) -2. 应用的图标支持上传图片自定义 - 已完成 -3. 自定义页脚 - 已完成 -4. 一级分类和二级分类固定宽度,超出宽度可左右滑动查看 - 已完成 -5. 气泡的小箭头靠左对齐 - 已完成 -6. logo图标设置区分明亮和暗黑模式 -7. 私有应用在首页添加标识 -8. 首页卡片右键菜单 -9. 应用支持配置多个URL,左键打开默认URL,右键可选择URL进行复制地址或者打开或者编辑应用 -10. 新增应用界面便捷增加分类 -11. 新增应用界面便捷增加图标图片 +1. logo图标设置区分明亮和暗黑模式 +2. 私有应用在首页添加标识 +3. 首页卡片右键菜单 +4. 应用支持配置多个URL,左键打开默认URL,右键可选择URL进行复制地址或者打开或者编辑应用 +5. 新增应用界面便捷增加分类 +6. 新增应用界面便捷增加图标图片 +7. 每行卡片数量支持自定义(4个、5个、6个、8个) ### 功能增强 -1. 应用管理页支持分页和按一级分类筛选 - 已完成 -2. 应用分页和附件分页功能 - 已完成 -3. 网站图标自动获取功能 -4. 书签收藏工具 +1. 网站图标自动获取功能 +2. 书签收藏工具 ### BUG修复 -1. 应用编辑页面没有回显带入图片 - 已解决 +1. 游客通过接口能查看私有应用; + +### 移动端适配 +1. 后台页面适配 ### 批量操作 1. 应用批量选择功能: - 批量删除 - 批量设置私有化/公有化 2. 附件批量选择功能: - - 批量删除 \ No newline at end of file + - 批量删除 + +## 项目进度 +- 已完成功能:17项 +- 待开发功能:10项 +- 完成率:63% \ No newline at end of file diff --git a/app.py b/app.py index c46ebb6..e646c70 100644 --- a/app.py +++ b/app.py @@ -250,7 +250,10 @@ def allowed_file(filename): def generate_random_filename(extension): - return f"{uuid.uuid4().hex}.{extension}" + # 生成12位随机字符串作为文件名 + chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + random_str = ''.join(random.choice(chars) for _ in range(12)) + return f"{random_str}.{extension}" def download_image(url, folder): @@ -657,6 +660,7 @@ def index(): categories=categories, current_page=page, total_pages=total_pages, + per_page=per_page, search_query=search_query, category_filter=category_filter) @@ -941,15 +945,39 @@ def edit_sub_category(main_id, sub_id): settings=settings, is_private=is_private) + @app.route('/api/apps') def api_apps(): apps = load_apps() + categories = load_categories() # 检查登录状态 is_logged_in = 'username' in session - # 如果未登录,过滤掉私有应用 + # 如果未登录,过滤掉私有应用和私有分类中的应用 if not is_logged_in: - apps = [app for app in apps if not app.get('private', False)] + filtered_apps = [] + for app in apps: + # 跳过私有应用 + if app.get('private', False): + continue + + main_cat = app['category']['main'] + sub_cat = app['category']['sub'] + + # 检查主分类是否为私有 + if main_cat in categories and categories[main_cat].get('private', False): + continue + + # 检查子分类是否为私有 + if (main_cat in categories and + 'sub_private' in categories[main_cat] and + sub_cat in categories[main_cat]['sub_private'] and + categories[main_cat]['sub_private'][sub_cat]): + continue + + filtered_apps.append(app) + + return jsonify(filtered_apps) return jsonify(apps) @@ -981,7 +1009,8 @@ def api_categories(): filtered_categories[main_id] = { 'name': main_data['name'], 'color': main_data['color'], # 保留颜色信息 - 'sub': filtered_sub + 'sub': filtered_sub, + 'weight': main_data.get('weight', 0) # 保留权重信息 } return jsonify(filtered_categories) diff --git a/data/apps-data.json b/data/apps-data.json index aec307c..b33ccd3 100644 --- a/data/apps-data.json +++ b/data/apps-data.json @@ -2,7 +2,7 @@ { "title": "SQL生成工具23", "url": "https://fastai.liuyan.wang/chat/share?shareId=dbvns61a0glb2q6nwv9utd5v", - "icon": "/upload/icon/c77f6bbbb33540d98a6cc088d01d7ad6.png", + "icon": "fa-solid fa-database", "description": "一个强大的SQL查询生成工具,支持多种数据库,可快速生成复杂查询语句", "category": { "main": "dev", @@ -14,7 +14,7 @@ { "title": "Nginx配置生成", "url": "https://fastai.liuyan.wang/chat/share?shareId=3340xwddpp3xi48g1evv7r4m", - "icon": "/upload/icon/4e9c7320469f4e6887f63e1f59f32ca1.png", + "icon": "/upload/icon/65eb0f29c9e1.png", "description": "快速生成Nginx配置文件,支持负载均衡、反向代理等常见配置", "category": { "main": "dev", @@ -52,7 +52,8 @@ "category": { "main": "tool", "sub": "office" - } + }, + "private": false }, { "title": "NFS-PV-PVC", @@ -62,7 +63,8 @@ "category": { "main": "dev", "sub": "cloud" - } + }, + "private": false }, { "title": "刑法助手", @@ -72,7 +74,8 @@ "category": { "main": "law", "sub": "criminal" - } + }, + "private": false }, { "title": "税法助手", @@ -82,7 +85,8 @@ "category": { "main": "law", "sub": "tax" - } + }, + "private": false }, { "title": "汉语新解", @@ -92,11 +96,12 @@ "category": { "main": "edu", "sub": "language" - } + }, + "private": false }, { "title": "AI绘画提示词", - "url": "https://fastai.liuyan.wang/chat/share?shareId=example1", + "url": "https://prompthero.com/stable-diffusion-prompts", "icon": "fa-paint-brush", "category": { "main": "ai", @@ -107,18 +112,19 @@ }, { "title": "AI写作助手", - "url": "https://fastai.liuyan.wang/chat/share?shareId=example2", - "icon": "fa-keyboard", + "url": "https://www.sudowrite.com/", + "icon": "fa-solid fa-graduation-cap", "category": { "main": "ai", "sub": "writing" }, - "description": "辅助创作各类文本内容,包括文章、报告和创意写作" + "description": "辅助创作各类文本内容,包括文章、报告和创意写作", + "private": false }, { - "title": "2222", - "url": "https://baidu.com", - "icon": "/upload/icon/c6379d5521f24ca789e6712008f115f5.png", + "title": "云资源管理器", + "url": "https://explore.cloud.google.com/", + "icon": "fa-solid fa-file-word", "description": "云计算相关工具和资源集合", "private": false, "category": { @@ -127,9 +133,9 @@ } }, { - "title": "66666", - "url": "https://baidu.com", - "icon": "/upload/icon/65eb0f29c9e147e0924890e9ae12e94f.png", + "title": "AI图像生成器", + "url": "https://www.midjourney.com/", + "icon": "fa-solid fa-calculator", "description": "AI图像处理和生成工具", "private": false, "category": { @@ -138,10 +144,10 @@ } }, { - "title": "77777", - "url": "https://baidu.com", - "icon": "/upload/icon/7fb7491311f7477e9e3bd17150a929b7.png", - "description": "", + "title": "创意写作助手", + "url": "https://www.novelai.net/", + "icon": "/upload/icon/c6379d5521f2.png", + "description": "提供创意写作灵感和结构建议", "private": false, "category": { "main": "ai", @@ -149,10 +155,10 @@ } }, { - "title": "88888", - "url": "https://baidu.com", + "title": "AI艺术创作", + "url": "https://www.artbreeder.com/", "icon": "fa-solid fa-flushed", - "description": "", + "description": "使用AI技术进行艺术创作和图像风格转换", "private": false, "category": { "main": "ai", @@ -160,10 +166,10 @@ } }, { - "title": "99999", - "url": "https://baidu.com", + "title": "语言学习助手", + "url": "https://www.duolingo.com/", "icon": "fa-solid fa-calculator", - "description": "", + "description": "提供多语言学习资源和练习工具", "private": false, "category": { "main": "edu", @@ -171,32 +177,10 @@ } }, { - "title": "12121", - "url": "https://baidu.com", - "icon": "fa-solid fa-heart", - "description": "", - "private": false, - "category": { - "main": "law", - "sub": "criminal" - } - }, - { - "title": "12123", - "url": "https://baidu.com", - "icon": "fa-solid fa-graduation-cap", - "description": "", - "private": false, - "category": { - "main": "edu", - "sub": "language" - } - }, - { - "title": "57567", - "url": "https://baidu.com", + "title": "法律案例库", + "url": "https://www.casemine.com/", "icon": "fa-solid fa-carrot", - "description": "", + "description": "收录各类法律案例和判决文书", "private": false, "category": { "main": "law", @@ -204,10 +188,10 @@ } }, { - "title": "ythtyh", - "url": "https://baidu.com", + "title": "法律时效查询", + "url": "https://www.law.cornell.edu/wex/statute_of_limitations", "icon": "fa-solid fa-clock", - "description": "", + "description": "查询各类法律诉讼时效和期限", "private": false, "category": { "main": "law", @@ -215,14 +199,102 @@ } }, { - "title": "dsfds", - "url": "https://baidu.com", - "icon": "fa-solid fa-robot", - "description": "", + "title": "数据分析工具", + "url": "https://www.kaggle.com/", + "icon": "fa-solid fa-chart-line", + "description": "提供数据可视化和分析功能", + "private": false, + "category": { + "main": "dev", + "sub": "data" + } + }, + { + "title": "UI设计助手", + "url": "https://www.figma.com/", + "icon": "fa-solid fa-paint-brush", + "description": "提供UI设计灵感和素材资源", + "private": false, + "category": { + "main": "design", + "sub": "ui" + } + }, + { + "title": "法律文书模板", + "url": "https://www.lawdepot.com/contracts/", + "icon": "fa-solid fa-balance-scale", + "description": "提供各类法律文书模板和范例", + "private": true, + "category": { + "main": "law", + "sub": "document" + } + }, + { + "title": "数学公式助手", + "url": "https://www.mathway.com/", + "icon": "fa-solid fa-square-root-variable", + "description": "提供数学公式编辑和计算功能", "private": false, "category": { "main": "edu", - "sub": "language" + "sub": "math" + } + }, + { + "title": "健身计划生成器", + "url": "https://www.jefit.com/", + "icon": "fa-solid fa-dumbbell", + "description": "根据个人情况生成定制化健身计划", + "private": false, + "category": { + "main": "health", + "sub": "fitness" + } + }, + { + "title": "营养计算器", + "url": "https://www.myfitnesspal.com/", + "icon": "fa-solid fa-apple-whole", + "description": "计算每日营养摄入和饮食建议", + "private": false, + "category": { + "main": "health", + "sub": "nutrition" + } + }, + { + "title": "平面设计素材库", + "url": "https://www.canva.com/", + "icon": "fa-solid fa-palette", + "description": "提供丰富的平面设计素材和模板", + "private": false, + "category": { + "main": "design", + "sub": "graphic" + } + }, + { + "title": "AI代码生成器", + "url": "https://github.com/features/copilot", + "icon": "fa-solid fa-code", + "description": "根据自然语言描述生成代码片段", + "private": true, + "category": { + "main": "ai", + "sub": "code" + } + }, + { + "title": "番茄工作法计时器", + "url": "https://pomofocus.io/", + "icon": "fa-solid fa-clock", + "description": "基于番茄工作法的时间管理工具", + "private": false, + "category": { + "main": "tool", + "sub": "time" } } ] \ No newline at end of file diff --git a/data/attachments.json b/data/attachments.json index e358d1b..09656c9 100644 --- a/data/attachments.json +++ b/data/attachments.json @@ -1,61 +1,66 @@ [ { - "filename": "d078c01de3be46deab9e85a94285d785.png", + "filename": "d078c01de3be.png", "type": "background", "upload_time": "2025-07-05 13:55:10" }, { - "filename": "5dd4f5d3cd7b48eca9967fa063ea5cd9.png", + "filename": "5dd4f5d3cd7b.png", "type": "background", "upload_time": "2025-07-05 13:55:15" }, { - "filename": "b2c128cf2d4e47daa349c5e7f38c932c.png", + "filename": "b2c128cf2d4e.png", "type": "logo", "upload_time": "2025-07-05 13:58:32" }, { - "filename": "5378dda810964da9a7515ec844628738.png", + "filename": "5378dda81096.png", "type": "logo", "upload_time": "2025-07-05 16:46:09" }, { - "filename": "f40e2eb965b24e358a5bba9523231f8f.png", + "filename": "f40e2eb965b2.png", "type": "logo", "upload_time": "2025-07-05 16:46:16" }, { - "filename": "e4e762f039ce471489fc65db6cd395c7.mp4", + "filename": "59d8647c45f8.png", + "type": "logo", + "upload_time": "2025-07-05 16:46:16" + }, + { + "filename": "e4e762f039ce.mp4", "type": "video", "upload_time": "2025-07-05 20:54:08" }, { - "filename": "b23249a9681840329afb0c4af489fc30.mp4", + "filename": "b23249a96818.mp4", "type": "video", "upload_time": "2025-07-05 21:02:17" }, { - "filename": "4e9c7320469f4e6887f63e1f59f32ca1.png", + "filename": "4e9c7320469f.png", "type": "icon", "upload_time": "2025-07-06 18:52:21" }, { - "filename": "65eb0f29c9e147e0924890e9ae12e94f.png", + "filename": "65eb0f29c9e1.png", "type": "icon", "upload_time": "2025-07-06 19:03:04" }, { - "filename": "c6379d5521f24ca789e6712008f115f5.png", + "filename": "c6379d5521f2.png", "type": "icon", "upload_time": "2025-07-06 20:21:54" }, { - "filename": "7fb7491311f7477e9e3bd17150a929b7.png", + "filename": "7fb7491311f7.png", "type": "icon", "upload_time": "2025-07-06 20:22:11" }, { - "filename": "c77f6bbbb33540d98a6cc088d01d7ad6.png", + "filename": "c77f6bbbb335.png", "type": "icon", "upload_time": "2025-07-06 20:22:28" } diff --git a/data/categories.json b/data/categories.json index ef2af00..bae2580 100644 --- a/data/categories.json +++ b/data/categories.json @@ -21,7 +21,7 @@ "cloud": { "name": "云计算", "color": "#480ca8", - "weight": 0 + "weight": 4 } }, "private": true, @@ -31,7 +31,7 @@ "ops": true, "cloud": false }, - "weight": 99999 + "weight": 9527 }, "edu": { "name": "教育学习", @@ -40,16 +40,21 @@ "science": { "name": "自然科学", "color": "#b5179e", - "weight": 0 + "weight": 2 }, "language": { "name": "语言", "color": "#7209b7", - "weight": 0 + "weight": 3 + }, + "math": { + "name": "数学", + "color": "#560bad", + "weight": 4 } }, - "private": false, - "weight": 0 + "private": true, + "weight": 8 }, "tool": { "name": "效率工具", @@ -58,10 +63,16 @@ "office": { "name": "办公", "color": "#560bad", - "weight": 0 + "weight": 1 + }, + "time": { + "name": "时间管理", + "color": "#3a0ca3", + "weight": 2 } }, - "weight": 0 + "weight": 7, + "private": false }, "law": { "name": "法律相关", @@ -70,15 +81,21 @@ "criminal": { "name": "刑法", "color": "#4361ee", - "weight": 0 + "weight": 2 }, "tax": { "name": "税法", "color": "#3f37c9", - "weight": 0 + "weight": 1 + }, + "document": { + "name": "法律文书", + "color": "#3a0ca3", + "weight": 1 } }, - "weight": 0 + "weight": 6, + "private": false }, "ai": { "name": "AI工具", @@ -92,13 +109,55 @@ "writing": { "name": "写作", "color": "#f9844a", - "weight": 0 + "weight": 5 + }, + "code": { + "name": "代码生成", + "color": "#f8961e", + "weight": 6 } }, - "private": false, + "private": true, "sub_private": { - "image": false + "image": false, + "code": true }, - "weight": 29999 + "weight": 52 + }, + "design": { + "name": "设计工具", + "color": "#f94144", + "sub": { + "ui": { + "name": "UI设计", + "color": "#f3722c", + "weight": 3 + }, + "graphic": { + "name": "平面设计", + "color": "#f8961e", + "weight": 4 + } + }, + "weight": 5, + "private": false + }, + "health": { + "name": "健康医疗", + "color": "#43aa8b", + "sub": { + "fitness": { + "name": "健身", + "color": "#90be6d", + "weight": 3 + }, + "nutrition": { + "name": "营养", + "color": "#4d908e", + "weight": 2 + } + }, + "weight": 4, + "private": false } } \ No newline at end of file diff --git a/data/settings.json b/data/settings.json index c689b4a..835ef59 100644 --- a/data/settings.json +++ b/data/settings.json @@ -2,14 +2,14 @@ "card_style": "compact", "search_history": [], "theme": "light", - "bg_image": "/upload/background/5dd4f5d3cd7b48eca9967fa063ea5cd9.png", - "dark_bg_image": "/upload/background/d078c01de3be46deab9e85a94285d785.png", - "site_title": "应用导航", + "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/b2c128cf2d4e47daa349c5e7f38c932c.png", + "logo_image": "/upload/logo/f40e2eb965b2.png", "dark_bg_rotate": false, "admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb", - "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/edit_app.html b/templates/edit_app.html index c9aab04..01cc102 100644 --- a/templates/edit_app.html +++ b/templates/edit_app.html @@ -9,6 +9,7 @@
+
diff --git a/templates/icontest.html b/templates/icontest.html new file mode 100644 index 0000000..e22c016 --- /dev/null +++ b/templates/icontest.html @@ -0,0 +1,45 @@ + + + + + + Font Awesome 图标示例 + + + + + +

Font Awesome 图标示例

+ +
+ 撤销 +
+
+ 撤销(替代) +
+
+ 取消链接 +
+
+ 扳手 +
+
+ 500px +
+
+ 无障碍图标 +
+
+ Accusoft +
+
+ Acquisitions Inc +
+ + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index b75ff11..edbe4d2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -35,6 +35,29 @@ transition: background-color 0.3s ease, color 0.3s ease; } + /* 新增私有卡片标记样式 */ + .private-badge { + position: absolute; + top: -1px; + right: -1px; + width: 0; + height: 0; + border-style: solid; + border-width: 0 30px 30px 0; + border-color: transparent #f8961e transparent transparent; + border-radius: 0 12px 0 0; /* 只圆角右上角 */ + } + + .private-badge::after { + content: "私"; + position: absolute; + top: 2px; + right: -25px; + color: white; + font-size: 10px; + transform: rotate(45deg); + } + img, svg { vertical-align: middle; margin-bottom: 7px !important; @@ -333,15 +356,45 @@ overflow-x: auto; padding-bottom: 10px; scrollbar-width: thin; - scrollbar-color: var(--primary-color) transparent; + scrollbar-color: #c2c3c9d6; justify-content: center; /* 默认居中 */ padding: 0 20px; /* 添加内边距 */ + scroll-behavior: smooth; + position: relative; } /* 当内容超出时靠左 */ .filter-row.scrollable { justify-content: flex-start; } + +/* 新增的滚动指示器 */ +.filter-row::before, +.filter-row::after { + content: ''; + position: sticky; + top: 0; + width: 40px; + height: 100%; + z-index: 1; + pointer-events: none; +} + +.filter-row::before { + left: 0; + background: linear-gradient(90deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0) 100%); +} + +.filter-row::after { + right: 0; + background: linear-gradient(270deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0) 100%); +} + +body.dark-theme .filter-row::before, +body.dark-theme .filter-row::after { + background: linear-gradient(90deg, rgba(30,30,30,0.8) 0%, rgba(30,30,30,0) 100%); +} + /* 自定义滚动条样式 */ .filter-row::-webkit-scrollbar { height: 6px; @@ -377,13 +430,13 @@ } /* 一级标签样式 */ .filter-btn[data-level="1"] { - font-size: 15px; - padding: 4px 18px; + font-size: 13px; + padding: 4px 12px; } /* 二级标签样式 */ .filter-btn[data-level="2"] { font-size: 13px; - padding: 6px 12px; + padding: 4px 12px; } /* 分类选择器颜色 */ .filter-btn.dev { @@ -419,9 +472,19 @@ overflow-x: auto; padding-bottom: 10px; scrollbar-width: thin; - scrollbar-color: var(--primary-color) transparent; + scrollbar-color: #c2c3c9d6; justify-content: center; /* 默认居中 */ padding: 0 20px; /* 添加内边距 */ + scroll-behavior: smooth; + position: relative; +} + +/* 私有主分类 */ +.category-title .badge { + font-size: 12px; + padding: 3px 6px; + vertical-align: middle; + margin-left: 8px; } /* 当内容超出时靠左 */ @@ -429,6 +492,33 @@ justify-content: flex-start; } +/* 新增的滚动指示器 */ +.secondary-filters-container::before, +.secondary-filters-container::after { + content: ''; + position: sticky; + top: 0; + width: 40px; + height: 100%; + z-index: 1; + pointer-events: none; +} + +.secondary-filters-container::before { + left: 0; + background: linear-gradient(90deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0) 100%); +} + +.secondary-filters-container::after { + right: 0; + background: linear-gradient(270deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0) 100%); +} + +body.dark-theme .secondary-filters-container::before, +body.dark-theme .secondary-filters-container::after { + background: linear-gradient(90deg, rgba(30,30,30,0.8) 0%, rgba(30,30,30,0) 100%); +} + /* 自定义二级筛选容器的滚动条样式 */ .secondary-filters-container::-webkit-scrollbar { height: 6px; @@ -813,7 +903,7 @@ }); } - // 按权重对分类进行排序 + // 按权重对分类进行排序(无论是否登录都排序) categories = Object.fromEntries( Object.entries(categories).sort((a, b) => { const weightA = a[1].weight || 0; @@ -835,671 +925,728 @@ } }); - // 渲染界面 - renderFilters(); - renderApps(); - loadingSpinner.style.display = 'none'; - primaryFilters.querySelector('[data-filter="all"]').textContent = '全部'; - } catch (error) { - console.error('加载数据失败:', error); - appListContainer.innerHTML = ` -
- 加载数据失败,请刷新页面重试 + // 对每个主分类下的子分类也按权重排序 + Object.values(categories).forEach(cat => { + if (cat.sub) { + 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; // 降序排列 + }) + ); +} +}); + +// 渲染界面 +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} +
`; - } - } - // 更新登录按钮状态 - function updateLoginButton() { - if (isLoggedIn) { - loginBtn.innerHTML = ''; - loginBtn.href = '/logout'; - loginBtn.title = '退出登录'; - adminBtn.href = '/manage'; // 已登录时直接跳转到管理页面 + 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: [] +}; + +// 加载设置 +async function loadSettings() { +try { + // 检查是否登录 + isLoggedIn = await checkLoginStatus(); + + // 获取游客默认背景设置 + 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 { - 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; - } - - // 展开二级标签 - 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; - } - } - - // 收起所有二级标签 - function collapseAllSecondary() { - secondaryFiltersWrapper.innerHTML = ''; - currentExpandedPrimary = null; - } - - // 设置当前筛选条件 - 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'); + // 首次访问,使用接口返回的默认配置 + settings.theme = guestSettings.theme; + settings.card_style = guestSettings.card_style; } - // 激活二级标签按钮 - 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'); - } + // 设置背景图片(始终使用服务器配置) + 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'); - // 重新渲染应用列表 - renderApps(); + setBackgroundImages(lightBg, darkBg); } - // 渲染应用列表 - function renderApps() { - appListContainer.innerHTML = ''; + applySettings(); +} catch (error) { + console.error('加载设置失败:', error); +} +} - // 按一级标签分组 - Object.entries(categories).forEach(([mainCatId, mainCatData]) => { - const groupApps = apps.filter(app => app.category.main === mainCatId); +// 设置背景图片 +function setBackgroundImages(lightImage, darkImage) { +// 移除现有的视频背景 +document.querySelectorAll('.video-bg-container').forEach(el => el.remove()); - // 应用筛选条件 - let filteredGroupApps = groupApps; +// 处理明亮模式背景 +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'; - // 一级标签筛选 - if (currentPrimaryFilter !== 'all' && currentPrimaryFilter !== mainCatId) { - return; // 跳过不匹配的一级标签组 - } + 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 (currentSecondaryFilter !== 'all') { - filteredGroupApps = filteredGroupApps.filter(app => - app.category.sub === currentSecondaryFilter - ); + lightVideoContainer.appendChild(lightVideo); + document.body.appendChild(lightVideoContainer); +} else { + document.documentElement.style.setProperty('--bg-image', lightImage === 'none' ? 'none' : `url('${lightImage}')`); +} - if (filteredGroupApps.length === 0) { - return; - } - } +// 处理暗黑模式背景 +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'; - // 对应用按权重排序(权重越大越靠前) - filteredGroupApps.sort((a, b) => { - const weightA = a.weight || 0; - const weightB = b.weight || 0; - return weightB - weightA; // 降序排列 - }); + const darkVideo = document.createElement('video'); + darkVideo.className = 'video-bg'; + darkVideo.src = darkImage; + darkVideo.autoplay = true; + darkVideo.loop = true; + darkVideo.muted = true; + darkVideo.playsInline = true; - // 获取该分类下第一个应用的图标(如果有应用的话) - const firstAppIcon = filteredGroupApps.length > 0 ? - filteredGroupApps[0].icon : - 'fa-cube'; // 默认图标 + darkVideoContainer.appendChild(darkVideo); + document.body.appendChild(darkVideoContainer); +} else { + document.documentElement.style.setProperty('--dark-bg-image', darkImage === 'none' ? 'none' : `url('${darkImage}')`); +} - // 创建分类组 - const groupDiv = document.createElement('div'); - groupDiv.className = 'app-group'; +// 添加背景加载完成的类 +setTimeout(() => { + document.body.classList.add('bg-loaded'); +}, 100); +} - // 添加分类标题(使用第一个应用的图标) - const titleDiv = document.createElement('div'); - titleDiv.className = 'category-title'; - titleDiv.innerHTML = ` ${mainCatData.name}`; - groupDiv.appendChild(titleDiv); +// 检查登录状态 +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 appList = document.createElement('div'); - appList.className = 'app-list'; +// 应用设置 +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'); - // 添加应用卡片 - 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; + // 显示/隐藏视频背景 + 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'); - const appItem = document.createElement('a'); - appItem.className = `app-item ${settings.card_style === 'compact' ? 'compact' : ''}`; - appItem.href = app.url; - appItem.target = '_blank'; + // 显示/隐藏视频背景 + 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'); +} - // 添加聊天气泡描述提示框 - const descriptionDiv = document.createElement('div'); - descriptionDiv.className = 'app-description'; - descriptionDiv.textContent = app.description || '暂无描述'; +// 应用卡片样式 +document.querySelectorAll('.app-item').forEach(item => { + item.classList.toggle('compact', settings.card_style === 'compact'); +}); - // 判断是Font Awesome图标还是自定义图片 - const iconHtml = app.icon.startsWith('/') || app.icon.startsWith('http') ? - `图标` : - ``; +// 更新主题按钮状态 +updateThemeButtonIcon(); +} - appItem.innerHTML = ` -
- ${iconHtml} -
-
-
${app.title}
-
${new URL(app.url).hostname}
-
- ${subCatName} -
-
- `; +// 更新主题按钮图标 +function updateThemeButtonIcon() { +const themeToggle = document.getElementById('themeToggle'); +if (document.body.classList.contains('dark-theme')) { + themeToggle.textContent = '☀️'; + themeToggle.title = '切换至明亮模式'; +} else { + themeToggle.textContent = '🌙'; + themeToggle.title = '切换至暗黑模式'; +} +} - 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: [] +// 保存设置 +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 }; + localStorage.setItem('navSettings', JSON.stringify(settingsToSave)); +} +} - // 加载设置 - async function loadSettings() { - try { - // 检查是否登录 - isLoggedIn = await checkLoginStatus(); +// 搜索功能 +function setupSearch() { +const searchInput = document.getElementById('searchInput'); +const searchResults = document.getElementById('searchResults'); +let searchTimeout; - // 获取游客默认背景设置 - 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-sscheme: 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', { +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(settings) + 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 { - // 游客只保存主题和卡片样式到本地 - const settingsToSave = { - theme: settings.theme, - card_style: settings.card_style, - search_history: settings.search_history - }; - localStorage.setItem('navSettings', JSON.stringify(settingsToSave)); + 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'); } - // 搜索功能 - function setupSearch() { - const searchInput = document.getElementById('searchInput'); - const searchResults = document.getElementById('searchResults'); - let searchTimeout; + updateThemeButtonIcon(); + saveSettings(); +}); +} - 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); - }); +// 简洁模式切换 +function setupCompactToggle() { +const compactToggle = document.getElementById('compactToggle'); +compactToggle.addEventListener('click', () => { + settings.card_style = settings.card_style === 'compact' ? 'normal' : 'compact'; + applySettings(); + saveSettings(); +}); +} - document.addEventListener('click', (e) => { - if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { - searchResults.style.display = 'none'; - } - }); - } +// 后台管理按钮点击事件 +function setupAdminButton() { +adminBtn.addEventListener('click', async (e) => { + if (!isLoggedIn) { + e.preventDefault(); + const loginCheck = await fetch('/api/check_login'); + const loginData = await loginCheck.json(); - // 主题切换功能 - 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'; - } - } - }); - } - - // 页面加载完成后初始化 - document.addEventListener('DOMContentLoaded', () => { - loadData(); - loadSettings(); - setupSearch(); - setupThemeSwitcher(); - setupCompactToggle(); - setupAdminButton(); - }); -function checkScrollable() { - document.querySelectorAll('.filter-row, .secondary-filters-container').forEach(container => { - // 检查内容宽度是否大于容器宽度 - const isScrollable = container.scrollWidth > container.clientWidth; - - if(isScrollable) { - container.classList.add('scrollable'); + if (!loginData.logged_in) { + window.location.href = '/login?next=/manage'; } else { - container.classList.remove('scrollable'); + window.location.href = '/manage'; } - }); + } +}); +} + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', () => { +loadData(); +loadSettings(); +setupSearch(); +setupThemeSwitcher(); +setupCompactToggle(); +setupAdminButton(); + +// 为静态的一级筛选容器添加滚轮事件 +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'); + } +}); } // 初始化时检查 @@ -1508,8 +1655,8 @@ window.addEventListener('load', checkScrollable); window.addEventListener('resize', checkScrollable); // 内容变化时检查(如筛选器更新后) new MutationObserver(checkScrollable).observe(document.body, { - childList: true, - subtree: true +childList: true, +subtree: true }); diff --git a/templates/manage.html b/templates/manage.html index 71cf799..961f210 100644 --- a/templates/manage.html +++ b/templates/manage.html @@ -90,7 +90,7 @@ {{ app.url[:30] }}... - + 编辑 diff --git a/upload/background/5dd4f5d3cd7b48eca9967fa063ea5cd9.png b/upload/background/5dd4f5d3cd7b.png similarity index 100% rename from upload/background/5dd4f5d3cd7b48eca9967fa063ea5cd9.png rename to upload/background/5dd4f5d3cd7b.png diff --git a/upload/background/d078c01de3be46deab9e85a94285d785.png b/upload/background/d078c01de3be.png similarity index 100% rename from upload/background/d078c01de3be46deab9e85a94285d785.png rename to upload/background/d078c01de3be.png diff --git a/upload/icon/3be7a6c0b2644348b4fad32eb6b11f23.png b/upload/icon/4e9c7320469f.png similarity index 100% rename from upload/icon/3be7a6c0b2644348b4fad32eb6b11f23.png rename to upload/icon/4e9c7320469f.png diff --git a/upload/icon/65eb0f29c9e147e0924890e9ae12e94f.png b/upload/icon/65eb0f29c9e1.png similarity index 100% rename from upload/icon/65eb0f29c9e147e0924890e9ae12e94f.png rename to upload/icon/65eb0f29c9e1.png diff --git a/upload/icon/7fb7491311f7477e9e3bd17150a929b7.png b/upload/icon/7fb7491311f7.png similarity index 100% rename from upload/icon/7fb7491311f7477e9e3bd17150a929b7.png rename to upload/icon/7fb7491311f7.png diff --git a/upload/icon/94bfd7eeef5f4b99a7507d734805cac8.png b/upload/icon/94bfd7eeef5f4b99a7507d734805cac8.png deleted file mode 100644 index 0595613..0000000 Binary files a/upload/icon/94bfd7eeef5f4b99a7507d734805cac8.png and /dev/null differ diff --git a/upload/icon/b0c4c4150f1d4920af737e4e5b7d9f9d.png b/upload/icon/b0c4c4150f1d4920af737e4e5b7d9f9d.png deleted file mode 100644 index 0595613..0000000 Binary files a/upload/icon/b0c4c4150f1d4920af737e4e5b7d9f9d.png and /dev/null differ diff --git a/upload/icon/c6379d5521f24ca789e6712008f115f5.png b/upload/icon/c6379d5521f2.png similarity index 100% rename from upload/icon/c6379d5521f24ca789e6712008f115f5.png rename to upload/icon/c6379d5521f2.png diff --git a/upload/icon/c77f6bbbb33540d98a6cc088d01d7ad6.png b/upload/icon/c77f6bbbb335.png similarity index 100% rename from upload/icon/c77f6bbbb33540d98a6cc088d01d7ad6.png rename to upload/icon/c77f6bbbb335.png diff --git a/upload/logo/5378dda810964da9a7515ec844628738.png b/upload/logo/5378dda81096.png similarity index 100% rename from upload/logo/5378dda810964da9a7515ec844628738.png rename to upload/logo/5378dda81096.png diff --git a/upload/icon/4e9c7320469f4e6887f63e1f59f32ca1.png b/upload/logo/59d8647c45f8.png similarity index 100% rename from upload/icon/4e9c7320469f4e6887f63e1f59f32ca1.png rename to upload/logo/59d8647c45f8.png diff --git a/upload/logo/59d8647c45f8416d8d53efe8b92ea136.png b/upload/logo/59d8647c45f8416d8d53efe8b92ea136.png deleted file mode 100644 index 0595613..0000000 Binary files a/upload/logo/59d8647c45f8416d8d53efe8b92ea136.png and /dev/null differ diff --git a/upload/logo/b2c128cf2d4e47daa349c5e7f38c932c.png b/upload/logo/b2c128cf2d4e.png similarity index 100% rename from upload/logo/b2c128cf2d4e47daa349c5e7f38c932c.png rename to upload/logo/b2c128cf2d4e.png diff --git a/upload/logo/f40e2eb965b24e358a5bba9523231f8f.png b/upload/logo/f40e2eb965b2.png similarity index 100% rename from upload/logo/f40e2eb965b24e358a5bba9523231f8f.png rename to upload/logo/f40e2eb965b2.png diff --git a/upload/video/b23249a9681840329afb0c4af489fc30.mp4 b/upload/video/b23249a96818.mp4 similarity index 100% rename from upload/video/b23249a9681840329afb0c4af489fc30.mp4 rename to upload/video/b23249a96818.mp4 diff --git a/upload/video/e4e762f039ce471489fc65db6cd395c7.mp4 b/upload/video/e4e762f039ce.mp4 similarity index 100% rename from upload/video/e4e762f039ce471489fc65db6cd395c7.mp4 rename to upload/video/e4e762f039ce.mp4