AIDaohang/templates/index.html

2719 lines
89 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>