Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:
- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
- docx family: kept docx (official, full toolchain) + docx-cn
(GB/T 9704 Chinese official-document constants),
dropped docx_writer (no scripts, name collided with docx)
- humanizer family: kept humanizer-zh (6 zh reference docs),
dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
live credentials
Result: 25 skills, 572 files, ~7.5 MB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
"""KPI 卡片布局 — 投行级克制风格"""
|
||
|
||
from pptx.util import Inches, Pt
|
||
from pptx.enum.shapes import MSO_SHAPE
|
||
from ..helpers import set_slide_bg, add_text, add_rounded_rect, add_rect, add_page_header, setup_content_page
|
||
|
||
|
||
def layout_kpi_cards(slide, data, theme):
|
||
"""KPI 大数字卡片 — 3~6 个指标,分栏+极浅分割线
|
||
|
||
data keys:
|
||
title (str): 页面标题
|
||
subtitle (str, optional): 副标题/说明
|
||
cards (list[dict]): 每个 card 包含:
|
||
label (str): 指标名
|
||
value (str): 数值(已格式化)
|
||
change (str, optional): 变化(如 "+5.9%")
|
||
note (str, optional): 脚注
|
||
footnote (str, optional): 数据来源/脚注(auto-footer 渲染)
|
||
"""
|
||
m, sw, content_y, content_w, footer_y = setup_content_page(slide, data, theme)
|
||
|
||
if data.get("subtitle"):
|
||
add_text(slide, data["subtitle"],
|
||
x=m, y=content_y, w=content_w, h=0.3,
|
||
font_size=theme["body_size"], color=theme["text_muted"],
|
||
font_name=theme["body_font"])
|
||
|
||
cards = data["cards"]
|
||
n = len(cards)
|
||
|
||
# 自动 2x3 网格(6 卡时)或单行
|
||
if n <= 4:
|
||
_render_row(slide, cards, theme, m, sw, content_y + 0.5, footer_y)
|
||
else:
|
||
# 2x3 网格
|
||
row1 = cards[:3]
|
||
row2 = cards[3:]
|
||
mid_y = content_y + 0.5
|
||
row_h = (footer_y - mid_y - 0.3) / 2
|
||
_render_row(slide, row1, theme, m, sw, mid_y, mid_y + row_h)
|
||
_render_row(slide, row2, theme, m, sw, mid_y + row_h + 0.15, footer_y - 0.15)
|
||
|
||
|
||
def _render_row(slide, cards, theme, m, sw, start_y, end_y):
|
||
"""渲染一行 KPI 卡片 — 分栏+极浅分割线,无阴影/圆角"""
|
||
n = len(cards)
|
||
content_w = sw - 2 * m
|
||
col_w = content_w / n
|
||
card_h = end_y - start_y - 0.2
|
||
|
||
# KPI 字号: 收敛到 28-32pt(原 44pt 太 SaaS 感)
|
||
kpi_num_size = min(theme.get("kpi_size", 44), 32)
|
||
change_size = min(theme["body_size"], 11)
|
||
|
||
for i, card in enumerate(cards):
|
||
cx = m + i * col_w
|
||
|
||
# 分割线(第一栏不画左边线)
|
||
if i > 0:
|
||
add_rect(slide, cx, start_y + 0.2, 0.008, card_h - 0.4,
|
||
fill=theme["border"])
|
||
|
||
# 指标名 — 小字、顶部
|
||
add_text(slide, card["label"],
|
||
x=cx + 0.25, y=start_y + 0.15, w=col_w - 0.5, h=0.35,
|
||
font_size=theme["body_size"], color=theme["text_muted"],
|
||
align="left", font_name=theme["body_font"])
|
||
|
||
# 大数字 — 28-32pt,克制
|
||
add_text(slide, card["value"],
|
||
x=cx + 0.25, y=start_y + 0.55, w=col_w - 0.5, h=1.0,
|
||
font_size=kpi_num_size, color=theme["primary"],
|
||
bold=True, align="left", valign="middle",
|
||
font_name=theme["header_font"])
|
||
|
||
# 变化值 — 10-12pt + 次要色
|
||
if card.get("change"):
|
||
change = card["change"]
|
||
is_positive = change.startswith("+") or (not change.startswith("-") and "%" in change)
|
||
color = theme["positive"] if is_positive else theme["negative"]
|
||
add_text(slide, change,
|
||
x=cx + 0.25, y=start_y + 1.6, w=col_w - 0.5, h=0.3,
|
||
font_size=change_size, color=color,
|
||
font_name=theme["body_font"])
|
||
|
||
# 脚注
|
||
if card.get("note"):
|
||
add_text(slide, card["note"],
|
||
x=cx + 0.25, y=start_y + 2.0, w=col_w - 0.5, h=0.5,
|
||
font_size=theme.get("footer_size", 8), color=theme["text_muted"],
|
||
font_name=theme["body_font"])
|