变迁左右滑动、自定义页脚等功能,应用编辑图标BUG修复

This commit is contained in:
王志珏 2025-07-07 20:08:59 +08:00
parent ee114dda50
commit e9a755fbe1
10 changed files with 1531 additions and 1242 deletions

View File

@ -14,29 +14,28 @@
### 界面优化
1. 每行卡片数量支持自定义4个、5个、6个、8个
2. 应用的图标支持上传图片自定义 - 已完成
3. 首页增加页脚
4. 一级分类和二级分类固定宽度,超出宽度可左右滑动查看
3. 自定义页脚 - 已完成
4. 一级分类和二级分类固定宽度,超出宽度可左右滑动查看 - 已完成
5. 气泡的小箭头靠左对齐 - 已完成
6. logo图标设置区分明亮和暗黑模式
7. 私有应用在首页添加标识
8. 首页卡片右键菜单
9. 应用支持配置多个URL左键打开默认URL右键可选择URL进行复制地址或者打开或者编辑应用
10. 新增应用界面便捷增加分类
11. 新增应用界面便捷增加图标图片
### 功能增强
8. 应用管理页支持分页和按一级分类筛选
9. 应用分页和附件分页功能
10. 网站图标自动获取功能
11. 书签收藏工具
1. 应用管理页支持分页和按一级分类筛选 - 已完成
2. 应用分页和附件分页功能 - 已完成
3. 网站图标自动获取功能
4. 书签收藏工具
### BUG修复
12. 应用编辑页面没有回显带入图片
1. 应用编辑页面没有回显带入图片 - 已解决
### 批量操作
13. 应用批量选择功能:
1. 应用批量选择功能:
- 批量删除
- 批量设置私有化/公有化
14. 附件批量选择功能:
2. 附件批量选择功能:
- 批量删除
15. 新增应用界面便捷增加分类
16. 新增应用界面便捷增加图标图片

87
app.py
View File

