非常多的优化
This commit is contained in:
parent
c665cf19bf
commit
3b6fb7de68
10
README.md
10
README.md
@ -2,9 +2,9 @@
|
|||||||
1、首页标题支持自定义 - 已完成
|
1、首页标题支持自定义 - 已完成
|
||||||
2、每行卡片数量支持自定义,4个、5个、6个、8个
|
2、每行卡片数量支持自定义,4个、5个、6个、8个
|
||||||
3、背景图片设置支持从已上传图片中选择,选择的时候提供预览 - 已完成
|
3、背景图片设置支持从已上传图片中选择,选择的时候提供预览 - 已完成
|
||||||
4、应用的图标也支持上传图片自定义;
|
4、应用的图标也支持上传图片自定义 - 已完成
|
||||||
5、增加附件管理页面,管理上传的图片,包括应用图标图片和背景图片 - 已完成
|
5、增加附件管理页面,管理上传的图片,包括应用图标图片和背景图片 - 已完成
|
||||||
6、首页增加页脚;
|
6、首页增加页脚 -
|
||||||
7、首页悬浮气泡内的文字改为居左 - 已完成
|
7、首页悬浮气泡内的文字改为居左 - 已完成
|
||||||
8、一级分类和二级分类固定宽度,超出宽度左右滚轮滑动查看;
|
8、一级分类和二级分类固定宽度,超出宽度左右滚轮滑动查看;
|
||||||
9、背景支持视频 - 已完成
|
9、背景支持视频 - 已完成
|
||||||
@ -19,4 +19,8 @@
|
|||||||
18、应用批量选择,批量删除,批量私有化、公有化
|
18、应用批量选择,批量删除,批量私有化、公有化
|
||||||
19、附件批量选择、批量删除
|
19、附件批量选择、批量删除
|
||||||
20、网站图标自动获取
|
20、网站图标自动获取
|
||||||
21、书签收藏工具
|
21、书签收藏工具
|
||||||
|
22、忘记密码
|
||||||
|
23、分类管理页面显示权重并按权重排序
|
||||||
|
24、登录失败保留用户输入 - 已完成
|
||||||
|
25、私有化BUG
|
||||||
93
app.py
93
app.py
@ -29,6 +29,9 @@ os.makedirs(BACKGROUND_FOLDER, exist_ok=True)
|
|||||||
VIDEO_FOLDER = os.path.join(UPLOAD_FOLDER, 'video')
|
VIDEO_FOLDER = os.path.join(UPLOAD_FOLDER, 'video')
|
||||||
os.makedirs(VIDEO_FOLDER, exist_ok=True)
|
os.makedirs(VIDEO_FOLDER, exist_ok=True)
|
||||||
|
|
||||||
|
ICON_FOLDER = os.path.join(UPLOAD_FOLDER, 'icon')
|
||||||
|
os.makedirs(ICON_FOLDER, exist_ok=True)
|
||||||
|
|
||||||
FONT_PATH = os.path.join(BASE_DIR, 'static', 'webfonts', 'arial.ttf')
|
FONT_PATH = os.path.join(BASE_DIR, 'static', 'webfonts', 'arial.ttf')
|
||||||
|
|
||||||
# 允许的文件扩展名
|
# 允许的文件扩展名
|
||||||
@ -39,6 +42,7 @@ ATTACHMENTS_FILE = os.path.join(DATA_DIR, 'attachments.json')
|
|||||||
# 系统设置文件路径
|
# 系统设置文件路径
|
||||||
SETTINGS_FILE = os.path.join(DATA_DIR, 'settings.json')
|
SETTINGS_FILE = os.path.join(DATA_DIR, 'settings.json')
|
||||||
|
|
||||||
|
GUEST_SETTINGS_FILE = os.path.join(DATA_DIR, 'guest_settings.json')
|
||||||
|
|
||||||
def migrate_settings(settings):
|
def migrate_settings(settings):
|
||||||
"""迁移旧版设置到新版格式"""
|
"""迁移旧版设置到新版格式"""
|
||||||
@ -91,6 +95,40 @@ def init_settings():
|
|||||||
|
|
||||||
init_settings()
|
init_settings()
|
||||||
|
|
||||||
|
def init_guest_settings():
|
||||||
|
"""初始化游客设置文件"""
|
||||||
|
if not os.path.exists(GUEST_SETTINGS_FILE):
|
||||||
|
default_guest_settings = {
|
||||||
|
"theme": "auto",
|
||||||
|
"card_style": "normal",
|
||||||
|
"bg_image": "none",
|
||||||
|
"dark_bg_image": "none"
|
||||||
|
}
|
||||||
|
with open(GUEST_SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(default_guest_settings, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
init_guest_settings()
|
||||||
|
|
||||||
|
def load_guest_settings():
|
||||||
|
"""加载游客设置"""
|
||||||
|
with open(GUEST_SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def save_guest_settings(data):
|
||||||
|
"""保存游客设置"""
|
||||||
|
with open(GUEST_SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
@app.route('/api/guest_settings', methods=['GET', 'POST'])
|
||||||
|
def handle_guest_settings():
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'username' not in session:
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 401
|
||||||
|
data = request.json
|
||||||
|
save_guest_settings(data)
|
||||||
|
return jsonify({'status': 'success'})
|
||||||
|
else:
|
||||||
|
return jsonify(load_guest_settings())
|
||||||
|
|
||||||
def load_settings():
|
def load_settings():
|
||||||
"""加载设置并确保包含密码哈希"""
|
"""加载设置并确保包含密码哈希"""
|
||||||
@ -245,14 +283,16 @@ def login():
|
|||||||
if username == 'admin' and 'admin_password_hash' in settings:
|
if username == 'admin' and 'admin_password_hash' in settings:
|
||||||
if check_password_hash(settings['admin_password_hash'], password):
|
if check_password_hash(settings['admin_password_hash'], password):
|
||||||
session['username'] = username
|
session['username'] = username
|
||||||
next_url = request.args.get('next', url_for('index'))
|
next_url = request.args.get('next', url_for('navigation')) # 默认跳转到首页
|
||||||
flash('登录成功', 'success')
|
flash('登录成功', 'success')
|
||||||
return redirect(next_url)
|
return redirect(next_url)
|
||||||
|
|
||||||
flash('用户名或密码错误', 'danger')
|
flash('用户名或密码错误', 'danger')
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|
||||||
return render_template('login.html')
|
# 获取next参数并传递给模板
|
||||||
|
next_page = request.args.get('next', '/')
|
||||||
|
return render_template('login.html', next=next_page)
|
||||||
|
|
||||||
@app.route('/logout')
|
@app.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
@ -305,6 +345,7 @@ def captcha():
|
|||||||
@login_required
|
@login_required
|
||||||
def system_settings():
|
def system_settings():
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
|
guest_settings = load_guest_settings() # 加载游客设置
|
||||||
attachments = load_attachments()
|
attachments = load_attachments()
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@ -366,6 +407,29 @@ def system_settings():
|
|||||||
elif dark_bg_type == 'none':
|
elif dark_bg_type == 'none':
|
||||||
settings['dark_bg_image'] = "none"
|
settings['dark_bg_image'] = "none"
|
||||||
|
|
||||||
|
# 更新游客设置
|
||||||
|
guest_settings['theme'] = request.form.get('guest_theme', 'auto')
|
||||||
|
guest_settings['card_style'] = request.form.get('guest_card_style', 'normal')
|
||||||
|
|
||||||
|
# 处理游客背景设置
|
||||||
|
guest_bg_type = request.form.get('guest_bg_type')
|
||||||
|
if guest_bg_type == 'none':
|
||||||
|
guest_settings['bg_image'] = 'none'
|
||||||
|
elif guest_bg_type == 'default':
|
||||||
|
guest_settings['bg_image'] = '/static/background_light.jpg'
|
||||||
|
else: # system
|
||||||
|
guest_settings['bg_image'] = settings.get('bg_image', '/static/background_light.jpg')
|
||||||
|
|
||||||
|
guest_dark_bg_type = request.form.get('guest_dark_bg_type')
|
||||||
|
if guest_dark_bg_type == 'none':
|
||||||
|
guest_settings['dark_bg_image'] = 'none'
|
||||||
|
elif guest_dark_bg_type == 'default':
|
||||||
|
guest_settings['dark_bg_image'] = '/static/background_dark.jpg'
|
||||||
|
else: # system
|
||||||
|
guest_settings['dark_bg_image'] = settings.get('dark_bg_image', '/static/background_dark.jpg')
|
||||||
|
|
||||||
|
save_guest_settings(guest_settings)
|
||||||
|
|
||||||
# 检查是否修改密码
|
# 检查是否修改密码
|
||||||
old_password = request.form.get('old_password')
|
old_password = request.form.get('old_password')
|
||||||
new_password = request.form.get('new_password')
|
new_password = request.form.get('new_password')
|
||||||
@ -392,7 +456,11 @@ def system_settings():
|
|||||||
# 从返回的settings中移除密码哈希
|
# 从返回的settings中移除密码哈希
|
||||||
settings_to_return = settings.copy()
|
settings_to_return = settings.copy()
|
||||||
settings_to_return.pop('admin_password_hash', None)
|
settings_to_return.pop('admin_password_hash', None)
|
||||||
return render_template('settings.html', settings=settings_to_return, icons=load_icons(), attachments=attachments)
|
return render_template('settings.html',
|
||||||
|
settings=settings_to_return,
|
||||||
|
guest_settings=guest_settings,
|
||||||
|
icons=load_icons(),
|
||||||
|
attachments=attachments)
|
||||||
|
|
||||||
|
|
||||||
# 添加附件管理路由
|
# 添加附件管理路由
|
||||||
@ -426,8 +494,10 @@ def upload_attachment():
|
|||||||
folder = LOGO_FOLDER
|
folder = LOGO_FOLDER
|
||||||
elif file_type == 'background':
|
elif file_type == 'background':
|
||||||
folder = BACKGROUND_FOLDER
|
folder = BACKGROUND_FOLDER
|
||||||
else: # video
|
elif file_type == 'video':
|
||||||
folder = VIDEO_FOLDER
|
folder = VIDEO_FOLDER
|
||||||
|
else: # icon
|
||||||
|
folder = ICON_FOLDER
|
||||||
|
|
||||||
filepath = os.path.join(folder, filename)
|
filepath = os.path.join(folder, filename)
|
||||||
file.save(filepath)
|
file.save(filepath)
|
||||||
@ -444,6 +514,9 @@ def upload_attachment():
|
|||||||
def uploaded_video(filename):
|
def uploaded_video(filename):
|
||||||
return send_from_directory(VIDEO_FOLDER, filename)
|
return send_from_directory(VIDEO_FOLDER, filename)
|
||||||
|
|
||||||
|
@app.route('/upload/icon/<filename>')
|
||||||
|
def uploaded_icon(filename):
|
||||||
|
return send_from_directory(ICON_FOLDER, filename)
|
||||||
|
|
||||||
@app.route('/delete_attachment/<filename>', methods=['POST'])
|
@app.route('/delete_attachment/<filename>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@ -451,7 +524,7 @@ def delete_attachment_route(filename):
|
|||||||
try:
|
try:
|
||||||
# 检查文件是否存在
|
# 检查文件是否存在
|
||||||
file_found = False
|
file_found = False
|
||||||
for folder in [LOGO_FOLDER, BACKGROUND_FOLDER]:
|
for folder in [LOGO_FOLDER, BACKGROUND_FOLDER, VIDEO_FOLDER, ICON_FOLDER]:
|
||||||
filepath = os.path.join(folder, filename)
|
filepath = os.path.join(folder, filename)
|
||||||
if os.path.exists(filepath):
|
if os.path.exists(filepath):
|
||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
@ -554,6 +627,7 @@ def add_app():
|
|||||||
categories = load_categories()
|
categories = load_categories()
|
||||||
icons = load_icons()
|
icons = load_icons()
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
|
attachments = load_attachments()
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
title = request.form['title']
|
title = request.form['title']
|
||||||
@ -582,7 +656,11 @@ def add_app():
|
|||||||
|
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
return render_template('add_app.html', categories=categories, settings=settings, icons=icons)
|
return render_template('add_app.html',
|
||||||
|
categories=categories,
|
||||||
|
settings=settings,
|
||||||
|
icons=load_icons(),
|
||||||
|
attachments=attachments) # 添加attachments参数
|
||||||
|
|
||||||
@app.route('/app/edit/<int:index>', methods=['GET', 'POST'])
|
@app.route('/app/edit/<int:index>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@ -591,6 +669,7 @@ def edit_app(index):
|
|||||||
apps = load_apps()
|
apps = load_apps()
|
||||||
icons = load_icons()
|
icons = load_icons()
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
|
attachments = load_attachments()
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
apps[index]['title'] = request.form['title']
|
apps[index]['title'] = request.form['title']
|
||||||
@ -606,7 +685,7 @@ def edit_app(index):
|
|||||||
save_apps(apps)
|
save_apps(apps)
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
return render_template('edit_app.html', app=apps[index], index=index, settings=settings, categories=categories, icons=icons)
|
return render_template('edit_app.html', app=apps[index], index=index, settings=settings, categories=categories, icons=icons, attachments=attachments)
|
||||||
|
|
||||||
@app.route('/api/icons')
|
@app.route('/api/icons')
|
||||||
def get_icons():
|
def get_icons():
|
||||||
|
|||||||
@ -2,29 +2,32 @@
|
|||||||
{
|
{
|
||||||
"title": "SQL生成工具23",
|
"title": "SQL生成工具23",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=dbvns61a0glb2q6nwv9utd5v",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=dbvns61a0glb2q6nwv9utd5v",
|
||||||
"icon": "fa-solid fa-drumstick-bite",
|
"icon": "/upload/icon/c77f6bbbb33540d98a6cc088d01d7ad6.png",
|
||||||
"description": "一个强大的SQL查询生成工具,支持多种数据库",
|
"description": "一个强大的SQL查询生成工具,支持多种数据库,可快速生成复杂查询语句",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "dev",
|
"main": "dev",
|
||||||
"sub": "data"
|
"sub": "data"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true,
|
||||||
|
"icon_type": "image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Nginx配置生成",
|
"title": "Nginx配置生成",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=3340xwddpp3xi48g1evv7r4m",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=3340xwddpp3xi48g1evv7r4m",
|
||||||
"icon": "fa-server",
|
"icon": "/upload/icon/4e9c7320469f4e6887f63e1f59f32ca1.png",
|
||||||
"description": "快速生成Nginx配置文件",
|
"description": "快速生成Nginx配置文件,支持负载均衡、反向代理等常见配置",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "dev",
|
"main": "dev",
|
||||||
"sub": "web"
|
"sub": "web"
|
||||||
}
|
},
|
||||||
|
"icon_type": "image",
|
||||||
|
"private": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "动物的一生",
|
"title": "动物的一生",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=0e5kd8ged5w7oxrhfcztvt5b",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=0e5kd8ged5w7oxrhfcztvt5b",
|
||||||
"icon": "fa-paw",
|
"icon": "fa-paw",
|
||||||
"description": "了解各种动物的生命周期",
|
"description": "了解各种动物的生命周期、习性和栖息地,适合儿童科普教育",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "edu",
|
"main": "edu",
|
||||||
"sub": "science"
|
"sub": "science"
|
||||||
@ -33,18 +36,19 @@
|
|||||||
{
|
{
|
||||||
"title": "Docker转换工具",
|
"title": "Docker转换工具",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=9ylu7kn735vc7z69n3otryss",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=9ylu7kn735vc7z69n3otryss",
|
||||||
"icon": "fa-docker",
|
"icon": "fa-brands fa-docker",
|
||||||
"description": "将不同格式的容器配置相互转换",
|
"description": "将不同格式的容器配置相互转换,支持Docker Compose和Kubernetes",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "dev",
|
"main": "dev",
|
||||||
"sub": "ops"
|
"sub": "ops"
|
||||||
}
|
},
|
||||||
|
"private": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "日报工具",
|
"title": "日报工具",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=arylhdiwlr84qmzmublhlbjj",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=arylhdiwlr84qmzmublhlbjj",
|
||||||
"icon": "fa-calendar-day",
|
"icon": "fa-calendar-day",
|
||||||
"description": "123",
|
"description": "自动生成工作日报模板,帮助整理每日工作内容和计划",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "tool",
|
"main": "tool",
|
||||||
"sub": "office"
|
"sub": "office"
|
||||||
@ -54,7 +58,7 @@
|
|||||||
"title": "NFS-PV-PVC",
|
"title": "NFS-PV-PVC",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=szkw0erj0eukvu8uch2kznzm",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=szkw0erj0eukvu8uch2kznzm",
|
||||||
"icon": "fa-network-wired",
|
"icon": "fa-network-wired",
|
||||||
"description": "123",
|
"description": "Kubernetes中NFS存储的PV和PVC配置生成器",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "dev",
|
"main": "dev",
|
||||||
"sub": "cloud"
|
"sub": "cloud"
|
||||||
@ -64,7 +68,7 @@
|
|||||||
"title": "刑法助手",
|
"title": "刑法助手",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=6ot25ul3kli322rybodpring",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=6ot25ul3kli322rybodpring",
|
||||||
"icon": "fa-gavel",
|
"icon": "fa-gavel",
|
||||||
"description": "123",
|
"description": "提供刑法相关法律条文查询和案例分析",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "law",
|
"main": "law",
|
||||||
"sub": "criminal"
|
"sub": "criminal"
|
||||||
@ -74,7 +78,7 @@
|
|||||||
"title": "税法助手",
|
"title": "税法助手",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=rjazldnzqc1gr03k1giije7v",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=rjazldnzqc1gr03k1giije7v",
|
||||||
"icon": "fa-file-invoice-dollar",
|
"icon": "fa-file-invoice-dollar",
|
||||||
"description": "123",
|
"description": "查询最新税收政策和计算税费的工具",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "law",
|
"main": "law",
|
||||||
"sub": "tax"
|
"sub": "tax"
|
||||||
@ -84,7 +88,7 @@
|
|||||||
"title": "汉语新解",
|
"title": "汉语新解",
|
||||||
"url": "https://fastai.liuyan.wang/chat/share?shareId=lif7r64uc12rmkhgv51gxslh",
|
"url": "https://fastai.liuyan.wang/chat/share?shareId=lif7r64uc12rmkhgv51gxslh",
|
||||||
"icon": "fa-language",
|
"icon": "fa-language",
|
||||||
"description": "123",
|
"description": "探索汉语词汇的起源、演变和现代用法",
|
||||||
"category": {
|
"category": {
|
||||||
"main": "edu",
|
"main": "edu",
|
||||||
"sub": "language"
|
"sub": "language"
|
||||||
@ -98,7 +102,7 @@
|
|||||||
"main": "ai",
|
"main": "ai",
|
||||||
"sub": "image"
|
"sub": "image"
|
||||||
},
|
},
|
||||||
"description": "",
|
"description": "生成AI绘画所需的创意提示词和风格描述",
|
||||||
"private": false
|
"private": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -108,6 +112,29 @@
|
|||||||
"category": {
|
"category": {
|
||||||
"main": "ai",
|
"main": "ai",
|
||||||
"sub": "writing"
|
"sub": "writing"
|
||||||
|
},
|
||||||
|
"description": "辅助创作各类文本内容,包括文章、报告和创意写作"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "2222",
|
||||||
|
"url": "https://baidu.com",
|
||||||
|
"icon": "/upload/icon/c6379d5521f24ca789e6712008f115f5.png",
|
||||||
|
"description": "云计算相关工具和资源集合",
|
||||||
|
"private": false,
|
||||||
|
"category": {
|
||||||
|
"main": "dev",
|
||||||
|
"sub": "cloud"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "66666",
|
||||||
|
"url": "https://baidu.com",
|
||||||
|
"icon": "/upload/icon/65eb0f29c9e147e0924890e9ae12e94f.png",
|
||||||
|
"description": "AI图像处理和生成工具",
|
||||||
|
"private": false,
|
||||||
|
"category": {
|
||||||
|
"main": "ai",
|
||||||
|
"sub": "image"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -33,5 +33,30 @@
|
|||||||
"filename": "b23249a9681840329afb0c4af489fc30.mp4",
|
"filename": "b23249a9681840329afb0c4af489fc30.mp4",
|
||||||
"type": "video",
|
"type": "video",
|
||||||
"upload_time": "2025-07-05 21:02:17"
|
"upload_time": "2025-07-05 21:02:17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "4e9c7320469f4e6887f63e1f59f32ca1.png",
|
||||||
|
"type": "icon",
|
||||||
|
"upload_time": "2025-07-06 18:52:21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "65eb0f29c9e147e0924890e9ae12e94f.png",
|
||||||
|
"type": "icon",
|
||||||
|
"upload_time": "2025-07-06 19:03:04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "c6379d5521f24ca789e6712008f115f5.png",
|
||||||
|
"type": "icon",
|
||||||
|
"upload_time": "2025-07-06 20:21:54"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "7fb7491311f7477e9e3bd17150a929b7.png",
|
||||||
|
"type": "icon",
|
||||||
|
"upload_time": "2025-07-06 20:22:11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "c77f6bbbb33540d98a6cc088d01d7ad6.png",
|
||||||
|
"type": "icon",
|
||||||
|
"upload_time": "2025-07-06 20:22:28"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -29,7 +29,7 @@
|
|||||||
"data": true,
|
"data": true,
|
||||||
"web": true,
|
"web": true,
|
||||||
"ops": true,
|
"ops": true,
|
||||||
"cloud": true
|
"cloud": false
|
||||||
},
|
},
|
||||||
"weight": 99999
|
"weight": 99999
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"card_style": "compact",
|
"card_style": "compact",
|
||||||
"search_history": [],
|
"search_history": [],
|
||||||
"theme": "light",
|
"theme": "dark",
|
||||||
"bg_image": "/upload/background/5dd4f5d3cd7b48eca9967fa063ea5cd9.png",
|
"bg_image": "/upload/background/5dd4f5d3cd7b48eca9967fa063ea5cd9.png",
|
||||||
"dark_bg_image": "/static/background_dark.jpg",
|
"dark_bg_image": "/upload/background/d078c01de3be46deab9e85a94285d785.png",
|
||||||
"site_title": "应用导航",
|
"site_title": "应用导航",
|
||||||
"show_logo": true,
|
"show_logo": true,
|
||||||
"logo_type": "image",
|
"logo_type": "image",
|
||||||
"logo_icon": "fa-solid fa-th-list",
|
"logo_icon": "fa-solid fa-th-list",
|
||||||
"logo_image": "/upload/logo/f40e2eb965b24e358a5bba9523231f8f.png",
|
"logo_image": "/upload/logo/5378dda810964da9a7515ec844628738.png",
|
||||||
"dark_bg_rotate": false,
|
"dark_bg_rotate": false,
|
||||||
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb"
|
"admin_password_hash": "scrypt:32768:8:1$mPFCfRRzOrcjE6z3$e72ef50a2d3f7292f64bcfc5e21f32c95ea8665414ea8d5f6b216735d68f151166c99fae21132c7949bd92ea32041f969cd4a471adb110a99328089541f7dccb"
|
||||||
}
|
}
|
||||||
9
static/css/all.min.css
vendored
9
static/css/all.min.css
vendored
File diff suppressed because one or more lines are too long
@ -17,18 +17,64 @@
|
|||||||
<label class="form-label">URL</label>
|
<label class="form-label">URL</label>
|
||||||
<input type="url" name="url" class="form-control" required>
|
<input type="url" name="url" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<!-- 在图标选择部分修改 -->
|
||||||
<label class="form-label">图标</label>
|
<div class="mb-3">
|
||||||
<input type="hidden" name="icon" id="selectedIcon" value="" required>
|
<label class="form-label">图标</label>
|
||||||
<div class="d-flex align-items-center mb-3">
|
<input type="hidden" name="icon" id="selectedIcon" value="" required>
|
||||||
<div class="icon-preview me-3">
|
<div class="d-flex align-items-center mb-3">
|
||||||
<i id="iconPreview" class="fas fa-question-circle fa-2x"></i>
|
<div class="icon-preview me-3">
|
||||||
|
<i id="iconPreview" class="fas fa-question-circle fa-2x"></i>
|
||||||
|
<img id="iconImagePreview" src="" style="display: none; max-width: 32px; max-height: 32px;">
|
||||||
|
</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>选择图标
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#iconImageModal">
|
||||||
|
<i class="fas fa-image me-2"></i>选择图片
|
||||||
|
</button>
|
||||||
|
</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">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="iconImageModalLabel">选择图标图片</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
|
<input type="text" id="iconImageSearch" class="form-control" placeholder="搜索图标图片...">
|
||||||
|
</div>
|
||||||
|
<div class="row row-cols-3 row-cols-sm-4 row-cols-md-5 row-cols-lg-6 row-cols-xl-8 g-3" id="iconImageGrid">
|
||||||
|
{% for attachment in attachments if attachment.type == 'icon' %}
|
||||||
|
<div class="col">
|
||||||
|
<div class="icon-image-item p-3 text-center rounded" data-filename="{{ attachment.filename }}" title="{{ attachment.filename }}">
|
||||||
|
<img src="{{ url_for('uploaded_icon', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail mb-2">
|
||||||
|
<div class="small text-truncate">{{ attachment.filename }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12 text-center py-3">
|
||||||
|
<p>暂无图标图片</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#iconModal">
|
|
||||||
<i class="fas fa-icons me-2"></i>选择图标
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="me-auto">
|
||||||
|
<span id="selectedIconImageName">未选择图片</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="confirmIconImage">确认选择</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">主分类</label>
|
<label class="form-label">主分类</label>
|
||||||
<select name="main_category" id="main_category" class="form-select" required>
|
<select name="main_category" id="main_category" class="form-select" required>
|
||||||
@ -173,6 +219,57 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 图标图片选择功能
|
||||||
|
let selectedIconImage = '';
|
||||||
|
let selectedIconImageName = '';
|
||||||
|
|
||||||
|
// 图标图片搜索功能
|
||||||
|
document.getElementById('iconImageSearch').addEventListener('input', function() {
|
||||||
|
const searchTerm = this.value.toLowerCase();
|
||||||
|
const iconImageItems = document.querySelectorAll('.icon-image-item');
|
||||||
|
|
||||||
|
iconImageItems.forEach(item => {
|
||||||
|
const filename = item.dataset.filename.toLowerCase();
|
||||||
|
if (filename.includes(searchTerm)) {
|
||||||
|
item.closest('.col').style.display = 'block';
|
||||||
|
} else {
|
||||||
|
item.closest('.col').style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图标图片点击选择
|
||||||
|
document.querySelectorAll('.icon-image-item').forEach(item => {
|
||||||
|
item.addEventListener('click', function() {
|
||||||
|
// 移除所有选中状态
|
||||||
|
document.querySelectorAll('.icon-image-item').forEach(i => {
|
||||||
|
i.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加选中状态
|
||||||
|
this.classList.add('selected');
|
||||||
|
selectedIconImage = this.dataset.filename;
|
||||||
|
selectedIconImageName = this.getAttribute('title');
|
||||||
|
|
||||||
|
// 更新底部显示
|
||||||
|
document.getElementById('selectedIconImageName').textContent = selectedIconImageName;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 确认选择图标图片
|
||||||
|
document.getElementById('confirmIconImage').addEventListener('click', function() {
|
||||||
|
if (selectedIconImage) {
|
||||||
|
document.getElementById('selectedIcon').value = '/upload/icon/' + selectedIconImage;
|
||||||
|
document.getElementById('iconPreview').style.display = 'none';
|
||||||
|
const iconImagePreview = document.getElementById('iconImagePreview');
|
||||||
|
iconImagePreview.src = '/upload/icon/' + selectedIconImage;
|
||||||
|
iconImagePreview.style.display = 'inline';
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('iconImageModal')).hide();
|
||||||
|
} else {
|
||||||
|
alert('请先选择一张图片');
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
<option value="logo">Logo</option>
|
<option value="logo">Logo</option>
|
||||||
<option value="background">背景图片</option>
|
<option value="background">背景图片</option>
|
||||||
<option value="video">背景视频</option>
|
<option value="video">背景视频</option>
|
||||||
|
<option value="icon">图标图片</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
@ -51,10 +52,12 @@
|
|||||||
<img src="{{ url_for('uploaded_logo', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
<img src="{{ url_for('uploaded_logo', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
||||||
{% elif attachment.type == 'background' %}
|
{% elif attachment.type == 'background' %}
|
||||||
<img src="{{ url_for('uploaded_background', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
<img src="{{ url_for('uploaded_background', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
||||||
{% else %}
|
{% elif attachment.type == 'video' %}
|
||||||
<video width="80" height="45" muted style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
<video width="80" height="45" muted style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
||||||
<source src="{{ url_for('uploaded_video', filename=attachment.filename) }}" type="video/mp4">
|
<source src="{{ url_for('uploaded_video', filename=attachment.filename) }}" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
|
{% else %}
|
||||||
|
<img src="{{ url_for('uploaded_icon', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ attachment.filename }}</td>
|
<td>{{ attachment.filename }}</td>
|
||||||
@ -63,8 +66,10 @@
|
|||||||
Logo
|
Logo
|
||||||
{% elif attachment.type == 'background' %}
|
{% elif attachment.type == 'background' %}
|
||||||
背景图片
|
背景图片
|
||||||
{% else %}
|
{% elif attachment.type == 'video' %}
|
||||||
背景视频
|
背景视频
|
||||||
|
{% else %}
|
||||||
|
图标图片
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ attachment.upload_time }}</td>
|
<td>{{ attachment.upload_time }}</td>
|
||||||
|
|||||||
@ -17,18 +17,64 @@
|
|||||||
<label class="form-label">URL</label>
|
<label class="form-label">URL</label>
|
||||||
<input type="url" name="url" class="form-control" value="{{ app.url }}" required>
|
<input type="url" name="url" class="form-control" value="{{ app.url }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<!-- 在图标选择部分修改 -->
|
||||||
<label class="form-label">图标</label>
|
<div class="mb-3">
|
||||||
<input type="hidden" name="icon" id="selectedIcon" value="{{ app.icon if app.icon else '' }}" required>
|
<label class="form-label">图标</label>
|
||||||
<div class="d-flex align-items-center mb-3">
|
<input type="hidden" name="icon" id="selectedIcon" value="" required>
|
||||||
<div class="icon-preview me-3">
|
<div class="d-flex align-items-center mb-3">
|
||||||
<i id="iconPreview" class="{% if app.icon %}{{ app.icon }}{% else %}fas fa-question-circle{% endif %} fa-2x"></i>
|
<div class="icon-preview me-3">
|
||||||
|
<i id="iconPreview" class="fas fa-question-circle fa-2x"></i>
|
||||||
|
<img id="iconImagePreview" src="" style="display: none; max-width: 32px; max-height: 32px;">
|
||||||
|
</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>选择图标
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#iconImageModal">
|
||||||
|
<i class="fas fa-image me-2"></i>选择图片
|
||||||
|
</button>
|
||||||
|
</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">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="iconImageModalLabel">选择图标图片</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
|
<input type="text" id="iconImageSearch" class="form-control" placeholder="搜索图标图片...">
|
||||||
|
</div>
|
||||||
|
<div class="row row-cols-3 row-cols-sm-4 row-cols-md-5 row-cols-lg-6 row-cols-xl-8 g-3" id="iconImageGrid">
|
||||||
|
{% for attachment in attachments if attachment.type == 'icon' %}
|
||||||
|
<div class="col">
|
||||||
|
<div class="icon-image-item p-3 text-center rounded" data-filename="{{ attachment.filename }}" title="{{ attachment.filename }}">
|
||||||
|
<img src="{{ url_for('uploaded_icon', filename=attachment.filename) }}" style="max-width: 50px; max-height: 50px;" class="img-thumbnail mb-2">
|
||||||
|
<div class="small text-truncate">{{ attachment.filename }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12 text-center py-3">
|
||||||
|
<p>暂无图标图片</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#iconModal">
|
|
||||||
<i class="fas fa-icons me-2"></i>选择图标
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="me-auto">
|
||||||
|
<span id="selectedIconImageName">未选择图片</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="confirmIconImage">确认选择</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">主分类</label>
|
<label class="form-label">主分类</label>
|
||||||
<select name="main_category" id="main_category" class="form-select" required>
|
<select name="main_category" id="main_category" class="form-select" required>
|
||||||
@ -199,6 +245,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 图标图片选择功能
|
||||||
|
let selectedIconImage = '';
|
||||||
|
let selectedIconImageName = '';
|
||||||
|
|
||||||
|
// 图标图片搜索功能
|
||||||
|
document.getElementById('iconImageSearch').addEventListener('input', function() {
|
||||||
|
const searchTerm = this.value.toLowerCase();
|
||||||
|
const iconImageItems = document.querySelectorAll('.icon-image-item');
|
||||||
|
|
||||||
|
iconImageItems.forEach(item => {
|
||||||
|
const filename = item.dataset.filename.toLowerCase();
|
||||||
|
if (filename.includes(searchTerm)) {
|
||||||
|
item.closest('.col').style.display = 'block';
|
||||||
|
} else {
|
||||||
|
item.closest('.col').style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图标图片点击选择
|
||||||
|
document.querySelectorAll('.icon-image-item').forEach(item => {
|
||||||
|
item.addEventListener('click', function() {
|
||||||
|
// 移除所有选中状态
|
||||||
|
document.querySelectorAll('.icon-image-item').forEach(i => {
|
||||||
|
i.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加选中状态
|
||||||
|
this.classList.add('selected');
|
||||||
|
selectedIconImage = this.dataset.filename;
|
||||||
|
selectedIconImageName = this.getAttribute('title');
|
||||||
|
|
||||||
|
// 更新底部显示
|
||||||
|
document.getElementById('selectedIconImageName').textContent = selectedIconImageName;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 确认选择图标图片
|
||||||
|
document.getElementById('confirmIconImage').addEventListener('click', function() {
|
||||||
|
if (selectedIconImage) {
|
||||||
|
document.getElementById('selectedIcon').value = '/upload/icon/' + selectedIconImage;
|
||||||
|
document.getElementById('iconPreview').style.display = 'none';
|
||||||
|
const iconImagePreview = document.getElementById('iconImagePreview');
|
||||||
|
iconImagePreview.src = '/upload/icon/' + selectedIconImage;
|
||||||
|
iconImagePreview.style.display = 'inline';
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('iconImageModal')).hide();
|
||||||
|
} else {
|
||||||
|
alert('请先选择一张图片');
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -477,6 +477,16 @@ body.dark-theme .app-url {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
|
overflow: hidden; /* 添加这行确保内容不会溢出 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-icon img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
margin: 0 !important; /* 修改margin为0 */
|
||||||
}
|
}
|
||||||
.app-info {
|
.app-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -528,7 +538,7 @@ body.dark-theme .app-url {
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
color: #6c757d;
|
color: 6c757d;
|
||||||
}
|
}
|
||||||
.app-group.hidden {
|
.app-group.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
@ -626,12 +636,12 @@ body.dark-theme .app-url {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="floating-buttons">
|
<div class="floating-buttons">
|
||||||
<a href="/login" class="floating-btn" id="loginBtn" title="登录/退出"><i class="fas fa-sign-in-alt"></i></a>
|
<a href="/login?next=/" class="floating-btn" id="loginBtn" title="登录/退出"><i class="fas fa-sign-in-alt"></i></a>
|
||||||
<a href="/manage" class="floating-btn" id="adminBtn" title="后台管理"><i class="fas fa-cog"></i></a>
|
<a href="/manage" class="floating-btn" id="adminBtn" title="后台管理"><i class="fas fa-cog"></i></a>
|
||||||
<button class="floating-btn" id="themeToggle" title="切换主题">🌙</button>
|
<button class="floating-btn" id="themeToggle" title="切换主题">🌙</button>
|
||||||
<button class="floating-btn" id="compactToggle" title="简洁模式">📱</button>
|
<button class="floating-btn" id="compactToggle" title="简洁模式">📱</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 全局变量存储应用和分类数据
|
// 全局变量存储应用和分类数据
|
||||||
@ -788,10 +798,12 @@ function updateLoginButton() {
|
|||||||
loginBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
|
loginBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
|
||||||
loginBtn.href = '/logout';
|
loginBtn.href = '/logout';
|
||||||
loginBtn.title = '退出登录';
|
loginBtn.title = '退出登录';
|
||||||
|
adminBtn.href = '/manage'; // 已登录时直接跳转到管理页面
|
||||||
} else {
|
} else {
|
||||||
loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i>';
|
loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i>';
|
||||||
loginBtn.href = '/login';
|
loginBtn.href = '/login';
|
||||||
loginBtn.title = '登录';
|
loginBtn.title = '登录';
|
||||||
|
adminBtn.href = '/login?next=/manage'; // 未登录时跳转到登录页面
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1055,36 +1067,41 @@ function renderApps() {
|
|||||||
appList.className = 'app-list';
|
appList.className = 'app-list';
|
||||||
|
|
||||||
// 添加应用卡片
|
// 添加应用卡片
|
||||||
filteredGroupApps.forEach(app => {
|
filteredGroupApps.forEach(app => {
|
||||||
const subCatName = categories[app.category.main]?.sub[app.category.sub]?.name || app.category.sub;
|
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 subCatColor = categories[app.category.main]?.sub[app.category.sub]?.color || mainCatData.color;
|
||||||
|
|
||||||
const appItem = document.createElement('a');
|
const appItem = document.createElement('a');
|
||||||
appItem.className = `app-item ${settings.card_style === 'compact' ? 'compact' : ''}`;
|
appItem.className = `app-item ${settings.card_style === 'compact' ? 'compact' : ''}`;
|
||||||
appItem.href = app.url;
|
appItem.href = app.url;
|
||||||
appItem.target = '_blank';
|
appItem.target = '_blank';
|
||||||
|
|
||||||
// 添加聊天气泡描述提示框
|
// 添加聊天气泡描述提示框
|
||||||
const descriptionDiv = document.createElement('div');
|
const descriptionDiv = document.createElement('div');
|
||||||
descriptionDiv.className = 'app-description';
|
descriptionDiv.className = 'app-description';
|
||||||
descriptionDiv.textContent = app.description || '暂无描述';
|
descriptionDiv.textContent = app.description || '暂无描述';
|
||||||
|
|
||||||
appItem.innerHTML = `
|
// 判断是Font Awesome图标还是自定义图片
|
||||||
<div class="app-icon">
|
const iconHtml = app.icon.startsWith('/') || app.icon.startsWith('http') ?
|
||||||
<i class="fas ${app.icon}"></i>
|
`<img src="${app.icon}" alt="图标">` :
|
||||||
</div>
|
`<i class="fas ${app.icon}"></i>`;
|
||||||
<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);
|
appItem.innerHTML = `
|
||||||
appList.appendChild(appItem);
|
<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);
|
groupDiv.appendChild(appList);
|
||||||
appListContainer.appendChild(groupDiv);
|
appListContainer.appendChild(groupDiv);
|
||||||
@ -1126,7 +1143,7 @@ let settings = {
|
|||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
try {
|
try {
|
||||||
// 检查是否登录
|
// 检查是否登录
|
||||||
const isLoggedIn = await checkLoginStatus();
|
isLoggedIn = await checkLoginStatus();
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
// 如果已登录,从服务器获取系统设置
|
// 如果已登录,从服务器获取系统设置
|
||||||
@ -1141,19 +1158,27 @@ async function loadSettings() {
|
|||||||
|
|
||||||
setBackgroundImages(lightBg, darkBg);
|
setBackgroundImages(lightBg, darkBg);
|
||||||
} else {
|
} else {
|
||||||
// 如果未登录,使用本地存储的设置或默认值
|
// 如果未登录,先检查本地存储
|
||||||
const localSettings = localStorage.getItem('navSettings');
|
const localSettings = localStorage.getItem('navSettings');
|
||||||
if (localSettings) {
|
if (localSettings) {
|
||||||
settings = JSON.parse(localSettings);
|
const localSettingsObj = JSON.parse(localSettings);
|
||||||
|
// 从本地存储加载主题和卡片样式
|
||||||
|
settings.theme = localSettingsObj.theme || 'auto';
|
||||||
|
settings.card_style = localSettingsObj.card_style || 'normal';
|
||||||
|
settings.search_history = localSettingsObj.search_history || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 游客模式不使用背景图片
|
// 获取游客默认背景设置
|
||||||
setBackgroundImages('none', 'none');
|
const guestResponse = await fetch('/api/guest_settings');
|
||||||
|
const guestSettings = await guestResponse.json();
|
||||||
|
|
||||||
// 检查系统偏好
|
// 设置背景图片(始终使用服务器配置)
|
||||||
if (settings.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
const lightBg = guestSettings.bg_image === 'none' ? 'none' :
|
||||||
document.body.classList.add('dark-theme');
|
(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();
|
applySettings();
|
||||||
@ -1276,7 +1301,13 @@ function saveSettings() {
|
|||||||
body: JSON.stringify(settings)
|
body: JSON.stringify(settings)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem('navSettings', JSON.stringify(settings));
|
// 游客只保存主题和卡片样式到本地
|
||||||
|
const settingsToSave = {
|
||||||
|
theme: settings.theme,
|
||||||
|
card_style: settings.card_style,
|
||||||
|
search_history: settings.search_history
|
||||||
|
};
|
||||||
|
localStorage.setItem('navSettings', JSON.stringify(settingsToSave));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,13 +46,14 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="username" class="form-label">用户名</label>
|
<label for="username" class="form-label">用户名</label>
|
||||||
<input type="text" class="form-control" id="username" name="username" required>
|
<input type="text" class="form-control" id="username" name="username" value="{{ request.form.username if request.form }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="password" class="form-label">密码</label>
|
<label for="password" class="form-label">密码</label>
|
||||||
<input type="password" class="form-control" id="password" name="password" required>
|
<input type="password" class="form-control" id="password" name="password" value="{{ request.form.password if request.form }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="captcha" class="form-label">验证码</label>
|
<label for="captcha" class="form-label">验证码</label>
|
||||||
|
|||||||
@ -49,7 +49,13 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for app in apps %}
|
{% for app in apps %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="fas {{ app.icon }} fa-lg"></i></td>
|
<td>
|
||||||
|
{% if app.icon.startswith('/upload/icon/') %}
|
||||||
|
<img src="{{ app.icon }}" style="max-width: 24px; max-height: 24px;" class="img-fluid">
|
||||||
|
{% else %}
|
||||||
|
<i class="fas {{ app.icon }} fa-lg"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ app.title }}
|
{{ app.title }}
|
||||||
{% if app.get('private', False) %}
|
{% if app.get('private', False) %}
|
||||||
|
|||||||
@ -217,6 +217,69 @@
|
|||||||
</div>
|
</div>
|
||||||
</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-user me-2"></i>游客设置</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="guest_theme" class="form-label">游客默认主题</label>
|
||||||
|
<select name="guest_theme" id="guest_theme" class="form-select">
|
||||||
|
<option value="auto" {% if guest_settings.theme == 'auto' %}selected{% endif %}>自动(跟随系统)</option>
|
||||||
|
<option value="light" {% if guest_settings.theme == 'light' %}selected{% endif %}>明亮模式</option>
|
||||||
|
<option value="dark" {% if guest_settings.theme == 'dark' %}selected{% endif %}>暗黑模式</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="guest_card_style" class="form-label">游客卡片样式</label>
|
||||||
|
<select name="guest_card_style" id="guest_card_style" class="form-select">
|
||||||
|
<option value="normal" {% if guest_settings.card_style == 'normal' %}selected{% endif %}>正常大小</option>
|
||||||
|
<option value="compact" {% if guest_settings.card_style == 'compact' %}selected{% endif %}>紧凑模式</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">游客明亮模式背景</label>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="radio" name="guest_bg_type" id="guest_bg_none" value="none"
|
||||||
|
{% if guest_settings.bg_image == 'none' %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="guest_bg_none">无背景</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="radio" name="guest_bg_type" id="guest_bg_default" value="default"
|
||||||
|
{% if guest_settings.bg_image == '/static/background_light.jpg' %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="guest_bg_default">默认背景</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="radio" name="guest_bg_type" id="guest_bg_system" value="system"
|
||||||
|
{% if guest_settings.bg_image not in ['none', '/static/background_light.jpg'] %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="guest_bg_system">使用系统设置背景</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">游客暗黑模式背景</label>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="radio" name="guest_dark_bg_type" id="guest_dark_bg_none" value="none"
|
||||||
|
{% if guest_settings.dark_bg_image == 'none' %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="guest_dark_bg_none">无背景</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="radio" name="guest_dark_bg_type" id="guest_dark_bg_default" value="default"
|
||||||
|
{% if guest_settings.dark_bg_image == '/static/background_dark.jpg' %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="guest_dark_bg_default">默认背景</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="radio" name="guest_dark_bg_type" id="guest_dark_bg_system" value="system"
|
||||||
|
{% if guest_settings.dark_bg_image not in ['none', '/static/background_dark.jpg'] %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="guest_dark_bg_system">使用系统设置背景</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 修改密码 -->
|
<!-- 修改密码 -->
|
||||||
<div class="card mb-4 border-light shadow-sm">
|
<div class="card mb-4 border-light shadow-sm">
|
||||||
<div class="card-header bg-light">
|
<div class="card-header bg-light">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user