diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3adff1f..a8835e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@
增加副本记录查询功能。
+重构 46 个 HTML 图片模板:公共文档骨架、设计变量、基础样式和组件样式统一维护,页面仅保留内容与独有布局;渲染前在本地组合为完整 HTML 字符串。
+
+重新整理功能帮助页,按 17 个分类展示 69 项功能和 105 条指令,并优化桌面三列与窄屏单列排版。
+
### version: 3.0:
大部分功能重写,还有部分未写完。
diff --git a/README.md b/README.md
index f1981ee..2b467aa 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@
- 支持本地 SQLite 避雷记录的增删改查。
- 支持开服、新闻、刷马、赤兔四类定时轮询与会话推送。
- 复用 `aiohttp.ClientSession`,统一处理 GET、POST、JSON、图片和分页请求。
-- 内置 46 个 HTML 模板,以及通用、门派/心法和奇遇图标资源。
+- 内置 46 个页面片段,通过公共布局与样式在本地组装为完整 HTML,并附带通用、门派/心法和奇遇图标资源。
## 数据来源
@@ -412,6 +412,15 @@ flowchart LR
图片默认使用质量 `100`、完整页面截图和普通设备缩放级别。模板可通过 `icons.img`、`icons.sect`、`icons.serendipity` 访问通用、门派/心法和奇遇图标。
+AstrBot 的渲染接口接收完整 HTML 字符串,因此插件不会依赖渲染端读取本地 CSS 文件。`core/template.py` 会异步读取并缓存公共布局、设计变量、基础样式、组件样式、页面专属样式和页面片段,组装完成后再把单个完整字符串交给渲染器。这样既满足渲染接口约束,也避免在 46 个页面中重复维护文档骨架和通用样式。
+
+样式职责如下:
+
+- `styles/tokens.css`:颜色、间距、圆角、字体和各页面内容宽度。
+- `styles/base.css`:页面背景、外框、基础排版和窄屏规则。
+- `styles/components.css`:信息面板、表格、状态、进度条、标签等跨页面组件。
+- `styles/pages/*.css`:只保留列宽、网格、卡片结构等页面独有布局。
+
### 6. 本地数据与缓存
插件使用两个 SQLite 文件:
@@ -456,13 +465,23 @@ astrbot_plugin_jx3/
│ ├── async_task.py # APScheduler 后台推送
│ ├── bilei_data.py # 避雷数据增删改查
│ ├── sqlite.py # aiosqlite 通用封装
-│ └── fun_basic.py # 模板、图标、时间和货币格式化工具
-└── templates/
- ├── helps.html # 内置功能帮助图
- ├── *.html # 各图片查询模板
- ├── img/ # 通用图片资源
- ├── sect/ # 门派与心法图标
- └── serendipity/ # 奇遇图标
+│ ├── fun_basic.py # 图标、时间和货币格式化工具
+│ └── template.py # 模板组合、异步读取与内存缓存
+├── templates/
+│ ├── layouts/
+│ │ └── base.html # 唯一的完整 HTML 文档骨架
+│ ├── pages/
+│ │ └── *.html # 46 个页面内容与 Jinja2 数据绑定
+│ ├── styles/
+│ │ ├── tokens.css # 设计变量与页面宽度
+│ │ ├── base.css # 全局背景、外框和排版
+│ │ ├── components.css # 公共组件样式
+│ │ └── pages/*.css # 页面独有布局
+│ ├── img/ # 通用图片资源
+│ ├── sect/ # 门派与心法图标
+│ └── serendipity/ # 奇遇图标
+└── tests/
+ └── test_template_system.py # 46 个模板的组合、渲染与安全检查
```
## 新增功能开发
@@ -471,10 +490,10 @@ astrbot_plugin_jx3/
1. 根据数据来源选择 `JX3APIService`、`AIJX3Service` 或 `JX3BOXService`。
2. 在业务服务中新增异步方法,复用 `APIClient`,完成请求、空数据检查、字段整理和标准返回对象构建。
-3. 需要图片输出时新增或复用 `templates/*.html`;不要在业务层直接拼装最终图片消息。
+3. 需要图片输出时新增或复用 `templates/pages/*.html`,并按需添加同名的 `templates/styles/pages/*.css`;通用视觉规则应进入公共样式层,不要在业务层拼装最终图片消息。
4. 在 `MessageBuilder` 中增加薄包装方法,选择文本、HTML 图片、远程图片、消息链或会话处理器。
5. 在 `main.py` 的 `command_map` 注册触发词。
-6. 同步更新 `templates/helps.html`、README 和 CHANGELOG,确保参数顺序以包装方法签名为准。
+6. 同步更新 `templates/pages/helps.html`、README 和 CHANGELOG,确保参数顺序以包装方法签名为准。
7. 如果新增运行时数据,使用参数化 SQL 并在初始化阶段创建表;如果新增敏感配置,同时更新 `_conf_schema.json`。
8. 至少执行语法检查,并在带有效凭据的 AstrBot 环境中手工验证成功、空数据、上游异常和参数错误路径。
@@ -482,15 +501,16 @@ astrbot_plugin_jx3/
```bash
python -m compileall -q .
+python -m unittest tests.test_template_system -v
```
-仓库当前没有自动化测试套件,`compileall` 只能检查 Python 语法,不能替代真实 AstrBot 消息、HTML 渲染和外部接口联调。
+模板测试会检查 46 个页面片段与同名样式是否一一对应、能否组合为完整文档、Jinja2 是否能够完成基础渲染,以及模板路径是否能阻止目录穿越。它和 `compileall` 仍不能替代带真实数据的 AstrBot 消息、HTML 渲染和外部接口联调。
## 当前版本状态
以下内容是对 v3.1 当前源码的静态核对结果,部署和二次开发前应注意:
-1. `command_map` 实际注册 108 个触发词;`templates/helps.html` 标注 105 条,并遗漏 `功能`、`小药`、`骗子`、`开服推送`。帮助图中的 `开服监控` 不是当前有效触发词。
+1. `command_map` 实际注册 108 个触发词;`templates/pages/helps.html` 标注 105 条,并遗漏 `功能`、`小药`、`骗子`、`开服推送`。帮助图中的 `开服监控` 不是当前有效触发词。
2. 默认服务器配置会用于四类后台任务,并只在 `烟花` 查询中通过 `serverdefault()` 显式补齐;其他可选服务器参数会原样传为空字符串。
3. `JX3BOXService._get_achievement_base_data()` 当前调用了该类中未定义的 `_base_request()`,因此 `资历` 在用户选择分类后无法完成基础数据加载。
4. 刷马和赤兔后台任务当前调用了 `JX3APIService` 中不存在的 `shuamamsg()`,启用 `smts` 或 `ctts` 后,定时回调会记录执行异常。
diff --git a/core/fun_basic.py b/core/fun_basic.py
index db94c9e..0cdae5f 100644
--- a/core/fun_basic.py
+++ b/core/fun_basic.py
@@ -1,25 +1,12 @@
-from pathlib import Path
from datetime import datetime,date
from zoneinfo import ZoneInfo
-import aiofiles
import base64
import os
from astrbot import logger
-async def load_template(template_name: str) -> str:
- """
- 异步加载模板内容(非阻塞)
- """
- plugin_dir = Path(__file__).parent.parent
- template_path = plugin_dir / "templates" / template_name
-
- if not template_path.exists():
- raise FileNotFoundError(f"模板文件不存在: {template_path}")
-
- async with aiofiles.open(template_path, "r", encoding="utf-8") as f:
- return await f.read()
+from .template import load_template
def gold_to_string(gold_amount):
@@ -161,4 +148,4 @@ def format_remaining(ts):
minutes, seconds = divmod(seconds, 60)
return f"{hours}时{minutes:02d}分{seconds:02d}秒"
except (TypeError, ValueError):
- return ""
\ No newline at end of file
+ return ""
diff --git a/core/template.py b/core/template.py
new file mode 100644
index 0000000..5f8e131
--- /dev/null
+++ b/core/template.py
@@ -0,0 +1,104 @@
+import asyncio
+import re
+from pathlib import Path
+
+import aiofiles
+
+
+_TITLE_PATTERN = re.compile(
+ r"\A\{# template-title: (.*?) #\}\r?\n",
+)
+
+
+class TemplateRepository:
+ """将公共布局、设计系统和页面片段组装为完整 Jinja 模板。"""
+
+ def __init__(self, root: Path):
+ self.root = root.resolve()
+ self._cache: dict[str, str] = {}
+ self._lock = asyncio.Lock()
+
+ async def _read(self, path: Path) -> str:
+ async with aiofiles.open(path, "r", encoding="utf-8") as file:
+ return await file.read()
+
+ def _page_path(self, template_name: str) -> Path:
+ name = Path(template_name)
+ if name.name != template_name or name.suffix.lower() != ".html":
+ raise ValueError(f"非法模板名称: {template_name}")
+ return self.root / "pages" / name.name
+
+ async def get(self, template_name: str) -> str:
+ """读取并缓存组装后的完整模板字符串。"""
+ cached = self._cache.get(template_name)
+ if cached is not None:
+ return cached
+
+ async with self._lock:
+ cached = self._cache.get(template_name)
+ if cached is not None:
+ return cached
+
+ page_path = self._page_path(template_name)
+ if not page_path.exists():
+ # 兼容迁移期间尚未拆分的旧模板。
+ legacy_path = self.root / template_name
+ if not legacy_path.exists():
+ raise FileNotFoundError(f"模板文件不存在: {page_path}")
+ template = await self._read(legacy_path)
+ self._cache[template_name] = template
+ return template
+
+ page_css_path = self.root / "styles" / "pages" / f"{page_path.stem}.css"
+ read_tasks = [
+ self._read(self.root / "layouts" / "base.html"),
+ self._read(self.root / "styles" / "tokens.css"),
+ self._read(self.root / "styles" / "base.css"),
+ self._read(page_path),
+ self._read(page_css_path),
+ self._read(self.root / "styles" / "components.css"),
+ ]
+ base, tokens, base_css, page, page_css, components = await asyncio.gather(
+ *read_tasks
+ )
+
+ title_match = _TITLE_PATTERN.match(page)
+ title = title_match.group(1).strip() if title_match else "剑网三数据查询"
+ content = _TITLE_PATTERN.sub("", page, count=1)
+
+ template = (
+ base.replace("__PAGE_ID__", page_path.stem)
+ .replace("", title)
+ .replace("/*__TOKENS_CSS__*/", tokens)
+ .replace("/*__BASE_CSS__*/", base_css)
+ .replace("/*__PAGE_CSS__*/", page_css)
+ .replace("/*__COMPONENTS_CSS__*/", components)
+ .replace("", content)
+ )
+
+ unresolved = (
+ "__PAGE_ID__",
+ "",
+ "/*__TOKENS_CSS__*/",
+ "/*__BASE_CSS__*/",
+ "/*__PAGE_CSS__*/",
+ "/*__COMPONENTS_CSS__*/",
+ "",
+ )
+ if any(marker in template for marker in unresolved):
+ raise ValueError(f"模板组装失败,存在未替换标记: {template_name}")
+
+ self._cache[template_name] = template
+ return template
+
+ def clear(self) -> None:
+ """清理内存缓存,主要用于开发和测试。"""
+ self._cache.clear()
+
+
+_TEMPLATE_ROOT = Path(__file__).parent.parent / "templates"
+_TEMPLATE_REPOSITORY = TemplateRepository(_TEMPLATE_ROOT)
+
+
+async def load_template(template_name: str) -> str:
+ return await _TEMPLATE_REPOSITORY.get(template_name)
diff --git a/templates/baizhan.html b/templates/baizhan.html
deleted file mode 100644
index 66559a4..0000000
--- a/templates/baizhan.html
+++ /dev/null
@@ -1,146 +0,0 @@
-
-
-
-
@@ -288,5 +99,3 @@ document.querySelectorAll(".start-time").forEach(function (cell) {
});
});
-
-
diff --git a/templates/pages/zhengyingpaimai.html b/templates/pages/zhengyingpaimai.html
new file mode 100644
index 0000000..7863e06
--- /dev/null
+++ b/templates/pages/zhengyingpaimai.html
@@ -0,0 +1,40 @@
+{# template-title: 阵营拍卖 #}
+
+
+
+
阵营拍卖
+
+
+
+
+ | 服务器 |
+ 物品名称 |
+ 拍得者 |
+ 所属阵营 |
+ 拍卖价格 |
+ 拍卖时间 |
+
+
+
+
+ {% for m in list %}
+
+ | {{ m.server }} |
+ {{ m.itemName }} |
+ {{ m.roleName }} |
+ {{ m.campName }} |
+ {{ m.itemAmount }} |
+ {{ m.time }} |
+
+ {% endfor %}
+
+
+
+
+
diff --git a/templates/pages/zhenyingevent.html b/templates/pages/zhenyingevent.html
new file mode 100644
index 0000000..4e398d6
--- /dev/null
+++ b/templates/pages/zhenyingevent.html
@@ -0,0 +1,38 @@
+{# template-title: 阵营事件 #}
+
+
+
+
阵营事件
+
更新时间:{{ update_time }}
+
+
+
+
+ | 阵营 |
+ 分线 |
+ 服务器 |
+ 角色名称 |
+ 时间 |
+
+
+
+
+ {% for item in items %}
+
+ | {{ item.campName }} |
+ {{ item.fenxianName }} |
+ {{ item.friendName }} |
+ {{ item.roleName }} |
+ {{ item.seizeTime }} |
+
+ {% endfor %}
+
+
+
+
diff --git a/templates/pages/zhuangshi.html b/templates/pages/zhuangshi.html
new file mode 100644
index 0000000..4181b33
--- /dev/null
+++ b/templates/pages/zhuangshi.html
@@ -0,0 +1,64 @@
+{# template-title: 家园家具一览 #}
+
+
+
+
家园家具展示
+
+ {% for item in data %}
+
+

+
+
+
+ {{ item.name }}
+
+
+
+ 来源:{{ item.source }}
+ 数量限制:{{ item.limit }}
+ 品质值:{{ item.quality }}
+
+
+ {% if item.tip %}
+
+ {{ item.tip }}
+
+ {% endif %}
+
+
+
+
+
实用
+
{{ item.practical }}
+
+
+
+
风水
+
{{ item.geomantic }}
+
+
+
趣味
+
{{ item.interesting }}
+
+
+
+
+
+
+ {% endfor %}
+
+
+
diff --git a/templates/pages/zhueevent.html b/templates/pages/zhueevent.html
new file mode 100644
index 0000000..6987907
--- /dev/null
+++ b/templates/pages/zhueevent.html
@@ -0,0 +1,28 @@
+{# template-title: 诛恶事件 #}
+
+
+
+
诛恶事件
+
更新时间:{{ update_time }}
+
+
+
+
+ | 服务器 |
+ 地图 |
+ 时间 |
+
+
+
+
+ {% for item in items %}
+
+ | {{ item.server }} |
+ {{ item.mapName }} |
+ {{ item.time }} |
+
+ {% endfor %}
+
+
+
+
diff --git a/templates/pages/zili.html b/templates/pages/zili.html
new file mode 100644
index 0000000..29f702e
--- /dev/null
+++ b/templates/pages/zili.html
@@ -0,0 +1,53 @@
+{# template-title: 资历查询 #}
+
+
+
+
+
+
+
+
{{ summary.name }}
+
{{ summary.percent_text }}
+
+
+ {{ summary.completed_points }} / {{ summary.total_points }} 点
+ · {{ summary.completed_count }} / {{ summary.achievement_count }} 项
+
+
+
+
+
+ {% for item in items %}
+
+
+
{{ item.name }}
+
{{ item.percent_text }}
+
+
+ {{ item.completed_points }} / {{ item.total_points }} 点
+ · {{ item.completed_count }} / {{ item.achievement_count }} 项
+
+
+
+ {% endfor %}
+
+
+
diff --git a/templates/pages/zilipaixing.html b/templates/pages/zilipaixing.html
new file mode 100644
index 0000000..7815c23
--- /dev/null
+++ b/templates/pages/zilipaixing.html
@@ -0,0 +1,89 @@
+{# template-title: 资历排行 #}
+
+
+
+
{{ school }} 资历排行
+
+
+ {{ server }} | 更新时间:{{ update_time }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 排名 |
+ 头像 |
+ 职业 |
+ 角色 |
+ 服务器 |
+ 资历 |
+
+
+
+
+ {% for item in items %}
+
+ |
+ {{ loop.index }}
+ |
+
+
+ {% if item.avatarUrl %}
+
+ {% endif %}
+ |
+
+
+
+ {% set force_icon = item.forceIcon or icons.sect.get(item.forceName) %}
+
+ {% if force_icon %}
+ 
+ {% endif %}
+
+
+ {{ item.forceName }}
+
+
+ |
+
+
+ {{ item.roleName }}
+ |
+
+
+ {{ item.serverName }}
+ |
+
+
+ {{ item.seniority }}
+ |
+
+ {% endfor %}
+
+
+
+
diff --git a/templates/qiwu.html b/templates/qiwu.html
deleted file mode 100644
index 7bb623e..0000000
--- a/templates/qiwu.html
+++ /dev/null
@@ -1,178 +0,0 @@
-
-
-
-
-
地图装饰器物
-
-
-
-
-
-
-
地图装饰器物({{ name }})
-
- {% for item in data %}
-
-

-
-
-
- {{ item.name }}
-
-
-
- 来源:{{ item.source }}
- 数量限制:{{ item.limit }}
- 品质值:{{ item.quality }}
-
-
- {% if item.tip %}
-
- {{ item.tip }}
-
- {% endif %}
-
-
-
-
-
实用
-
{{ item.practical }}
-
-
-
-
风水
-
{{ item.geomantic }}
-
-
-
趣味
-
{{ item.interesting }}
-
-
-
-
-
-
- {% endfor %}
-
-
-
-
-
diff --git a/templates/qiyuhuizong.html b/templates/qiyuhuizong.html
deleted file mode 100644
index 04c5439..0000000
--- a/templates/qiyuhuizong.html
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
奇遇汇总
-
-
-
-
-
-
-
{{ server }} 奇遇汇总
-
最近 {{ num }} 天 · 更新时间:{{ update_time }}
-
-
-
-
- | 区服 |
- 奇遇名称 |
- 触发次数 |
- 最近角色 |
- 最近时间 |
-
-
-
-
- {% for m in items %}
-
- | {{ m.server }} |
- {{ m.event }} |
- {{ m.count }} |
- {{ m.latest_name }} |
- {{ m.latest_time }} |
-
- {% endfor %}
-
-
-
-
-
-
-
diff --git a/templates/qiyuliebiao.html b/templates/qiyuliebiao.html
deleted file mode 100644
index 99c8d9b..0000000
--- a/templates/qiyuliebiao.html
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
区服奇遇
-
-
-
-
-
-
-
区服奇遇
-
-
-
-
- | 奇遇名称 |
- 服务器 |
- 触发角色 |
- 触发时间 |
-
-
-
-
- {% for m in items %}
-
- | {{ m.event }} |
- {{ m.server }} |
- {{ m.name }} |
- {{ m.time }} |
-
- {% endfor %}
-
-
-
-
-
-
-
diff --git a/templates/qufuzhuangtai.html b/templates/qufuzhuangtai.html
deleted file mode 100644
index 2926186..0000000
--- a/templates/qufuzhuangtai.html
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
区服状态面板
-
-
-
-
-
-
-
-
无界区({{ server_wj|length if server_wj else 0 }})
- {% if server_wj %}
-
- {% for item in server_wj %}
-
{{ item.server }}
- {% endfor %}
-
- {% else %}
-
暂无数据
- {% endif %}
-
-
-
-
-
电信区({{ server_dx|length if server_dx else 0 }})
- {% if server_dx %}
-
- {% for item in server_dx %}
-
{{ item.server }}
- {% endfor %}
-
- {% else %}
-
暂无数据
- {% endif %}
-
-
-
-
-
双线区({{ server_sx|length if server_sx else 0 }})
- {% if server_sx %}
-
- {% for item in server_sx %}
-
{{ item.server }}
- {% endfor %}
-
- {% else %}
-
暂无数据
- {% endif %}
-
-
-
-
diff --git a/templates/rank_role.html b/templates/rank_role.html
deleted file mode 100644
index 7ec587f..0000000
--- a/templates/rank_role.html
+++ /dev/null
@@ -1,158 +0,0 @@
-
-
-
-
-
角色榜单
-
-
-
-
-
-
-
{{ rank_name }}
-
{{ server }} · 更新时间:{{ update_time }}
-
-
-
-
- | 排名 |
- 职业 |
- 角色名称 |
- 等级 |
- 阵营 |
- 帮会 |
- 排行积分 |
-
-
-
-
- {% for item in items %}
-
- | {{ loop.index }} |
-
-
- {% if icons.sect.get(item.forceName) %}
- 
- {% endif %}
- {{ item.forceName }}
-
- |
- {{ item.name }} |
- {{ item.level }} |
- {{ item.campName }} |
- {{ item.tongName }} |
- {{ item.totalScore }} |
-
- {% endfor %}
-
-
-
-
-
-
diff --git a/templates/rank_tong0.html b/templates/rank_tong0.html
deleted file mode 100644
index 8605dda..0000000
--- a/templates/rank_tong0.html
+++ /dev/null
@@ -1,141 +0,0 @@
-
-
-
-
-
帮会榜单
-
-
-
-
-
-
-
{{ rank_name }}
-
{{ server }} · 更新时间:{{ update_time }}
-
-
-
-
- | 排名 |
- 帮会名称 |
- 帮主 |
- 据点 |
- 人数 |
- 排行积分 |
-
-
-
-
- {% for item in items %}
-
- | {{ loop.index }} |
- {{ item.tongName }} |
- {{ item.masterName }} |
- {{ item.castleName }} |
- {{ item.memberCount }}/{{ item.maxMemberCount }} |
- {{ item.titlePoint }} |
-
- {% endfor %}
-
-
-
-
-
-
diff --git a/templates/rank_tong1.html b/templates/rank_tong1.html
deleted file mode 100644
index b2e23c4..0000000
--- a/templates/rank_tong1.html
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-
帮会榜单
-
-
-
-
-
-
-
{{ rank_name }}
-
{{ server }} · 更新时间:{{ update_time }}
-
-
-
-
- | 排名 |
- 帮会名称 |
- 帮主 |
- 人数 |
- 排行积分 |
-
-
-
-
- {% for item in items %}
-
- | {{ loop.index }} |
- {{ item.tongName }} |
- {{ item.masterName }} |
- {{ item.maxLimit }} |
- {{ item.totalScore }} |
-
- {% endfor %}
-
-
-
-
-
-
diff --git a/templates/rank_tong2.html b/templates/rank_tong2.html
deleted file mode 100644
index efd4c63..0000000
--- a/templates/rank_tong2.html
+++ /dev/null
@@ -1,137 +0,0 @@
-
-
-
-
-
帮会榜单
-
-
-
-
-
-
-
{{ rank_name }}
-
{{ server }} · 更新时间:{{ update_time }}
-
-
-
-
- | 排名 |
- 帮会名称 |
- 帮主 |
- 排行积分 |
-
-
-
-
- {% for item in items %}
-
- | {{ loop.index }} |
- {{ item.tongName }} |
- {{ item.masterName }} |
- {{ item.key }} |
-
- {% endfor %}
-
-
-
-
-
-
diff --git a/templates/richangyuche.html b/templates/richangyuche.html
deleted file mode 100644
index 2e8f91f..0000000
--- a/templates/richangyuche.html
+++ /dev/null
@@ -1,112 +0,0 @@
-
-
-
-
-
日常预测
-
-
-
-
-
-
-
-
日常预测
-
-
-
- {% for w in ["周一","周二","周三","周四","周五","周六","周日"] %}
-
{{ w }}
- {% endfor %}
-
-
- {% for m in items %}
-
- {% if m.en %}
-
{{ m.date }}
-
日常:{{ m.war }}
-
战场:{{ m.battle }}
- {% endif %}
-
- {% endfor %}
-
-
-
-
-
diff --git a/templates/shilianpaixing.html b/templates/shilianpaixing.html
deleted file mode 100644
index a2b0ac1..0000000
--- a/templates/shilianpaixing.html
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
试炼排行
-
-
-
-
-
-
-
{{ name }} 试炼排行
-
{{ server }} | 更新时间:{{ update_time }}
-
-
-
-
- | 排名 |
- 角色名 |
- 装备分数 |
- 最高层数 |
- 排名积分 |
-
-
-
-
- {% for item in items %}
-
- | {{ loop.index }} |
- {{ item.role_name }} |
- {{ item.equip_score }} |
- {{ item.max_level }} |
- {{ item.total_score }} |
-
- {% endfor %}
-
-
-
-
-
-
diff --git a/templates/shitu.html b/templates/shitu.html
deleted file mode 100644
index 2c6b546..0000000
--- a/templates/shitu.html
+++ /dev/null
@@ -1,151 +0,0 @@
-
-
-
-
-
师徒招募
-
-
-
-
-
-
-
{{ title }}
-
- {{ server }}
- {% if keyword %} · 关键词:{{ keyword }}{% endif %}
- · 更新时间:{{ update_time }}
-
-
-
-
-
- | 职业 |
- 角色名称 |
- 等级 |
- 体型 |
- 帮会 |
- 阵营 |
- 信息 |
-
-
-
-
- {% for item in items %}
-
-
-
- {% if icons.sect.get(item.forceName) %}
- 
- {% endif %}
- {{ item.forceName }}
-
- |
- {{ item.roleName }} |
- {{ item.roleLevel }} |
- {{ item.bodyName }} |
- {{ item.tongName }} |
- {{ item.campName }} |
-
-
- {% endfor %}
-
-
-
-
-
-
-
diff --git a/templates/styles/base.css b/templates/styles/base.css
new file mode 100644
index 0000000..1f190bc
--- /dev/null
+++ b/templates/styles/base.css
@@ -0,0 +1,126 @@
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body.jx3-template {
+ min-width: 0;
+ padding: 20px;
+ color: var(--jx3-text);
+ font-family: var(--jx3-font);
+ font-size: 16px;
+ line-height: 1.6;
+ background-color: var(--jx3-bg);
+ background-image:
+ radial-gradient(circle at 50% -10%, rgba(67, 111, 150, 0.34), transparent 38%),
+ linear-gradient(180deg, rgba(4, 17, 29, 0.18), rgba(4, 17, 29, 0.72)),
+ url("{{ icons.img.get('bg', '') }}");
+ background-repeat: no-repeat, no-repeat, repeat;
+ background-size: 100% 560px, 100% 100%, 540px 316px;
+ background-blend-mode: screen, normal, multiply;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+.jx3-frame {
+ position: relative;
+ width: 100%;
+ min-height: calc(100vh - 40px);
+ padding: 26px;
+ border: 1px solid var(--jx3-border-strong);
+ background: rgba(4, 17, 29, 0.2);
+ box-shadow:
+ inset 0 0 0 3px rgba(4, 17, 29, 0.9),
+ inset 0 0 0 4px rgba(203, 160, 78, 0.2),
+ var(--jx3-shadow-md);
+}
+
+.jx3-frame::before,
+.jx3-frame::after {
+ position: absolute;
+ width: 42px;
+ height: 42px;
+ border-color: var(--jx3-gold);
+ content: "";
+ pointer-events: none;
+}
+
+.jx3-frame::before {
+ top: 8px;
+ left: 8px;
+ border-top: 2px solid var(--jx3-gold);
+ border-left: 2px solid var(--jx3-gold);
+}
+
+.jx3-frame::after {
+ right: 8px;
+ bottom: 8px;
+ border-right: 2px solid var(--jx3-gold);
+ border-bottom: 2px solid var(--jx3-gold);
+}
+
+.jx3-template :where(.container, .page, .page-container),
+.jx3-template > .jx3-frame > :where(.panel, .header, .section) {
+ width: min(var(--jx3-page-width), calc(100vw - 92px));
+ max-width: none;
+ margin-right: auto;
+ margin-left: auto;
+ color: var(--jx3-text);
+}
+
+.jx3-template :where(h1, h2, h3, p) {
+ overflow-wrap: anywhere;
+}
+
+.jx3-template h1 {
+ margin: 0 0 var(--jx3-space-6);
+ color: var(--jx3-text);
+ font-size: 34px;
+ line-height: 1.25;
+ font-weight: 800;
+ letter-spacing: 0.06em;
+ text-align: center;
+ text-shadow: 0 2px 12px rgba(0, 0, 0, 0.32);
+}
+
+.jx3-template h2,
+.jx3-template h3 {
+ color: var(--jx3-text);
+}
+
+.jx3-template a {
+ color: var(--jx3-blue);
+}
+
+.jx3-template img {
+ max-width: 100%;
+}
+
+@media (max-width: 760px) {
+ body.jx3-template {
+ padding: 10px;
+ font-size: 14px;
+ }
+
+ .jx3-frame {
+ min-height: calc(100vh - 20px);
+ padding: 16px;
+ }
+
+ .jx3-template :where(.container, .page, .page-container),
+ .jx3-template > .jx3-frame > :where(.panel, .header, .section) {
+ width: 100%;
+ }
+
+ .jx3-template h1 {
+ margin-bottom: 16px;
+ font-size: 26px;
+ }
+}
diff --git a/templates/styles/components.css b/templates/styles/components.css
new file mode 100644
index 0000000..44a4f7b
--- /dev/null
+++ b/templates/styles/components.css
@@ -0,0 +1,266 @@
+/* 公共信息面板 */
+.jx3-template :where(
+ .header,
+ .page-header,
+ .top-info,
+ .info-bar,
+ .summary,
+ .main-card,
+ .card,
+ .panel,
+ .section,
+ .achievement-card,
+ .material-card,
+ .boss-item,
+ .cell,
+ .stat-item,
+ .stat-box,
+ .empty-card,
+ .empty-state
+) {
+ color: var(--jx3-text);
+ border: 1px solid var(--jx3-border-soft);
+ background: linear-gradient(180deg, rgba(17, 52, 81, 0.96), rgba(9, 35, 57, 0.96));
+ border-radius: var(--jx3-radius-md);
+ box-shadow: var(--jx3-shadow-sm);
+}
+
+.jx3-template :where(
+ .item,
+ .summary-panel,
+ .summary-item,
+ .price-box,
+ .price-item,
+ .material-price-item,
+ .store-price-item,
+ .subtotal-row,
+ .material-total,
+ .achievement-info,
+ .detail-item,
+ .boss-card,
+ .server-card,
+ .recruit-card
+) {
+ color: var(--jx3-text-secondary);
+ border-color: var(--jx3-border-faint);
+ background: rgba(13, 41, 66, 0.82);
+}
+
+.jx3-template :where(.header, .page-header, .top-info, .info-bar) {
+ padding: var(--jx3-space-5) var(--jx3-space-6);
+ border-color: var(--jx3-border);
+}
+
+.jx3-template :where(.meta, .meta-row, .page-subtitle, .top-summary, .top-desc, .desc, .description, .time) {
+ color: var(--jx3-text-muted);
+}
+
+.jx3-template .role {
+ color: var(--jx3-text-secondary);
+}
+
+.jx3-template :where(.section-title, .section-header, .table-title, .summary-title, .card-header) {
+ color: var(--jx3-gold-bright);
+ border-color: var(--jx3-border-soft);
+}
+
+.jx3-template .section-title::before {
+ background: var(--jx3-gold);
+}
+
+/* 表格体系 */
+.jx3-template :where(.table-wrapper, .table-scroll) {
+ overflow: hidden;
+ border: 1px solid var(--jx3-border-soft);
+ border-radius: var(--jx3-radius-md);
+ background: rgba(7, 26, 43, 0.76);
+ box-shadow: var(--jx3-shadow-sm);
+}
+
+.jx3-template table {
+ width: 100%;
+ overflow: hidden;
+ border-collapse: collapse;
+ border-radius: var(--jx3-radius-md);
+ color: var(--jx3-text-secondary);
+ background: rgba(7, 26, 43, 0.64);
+ border-color: var(--jx3-border-soft);
+ box-shadow: none;
+}
+
+.jx3-template tbody tr:nth-child(odd) {
+ background: transparent;
+}
+
+.jx3-template thead {
+ color: var(--jx3-gold-bright);
+ background: #102f4a;
+ border-top: 1px solid var(--jx3-border-strong);
+ border-bottom: 1px solid var(--jx3-border-strong);
+}
+
+.jx3-template :where(th, td) {
+ padding: 13px 14px;
+ color: inherit;
+ border-color: var(--jx3-border-faint);
+ font-size: 15px;
+}
+
+.jx3-template th {
+ color: var(--jx3-gold-bright);
+ font-weight: 700;
+ letter-spacing: 0.03em;
+}
+
+.jx3-template tbody tr:nth-child(even) {
+ background: rgba(31, 74, 107, 0.18);
+}
+
+.jx3-template tbody tr:hover {
+ background: rgba(225, 183, 102, 0.09);
+}
+
+.jx3-template tbody tr:last-child td {
+ border-bottom-color: transparent;
+}
+
+/* 状态与重点数据 */
+.jx3-template :where(.rank-1, .price-col, .summary-percent, .item-percent, .point-value, .money-gold) {
+ color: var(--jx3-gold-bright);
+}
+
+.jx3-template .rank-2 {
+ color: #c8d2dc;
+}
+
+.jx3-template .rank-3 {
+ color: #c88a62;
+}
+
+.jx3-template :where(.winrate, .success, .unfinished, .status-unfinished) {
+ color: var(--jx3-green);
+}
+
+.jx3-template :where(.danger, .finished, .status-finished, .boss-final, .boss) {
+ color: var(--jx3-red);
+}
+
+.jx3-template :where(.empty, .empty-row, .empty-data, .empty-state) {
+ color: var(--jx3-text-muted);
+}
+
+.jx3-template :where(.progress, .map-progress) {
+ overflow: hidden;
+ background: rgba(135, 157, 178, 0.2);
+ border: 1px solid rgba(135, 157, 178, 0.16);
+}
+
+.jx3-template :where(.progress-fill, .map-progress > span) {
+ background: linear-gradient(90deg, var(--jx3-gold-muted), var(--jx3-gold-bright));
+}
+
+.jx3-template :where(.avatar, .force-icon, .school-icon, .item-icon, .material-icon, .main-icon) {
+ border: 1px solid var(--jx3-border-soft);
+ background: rgba(7, 26, 43, 0.6);
+}
+
+.jx3-template :where(.tag, .category-tag, .channel-tag, .status-badge, .quality-badge, .material-tag) {
+ border: 1px solid var(--jx3-border-soft);
+ background: rgba(225, 183, 102, 0.1);
+ color: var(--jx3-gold-bright);
+}
+
+.jx3-template :where(.footer, .card-footer, .summary-footer) {
+ color: var(--jx3-text-muted);
+ border-color: var(--jx3-border-soft);
+}
+
+/* 页面级色彩收敛 */
+.jx3-template :where(.page-title, .main-name, .role-name, .item-name, .material-name, .achievement-name, .stat-value) {
+ color: var(--jx3-text);
+}
+
+.jx3-template :where(
+ .title,
+ .summary-name,
+ .feature-name,
+ .event-name,
+ .quantity-value,
+ .price-box-value,
+ .material-price-value,
+ .store-price-value,
+ .subtotal-value,
+ .material-total-value
+) {
+ color: var(--jx3-text);
+}
+
+.jx3-template :where(
+ .sub-info,
+ .section-desc,
+ .summary-count,
+ .score,
+ .tip-list li,
+ .material-meta,
+ .material-footer,
+ .raw-price,
+ .quantity-label,
+ .subtotal-label,
+ .material-total-label
+) {
+ color: var(--jx3-text-muted);
+}
+
+.jx3-template :where(.command, .feature, .material-card-header, .summary-section) {
+ border-color: var(--jx3-border-faint);
+}
+
+.jx3-template .summary-section {
+ background: var(--jx3-border-faint);
+}
+
+.jx3-template .tip-list li::before {
+ background: var(--jx3-gold);
+}
+
+.jx3-template .command {
+ color: var(--jx3-text-secondary);
+ background: rgba(114, 169, 213, 0.1);
+}
+
+.jx3-template .command:hover {
+ color: var(--jx3-gold-bright);
+ border-color: var(--jx3-border-soft);
+ background: rgba(225, 183, 102, 0.1);
+}
+
+.jx3-template :where(.stat-label, .price-box-label, .material-price-label, .store-price-label, .point-label) {
+ color: var(--jx3-text-muted);
+}
+
+.jx3-template :where(.stat-value, .price-box-value, .material-price-value, .store-price-value, .point-number) {
+ font-variant-numeric: tabular-nums;
+}
+
+@media (max-width: 760px) {
+ .jx3-template :where(.header, .page-header, .top-info, .info-bar) {
+ padding: 16px;
+ }
+
+ .jx3-template :where(th, td) {
+ padding: 8px 4px;
+ font-size: 11px;
+ white-space: normal !important;
+ word-break: break-word;
+ }
+
+ .jx3-template table {
+ min-width: 0 !important;
+ table-layout: auto !important;
+ }
+
+ .jx3-template :where(.avatar, .force-icon, .school-icon) {
+ width: 28px;
+ height: 28px;
+ }
+}
diff --git a/templates/styles/pages/baizhan.css b/templates/styles/pages/baizhan.css
new file mode 100644
index 0000000..3c581b3
--- /dev/null
+++ b/templates/styles/pages/baizhan.css
@@ -0,0 +1,67 @@
+/* ===== 顶部信息 ===== */
+ .info-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: white;
+ padding: 16px 20px;
+ border-radius: 10px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.08);
+ margin-bottom: 20px;
+ }
+
+ .time {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ }
+
+ .boss-final {
+ font-size: 22px;
+ font-weight: 800;
+ color: #d63031;
+ }
+
+ /* ===== 网格 ===== */
+ .grid {
+ display: grid;
+ grid-template-columns: repeat(10, 1fr);
+ gap: 10px;
+ }
+
+ .cell {
+ background: white;
+ border-radius: 10px;
+ padding: 12px;
+ box-shadow: 0 2px 6px rgba(0,0,0,0.08);
+ min-height: 110px;
+
+ /* 核心:全部居中 */
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ text-align: center;
+ }
+
+ .index {
+ font-weight: bold;
+ font-size: 18px;
+ color: #333;
+ margin-bottom: 4px;
+ }
+
+ .boss {
+ color: #d63031;
+ font-weight: 700;
+ font-size: 16px;
+ margin-bottom: 6px;
+ }
+
+ .buff {
+ color: #0984e3;
+ font-size: 14px;
+ line-height: 1.5em;
+ word-break: break-word;
+ }
diff --git a/templates/styles/pages/bangzhanjilu.css b/templates/styles/pages/bangzhanjilu.css
new file mode 100644
index 0000000..41aad75
--- /dev/null
+++ b/templates/styles/pages/bangzhanjilu.css
@@ -0,0 +1,44 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .zone-col,
+ .server-col {
+ width: 90px;
+ text-align: center;
+ white-space: nowrap;
+ font-weight: 700;
+ }
+
+ .tong-col {
+ min-width: 150px;
+ max-width: 220px;
+ color: #333;
+ font-weight: 700;
+ word-break: break-word;
+ line-height: 1.45;
+ }
+
+ .time-col {
+ min-width: 148px;
+ color: #60657a;
+ text-align: center;
+ white-space: nowrap;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .duration-col {
+ width: 120px;
+ color: #d93939;
+ text-align: center;
+ white-space: nowrap;
+ font-weight: 800;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/bilei.css b/templates/styles/pages/bilei.css
new file mode 100644
index 0000000..8df31c0
--- /dev/null
+++ b/templates/styles/pages/bilei.css
@@ -0,0 +1,12 @@
+table {
+ margin-top: 10px;
+ text-align: left; /* 不影响表格内容 */
+ }
+
+ .text-col {
+ max-width: 360px;
+ line-height: 1.5em;
+ word-break: break-word;
+ font-weight: 600;
+ color: #333;
+ }
diff --git a/templates/styles/pages/chengbeng.css b/templates/styles/pages/chengbeng.css
new file mode 100644
index 0000000..16bd373
--- /dev/null
+++ b/templates/styles/pages/chengbeng.css
@@ -0,0 +1,738 @@
+/* ==============================
+ 页面标题
+ ============================== */
+
+ .page-header {
+ margin-bottom: 24px;
+ text-align: center;
+ }
+
+ .page-title {
+ margin: 0 0 10px;
+ font-size: 40px;
+ line-height: 1.3;
+ font-weight: 800;
+ color: #222;
+ }
+
+ .page-subtitle {
+ margin: 0;
+ color: #777;
+ font-size: 19px;
+ line-height: 1.7;
+ }
+
+ /* ==============================
+ 主卡片
+ ============================== */
+
+ .main-card {
+ overflow: hidden;
+ margin-bottom: 30px;
+ background: #fff;
+ border-radius: 20px;
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.09);
+ }
+
+ .main-card-top {
+ display: grid;
+ grid-template-columns: 210px minmax(0, 1fr);
+ gap: 30px;
+ padding: 32px;
+ text-align: left;
+ }
+
+ .main-icon-wrapper {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ }
+
+ .main-icon {
+ display: flex;
+ width: 190px;
+ height: 190px;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ background: #f4f1f9;
+ border: 3px solid rgba(138, 79, 211, 0.3);
+ border-radius: 22px;
+ box-shadow: 0 5px 18px rgba(0, 0, 0, 0.1);
+ }
+
+ .main-icon img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ .main-info {
+ min-width: 0;
+ }
+
+ .main-name-row {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 14px;
+ margin-bottom: 16px;
+ }
+
+ .main-name {
+ margin: 0;
+ font-size: 36px;
+ line-height: 1.35;
+ font-weight: 800;
+ color: #222;
+ }
+
+ .quality-badge {
+ display: inline-flex;
+ min-height: 38px;
+ align-items: center;
+ justify-content: center;
+ padding: 7px 16px;
+ border-radius: 999px;
+ font-size: 17px;
+ font-weight: 700;
+ white-space: nowrap;
+ }
+
+ .quality-1 {
+ color: #555;
+ background: #efefef;
+ }
+
+ .quality-2 {
+ color: #237a3b;
+ background: #e5f6e9;
+ }
+
+ .quality-3 {
+ color: #1e6bd6;
+ background: #e7f0fd;
+ }
+
+ .quality-4 {
+ color: #7c3fc2;
+ background: #f0e7fb;
+ }
+
+ .quality-5 {
+ color: #c76b00;
+ background: #fff0d9;
+ }
+
+ .tip-list {
+ margin: 0 0 22px;
+ padding: 0;
+ list-style: none;
+ }
+
+ .tip-list li {
+ position: relative;
+ margin-bottom: 9px;
+ padding-left: 22px;
+ color: #555;
+ font-size: 19px;
+ line-height: 1.8;
+ }
+
+ .tip-list li::before {
+ position: absolute;
+ top: 14px;
+ left: 2px;
+ width: 8px;
+ height: 8px;
+ background: #8a4fd3;
+ border-radius: 50%;
+ content: "";
+ }
+
+ /* ==============================
+ 主信息参数
+ ============================== */
+
+ .stat-grid {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(120px, 1fr));
+ gap: 14px;
+ }
+
+ .stat-item {
+ padding: 18px 15px;
+ background: #f7f7fa;
+ border: 1px solid #ededf2;
+ border-radius: 13px;
+ text-align: center;
+ }
+
+ .stat-label {
+ margin-bottom: 8px;
+ color: #777;
+ font-size: 16px;
+ }
+
+ .stat-value {
+ color: #333;
+ font-size: 22px;
+ line-height: 1.4;
+ font-weight: 800;
+ }
+
+ /* ==============================
+ 汇总区域
+ ============================== */
+
+ .summary-section {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1px;
+ background: #e9e9ef;
+ border-top: 1px solid #e9e9ef;
+ }
+
+ .summary-panel {
+ padding: 28px 30px 30px;
+ background: #fff;
+ text-align: left;
+ }
+
+ .summary-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-bottom: 20px;
+ font-size: 25px;
+ font-weight: 800;
+ }
+
+ .summary-title.market {
+ color: #d93939;
+ }
+
+ .summary-title.cost {
+ color: #2e8b57;
+ }
+
+ .summary-count {
+ color: #888;
+ font-size: 16px;
+ font-weight: 500;
+ }
+
+ .price-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 14px;
+ }
+
+ .price-box {
+ padding: 20px 14px;
+ background: #f8f8fa;
+ border-radius: 13px;
+ text-align: center;
+ }
+
+ .price-box-label {
+ margin-bottom: 10px;
+ color: #777;
+ font-size: 16px;
+ }
+
+ .price-box-value {
+ color: #333;
+ font-size: 21px;
+ font-weight: 800;
+ line-height: 1.55;
+ }
+
+ .price-box.lowest .price-box-value {
+ color: #2e8b57;
+ }
+
+ .price-box.average .price-box-value {
+ color: #d98b16;
+ }
+
+ .price-box.highest .price-box-value {
+ color: #d93939;
+ }
+
+ .summary-footer {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 12px 22px;
+ margin-top: 18px;
+ color: #777;
+ font-size: 16px;
+ line-height: 1.7;
+ }
+
+ .subtotal-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 16px;
+ margin-top: 17px;
+ padding: 17px 18px;
+ background: #f4f4f8;
+ border-radius: 11px;
+ }
+
+ .subtotal-label {
+ color: #555;
+ font-size: 18px;
+ }
+
+ .subtotal-value {
+ color: #2e8b57;
+ font-size: 23px;
+ font-weight: 800;
+ }
+
+ /* ==============================
+ 游戏币显示
+ ============================== */
+
+ .money {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 4px;
+ }
+
+ .money-gold {
+ color: #cc8b00;
+ }
+
+ .money-silver {
+ color: #697581;
+ }
+
+ .money-copper {
+ color: #b5673b;
+ }
+
+ .raw-price {
+ display: block;
+ margin-top: 5px;
+ color: #999;
+ font-size: 14px;
+ font-weight: 500;
+ }
+
+ /* ==============================
+ 材料区域标题
+ ============================== */
+
+ .material-section {
+ margin-top: 12px;
+ }
+
+ .section-header {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: 20px;
+ margin-bottom: 18px;
+ }
+
+ .section-title {
+ margin: 0;
+ font-size: 31px;
+ font-weight: 800;
+ color: #222;
+ }
+
+ .section-desc {
+ color: #777;
+ font-size: 18px;
+ line-height: 1.6;
+ }
+
+ /* ==============================
+ 材料卡片
+ ============================== */
+
+ .material-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 22px;
+ }
+
+ .material-card {
+ overflow: hidden;
+ background: #fff;
+ border: 1px solid #e5e5eb;
+ border-radius: 17px;
+ box-shadow: 0 5px 18px rgba(0, 0, 0, 0.07);
+ text-align: left;
+ transition:
+ transform 0.18s ease,
+ box-shadow 0.18s ease;
+ }
+
+ .material-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 9px 25px rgba(0, 0, 0, 0.11);
+ }
+
+ .material-card-header {
+ display: grid;
+ grid-template-columns: 98px minmax(0, 1fr) auto;
+ gap: 18px;
+ align-items: center;
+ padding: 22px;
+ border-bottom: 1px solid #eeeeef;
+ }
+
+ .material-icon {
+ display: flex;
+ width: 98px;
+ height: 98px;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ background: #f4f1f9;
+ border: 2px solid rgba(138, 79, 211, 0.18);
+ border-radius: 14px;
+ }
+
+ .material-icon img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ .material-info {
+ min-width: 0;
+ }
+
+ .material-name {
+ margin: 0 0 10px;
+ font-size: 26px;
+ line-height: 1.35;
+ font-weight: 800;
+ color: #222;
+ }
+
+ .material-meta {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 9px;
+ }
+
+ .material-tag {
+ display: inline-flex;
+ align-items: center;
+ min-height: 31px;
+ padding: 5px 11px;
+ border-radius: 999px;
+ color: #666;
+ background: #f0f0f3;
+ font-size: 15px;
+ line-height: 1.3;
+ font-weight: 600;
+ }
+
+ .tag-trading {
+ color: #1e6bd6;
+ background: #e9f1fe;
+ }
+
+ .tag-store {
+ color: #2e8b57;
+ background: #e7f5ed;
+ }
+
+ .tag-quality-1 {
+ color: #555;
+ background: #efefef;
+ }
+
+ .tag-quality-2 {
+ color: #237a3b;
+ background: #e5f6e9;
+ }
+
+ .tag-quality-3 {
+ color: #1e6bd6;
+ background: #e7f0fd;
+ }
+
+ .tag-quality-4 {
+ color: #7c3fc2;
+ background: #f0e7fb;
+ }
+
+ .tag-quality-5 {
+ color: #c76b00;
+ background: #fff0d9;
+ }
+
+ .material-quantity {
+ min-width: 90px;
+ text-align: right;
+ }
+
+ .quantity-label {
+ margin-bottom: 4px;
+ color: #888;
+ font-size: 15px;
+ }
+
+ .quantity-value {
+ color: #333;
+ font-size: 29px;
+ font-weight: 800;
+ }
+
+ .material-card-body {
+ padding: 20px 22px 23px;
+ }
+
+ .material-price-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 12px;
+ }
+
+ .material-price-item {
+ padding: 15px 10px;
+ background: #f8f8fa;
+ border-radius: 11px;
+ text-align: center;
+ }
+
+ .material-price-label {
+ margin-bottom: 8px;
+ color: #777;
+ font-size: 15px;
+ }
+
+ .material-price-value {
+ color: #333;
+ font-size: 18px;
+ font-weight: 700;
+ line-height: 1.55;
+ }
+
+ .material-total {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 14px;
+ margin-top: 16px;
+ padding: 16px 17px;
+ background: #f5f5f8;
+ border-radius: 10px;
+ }
+
+ .material-total-label {
+ color: #555;
+ font-size: 17px;
+ }
+
+ .material-total-value {
+ color: #d93939;
+ font-size: 21px;
+ font-weight: 800;
+ }
+
+ .material-footer {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 10px 18px;
+ margin-top: 15px;
+ color: #888;
+ font-size: 15px;
+ line-height: 1.6;
+ }
+
+ /* ==============================
+ 商店材料
+ ============================== */
+
+ .store-price-box {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+ }
+
+ .store-price-item {
+ padding: 20px 14px;
+ background: #f8f8fa;
+ border-radius: 11px;
+ text-align: center;
+ }
+
+ .store-price-label {
+ margin-bottom: 9px;
+ color: #777;
+ font-size: 16px;
+ }
+
+ .store-price-value {
+ color: #2e8b57;
+ font-size: 22px;
+ font-weight: 800;
+ line-height: 1.55;
+ }
+
+ .empty-card {
+ grid-column: 1 / -1;
+ min-height: 170px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #fff;
+ border: 2px dashed #ccc;
+ border-radius: 16px;
+ color: #888;
+ font-size: 20px;
+ }
+
+ /* ==============================
+ 响应式
+ ============================== */
+
+ @media (max-width: 1100px) {
+ .main-card-top {
+ grid-template-columns: 180px minmax(0, 1fr);
+ }
+
+ .main-icon {
+ width: 165px;
+ height: 165px;
+ }
+
+ .stat-grid {
+ grid-template-columns: repeat(3, minmax(120px, 1fr));
+ }
+
+ .summary-section {
+ grid-template-columns: 1fr;
+ }
+
+ .material-grid {
+ grid-template-columns: 1fr;
+ }
+ }
+
+ @media (max-width: 760px) {
+ body {
+ padding: 12px;
+ font-size: 17px;
+ }
+
+ .page-title {
+ font-size: 31px;
+ }
+
+ .page-subtitle {
+ font-size: 17px;
+ }
+
+ .main-card-top {
+ grid-template-columns: 1fr;
+ padding: 22px 17px;
+ }
+
+ .main-icon-wrapper {
+ justify-content: flex-start;
+ }
+
+ .main-icon {
+ width: 135px;
+ height: 135px;
+ }
+
+ .main-name {
+ font-size: 29px;
+ }
+
+ .tip-list li {
+ font-size: 17px;
+ }
+
+ .stat-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .stat-value {
+ font-size: 19px;
+ }
+
+ .summary-panel {
+ padding: 22px 17px;
+ }
+
+ .summary-title {
+ font-size: 22px;
+ }
+
+ .price-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .section-header {
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 7px;
+ }
+
+ .section-title {
+ font-size: 27px;
+ }
+
+ .section-desc {
+ font-size: 16px;
+ }
+
+ .material-card-header {
+ grid-template-columns: 78px minmax(0, 1fr);
+ padding: 18px;
+ }
+
+ .material-icon {
+ width: 78px;
+ height: 78px;
+ }
+
+ .material-name {
+ font-size: 22px;
+ }
+
+ .material-quantity {
+ grid-column: 1 / -1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-width: 0;
+ text-align: left;
+ }
+
+ .quantity-value {
+ font-size: 24px;
+ }
+
+ .material-price-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .store-price-box {
+ grid-template-columns: 1fr;
+ }
+ }
diff --git a/templates/chengjiu.html b/templates/styles/pages/chengjiu.css
similarity index 59%
rename from templates/chengjiu.html
rename to templates/styles/pages/chengjiu.css
index 5083453..dfa424e 100644
--- a/templates/chengjiu.html
+++ b/templates/styles/pages/chengjiu.css
@@ -1,59 +1,4 @@
-
-
-
-
-
-
{{ roleName | default("角色") }} - 成就查询
-
-
-
-
-
-
-
-
-
- {% if data %}
- {% for item in data %}
-
-
-
-
- {{ item.desc | default("暂无成就说明", true) }}
-
-
-
-
- {% endfor %}
- {% else %}
-
- 暂未查询到成就数据
-
- {% endif %}
-
-
-
-
\ No newline at end of file
diff --git a/templates/styles/pages/diaoluo.css b/templates/styles/pages/diaoluo.css
new file mode 100644
index 0000000..44bc528
--- /dev/null
+++ b/templates/styles/pages/diaoluo.css
@@ -0,0 +1,46 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .item-col {
+ min-width: 120px;
+ color: #d93939;
+ font-weight: 800;
+ white-space: nowrap;
+ }
+
+ .server-col {
+ min-width: 90px;
+ text-align: center;
+ white-space: nowrap;
+ }
+
+ .map-col {
+ max-width: 260px;
+ color: #333;
+ word-break: break-word;
+ line-height: 1.45;
+ }
+
+ .role-col {
+ max-width: 260px;
+ color: #333;
+ font-weight: 700;
+ word-break: break-word;
+ line-height: 1.45;
+ }
+
+ .time-col {
+ min-width: 150px;
+ color: #60657a;
+ text-align: center;
+ white-space: nowrap;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/dilujilu.css b/templates/styles/pages/dilujilu.css
new file mode 100644
index 0000000..c28ade6
--- /dev/null
+++ b/templates/styles/pages/dilujilu.css
@@ -0,0 +1,21 @@
+table {
+ margin-top: 10px;
+ text-align: left;
+ }
+
+ .text-col {
+ max-width: 360px;
+ line-height: 1.5em;
+ word-break: break-word;
+ font-weight: 600;
+ color: #333;
+ }
+
+ /* ===== 阵营整行着色 ===== */
+ tr.camp-haoqi {
+ color: #1e6bd6; /* 浩气盟 */
+ }
+
+ tr.camp-eren {
+ color: #d93939; /* 恶人谷 */
+ }
diff --git a/templates/fubenjilu.html b/templates/styles/pages/fubenjilu.css
similarity index 55%
rename from templates/fubenjilu.html
rename to templates/styles/pages/fubenjilu.css
index 95dbb95..b3aba07 100644
--- a/templates/fubenjilu.html
+++ b/templates/styles/pages/fubenjilu.css
@@ -1,37 +1,4 @@
-
-
-
-
-
副本奖励记录
-
-
-
-
-
-
-
-
副本奖励记录
-
-
- 灰色表示奖励已经获取,绿色表示奖励尚未获取
-
-
-
-
-
- 奖励已获取
-
-
-
-
- 奖励未获取
-
-
-
- {% if list %}
-
-
-
- | 副本名称 |
- Boss 奖励状态 |
-
-
-
-
- {% for m in list %}
-
- |
-
- {{ m.mapName }}
-
-
-
- 已获取 {{ m.bossFinished }} / {{ m.bossCount }}
-
- |
-
-
-
- {% for boss in m.bossProgress %}
-
-
- #{{ boss.progressId }}
-
-
-
- {{ boss.name }}
-
-
-
- {% if boss.finished %}
- ✓
- 奖励已获取
- {% else %}
- !
- 奖励未获取
- {% endif %}
-
-
- {% endfor %}
-
- |
-
- {% endfor %}
-
-
-
- {% else %}
-
- 暂无副本奖励记录
-
- {% endif %}
-
-
-
-
\ No newline at end of file
diff --git a/templates/styles/pages/guanaishouling.css b/templates/styles/pages/guanaishouling.css
new file mode 100644
index 0000000..26543eb
--- /dev/null
+++ b/templates/styles/pages/guanaishouling.css
@@ -0,0 +1,78 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }
+
+ .section {
+ margin-bottom: 24px;
+ }
+
+ .section-title {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin: 0 0 12px 0;
+ color: #222;
+ font-size: 22px;
+ font-weight: 800;
+ }
+
+ .section-title::before {
+ content: "";
+ display: block;
+ width: 5px;
+ height: 28px;
+ background: #d93939;
+ border-radius: 3px;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ tbody tr.camp-eren {
+ color: #b91c1c;
+ }
+
+ tbody tr.camp-haoqi {
+ color: #1d4ed8;
+ }
+
+ .camp-col,
+ .status-col {
+ width: 90px;
+ text-align: center;
+ white-space: nowrap;
+ font-weight: 800;
+ }
+
+ .castle-col {
+ min-width: 120px;
+ font-weight: 800;
+ color: #333;
+ white-space: nowrap;
+ }
+
+ tr.camp-eren .castle-col {
+ color: #b91c1c;
+ }
+
+ tr.camp-haoqi .castle-col {
+ color: #1d4ed8;
+ }
+
+ .time-col,
+ .remain-col {
+ min-width: 150px;
+ color: #60657a;
+ text-align: center;
+ white-space: nowrap;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .remain-col {
+ color: #d93939;
+ font-weight: 800;
+ }
diff --git a/templates/styles/pages/helps.css b/templates/styles/pages/helps.css
new file mode 100644
index 0000000..e0a7e6f
--- /dev/null
+++ b/templates/styles/pages/helps.css
@@ -0,0 +1,238 @@
+.help-page {
+ position: relative;
+ padding: 22px;
+ overflow: hidden;
+ border: 1px solid rgba(224, 200, 141, 0.72);
+ border-radius: 20px;
+ background:
+ linear-gradient(135deg, rgba(255, 255, 255, 0.035), transparent 24%),
+ rgba(8, 27, 49, 0.92);
+ box-shadow: inset 0 0 32px rgba(126, 161, 216, 0.06);
+}
+
+.help-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 24px;
+ margin-bottom: 18px;
+}
+
+.top-name {
+ color: #fff0c8;
+ font-size: 24px;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+}
+
+.top-summary {
+ margin-top: 5px;
+ font-size: 14px;
+}
+
+.top-desc {
+ font-size: 14px;
+ line-height: 1.75;
+ text-align: right;
+}
+
+.section-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 16px;
+ align-items: start;
+}
+
+.section {
+ min-width: 0;
+ overflow: hidden;
+}
+
+.section.span-2 {
+ grid-column: span 2;
+}
+
+.section.span-3 {
+ grid-column: 1 / -1;
+}
+
+.section-title {
+ padding: 11px 15px;
+ border-bottom: 1px solid rgba(220, 199, 145, 0.3);
+ background: linear-gradient(90deg, rgba(198, 165, 93, 0.24), rgba(105, 138, 194, 0.08));
+ font-size: 19px;
+ font-weight: 800;
+ letter-spacing: 0.05em;
+}
+
+.section-body {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 0 18px;
+ padding: 7px 13px 10px;
+}
+
+.span-2 .section-body {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.feature {
+ display: grid;
+ grid-template-columns: 88px minmax(0, 1fr);
+ gap: 9px;
+ align-items: start;
+ min-width: 0;
+ padding: 8px 0;
+ border-bottom: 1px dashed rgba(186, 205, 239, 0.15);
+}
+
+.feature-name {
+ padding-top: 3px;
+ font-size: 14px;
+ line-height: 1.5;
+ font-weight: 700;
+}
+
+.commands {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+ min-width: 0;
+}
+
+.command {
+ display: inline-block;
+ padding: 3px 7px;
+ border: 1px solid rgba(157, 184, 228, 0.22);
+ border-radius: 5px;
+ font-size: 12px;
+ line-height: 1.5;
+ word-break: break-all;
+}
+
+.ranking-section .section-body {
+ display: block;
+}
+
+.ranking-feature {
+ grid-template-columns: 88px minmax(0, 1fr);
+}
+
+.ranking-list {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 5px;
+}
+
+.ranking-list .command {
+ width: 100%;
+}
+
+.trial-feature {
+ margin-top: 4px;
+}
+
+.full-row {
+ grid-column: 1 / -1;
+}
+
+.push-section .section-body {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.section--danger {
+ border-color: rgba(213, 106, 99, 0.54) !important;
+}
+
+.section--danger .section-title {
+ color: #ffd6d3;
+ background: linear-gradient(90deg, rgba(176, 65, 65, 0.3), rgba(105, 138, 194, 0.06));
+}
+
+.section--success {
+ border-color: rgba(98, 188, 146, 0.52) !important;
+}
+
+.section--success .section-title {
+ color: #d8ffeb;
+ background: linear-gradient(90deg, rgba(39, 148, 103, 0.28), rgba(105, 138, 194, 0.06));
+}
+
+.help-footer {
+ margin-top: 17px;
+ padding: 13px 16px 2px;
+ border-top: 1px solid rgba(216, 195, 140, 0.24);
+ font-size: 13px;
+ line-height: 1.8;
+ text-align: center;
+}
+
+.help-footer strong {
+ color: #e6d4a4;
+}
+
+@media (max-width: 1180px) {
+ .section-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .section.span-3 {
+ grid-column: 1 / -1;
+ }
+
+ .section.span-2 {
+ grid-column: span 2;
+ }
+
+ .ranking-list {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .push-section .section-body {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 760px) {
+ .help-page {
+ padding: 13px;
+ border-radius: 14px;
+ }
+
+ .help-header {
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .top-name {
+ font-size: 20px;
+ }
+
+ .top-desc {
+ text-align: left;
+ }
+
+ .section-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .section.span-2,
+ .section.span-3 {
+ grid-column: span 1;
+ }
+
+ .span-2 .section-body,
+ .push-section .section-body {
+ grid-template-columns: 1fr;
+ }
+
+ .feature,
+ .ranking-feature {
+ grid-template-columns: 78px minmax(0, 1fr);
+ }
+
+ .ranking-list {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/templates/styles/pages/huajia.css b/templates/styles/pages/huajia.css
new file mode 100644
index 0000000..0ae9ec2
--- /dev/null
+++ b/templates/styles/pages/huajia.css
@@ -0,0 +1,26 @@
+h2 {
+ margin-top: 40px;
+ margin-bottom: 12px;
+ color: #d93939;
+ font-size: 26px;
+ font-weight: 700;
+ }table {
+ margin-bottom: 20px;
+ }th, td {
+ text-align: left;
+ }
+
+ .lines {
+ color: #555;
+ font-weight: 600;
+ }
+
+ .price {
+ color: #1e6bd6;
+ font-weight: 700;
+ }
+
+ .empty {
+ color: #999;
+ font-style: italic;
+ }
diff --git a/templates/styles/pages/jiaoyihang.css b/templates/styles/pages/jiaoyihang.css
new file mode 100644
index 0000000..65524d0
--- /dev/null
+++ b/templates/styles/pages/jiaoyihang.css
@@ -0,0 +1,107 @@
+.header {
+ background: #ffffff;
+ border: 1px solid #e6e8ee;
+ border-radius: 8px;
+ padding: 22px 26px;
+ margin-bottom: 16px;
+ box-shadow: 0 6px 18px rgba(35, 38, 47, 0.06);
+ }
+
+ .meta {
+ display: flex;
+ gap: 18px;
+ margin-top: 10px;
+ font-size: 15px;
+ color: #667085;
+ }
+
+ .table-wrapper {
+ background: #ffffff;
+ border: 1px solid #e6e8ee;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 6px 18px rgba(35, 38, 47, 0.06);
+ }table {
+ table-layout: fixed;
+ }
+
+ th {
+ padding: 14px 16px;
+ font-size: 16px;
+ font-weight: 700;
+ text-align: left;
+ white-space: nowrap;
+ }
+
+ th:nth-child(2),
+ th:nth-child(3),
+ th:nth-child(4),
+ td:nth-child(2),
+ td:nth-child(3),
+ td:nth-child(4) {
+ text-align: center;
+ }
+
+ td {
+ padding: 14px 16px;
+ border-bottom: 1px solid #edf0f5;
+ font-size: 15px;
+ vertical-align: middle;
+ }
+
+ .item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ }
+
+ .item-icon {
+ width: 34px;
+ height: 34px;
+ border-radius: 6px;
+ flex: 0 0 auto;
+ background: #edf0f5;
+ }
+
+ .item-name {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-weight: 700;
+ color: #20232a;
+ }
+
+ .price {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ white-space: nowrap;
+ font-weight: 700;
+ color: #2f343d;
+ }
+
+ .money-part {
+ display: inline-flex;
+ align-items: center;
+ gap: 2px;
+ margin-right: 3px;
+ }
+
+ .money-icon {
+ width: 17px;
+ height: 17px;
+ object-fit: contain;
+ }
+
+ .sample {
+ font-weight: 700;
+ color: #2f343d;
+ }
+
+ .time {
+ color: #667085;
+ font-size: 14px;
+ white-space: nowrap;
+ }
diff --git a/templates/styles/pages/jineng.css b/templates/styles/pages/jineng.css
new file mode 100644
index 0000000..5c4f999
--- /dev/null
+++ b/templates/styles/pages/jineng.css
@@ -0,0 +1,84 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }
+
+ .section {
+ margin-bottom: 24px;
+ }
+
+ .section-title {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin: 0 0 12px 0;
+ color: #222;
+ font-size: 24px;
+ font-weight: 800;
+ }
+
+ .section-title::before {
+ content: "";
+ display: block;
+ width: 5px;
+ height: 30px;
+ background: #2f74d0;
+ border-radius: 3px;
+ }
+
+ .cards {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+ }
+
+ .card {
+ display: grid;
+ grid-template-columns: 54px minmax(0, 1fr);
+ gap: 12px;
+ background: white;
+ border-radius: 12px;
+ padding: 14px;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.08);
+ }
+
+ .icon {
+ width: 48px;
+ height: 48px;
+ border-radius: 8px;
+ object-fit: cover;
+ background: #f1f2f6;
+ }
+
+ .head {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 8px;
+ }
+
+ .name {
+ color: #222;
+ font-size: 18px;
+ font-weight: 800;
+ line-height: 1.25;
+ }
+
+ .tag {
+ color: #5d6680;
+ background: #eef2f8;
+ border-radius: 999px;
+ padding: 2px 8px;
+ font-size: 13px;
+ white-space: nowrap;
+ }
+
+ .desc {
+ color: #333;
+ font-size: 15px;
+ line-height: 1.55;
+ word-break: break-word;
+ }
diff --git a/templates/styles/pages/jingnai.css b/templates/styles/pages/jingnai.css
new file mode 100644
index 0000000..71e3d53
--- /dev/null
+++ b/templates/styles/pages/jingnai.css
@@ -0,0 +1,66 @@
+.meta {
+ margin-bottom: 22px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }
+
+ .summary {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 14px;
+ margin-bottom: 22px;
+ }
+
+ .stat {
+ background: white;
+ border-radius: 12px;
+ padding: 18px 14px;
+ text-align: center;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.08);
+ }
+
+ .label {
+ color: #7a7f99;
+ font-size: 16px;
+ margin-bottom: 8px;
+ }
+
+ .value {
+ color: #d93939;
+ font-size: 30px;
+ line-height: 1;
+ font-weight: 800;
+ font-variant-numeric: tabular-nums;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .skill-col {
+ width: 34%;
+ font-weight: 800;
+ color: #333;
+ word-break: break-word;
+ line-height: 1.45;
+ }
+
+ .leader-col {
+ width: 30%;
+ color: #333;
+ word-break: break-word;
+ line-height: 1.45;
+ }
+
+ .color-col,
+ .level-col {
+ text-align: center;
+ white-space: nowrap;
+ font-weight: 700;
+ }
+
+ .level-col {
+ color: #d93939;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/jinjia.css b/templates/styles/pages/jinjia.css
new file mode 100644
index 0000000..8df31c0
--- /dev/null
+++ b/templates/styles/pages/jinjia.css
@@ -0,0 +1,12 @@
+table {
+ margin-top: 10px;
+ text-align: left; /* 不影响表格内容 */
+ }
+
+ .text-col {
+ max-width: 360px;
+ line-height: 1.5em;
+ word-break: break-word;
+ font-weight: 600;
+ color: #333;
+ }
diff --git a/templates/styles/pages/jinqiqiyu.css b/templates/styles/pages/jinqiqiyu.css
new file mode 100644
index 0000000..59ca324
--- /dev/null
+++ b/templates/styles/pages/jinqiqiyu.css
@@ -0,0 +1,24 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ white-space: nowrap;
+ }
+
+ .event-col {
+ font-weight: 700;
+ color: #333;
+ }
+
+ .role-col {
+ color: #333;
+ }
+
+ .time-col {
+ color: #60657a;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/juesheliaotian.css b/templates/styles/pages/juesheliaotian.css
new file mode 100644
index 0000000..20afbcb
--- /dev/null
+++ b/templates/styles/pages/juesheliaotian.css
@@ -0,0 +1,99 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ margin-top: 10px;
+ table-layout: fixed;
+ text-align: left;
+ }th,
+ td {
+ vertical-align: middle;
+ }
+
+ th {
+ font-weight: 700;
+ text-align: center;
+ }
+
+ /* 角色列 */
+ .role-col {
+ width: 16%;
+ color: #333333;
+ font-weight: 700;
+ text-align: center;
+ word-break: break-word;
+ }
+
+ /* 频道列 */
+ .channel-col {
+ width: 10%;
+ text-align: center;
+ }
+
+ /* 消息列 */
+ .message-col {
+ width: 56%;
+ color: #333333;
+ font-weight: 600;
+ line-height: 1.65;
+ word-break: break-word;
+ overflow-wrap: anywhere;
+ }
+
+ /* 时间列 */
+ .time-col {
+ width: 18%;
+ color: #757b8f;
+ text-align: center;
+ white-space: nowrap;
+ }
+
+ /* 频道标签 */
+ .channel-tag {
+ display: inline-block;
+ min-width: 58px;
+ padding: 5px 10px;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 700;
+ text-align: center;
+ }
+
+ .channel-world {
+ color: #d93939;
+ border: 1px solid #f3bcbc;
+ background: #fff0f0;
+ }
+
+ .channel-school {
+ color: #7d43b7;
+ border: 1px solid #d8c0ef;
+ background: #f6effd;
+ }
+
+ .channel-team {
+ color: #197d5b;
+ border: 1px solid #b8e4d5;
+ background: #edf9f5;
+ }
+
+ .channel-map {
+ color: #1e6bd6;
+ border: 1px solid #bdd3f5;
+ background: #eef5ff;
+ }
+
+ .channel-other {
+ color: #666666;
+ border: 1px solid #d8d8d8;
+ background: #f4f4f4;
+ }
+
+ .empty-row td {
+ padding: 50px 20px;
+ color: #8a8fa3;
+ font-size: 17px;
+ text-align: center;
+ }
diff --git a/templates/styles/pages/juesheqiyu.css b/templates/styles/pages/juesheqiyu.css
new file mode 100644
index 0000000..b705b4e
--- /dev/null
+++ b/templates/styles/pages/juesheqiyu.css
@@ -0,0 +1,57 @@
+.section {
+ margin-bottom: 32px;
+ }
+
+ .section-title {
+ font-size: 36px;
+ font-weight: bold;
+ margin-bottom: 16px;
+ border-left: 4px solid #4f7cff;
+ padding-left: 10px;
+ color: #1f2a44;
+ }
+
+ .card-container {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+ }
+
+ .card {
+ background: #ffffff;
+ border-radius: 8px;
+ padding: 16px 12px;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ min-height: 90px;
+ }
+
+ .card img {
+ width: 100%;
+ max-width: 180px;
+ height: auto; /* 自动保持比例 */
+ object-fit: contain;
+ margin-bottom: 6px;
+ }
+
+ .event-name {
+ font-size: 22px;
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: #2b3a67;
+ }
+
+ .time {
+ font-size: 22px;
+ color: #7a7f99;
+ }
+
+ .empty {
+ color: #999;
+ font-size: 14px;
+ padding: 10px 0;
+ }
diff --git a/templates/styles/pages/mingjianpaihang.css b/templates/styles/pages/mingjianpaihang.css
new file mode 100644
index 0000000..08affc5
--- /dev/null
+++ b/templates/styles/pages/mingjianpaihang.css
@@ -0,0 +1,21 @@
+table {
+ margin-top: 10px;
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ }
+
+ .rank-1 { color: gold; font-weight: bold; }
+ .rank-2 { color: silver; font-weight: bold; }
+ .rank-3 { color: #cd7f32; font-weight: bold; }
+
+ .winrate {
+ color: #27ae60;
+ font-weight: bold;
+ }
diff --git a/templates/styles/pages/mingjiantongji.css b/templates/styles/pages/mingjiantongji.css
new file mode 100644
index 0000000..71cc363
--- /dev/null
+++ b/templates/styles/pages/mingjiantongji.css
@@ -0,0 +1,34 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ white-space: nowrap;
+ vertical-align: middle;
+ }
+
+ .school {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 700;
+ color: #333;
+ }
+
+ .school-icon {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ object-fit: contain;
+ flex: 0 0 auto;
+ }
+
+ .num-col {
+ color: #d93939;
+ font-weight: 700;
+ text-align: center;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/qiwu.css b/templates/styles/pages/qiwu.css
new file mode 100644
index 0000000..09b355d
--- /dev/null
+++ b/templates/styles/pages/qiwu.css
@@ -0,0 +1,84 @@
+.item {
+ display: flex;
+ background: #fff;
+ border-radius: 14px;
+ box-shadow: 0 6px 18px rgba(0,0,0,0.08);
+ margin-bottom: 22px;
+ overflow: hidden;
+ }
+
+ .item img {
+ width: 200px;
+ height: 200px;
+ object-fit: contain;
+ background: #fafafa;
+ padding: 14px;
+ }
+
+ .info {
+ flex: 1;
+ padding: 18px 22px;
+ }
+
+ .name {
+ font-size: 26px;
+ font-weight: 800;
+ margin-bottom: 6px;
+ }
+
+ /* 品质颜色 */
+ .q1 { color: #999; }
+ .q2 { color: #2ecc71; }
+ .q3 { color: #3498db; }
+ .q4 { color: #9b59b6; }
+ .q5 { color: #e67e22; }
+
+ .meta {
+ font-size: 15px;
+ color: #555;
+ margin-bottom: 12px;
+ }
+
+ .meta span {
+ margin-right: 16px;
+ }
+
+ .tip {
+ background: #f8f9fb;
+ border-left: 4px solid #d93939;
+ padding: 8px 12px;
+ font-size: 14px;
+ color: #555;
+ margin-bottom: 14px;
+ }
+
+ .attrs {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 10px;
+ }
+
+ .attr {
+ background: #fafafa;
+ border-radius: 10px;
+ padding: 10px;
+ text-align: center;
+ }
+
+ .attr-name {
+ font-size: 13px;
+ color: #777;
+ margin-bottom: 4px;
+ }
+
+ .attr-value {
+ font-size: 17px;
+ font-weight: 700;
+ color: #333;
+ }
+
+ .footer {
+ margin-top: 10px;
+ font-size: 14px;
+ color: #777;
+ }
diff --git a/templates/qixue.html b/templates/styles/pages/qixue.css
similarity index 51%
rename from templates/qixue.html
rename to templates/styles/pages/qixue.css
index eb0e5eb..7a2d275 100644
--- a/templates/qixue.html
+++ b/templates/styles/pages/qixue.css
@@ -1,32 +1,4 @@
-
-
-
-
-
奇穴详情
-
-
-
-
-
-
-
{{ name }} 奇穴详情
-
更新时间:{{ update_time }}
-
- {% for group in groups %}
-
-
{{ group['title'] }}
-
- {% for item in group.talents %}
-
- {% if item['icon'] %}
-
![{{ item['name'] }}]({{ item['icon'] }})
- {% else %}
-
- {% endif %}
-
-
- {{ item['name'] }}
- {{ item['class_text'] }}
- {% if item['interval'] %}
- {{ item['interval'] }}
- {% endif %}
-
-
{{ item['desc'] }}
-
-
- {% endfor %}
-
-
- {% endfor %}
-
-
-
-
diff --git a/templates/styles/pages/qiyuhuizong.css b/templates/styles/pages/qiyuhuizong.css
new file mode 100644
index 0000000..20ad96e
--- /dev/null
+++ b/templates/styles/pages/qiyuhuizong.css
@@ -0,0 +1,30 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ white-space: nowrap;
+ }
+
+ .event-col {
+ font-weight: 700;
+ color: #333;
+ }
+
+ .count-col {
+ color: #d93939;
+ font-weight: 700;
+ text-align: center;
+ }
+
+ .role-col {
+ color: #333;
+ }
+
+ .time-col {
+ color: #60657a;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/qiyuliebiao.css b/templates/styles/pages/qiyuliebiao.css
new file mode 100644
index 0000000..8df31c0
--- /dev/null
+++ b/templates/styles/pages/qiyuliebiao.css
@@ -0,0 +1,12 @@
+table {
+ margin-top: 10px;
+ text-align: left; /* 不影响表格内容 */
+ }
+
+ .text-col {
+ max-width: 360px;
+ line-height: 1.5em;
+ word-break: break-word;
+ font-weight: 600;
+ color: #333;
+ }
diff --git a/templates/styles/pages/qufuzhuangtai.css b/templates/styles/pages/qufuzhuangtai.css
new file mode 100644
index 0000000..77a1a1a
--- /dev/null
+++ b/templates/styles/pages/qufuzhuangtai.css
@@ -0,0 +1,54 @@
+.section {
+ margin-bottom: 32px;
+ }
+ .section-title {
+ font-size: 32px;
+ font-weight: bold;
+ margin-bottom: 16px;
+ border-left: 4px solid #4f7cff;
+ padding-left: 10px;
+ color: #1f2a44;
+ }
+
+ .card-container {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+ }
+ .card {
+ border-radius: 8px;
+ padding: 30px 12px;
+ text-align: center;
+ font-weight: 600;
+ font-size: 28px;
+ color: #ffffff;
+ }
+
+ /* 整块背景颜色控制 */
+ .爆满 {
+ background: #d9534f; /* 红 */
+ }
+ .拥挤 {
+ background: #f0ad4e; /* 橙 */
+ color: #3b2e00;
+ }
+ .正常, .顺畅 {
+ background: #4cae4c; /* 绿 */
+ }
+ .维护 {
+ background: #bfbfbf; /* 灰 */
+ color: #333333;
+ }
+
+ .empty {
+ color: #7a7f99;
+ font-size: 16px;
+ padding: 6px 0;
+ }
+
+ @media (max-width: 1000px) {
+ .card-container { grid-template-columns: repeat(2, 1fr); }
+ }
+ @media (max-width: 600px) {
+ .card-container { grid-template-columns: repeat(1, 1fr); }
+ }
diff --git a/templates/styles/pages/rank_role.css b/templates/styles/pages/rank_role.css
new file mode 100644
index 0000000..da20550
--- /dev/null
+++ b/templates/styles/pages/rank_role.css
@@ -0,0 +1,61 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .rank-col,
+ .level-col,
+ .camp-col,
+ .score-col {
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ .rank-col {
+ width: 56px;
+ color: #d93939;
+ font-weight: 800;
+ }
+
+ .force {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: 108px;
+ font-weight: 700;
+ color: #333;
+ white-space: nowrap;
+ }
+
+ .force-icon {
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ object-fit: contain;
+ flex: 0 0 auto;
+ }
+
+ .role-col {
+ min-width: 120px;
+ font-weight: 700;
+ color: #333;
+ white-space: nowrap;
+ }
+
+ .tong-col {
+ max-width: 260px;
+ word-break: break-word;
+ color: #333;
+ }
+
+ .score-col {
+ color: #d93939;
+ font-weight: 800;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/rank_tong0.css b/templates/styles/pages/rank_tong0.css
new file mode 100644
index 0000000..bb78ddc
--- /dev/null
+++ b/templates/styles/pages/rank_tong0.css
@@ -0,0 +1,53 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .rank-col,
+ .people-col,
+ .score-col {
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ .rank-col {
+ width: 56px;
+ color: #d93939;
+ font-weight: 800;
+ }
+
+ .tong-col {
+ min-width: 220px;
+ font-weight: 800;
+ color: #333;
+ word-break: break-word;
+ }
+
+ .master-col {
+ min-width: 150px;
+ color: #333;
+ word-break: break-word;
+ }
+
+ .castle-col {
+ min-width: 110px;
+ color: #60657a;
+ word-break: break-word;
+ }
+
+ .people-col {
+ color: #333;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .score-col {
+ color: #d93939;
+ font-weight: 800;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/rank_tong1.css b/templates/styles/pages/rank_tong1.css
new file mode 100644
index 0000000..bb78ddc
--- /dev/null
+++ b/templates/styles/pages/rank_tong1.css
@@ -0,0 +1,53 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .rank-col,
+ .people-col,
+ .score-col {
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ .rank-col {
+ width: 56px;
+ color: #d93939;
+ font-weight: 800;
+ }
+
+ .tong-col {
+ min-width: 220px;
+ font-weight: 800;
+ color: #333;
+ word-break: break-word;
+ }
+
+ .master-col {
+ min-width: 150px;
+ color: #333;
+ word-break: break-word;
+ }
+
+ .castle-col {
+ min-width: 110px;
+ color: #60657a;
+ word-break: break-word;
+ }
+
+ .people-col {
+ color: #333;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .score-col {
+ color: #d93939;
+ font-weight: 800;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/rank_tong2.css b/templates/styles/pages/rank_tong2.css
new file mode 100644
index 0000000..bb78ddc
--- /dev/null
+++ b/templates/styles/pages/rank_tong2.css
@@ -0,0 +1,53 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .rank-col,
+ .people-col,
+ .score-col {
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ .rank-col {
+ width: 56px;
+ color: #d93939;
+ font-weight: 800;
+ }
+
+ .tong-col {
+ min-width: 220px;
+ font-weight: 800;
+ color: #333;
+ word-break: break-word;
+ }
+
+ .master-col {
+ min-width: 150px;
+ color: #333;
+ word-break: break-word;
+ }
+
+ .castle-col {
+ min-width: 110px;
+ color: #60657a;
+ word-break: break-word;
+ }
+
+ .people-col {
+ color: #333;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .score-col {
+ color: #d93939;
+ font-weight: 800;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/richangyuche.css b/templates/styles/pages/richangyuche.css
new file mode 100644
index 0000000..9ae76db
--- /dev/null
+++ b/templates/styles/pages/richangyuche.css
@@ -0,0 +1,51 @@
+/* ====== 日历网格 ====== */
+.calendar {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 14px;
+}
+
+/* 表头 */
+.week {
+ text-align: center;
+ font-size: 18px;
+ font-weight: 700;
+ color: #555;
+}
+
+/* 卡片基础 */
+.card {
+ min-height: 120px;
+ border-radius: 12px;
+ padding: 12px;
+ box-sizing: border-box;
+ background: transparent;
+}
+
+/* 状态样式 */
+.card.past {
+ background: #e5e5e5;
+ color: #666;
+}
+
+.card.today {
+ background: #3b82f6;
+ color: #fff;
+}
+
+.card.future {
+ background: #ffffff;
+ color: #333;
+ border: 1px solid #ddd;
+}
+
+/* 内容 */
+.date {
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.line {
+ font-size: 16px;
+ margin-top: 6px;
+}
diff --git a/templates/styles/pages/shilianpaixing.css b/templates/styles/pages/shilianpaixing.css
new file mode 100644
index 0000000..df5d8e1
--- /dev/null
+++ b/templates/styles/pages/shilianpaixing.css
@@ -0,0 +1,34 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .rank-col,
+ .number-col {
+ white-space: nowrap;
+ text-align: center;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .rank-col {
+ width: 60px;
+ color: #d93939;
+ font-weight: 800;
+ }
+
+ .role-col {
+ min-width: 180px;
+ font-weight: 700;
+ color: #333;
+ }
+
+ .score-col {
+ color: #d93939;
+ font-weight: 800;
+ }
diff --git a/templates/styles/pages/shitu.css b/templates/styles/pages/shitu.css
new file mode 100644
index 0000000..452e1b5
--- /dev/null
+++ b/templates/styles/pages/shitu.css
@@ -0,0 +1,49 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .force-col {
+ min-width: 120px;
+ }
+
+ .force {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 700;
+ color: #333;
+ white-space: nowrap;
+ }
+
+ .force-icon {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ object-fit: contain;
+ flex: 0 0 auto;
+ }
+
+ .name-col {
+ font-weight: 700;
+ color: #333;
+ white-space: nowrap;
+ }
+
+ .small-col {
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ .comment-col {
+ max-width: 420px;
+ line-height: 1.5;
+ word-break: break-word;
+ color: #333;
+ }
diff --git a/templates/styles/pages/temp_test.css b/templates/styles/pages/temp_test.css
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/templates/styles/pages/temp_test.css
@@ -0,0 +1 @@
+
diff --git a/templates/styles/pages/tuanduizhaomu.css b/templates/styles/pages/tuanduizhaomu.css
new file mode 100644
index 0000000..8df31c0
--- /dev/null
+++ b/templates/styles/pages/tuanduizhaomu.css
@@ -0,0 +1,12 @@
+table {
+ margin-top: 10px;
+ text-align: left; /* 不影响表格内容 */
+ }
+
+ .text-col {
+ max-width: 360px;
+ line-height: 1.5em;
+ word-break: break-word;
+ font-weight: 600;
+ color: #333;
+ }
diff --git a/templates/styles/pages/weizuoqiyu.css b/templates/styles/pages/weizuoqiyu.css
new file mode 100644
index 0000000..41a2d07
--- /dev/null
+++ b/templates/styles/pages/weizuoqiyu.css
@@ -0,0 +1,76 @@
+.header {
+ margin-bottom: 24px;
+ color: #1f2a44;
+ }
+
+ .title {
+ font-size: 34px;
+ font-weight: 800;
+ margin-bottom: 8px;
+ }
+
+ .meta {
+ font-size: 20px;
+ color: #7a7f99;
+ }
+
+ .section {
+ margin-bottom: 32px;
+ }
+
+ .section-title {
+ font-size: 36px;
+ font-weight: bold;
+ margin-bottom: 16px;
+ border-left: 4px solid #4f7cff;
+ padding-left: 10px;
+ color: #1f2a44;
+ }
+
+ .card-container {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+ }
+
+ .card {
+ background: #ffffff;
+ border-radius: 8px;
+ padding: 16px 12px;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ min-height: 90px;
+ }
+
+ .card img {
+ width: 100%;
+ max-width: 180px;
+ height: auto;
+ object-fit: contain;
+ margin-bottom: 6px;
+ }
+
+ .event-name {
+ font-size: 22px;
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: #2b3a67;
+ }
+
+ .time {
+ font-size: 22px;
+ color: #7a7f99;
+ }
+
+ .empty {
+ background: #ffffff;
+ border-radius: 8px;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.06);
+ color: #7a7f99;
+ font-size: 20px;
+ padding: 22px;
+ }
diff --git a/templates/styles/pages/wujia.css b/templates/styles/pages/wujia.css
new file mode 100644
index 0000000..d2635d1
--- /dev/null
+++ b/templates/styles/pages/wujia.css
@@ -0,0 +1,155 @@
+.goods-image {
+ display: block;
+ width: 100%;
+ border-radius: 16px;
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12);
+ margin-bottom: 14px;
+ }
+
+ .goods-desc {
+ font-size: 16px;
+ line-height: 1.8;
+ color: #444;
+ margin: 0 0 10px;
+ text-align: left;
+ }
+
+ .meta-row {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 20px 40px;
+ font-size: 18px;
+ margin-bottom: 30px;
+ }
+
+ .meta-row .price {
+ color: #d93939;
+ font-weight: 800;
+ }
+
+ .table-wrapper {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 20px;
+ align-items: start;
+ }
+
+ .table-card {
+ width: 100%;
+ min-width: 0;
+ }
+
+ .table-title {
+ font-size: 20px;
+ font-weight: 700;
+ margin-bottom: 8px;
+ text-align: center;
+ color: #d93939;
+ }
+
+ .table-scroll {
+ width: 100%;
+ overflow-x: auto;
+ border-radius: 12px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+ }table {
+ min-width: 420px;
+ text-align: left;
+ }th,
+ td {
+ white-space: nowrap;
+ }
+
+ th {
+ font-weight: 700;
+ }
+
+ .server-col {
+ font-weight: 600;
+ }
+
+ .price-col {
+ color: #d93939;
+ font-weight: 700;
+ }
+
+ .empty-row {
+ height: 80px;
+ padding: 28px 10px;
+ text-align: center;
+ color: #999999;
+ font-weight: 400;
+ }
+
+ /* ===== 万宝楼公示(蓝色) ===== */
+
+ .table-card.notice .table-title {
+ color: #1e6bd6;
+ }
+
+ .table-card.notice .price-col {
+ color: #1e6bd6;
+ }
+
+ .table-card.notice thead {
+ background: #1e6bd6;
+ }
+
+ /* ===== 万宝楼在售(绿色) ===== */
+
+ .table-card.sale .table-title {
+ color: #2e8b57;
+ }
+
+ .table-card.sale .price-col {
+ color: #2e8b57;
+ }
+
+ .table-card.sale thead {
+ background: #2e8b57;
+ }
+
+ /* ===== 查询区服(紫色) ===== */
+
+ .table-card.query .table-title {
+ color: #8a4fd3;
+ }
+
+ .table-card.query .price-col {
+ color: #8a4fd3;
+ }
+
+ .table-card.query thead {
+ background: #8a4fd3;
+ }
+
+ @media (max-width: 1100px) {
+ .table-wrapper {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ }
+
+ @media (max-width: 720px) {
+ body {
+ padding: 12px;
+ }
+
+ h1 {
+ font-size: 24px;
+ }
+
+ .goods-desc {
+ font-size: 14px;
+ }
+
+ .meta-row {
+ gap: 12px 20px;
+ font-size: 16px;
+ }
+
+ .table-wrapper {
+ grid-template-columns: 1fr;
+ }
+ }
diff --git a/templates/styles/pages/xiaoyao.css b/templates/styles/pages/xiaoyao.css
new file mode 100644
index 0000000..c9098b9
--- /dev/null
+++ b/templates/styles/pages/xiaoyao.css
@@ -0,0 +1,18 @@
+table {
+ text-align: center;
+ }
+
+ .purple {
+ color: #a020f0;
+ font-weight: 600;
+ }
+
+ .blue {
+ color: #1e90ff;
+ font-weight: 600;
+ }
+
+ .kungfu {
+ font-weight: bold;
+ color: #333;
+ }
diff --git a/templates/styles/pages/xingxiashijian.css b/templates/styles/pages/xingxiashijian.css
new file mode 100644
index 0000000..d2980f8
--- /dev/null
+++ b/templates/styles/pages/xingxiashijian.css
@@ -0,0 +1,64 @@
+/* ====== 列表 ====== */
+.list {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+/* 卡片 */
+.card {
+ background: #ffffff;
+ border-radius: 12px;
+ padding: 16px 18px;
+ box-shadow: 0 4px 14px rgba(0,0,0,0.06);
+ display: flex;
+ align-items: center;
+}
+
+/* 时间 */
+.time {
+ width: 70px;
+ text-align: center;
+ font-size: 26px;
+ font-weight: 700;
+ color: #2563eb;
+ flex-shrink: 0;
+}
+
+/* 内容 */
+.content {
+ flex: 1;
+ padding: 0 16px;
+}
+
+.stage {
+ font-size: 22px;
+ font-weight: 700;
+ margin-bottom: 6px;
+}
+
+.meta {
+ font-size: 18px;
+ color: #666;
+ margin-bottom: 6px;
+}
+
+.desc {
+ font-size: 20px;
+ color: #444;
+ line-height: 1.5;
+}
+
+/* 图标 */
+.icon {
+ width: 36px;
+ height: 36px;
+ border-radius: 8px;
+ background: #e5e7eb;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ font-weight: 700;
+ color: #374151;
+}
diff --git a/templates/styles/pages/yanhuan.css b/templates/styles/pages/yanhuan.css
new file mode 100644
index 0000000..8df31c0
--- /dev/null
+++ b/templates/styles/pages/yanhuan.css
@@ -0,0 +1,12 @@
+table {
+ margin-top: 10px;
+ text-align: left; /* 不影响表格内容 */
+ }
+
+ .text-col {
+ max-width: 360px;
+ line-height: 1.5em;
+ word-break: break-word;
+ font-weight: 600;
+ color: #333;
+ }
diff --git a/templates/styles/pages/zhanji.css b/templates/styles/pages/zhanji.css
new file mode 100644
index 0000000..55d539d
--- /dev/null
+++ b/templates/styles/pages/zhanji.css
@@ -0,0 +1,158 @@
+.panel {
+ width: 1100px;
+ margin: auto;
+ background: #ffffff;
+ border-radius: 12px;
+ padding: 22px;
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
+}
+
+/* ===== 头部 ===== */
+.header {
+ display: flex;
+ justify-content: space-between;
+ border-bottom: 1px solid #ddd;
+ padding-bottom: 14px;
+}
+
+.role-name {
+ font-size: 40px;
+ font-weight: bold;
+ color: #1f1f1f;
+}
+
+.sub-info {
+ margin-top: 6px;
+ font-size: 22px;
+ color: #666;
+}
+
+/* ===== 头像 + 核心数据 ===== */
+.stats-wrap {
+ display: flex;
+ gap: 24px;
+ margin: 26px 0;
+ align-items: stretch;
+}
+
+/* 头像区域 */
+.avatar-box {
+ width: 280px;
+ height: 360px;
+ background: #e9ebf1;
+ border-radius: 16px;
+ overflow: hidden;
+ flex-shrink: 0;
+}
+
+.avatar-box img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+/* 右侧数据 */
+.stats {
+ flex: 1;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: repeat(2, 1fr);
+ gap: 14px;
+}
+
+/* 单个数据块 */
+.stat-box {
+ background: #f5f6fa;
+ border-radius: 12px;
+ padding: 20px;
+ text-align: center;
+}
+
+.stat-title {
+ font-size: 26px;
+ color: #777;
+}
+
+.stat-value {
+ font-size: 36px;
+ margin-top: 8px;
+ font-weight: bold;
+ color: #222;
+}
+
+.winrate {
+ color: #2fbf71;
+}
+
+.mmr {
+ color: #f5a623;
+}
+
+/* ===== 表格 ===== */table {
+ margin-top: 20px;
+ font-size: 18px;
+ table-layout: fixed;
+}th, td {
+ text-align: center;
+}
+
+th {
+ color: #666;
+ font-weight: normal;
+}
+
+tbody tr:nth-child(odd) {
+ background: #ffffff;
+}
+
+.win {
+ color: #2fbf71;
+ font-weight: bold;
+}
+
+.lose {
+ color: #e65050;
+ font-weight: bold;
+}
+
+.mmr-up {
+ color: #2fbf71;
+}
+
+.mmr-down {
+ color: #e65050;
+}
+
+.mvp {
+ color: #f5a623;
+ font-weight: bold;
+}
+
+.time-col {
+ width: 170px;
+ font-size: 16px;
+ font-variant-numeric: tabular-nums;
+}
+
+.duration-col {
+ width: 92px;
+}
+
+.kungfu-col {
+ width: 150px;
+}
+
+.result-col {
+ width: 64px;
+}
+
+.score-col {
+ width: 104px;
+}
+
+.grade-col {
+ width: 96px;
+}
+
+.mvp-col {
+ width: 58px;
+}
diff --git a/templates/styles/pages/zhengyingpaimai.css b/templates/styles/pages/zhengyingpaimai.css
new file mode 100644
index 0000000..c28ade6
--- /dev/null
+++ b/templates/styles/pages/zhengyingpaimai.css
@@ -0,0 +1,21 @@
+table {
+ margin-top: 10px;
+ text-align: left;
+ }
+
+ .text-col {
+ max-width: 360px;
+ line-height: 1.5em;
+ word-break: break-word;
+ font-weight: 600;
+ color: #333;
+ }
+
+ /* ===== 阵营整行着色 ===== */
+ tr.camp-haoqi {
+ color: #1e6bd6; /* 浩气盟 */
+ }
+
+ tr.camp-eren {
+ color: #d93939; /* 恶人谷 */
+ }
diff --git a/templates/styles/pages/zhenyingevent.css b/templates/styles/pages/zhenyingevent.css
new file mode 100644
index 0000000..c09b3a9
--- /dev/null
+++ b/templates/styles/pages/zhenyingevent.css
@@ -0,0 +1,59 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ tbody tr.camp-eren {
+ background: #fff2f2;
+ color: #b91c1c;
+ }
+
+ tbody tr.camp-haoqi {
+ background: #eff6ff;
+ color: #1d4ed8;
+ }
+
+ .camp-col {
+ width: 90px;
+ text-align: center;
+ white-space: nowrap;
+ font-weight: 800;
+ }
+
+ .fenxian-col,
+ .server-col {
+ width: 130px;
+ text-align: center;
+ white-space: nowrap;
+ font-weight: 700;
+ }
+
+ .role-col {
+ max-width: 320px;
+ color: #333;
+ font-weight: 700;
+ word-break: break-word;
+ line-height: 1.45;
+ }
+
+ tr.camp-eren .role-col {
+ color: #b91c1c;
+ }
+
+ tr.camp-haoqi .role-col {
+ color: #1d4ed8;
+ }
+
+ .time-col {
+ min-width: 150px;
+ color: #60657a;
+ text-align: center;
+ white-space: nowrap;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/zhuangshi.css b/templates/styles/pages/zhuangshi.css
new file mode 100644
index 0000000..48ab99a
--- /dev/null
+++ b/templates/styles/pages/zhuangshi.css
@@ -0,0 +1,84 @@
+.card {
+ display: flex;
+ background: #fff;
+ border-radius: 16px;
+ box-shadow: 0 6px 20px rgba(0,0,0,0.08);
+ margin-bottom: 24px;
+ overflow: hidden;
+ }
+
+ .card img {
+ width: 220px;
+ height: 220px;
+ object-fit: contain;
+ background: #fafafa;
+ padding: 16px;
+ }
+
+ .content {
+ flex: 1;
+ padding: 20px 24px;
+ }
+
+ .title {
+ font-size: 26px;
+ font-weight: 800;
+ margin-bottom: 6px;
+ }
+
+ /* 品质颜色 */
+ .q1 { color: #999; }
+ .q2 { color: #2ecc71; }
+ .q3 { color: #3498db; }
+ .q4 { color: #9b59b6; }
+ .q5 { color: #e67e22; }
+
+ .meta {
+ color: #666;
+ font-size: 15px;
+ margin-bottom: 14px;
+ }
+
+ .meta span {
+ margin-right: 18px;
+ }
+
+ .tip {
+ background: #f8f9fb;
+ border-left: 4px solid #d93939;
+ padding: 10px 14px;
+ font-size: 14px;
+ color: #555;
+ margin-bottom: 16px;
+ }
+
+ .stats {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 12px;
+ }
+
+ .stat {
+ background: #fafafa;
+ border-radius: 10px;
+ padding: 10px;
+ text-align: center;
+ }
+
+ .stat-name {
+ font-size: 13px;
+ color: #777;
+ margin-bottom: 6px;
+ }
+
+ .stat-value {
+ font-size: 18px;
+ font-weight: 700;
+ color: #333;
+ }
+
+ .footer {
+ margin-top: 12px;
+ font-size: 14px;
+ color: #888;
+ }
diff --git a/templates/styles/pages/zhueevent.css b/templates/styles/pages/zhueevent.css
new file mode 100644
index 0000000..e80bf7a
--- /dev/null
+++ b/templates/styles/pages/zhueevent.css
@@ -0,0 +1,34 @@
+.meta {
+ margin-bottom: 24px;
+ color: #7a7f99;
+ font-size: 16px;
+ text-align: center;
+ }table {
+ text-align: left;
+ }th, td {
+ vertical-align: middle;
+ }
+
+ .server-col {
+ width: 160px;
+ text-align: center;
+ white-space: nowrap;
+ font-weight: 800;
+ color: #d93939;
+ }
+
+ .map-col {
+ max-width: 300px;
+ color: #333;
+ font-weight: 700;
+ word-break: break-word;
+ line-height: 1.45;
+ }
+
+ .time-col {
+ min-width: 160px;
+ color: #60657a;
+ text-align: center;
+ white-space: nowrap;
+ font-variant-numeric: tabular-nums;
+ }
diff --git a/templates/styles/pages/zili.css b/templates/styles/pages/zili.css
new file mode 100644
index 0000000..65745cd
--- /dev/null
+++ b/templates/styles/pages/zili.css
@@ -0,0 +1,122 @@
+.header {
+ background: #ffffff;
+ border-radius: 14px;
+ padding: 22px 26px;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.08);
+ margin-bottom: 16px;
+ }
+
+ .title-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px;
+ }
+
+ .role {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ color: #565d70;
+ font-size: 16px;
+ white-space: nowrap;
+ }
+
+ .force-icon {
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ object-fit: contain;
+ }
+
+ .meta {
+ margin-top: 12px;
+ color: #7a7f99;
+ font-size: 15px;
+ line-height: 1.7;
+ }
+
+ .summary {
+ background: #ffffff;
+ border-radius: 14px;
+ padding: 22px 26px;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.08);
+ margin-bottom: 16px;
+ }
+
+ .summary-top,
+ .item-top {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 14px;
+ }
+
+ .summary-name {
+ font-size: 24px;
+ font-weight: 800;
+ color: #222;
+ }
+
+ .summary-percent {
+ font-size: 34px;
+ line-height: 1;
+ font-weight: 900;
+ color: #d93939;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .score {
+ margin-top: 8px;
+ color: #656d80;
+ font-size: 15px;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .progress {
+ height: 14px;
+ margin-top: 14px;
+ border-radius: 999px;
+ overflow: hidden;
+ background: #e7e9ef;
+ }
+
+ .progress-fill {
+ height: 100%;
+ min-width: 3px;
+ border-radius: 999px;
+ background: linear-gradient(90deg, #d93939, #f59e42);
+ }
+
+ .list {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 10px;
+ }
+
+ .item {
+ background: #ffffff;
+ border-radius: 12px;
+ padding: 15px 16px;
+ box-shadow: 0 3px 12px rgba(0,0,0,0.06);
+ }
+
+ .item-name {
+ min-width: 0;
+ font-size: 17px;
+ font-weight: 800;
+ color: #303543;
+ word-break: break-word;
+ }
+
+ .item-percent {
+ color: #d93939;
+ font-size: 18px;
+ font-weight: 900;
+ white-space: nowrap;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .item .score {
+ font-size: 14px;
+ }
diff --git a/templates/zilipaixing.html b/templates/styles/pages/zilipaixing.css
similarity index 50%
rename from templates/zilipaixing.html
rename to templates/styles/pages/zilipaixing.css
index 76f5a40..f61bffc 100644
--- a/templates/zilipaixing.html
+++ b/templates/styles/pages/zilipaixing.css
@@ -1,65 +1,13 @@
-
-
-
-
-
资历排行
-
-
-
-
-
-
-
-
{{ school }} 资历排行
-
-
- {{ server }} | 更新时间:{{ update_time }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | 排名 |
- 头像 |
- 职业 |
- 角色 |
- 服务器 |
- 资历 |
-
-
-
-
- {% for item in items %}
-
- |
- {{ loop.index }}
- |
-
-
- {% if item.avatarUrl %}
-
- {% endif %}
- |
-
-
-
- {% set force_icon = item.forceIcon or icons.sect.get(item.forceName) %}
-
- {% if force_icon %}
- 
- {% endif %}
-
-
- {{ item.forceName }}
-
-
- |
-
-
- {{ item.roleName }}
- |
-
-
- {{ item.serverName }}
- |
-
-
- {{ item.seniority }}
- |
-
- {% endfor %}
-
-
-
-
-
-
\ No newline at end of file
diff --git a/templates/styles/tokens.css b/templates/styles/tokens.css
new file mode 100644
index 0000000..d8ea857
--- /dev/null
+++ b/templates/styles/tokens.css
@@ -0,0 +1,123 @@
+:root {
+ --jx3-bg-deep: #04111d;
+ --jx3-bg: #071a2b;
+ --jx3-bg-raised: #0a2238;
+ --jx3-surface: #0d2942;
+ --jx3-surface-strong: #113451;
+ --jx3-surface-soft: #173d5d;
+ --jx3-surface-muted: #1b4567;
+ --jx3-border: #9a7432;
+ --jx3-border-strong: #c09647;
+ --jx3-border-soft: rgba(203, 160, 78, 0.36);
+ --jx3-border-faint: rgba(141, 175, 207, 0.18);
+ --jx3-gold: #e1b766;
+ --jx3-gold-bright: #f2cf82;
+ --jx3-gold-muted: #b89655;
+ --jx3-text: #f5f0e6;
+ --jx3-text-secondary: #c4d2df;
+ --jx3-text-muted: #879db2;
+ --jx3-blue: #72a9d5;
+ --jx3-green: #62bc92;
+ --jx3-red: #d56a63;
+ --jx3-purple: #a98bd2;
+ --jx3-shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.18);
+ --jx3-shadow-md: 0 12px 30px rgba(0, 0, 0, 0.24);
+ --jx3-radius-sm: 6px;
+ --jx3-radius-md: 10px;
+ --jx3-radius-lg: 14px;
+ --jx3-space-1: 4px;
+ --jx3-space-2: 8px;
+ --jx3-space-3: 12px;
+ --jx3-space-4: 16px;
+ --jx3-space-5: 20px;
+ --jx3-space-6: 24px;
+ --jx3-space-8: 32px;
+ --jx3-page-width: 1200px;
+ --jx3-font: "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", Arial, sans-serif;
+}
+
+.jx3-template--mingjiantongji {
+ --jx3-page-width: 640px;
+}
+
+.jx3-template--xingxiashijian {
+ --jx3-page-width: 900px;
+}
+
+.jx3-template--shilianpaixing,
+.jx3-template--zhueevent {
+ --jx3-page-width: 960px;
+}
+
+.jx3-template--jingnai,
+.jx3-template--zili {
+ --jx3-page-width: 980px;
+}
+
+.jx3-template--bilei,
+.jx3-template--mingjianpaihang,
+.jx3-template--qiyuliebiao,
+.jx3-template--yanhuan {
+ --jx3-page-width: 1000px;
+}
+
+.jx3-template--jinqiqiyu,
+.jx3-template--rank_tong0,
+.jx3-template--rank_tong1,
+.jx3-template--rank_tong2,
+.jx3-template--zilipaixing {
+ --jx3-page-width: 1040px;
+}
+
+.jx3-template--diaoluo,
+.jx3-template--qiyuhuizong,
+.jx3-template--zhenyingevent {
+ --jx3-page-width: 1080px;
+}
+
+.jx3-template--richangyuche {
+ --jx3-page-width: 1100px;
+}
+
+.jx3-template--zhanji {
+ --jx3-page-width: 1100px;
+}
+
+.jx3-template--jiaoyihang,
+.jx3-template--jineng,
+.jx3-template--qixue,
+.jx3-template--rank_role {
+ --jx3-page-width: 1120px;
+}
+
+.jx3-template--bangzhanjilu,
+.jx3-template--chengjiu,
+.jx3-template--guanaishouling {
+ --jx3-page-width: 1180px;
+}
+
+.jx3-template--dilujilu,
+.jx3-template--jinjia,
+.jx3-template--juesheliaotian,
+.jx3-template--tuanduizhaomu,
+.jx3-template--zhengyingpaimai {
+ --jx3-page-width: 1300px;
+}
+
+.jx3-template--shitu {
+ --jx3-page-width: 1280px;
+}
+
+.jx3-template--huajia,
+.jx3-template--wujia,
+.jx3-template--xiaoyao {
+ --jx3-page-width: 1400px;
+}
+
+.jx3-template--chengbeng {
+ --jx3-page-width: 1500px;
+}
+
+.jx3-template--helps {
+ --jx3-page-width: 1520px;
+}
diff --git a/templates/tuanduizhaomu.html b/templates/tuanduizhaomu.html
deleted file mode 100644
index 31f8868..0000000
--- a/templates/tuanduizhaomu.html
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
-
团队招募
-
-
-
-
-
-
-
团队招募
-
-
-
-
- | 副本名称 |
- 招募人 |
- 招募时间 |
- 人数 |
- 招募说明 |
-
-
-
-
- {% for m in list %}
-
- | {{ m.activityName }} |
- {{ m.roleName }} |
- {{ m.createTime }} |
- {{ m.maxMemberCount }} |
- {{ m.commentText }} |
-
- {% endfor %}
-
-
-
-
-
-
-
diff --git a/templates/weizuoqiyu.html b/templates/weizuoqiyu.html
deleted file mode 100644
index 5010029..0000000
--- a/templates/weizuoqiyu.html
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-
未做奇遇
-
-
-
-
-
-
-
普通奇遇({{ ptqy|length if ptqy else 0 }})
- {% if ptqy %}
-
- {% for item in ptqy %}
-
- {% if icons.serendipity.get(item.event) %}
-

- {% else %}
-
{{ item.event }}
- {% endif %}
-
{{ item.time }}
-
- {% endfor %}
-
- {% else %}
-
暂无未做普通奇遇
- {% endif %}
-
-
-
-
绝世奇遇({{ jsqy|length if jsqy else 0 }})
- {% if jsqy %}
-
- {% for item in jsqy %}
-
- {% if icons.serendipity.get(item.event) %}
-

- {% else %}
-
{{ item.event }}
- {% endif %}
-
{{ item.time }}
-
- {% endfor %}
-
- {% else %}
-
暂无未做绝世奇遇
- {% endif %}
-
-
-
diff --git a/templates/xingxiashijian.html b/templates/xingxiashijian.html
deleted file mode 100644
index bd8c894..0000000
--- a/templates/xingxiashijian.html
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
-
名望事件
-
-
-
-
-
-
-
-
名望事件({{ name }})
-
-
- {% for m in items %}
-
-
{{ m.time }}
-
-
-
{{ m.stage }}
-
{{ m.map }} · {{ m.site }}
-
{{ m.desc }}
-
-
-
{{ m.icon }}
-
- {% endfor %}
-
-
-
-
-
diff --git a/templates/yanhuan.html b/templates/yanhuan.html
deleted file mode 100644
index 33ccb3d..0000000
--- a/templates/yanhuan.html
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
-
烟花记录
-
-
-
-
-
-
-
烟花记录
-
-
-
-
- | 服务器 |
- 烟花名称 |
- 使用地点 |
- 释放角色 |
- 接收角色 |
- 释放时间 |
-
-
-
-
- {% for m in list %}
-
- | {{ m.server }} |
- {{ m.firework }} |
- {{ m.mapName }} |
- {{ m.sender }} |
- {{ m.receiver }} |
- {{ m.time }} |
-
- {% endfor %}
-
-
-
-
-
-
-
diff --git a/templates/zhengyingpaimai.html b/templates/zhengyingpaimai.html
deleted file mode 100644
index c5c6749..0000000
--- a/templates/zhengyingpaimai.html
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
-
-
阵营拍卖
-
-
-
-
-
-
-
阵营拍卖
-
-
-
-
- | 服务器 |
- 物品名称 |
- 拍得者 |
- 所属阵营 |
- 拍卖价格 |
- 拍卖时间 |
-
-
-
-
- {% for m in list %}
-
- | {{ m.server }} |
- {{ m.itemName }} |
- {{ m.roleName }} |
- {{ m.campName }} |
- {{ m.itemAmount }} |
- {{ m.time }} |
-
- {% endfor %}
-
-
-
-
-
-
-
diff --git a/templates/zhenyingevent.html b/templates/zhenyingevent.html
deleted file mode 100644
index 63fc518..0000000
--- a/templates/zhenyingevent.html
+++ /dev/null
@@ -1,151 +0,0 @@
-
-
-
-
-
阵营事件
-
-
-
-
-
-
-
阵营事件
-
更新时间:{{ update_time }}
-
-
-
-
- | 阵营 |
- 分线 |
- 服务器 |
- 角色名称 |
- 时间 |
-
-
-
-
- {% for item in items %}
-
- | {{ item.campName }} |
- {{ item.fenxianName }} |
- {{ item.friendName }} |
- {{ item.roleName }} |
- {{ item.seizeTime }} |
-
- {% endfor %}
-
-
-
-
-
-
diff --git a/templates/zhuangshi.html b/templates/zhuangshi.html
deleted file mode 100644
index b55246c..0000000
--- a/templates/zhuangshi.html
+++ /dev/null
@@ -1,178 +0,0 @@
-
-
-
-
-
家园家具一览
-
-
-
-
-
-
-
家园家具展示
-
- {% for item in data %}
-
-

-
-
-
- {{ item.name }}
-
-
-
- 来源:{{ item.source }}
- 数量限制:{{ item.limit }}
- 品质值:{{ item.quality }}
-
-
- {% if item.tip %}
-
- {{ item.tip }}
-
- {% endif %}
-
-
-
-
-
实用
-
{{ item.practical }}
-
-
-
-
风水
-
{{ item.geomantic }}
-
-
-
趣味
-
{{ item.interesting }}
-
-
-
-
-
-
- {% endfor %}
-
-
-
-
-
diff --git a/templates/zhueevent.html b/templates/zhueevent.html
deleted file mode 100644
index 8f2bf35..0000000
--- a/templates/zhueevent.html
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
诛恶事件
-
-
-
-
-
-
-
诛恶事件
-
更新时间:{{ update_time }}
-
-
-
-
- | 服务器 |
- 地图 |
- 时间 |
-
-
-
-
- {% for item in items %}
-
- | {{ item.server }} |
- {{ item.mapName }} |
- {{ item.time }} |
-
- {% endfor %}
-
-
-
-
-
-
diff --git a/templates/zili.html b/templates/zili.html
deleted file mode 100644
index 309b086..0000000
--- a/templates/zili.html
+++ /dev/null
@@ -1,206 +0,0 @@
-
-
-
-
-
资历查询
-
-
-
-
-
-
-
-
-
-
-
{{ summary.name }}
-
{{ summary.percent_text }}
-
-
- {{ summary.completed_points }} / {{ summary.total_points }} 点
- · {{ summary.completed_count }} / {{ summary.achievement_count }} 项
-
-
-
-
-
- {% for item in items %}
-
-
-
{{ item.name }}
-
{{ item.percent_text }}
-
-
- {{ item.completed_points }} / {{ item.total_points }} 点
- · {{ item.completed_count }} / {{ item.achievement_count }} 项
-
-
-
- {% endfor %}
-
-
-
-
-
diff --git a/tests/test_template_system.py b/tests/test_template_system.py
new file mode 100644
index 0000000..da9cd9a
--- /dev/null
+++ b/tests/test_template_system.py
@@ -0,0 +1,100 @@
+import asyncio
+import re
+import unittest
+from pathlib import Path
+
+from jinja2 import ChainableUndefined, Environment
+
+from core.template import TemplateRepository
+
+
+ROOT = Path(__file__).parent.parent
+TEMPLATE_ROOT = ROOT / "templates"
+
+
+class TemplateRepositoryTests(unittest.TestCase):
+ def setUp(self):
+ self.repository = TemplateRepository(TEMPLATE_ROOT)
+
+ def test_all_pages_compose_to_valid_jinja_documents(self):
+ pages = sorted((TEMPLATE_ROOT / "pages").glob("*.html"))
+ page_styles = sorted((TEMPLATE_ROOT / "styles" / "pages").glob("*.css"))
+ self.assertEqual(46, len(pages))
+ self.assertEqual(
+ {page.stem for page in pages},
+ {style.stem for style in page_styles},
+ )
+
+ environment = Environment(undefined=ChainableUndefined)
+ empty_context = {
+ "data": {},
+ "icons": {"img": {}, "sect": {}, "serendipity": {}},
+ "items": [],
+ "summary": {},
+ }
+ for page in pages:
+ with self.subTest(page=page.name):
+ fragment = page.read_text(encoding="utf-8")
+ for document_tag in ("!DOCTYPE", "html", "head", "style", "body"):
+ self.assertIsNone(
+ re.search(rf"<{document_tag}(?:\s|>)", fragment, re.IGNORECASE)
+ )
+
+ template = asyncio.run(self.repository.get(page.name))
+ self.assertIn("", template)
+ self.assertIn("", template)
+ self.assertNotIn("", template)
+ self.assertNotIn("/*__PAGE_CSS__*/", template)
+ rendered = environment.from_string(template).render(empty_context)
+ self.assertIn("", rendered)
+
+ def test_cache_returns_same_template_instance(self):
+ first = asyncio.run(self.repository.get("helps.html"))
+ second = asyncio.run(self.repository.get("helps.html"))
+ self.assertIs(first, second)
+
+ def test_rejects_path_traversal(self):
+ with self.assertRaises(ValueError):
+ asyncio.run(self.repository.get("../helps.html"))
+
+ def test_help_page_catalogue_structure(self):
+ fragment = (TEMPLATE_ROOT / "pages" / "helps.html").read_text(
+ encoding="utf-8"
+ )
+ categories = re.findall(
+ r'
([^<]+)
', fragment
+ )
+ features = re.findall(r'
(.*?)', fragment)
+
+ self.assertEqual(
+ [
+ "活动情报",
+ "名剑相关",
+ "排行榜单",
+ "物价交易",
+ "阵营战场",
+ "角色名片",
+ "奇遇相关",
+ "百战记录",
+ "角色信息",
+ "资历相关",
+ "心法配装",
+ "游戏社区",
+ "百度贴吧",
+ "系统工具",
+ "杂项功能",
+ "避雷功能",
+ "推送功能",
+ ],
+ categories,
+ )
+ self.assertEqual(69, len(features))
+ self.assertEqual(105, len(commands))
+ self.assertEqual(105, len(set(commands)))
+ self.assertIn("共 17 个功能分类 · 69 项功能 · 105 条指令", fragment)
+
+
+if __name__ == "__main__":
+ unittest.main()