按PlayStation设计规范重构菜单生图并恢复全量功能卡片展示
This commit is contained in:
@@ -104,21 +104,17 @@ class RobotMenuRenderTool:
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
def _iter_user_command_features(self) -> list[Feature]:
|
def _iter_user_command_features(self) -> list[Feature]:
|
||||||
"""筛选“用户可直接触发”的功能集合。
|
"""返回菜单要展示的功能集合。
|
||||||
|
|
||||||
设计说明:
|
说明:
|
||||||
1. 为了让菜单图片更短、更直观,只展示存在明确聊天指令的功能;
|
1. 按用户最新要求“不要管之前限制”,这里不再做仅指令功能过滤;
|
||||||
2. 纯自动/定时类功能不在图片主体展示,避免干扰普通用户阅读。
|
2. 全量展示 Feature,保证用户在一张图里看到完整功能地图;
|
||||||
|
3. 没有显式指令的功能,在卡片中标注为“自动/定时触发”。
|
||||||
"""
|
"""
|
||||||
result: list[Feature] = []
|
return list(Feature)
|
||||||
for feature in Feature:
|
|
||||||
_, cmd_hint = self._split_feature_description(feature.description)
|
|
||||||
if self._extract_command_tokens(cmd_hint):
|
|
||||||
result.append(feature)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def build_feature_status_markdown(self, group_id: str) -> str:
|
def build_feature_status_markdown(self, group_id: str) -> str:
|
||||||
"""构建紧凑菜单 Markdown(强调“怎么用”,并显示开启/关闭状态)。"""
|
"""构建菜单 Markdown(全量功能 + 状态 + 指令/触发方式)。"""
|
||||||
user_features = self._iter_user_command_features()
|
user_features = self._iter_user_command_features()
|
||||||
lines = [
|
lines = [
|
||||||
"# 机器人功能菜单",
|
"# 机器人功能菜单",
|
||||||
@@ -126,10 +122,6 @@ class RobotMenuRenderTool:
|
|||||||
f"- 目标:`{group_id}`",
|
f"- 目标:`{group_id}`",
|
||||||
f"- 生成时间:`{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}`",
|
f"- 生成时间:`{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}`",
|
||||||
"",
|
"",
|
||||||
"## 快速使用",
|
|
||||||
"- 发送 `菜单` 可再次查看本图",
|
|
||||||
"- 以下仅保留用户可直接使用的触发方式",
|
|
||||||
"",
|
|
||||||
"## 功能与指令",
|
"## 功能与指令",
|
||||||
]
|
]
|
||||||
for feature in user_features:
|
for feature in user_features:
|
||||||
@@ -154,30 +146,28 @@ class RobotMenuRenderTool:
|
|||||||
return "<br>".join(rows)
|
return "<br>".join(rows)
|
||||||
|
|
||||||
def build_feature_status_html(self, group_id: str) -> str:
|
def build_feature_status_html(self, group_id: str) -> str:
|
||||||
"""构建菜单 HTML(自定义样式,不复用 md2image 默认样式)。"""
|
"""构建 PlayStation 风格菜单 HTML(黑-白-蓝三段式)。"""
|
||||||
# UI 设计方向(依据用户指定 skill):
|
# 设计实现说明(基于 DESIGN-playstation.md):
|
||||||
# 1. 采用“精致极简 + 信息密度高”的纵向单列排版;
|
# 1. 三段式表面:黑色英雄区 -> 白色内容区 -> 蓝色底部;
|
||||||
# 2. 仅展示用户可直接触发的功能,保证篇幅短、理解快;
|
# 2. 主色锚点固定为 PlayStation Blue #0070cc,交互强调色为 #1eaedb;
|
||||||
# 3. 使用克制但有辨识度的色彩/字体组合,避免通用模板感。
|
# 3. 组件采用圆角胶囊/卡片体系,保持信息密度与可读性平衡。
|
||||||
user_features = self._iter_user_command_features()
|
user_features = self._iter_user_command_features()
|
||||||
feature_cards = []
|
feature_cards = []
|
||||||
|
enabled_count = 0
|
||||||
for feature in user_features:
|
for feature in user_features:
|
||||||
title, _ = self._split_feature_description(feature.description)
|
title, _ = self._split_feature_description(feature.description)
|
||||||
command_examples = self._get_feature_command_examples(feature)
|
command_examples = self._get_feature_command_examples(feature)
|
||||||
status = GroupBotManager.get_group_permission(group_id, feature)
|
status = GroupBotManager.get_group_permission(group_id, feature)
|
||||||
is_enabled = status == PermissionStatus.ENABLED
|
is_enabled = status == PermissionStatus.ENABLED
|
||||||
|
if is_enabled:
|
||||||
|
enabled_count += 1
|
||||||
status_text = "开启" if is_enabled else "关闭"
|
status_text = "开启" if is_enabled else "关闭"
|
||||||
status_class = "status-on" if is_enabled else "status-off"
|
status_class = "status-on" if is_enabled else "status-off"
|
||||||
# 为纵向卡片增加轻量色彩分组,提升视觉节奏感,避免纯白卡片过于单调。
|
|
||||||
try:
|
|
||||||
tone_idx = int(feature.value) % 4
|
|
||||||
except Exception:
|
|
||||||
tone_idx = 0
|
|
||||||
feature_cards.append(
|
feature_cards.append(
|
||||||
f"""
|
f"""
|
||||||
<section class="feature-card tone-{tone_idx}">
|
<section class="feature-card">
|
||||||
<div class="feature-top">
|
<div class="feature-top">
|
||||||
<div class="feature-index">{feature.value}</div>
|
<div class="feature-index">{feature.value:02d}</div>
|
||||||
<div class="feature-meta">
|
<div class="feature-meta">
|
||||||
<h3>{html.escape(title)}</h3>
|
<h3>{html.escape(title)}</h3>
|
||||||
<p class="feature-key">功能键:<code>{html.escape(feature.name)}</code></p>
|
<p class="feature-key">功能键:<code>{html.escape(feature.name)}</code></p>
|
||||||
@@ -199,189 +189,220 @@ class RobotMenuRenderTool:
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<style>
|
<style>
|
||||||
:root {{
|
:root {{
|
||||||
--card: #ffffff;
|
--ps-blue: #0070cc;
|
||||||
--line: #d8e4ef;
|
--ps-cyan: #1eaedb;
|
||||||
--text: #1f2d3d;
|
--ps-black: #000000;
|
||||||
--muted: #5e748a;
|
--ps-white: #ffffff;
|
||||||
--brand: #0b4f93;
|
--ink: #1f1f1f;
|
||||||
--brand-soft: #e9f2ff;
|
--muted: #6b6b6b;
|
||||||
--accent: #f59e0b;
|
--line: #f3f3f3;
|
||||||
|
--ok: #0f7a4f;
|
||||||
|
--ok-bg: #e9f9f1;
|
||||||
|
--off: #9f2f2f;
|
||||||
|
--off-bg: #feefef;
|
||||||
}}
|
}}
|
||||||
* {{ box-sizing: border-box; }}
|
* {{ box-sizing: border-box; }}
|
||||||
body {{
|
body {{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 10px;
|
padding: 16px;
|
||||||
font-family: "Source Han Sans SC", "PingFang SC", "Noto Sans CJK SC", sans-serif;
|
font-family: "PlayStation SST", "SST", "PingFang SC", "Noto Sans CJK SC", sans-serif;
|
||||||
background:
|
background: linear-gradient(180deg, #f5f7fa 0%, #ffffff 100%);
|
||||||
radial-gradient(circle at 12% 8%, rgba(11,79,147,0.08), transparent 36%),
|
color: var(--ink);
|
||||||
linear-gradient(180deg, #edf3f9 0%, #f7fbff 100%);
|
|
||||||
color: var(--text);
|
|
||||||
}}
|
}}
|
||||||
.page {{
|
.page {{
|
||||||
width: 660px;
|
width: 980px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: var(--card);
|
background: #fff;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid #dfe6ef;
|
||||||
border-radius: 12px;
|
border-radius: 24px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 8px 16px rgba(20, 47, 76, 0.08);
|
box-shadow: rgba(0, 0, 0, 0.08) 0 5px 9px 0;
|
||||||
}}
|
}}
|
||||||
.hero {{
|
.hero {{
|
||||||
padding: 12px 14px 10px 14px;
|
padding: 28px 30px 24px 30px;
|
||||||
background:
|
background: linear-gradient(180deg, #121314 0%, #000000 100%);
|
||||||
linear-gradient(130deg, rgba(255,255,255,0.12), rgba(255,255,255,0) 45%),
|
color: var(--ps-white);
|
||||||
linear-gradient(135deg, #0b4f93 0%, #256db3 58%, #3181c9 100%);
|
}}
|
||||||
color: #fff;
|
.hero-kicker {{
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: .3px;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: rgba(255,255,255,0.12);
|
||||||
|
border: 1px solid rgba(255,255,255,0.18);
|
||||||
|
margin-bottom: 12px;
|
||||||
}}
|
}}
|
||||||
.hero h1 {{
|
.hero h1 {{
|
||||||
margin: 0 0 4px 0;
|
margin: 0 0 8px 0;
|
||||||
font-size: 20px;
|
font-size: 44px;
|
||||||
font-family: "Source Han Serif SC", "STSong", serif;
|
font-weight: 300;
|
||||||
letter-spacing: .4px;
|
line-height: 1.25;
|
||||||
|
letter-spacing: .1px;
|
||||||
|
}}
|
||||||
|
.hero-sub {{
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: rgba(255,255,255,.86);
|
||||||
|
}}
|
||||||
|
.hero-meta {{
|
||||||
|
margin-top: 14px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}}
|
||||||
|
.meta-pill {{
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: rgba(255,255,255,0.10);
|
||||||
|
border: 1px solid rgba(255,255,255,0.16);
|
||||||
|
}}
|
||||||
|
.meta-pill strong {{ font-weight: 700; }}
|
||||||
|
.content {{
|
||||||
|
padding: 22px 24px 20px 24px;
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f5f7fa 100%);
|
||||||
}}
|
}}
|
||||||
.hero p {{ margin: 1px 0; font-size: 11px; opacity: .95; }}
|
|
||||||
.content {{ padding: 8px 10px 10px 10px; }}
|
|
||||||
.block {{
|
.block {{
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 9px;
|
border-radius: 20px;
|
||||||
padding: 8px 10px;
|
padding: 16px 18px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 14px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.06) 0 5px 9px 0;
|
||||||
}}
|
}}
|
||||||
.block h2 {{
|
.block h2 {{
|
||||||
margin: 0 0 5px 0;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #12395f;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}}
|
|
||||||
.block h2::before {{
|
|
||||||
content: "";
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--accent);
|
|
||||||
display: inline-block;
|
|
||||||
}}
|
|
||||||
.block ul {{
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 16px;
|
font-size: 22px;
|
||||||
color: var(--muted);
|
font-weight: 300;
|
||||||
line-height: 1.45;
|
line-height: 1.25;
|
||||||
font-size: 11px;
|
letter-spacing: .1px;
|
||||||
|
color: #000;
|
||||||
|
}}
|
||||||
|
.feature-list {{
|
||||||
|
margin-top: 14px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
}}
|
}}
|
||||||
/* 纵向单列流式布局:避免双列模式下因内容长度差异产生空白洞。 */
|
|
||||||
.feature-list {{ display: flex; flex-direction: column; gap: 8px; }}
|
|
||||||
.feature-card {{
|
.feature-card {{
|
||||||
border: 1px solid #d6e4f3;
|
border: 1px solid #e6edf5;
|
||||||
border-left-width: 4px;
|
border-radius: 19px;
|
||||||
border-radius: 9px;
|
padding: 12px 13px;
|
||||||
padding: 7px 9px;
|
background: #fff;
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
|
box-shadow: rgba(0, 0, 0, 0.08) 0 5px 9px 0;
|
||||||
box-shadow: 0 3px 8px rgba(16, 64, 125, 0.05);
|
transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
|
||||||
|
}}
|
||||||
|
.feature-card:hover {{
|
||||||
|
transform: scale(1.02);
|
||||||
|
border-color: var(--ps-cyan);
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.16) 0 5px 9px 0;
|
||||||
}}
|
}}
|
||||||
.feature-card.tone-0 {{ border-left-color: #2f8cff; }}
|
|
||||||
.feature-card.tone-1 {{ border-left-color: #10b981; }}
|
|
||||||
.feature-card.tone-2 {{ border-left-color: #f59e0b; }}
|
|
||||||
.feature-card.tone-3 {{ border-left-color: #ef4444; }}
|
|
||||||
.feature-top {{ display: flex; align-items: center; gap: 8px; }}
|
.feature-top {{ display: flex; align-items: center; gap: 8px; }}
|
||||||
.feature-index {{
|
.feature-index {{
|
||||||
min-width: 26px;
|
min-width: 38px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #0f5fb7;
|
color: var(--ps-blue);
|
||||||
background: var(--brand-soft);
|
background: #eef6ff;
|
||||||
border: 1px solid #cfe1fb;
|
border: 1px solid #d3e8ff;
|
||||||
border-radius: 6px;
|
border-radius: 12px;
|
||||||
padding: 2px 4px;
|
padding: 4px 6px;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
}}
|
}}
|
||||||
.feature-meta {{ flex: 1; }}
|
.feature-meta {{ flex: 1; }}
|
||||||
.feature-meta h3 {{ margin: 0 0 1px 0; font-size: 12px; }}
|
.feature-meta h3 {{
|
||||||
.feature-key {{ margin: 0; font-size: 10px; color: var(--muted); }}
|
margin: 0 0 2px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.25;
|
||||||
|
letter-spacing: .1px;
|
||||||
|
color: #000;
|
||||||
|
}}
|
||||||
|
.feature-key {{ margin: 0; font-size: 12px; color: var(--muted); }}
|
||||||
.status-badge {{
|
.status-badge {{
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
padding: 2px 8px;
|
padding: 4px 10px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}}
|
}}
|
||||||
.status-on {{
|
.status-on {{
|
||||||
color: #0f7a4f;
|
color: var(--ok);
|
||||||
background: #e9f9f1;
|
background: var(--ok-bg);
|
||||||
border-color: #bde8d2;
|
border-color: #bde8d2;
|
||||||
}}
|
}}
|
||||||
.status-off {{
|
.status-off {{
|
||||||
color: #9f2f2f;
|
color: var(--off);
|
||||||
background: #feefef;
|
background: var(--off-bg);
|
||||||
border-color: #f3c7c7;
|
border-color: #f3c7c7;
|
||||||
}}
|
}}
|
||||||
.feature-body {{
|
.feature-body {{
|
||||||
margin-top: 5px;
|
margin-top: 10px;
|
||||||
padding: 5px 7px;
|
padding: 10px 12px;
|
||||||
border-radius: 7px;
|
border-radius: 12px;
|
||||||
background: #f7fbff;
|
background: #f8fbff;
|
||||||
border: 1px dashed #d2e4f7;
|
border: 1px dashed #d6e6f7;
|
||||||
}}
|
}}
|
||||||
.feature-body p {{ margin: 0; font-size: 10.5px; color: #3d5870; line-height: 1.45; }}
|
.feature-body p {{ margin: 0; font-size: 14px; color: #1f1f1f; line-height: 1.5; }}
|
||||||
.line-main {{ color: #1f3f62; }}
|
.line-main {{ color: #1f1f1f; }}
|
||||||
.line-sub {{ color: #5a738a; }}
|
.line-sub {{ color: #6b6b6b; }}
|
||||||
code {{
|
code {{
|
||||||
background: #eef6ff;
|
background: #eef6ff;
|
||||||
border: 1px solid #d2e6ff;
|
border: 1px solid #d2e6ff;
|
||||||
color: #12539a;
|
color: #0068bd;
|
||||||
padding: 0 5px;
|
padding: 1px 7px;
|
||||||
border-radius: 5px;
|
border-radius: 999px;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
|
}}
|
||||||
|
.footer {{
|
||||||
|
background: var(--ps-blue);
|
||||||
|
color: #fff;
|
||||||
|
padding: 14px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}}
|
||||||
|
.footer a {{
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dashed rgba(255,255,255,.55);
|
||||||
|
}}
|
||||||
|
@media (max-width: 1024px) {{
|
||||||
|
.page {{ width: 100%; border-radius: 20px; }}
|
||||||
|
.hero h1 {{ font-size: 35px; }}
|
||||||
|
.feature-list {{ grid-template-columns: 1fr; }}
|
||||||
}}
|
}}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<header class="hero">
|
<header class="hero">
|
||||||
|
<div class="hero-kicker">PlayStation Style Interface</div>
|
||||||
<h1>机器人功能菜单</h1>
|
<h1>机器人功能菜单</h1>
|
||||||
<p>目标群/会话:{html.escape(group_id)}</p>
|
<p class="hero-sub">用户功能查看中心 · 直达命令与状态一屏可见</p>
|
||||||
<p>生成时间:{now_text}</p>
|
<div class="hero-meta">
|
||||||
|
<span class="meta-pill">目标:<strong>{html.escape(group_id)}</strong></span>
|
||||||
|
<span class="meta-pill">功能总数:<strong>{len(user_features)}</strong></span>
|
||||||
|
<span class="meta-pill">已开启:<strong>{enabled_count}</strong></span>
|
||||||
|
<span class="meta-pill">生成时间:<strong>{now_text}</strong></span>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="content">
|
<main class="content">
|
||||||
<section class="block">
|
<section class="block">
|
||||||
<h2>快速上手</h2>
|
<h2>功能卡片</h2>
|
||||||
<div class="feature-list">
|
|
||||||
<section class="feature-card tone-0">
|
|
||||||
<div class="feature-top">
|
|
||||||
<div class="feature-index">A</div>
|
|
||||||
<div class="feature-meta">
|
|
||||||
<h3>查看菜单</h3>
|
|
||||||
<p class="feature-key"><code>菜单</code></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="feature-body">
|
|
||||||
<p><span class="line-main">发送一次即可查看全部功能卡片</span></p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="feature-card tone-1">
|
|
||||||
<div class="feature-top">
|
|
||||||
<div class="feature-index">B</div>
|
|
||||||
<div class="feature-meta">
|
|
||||||
<h3>展示范围</h3>
|
|
||||||
<p class="feature-key">用户直接可用指令</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="feature-body">
|
|
||||||
<p><span class="line-main">当前共 {len(user_features)} 项</span></p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="block">
|
|
||||||
<h2>功能与指令</h2>
|
|
||||||
<div class="feature-list">
|
<div class="feature-list">
|
||||||
{''.join(feature_cards)}
|
{''.join(feature_cards)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
<footer class="footer">
|
||||||
|
<span>PlayStation Blue UI · 功能查看体验重构</span>
|
||||||
|
<a href="javascript:void(0)">菜单</a>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user