2719 lines
89 KiB
HTML
2719 lines
89 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||
<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;
|
||
--title-color: #4361ee;
|
||
--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;
|
||
--title-transition: color 0.15s ease-in-out;
|
||
--card-bg-light: rgba(255, 255, 255, 0.8);
|
||
--filter-bg-light: rgba(255, 255, 255, 0.8);
|
||
--card-bg-dark: rgba(30, 30, 30, 0.8);
|
||
--filter-bg-dark: rgba(30, 30, 30, 0.8);
|
||
--floating-btn-bg-light: rgba(255, 255, 255, 0.8);
|
||
--floating-btn-bg-dark: rgba(30, 30, 30, 0.8);
|
||
}
|
||
◦ {
|
||
|
||
box-sizing: border-box;
|
||
}
|
||
/* 强制页面内容区域始终显示垂直滚动条 */
|
||
html {
|
||
overflow-y: scroll;
|
||
}
|
||
|
||
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.15s ease-in-out;
|
||
overflow-x: hidden;
|
||
width: 100%;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 新增私有卡片标记样式 */
|
||
.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.3s ease-in-out;
|
||
}
|
||
|
||
/* 视频背景容器 */
|
||
.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(--title-color);
|
||
margin-bottom: 30px;
|
||
font-weight: 600;
|
||
position: relative;
|
||
transition: var(--title-transition);
|
||
}
|
||
|
||
/* 移除导航栏按钮样式 */
|
||
.nav-header {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* 新增的搜索框样式 */
|
||
/* 修改后的搜索框和结果样式 */
|
||
.search-container {
|
||
margin: 20px auto;
|
||
max-width: 500px; /* 缩小搜索框最大宽度 */
|
||
position: relative;
|
||
width: 100%;
|
||
padding: 0 15px;
|
||
}
|
||
|
||
.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.15s ease-in-out, color 0.15s ease-in-out, border-color 0.15s ease-in-out;
|
||
}
|
||
|
||
.search-results {
|
||
position: absolute;
|
||
width: calc(100% - 30px); /* 修改为与搜索框底边宽度一致 */
|
||
/* left: 33px; */ /* 确保与搜索框对齐 */
|
||
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.15s ease-in-out;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: #939393;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* 自定义滚动条样式 */
|
||
.search-results::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
.search-results::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.search-results::-webkit-scrollbar-thumb {
|
||
background-color: var(--primary-color);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.search-result-item {
|
||
padding: 10px 20px;
|
||
border-bottom: 1px solid #eee;
|
||
cursor: pointer;
|
||
transition: background-color 0.15s ease-in-out;
|
||
}
|
||
|
||
.search-result-item:hover {
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
/* 暗黑模式下的搜索结果样式 */
|
||
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;
|
||
}
|
||
|
||
/* 右下角按钮组 */
|
||
.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(--floating-btn-bg-light);
|
||
color: var(--primary-color);
|
||
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: var(--floating-btn-bg-dark);
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.floating-btn#addBtn {
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
display: none;
|
||
}
|
||
|
||
.floating-btn#addBtn:hover {
|
||
background-color: var(--primary-color);
|
||
}
|
||
|
||
body.dark-theme .floating-btn#addBtn {
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
/* 简洁模式卡片样式 */
|
||
.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: var(--title-color) !important;
|
||
}
|
||
body.dark-theme .app-item {
|
||
background-color: var(--card-bg-dark);
|
||
border-color: #333333a1;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .category-title {
|
||
border-bottom-color: #333;
|
||
color: #e0e0e0;
|
||
}
|
||
body.dark-theme .filter-container {
|
||
background-color: var(--filter-bg-dark) !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: var(--floating-btn-bg-dark);
|
||
color: var(--primary-color);
|
||
}
|
||
body.dark-theme .floating-btn:hover {
|
||
background-color: var(--floating-btn-bg-dark);
|
||
}
|
||
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: var(--filter-bg-light);
|
||
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.15s ease-in-out;
|
||
margin-left: -15px;
|
||
margin-right: -15px;
|
||
width: calc(100% + 30px);
|
||
}
|
||
|
||
/* 修改后的筛选行样式 */
|
||
.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 15px;
|
||
scroll-behavior: smooth;
|
||
position: relative;
|
||
width: 100%;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
/* 当内容超出时靠左 */
|
||
.filter-row.scrollable {
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
/* 新增的滚动指示器 */
|
||
.filter-row::before,
|
||
.filter-row::after {
|
||
display: 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,
|
||
.secondary-filters-container::-webkit-scrollbar,
|
||
.search-results::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.filter-row,
|
||
.secondary-filters-container {
|
||
-ms-overflow-style: none;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
.filter-row::-webkit-scrollbar-thumb,
|
||
.secondary-filters-container::-webkit-scrollbar-thumb,
|
||
.search-results::-webkit-scrollbar-thumb {
|
||
background-color: var(--primary-color);
|
||
border-radius: 2px; /* 更小的圆角 */
|
||
border: none;
|
||
}
|
||
|
||
.filter-row::-webkit-scrollbar-track,
|
||
.secondary-filters-container::-webkit-scrollbar-track,
|
||
.search-results::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.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;
|
||
margin-right: 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 15px;
|
||
scroll-behavior: smooth;
|
||
position: relative;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
/* 私有主分类 */
|
||
.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;
|
||
width: 100%;
|
||
}
|
||
.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: var(--title-transition);
|
||
}
|
||
.category-title i {
|
||
margin-right: 10px;
|
||
}
|
||
.app-group {
|
||
margin-bottom: 30px;
|
||
width: 100%;
|
||
}
|
||
.app-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 20px;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 新增卡片动画样式 */
|
||
.app-item {
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||
}
|
||
.app-item.visible {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
@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);
|
||
}
|
||
body {
|
||
padding: 15px;
|
||
}
|
||
.filter-container {
|
||
margin-left: -15px;
|
||
margin-right: -15px;
|
||
padding-left: 20px;
|
||
padding-right: 20px;
|
||
width: calc(100% + 30px);
|
||
}
|
||
.floating-btn::after {
|
||
display: none;
|
||
}
|
||
}
|
||
@media (max-width: 576px) {
|
||
.app-list {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
body {
|
||
padding: 10px;
|
||
}
|
||
.filter-container {
|
||
margin-left: -10px;
|
||
margin-right: -10px;
|
||
padding-left: 15px;
|
||
padding-right: 15px;
|
||
width: calc(100% + 20px);
|
||
}
|
||
}
|
||
.app-item {
|
||
background-color: var(--card-bg-light);
|
||
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;
|
||
width: 100%;
|
||
}
|
||
.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.15s ease-in-out;
|
||
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: var(--title-transition);
|
||
}
|
||
.app-url {
|
||
font-size: 13px;
|
||
color: #6c757d;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
transition: color 0.15s ease-in-out;
|
||
}
|
||
.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: -15px;
|
||
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;
|
||
width: 102%;
|
||
margin-left: -13px;
|
||
margin-right: -20px;
|
||
box-sizing: border-box;
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
/* 右键菜单样式 */
|
||
.context-menu {
|
||
position: absolute;
|
||
z-index: 1000;
|
||
background-color: white;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||
display: none;
|
||
}
|
||
|
||
.context-menu.dark {
|
||
background-color: #333;
|
||
border-color: #555;
|
||
}
|
||
|
||
.context-menu-item {
|
||
padding: 8px 16px;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.context-menu-item:hover {
|
||
background-color: #8b8a8a;
|
||
}
|
||
|
||
.context-menu-item.dark:hover {
|
||
background-color: #444;
|
||
}
|
||
|
||
.context-menu-divider {
|
||
height: 1px;
|
||
background-color: #eee;
|
||
margin: 4px 0;
|
||
}
|
||
|
||
.context-menu-divider.dark {
|
||
background-color: #555;
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
/* 主题样式设置面板 */
|
||
.theme-settings-panel {
|
||
position: fixed;
|
||
right: 0;
|
||
top: 45%;
|
||
transform: translateY(-50%);
|
||
z-index: 1001;
|
||
display: flex;
|
||
transition: all 0.3s ease;
|
||
border-radius: 8px 0 0 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.theme-settings-panel.collapsed {
|
||
right: -220px;
|
||
}
|
||
|
||
/* 紧凑布局修改 */
|
||
.settings-toggle {
|
||
width: 36px;
|
||
height: 36px;
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
border-radius: 8px 0 0 8px;
|
||
margin-left: -1px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 新增hover旋转效果 */
|
||
.settings-toggle i {
|
||
transition: transform 0.2s ease-in-out;
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
/* 面板展开时图标旋转状态 */
|
||
.theme-settings-panel:not(.collapsed) .settings-toggle i {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
/* 面板展开时图标Hover旋转状态 */
|
||
.theme-settings-panel:not(.collapsed) .settings-toggle:hover i {
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
/* Hover时图标状态(仅在面板收起时生效) */
|
||
.theme-settings-panel.collapsed .settings-toggle:hover i {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
|
||
.settings-content {
|
||
width: 220px; /* 缩小面板宽度 */
|
||
background-color: var(--card-bg-light);
|
||
padding: 12px;
|
||
border-left: 1px solid rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.dark-theme .settings-content {
|
||
background-color: var(--card-bg-dark);
|
||
border-left-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.settings-header {
|
||
margin-bottom: 10px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.dark-theme .settings-header {
|
||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.settings-header h4 {
|
||
margin: 0;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.settings-body {
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
padding-right: 5px;
|
||
}
|
||
|
||
.light-settings, .dark-settings {
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.dark-theme .light-settings,
|
||
.dark-theme .dark-settings {
|
||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.light-settings h5, .dark-settings h5 {
|
||
margin: 8px 0 10px 0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
font-size: 12px;
|
||
color: var(--text-color);
|
||
}
|
||
|
||
.form-control-color {
|
||
width: 100%;
|
||
height: 28px;
|
||
padding: 2px;
|
||
border-radius: 4px;
|
||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.dark-theme .form-control-color {
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.form-range-container {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.form-range {
|
||
flex: 1;
|
||
height: 6px;
|
||
margin: 0;
|
||
}
|
||
|
||
.opacity-value {
|
||
font-size: 12px;
|
||
min-width: 30px;
|
||
text-align: right;
|
||
}
|
||
|
||
.settings-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.settings-actions .btn {
|
||
padding: 4px 8px;
|
||
font-size: 12px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.settings-actions .btn-primary {
|
||
background-color: var(--primary-color);
|
||
border-color: var(--primary-color);
|
||
}
|
||
|
||
.settings-actions .btn-primary:hover {
|
||
background-color: var(--bs-btn-hover-bg);
|
||
border-color: var(--bs-btn-hover-border-color);
|
||
}
|
||
|
||
.dark-settings {
|
||
display: none;
|
||
}
|
||
|
||
.dark-theme .light-settings {
|
||
display: none;
|
||
}
|
||
|
||
.dark-theme .dark-settings {
|
||
display: block;
|
||
}
|
||
|
||
/* 自定义滚动条 */
|
||
.settings-body::-webkit-scrollbar {
|
||
width: 4px;
|
||
}
|
||
|
||
.settings-body::-webkit-scrollbar-thumb {
|
||
background-color: var(--primary-color);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.settings-body::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 背景图片容器 -->
|
||
<div class="bg-container bg-light"></div>
|
||
<div class="bg-container bg-dark"></div>
|
||
|
||
|
||
<!-- 主题样式设置面板 -->
|
||
<div class="theme-settings-panel collapsed">
|
||
<div class="settings-toggle" title="主题设置">
|
||
<i class="fas fa-palette"></i>
|
||
</div>
|
||
<div class="settings-content">
|
||
<div class="settings-header">
|
||
<h4>主题设置</h4>
|
||
</div>
|
||
<div class="settings-body">
|
||
<div class="light-settings">
|
||
<h5>明亮模式</h5>
|
||
<div class="form-group">
|
||
<label>主色</label>
|
||
<input type="color" class="form-control form-control-color" id="lightPrimaryColor" value="#4361ee">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>标题色</label>
|
||
<input type="color" class="form-control form-control-color" id="lightTitleColor" value="#4361ee">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>背景色</label>
|
||
<input type="color" class="form-control form-control-color" id="lightBgColor" value="#ffffff">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>透明度</label>
|
||
<div class="form-range-container">
|
||
<input type="range" class="form-range" id="lightOpacity" min="0.1" max="1" step="0.1" value="0.8">
|
||
<span class="opacity-value">80%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="dark-settings">
|
||
<h5>暗黑模式</h5>
|
||
<div class="form-group">
|
||
<label>主色</label>
|
||
<input type="color" class="form-control form-control-color" id="darkPrimaryColor" value="#4361ee">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>标题色</label>
|
||
<input type="color" class="form-control form-control-color" id="darkTitleColor" value="#4361ee">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>背景色</label>
|
||
<input type="color" class="form-control form-control-color" id="darkBgColor" value="#1e1e1e">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>透明度</label>
|
||
<div class="form-range-container">
|
||
<input type="range" class="form-range" id="darkOpacity" min="0.1" max="1" step="0.1" value="0.8">
|
||
<span class="opacity-value">80%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-actions">
|
||
<button class="btn btn-sm btn-outline-secondary" id="resetThemeSettings">重置</button>
|
||
<button class="btn btn-sm btn-primary" id="saveThemeSettings">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</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>
|
||
<!-- 一级标签按钮将通过JS动态生成 -->
|
||
</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">
|
||
<button class="floating-btn" id="backToTopBtn" title="返回顶部" style="display: none;">↑</button>
|
||
<button class="floating-btn" id="themeToggle" title="切换主题">🌙</button>
|
||
<button class="floating-btn" id="compactToggle" title="简洁模式">📱</button>
|
||
<a href="/manage" class="floating-btn" id="adminBtn" title="后台管理"><i class="fas fa-cog" style="color: #6c757d;"></i></a>
|
||
<a href="/app/add" class="floating-btn" id="addBtn" title="添加应用"><i class="fas fa-plus" style="color: #28a745;"></i></a>
|
||
<a href="/login?next=/" class="floating-btn" id="loginBtn" title="登录/退出"><i class="fas fa-sign-in-alt" style="color: #007bff;"></i></a>
|
||
</div>
|
||
|
||
<!-- 右键菜单 -->
|
||
<div id="contextMenu" class="context-menu">
|
||
<div class="context-menu-item" data-action="open">打开</div>
|
||
<div class="context-menu-item" data-action="copy">复制地址</div>
|
||
<div class="context-menu-divider"></div>
|
||
<div class="context-menu-item" data-action="edit" style="display: none;">编辑</div>
|
||
<div class="context-menu-item" data-action="delete" style="display: none;">删除</div>
|
||
</div>
|
||
|
||
<footer class="footer">
|
||
{% include 'footer.html' %}
|
||
</footer>
|
||
<script>
|
||
// 全局变量存储应用和分类数据
|
||
let apps = [];
|
||
let categories = {};
|
||
let isLoggedIn = false;
|
||
let themeSettingsLoaded = 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');
|
||
const backToTopBtn = document.getElementById('backToTopBtn');
|
||
|
||
let currentPrimaryFilter = 'all';
|
||
let currentSecondaryFilter = 'all';
|
||
let currentExpandedPrimary = null;
|
||
let activeSecondaryFilter = 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; // 降序排列
|
||
})
|
||
);
|
||
}
|
||
});
|
||
|
||
// 渲染界面
|
||
renderFilters();
|
||
renderApps();
|
||
loadingSpinner.style.display = 'none';
|
||
primaryFilters.querySelector('[data-filter="all"]').textContent = '全部';
|
||
|
||
// 修复分类按钮滑动问题
|
||
setupFilterScroll();
|
||
} catch (error) {
|
||
console.error('加载数据失败:', error);
|
||
appListContainer.innerHTML = `
|
||
<div class="alert alert-danger">
|
||
加载数据失败,请刷新页面重试
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// 新增的修复分类按钮滑动问题的函数
|
||
function setupFilterScroll() {
|
||
const filterRow = document.querySelector('.filter-row');
|
||
const secondaryFilters = document.querySelector('.secondary-filters-container');
|
||
|
||
// 检查是否需要显示滚动条并调整对齐方式
|
||
function checkScrollable() {
|
||
// 处理主筛选行
|
||
if (filterRow.scrollWidth > filterRow.clientWidth) {
|
||
filterRow.classList.add('scrollable');
|
||
filterRow.classList.remove('centered');
|
||
filterRow.style.justifyContent = 'flex-start';
|
||
} else {
|
||
filterRow.classList.remove('scrollable');
|
||
filterRow.classList.add('centered');
|
||
filterRow.style.justifyContent = 'center';
|
||
}
|
||
|
||
// 处理二级筛选行
|
||
if (secondaryFilters) {
|
||
if (secondaryFilters.scrollWidth > secondaryFilters.clientWidth) {
|
||
secondaryFilters.classList.add('scrollable');
|
||
secondaryFilters.classList.remove('centered');
|
||
secondaryFilters.style.justifyContent = 'flex-start';
|
||
} else {
|
||
secondaryFilters.classList.remove('scrollable');
|
||
secondaryFilters.classList.add('centered');
|
||
secondaryFilters.style.justifyContent = 'center';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加滚动事件监听
|
||
filterRow.addEventListener('scroll', () => {
|
||
// 添加滚动到边缘时的样式变化
|
||
if (filterRow.scrollLeft === 0) {
|
||
filterRow.classList.add('scroll-start');
|
||
filterRow.classList.remove('scroll-middle');
|
||
} else if (filterRow.scrollLeft + filterRow.clientWidth >= filterRow.scrollWidth - 1) {
|
||
filterRow.classList.add('scroll-end');
|
||
filterRow.classList.remove('scroll-middle');
|
||
} else {
|
||
filterRow.classList.add('scroll-middle');
|
||
filterRow.classList.remove('scroll-start', 'scroll-end');
|
||
}
|
||
});
|
||
|
||
if (secondaryFilters) {
|
||
secondaryFilters.addEventListener('scroll', () => {
|
||
if (secondaryFilters.scrollLeft === 0) {
|
||
secondaryFilters.classList.add('scroll-start');
|
||
secondaryFilters.classList.remove('scroll-middle');
|
||
} else if (secondaryFilters.scrollLeft + secondaryFilters.clientWidth >= secondaryFilters.scrollWidth - 1) {
|
||
secondaryFilters.classList.add('scroll-end');
|
||
secondaryFilters.classList.remove('scroll-middle');
|
||
} else {
|
||
secondaryFilters.classList.add('scroll-middle');
|
||
secondaryFilters.classList.remove('scroll-start', 'scroll-end');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 初始检查
|
||
checkScrollable();
|
||
window.addEventListener('resize', checkScrollable);
|
||
|
||
// 添加平滑滚动行为
|
||
filterRow.style.scrollBehavior = 'smooth';
|
||
if (secondaryFilters) {
|
||
secondaryFilters.style.scrollBehavior = 'smooth';
|
||
}
|
||
|
||
// 确保按钮可以滑动到最边缘
|
||
filterRow.style.padding = '0 15px';
|
||
if (secondaryFilters) {
|
||
secondaryFilters.style.padding = '0 15px';
|
||
}
|
||
}
|
||
|
||
// 更新登录按钮状态
|
||
function updateLoginButton() {
|
||
if (isLoggedIn) {
|
||
loginBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
|
||
loginBtn.href = '/logout';
|
||
loginBtn.title = '退出登录';
|
||
adminBtn.href = '/manage'; // 已登录时直接跳转到管理页面
|
||
document.getElementById('addBtn').style.display = 'flex'; // 显示添加按钮
|
||
} else {
|
||
loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i>';
|
||
loginBtn.href = '/login';
|
||
loginBtn.title = '登录';
|
||
adminBtn.href = '/login?next=/manage'; // 未登录时跳转到登录页面
|
||
document.getElementById('addBtn').style.display = 'none'; // 隐藏添加按钮
|
||
}
|
||
}
|
||
|
||
// 根据分类ID获取对应的颜色类
|
||
function getColorForCategory(categoryId) {
|
||
return categoryId;
|
||
}
|
||
|
||
// 生成筛选按钮
|
||
function renderFilters() {
|
||
// 生成一级标签
|
||
primaryFilters.innerHTML = '';
|
||
const allPrimaryBtn = document.createElement('button');
|
||
allPrimaryBtn.className = 'filter-btn active';
|
||
allPrimaryBtn.textContent = '全部';
|
||
allPrimaryBtn.dataset.level = '1';
|
||
allPrimaryBtn.dataset.filter = 'all';
|
||
allPrimaryBtn.addEventListener('click', () => {
|
||
setFilter('all', 'all');
|
||
showAllSecondaryFilters();
|
||
setAllPrimaryCaretDown(true);
|
||
activeSecondaryFilter = null; // 重置二级筛选状态
|
||
});
|
||
primaryFilters.appendChild(allPrimaryBtn);
|
||
|
||
// 添加一级标签按钮(从分类数据生成)
|
||
Object.entries(categories).forEach(([catId, catData]) => {
|
||
const btn = document.createElement('button');
|
||
btn.className = `filter-btn ${catId}`;
|
||
btn.innerHTML = `<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);
|
||
activeSecondaryFilter = null; // 重置二级筛选状态
|
||
} else {
|
||
// 否则展开该一级分类下的二级分类
|
||
expandSecondary(catId);
|
||
setFilter(catId, 'all');
|
||
setAllPrimaryCaretDown(false);
|
||
setPrimaryCaretDown(catId, true);
|
||
activeSecondaryFilter = null; // 重置二级筛选状态
|
||
}
|
||
});
|
||
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');
|
||
activeSecondaryFilter = null; // 重置二级筛选状态
|
||
});
|
||
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', () => {
|
||
// 如果点击的是当前激活的二级筛选,则取消筛选
|
||
if (activeSecondaryFilter === subCatId) {
|
||
setFilter('all', 'all');
|
||
activeSecondaryFilter = null;
|
||
} else {
|
||
setFilter('all', subCatId);
|
||
activeSecondaryFilter = 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');
|
||
activeSecondaryFilter = null; // 重置二级筛选状态
|
||
});
|
||
container.appendChild(allSecondaryBtn);
|
||
|
||
Object.entries(categories[primaryFilterId].sub).forEach(([subCatId, subCatData]) => {
|
||
const btn = document.createElement('button');
|
||
btn.className = `filter-btn ${primaryFilterId}`;
|
||
btn.textContent = subCatData.name;
|
||
btn.dataset.level = '2';
|
||
btn.dataset.filter = subCatId;
|
||
btn.style.backgroundColor = subCatData.color;
|
||
btn.style.color = getContrastColor(subCatData.color);
|
||
btn.addEventListener('click', () => {
|
||
// 如果点击的是当前激活的二级筛选,则取消筛选
|
||
if (activeSecondaryFilter === subCatId) {
|
||
setFilter(primaryFilterId, 'all');
|
||
activeSecondaryFilter = null;
|
||
} else {
|
||
setFilter(primaryFilterId, subCatId);
|
||
activeSecondaryFilter = subCatId;
|
||
}
|
||
});
|
||
container.appendChild(btn);
|
||
});
|
||
|
||
secondaryFiltersWrapper.innerHTML = '';
|
||
secondaryFiltersWrapper.appendChild(container);
|
||
currentExpandedPrimary = primaryFilterId;
|
||
|
||
// 为新创建的二级筛选容器添加滚轮事件
|
||
addWheelEventToContainer(container);
|
||
}
|
||
}
|
||
|
||
// 收起所有二级标签
|
||
function collapseAllSecondary() {
|
||
secondaryFiltersWrapper.innerHTML = '';
|
||
currentExpandedPrimary = null;
|
||
}
|
||
|
||
// 为滚动容器添加滚轮事件
|
||
function addWheelEventToContainer(container) {
|
||
container.addEventListener('wheel', (e) => {
|
||
if (e.deltaY !== 0) {
|
||
e.preventDefault();
|
||
container.scrollLeft += e.deltaY;
|
||
}
|
||
});
|
||
}
|
||
|
||
// 设置当前筛选条件
|
||
function setFilter(primaryFilter, secondaryFilter) {
|
||
currentPrimaryFilter = primaryFilter;
|
||
currentSecondaryFilter = secondaryFilter;
|
||
|
||
// 更新按钮激活状态
|
||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
|
||
// 激活一级标签按钮
|
||
if (primaryFilter === 'all') {
|
||
primaryFilters.querySelector('[data-filter="all"]').classList.add('active');
|
||
} else {
|
||
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);
|
||
}
|
||
|
||
// 添加卡片动画效果
|
||
animateCards();
|
||
}
|
||
|
||
// 添加卡片动画效果
|
||
function animateCards() {
|
||
const cards = document.querySelectorAll('.app-item');
|
||
cards.forEach((card, index) => {
|
||
// 使用setTimeout实现卡片依次出现的效果
|
||
setTimeout(() => {
|
||
card.classList.add('visible');
|
||
}, index * 10); // 每个卡片间隔50ms
|
||
});
|
||
}
|
||
|
||
|
||
// 辅助函数:十六进制颜色转RGBA
|
||
function hexToRgba(hex, alpha) {
|
||
const r = parseInt(hex.slice(1, 3), 16);
|
||
const g = parseInt(hex.slice(3, 5), 16);
|
||
const b = parseInt(hex.slice(5, 7), 16);
|
||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||
}
|
||
|
||
// 辅助函数:计算对比色
|
||
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 applyThemeStyles(settings) {
|
||
const isDark = document.body.classList.contains('dark-theme');
|
||
const theme = isDark ? settings.dark : settings.light;
|
||
|
||
// 更新CSS变量
|
||
document.documentElement.style.setProperty('--primary-color', theme.primary_color);
|
||
document.documentElement.style.setProperty('--title-color', theme.title_color);
|
||
|
||
// 更新卡片、筛选器和按钮的透明度和背景色
|
||
const opacity = theme.opacity;
|
||
const bgColor = theme.bg_color;
|
||
const cardBg = hexToRgba(bgColor, opacity);
|
||
const filterBg = hexToRgba(bgColor, opacity);
|
||
const floatingBtnBg = hexToRgba(bgColor, opacity);
|
||
|
||
// 同时更新明亮和暗黑模式的CSS变量
|
||
document.documentElement.style.setProperty('--card-bg-light', hexToRgba(settings.light.bg_color, settings.light.opacity));
|
||
document.documentElement.style.setProperty('--filter-bg-light', hexToRgba(settings.light.bg_color, settings.light.opacity));
|
||
document.documentElement.style.setProperty('--floating-btn-bg-light', hexToRgba(settings.light.bg_color, settings.light.opacity));
|
||
|
||
document.documentElement.style.setProperty('--card-bg-dark', hexToRgba(settings.dark.bg_color, settings.dark.opacity));
|
||
document.documentElement.style.setProperty('--filter-bg-dark', hexToRgba(settings.dark.bg_color, settings.dark.opacity));
|
||
document.documentElement.style.setProperty('--floating-btn-bg-dark', hexToRgba(settings.dark.bg_color, settings.dark.opacity));
|
||
|
||
// 更新当前显示的卡片、筛选器和按钮
|
||
document.querySelectorAll('.app-item').forEach(item => {
|
||
item.style.backgroundColor = cardBg;
|
||
});
|
||
|
||
document.querySelector('.filter-container').style.backgroundColor = filterBg;
|
||
document.querySelectorAll('.floating-btn').forEach(btn => {
|
||
btn.style.backgroundColor = floatingBtnBg;
|
||
});
|
||
}
|
||
|
||
// 新增函数:从UI获取当前设置并应用样式
|
||
function applyThemeStylesFromUI() {
|
||
const settings = {
|
||
light: {
|
||
primary_color: document.getElementById('lightPrimaryColor').value,
|
||
title_color: document.getElementById('lightTitleColor').value,
|
||
opacity: parseFloat(document.getElementById('lightOpacity').value),
|
||
bg_color: document.getElementById('lightBgColor').value
|
||
},
|
||
dark: {
|
||
primary_color: document.getElementById('darkPrimaryColor').value,
|
||
title_color: document.getElementById('darkTitleColor').value,
|
||
opacity: parseFloat(document.getElementById('darkOpacity').value),
|
||
bg_color: document.getElementById('darkBgColor').value
|
||
}
|
||
};
|
||
applyThemeStyles(settings);
|
||
}
|
||
|
||
|
||
// 应用设置
|
||
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();
|
||
|
||
// 修改这里:不再直接调用 loadThemeSettings,而是从UI获取当前设置
|
||
if (themeSettingsLoaded) {
|
||
applyThemeStylesFromUI();
|
||
} else {
|
||
loadThemeSettings(); // 首次加载时调用
|
||
}
|
||
}
|
||
// 更新主题按钮图标
|
||
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', () => {
|
||
// 使用requestAnimationFrame确保在浏览器重绘前完成所有样式变更
|
||
requestAnimationFrame(() => {
|
||
// 确定新主题
|
||
const newTheme = document.body.classList.contains('dark-theme') ? 'light' : 'dark';
|
||
|
||
// 更新界面主题
|
||
document.body.classList.remove('dark-theme', 'light-theme');
|
||
if (newTheme === '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');
|
||
} else {
|
||
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');
|
||
}
|
||
|
||
// 更新主题按钮图标
|
||
updateThemeButtonIcon();
|
||
|
||
// 更新本地设置(仅主题)
|
||
settings.theme = newTheme;
|
||
|
||
// 触发自定义事件通知主题已更改
|
||
const themeChangedEvent = new Event('themeChanged');
|
||
document.dispatchEvent(themeChangedEvent);
|
||
|
||
// 保存设置(不会影响后台的背景设置)
|
||
if (isLoggedIn) {
|
||
// 已登录用户只更新主题设置
|
||
fetch('/api/settings', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ theme: newTheme })
|
||
}).catch(error => {
|
||
console.error('保存主题设置失败:', error);
|
||
});
|
||
} else {
|
||
// 游客保存到本地(仅保存主题、卡片样式和搜索历史)
|
||
const settingsToSave = {
|
||
theme: newTheme,
|
||
card_style: settings.card_style,
|
||
search_history: settings.search_history
|
||
};
|
||
localStorage.setItem('navSettings', JSON.stringify(settingsToSave));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 简洁模式切换
|
||
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';
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 返回顶部功能
|
||
function setupBackToTop() {
|
||
window.addEventListener('scroll', () => {
|
||
if (window.pageYOffset > 300) {
|
||
backToTopBtn.style.display = 'flex';
|
||
} else {
|
||
backToTopBtn.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
backToTopBtn.addEventListener('click', () => {
|
||
window.scrollTo({
|
||
top: 0,
|
||
behavior: 'smooth'
|
||
});
|
||
});
|
||
}
|
||
|
||
// 右键菜单功能
|
||
function setupContextMenu() {
|
||
const contextMenu = document.getElementById('contextMenu');
|
||
let currentApp = null;
|
||
let currentAppIndex = -1;
|
||
|
||
document.addEventListener('contextmenu', (e) => {
|
||
const appItem = e.target.closest('.app-item');
|
||
if (appItem) {
|
||
e.preventDefault();
|
||
currentApp = apps.find(app => app.title === appItem.querySelector('.app-title').textContent);
|
||
currentAppIndex = apps.findIndex(app => app.title === appItem.querySelector('.app-title').textContent);
|
||
|
||
contextMenu.style.left = `${e.pageX}px`;
|
||
contextMenu.style.top = `${e.pageY}px`;
|
||
contextMenu.style.display = 'block';
|
||
|
||
if (document.body.classList.contains('dark-theme')) {
|
||
contextMenu.classList.add('dark');
|
||
} else {
|
||
contextMenu.classList.remove('dark');
|
||
}
|
||
|
||
const editItem = contextMenu.querySelector('[data-action="edit"]');
|
||
const deleteItem = contextMenu.querySelector('[data-action="delete"]');
|
||
editItem.style.display = isLoggedIn ? 'block' : 'none';
|
||
deleteItem.style.display = isLoggedIn ? 'block' : 'none';
|
||
}
|
||
});
|
||
|
||
document.addEventListener('click', (e) => {
|
||
if (!contextMenu.contains(e.target)) {
|
||
contextMenu.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
contextMenu.addEventListener('click', (e) => {
|
||
const menuItem = e.target.closest('.context-menu-item');
|
||
if (!menuItem || !currentApp) return;
|
||
|
||
const action = menuItem.dataset.action;
|
||
switch (action) {
|
||
case 'open':
|
||
window.open(currentApp.url, '_blank');
|
||
break;
|
||
case 'copy':
|
||
navigator.clipboard.writeText(currentApp.url)
|
||
.then(() => showNotification('已复制到剪贴板'))
|
||
.catch(() => fallbackCopyText(currentApp.url));
|
||
break;
|
||
case 'edit':
|
||
if (isLoggedIn) window.location.href = `/app/edit/${currentAppIndex}`;
|
||
break;
|
||
case 'delete':
|
||
if (isLoggedIn && confirm(`确定要删除 "${currentApp.title}" 吗?`)) {
|
||
fetch(`/app/delete/${currentAppIndex}`, { method: 'GET' })
|
||
.then(response => response.ok && loadData())
|
||
.catch(error => console.error('删除失败:', error));
|
||
}
|
||
break;
|
||
}
|
||
contextMenu.style.display = 'none';
|
||
});
|
||
}
|
||
|
||
// 主题样式设置功能
|
||
// 修改后的主题样式设置功能
|
||
function setupThemeSettings() {
|
||
const settingsPanel = document.querySelector('.theme-settings-panel');
|
||
const settingsToggle = document.querySelector('.settings-toggle');
|
||
const resetBtn = document.getElementById('resetThemeSettings');
|
||
const saveBtn = document.getElementById('saveThemeSettings');
|
||
|
||
settingsPanel.classList.add('collapsed');
|
||
|
||
// 点击展开/收起面板
|
||
settingsToggle.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
settingsPanel.classList.toggle('collapsed');
|
||
});
|
||
|
||
// 点击页面其他区域收起面板
|
||
document.addEventListener('click', (e) => {
|
||
if (!settingsPanel.contains(e.target) && !settingsPanel.classList.contains('collapsed')) {
|
||
settingsPanel.classList.add('collapsed');
|
||
}
|
||
});
|
||
|
||
// 阻止面板内部点击事件冒泡
|
||
settingsPanel.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
});
|
||
|
||
// 当前主题设置缓存
|
||
let currentThemeSettings = null;
|
||
|
||
// 确保登录状态是最新的
|
||
async function checkLoginStatus() {
|
||
try {
|
||
const response = await fetch('/api/check_login');
|
||
const data = await response.json();
|
||
isLoggedIn = data.logged_in;
|
||
return isLoggedIn;
|
||
} catch (error) {
|
||
console.error('检查登录状态失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 加载主题设置
|
||
async function loadThemeSettings() {
|
||
// 确保获取最新的登录状态
|
||
const loggedIn = await checkLoginStatus();
|
||
|
||
if (loggedIn) {
|
||
// 登录用户从后端加载配置
|
||
try {
|
||
const response = await fetch('/api/theme_settings');
|
||
if (!response.ok) throw new Error('获取主题设置失败');
|
||
currentThemeSettings = await response.json();
|
||
updateUIWithSettings(currentThemeSettings);
|
||
themeSettingsLoaded = true;
|
||
applyCurrentThemeSettings();
|
||
// 登录后清除本地存储的主题设置
|
||
localStorage.removeItem('themeSettings');
|
||
} catch (error) {
|
||
console.error('从后端加载主题设置失败:', error);
|
||
// 如果后端加载失败,尝试从本地加载(如果有)
|
||
const localSettings = localStorage.getItem('themeSettings');
|
||
if (localSettings) {
|
||
currentThemeSettings = JSON.parse(localSettings);
|
||
updateUIWithSettings(currentThemeSettings);
|
||
themeSettingsLoaded = true;
|
||
applyCurrentThemeSettings();
|
||
} else {
|
||
// 使用默认设置
|
||
currentThemeSettings = getDefaultThemeSettings();
|
||
updateUIWithSettings(currentThemeSettings);
|
||
themeSettingsLoaded = true;
|
||
applyCurrentThemeSettings();
|
||
}
|
||
}
|
||
} else {
|
||
// 未登录用户从本地存储加载配置
|
||
const localSettings = localStorage.getItem('themeSettings');
|
||
if (localSettings) {
|
||
currentThemeSettings = JSON.parse(localSettings);
|
||
updateUIWithSettings(currentThemeSettings);
|
||
themeSettingsLoaded = true;
|
||
applyCurrentThemeSettings();
|
||
} else {
|
||
// 使用默认设置
|
||
currentThemeSettings = getDefaultThemeSettings();
|
||
updateUIWithSettings(currentThemeSettings);
|
||
themeSettingsLoaded = true;
|
||
applyCurrentThemeSettings();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 应用当前主题设置(根据当前是暗黑还是明亮模式)
|
||
function applyCurrentThemeSettings() {
|
||
if (!currentThemeSettings) return;
|
||
|
||
const isDark = document.body.classList.contains('dark-theme');
|
||
const theme = isDark ? currentThemeSettings.dark : currentThemeSettings.light;
|
||
|
||
// 更新CSS变量
|
||
document.documentElement.style.setProperty('--primary-color', theme.primary_color);
|
||
document.documentElement.style.setProperty('--title-color', theme.title_color);
|
||
|
||
// 更新卡片、筛选器和按钮的透明度和背景色
|
||
const cardBg = hexToRgba(theme.bg_color, theme.opacity);
|
||
const filterBg = hexToRgba(theme.bg_color, theme.opacity);
|
||
const floatingBtnBg = hexToRgba(theme.bg_color, theme.opacity);
|
||
|
||
document.documentElement.style.setProperty('--card-bg-light', hexToRgba(currentThemeSettings.light.bg_color, currentThemeSettings.light.opacity));
|
||
document.documentElement.style.setProperty('--filter-bg-light', hexToRgba(currentThemeSettings.light.bg_color, currentThemeSettings.light.opacity));
|
||
document.documentElement.style.setProperty('--floating-btn-bg-light', hexToRgba(currentThemeSettings.light.bg_color, currentThemeSettings.light.opacity));
|
||
|
||
document.documentElement.style.setProperty('--card-bg-dark', hexToRgba(currentThemeSettings.dark.bg_color, currentThemeSettings.dark.opacity));
|
||
document.documentElement.style.setProperty('--filter-bg-dark', hexToRgba(currentThemeSettings.dark.bg_color, currentThemeSettings.dark.opacity));
|
||
document.documentElement.style.setProperty('--floating-btn-bg-dark', hexToRgba(currentThemeSettings.dark.bg_color, currentThemeSettings.dark.opacity));
|
||
|
||
// 更新当前显示的卡片、筛选器和按钮
|
||
document.querySelectorAll('.app-item').forEach(item => {
|
||
item.style.backgroundColor = cardBg;
|
||
});
|
||
|
||
document.querySelector('.filter-container').style.backgroundColor = filterBg;
|
||
document.querySelectorAll('.floating-btn').forEach(btn => {
|
||
btn.style.backgroundColor = floatingBtnBg;
|
||
});
|
||
}
|
||
|
||
// 其他辅助函数...
|
||
function updateUIWithSettings(settings) {
|
||
document.getElementById('lightPrimaryColor').value = settings.light.primary_color;
|
||
document.getElementById('lightTitleColor').value = settings.light.title_color;
|
||
document.getElementById('lightOpacity').value = settings.light.opacity;
|
||
document.getElementById('lightBgColor').value = settings.light.bg_color;
|
||
document.querySelector('#lightOpacity + .opacity-value').textContent = `${Math.round(settings.light.opacity * 100)}%`;
|
||
|
||
document.getElementById('darkPrimaryColor').value = settings.dark.primary_color;
|
||
document.getElementById('darkTitleColor').value = settings.dark.title_color;
|
||
document.getElementById('darkOpacity').value = settings.dark.opacity;
|
||
document.getElementById('darkBgColor').value = settings.dark.bg_color;
|
||
document.querySelector('#darkOpacity + .opacity-value').textContent = `${Math.round(settings.dark.opacity * 100)}%`;
|
||
}
|
||
|
||
function getDefaultThemeSettings() {
|
||
return {
|
||
light: {
|
||
primary_color: '#4361ee',
|
||
title_color: '#4361ee',
|
||
opacity: 0.3,
|
||
bg_color: '#ffffff'
|
||
},
|
||
dark: {
|
||
primary_color: '#4361ee',
|
||
title_color: '#4361ee',
|
||
opacity: 0.7,
|
||
bg_color: '#1e1e1e'
|
||
}
|
||
};
|
||
}
|
||
|
||
// 保存主题设置
|
||
async function saveThemeSettings() {
|
||
const settings = {
|
||
light: {
|
||
primary_color: document.getElementById('lightPrimaryColor').value,
|
||
title_color: document.getElementById('lightTitleColor').value,
|
||
opacity: parseFloat(document.getElementById('lightOpacity').value),
|
||
bg_color: document.getElementById('lightBgColor').value
|
||
},
|
||
dark: {
|
||
primary_color: document.getElementById('darkPrimaryColor').value,
|
||
title_color: document.getElementById('darkTitleColor').value,
|
||
opacity: parseFloat(document.getElementById('darkOpacity').value),
|
||
bg_color: document.getElementById('darkBgColor').value
|
||
}
|
||
};
|
||
|
||
// 确保获取最新的登录状态
|
||
const loggedIn = await checkLoginStatus();
|
||
|
||
currentThemeSettings = settings;
|
||
|
||
if (loggedIn) {
|
||
try {
|
||
const response = await fetch('/api/theme_settings', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(settings)
|
||
});
|
||
|
||
if (response.ok) {
|
||
showNotification('主题设置已保存');
|
||
// 登录用户保存成功后清除本地存储
|
||
localStorage.removeItem('themeSettings');
|
||
} else {
|
||
showNotification('保存失败', 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('保存主题设置失败:', error);
|
||
showNotification('保存失败', 'error');
|
||
}
|
||
} else {
|
||
// 未登录用户保存到本地
|
||
localStorage.setItem('themeSettings', JSON.stringify(settings));
|
||
showNotification('主题设置已保存到本地');
|
||
}
|
||
|
||
// 立即应用新设置
|
||
applyCurrentThemeSettings();
|
||
}
|
||
|
||
resetBtn.addEventListener('click', () => {
|
||
const defaultSettings = getDefaultThemeSettings();
|
||
updateUIWithSettings(defaultSettings);
|
||
currentThemeSettings = defaultSettings;
|
||
applyCurrentThemeSettings();
|
||
});
|
||
|
||
saveBtn.addEventListener('click', saveThemeSettings);
|
||
|
||
['lightPrimaryColor', 'lightTitleColor', 'lightOpacity', 'lightBgColor'].forEach(id => {
|
||
document.getElementById(id).addEventListener('input', () => updateLivePreview('light'));
|
||
});
|
||
|
||
['darkPrimaryColor', 'darkTitleColor', 'darkOpacity', 'darkBgColor'].forEach(id => {
|
||
document.getElementById(id).addEventListener('input', () => updateLivePreview('dark'));
|
||
});
|
||
|
||
function updateLivePreview(mode) {
|
||
const settings = {
|
||
light: {
|
||
primary_color: document.getElementById('lightPrimaryColor').value,
|
||
title_color: document.getElementById('lightTitleColor').value,
|
||
opacity: parseFloat(document.getElementById('lightOpacity').value),
|
||
bg_color: document.getElementById('lightBgColor').value
|
||
},
|
||
dark: {
|
||
primary_color: document.getElementById('darkPrimaryColor').value,
|
||
title_color: document.getElementById('darkTitleColor').value,
|
||
opacity: parseFloat(document.getElementById('darkOpacity').value),
|
||
bg_color: document.getElementById('darkBgColor').value
|
||
}
|
||
};
|
||
|
||
if (mode === 'light') {
|
||
document.querySelector('#lightOpacity + .opacity-value').textContent = `${Math.round(settings.light.opacity * 100)}%`;
|
||
} else {
|
||
document.querySelector('#darkOpacity + .opacity-value').textContent = `${Math.round(settings.dark.opacity * 100)}%`;
|
||
}
|
||
|
||
// 更新预览但不保存
|
||
currentThemeSettings = settings;
|
||
applyCurrentThemeSettings();
|
||
}
|
||
|
||
// 监听主题切换事件
|
||
document.addEventListener('themeChanged', () => {
|
||
if (currentThemeSettings) {
|
||
applyCurrentThemeSettings();
|
||
}
|
||
});
|
||
|
||
// 监听登录状态变化
|
||
document.addEventListener('loginStatusChanged', loadThemeSettings);
|
||
|
||
// 初始化加载主题设置
|
||
loadThemeSettings();
|
||
}
|
||
|
||
// 显示通知
|
||
function showNotification(message, type = 'success') {
|
||
const notification = document.createElement('div');
|
||
notification.className = `notification ${type}`;
|
||
notification.textContent = message;
|
||
document.body.appendChild(notification);
|
||
|
||
setTimeout(() => {
|
||
notification.classList.add('fade-out');
|
||
setTimeout(() => notification.remove(), 300);
|
||
}, 3000);
|
||
}
|
||
|
||
// 备用的复制方法
|
||
function fallbackCopyText(text) {
|
||
const textarea = document.createElement('textarea');
|
||
textarea.value = text;
|
||
document.body.appendChild(textarea);
|
||
textarea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textarea);
|
||
showNotification('已复制到剪贴板');
|
||
}
|
||
|
||
// 初始化
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
loadData();
|
||
loadSettings();
|
||
setupSearch();
|
||
setupThemeSwitcher();
|
||
setupCompactToggle();
|
||
setupAdminButton();
|
||
setupBackToTop();
|
||
setupContextMenu();
|
||
setupThemeSettings();
|
||
});
|
||
</script>
|
||
<!-- 添加通知样式 -->
|
||
<style>
|
||
.notification {
|
||
position: fixed;
|
||
bottom: 20px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
padding: 12px 24px;
|
||
border-radius: 4px;
|
||
background-color: #28a745;
|
||
color: white;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
z-index: 1100;
|
||
animation: slideIn 0.3s ease-out;
|
||
}
|
||
|
||
.notification.error {
|
||
background-color: #dc3545;
|
||
}
|
||
|
||
.notification.fade-out {
|
||
animation: fadeOut 0.3s ease-out;
|
||
opacity: 0;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(-50%) translateY(100%);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(-50%) translateY(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes fadeOut {
|
||
from {
|
||
opacity: 1;
|
||
}
|
||
to {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
</style>
|
||
</body>
|
||
</html> |