mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
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:
@@ -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,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
@@ -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
@@ -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\ (策略/选股/回测/监控)
|
||||
// 默认保留 (重装不丢), 但给用户彻底清除的选项
|
||||
|
||||
Reference in New Issue
Block a user