1663 lines
50 KiB
HTML
1663 lines
50 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>应用导航</title>
|
||
{% if settings and settings.logo_type == 'image' and settings.logo_image %}
|
||
<link rel="icon" href="{{ settings.logo_image }}">
|
||
{% else %}
|
||
<link rel="icon" href="/static/favicon.png">
|
||
{% endif %}
|
||
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
|
||
<link rel="stylesheet" href="/static/css/all.min.css">
|
||
<style>
|
||
:root {
|
||
--primary-color: #4361ee;
|
||
--hover-color: #3a56d4;
|
||
--dev-color: #4cc9f0;
|
||
--edu-color: #f72585;
|
||
--tool-color: #7209b7;
|
||
--law-color: #4895ef;
|
||
--ai-color: #f8961e;
|
||
--tooltip-bg: rgb(149 236 105);
|
||
--tooltip-text: black;
|
||
--bg-image: none;
|
||
--dark-bg-image: none;
|
||
}
|
||
body {
|
||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
background-color: #f8f9fa;
|
||
color: #333;
|
||
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;
|
||
}
|
||
|
||
/* 背景图片容器 */
|
||
.bg-container {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-size: cover;
|
||
background-attachment: fixed;
|
||
background-position: center;
|
||
background-repeat: no-repeat;
|
||
z-index: -1;
|
||
opacity: 0;
|
||
transition: opacity 0.5s ease;
|
||
}
|
||
|
||
/* 视频背景容器 */
|
||
.video-bg-container {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: -1;
|
||
overflow: hidden;
|
||
}
|
||
.video-bg {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
min-width: 100%;
|
||
min-height: 100%;
|
||
width: auto;
|
||
height: auto;
|
||
object-fit: cover;
|
||
}
|
||
|
||
/* 明亮主题背景 */
|
||
.bg-light {
|
||
background-image: var(--bg-image);
|
||
}
|
||
|
||
/* 暗黑主题背景 */
|
||
.bg-dark {
|
||
background-image: var(--dark-bg-image);
|
||
}
|
||
|
||
/* 背景加载完成后的状态 */
|
||
body.bg-loaded .bg-container {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 暗黑主题下显示暗黑背景 */
|
||
body.dark-theme .bg-light {
|
||
display: none;
|
||
}
|
||
|
||
body.dark-theme .bg-dark {
|
||
display: block;
|
||
}
|
||
|
||
/* 明亮主题下显示明亮背景 */
|
||
body:not(.dark-theme) .bg-light {
|
||
display: block;
|
||
}
|
||
|
||
body:not(.dark-theme) .bg-dark {
|
||
display: none;
|
||
}
|
||
|
||
h1 {
|
||
text-align: center;
|
||
color: var(--primary-color);
|
||
margin-bottom: 30px;
|
||
font-weight: 600;
|
||
position: relative;
|
||
}
|
||
|
||
/* 移除导航栏按钮样式 */
|
||
.nav-header {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* 新增的搜索框样式 */
|
||
.search-container {
|
||
margin: 20px auto;
|
||
max-width: 600px;
|
||
position: relative;
|
||
}
|
||
.search-input {
|
||
width: 100%;
|
||
padding: 12px 20px;
|
||
border-radius: 25px;
|
||
border: 1px solid #ddd;
|
||
font-size: 16px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||
}
|
||
.search-results {
|
||
position: absolute;
|
||
width: 100%;
|
||
background: white;
|
||
border-radius: 0 0 10px 10px;
|
||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||
z-index: 100;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
display: none;
|
||
transition: background-color 0.3s ease;
|
||
}
|
||
.search-result-item {
|
||
padding: 10px 20px;
|
||
border-bottom: 1px solid #eee;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s ease;
|
||
}
|
||
.search-result-item:hover {
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
/* 右下角按钮组 */
|
||
.floating-buttons {
|
||
position: fixed;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
z-index: 1000;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.floating-btn {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
border: none;
|
||
background: var(--primary-color);
|
||
color: white;
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||
transition: all 0.3s;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.floating-btn:hover {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
/* 按钮提示文字 */
|
||
.floating-btn::after {
|
||
content: attr(title);
|
||
position: absolute;
|
||
right: 50px;
|
||
white-space: nowrap;
|
||
background: rgba(0,0,0,0.7);
|
||
color: white;
|
||
padding: 5px 10px;
|
||
border-radius: 5px;
|
||
font-size: 14px;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.3s;
|
||
}
|
||
.floating-btn:hover::after {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 暗黑模式下的按钮样式 */
|
||
body.dark-theme .floating-btn {
|
||
background: #444;
|
||
color: #eee;
|
||
}
|
||
|
||
/* 简洁模式卡片样式 */
|
||
.app-item.compact {
|
||
padding: 10px 15px;
|
||
height: 50px;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.app-item.compact .app-icon {
|
||
margin-right: 15px;
|
||
margin-bottom: 0;
|
||
width: 30px;
|
||
height: 30px;
|
||
font-size: 16px;
|
||
}
|
||
.app-item.compact .app-info {
|
||
width: 100%;
|
||
}
|
||
.app-item.compact .app-title {
|
||
font-size: 15px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
flex-grow: 1;
|
||
}
|
||
.app-item.compact .app-url,
|
||
.app-item.compact .app-tags {
|
||
display: none;
|
||
}
|
||
|
||
/* 暗黑主题 */
|
||
body.dark-theme {
|
||
background-color: #121212;
|
||
color: #e0e0e0;
|
||
--tooltip-bg: rgba(30, 30, 30, 0.95);
|
||
--tooltip-text: #e0e0e0;
|
||
}
|
||
|
||
/* 新增暗黑模式下标题颜色设置 */
|
||
body.dark-theme h1 {
|
||
color: white !important;
|
||
}
|
||
body.dark-theme .app-item {
|
||
background-color: #1e1e1edb;
|
||
border-color: #333333a1;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .category-title {
|
||
border-bottom-color: #333;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .filter-container {
|
||
background-color: #121212d4 !important;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .secondary-filters-container {
|
||
background-color: #12121200 !important;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .filter-btn {
|
||
background-color: #2d2d2d !important;
|
||
color: #e0e0e0 !important;
|
||
}
|
||
body.dark-theme .search-input {
|
||
background-color: #1e1e1e;
|
||
color: #e0e0e0;
|
||
border-color: #333;
|
||
}
|
||
body.dark-theme .search-results {
|
||
background-color: #1e1e1e;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .search-result-item {
|
||
border-bottom-color: #333;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .search-result-item:hover {
|
||
background-color: #2a2a2a;
|
||
}
|
||
body.dark-theme .floating-btn {
|
||
background-color: #444;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .floating-btn:hover {
|
||
background-color: #555;
|
||
}
|
||
body.dark-theme .app-url {
|
||
color: #aaa !important;
|
||
}
|
||
|
||
/* 修改后的筛选容器样式 - 支持横向滚动 */
|
||
.filter-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
position: sticky;
|
||
top: 0;
|
||
background-color: rgb(255 255 255 / 34%);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border-radius: 12px;
|
||
z-index: 10;
|
||
padding: 15px;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
transition: background-color 0.3s ease, color 0.3s ease;
|
||
}
|
||
|
||
/* 修改后的筛选行样式 - 支持横向滚动 */
|
||
.filter-row {
|
||
display: flex;
|
||
flex-wrap: nowrap;
|
||
gap: 10px;
|
||
overflow-x: auto;
|
||
padding-bottom: 10px;
|
||
scrollbar-width: thin;
|
||
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;
|
||
}
|
||
.filter-row::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
.filter-row::-webkit-scrollbar-thumb {
|
||
background-color: var(--primary-color);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.filter-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 20px;
|
||
background-color: #e9ecef;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-size: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
white-space: nowrap; /* 禁止按钮内文字换行 */
|
||
flex-shrink: 0; /* 禁止按钮缩小 */
|
||
}
|
||
.filter-btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||
}
|
||
.filter-btn.active {
|
||
font-weight: bold;
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||
}
|
||
/* 一级标签样式 */
|
||
.filter-btn[data-level="1"] {
|
||
font-size: 13px;
|
||
padding: 4px 12px;
|
||
}
|
||
/* 二级标签样式 */
|
||
.filter-btn[data-level="2"] {
|
||
font-size: 13px;
|
||
padding: 4px 12px;
|
||
}
|
||
/* 分类选择器颜色 */
|
||
.filter-btn.dev {
|
||
background-color: var(--dev-color, #4cc9f0) !important;
|
||
}
|
||
.filter-btn.edu {
|
||
background-color: var(--edu-color, #f72585) !important;
|
||
}
|
||
.filter-btn.tool {
|
||
background-color: var(--tool-color, #7209b7) !important;
|
||
}
|
||
.filter-btn.law {
|
||
background-color: var(--law-color, #4895ef) !important;
|
||
}
|
||
.filter-btn.ai {
|
||
background-color: var(--ai-color, #f8961e) !important;
|
||
}
|
||
|
||
.caret {
|
||
margin-right: 8px;
|
||
transition: transform 0.2s;
|
||
}
|
||
.caret.down {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
/* 修改后的二级筛选容器样式 - 支持横向滚动 */
|
||
.secondary-filters-container {
|
||
width: 100%;
|
||
display: none;
|
||
flex-wrap: nowrap;
|
||
gap: 10px;
|
||
overflow-x: auto;
|
||
padding-bottom: 10px;
|
||
scrollbar-width: thin;
|
||
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;
|
||
}
|
||
|
||
/* 当内容超出时靠左 */
|
||
.secondary-filters-container.scrollable {
|
||
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;
|
||
}
|
||
.secondary-filters-container::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
.secondary-filters-container::-webkit-scrollbar-thumb {
|
||
background-color: var(--primary-color);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.secondary-filters-container.show {
|
||
display: flex;
|
||
}
|
||
.secondary-filters-wrapper {
|
||
width: 100%;
|
||
position: relative;
|
||
min-height: 30px;
|
||
}
|
||
.app-list-container {
|
||
margin-top: 20px;
|
||
position: relative;
|
||
}
|
||
.category-title {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
margin: 30px 0 15px 0;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid #e9ecef70;
|
||
display: flex;
|
||
align-items: center;
|
||
transition: color 0.3s ease, border-color 0.3s ease;
|
||
}
|
||
.category-title i {
|
||
margin-right: 10px;
|
||
}
|
||
.app-group {
|
||
margin-bottom: 30px;
|
||
}
|
||
.app-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 20px;
|
||
}
|
||
@media (max-width: 1200px) {
|
||
.app-list {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
}
|
||
}
|
||
@media (max-width: 992px) {
|
||
.app-list {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
}
|
||
}
|
||
@media (max-width: 768px) {
|
||
.app-list {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
@media (max-width: 576px) {
|
||
.app-list {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
.app-item {
|
||
background-color: #ffffff69;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
box-shadow: 0 4px 6px rgb(0 0 0 / 13%);
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
border: 1px solid #e9ecef;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
position: relative;
|
||
}
|
||
.app-item:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 10px 15px rgba(0,0,0,0.1);
|
||
border-color: var(--primary-color);
|
||
}
|
||
.app-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
margin-right: 15px;
|
||
border-radius: 8px;
|
||
object-fit: cover;
|
||
background-color: #f1f3f5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
color: var(--primary-color);
|
||
transition: background-color 0.3s ease;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.app-icon img {
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
width: auto;
|
||
height: auto;
|
||
object-fit: contain;
|
||
margin: 0 !important;
|
||
}
|
||
.app-info {
|
||
flex: 1;
|
||
}
|
||
.app-title {
|
||
font-weight: 600;
|
||
margin-bottom: 4px;
|
||
transition: color 0.3s ease;
|
||
}
|
||
.app-url {
|
||
font-size: 13px;
|
||
color: #6c757d;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
transition: color 0.3s ease;
|
||
}
|
||
.app-tags {
|
||
display: flex;
|
||
gap: 5px;
|
||
margin-top: 5px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.app-tag {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
font-size: 12px;
|
||
border-radius: 10px;
|
||
color: white;
|
||
}
|
||
/* 分类标签颜色 */
|
||
.tag-dev {
|
||
background-color: var(--dev-color, #4cc9f0) !important;
|
||
}
|
||
.tag-edu {
|
||
background-color: var(--edu-color, #f72585) !important;
|
||
}
|
||
.tag-tool {
|
||
background-color: var(--tool-color, #7209b7) !important;
|
||
}
|
||
.tag-law {
|
||
background-color: var(--law-color, #4895ef) !important;
|
||
}
|
||
.tag-ai {
|
||
background-color: var(--ai-color, #f8961e) !important;
|
||
}
|
||
|
||
.no-results {
|
||
grid-column: 1 / -1;
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: 6c757d;
|
||
}
|
||
.app-group.hidden {
|
||
display: none;
|
||
}
|
||
.loading-spinner {
|
||
display: inline-block;
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 3px solid rgba(0,0,0,.1);
|
||
border-radius: 50%;
|
||
border-top-color: var(--primary-color);
|
||
animation: spin 1s ease-in-out infinite;
|
||
margin-right: 10px;
|
||
}
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* 修改后的聊天气泡提示框样式 */
|
||
.app-description {
|
||
position: absolute;
|
||
bottom: calc(100% + 10px);
|
||
left: 20px;
|
||
transform: none;
|
||
background-color: var(--tooltip-bg);
|
||
color: var(--tooltip-text);
|
||
padding: 10px 15px;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
max-width: 300px;
|
||
width: max-content;
|
||
opacity: 0;
|
||
transition: opacity 0.3s;
|
||
pointer-events: none;
|
||
z-index: 100;
|
||
text-align: left;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
/* 修改后的聊天气泡小三角 - 移到左侧 */
|
||
.app-description::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 20px;
|
||
transform: none;
|
||
border-width: 8px;
|
||
border-style: solid;
|
||
border-color: var(--tooltip-bg) transparent transparent transparent;
|
||
}
|
||
.app-item:hover .app-description {
|
||
opacity: 1;
|
||
}
|
||
.footer {
|
||
margin-top: 40px;
|
||
padding: 20px 0;
|
||
border-top: 1px solid #eee;
|
||
text-align: center;
|
||
color: #777;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 背景图片容器 -->
|
||
<div class="bg-container bg-light"></div>
|
||
<div class="bg-container bg-dark"></div>
|
||
|
||
<div class="nav-header">
|
||
<h1>
|
||
{% if settings and settings.show_logo %}
|
||
{% if settings.logo_type == 'icon' %}
|
||
<i class="fas {{ settings.logo_icon }}"></i>
|
||
{% else %}
|
||
<img src="{{ settings.logo_image if settings.logo_image else '/static/favicon.png' }}" style="height: 1em; vertical-align: middle;">
|
||
{% endif %}
|
||
{% endif %}
|
||
{{ settings.site_title if settings else "应用导航中心" }}
|
||
</h1>
|
||
</div>
|
||
|
||
<div class="search-container">
|
||
<input type="text" class="search-input" placeholder="搜索应用、网址或分类..." id="searchInput">
|
||
<div class="search-results" id="searchResults"></div>
|
||
</div>
|
||
|
||
<div class="filter-container">
|
||
<div class="filter-row" id="primaryFilters">
|
||
<button class="filter-btn active" data-level="1" data-filter="all">
|
||
<span id="loadingSpinner" class="loading-spinner"></span>
|
||
加载中...
|
||
</button>
|
||
</div>
|
||
<div class="secondary-filters-wrapper" id="secondaryFiltersWrapper">
|
||
<!-- 二级标签容器将通过JS动态生成 -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="app-list-container" id="appListContainer">
|
||
<div class="text-center py-5">
|
||
<div class="loading-spinner" style="width: 40px; height: 40px; margin: 0 auto 15px;"></div>
|
||
<p>正在加载应用数据...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="floating-buttons">
|
||
<a href="/login?next=/" class="floating-btn" id="loginBtn" title="登录/退出"><i class="fas fa-sign-in-alt"></i></a>
|
||
<a href="/manage" class="floating-btn" id="adminBtn" title="后台管理"><i class="fas fa-cog"></i></a>
|
||
<button class="floating-btn" id="themeToggle" title="切换主题">🌙</button>
|
||
<button class="floating-btn" id="compactToggle" title="简洁模式">📱</button>
|
||
</div>
|
||
|
||
<footer class="footer">
|
||
{% include 'footer.html' %}
|
||
</footer>
|
||
<script>
|
||
// 全局变量存储应用和分类数据
|
||
let apps = [];
|
||
let categories = {};
|
||
let isLoggedIn = false;
|
||
|
||
// DOM元素
|
||
const appListContainer = document.getElementById('appListContainer');
|
||
const primaryFilters = document.getElementById('primaryFilters');
|
||
const secondaryFiltersWrapper = document.getElementById('secondaryFiltersWrapper');
|
||
const loadingSpinner = document.getElementById('loadingSpinner');
|
||
const loginBtn = document.getElementById('loginBtn');
|
||
const adminBtn = document.getElementById('adminBtn');
|
||
|
||
let currentPrimaryFilter = 'all';
|
||
let currentSecondaryFilter = 'all';
|
||
let currentExpandedPrimary = null;
|
||
|
||
// 从后端加载数据
|
||
async function loadData() {
|
||
try {
|
||
// 检查登录状态
|
||
const loginCheck = await fetch('/api/check_login');
|
||
const loginData = await loginCheck.json();
|
||
isLoggedIn = loginData.logged_in;
|
||
|
||
// 更新登录按钮状态
|
||
updateLoginButton();
|
||
|
||
// 加载应用数据
|
||
const appsResponse = await fetch('/api/apps');
|
||
apps = await appsResponse.json();
|
||
|
||
// 加载分类数据
|
||
const categoriesResponse = await fetch('/api/categories');
|
||
categories = await categoriesResponse.json();
|
||
|
||
// 动态添加CSS变量
|
||
const style = document.createElement('style');
|
||
let cssVariables = '';
|
||
|
||
Object.entries(categories).forEach(([catId, catData]) => {
|
||
// 为主分类添加颜色变量
|
||
cssVariables += `:root { --${catId}-color: ${catData.color}; }\n`;
|
||
|
||
// 为子分类添加颜色变量
|
||
if (catData.sub) {
|
||
Object.entries(catData.sub).forEach(([subId, subData]) => {
|
||
if (subData.color) {
|
||
cssVariables += `:root { --${subId}-color: ${subData.color}; }\n`;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
style.textContent = cssVariables;
|
||
document.head.appendChild(style);
|
||
|
||
// 转换应用数据结构,添加tags数组
|
||
apps = apps.map(app => {
|
||
const mainCat = app.category.main;
|
||
const subCat = app.category.sub;
|
||
const mainCatName = categories[mainCat]?.name || mainCat;
|
||
const subCatName = categories[mainCat]?.sub[subCat]?.name || subCat;
|
||
const subCatColor = categories[mainCat]?.sub[subCat]?.color || categories[mainCat]?.color;
|
||
|
||
return {
|
||
...app,
|
||
tags: [
|
||
{
|
||
level: 1,
|
||
id: mainCat,
|
||
name: mainCatName,
|
||
color: categories[mainCat]?.color
|
||
},
|
||
{
|
||
level: 2,
|
||
id: subCat,
|
||
name: subCatName,
|
||
color: subCatColor
|
||
}
|
||
]
|
||
};
|
||
});
|
||
|
||
// 过滤掉私有分类的应用
|
||
if (!isLoggedIn) {
|
||
apps = apps.filter(app => {
|
||
const mainCat = app.category.main;
|
||
const subCat = app.category.sub;
|
||
// 检查主分类是否私有
|
||
const isMainPrivate = categories[mainCat]?.private || false;
|
||
// 检查子分类是否私有
|
||
const isSubPrivate = categories[mainCat]?.sub_private?.[subCat] || false;
|
||
return !isMainPrivate && !isSubPrivate;
|
||
});
|
||
|
||
// 过滤掉私有分类
|
||
Object.keys(categories).forEach(catId => {
|
||
if (categories[catId].private) {
|
||
delete categories[catId];
|
||
} else if (categories[catId].sub_private) {
|
||
Object.keys(categories[catId].sub_private).forEach(subId => {
|
||
if (categories[catId].sub_private[subId]) {
|
||
delete categories[catId].sub[subId];
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// 按权重对分类进行排序(无论是否登录都排序)
|
||
categories = Object.fromEntries(
|
||
Object.entries(categories).sort((a, b) => {
|
||
const weightA = a[1].weight || 0;
|
||
const weightB = b[1].weight || 0;
|
||
return weightB - weightA; // 降序排列
|
||
})
|
||
);
|
||
|
||
// 对每个主分类下的子分类也按权重排序
|
||
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; // 降序排列
|
||
})
|
||
);
|
||
}
|
||
});
|
||
|
||
// 对每个主分类下的子分类也按权重排序
|
||
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 = `
|
||
<div class="alert alert-danger">
|
||
加载数据失败,请刷新页面重试
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// 更新登录按钮状态
|
||
function updateLoginButton() {
|
||
if (isLoggedIn) {
|
||
loginBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
|
||
loginBtn.href = '/logout';
|
||
loginBtn.title = '退出登录';
|
||
adminBtn.href = '/manage'; // 已登录时直接跳转到管理页面
|
||
} else {
|
||
loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i>';
|
||
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 = `<i class="fas fa-caret-right caret down"></i> ${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 = `<i class="fas ${firstAppIcon}"></i> ${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') ?
|
||
`<img src="${app.icon}" alt="图标">` :
|
||
`<i class="fas ${app.icon}"></i>`;
|
||
|
||
appItem.innerHTML += `
|
||
<div class="app-icon">
|
||
${iconHtml}
|
||
</div>
|
||
<div class="app-info">
|
||
<div class="app-title">${app.title}</div>
|
||
<div class="app-url">${new URL(app.url).hostname}</div>
|
||
<div class="app-tags">
|
||
<span class="app-tag" style="background-color: ${subCatColor}; color: ${getContrastColor(subCatColor)}">${subCatName}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 {
|
||
// 首次访问,使用接口返回的默认配置
|
||
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(settings)
|
||
});
|
||
} else {
|
||
// 游客只保存主题和卡片样式到本地
|
||
const settingsToSave = {
|
||
theme: settings.theme,
|
||
card_style: settings.card_style,
|
||
search_history: settings.search_history
|
||
};
|
||
localStorage.setItem('navSettings', JSON.stringify(settingsToSave));
|
||
}
|
||
}
|
||
|
||
// 搜索功能
|
||
function setupSearch() {
|
||
const searchInput = document.getElementById('searchInput');
|
||
const searchResults = document.getElementById('searchResults');
|
||
let searchTimeout;
|
||
|
||
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 = `
|
||
<div><strong>${app.title}</strong></div>
|
||
<div style="font-size:12px;color:#666;">${app.url}</div>
|
||
`;
|
||
item.addEventListener('click', () => {
|
||
window.open(app.url, '_blank');
|
||
});
|
||
searchResults.appendChild(item);
|
||
});
|
||
searchResults.style.display = 'block';
|
||
} else {
|
||
searchResults.innerHTML = '<div class="search-result-item">无搜索结果</div>';
|
||
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 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();
|
||
|
||
// 为静态的一级筛选容器添加滚轮事件
|
||
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
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |