feat(public): 主题切换修复 + 卡片图标服务端本地缓存

- 主题切换:菜单改为 fixed 定位并弹出在父菜单右侧;FIXED_THEMES 扩为 9 主题并统一校验;
  子菜单点击无响应修复(合并 IIFE 作用域使 applyTheme 对点击处理器可见);删除死代码。
- 卡片图标:新增 GET /site/icon/<domain>?letter=X 接口,首次请求由服务端拉取 favicon
  并缓存到 data/icon/(运行时自动建目录,已加 .gitignore),之后直接读本地不再重复拉取;
  拉取失败或内网/不可达网址自动生成「站点名称首字母」SVG 头像兜底,杜绝空白图标。
- 模型 Nav.to_dict 新增 domain/scheme/letter/color 计算字段,供卡片与图标接口复用。
This commit is contained in:
bwstudio
2026-09-07 19:14:50 +08:00
parent e10ef5f243
commit 246564fdb3
6 changed files with 295 additions and 36 deletions
+36
View File
@@ -54,6 +54,38 @@ class Nav(db.Model):
def to_dict(self):
"""方便模板直接读取"""
url = self.url or ''
# 提取域名 / 协议,用于加载 favicon 与生成确定性配色
domain = ''
scheme = 'https'
try:
from urllib.parse import urlparse
parsed = urlparse(url if '://' in url else 'http://' + url)
domain = parsed.netloc or parsed.path.split('/')[0]
if parsed.scheme:
scheme = parsed.scheme
except Exception:
domain = ''
# 取标题首个可见字符作为「无 favicon 时的兜底字母头像」
letter = ''
for ch in (self.title or ''):
if ch.strip():
letter = ch.upper()
break
if not letter:
letter = (domain[:1] or '·').upper()
# 按域名哈希得到稳定的渐变配色(同域名永远同色,跨域名尽量区分)
hue = 0
if domain:
s = 0
for ch in domain:
s = (s * 31 + ord(ch)) & 0xffffffff
hue = s % 360
color = 'linear-gradient(135deg, hsl(%d,62%%,56%%) 0%%, hsl(%d,72%%,46%%) 100%%)' % (
hue, (hue + 28) % 360)
return {
'id': self.id,
'category': self.category,
@@ -61,6 +93,10 @@ class Nav(db.Model):
'url': self.url,
'description': self.description or '',
'icon': self.icon or 'layui-icon-link',
'letter': letter,
'color': color,
'domain': domain,
'scheme': scheme,
'sort': self.sort or 0,
'enable': self.enable,
'is_external': self.is_external,