Merge remote-tracking branch 'origin/main' into pr-1

# Conflicts:
#	packaging/generate_icon.py
#	packaging/icon.ico
#	packaging/tickflow.iss
This commit is contained in:
shy3130
2026-06-24 10:01:54 +08:00
5 changed files with 100 additions and 81 deletions
+5 -4
View File
@@ -1,11 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<!-- 中间透明(无底色, 显示页面/浏览器背景) -->
<!-- 线条加粗: 浏览器 tab 尺寸很小, 需要更粗才能看清 -->
<!-- 左方括号 -->
<path d="M10 4 L4 4 L4 28 L10 28" stroke="#5B21B6" stroke-width="2.5" stroke-linejoin="miter"/>
<path d="M10 4 L4 4 L4 28 L10 28" stroke="#5B21B6" stroke-width="3" stroke-linejoin="miter"/>
<!-- 右方括号 -->
<path d="M22 4 L28 4 L28 28 L22 28" stroke="#5B21B6" stroke-width="2.5" stroke-linejoin="miter"/>
<path d="M22 4 L28 4 L28 28 L22 28" stroke="#5B21B6" stroke-width="3" stroke-linejoin="miter"/>
<!-- K 线 wick(上下影) -->
<line x1="16" y1="7" x2="16" y2="25" stroke="#5B21B6" stroke-width="2" stroke-linecap="round" stroke-opacity="0.65"/>
<line x1="16" y1="7" x2="16" y2="25" stroke="#5B21B6" stroke-width="2.5" stroke-linecap="round" stroke-opacity="0.7"/>
<!-- K 线 body -->
<rect x="12.5" y="11.5" width="7" height="11" fill="#5B21B6" rx="0.8"/>
<rect x="12.5" y="9" width="7" height="10" fill="#5B21B6" rx="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 640 B

After

Width:  |  Height:  |  Size: 712 B

+3 -3
View File
@@ -3,7 +3,7 @@
// 概念:
// - 外层 brackets:终端 / 代码 / 引用边界 — 赛博 + quant 气质
// - 中央 wick+body:一根标准 K 线 — 直接的金融指代
// - body 在垂直中线偏下:体重感,bullish 暗示(上影长 > 下影)
// - body 偏上 + 下影长:bullish 站稳感 (上影短 / 下影)
//
// 用 currentColor,继承父级 color 设定,方便切换品牌色。
interface LogoProps {
@@ -48,9 +48,9 @@ export function Logo({ className, size = 32, style }: LogoProps) {
strokeLinecap="round"
strokeOpacity="0.6"
/>
{/* K 线 body — 偏下,意味"上影长 / 反弹站住" */}
{/* K 线 body — 偏上,上影短/下影长, bullish 站稳感 */}
<rect
x="13" y="12" width="6" height="10"
x="13" y="9" width="6" height="10"
fill="currentColor"
rx="0.5"
/>
+66 -70
View File
@@ -1,16 +1,17 @@
"""生成应用图标 .ico — 与前端 favicon.svg / Logo.tsx 保持一致
"""生成应用图标 icon.ico — 白底圆角 + 紫色 Logo
几何 (32x32 基准, 按比例缩放到各尺寸):
- 紫色方括号 [ ] (brand color #5B21B6)
- 中间一根带 wick 的 K 线 body (实心矩形)
设计 (与 frontend Logo.tsx / favicon.svg 一致):
背景: 白色圆角方块
内容: 紫色 #5B21B6 方括号 + K线 (上影短/下影长, bullish 站稳)
用途:
- exe 图标 (PyInstaller icon=)
- 安装包图标 (Inno Setup SetupIconFile=)
- 快捷方式图标
蜡烛几何 (32x32 viewBox):
wick: y=7 ~ y=25
body: y=9 ~ y=19 (偏上)
→ 上影 = 2 (短), 下影 = 6 (长)
每尺寸独立绘制: 小尺寸加粗线条, 大尺寸精细。
运行: python packaging/generate_icon.py
产物: packaging/icon.ico (16/32/48/64/128/256 多尺寸)
产物: packaging/icon.ico (16/32/48/64/128/256 多尺寸)
"""
from __future__ import annotations
@@ -18,76 +19,71 @@ from pathlib import Path
from PIL import Image, ImageDraw
# 品牌色 (与 frontend/public/favicon.svg 一致)
BRAND = (91, 33, 182, 255) # #5B21B6
BRAND_DIM = (91, 33, 182, 166) # stroke-opacity 0.65 (wick)
OUT = Path(__file__).parent / "icon.ico"
OUTPUT = Path(__file__).parent / "icon.ico"
BRAND = (91, 33, 182, 255) # #5B21B6
BRAND_WICK = (91, 33, 182, 153) # 0.6 opacity
def _draw(size: int, sw_b: float, sw_w: float, body_w: float) -> Image.Image:
"""绘制单尺寸图标 (像素直接映射 32-viewBox)。"""
factor = max(8, 2048 // size)
s = size * factor
img = Image.new("RGBA", (s, s), (255, 255, 255, 0))
d = ImageDraw.Draw(img)
# 白色圆角背景
d.rounded_rectangle([0, 0, s - 1, s - 1], radius=int(s * 0.18), fill=(255, 255, 255, 255))
def p(v):
return v * s / 32
swb = max(1, int(round(sw_b * factor)))
sww = max(1, int(round(sw_w * factor)))
# 方括号 [ ]
for pts in [[(10, 4), (4, 4), (4, 28), (10, 28)], [(22, 4), (28, 4), (28, 28), (22, 28)]]:
scaled = [(p(x), p(y)) for x, y in pts]
for i in range(len(scaled) - 1):
d.line([scaled[i], scaled[i + 1]], fill=BRAND, width=swb, joint="curve")
# wick 影线 (上短下长)
d.line([(p(16), p(7)), (p(16), p(25))], fill=BRAND_WICK, width=sww)
wcap = sww // 2 + 1
for cy in [7, 25]:
d.ellipse([p(16) - wcap, p(cy) - wcap, p(16) + wcap, p(cy) + wcap], fill=BRAND_WICK)
# body 实体 (偏上 → 上影短下影长)
d.rounded_rectangle(
[p(16 - body_w / 2), p(9), p(16 + body_w / 2), p(19)],
radius=p(0.5), fill=BRAND,
)
return img.resize((size, size), Image.LANCZOS)
def draw_logo(size: int) -> Image.Image:
""" favicon.svg 的几何绘制指定尺寸的图标。
坐标基于 32x32 viewBox, 按比例缩放到 size。
"""
# 用 4x 超采样再缩放, 抗锯齿
scale = max(4, 256 // size)
s = size * scale
img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
# 把 32-viewBox 坐标缩放到 s
def x(v: float) -> float:
return v * s / 32
def y(v: float) -> float:
return v * s / 32
sw = 2.5 # 括号线宽
bw = 2.0 # wick 线宽
w = max(1, int(round(sw * scale)))
ww = max(1, int(round(bw * scale)))
# 左方括号 [ : M10,4 L4,4 L4,28 L10,28
d.line([(x(10), y(4)), (x(4), y(4))], fill=BRAND, width=w)
d.line([(x(4), y(4)), (x(4), y(28))], fill=BRAND, width=w)
d.line([(x(4), y(28)), (x(10), y(28))], fill=BRAND, width=w)
# 括号转角处用圆点补强 (line 转角有缝隙)
r = w // 2 + 1
for cx, cy in [(x(4), y(4)), (x(4), y(28))]:
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=BRAND)
# 右方括号 ] : M22,4 L28,4 L28,28 L22,28
d.line([(x(22), y(4)), (x(28), y(4))], fill=BRAND, width=w)
d.line([(x(28), y(4)), (x(28), y(28))], fill=BRAND, width=w)
d.line([(x(28), y(28)), (x(22), y(28))], fill=BRAND, width=w)
for cx, cy in [(x(28), y(4)), (x(28), y(28))]:
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=BRAND)
# K 线 wick: x=16, y=7..25
d.line([(x(16), y(7)), (x(16), y(25))], fill=BRAND_DIM, width=ww)
# K 线 body: rect (12.5, 11.5) 7x11, rx=0.8
bx0, by0 = x(12.5), y(11.5)
bx1, by1 = x(12.5) + x(7), y(11.5) + y(11)
radius = x(0.8)
d.rounded_rectangle([bx0, by0, bx1, by1], radius=radius, fill=BRAND)
# 缩放到目标尺寸 (高质量抗锯齿)
return img.resize((size, size), Image.LANCZOS)
"""尺寸选参数 (小尺寸加粗)。"""
if size <= 16:
return _draw(size, sw_b=4.0, sw_w=3.5, body_w=9)
elif size <= 32:
return _draw(size, sw_b=3.0, sw_w=2.5, body_w=8)
elif size <= 48:
return _draw(size, sw_b=2.5, sw_w=2.0, body_w=7)
else:
return _draw(size, sw_b=2.0, sw_w=1.5, body_w=6)
def main() -> None:
sizes = [16, 32, 48, 64, 128, 256]
images = [draw_logo(s) for s in sizes]
# 第一个作为主图, 其余作为变体
images[0].save(
OUTPUT,
format="ICO",
sizes=[(s, s) for s in sizes],
append_images=images[1:],
images = [draw_logo(sz) for sz in sizes]
# 主图 256 (大尺寸优先, 资源管理器大图清晰)
images[-1].save(
OUT, format="ICO",
sizes=[(sz, sz) for sz in sizes],
append_images=images[:-1],
)
print(f"生成图标: {OUTPUT} (尺寸 {sizes})")
print(f"生成: {OUT} (尺寸 {sizes})")
if __name__ == "__main__":
Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 12 KiB

+26 -4
View File
@@ -33,16 +33,19 @@ AppName={#MyAppName}
AppVerName={#MyAppName} {#MyAppVersion}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={localappdata}\Programs\TickFlowStockPanel
; 默认装到 D 盘 (非系统盘), 用户可在向导中改任意位置
; 若 D 盘不存在, [Code] 段 InitializeWizard 会自动回退到用户目录
DefaultDirName=D:\TickFlowStockPanel
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
OutputDir=Output
OutputBaseFilename=TickFlowStockPanel-Setup-{#MyAppVersion}
; 关键: 不需要管理员权限 (用户目录安装)
; PrivilegesRequired=lowest 固定走用户安装, 永不弹 UAC
; 不用 PrivilegesRequiredOverridesAllowed (它在静默模式下路径解析不确定)
; 关键: 不需要管理员权限, 永不弹 UAC
; 装到 D 盘普通目录 (非 Program Files) 不需要管理员权限
PrivilegesRequired=lowest
; 允许用户在向导中自由选择安装目录
DisableDirPage=no
; 压缩
Compression=lzma2/ultra64
@@ -95,6 +98,25 @@ Filename: "{cmd}"; Parameters: "/C taskkill /F /IM {#MyAppExeName}"; Flags: runh
Type: filesandordirs; Name: "{app}"
[Code]
// ── 启动时: 若 D 盘不存在, 回退默认路径到用户目录 ───────────────
// 避免默认 D:\... 但系统没 D 盘时向导显示无效路径
function InitializeSetup(): Boolean;
begin
Result := True;
end;
procedure InitializeWizard();
var
DefaultDir: String;
begin
// D 盘存在 → 用 D 盘; 否则回退用户目录 (无需管理员权限)
if not DirExists('D:\') then
begin
DefaultDir := ExpandConstant('{localappdata}\Programs\TickFlowStockPanel');
WizardForm.DirEdit.Text := DefaultDir;
end;
end;
// ── 卸载时询问是否删除用户数据 ─────────────────────────────────
// 用户数据在 %LOCALAPPDATA%\TickFlowStockPanel\ (策略/选股/回测/监控)
// 默认保留 (重装不丢), 但给用户彻底清除的选项