diff --git a/CHANGELOG.md b/CHANGELOG.md index a8835e9..f5bb556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ 重构 46 个 HTML 图片模板:公共文档骨架、设计变量、基础样式和组件样式统一维护,页面仅保留内容与独有布局;渲染前在本地组合为完整 HTML 字符串。 -重新整理功能帮助页,按 17 个分类展示 69 项功能和 105 条指令,并优化桌面三列与窄屏单列排版。 +进一步组件化列表和卡片页面:表格、排行、技能/奇穴、奇遇网格、区服状态、器物详情等改为复用公共 CSS;页面专属 CSS 从 46 个减少至 13 个。模板通过元数据声明组件,加载器从单一组件样式表中选择性注入所需样式,并支持无同名 CSS 的标准页面及共享资源缓存。 + +重新整理功能帮助页,按 17 个分类展示 69 项功能和 105 条指令,并按固定图片画布优化三列排版。 ### version: 3.0: diff --git a/README.md b/README.md index 2b467aa..79b439b 100644 --- a/README.md +++ b/README.md @@ -412,14 +412,33 @@ flowchart LR 图片默认使用质量 `100`、完整页面截图和普通设备缩放级别。模板可通过 `icons.img`、`icons.sect`、`icons.serendipity` 访问通用、门派/心法和奇遇图标。 -AstrBot 的渲染接口接收完整 HTML 字符串,因此插件不会依赖渲染端读取本地 CSS 文件。`core/template.py` 会异步读取并缓存公共布局、设计变量、基础样式、组件样式、页面专属样式和页面片段,组装完成后再把单个完整字符串交给渲染器。这样既满足渲染接口约束,也避免在 46 个页面中重复维护文档骨架和通用样式。 +AstrBot 的渲染接口接收完整 HTML 字符串,因此插件不会依赖渲染端读取本地 CSS 文件。`core/template.py` 会异步读取并缓存公共布局、设计变量、基础样式和组件样式,再按需读取页面专属样式及页面片段,组装完成后把单个完整字符串交给渲染器。共享资源只在首次请求时读取一次;标准页面没有同名 CSS 文件也可以正常组合。 + +`styles/components.css` 是公共组件的唯一来源,内部使用 `@component` 标记划分表格、网格、能力卡片等组件块。页面通过顶部元数据声明需要的组件,加载器只把声明过的样式块注入最终 HTML,不会让只使用表格的页面同时携带技能卡片、器物卡片等无关 CSS: + +```html +{# template-title: 角色榜单 #} +{# template-components: data-table #} +``` 样式职责如下: - `styles/tokens.css`:颜色、间距、圆角、字体和各页面内容宽度。 -- `styles/base.css`:页面背景、外框、基础排版和窄屏规则。 -- `styles/components.css`:信息面板、表格、状态、进度条、标签等跨页面组件。 -- `styles/pages/*.css`:只保留列宽、网格、卡片结构等页面独有布局。 +- `styles/base.css`:固定图片画布的页面背景、外框和基础排版。 +- `styles/components.css`:统一维护数据表格、列状态、排行、统计卡片、可配置列数网格、技能/奇穴卡片、奇遇卡片、器物详情、标签和空数据等跨页面组件。 +- `styles/pages/*.css`:可选,仅保留成本计算、成就、副本记录等无法合理复用的复杂页面布局。当前 46 个页面中只有 13 个需要专属 CSS。 + +表格列数直接由模板中的 ``、`` 数量决定,不需要为四列、五列等情况分别创建样式。卡片网格通过 `--grid-columns` 配置列数,例如: + +```html +
+ ... +
+``` + +数据页使用 `.data-table`、`.data-cell--rank`、`.data-cell--time`、`.data-cell--score` 等公共类;技能与奇穴使用 `.ability-card`,角色奇遇与区服状态使用 `.card-grid`,器物与家园装饰使用 `.object-card`。新增普通列表页面时应优先组合这些公共组件,不再创建内容重复的页面 CSS。 + +这些模板不是响应式网页:每次查询只生成一个固定版式的完整 HTML 字符串,注入本次业务数据后由 AstrBot 使用 `full_page=True` 截取整页图片。样式不包含移动端断点;内容宽度由 `--jx3-page-width` 固定,列数由模板结构或 `--grid-columns` 明确决定,不随渲染服务的默认视口发生重排。 ### 6. 本地数据与缓存 @@ -475,8 +494,8 @@ astrbot_plugin_jx3/ │ ├── styles/ │ │ ├── tokens.css # 设计变量与页面宽度 │ │ ├── base.css # 全局背景、外框和排版 -│ │ ├── components.css # 公共组件样式 -│ │ └── pages/*.css # 页面独有布局 +│ │ ├── components.css # 表格、网格、卡片、状态等公共组件 +│ │ └── pages/*.css # 可选的复杂页面独有布局(当前 13 个) │ ├── img/ # 通用图片资源 │ ├── sect/ # 门派与心法图标 │ └── serendipity/ # 奇遇图标 @@ -490,7 +509,7 @@ astrbot_plugin_jx3/ 1. 根据数据来源选择 `JX3APIService`、`AIJX3Service` 或 `JX3BOXService`。 2. 在业务服务中新增异步方法,复用 `APIClient`,完成请求、空数据检查、字段整理和标准返回对象构建。 -3. 需要图片输出时新增或复用 `templates/pages/*.html`,并按需添加同名的 `templates/styles/pages/*.css`;通用视觉规则应进入公共样式层,不要在业务层拼装最终图片消息。 +3. 需要图片输出时新增或复用 `templates/pages/*.html`,优先组合 `data-table`、`card-grid`、`ability-card`、`object-card` 等公共组件,并通过 `template-components` 元数据声明所需组件;只有无法复用的复杂布局才添加同名的 `templates/styles/pages/*.css`,不要在业务层拼装最终图片消息。 4. 在 `MessageBuilder` 中增加薄包装方法,选择文本、HTML 图片、远程图片、消息链或会话处理器。 5. 在 `main.py` 的 `command_map` 注册触发词。 6. 同步更新 `templates/pages/helps.html`、README 和 CHANGELOG,确保参数顺序以包装方法签名为准。 @@ -504,7 +523,7 @@ python -m compileall -q . python -m unittest tests.test_template_system -v ``` -模板测试会检查 46 个页面片段与同名样式是否一一对应、能否组合为完整文档、Jinja2 是否能够完成基础渲染,以及模板路径是否能阻止目录穿越。它和 `compileall` 仍不能替代带真实数据的 AstrBot 消息、HTML 渲染和外部接口联调。 +模板测试会检查 46 个页面片段、13 个可选页面样式、公共组件按声明选择性注入、完整文档组合、Jinja2 基础渲染,以及模板路径能否阻止目录穿越。它和 `compileall` 仍不能替代带真实数据的 AstrBot 消息、HTML 渲染和外部接口联调。 ## 当前版本状态 diff --git a/core/async_task.py b/core/async_task.py index 26ccaa7..e38da6a 100644 --- a/core/async_task.py +++ b/core/async_task.py @@ -107,7 +107,7 @@ class AsyncTask: async def init_tasks(self): settings = [ - ("kfts", "开服监控", lambda: self.jx3fun.kaifu(self.server)), + ("kfts", "开服监控", lambda: self.jx3fun.kaifu("梦江南")), ("xwts", "新闻资讯", lambda: self.jx3fun.xinwen(1)), ("smts", "刷马消息", lambda: self.jx3fun.shuamamsg(self.server,type="horse",subtype="foreshow")), ("ctts", "赤兔消息", lambda: self.jx3fun.shuamamsg(self.server,type="chitu-horse",subtype="share_msg")), diff --git a/core/jx3api_data.py b/core/jx3api_data.py index 761c32b..007a884 100644 --- a/core/jx3api_data.py +++ b/core/jx3api_data.py @@ -1438,7 +1438,7 @@ class JX3APIService: """新闻资讯""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: result = data[0] - return_data["status"] = result.get('catid') + return_data["status"] = int(result.get('catid')) result_msg = "新闻资讯推送\n" # 仅展示前1条,避免消息过长 diff --git a/core/template.py b/core/template.py index 5f8e131..828f45a 100644 --- a/core/template.py +++ b/core/template.py @@ -8,6 +8,15 @@ import aiofiles _TITLE_PATTERN = re.compile( r"\A\{# template-title: (.*?) #\}\r?\n", ) +_PAGE_COMPONENTS_PATTERN = re.compile( + r"\A\{# template-components: ([a-z0-9 -]+) #\}\r?\n", +) +_COMPONENT_STYLE_PATTERN = re.compile( + r"/\* @component (?P[a-z0-9-]+) \*/\s*" + r"(?P.*?)\s*" + r"/\* @endcomponent \*/", + re.DOTALL, +) class TemplateRepository: @@ -16,6 +25,7 @@ class TemplateRepository: def __init__(self, root: Path): self.root = root.resolve() self._cache: dict[str, str] = {} + self._shared_assets: tuple[str, str, str, str] | None = None self._lock = asyncio.Lock() async def _read(self, path: Path) -> str: @@ -28,6 +38,26 @@ class TemplateRepository: raise ValueError(f"非法模板名称: {template_name}") return self.root / "pages" / name.name + @staticmethod + def _select_component_styles(source: str, requested: set[str]) -> str: + """从单一组件样式表中挑选当前页面声明的组件块。""" + matches = list(_COMPONENT_STYLE_PATTERN.finditer(source)) + available = [match.group("name") for match in matches] + if len(available) != len(set(available)): + raise ValueError("公共组件样式存在重复名称") + + unknown = requested.difference(available) + if unknown: + names = ", ".join(sorted(unknown)) + raise ValueError(f"页面声明了不存在的公共组件: {names}") + + return _COMPONENT_STYLE_PATTERN.sub( + lambda match: match.group("css") + if match.group("name") in requested + else "", + source, + ) + async def get(self, template_name: str) -> str: """读取并缓存组装后的完整模板字符串。""" cached = self._cache.get(template_name) @@ -49,22 +79,39 @@ class TemplateRepository: self._cache[template_name] = template return template + if self._shared_assets is None: + self._shared_assets = tuple( + await asyncio.gather( + self._read(self.root / "layouts" / "base.html"), + self._read(self.root / "styles" / "tokens.css"), + self._read(self.root / "styles" / "base.css"), + self._read(self.root / "styles" / "components.css"), + ) + ) + + base, tokens, base_css, component_source = self._shared_assets 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 - ) + if page_css_path.exists(): + page, page_css = await asyncio.gather( + self._read(page_path), + self._read(page_css_path), + ) + else: + page = await self._read(page_path) + page_css = "" title_match = _TITLE_PATTERN.match(page) title = title_match.group(1).strip() if title_match else "剑网三数据查询" content = _TITLE_PATTERN.sub("", page, count=1) + component_match = _PAGE_COMPONENTS_PATTERN.match(content) + requested_components = ( + set(component_match.group(1).split()) if component_match else set() + ) + content = _PAGE_COMPONENTS_PATTERN.sub("", content, count=1) + components = self._select_component_styles( + component_source, + requested_components, + ) template = ( base.replace("__PAGE_ID__", page_path.stem) @@ -94,6 +141,7 @@ class TemplateRepository: def clear(self) -> None: """清理内存缓存,主要用于开发和测试。""" self._cache.clear() + self._shared_assets = None _TEMPLATE_ROOT = Path(__file__).parent.parent / "templates" diff --git a/main.py b/main.py index c200680..541af3c 100644 --- a/main.py +++ b/main.py @@ -141,7 +141,7 @@ class Jx3ApiPlugin(Star): "sect": sect, "serendipity": serendipity } - logger.debug(f"图片base64编码加载完成: {self.icons}") + logger.debug(f"图片base64编码加载完成") def create_all(self): diff --git a/templates/pages/bangzhanjilu.html b/templates/pages/bangzhanjilu.html index c443f91..a30a5ec 100644 --- a/templates/pages/bangzhanjilu.html +++ b/templates/pages/bangzhanjilu.html @@ -1,11 +1,12 @@ {# template-title: 帮战记录 #} +{# template-components: data-table #} -
+

帮战记录

-
{{ server }} | 更新时间:{{ update_time }}
+
{{ server }} | 更新时间:{{ update_time }}
- +
@@ -19,14 +20,13 @@ {% for item in items %} - - - - - + + + + + {% endfor %}
宣战帮会
{{ item.declaringName }}{{ item.acceptingName }}{{ item.startTime }}{{ item.durationSeconds }}{{ item.endTime }}{{ item.declaringName }}{{ item.acceptingName }}{{ item.startTime }}{{ item.durationSeconds }}{{ item.endTime }}
- diff --git a/templates/pages/bilei.html b/templates/pages/bilei.html index 02996c3..4344a16 100644 --- a/templates/pages/bilei.html +++ b/templates/pages/bilei.html @@ -1,10 +1,11 @@ {# template-title: 避雷查看 #} +{# template-components: data-table #} -
+

避雷查看

- +
@@ -20,7 +21,7 @@ - + @@ -29,4 +30,3 @@
ID
{{ m.id }} {{ m.name }}{{ m.text }}{{ m.text }} {{ m.time }} {{ m.user }}
- diff --git a/templates/pages/diaoluo.html b/templates/pages/diaoluo.html index 9637487..04fe1b5 100644 --- a/templates/pages/diaoluo.html +++ b/templates/pages/diaoluo.html @@ -1,11 +1,12 @@ {# template-title: 掉落记录 #} +{# template-components: data-table #} -
+

{{ name }} 掉落记录

-
{{ server }} · 数量:{{ limit }} · 更新时间:{{ update_time }}
+
{{ server }} · 数量:{{ limit }} · 更新时间:{{ update_time }}
- +
@@ -19,14 +20,13 @@ {% for item in items %} - - - - - + + + + + {% endfor %}
物品名称
{{ item.itemName }}{{ item.server }}{{ item.mapName }}{{ item.roleName }}{{ item.time }}{{ item.itemName }}{{ item.server }}{{ item.mapName }}{{ item.roleName }}{{ item.time }}
- diff --git a/templates/pages/dilujilu.html b/templates/pages/dilujilu.html index f1f15f5..681f15b 100644 --- a/templates/pages/dilujilu.html +++ b/templates/pages/dilujilu.html @@ -1,10 +1,11 @@ {# template-title: 的卢记录 #} +{# template-components: data-table #} -
+

的卢记录

- +
@@ -43,4 +44,3 @@
服务器
- diff --git a/templates/pages/guanaishouling.html b/templates/pages/guanaishouling.html index b471b25..2e900f0 100644 --- a/templates/pages/guanaishouling.html +++ b/templates/pages/guanaishouling.html @@ -1,14 +1,15 @@ {# template-title: 关隘首领 #} +{# template-components: data-table #} -
+

关隘首领

-
更新时间:{{ update_time }}
+
更新时间:{{ update_time }}
{% for group in groups %} -
-
{{ group.server }}
- +
+
{{ group.server }}
+
@@ -29,12 +30,12 @@ class="camp-haoqi" {% endif %} > - - - - - - + + + + + + {% endfor %} @@ -42,4 +43,3 @@ {% endfor %} - diff --git a/templates/pages/huajia.html b/templates/pages/huajia.html index 07d0c1b..e9ea012 100644 --- a/templates/pages/huajia.html +++ b/templates/pages/huajia.html @@ -1,7 +1,8 @@ {# template-title: 花价查询 #} +{# template-components: data-table data-extras #} -
+

花价查询({{ server }})

{% for map_name in [ @@ -13,9 +14,9 @@ ] %} {% if data.get(map_name) %} -

{{ map_name }}

+

{{ map_name }}

-
归属{{ item.camp_name }}{{ item.castle }}{{ item.str_status }}{{ item.start_time }}{{ item.end_time }}{{ item.remaining_time }}{{ item.camp_name }}{{ item.castle }}{{ item.str_status }}{{ item.start_time }}{{ item.end_time }}{{ item.remaining_time }}
+
@@ -35,8 +36,8 @@ {% endif %} - - + @@ -47,4 +48,3 @@ {% endfor %} - diff --git a/templates/pages/jineng.html b/templates/pages/jineng.html index d09afbd..7dae987 100644 --- a/templates/pages/jineng.html +++ b/templates/pages/jineng.html @@ -1,38 +1,39 @@ {# template-title: 技能详情 #} +{# template-components: data-layout ability-card #} -
+

{{ name }} 技能详情

-
更新时间:{{ update_time }}
+
更新时间:{{ update_time }}
{% for group in groups %} -
-
{{ group.title }}
-
+
+
{{ group.title }}
+
{% for skill in group.skills %} -
+
{% if skill.icon %} - {{ skill.name }} + {{ skill.name }} {% else %} -
+
{% endif %}
-
- {{ skill.name }} +
+ {{ skill.name }} {% if skill.interval %} - {{ skill.interval }} + {{ skill.interval }} {% endif %} {% if skill.distance %} - {{ skill.distance }} + {{ skill.distance }} {% endif %} {% if skill.release_type %} - {{ skill.release_type }} + {{ skill.release_type }} {% endif %} {% if skill.weapon %} - {{ skill.weapon }} + {{ skill.weapon }} {% endif %}
-
{{ skill.desc }}
+
{{ skill.desc }}
{% endfor %} @@ -40,4 +41,3 @@
{% endfor %}
- diff --git a/templates/pages/jingnai.html b/templates/pages/jingnai.html index 602d797..a6df145 100644 --- a/templates/pages/jingnai.html +++ b/templates/pages/jingnai.html @@ -1,26 +1,27 @@ {# template-title: 精耐查询 #} +{# template-components: data-table data-layout #} -
+

{{ server }} - {{ role_name }}

-
更新时间:{{ update_time }}
+
更新时间:{{ update_time }}
-
-
-
精力
-
{{ skill_energy }}
+
+
+
精力
+
{{ skill_energy }}
-
-
耐力
-
{{ skill_stamina }}
+
+
耐力
+
{{ skill_stamina }}
-
-
技能数量
-
{{ skill_count }}
+
+
技能数量
+
{{ skill_count }}
-
名称{{ item.price }} + {{ item.price }} {{ item.line | join('、') }}
+
@@ -33,13 +34,12 @@ {% for item in items %} - - - - + + + + {% endfor %}
技能名称
{{ item.szSkillName }}{{ item.szBossName }}{{ item.skill_color_text }}{{ item.nLevel }}{{ item.szSkillName }}{{ item.szBossName }}{{ item.skill_color_text }}{{ item.nLevel }}
- diff --git a/templates/pages/jinjia.html b/templates/pages/jinjia.html index 3222030..3775182 100644 --- a/templates/pages/jinjia.html +++ b/templates/pages/jinjia.html @@ -1,10 +1,11 @@ {# template-title: 区服各平台金价 #} +{# template-components: data-table #} -
+

区服各平台金价

- +
@@ -35,4 +36,3 @@
服务器
- diff --git a/templates/pages/jinqiqiyu.html b/templates/pages/jinqiqiyu.html index a998163..23e9b72 100644 --- a/templates/pages/jinqiqiyu.html +++ b/templates/pages/jinqiqiyu.html @@ -1,11 +1,12 @@ {# template-title: 近期奇遇 #} +{# template-components: data-table #} -
+

{{ server }} 近期奇遇

-
更新时间:{{ update_time }}
+
更新时间:{{ update_time }}
- +
@@ -19,13 +20,12 @@ {% for m in items %} - - - + + + {% endfor %}
区服
{{ m.server }}{{ m.name }}{{ m.event }}{{ m.time }}{{ m.name }}{{ m.event }}{{ m.time }}
- diff --git a/templates/pages/juesheqiyu.html b/templates/pages/juesheqiyu.html index 1e836ce..32f9182 100644 --- a/templates/pages/juesheqiyu.html +++ b/templates/pages/juesheqiyu.html @@ -1,66 +1,66 @@ {# template-title: 角色奇遇 #} +{# template-components: data-layout collection-card #} -
-
普通奇遇({{ ptqy|length if ptqy else 0 }})
+
+
普通奇遇({{ ptqy|length if ptqy else 0 }})
{% if ptqy %} -
+
{% for item in ptqy %} -
+
{% if icons.serendipity.get(item.event) %} - {{ item.event }} + {{ item.event }} {% else %} -
{{ item.event }}
+
{{ item.event }}
{% endif %} -
{{ item.time }}
+
{{ item.time }}
{% endfor %}
{% else %} -
暂无普通奇遇
+
暂无普通奇遇
{% endif %}
-
-
绝世奇遇({{ jsqy|length if jsqy else 0 }})
+
+
绝世奇遇({{ jsqy|length if jsqy else 0 }})
{% if jsqy %} -
+
{% for item in jsqy %} -
+
{% if icons.serendipity.get(item.event) %} - {{ item.event }} + {{ item.event }} {% else %} -
{{ item.event }}
+
{{ item.event }}
{% endif %} -
{{ item.time }}
+
{{ item.time }}
{% endfor %}
{% else %} -
暂无绝世奇遇
+
暂无绝世奇遇
{% endif %}
-
-
宠物奇遇({{ cwqy|length if cwqy else 0 }})
+
+
宠物奇遇({{ cwqy|length if cwqy else 0 }})
{% if cwqy %} -
+
{% for item in cwqy %} -
+
{% if icons.serendipity.get(item.event) %} - {{ item.event }} + {{ item.event }} {% else %} -
{{ item.event }}
+
{{ item.event }}
{% endif %} -
{{ item.time }}
+
{{ item.time }}
{% endfor %}
{% else %} -
暂无宠物奇遇
+
暂无宠物奇遇
{% endif %}
- diff --git a/templates/pages/mingjianpaihang.html b/templates/pages/mingjianpaihang.html index c263e5e..b0fc033 100644 --- a/templates/pages/mingjianpaihang.html +++ b/templates/pages/mingjianpaihang.html @@ -1,10 +1,11 @@ {# template-title: 竞技排行榜 #} +{# template-components: data-table #} -
+

名剑排行榜

- +
@@ -20,7 +21,7 @@ {% for m in lists %} - @@ -40,11 +41,10 @@ - + {% endfor %}
排名
- + {{ m.roleName }}{{ m.score }}{{ m.winRate }}{{ m.winRate }}
- diff --git a/templates/pages/mingjiantongji.html b/templates/pages/mingjiantongji.html index f02f545..0a70e6b 100644 --- a/templates/pages/mingjiantongji.html +++ b/templates/pages/mingjiantongji.html @@ -1,11 +1,12 @@ {# template-title: 名剑统计 #} +{# template-components: data-table #} -
+

名剑统计(前1000) · {{ mode }}

-
更新时间:{{ update_time }}
+
更新时间:{{ update_time }}
- +
@@ -18,19 +19,18 @@ {% for m in items %} - - + + {% endfor %}
门派
-
+
{% if icons.sect.get(m.forceName) %} - {{ m.forceName }} + {{ m.forceName }} {% endif %} {{ m.forceName }}
{{ m.thisWeek }}{{ m.lastWeek }}{{ m.thisWeek }}{{ m.lastWeek }}
- diff --git a/templates/pages/qiwu.html b/templates/pages/qiwu.html index affb57c..0072b9f 100644 --- a/templates/pages/qiwu.html +++ b/templates/pages/qiwu.html @@ -1,54 +1,55 @@ {# template-title: 地图装饰器物 #} +{# template-components: object-card #} -
+

地图装饰器物({{ name }})

{% for item in data %} -
- {{ item.name }} +
+ {{ item.name }} -
-
+
+
{{ item.name }}
-
+
来源:{{ item.source }} 数量限制:{{ item.limit }} 品质值:{{ item.quality }}
{% if item.tip %} -
+
{{ item.tip }}
{% endif %} -
-
-
观赏
-
{{ item.view }}
+
+
+
观赏
+
{{ item.view }}
-
-
实用
-
{{ item.practical }}
+
+
实用
+
{{ item.practical }}
-
-
坚固
-
{{ item.hard }}
+
+
坚固
+
{{ item.hard }}
-
-
风水
-
{{ item.geomantic }}
+
+
风水
+
{{ item.geomantic }}
-
-
趣味
-
{{ item.interesting }}
+
+
趣味
+
{{ item.interesting }}
-