@ -11,6 +11,7 @@ from functools import wraps
from werkzeug.utils import secure_filename
import requests
from urllib.parse import urlparse
from math import ceil
app = Flask(__name__)
app.secret_key = 'your_secret_key_here' # 请更改为安全的密钥
@ -44,6 +45,7 @@ SETTINGS_FILE = os.path.join(DATA_DIR, 'settings.json')
GUEST_SETTINGS_FILE = os.path.join(DATA_DIR, 'guest_settings.json')
def migrate_settings(settings):
"""迁移旧版设置到新版格式"""
if 'admin_password' in settings:
@ -59,6 +61,12 @@ def migrate_settings(settings):
'123456',
method='pbkdf2:sha256'
)
# 确保有页脚设置
if 'footer_html' not in settings:
settings[
'footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
return settings
@ -78,7 +86,8 @@ def init_settings():
"logo_icon": "fa-th-list",
"logo_image": "",
"uploaded_backgrounds": [],
"uploaded_logos": []
"uploaded_logos": [],
"footer_html": '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
}
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(default_settings, f, ensure_ascii=False, indent=2)
@ -90,6 +99,14 @@ def init_settings():
if 'admin_password_hash' not in settings:
# 需要迁移
settings = migrate_settings(settings)
# 添加新字段的默认值
if 'footer_html' not in settings:
settings['footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(settings, f, ensure_ascii=False, indent=2)
elif 'footer_html' not in settings:
# 已有密码哈希但没有页脚设置的情况
settings['footer_html'] = '<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>'
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(settings, f, ensure_ascii=False, indent=2)
@ -356,6 +373,7 @@ def system_settings():
settings['site_title'] = request.form.get('site_title', '应用导航中心')
settings['show_logo'] = 'show_logo' in request.form
settings['logo_type'] = request.form.get('logo_type', 'icon')
settings['footer_html'] = request.form.get('footer_html', '')
# 处理logo设置
if settings['logo_type'] == 'icon':
@ -467,9 +485,38 @@ def system_settings():
@app.route('/attachments')
@login_required
def manage_attachments():
type_filter = request.args.get('type', 'all')
search_query = request.args.get('search', '').lower()
page = int(request.args.get('page', 1))
per_page = 10 # 每页显示10条数据
settings = load_settings()
attachments = load_attachments()
return render_template('attachments.html', settings=settings, attachments=attachments)
# 应用筛选
filtered_attachments = []
for attachment in attachments:
# 类型筛选
if type_filter != 'all' and attachment['type'] != type_filter:
continue
# 搜索筛选
if search_query and search_query not in attachment['filename'].lower():
continue
filtered_attachments.append(attachment)
# 分页处理
total_pages = ceil(len(filtered_attachments) / per_page)
paginated_attachments = filtered_attachments[(page - 1) * per_page: page * per_page]
return render_template('attachments.html',
settings=settings,
attachments=paginated_attachments,
current_page=page,
total_pages=total_pages,
search_query=search_query,
type_filter=type_filter)
@app.route('/upload_attachment', methods=['POST'])
@ -573,21 +620,45 @@ def handle_settings():
@login_required
def index():
category_filter = request.args.get('category')
search_query = request.args.get('search', '').lower()
page = int(request.args.get('page', 1))
per_page = 10 # 每页显示10条数据
apps = load_apps()
categories = load_categories()
settings = load_settings()
if category_filter:
# 应用筛选
filtered_apps = []
for app in apps:
# 检查是否匹配主分类或子分类
if (app['category']['main'] == category_filter or
# 分类筛选
if category_filter and not (app['category']['main'] == category_filter or
app['category']['sub'] == category_filter or
(category_filter in categories and app['category']['main'] == category_filter)):
filtered_apps.append(app)
apps = filtered_apps
continue
return render_template('manage.html', apps=apps, settings=settings, categories=categories)
# 搜索筛选
if search_query and not (search_query in app['title'].lower() or
search_query in app['url'].lower() or
search_query in app.get('description', '').lower() or
search_query in app['category']['main'].lower() or
search_query in app['category']['sub'].lower()):
continue
filtered_apps.append(app)
# 分页处理
total_pages = ceil(len(filtered_apps) / per_page)
paginated_apps = filtered_apps[(page - 1) * per_page: page * per_page]
return render_template('manage.html',
apps=paginated_apps,
settings=settings,
categories=categories,
current_page=page,
total_pages=total_pages,
search_query=search_query,
category_filter=category_filter)
@app.route('/')
def navigation():

View File

@ -8,7 +8,8 @@
"show_logo": true,
"logo_type": "image",
"logo_icon": "fa-solid fa-th-list",
"logo_image": "/upload/logo/5378dda810964da9a7515ec844628738.png",
"logo_image": "/upload/logo/b2c128cf2d4e47daa349c5e7f38c932c.png",
"dark_bg_rotate": false,
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb"
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb",
"footer_html": "<div class=\"flex flex-col items-center text-slate-400 text-sm py-4\" style=\"margin-top:100px\">\r\n <div class=\"flex items-center\">\r\n <span>© 2023 AIDaohang. All rights reserved.</span>\r\n <span class=\"mx-2\">|</span>\r\n <span>Powered by <a href=\"https://github.com\" target=\"_blank\" class=\"hover:text-slate-200 ml-1\">AIDaohang</a></span>\r\n </div>\r\n</div>"
}

View File

@ -32,7 +32,25 @@
</div>
<div class="mb-4">
<h5 class="border-bottom pb-2 mb-3">附件列表</h5>
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="border-bottom pb-2 mb-0">附件列表</h5>
<form class="d-flex" method="GET">
<select name="type" class="form-select me-2" style="width: 120px;">
<option value="all" {% if type_filter == 'all' %}selected{% endif %}>所有类型</option>
<option value="logo" {% if type_filter == 'logo' %}selected{% endif %}>Logo</option>
<option value="background" {% if type_filter == 'background' %}selected{% endif %}>背景图片</option>
<option value="video" {% if type_filter == 'video' %}selected{% endif %}>背景视频</option>
<option value="icon" {% if type_filter == 'icon' %}selected{% endif %}>图标图片</option>
</select>
<input type="text" name="search" class="form-control me-2" placeholder="搜索文件名..." value="{{ search_query }}">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search"></i>
</button>
{% if type_filter != 'all' or search_query %}
<a href="{{ url_for('manage_attachments') }}" class="btn btn-secondary ms-2">重置</a>
{% endif %}
</form>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
@ -83,12 +101,37 @@
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center">暂无附件</td>
<td colspan="5" class="text-center">没有找到匹配的附件</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- 分页导航 -->
{% if total_pages > 1 %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('manage_attachments', page=current_page-1, type=type_filter, search=search_query) }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% for page_num in range(1, total_pages + 1) %}
<li class="page-item {% if page_num == current_page %}active{% endif %}">
<a class="page-link" href="{{ url_for('manage_attachments', page=page_num, type=type_filter, search=search_query) }}">{{ page_num }}</a>
</li>
{% endfor %}
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('manage_attachments', page=current_page+1, type=type_filter, search=search_query) }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
{% endif %}
</div>
<div class="mt-4">
<a href="{{ url_for('index') }}" class="btn btn-secondary">

View File

@ -13,7 +13,7 @@
<link rel="stylesheet" href="/static/css/all.min.css">
<style>
body {
padding-top: 20px; /* 修改了这里 */
padding-top: 0px;
padding-bottom: 40px;
}
.navbar {
@ -121,7 +121,7 @@
{% block content %}{% endblock %}
<footer class="footer">
<p>© 2025 导航管理系统</p>
{% include 'footer.html' %}
</footer>
</div>

View File

@ -17,14 +17,19 @@
<label class="form-label">URL</label>
<input type="url" name="url" class="form-control" value="{{ app.url }}" required>
</div>
<!-- 在图标选择部分修改 -->
<!-- 修改后的图标选择部分 -->
<div class="mb-3">
<label class="form-label">图标</label>
<input type="hidden" name="icon" id="selectedIcon" value="" required>
<input type="hidden" name="icon" id="selectedIcon" value="{{ app.icon }}" required>
<div class="d-flex align-items-center mb-3">
<div class="icon-preview me-3">
<i id="iconPreview" class="fas fa-question-circle fa-2x"></i>
{% if app.icon.startswith('/upload/icon/') %}
<img id="iconImagePreview" src="{{ app.icon }}" style="max-width: 32px; max-height: 32px;">
<i id="iconPreview" class="fas fa-question-circle fa-2x" style="display: none;"></i>
{% else %}
<i id="iconPreview" class="{{ app.icon }} fa-2x"></i>
<img id="iconImagePreview" src="" style="display: none; max-width: 32px; max-height: 32px;">
{% endif %}
</div>
<button type="button" class="btn btn-outline-primary me-2" data-bs-toggle="modal" data-bs-target="#iconModal">
<i class="fas fa-icons me-2"></i>选择图标
@ -35,7 +40,7 @@
</div>
</div>
<!-- 添加图标图片选择模态框 -->
<!-- 图标图片选择模态框 -->
<div class="modal fade" id="iconImageModal" tabindex="-1" aria-labelledby="iconImageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
@ -205,7 +210,10 @@
document.getElementById('confirmIcon').addEventListener('click', function() {
if (selectedIcon) {
document.getElementById('selectedIcon').value = selectedIcon;
document.getElementById('iconPreview').className = selectedIcon + ' fa-2x';
const iconPreview = document.getElementById('iconPreview');
iconPreview.className = selectedIcon + ' fa-2x';
iconPreview.style.display = 'inline';
document.getElementById('iconImagePreview').style.display = 'none';
bootstrap.Modal.getInstance(document.getElementById('iconModal')).hide();
} else {
alert('请先选择一个图标');
@ -238,16 +246,12 @@
});
// 初始化时高亮已选图标
if (selectedIcon) {
if (selectedIcon && !selectedIcon.startsWith('/upload/icon/')) {
const selectedItem = document.querySelector(`.icon-item[data-icon="${selectedIcon}"]`);
if (selectedItem) {
selectedItem.classList.add('selected');
}
}
});
// 图标图片选择功能
let selectedIconImage = '';
let selectedIconImageName = '';
// 图标图片搜索功能
document.getElementById('iconImageSearch').addEventListener('input', function() {
@ -285,16 +289,18 @@ document.querySelectorAll('.icon-image-item').forEach(item => {
// 确认选择图标图片
document.getElementById('confirmIconImage').addEventListener('click', function() {
if (selectedIconImage) {
document.getElementById('selectedIcon').value = '/upload/icon/' + selectedIconImage;
document.getElementById('iconPreview').style.display = 'none';
const iconPath = '/upload/icon/' + selectedIconImage;
document.getElementById('selectedIcon').value = iconPath;
const iconImagePreview = document.getElementById('iconImagePreview');
iconImagePreview.src = '/upload/icon/' + selectedIconImage;
iconImagePreview.src = iconPath;
iconImagePreview.style.display = 'inline';
document.getElementById('iconPreview').style.display = 'none';
bootstrap.Modal.getInstance(document.getElementById('iconImageModal')).hide();
} else {
alert('请先选择一张图片');
}
});
});
</script>
<style>
@ -325,12 +331,29 @@ document.getElementById('confirmIconImage').addEventListener('click', function()
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
#iconGrid {
.icon-image-item {
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
}
.icon-image-item:hover {
background-color: #f8f9fa;
border-color: #dee2e6;
}
.icon-image-item.selected {
background-color: #e7f1ff;
border-color: #86b7fe;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
#iconGrid, #iconImageGrid {
max-height: 60vh;
overflow-y: auto;
}
#selectedIconName {
#selectedIconName, #selectedIconImageName {
font-weight: bold;
color: #0d6efd;
}

6
templates/footer.html Normal file
View File

@ -0,0 +1,6 @@
<!-- templates/footer.html -->
{% if settings and settings.footer_html %}
{{ settings.footer_html|safe }}
{% else %}
<div class="flex justify-center text-slate-300" style="margin-top:100px">Powered By <a href="https://github.com" target="_blank" class="ml-[5px]">AIDaohang</a></div>
{% endif %}

View File

@ -22,8 +22,8 @@
--ai-color: #f8961e;
--tooltip-bg: rgb(149 236 105);
--tooltip-text: black;
--bg-image: none; /* 修改为none */
--dark-bg-image: none; /* 修改为none */
--bg-image: none;
--dark-bg-image: none;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
@ -307,7 +307,7 @@ body.dark-theme .app-url {
color: #aaa !important;
}
/* 修改后的筛选容器样式 - 支持横向滚动 */
.filter-container {
display: flex;
flex-direction: column;
@ -324,12 +324,36 @@ body.dark-theme .app-url {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease, color 0.3s ease;
}
/* 修改后的筛选行样式 - 支持横向滚动 */
.filter-row {
display: flex;
flex-wrap: wrap;
flex-wrap: nowrap;
gap: 10px;
justify-content: center;
overflow-x: auto;
padding-bottom: 10px;
scrollbar-width: thin;
scrollbar-color: var(--primary-color) transparent;
justify-content: center; /* 默认居中 */
padding: 0 20px; /* 添加内边距 */
}
/* 当内容超出时靠左 */
.filter-row.scrollable {
justify-content: flex-start;
}
/* 自定义滚动条样式 */
.filter-row::-webkit-scrollbar {
height: 6px;
}
.filter-row::-webkit-scrollbar-track {
background: transparent;
}
.filter-row::-webkit-scrollbar-thumb {
background-color: var(--primary-color);
border-radius: 3px;
}
.filter-btn {
padding: 8px 16px;
border-radius: 20px;
@ -340,6 +364,8 @@ body.dark-theme .app-url {
font-size: 14px;
display: flex;
align-items: center;
white-space: nowrap; /* 禁止按钮内文字换行 */
flex-shrink: 0; /* 禁止按钮缩小 */
}
.filter-btn:hover {
transform: translateY(-2px);
@ -383,22 +409,45 @@ body.dark-theme .app-url {
.caret.down {
transform: rotate(90deg);
}
/* 修改后的二级筛选容器样式 - 支持横向滚动 */
.secondary-filters-container {
width: 100%;
display: none;
flex-wrap: wrap;
flex-wrap: nowrap;
gap: 10px;
justify-content: center;
padding: 10px 0;
transition: background-color 0.3s ease;
overflow-x: auto;
padding-bottom: 10px;
scrollbar-width: thin;
scrollbar-color: var(--primary-color) transparent;
justify-content: center; /* 默认居中 */
padding: 0 20px; /* 添加内边距 */
}
/* 当内容超出时靠左 */
.secondary-filters-container.scrollable {
justify-content: flex-start;
}
/* 自定义二级筛选容器的滚动条样式 */
.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: 50px;
min-height: 30px;
}
.app-list-container {
margin-top: 20px;
@ -477,7 +526,7 @@ body.dark-theme .app-url {
font-size: 18px;
color: var(--primary-color);
transition: background-color 0.3s ease;
overflow: hidden; /* 添加这行确保内容不会溢出 */
overflow: hidden;
}
.app-icon img {
@ -486,7 +535,7 @@ body.dark-theme .app-url {
width: auto;
height: auto;
object-fit: contain;
margin: 0 !important; /* 修改margin为0 */
margin: 0 !important;
}
.app-info {
flex: 1;
@ -561,8 +610,8 @@ body.dark-theme .app-url {
.app-description {
position: absolute;
bottom: calc(100% + 10px);
left: 20px; /* 调整左侧位置 */
transform: none; /* 移除水平居中转换 */
left: 20px;
transform: none;
background-color: var(--tooltip-bg);
color: var(--tooltip-text);
padding: 10px 15px;
@ -583,8 +632,8 @@ body.dark-theme .app-url {
content: '';
position: absolute;
top: 100%;
left: 20px; /* 调整与气泡对齐 */
transform: none; /* 移除水平居中转换 */
left: 20px;
transform: none;
border-width: 8px;
border-style: solid;
border-color: var(--tooltip-bg) transparent transparent transparent;
@ -592,6 +641,13 @@ body.dark-theme .app-url {
.app-item:hover .app-description {
opacity: 1;
}
.footer {
margin-top: 40px;
padding: 20px 0;
border-top: 1px solid #eee;
text-align: center;
color: #777;
}
</style>
</head>
<body>
@ -643,6 +699,9 @@ body.dark-theme .app-url {
<button class="floating-btn" id="compactToggle" title="简洁模式">📱</button>
</div>
<footer class="footer">
{% include 'footer.html' %}
</footer>
<script>
// 全局变量存储应用和分类数据
let apps = [];
@ -791,7 +850,6 @@ primaryFilters.querySelector('[data-filter="all"]').textContent = '全部';
}
}
// 更新登录按钮状态
function updateLoginButton() {
if (isLoggedIn) {
@ -1259,7 +1317,7 @@ function applySettings() {
// 应用主题
document.body.classList.remove('dark-theme', 'light-theme');
if (settings.theme === 'dark' ||
(settings.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
(settings.theme === 'auto' && window.matchMedia('(prefers-color-sscheme: dark)').matches)) {
document.body.classList.add('dark-theme');
// 显示/隐藏视频背景
@ -1431,6 +1489,28 @@ document.addEventListener('DOMContentLoaded', () => {
setupCompactToggle();
setupAdminButton();
});
function checkScrollable() {
document.querySelectorAll('.filter-row, .secondary-filters-container').forEach(container => {
// 检查内容宽度是否大于容器宽度
const isScrollable = container.scrollWidth > container.clientWidth;
if(isScrollable) {
container.classList.add('scrollable');
} else {
container.classList.remove('scrollable');
}
});
}
// 初始化时检查
window.addEventListener('load', checkScrollable);
// 窗口大小改变时检查
window.addEventListener('resize', checkScrollable);
// 内容变化时检查(如筛选器更新后)
new MutationObserver(checkScrollable).observe(document.body, {
childList: true,
subtree: true
});
</script>
</body>
</html>

View File

@ -10,6 +10,15 @@
<i class="fas fa-plus"></i> 添加应用
</a>
</div>
<div>
<form class="d-flex" method="GET">
<input type="hidden" name="category" value="{{ category_filter }}">
<input type="text" name="search" class="form-control form-control-sm me-2" placeholder="搜索应用..." value="{{ search_query }}">
<button type="submit" class="btn btn-sm btn-light">
<i class="fas fa-search"></i>
</button>
</form>
</div>
</div>
<div class="card-body">
<div class="mb-3">
@ -31,6 +40,9 @@
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">筛选</button>
{% if category_filter or search_query %}
<a href="{{ url_for('index') }}" class="btn btn-secondary ms-2">重置</a>
{% endif %}
</div>
</form>
</div>
@ -86,10 +98,39 @@
</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center">没有找到匹配的应用</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- 分页导航 -->
{% if total_pages > 1 %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('index', page=current_page-1, category=category_filter, search=search_query) }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% for page_num in range(1, total_pages + 1) %}
<li class="page-item {% if page_num == current_page %}active{% endif %}">
<a class="page-link" href="{{ url_for('index', page=page_num, category=category_filter, search=search_query) }}">{{ page_num }}</a>
</li>
{% endfor %}
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('index', page=current_page+1, category=category_filter, search=search_query) }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -280,6 +280,27 @@
</div>
</div>
<!-- 页脚设置 -->
<div class="card mb-4 border-light shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-code me-2"></i>页脚设置</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="footer_html" class="form-label">自定义页脚HTML代码</label>
<textarea class="form-control" id="footer_html" name="footer_html" rows="4">{{ settings.footer_html if settings.footer_html else '' }}</textarea>
<div class="form-text">支持HTML代码可用于添加版权信息、统计代码等</div>
</div>
<div class="preview-area mt-3 p-3 bg-light rounded">
<h6>预览效果:</h6>
<div id="footer_preview">
{{ settings.footer_html|safe if settings.footer_html else '' }}
</div>
</div>
</div>
</div>
<!-- 修改密码 -->
<div class="card mb-4 border-light shadow-sm">
<div class="card-header bg-light">
@ -464,6 +485,10 @@
</div>
<script>
// 页脚预览
document.getElementById('footer_html').addEventListener('input', function() {
document.getElementById('footer_preview').innerHTML = this.value;
});
// 全局选择函数
function selectLogo(filename) {
document.getElementById('selected_logo').value = filename;