feat: 新增门店选址分析功能
- 创建选址分析7个SQL视图+5张物化表(38s→33ms) - 后端新增5个选址API端点 - 前端新增SiteSelectionPage页面(散点图+4Tab) - 侧边栏新增门店选址导航入口 - 修复SQL列引用错误(d.→a.,去掉重复列) - 创建v_store_action_priority_deep_april和v_store_area_efficiency_april基础视图
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add subtitle text to each screenshot, then build video with ffmpeg."""
|
||||
import os, subprocess
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
DEMO_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
FONT_PATH = "/System/Library/Fonts/STHeiti Medium.ttc"
|
||||
FONT_SIZE = 36
|
||||
|
||||
# (filename, subtitle_text)
|
||||
screenshots = [
|
||||
("s01-dashboard-top.png", "总部驾驶舱 — 全局经营总览,KPI一屏掌握"),
|
||||
("s02-dashboard-charts.png", "象限散点图·瀑布图·平台成本率 — 数据深度分析"),
|
||||
("s03-regional-overview.png", "区域经理工作台 — 12区经营汇总,红黄绿风险分布"),
|
||||
("s04-store-manager-full.png", "店长工作台 — 经营概览·指标进度·SKU备货·任务闭环"),
|
||||
("s05-store-manager-top.png", "店长工作台 — 异常标注+指标进度条,几分钟看完就知道今天干什么"),
|
||||
("s06-store-detail-overview.png", "门店详情·概览 — 菜百店全链路:KPI·日度趋势·平台经济性"),
|
||||
("s07-store-detail-meal-period.png", "门店详情·餐段分析 — 午市/晚市/下午茶/早市客流与客单价"),
|
||||
("s08-store-detail-category.png", "门店详情·品类结构 — 销售集中度与搭售机会分析"),
|
||||
("s09-store-detail-cost.png", "门店详情·成本分析 — 理论成本vs实际成本,精准定位差异"),
|
||||
("s10-store-detail-member.png", "门店详情·会员分析 — RFM分层,沉睡会员召回机会"),
|
||||
("s11-store-detail-anomaly.png", "门店详情·异常账单 — 风险内控,自动识别与闭环处理"),
|
||||
("s12-monthly-review-summary.png", "月度验收·验收汇总 — 从数字化到智能化,只推可执行指令"),
|
||||
("s13-monthly-review-grade.png", "月度验收·升降级面板 — P0/P1/P2等级变化,复制盈利模型"),
|
||||
("s14-monthly-review-completion.png","月度验收·任务完成率 — 按优先级汇总,闭环验证改善效果"),
|
||||
("s15-monthly-review-activity.png", "月度验收·活动清单 — 逐活动贡献毛利,停改留由数据驱动"),
|
||||
("s16-monthly-review-sku.png", "月度验收·SKU治理 — ABC分类,长尾精简,标准化输出"),
|
||||
]
|
||||
|
||||
sub_dir = os.path.join(DEMO_DIR, "sub")
|
||||
os.makedirs(sub_dir, exist_ok=True)
|
||||
|
||||
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
||||
|
||||
for fname, text in screenshots:
|
||||
src = os.path.join(DEMO_DIR, fname)
|
||||
img = Image.open(src).convert("RGBA")
|
||||
w, h = img.size
|
||||
|
||||
# Create overlay for text background bar
|
||||
overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(overlay)
|
||||
|
||||
# Measure text
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
tw = bbox[2] - bbox[0]
|
||||
th = bbox[3] - bbox[1]
|
||||
|
||||
# Draw semi-transparent black bar at bottom
|
||||
bar_h = th + 30
|
||||
bar_y = h - bar_h
|
||||
draw.rectangle([0, bar_y, w, h], fill=(0, 0, 0, 180))
|
||||
|
||||
# Draw text centered
|
||||
tx = (w - tw) // 2
|
||||
ty = bar_y + 15
|
||||
draw.text((tx, ty), text, font=font, fill=(255, 255, 255, 255))
|
||||
|
||||
# Composite
|
||||
result = Image.alpha_composite(img, overlay).convert("RGB")
|
||||
dst = os.path.join(sub_dir, fname)
|
||||
result.save(dst, "PNG")
|
||||
print(f" {fname} -> sub/{fname}")
|
||||
|
||||
print(f"\nGenerated {len(screenshots)} annotated screenshots in sub/")
|
||||
|
||||
# Build video
|
||||
print("\nBuilding video with ffmpeg...")
|
||||
cmd = [
|
||||
"ffmpeg", "-y",
|
||||
"-framerate", "1/2",
|
||||
"-pattern_type", "glob",
|
||||
"-i", os.path.join(sub_dir, "s*.png"),
|
||||
"-c:v", "libx264",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:0x0F0F0F",
|
||||
"-r", "30",
|
||||
os.path.join(DEMO_DIR, "product-demo.mp4")
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print("Video built: product-demo.mp4")
|
||||
else:
|
||||
print(f"Error: {result.stderr[-500:]}")
|
||||
Reference in New Issue
Block a user