From 1e31a54573c624f95b1a3b2b76cdbeab2c0e76b7 Mon Sep 17 00:00:00 2001 From: karuo Date: Mon, 2 Mar 2026 02:35:49 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-03-02=2002:35=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9A=E6=B0=B4=E6=A1=A5=E5=B9=B3=E5=8F=B0=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E3=80=81=E5=8D=A1=E6=9C=A8=E3=80=81=E8=BF=90=E8=90=A5=E4=B8=AD?= =?UTF-8?q?=E6=9E=A2=E5=B7=A5=E4=BD=9C=E5=8F=B0=20|=20=E6=8E=92=E9=99=A4?= =?UTF-8?q?=20>20MB:=2014=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md | 30 ++- .../飞书管理/参考资料/飞书日志JSON格式与API对照.md | 1 + .../飞书管理/脚本/upload_json_to_feishu_doc.py | 131 ++++++++---- .../PPT制作/脚本/generate_novel_illustrations.py | 188 ++++++++++++++++-- 运营中枢/工作台/gitea_push_log.md | 1 + 运营中枢/工作台/代码管理.md | 1 + 6 files changed, 292 insertions(+), 60 deletions(-) diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md b/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md index b8483312..63dc6f04 100755 --- a/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md +++ b/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md @@ -1,11 +1,11 @@ --- name: 飞书管理 description: 飞书日志/文档自动写入与知识库管理 -triggers: 飞书日志、写入飞书、飞书知识库、飞书运营报表、派对效果数据、104场写入、运营报表填写、派对截图填表发群、Excel写飞书、批量写飞书表格、表格日报、卡若的飞书日志、卡若飞书日志 +triggers: 飞书日志、写入飞书、飞书知识库、飞书运营报表、派对效果数据、104场写入、运营报表填写、派对截图填表发群、Excel写飞书、批量写飞书表格、表格日报、卡若的飞书日志、卡若飞书日志、上传json飞书、json上传飞书文档、按原格式上传飞书 owner: 水桥 group: 水 -version: "1.2" -updated: "2026-02-25" +version: "1.3" +updated: "2026-03-02" --- # 飞书日志写入 Skill @@ -349,6 +349,29 @@ JSON 格式:与 `团队入职流程与新人登记表_feishu_blocks.json` 相 --- +## 飞书导出 JSON 按原格式上传 + +将飞书导出的 JSON 文件(含 `content` + `blocks`)**按原有类型**上传为 Wiki 子文档:文档保持文档、多维表格会新建多维表格并嵌入、问卷/思维笔记等按 JSON 内类型还原。 + +**规则**:原 JSON 里是什么格式就生成什么格式;不把多维表格/看板换成链接,直接生成对应文档或多维表格块。 + +```bash +# 上传单个导出 JSON(默认父节点:日记/新研究) +python3 脚本/upload_json_to_feishu_doc.py /path/to/xxx.json + +# 指定父节点与标题 +python3 脚本/upload_json_to_feishu_doc.py /path/to/xxx.json --parent --title "文档标题" +``` + +- **block_type 2**:正文 → 正文块 +- **block_type 3/4/6**:标题 → 对应标题块 +- **block_type 43(board/bitable)**:多维表格 → 在云空间新建多维表格(bitable),并将该块嵌入文档 +- 其他类型(todo、callout 等)按导出结构透传 + +应用需具备「创建多维表格」权限;若无权限,多维表格块会退化为一段说明文字。 + +--- + ## 统一文章上传(强制入口) 用于“本地 Markdown → 飞书 Wiki 文档”的统一发布。 @@ -424,6 +447,7 @@ python3 script.py --arg value ├── feishu_video_clip_README.md ├── wanzhi_feishu_project_sync.py # 玩值电竞→飞书项目同步 ├── feishu_wiki_create_doc.py # Wiki 子文档创建(日记/研究) + ├── upload_json_to_feishu_doc.py # 飞书导出 JSON 按原格式上传(文档/多维表格/问卷等) └── .feishu_tokens.json # Token 存储 ``` diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/飞书日志JSON格式与API对照.md b/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/飞书日志JSON格式与API对照.md index bcbb8a89..1c701b72 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/飞书日志JSON格式与API对照.md +++ b/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/飞书日志JSON格式与API对照.md @@ -32,6 +32,7 @@ | 18 | 画廊 | `gallery` | `gallery.imageList[].fileToken`,单图也可用 | | 19 | 高亮块 | `callout` | `callout.background_color`、`border_color`、`emoji_id`(如 sunrise) | | 27 | 图片(导出) | `image` | 导出用;**写入用 12 file**,先 `drive/v1/medias/upload_all` 上传 | +| 43 | 多维表格 | `board`/`bitable` | 导出为 `board.token`;写入用 `bitable.token`。按原格式上传时由 `upload_json_to_feishu_doc.py` 新建多维表格并嵌入 | --- diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/upload_json_to_feishu_doc.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/upload_json_to_feishu_doc.py index de367397..2801af90 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/upload_json_to_feishu_doc.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/upload_json_to_feishu_doc.py @@ -1,12 +1,15 @@ #!/usr/bin/env python3 """ -将飞书导出的 JSON 文件(含 content + blocks)上传为飞书 Wiki 子文档。 -用法: python3 upload_json_to_feishu_doc.py /path/to/水流程规划.json +将飞书导出的 JSON 文件(含 content + blocks)按**原有格式**上传为飞书 Wiki 子文档。 +- 文档→文档,多维表格→多维表格(block_type 43 会新建多维表格并嵌入),问卷/思维笔记等按类型还原。 +用法: python3 upload_json_to_feishu_doc.py /path/to/xxx.json 可选: --parent --title "文档标题" """ import json import sys +import time import argparse +import requests from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent @@ -18,37 +21,65 @@ from feishu_wiki_create_doc import create_wiki_doc, get_token, CONFIG DEFAULT_PARENT = "KNf7wA8Rki1NSdkkSIqcdFtTnWb" -def _text_block(content: str): - return { - "block_type": 2, - "text": { - "elements": [{"text_run": {"content": content, "text_element_style": {}}}], - "style": {}, - }, - } +def _to_api_block(b: dict) -> dict | None: + """将导出块转为 API 可用的块(去掉 block_id、parent_id,保留 block_type 与类型字段)。""" + bt = b.get("block_type") + out = {"block_type": bt} + if bt == 2 and b.get("text"): + out["text"] = b["text"] + elif bt == 3 and b.get("heading1"): + out["heading1"] = b["heading1"] + elif bt == 4 and b.get("heading2"): + out["heading2"] = b["heading2"] + elif bt == 6 and b.get("heading4"): + out["heading4"] = b["heading4"] + elif bt == 17 and b.get("todo"): + out["todo"] = b["todo"] + elif bt == 19 and b.get("callout"): + out["callout"] = b["callout"] + elif bt == 43: + # 多维表格:导出为 board.token,API 为 bitable.token;占位,后续用新建的 app_token 替换 + token = (b.get("board") or b.get("bitable") or {}).get("token", "") + out["_bitable_placeholder"] = True + out["_bitable_token"] = token # 可能为原文档 token(同租户可尝试直接嵌) + out["bitable"] = {"token": token or "PLACEHOLDER"} + else: + # 其他类型尽量透传类型字段 + for key in ("page", "board", "bitable", "sheet", "mindnote", "poll"): + if key in b and not key.startswith("_"): + out[key] = b[key] + break + if "_bitable_placeholder" not in out and "bitable" not in out and "board" in b: + out["_bitable_placeholder"] = True + out["bitable"] = {"token": (b.get("board") or {}).get("token", "PLACEHOLDER")} + return out -def _heading1_block(content: str): - return { - "block_type": 3, - "heading1": { - "elements": [{"text_run": {"content": content, "text_element_style": {}}}], - "style": {}, - }, - } +def create_bitable_app(access_token: str, name: str, folder_token: str | None = None) -> str | None: + """在飞书云空间创建多维表格,返回 app_token。""" + url = "https://open.feishu.cn/open-apis/bitable/v1/apps" + headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"} + payload = {"name": name or "多维表格"} + if folder_token: + payload["folder_token"] = folder_token + r = requests.post(url, headers=headers, json=payload, timeout=30) + data = r.json() + if data.get("code") == 0: + return data.get("data", {}).get("app_token") + return None def blocks_from_export_json(data: dict) -> tuple[str, list]: """ 从飞书导出格式(content + blocks)解析出标题和可写入的 children 列表。 + 多维表格(block_type 43/board)会产出占位块,需在获得 token 后调用 resolve_bitable_placeholders 替换。 返回 (title, children)。 """ blocks = data.get("blocks") or [] if not blocks: title = (data.get("content") or "未命名").split("\n")[0].strip() or "未命名" - return title, [_heading1_block(title)] + return title, [{"block_type": 3, "heading1": {"elements": [{"text_run": {"content": title, "text_element_style": {}}}], "style": {}}}] - # 找根块(block_type 1 page 或 parent_id 为空) root = None by_id = {} for b in blocks: @@ -58,9 +89,8 @@ def blocks_from_export_json(data: dict) -> tuple[str, list]: if not root: title = (data.get("content") or "未命名").split("\n")[0].strip() or "未命名" - return title, [_heading1_block(title)] + return title, [{"block_type": 3, "heading1": {"elements": [{"text_run": {"content": title, "text_element_style": {}}}], "style": {}}}] - # 标题:从 page 或 content 第一行取 title = "未命名" if root.get("page") and root["page"].get("elements"): for el in root["page"]["elements"]: @@ -73,7 +103,7 @@ def blocks_from_export_json(data: dict) -> tuple[str, list]: child_ids = root.get("children") or [] children = [] - children.append(_heading1_block(title)) + children.append({"block_type": 3, "heading1": {"elements": [{"text_run": {"content": title, "text_element_style": {}}}], "style": {}}}) for bid in child_ids: b = by_id.get(bid) @@ -82,24 +112,44 @@ def blocks_from_export_json(data: dict) -> tuple[str, list]: bt = b.get("block_type") if bt == 2 and b.get("text"): els = b["text"].get("elements") or [] - content = "" - for el in els: - content += el.get("text_run", {}).get("content", "") - content = content.strip() - if content: - children.append(_text_block(content)) - # 空段落可跳过,也可保留一行空 - elif bt == 43 and b.get("board"): - # 多维表格/看板:API 可能不支持直接插入,用说明占位 - token = b["board"].get("token", "") - children.append(_text_block("(原文档含多维表格/看板,可在原链接中查看)")) - # 其他类型可后续扩展 + content = "".join(el.get("text_run", {}).get("content", "") for el in els).strip() + if not content: + continue + style = b["text"].get("style", {}) + el_style = (b["text"].get("elements") or [{}])[0].get("text_element_style", {}) + children.append({"block_type": 2, "text": {"elements": [{"text_run": {"content": content, "text_element_style": el_style}}], "style": style}}) + elif bt == 43 and (b.get("board") or b.get("bitable")): + token = (b.get("board") or b.get("bitable") or {}).get("token", "") + children.append({"_bitable_placeholder": True, "block_type": 43, "bitable": {"token": token}, "name": "流量来源"}) + elif bt not in (2, 43): + api_block = _to_api_block(b) + if api_block and not api_block.get("_bitable_placeholder"): + children.append(api_block) return title, children +def resolve_bitable_placeholders(children: list, access_token: str, default_name: str = "多维表格") -> list: + """将 children 中的 _bitable_placeholder 替换为新建的多维表格块(block_type 43 + bitable.token)。""" + out = [] + for i, c in enumerate(children): + if isinstance(c, dict) and c.get("_bitable_placeholder") and c.get("block_type") == 43: + name = c.get("name") or default_name + app_token = create_bitable_app(access_token, name) + if app_token: + out.append({"block_type": 43, "bitable": {"token": app_token}}) + time.sleep(0.4) + else: + out.append({"block_type": 2, "text": {"elements": [{"text_run": {"content": "(多维表格创建失败,请手动插入)", "text_element_style": {}}}], "style": {}}}) + else: + c_copy = {k: v for k, v in (c or {}).items() if not k.startswith("_")} + if c_copy: + out.append(c_copy) + return out + + def main(): - ap = argparse.ArgumentParser(description="将飞书导出 JSON 上传为飞书 Wiki 文档") + ap = argparse.ArgumentParser(description="将飞书导出 JSON 按原格式上传为飞书 Wiki 文档") ap.add_argument("json_path", help="JSON 文件路径(含 content + blocks)") ap.add_argument("--parent", default=DEFAULT_PARENT, help="Wiki 父节点 token") ap.add_argument("--title", default=None, help="覆盖文档标题(默认从 JSON 解析)") @@ -117,8 +167,15 @@ def main(): if args.title: title = args.title + token = get_token(args.parent) + if not token: + print("❌ 无法获取飞书 Token") + sys.exit(1) + + children = resolve_bitable_placeholders(children, token, default_name="流量来源") + print("=" * 50) - print(f"📤 上传为飞书文档:{title}") + print(f"📤 上传为飞书文档(按原格式):{title}") print(f" 父节点: {args.parent}") print(f" 块数: {len(children)}") print("=" * 50) diff --git a/03_卡木(木)/木果_项目模板/PPT制作/脚本/generate_novel_illustrations.py b/03_卡木(木)/木果_项目模板/PPT制作/脚本/generate_novel_illustrations.py index cdd30dc9..a0fdb1eb 100644 --- a/03_卡木(木)/木果_项目模板/PPT制作/脚本/generate_novel_illustrations.py +++ b/03_卡木(木)/木果_项目模板/PPT制作/脚本/generate_novel_illustrations.py @@ -93,34 +93,182 @@ def _font(size: int): return ImageFont.load_default() +def _radial_gradient(img, cx, cy, r_inner, r_outer, c_center, c_edge): + """从中心到边缘的径向渐变""" + from PIL import ImageDraw + W, H = img.size + pix = img.load() + for y in range(H): + for x in range(W): + d = ((x - cx) ** 2 + (y - cy) ** 2) ** 0.5 + t = min(1.0, max(0.0, (d - r_inner) / (r_outer - r_inner))) if r_outer > r_inner else 0 + r = int(c_center[0] + (c_edge[0] - c_center[0]) * t) + g = int(c_center[1] + (c_edge[1] - c_center[1]) * t) + b = int(c_center[2] + (c_edge[2] - c_center[2]) * t) + pix[x, y] = (max(0, min(255, r)), max(0, min(255, g)), max(0, min(255, b))) + + +def _draw_speed_lines(draw, W, H, n=40, color=(255, 220, 230, 80)): + """漫画速度线(从一角放射)""" + import random + random.seed(42) + for _ in range(n): + x0, y0 = random.randint(0, W // 3), random.randint(0, H // 3) + dx, dy = random.randint(W // 2, W), random.randint(H // 2, H) + draw.line([(x0, y0), (x0 + dx, y0 + dy)], fill=(color[0], color[1], color[2]), width=2) + + +def _draw_vignette(img, strength=0.4): + """四角暗角""" + from PIL import ImageDraw + W, H = img.size + overlay = Image.new("RGBA", (W, H), (0, 0, 0, 0)) + draw = ImageDraw.Draw(overlay) + for y in range(H): + for x in range(W): + nx, ny = 2 * x / W - 1, 2 * y / H - 1 + d = (nx * nx + ny * ny) ** 0.5 + a = int(255 * strength * min(1, d * 1.2)) + if a > 0: + draw.point((x, y), fill=(0, 0, 0, a)) + out = Image.new("RGB", (W, H)) + out.paste(img, (0, 0)) + from PIL import Image + out.paste(overlay, (0, 0), overlay) + return out + + def generate_with_pil(): - """用 PIL 绘制 5 张漫画风格占位图(渐变+标题+简单构图)""" + """用 PIL 绘制 5 张漫画风格插画(双人剪影+光效+速度线+层次)""" from PIL import Image, ImageDraw W, H = 1024, 1024 OUT_DIR.mkdir(parents=True, exist_ok=True) - titles = ["场景1 · 蒙眼拥抱", "场景2 · 侧抱", "场景3 · 额相抵", "后续1 · 温柔对视", "后续2 · 并肩"] - colors = [ - ((180, 100, 120), (80, 40, 60)), - ((120, 80, 140), (50, 30, 70)), - ((100, 120, 160), (40, 50, 80)), - ((140, 160, 180), (60, 70, 90)), - ((160, 140, 120), (70, 60, 50)), + + # 场景配置:背景中心色、边缘色、剪影色、标题、副标题 + scenes = [ + { + "center": (220, 160, 200), + "edge": (60, 35, 55), + "silhouette": (45, 30, 45), + "title": "蒙眼拥抱", + "sub": "—— 场景一", + "speed_lines": True, + }, + { + "center": (200, 150, 220), + "edge": (50, 40, 70), + "silhouette": (40, 32, 55), + "title": "侧抱", + "sub": "—— 场景二", + "speed_lines": True, + }, + { + "center": (210, 170, 230), + "edge": (55, 45, 75), + "silhouette": (42, 35, 58), + "title": "额相抵", + "sub": "—— 场景三", + "speed_lines": True, + }, + { + "center": (230, 200, 220), + "edge": (70, 60, 85), + "silhouette": (50, 42, 62), + "title": "温柔对视", + "sub": "—— 后续一", + "speed_lines": False, + }, + { + "center": (240, 210, 200), + "edge": (75, 65, 80), + "silhouette": (52, 45, 65), + "title": "并肩", + "sub": "—— 后续二", + "speed_lines": False, + }, ] - for (filename, _), title, (c1, c2) in zip(PROMPTS, titles, colors): + + for idx, ((filename, _), sc) in enumerate(zip(PROMPTS, scenes)): out_path = OUT_DIR / filename img = Image.new("RGB", (W, H)) + cx, cy = W // 2, H // 2 + _radial_gradient(img, cx, cy, 0, int((W * W + H * H) ** 0.5 * 0.6), sc["center"], sc["edge"]) + + # 中心柔光 + glow = Image.new("RGBA", (W, H), (0, 0, 0, 0)) + gdraw = ImageDraw.Draw(glow) + for r in range(350, 100, -15): + alpha = 25 if r > 200 else 15 + gdraw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=(255, 245, 255, alpha), width=4) + img.paste(glow, (0, 0), glow) + draw = ImageDraw.Draw(img) - for y in range(H): - t = y / (H - 1) - r = int(c1[0] + (c2[0] - c1[0]) * t) - g = int(c1[1] + (c2[1] - c1[1]) * t) - b = int(c1[2] + (c2[2] - c1[2]) * t) - draw.line([(0, y), (W, y)], fill=(r, g, b)) - font = _font(52) - bbox = draw.textbbox((0, 0), title, font=font) - tw = bbox[2] - bbox[0] - draw.text(((W - tw) // 2, H // 2 - 40), title, font=font, fill=(255, 255, 255, 230)) - draw.rounded_rectangle([W//4, H//3, 3*W//4, 2*H//3], radius=24, outline=(255, 255, 255, 120), width=3) + s = sc["silhouette"] + + if idx == 0: + # 场景1:正面拥抱(女前男后,女有长发+蒙眼带,男环抱) + # 女性剪影:椭圆头+长发轮廓+身体 + draw.ellipse([cx - 85, cy - 220, cx + 85, cy - 70], fill=s, outline=(60, 45, 60)) + draw.ellipse([cx - 75, cy - 200, cx + 75, cy - 90], fill=(30, 22, 30)) # 蒙眼带区域 + draw.polygon([(cx - 95, cy - 80), (cx - 110, cy + 180), (cx - 60, cy + 200), (cx - 50, cy - 60)], fill=s) + draw.polygon([(cx + 95, cy - 80), (cx + 110, cy + 180), (cx + 60, cy + 200), (cx + 50, cy - 60)], fill=s) + draw.ellipse([cx - 70, cy - 30, cx + 70, cy + 120], fill=s) + # 男性剪影:从后环抱 + draw.ellipse([cx - 100, cy - 180, cx + 100, cy - 30], fill=(s[0] + 15, s[1] + 12, s[2] + 15)) + draw.polygon([(cx - 120, cy - 50), (cx - 130, cy + 220), (cx + 130, cy + 220), (cx + 120, cy - 50)], fill=(s[0] + 15, s[1] + 12, s[2] + 15)) + elif idx == 1: + # 场景2:侧抱(男在侧,一手环腰) + draw.ellipse([cx - 90, cy - 200, cx + 90, cy - 50], fill=s) + draw.ellipse([cx - 70, cy - 180, cx + 70, cy - 70], fill=(28, 20, 28)) + draw.polygon([(cx - 100, cy - 40), (cx - 115, cy + 200), (cx - 55, cy + 210), (cx - 45, cy - 50)], fill=s) + draw.polygon([(cx + 100, cy - 40), (cx + 115, cy + 200), (cx + 55, cy + 210), (cx + 45, cy - 50)], fill=s) + draw.ellipse([cx - 75, cy + 10, cx + 75, cy + 140], fill=s) + # 男性侧影 + draw.ellipse([cx + 60, cy - 160, cx + 220, cy + 20], fill=(s[0] + 12, s[1] + 10, s[2] + 12)) + draw.polygon([(cx + 80, cy - 20), (cx + 100, cy + 220), (cx + 230, cy + 200), (cx + 200, cy - 40)], fill=(s[0] + 12, s[1] + 10, s[2] + 12)) + draw.ellipse([cx + 140, cy - 50, cx + 220, cy + 50], fill=(s[0] + 12, s[1] + 10, s[2] + 12)) + elif idx == 2: + # 场景3:额相抵、抚发 + draw.ellipse([cx - 88, cy - 210, cx + 88, cy - 65], fill=s) + draw.ellipse([cx - 68, cy - 190, cx + 68, cy - 85], fill=(28, 22, 30)) + draw.polygon([(cx - 98, cy - 55), (cx - 112, cy + 190), (cx - 52, cy + 200), (cx - 48, cy - 55)], fill=s) + draw.polygon([(cx + 98, cy - 55), (cx + 112, cy + 190), (cx + 52, cy + 200), (cx + 48, cy - 55)], fill=s) + draw.ellipse([cx - 72, cy - 10, cx + 72, cy + 115], fill=s) + draw.ellipse([cx - 95, cy - 175, cx + 95, cy - 25], fill=(s[0] + 10, s[1] + 8, s[2] + 10)) + draw.polygon([(cx - 105, cy - 35), (cx - 118, cy + 210), (cx + 118, cy + 210), (cx + 105, cy - 35)], fill=(s[0] + 10, s[1] + 8, s[2] + 10)) + elif idx == 3: + # 后续1:面对面温柔对视(无蒙眼,两人相对) + draw.ellipse([cx - 200, cy - 180, cx - 20, cy + 20], fill=s) # 女左 + draw.polygon([(cx - 210, cy + 10), (cx - 230, cy + 220), (cx - 50, cy + 230), (cx - 30, cy + 30)], fill=s) + draw.ellipse([cx - 180, cy - 30, cx - 40, cy + 100], fill=s) + draw.ellipse([cx + 20, cy - 180, cx + 200, cy + 20], fill=(s[0] + 12, s[1] + 10, s[2] + 12)) # 男右 + draw.polygon([(cx + 30, cy + 10), (cx + 50, cy + 230), (cx + 230, cy + 220), (cx + 210, cy + 30)], fill=(s[0] + 12, s[1] + 10, s[2] + 12)) + draw.ellipse([cx + 40, cy - 30, cx + 180, cy + 100], fill=(s[0] + 12, s[1] + 10, s[2] + 12)) + else: + # 后续2:并肩坐(两人并排,女靠肩) + draw.ellipse([cx - 220, cy - 80, cx - 80, cy + 80], fill=s) + draw.polygon([(cx - 230, cy + 50), (cx - 250, cy + 230), (cx - 120, cy + 240), (cx - 100, cy + 60)], fill=s) + draw.ellipse([cx - 210, cy + 30, cx - 100, cy + 180], fill=s) + draw.ellipse([cx + 60, cy - 100, cx + 200, cy + 60], fill=(s[0] + 10, s[1] + 8, s[2] + 12)) + draw.polygon([(cx + 90, cy + 40), (cx + 70, cy + 230), (cx + 220, cy + 220), (cx + 210, cy + 50)], fill=(s[0] + 10, s[1] + 8, s[2] + 12)) + draw.ellipse([cx + 100, cy + 50, cx + 200, cy + 170], fill=(s[0] + 10, s[1] + 8, s[2] + 12)) + + if sc.get("speed_lines"): + _draw_speed_lines(draw, W, H, n=55, color=(255, 235, 245)) + + # 暗角 + img = _draw_vignette(img, strength=0.35) + + draw = ImageDraw.Draw(img) + font_t = _font(56) + font_s = _font(28) + bbox_t = draw.textbbox((0, 0), sc["title"], font=font_t) + tw = bbox_t[2] - bbox_t[0] + draw.text(((W - tw) // 2, H - 140), sc["title"], font=font_t, fill=(255, 252, 255)) + bbox_s = draw.textbbox((0, 0), sc["sub"], font=font_s) + sw = bbox_s[2] - bbox_s[0] + draw.text(((W - sw) // 2, H - 78), sc["sub"], font=font_s, fill=(220, 210, 225)) + img.save(out_path, "PNG") print("OK:", out_path) return True diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index cd7c3588..002098e0 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -192,3 +192,4 @@ | 2026-03-01 23:41:32 | 🔄 卡若AI 同步 2026-03-01 23:41 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | | 2026-03-02 00:14:40 | 🔄 卡若AI 同步 2026-03-02 00:14 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 14 个 | | 2026-03-02 00:25:52 | 🔄 卡若AI 同步 2026-03-02 00:25 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | +| 2026-03-02 02:30:31 | 🔄 卡若AI 同步 2026-03-02 02:30 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index bca821cd..507b4d45 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -195,3 +195,4 @@ | 2026-03-01 23:41:32 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-01 23:41 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-02 00:14:40 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-02 00:14 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-02 00:25:52 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-02 00:25 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-03-02 02:30:31 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-02 02:30 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |