feat: add brand icons for Chrome shortcuts and PWA manifest
Dark cyan-green candlestick icon for hub and four exchanges; generate/sync scripts and docs/shortcut-icon.md. Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -21,6 +21,7 @@ cd crypto_monitor
|
|||||||
四所 `.env` 同步脚本(计仓 + 自动划转):见 **[docs/env-sync-scripts.md](./docs/env-sync-scripts.md)**。
|
四所 `.env` 同步脚本(计仓 + 自动划转):见 **[docs/env-sync-scripts.md](./docs/env-sync-scripts.md)**。
|
||||||
计仓模式(以损定仓 / 全仓杠杆):见 **[docs/position-sizing-mode.md](./docs/position-sizing-mode.md)**。
|
计仓模式(以损定仓 / 全仓杠杆):见 **[docs/position-sizing-mode.md](./docs/position-sizing-mode.md)**。
|
||||||
每日自动划转(双向归集至目标 U):见 **[docs/auto-transfer-daily.md](./docs/auto-transfer-daily.md)**。
|
每日自动划转(双向归集至目标 U):见 **[docs/auto-transfer-daily.md](./docs/auto-transfer-daily.md)**。
|
||||||
|
Chrome 快捷方式图标:见 **[docs/shortcut-icon.md](./docs/shortcut-icon.md)**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Crypto Monitor">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#00d4ff"/>
|
||||||
|
<stop offset="55%" stop-color="#3d8bff"/>
|
||||||
|
<stop offset="100%" stop-color="#00ff9d"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="up" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#00ff9d"/>
|
||||||
|
<stop offset="100%" stop-color="#7dffc8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="down" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ff6b8a"/>
|
||||||
|
<stop offset="100%" stop-color="#ff4d6d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="512" height="512" rx="112" fill="#0b0e18"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="#12182a"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="none" stroke="url(#ring)" stroke-width="10" opacity="0.9"/>
|
||||||
|
<path d="M96 348 L176 268 L248 308 L352 168 L416 228" fill="none" stroke="url(#ring)" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" opacity="0.35"/>
|
||||||
|
<line x1="148" y1="332" x2="148" y2="228" stroke="url(#down)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="132" y="252" width="32" height="52" rx="6" fill="url(#down)"/>
|
||||||
|
<line x1="228" y1="352" x2="228" y2="196" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="212" y="220" width="32" height="88" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="308" y1="320" x2="308" y2="240" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="292" y="264" width="32" height="40" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="384" y1="300" x2="384" y2="180" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="368" y="208" width="32" height="64" rx="6" fill="url(#up)"/>
|
||||||
|
<circle cx="256" cy="256" r="52" fill="none" stroke="url(#ring)" stroke-width="6" opacity="0.55"/>
|
||||||
|
<path d="M236 276 L256 220 L276 276 Z" fill="url(#ring)" opacity="0.95"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 224 B |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Crypto Monitor">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#00d4ff"/>
|
||||||
|
<stop offset="55%" stop-color="#3d8bff"/>
|
||||||
|
<stop offset="100%" stop-color="#00ff9d"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="up" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#00ff9d"/>
|
||||||
|
<stop offset="100%" stop-color="#7dffc8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="down" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ff6b8a"/>
|
||||||
|
<stop offset="100%" stop-color="#ff4d6d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="512" height="512" rx="112" fill="#0b0e18"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="#12182a"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="none" stroke="url(#ring)" stroke-width="10" opacity="0.9"/>
|
||||||
|
<path d="M96 348 L176 268 L248 308 L352 168 L416 228" fill="none" stroke="url(#ring)" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" opacity="0.35"/>
|
||||||
|
<line x1="148" y1="332" x2="148" y2="228" stroke="url(#down)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="132" y="252" width="32" height="52" rx="6" fill="url(#down)"/>
|
||||||
|
<line x1="228" y1="352" x2="228" y2="196" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="212" y="220" width="32" height="88" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="308" y1="320" x2="308" y2="240" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="292" y="264" width="32" height="40" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="384" y1="300" x2="384" y2="180" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="368" y="208" width="32" height="64" rx="6" fill="url(#up)"/>
|
||||||
|
<circle cx="256" cy="256" r="52" fill="none" stroke="url(#ring)" stroke-width="6" opacity="0.55"/>
|
||||||
|
<path d="M236 276 L256 220 L276 276 Z" fill="url(#ring)" opacity="0.95"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "交易监控复盘",
|
||||||
|
"short_name": "监控",
|
||||||
|
"description": "加密货币永续交易监控与复盘",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0b0d14",
|
||||||
|
"theme_color": "#0b0d14",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "__ICON_PREFIX__/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "__ICON_PREFIX__/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "复盘系统中控",
|
||||||
|
"short_name": "中控",
|
||||||
|
"description": "四所交易监控与行情中控",
|
||||||
|
"start_url": "/monitor",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0b0e18",
|
||||||
|
"theme_color": "#0b0e18",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "__ICON_PREFIX__/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "__ICON_PREFIX__/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 224 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Crypto Monitor">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#00d4ff"/>
|
||||||
|
<stop offset="55%" stop-color="#3d8bff"/>
|
||||||
|
<stop offset="100%" stop-color="#00ff9d"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="up" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#00ff9d"/>
|
||||||
|
<stop offset="100%" stop-color="#7dffc8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="down" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ff6b8a"/>
|
||||||
|
<stop offset="100%" stop-color="#ff4d6d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="512" height="512" rx="112" fill="#0b0e18"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="#12182a"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="none" stroke="url(#ring)" stroke-width="10" opacity="0.9"/>
|
||||||
|
<path d="M96 348 L176 268 L248 308 L352 168 L416 228" fill="none" stroke="url(#ring)" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" opacity="0.35"/>
|
||||||
|
<line x1="148" y1="332" x2="148" y2="228" stroke="url(#down)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="132" y="252" width="32" height="52" rx="6" fill="url(#down)"/>
|
||||||
|
<line x1="228" y1="352" x2="228" y2="196" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="212" y="220" width="32" height="88" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="308" y1="320" x2="308" y2="240" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="292" y="264" width="32" height="40" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="384" y1="300" x2="384" y2="180" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="368" y="208" width="32" height="64" rx="6" fill="url(#up)"/>
|
||||||
|
<circle cx="256" cy="256" r="52" fill="none" stroke="url(#ring)" stroke-width="6" opacity="0.55"/>
|
||||||
|
<path d="M236 276 L256 220 L276 276 Z" fill="url(#ring)" opacity="0.95"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "交易监控复盘",
|
||||||
|
"short_name": "监控",
|
||||||
|
"description": "加密货币永续交易监控与复盘",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0b0d14",
|
||||||
|
"theme_color": "#0b0d14",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="theme-color" content="#0b0d14">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="监控">
|
||||||
|
<link rel="icon" href="/static/icons/favicon.ico" sizes="32x32">
|
||||||
|
<link rel="icon" href="/static/icons/icon.svg" type="image/svg+xml">
|
||||||
|
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
<style>
|
<style>
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 224 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Crypto Monitor">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#00d4ff"/>
|
||||||
|
<stop offset="55%" stop-color="#3d8bff"/>
|
||||||
|
<stop offset="100%" stop-color="#00ff9d"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="up" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#00ff9d"/>
|
||||||
|
<stop offset="100%" stop-color="#7dffc8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="down" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ff6b8a"/>
|
||||||
|
<stop offset="100%" stop-color="#ff4d6d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="512" height="512" rx="112" fill="#0b0e18"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="#12182a"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="none" stroke="url(#ring)" stroke-width="10" opacity="0.9"/>
|
||||||
|
<path d="M96 348 L176 268 L248 308 L352 168 L416 228" fill="none" stroke="url(#ring)" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" opacity="0.35"/>
|
||||||
|
<line x1="148" y1="332" x2="148" y2="228" stroke="url(#down)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="132" y="252" width="32" height="52" rx="6" fill="url(#down)"/>
|
||||||
|
<line x1="228" y1="352" x2="228" y2="196" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="212" y="220" width="32" height="88" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="308" y1="320" x2="308" y2="240" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="292" y="264" width="32" height="40" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="384" y1="300" x2="384" y2="180" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="368" y="208" width="32" height="64" rx="6" fill="url(#up)"/>
|
||||||
|
<circle cx="256" cy="256" r="52" fill="none" stroke="url(#ring)" stroke-width="6" opacity="0.55"/>
|
||||||
|
<path d="M236 276 L256 220 L276 276 Z" fill="url(#ring)" opacity="0.95"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "交易监控复盘",
|
||||||
|
"short_name": "监控",
|
||||||
|
"description": "加密货币永续交易监控与复盘",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0b0d14",
|
||||||
|
"theme_color": "#0b0d14",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="theme-color" content="#0b0d14">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="监控">
|
||||||
|
<link rel="icon" href="/static/icons/favicon.ico" sizes="32x32">
|
||||||
|
<link rel="icon" href="/static/icons/icon.svg" type="image/svg+xml">
|
||||||
|
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
<style>
|
<style>
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 224 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Crypto Monitor">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#00d4ff"/>
|
||||||
|
<stop offset="55%" stop-color="#3d8bff"/>
|
||||||
|
<stop offset="100%" stop-color="#00ff9d"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="up" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#00ff9d"/>
|
||||||
|
<stop offset="100%" stop-color="#7dffc8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="down" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ff6b8a"/>
|
||||||
|
<stop offset="100%" stop-color="#ff4d6d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="512" height="512" rx="112" fill="#0b0e18"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="#12182a"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="none" stroke="url(#ring)" stroke-width="10" opacity="0.9"/>
|
||||||
|
<path d="M96 348 L176 268 L248 308 L352 168 L416 228" fill="none" stroke="url(#ring)" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" opacity="0.35"/>
|
||||||
|
<line x1="148" y1="332" x2="148" y2="228" stroke="url(#down)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="132" y="252" width="32" height="52" rx="6" fill="url(#down)"/>
|
||||||
|
<line x1="228" y1="352" x2="228" y2="196" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="212" y="220" width="32" height="88" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="308" y1="320" x2="308" y2="240" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="292" y="264" width="32" height="40" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="384" y1="300" x2="384" y2="180" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="368" y="208" width="32" height="64" rx="6" fill="url(#up)"/>
|
||||||
|
<circle cx="256" cy="256" r="52" fill="none" stroke="url(#ring)" stroke-width="6" opacity="0.55"/>
|
||||||
|
<path d="M236 276 L256 220 L276 276 Z" fill="url(#ring)" opacity="0.95"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "交易监控复盘",
|
||||||
|
"short_name": "监控",
|
||||||
|
"description": "加密货币永续交易监控与复盘",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0b0d14",
|
||||||
|
"theme_color": "#0b0d14",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="theme-color" content="#0b0d14">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="监控">
|
||||||
|
<link rel="icon" href="/static/icons/favicon.ico" sizes="32x32">
|
||||||
|
<link rel="icon" href="/static/icons/icon.svg" type="image/svg+xml">
|
||||||
|
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 机器人交易监控</title>
|
<title>{{ exchange_display }} · 加密货币 | 机器人交易监控</title>
|
||||||
<style>
|
<style>
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 224 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Crypto Monitor">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#00d4ff"/>
|
||||||
|
<stop offset="55%" stop-color="#3d8bff"/>
|
||||||
|
<stop offset="100%" stop-color="#00ff9d"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="up" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#00ff9d"/>
|
||||||
|
<stop offset="100%" stop-color="#7dffc8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="down" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ff6b8a"/>
|
||||||
|
<stop offset="100%" stop-color="#ff4d6d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="512" height="512" rx="112" fill="#0b0e18"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="#12182a"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="none" stroke="url(#ring)" stroke-width="10" opacity="0.9"/>
|
||||||
|
<path d="M96 348 L176 268 L248 308 L352 168 L416 228" fill="none" stroke="url(#ring)" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" opacity="0.35"/>
|
||||||
|
<line x1="148" y1="332" x2="148" y2="228" stroke="url(#down)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="132" y="252" width="32" height="52" rx="6" fill="url(#down)"/>
|
||||||
|
<line x1="228" y1="352" x2="228" y2="196" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="212" y="220" width="32" height="88" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="308" y1="320" x2="308" y2="240" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="292" y="264" width="32" height="40" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="384" y1="300" x2="384" y2="180" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="368" y="208" width="32" height="64" rx="6" fill="url(#up)"/>
|
||||||
|
<circle cx="256" cy="256" r="52" fill="none" stroke="url(#ring)" stroke-width="6" opacity="0.55"/>
|
||||||
|
<path d="M236 276 L256 220 L276 276 Z" fill="url(#ring)" opacity="0.95"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "交易监控复盘",
|
||||||
|
"short_name": "监控",
|
||||||
|
"description": "加密货币永续交易监控与复盘",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0b0d14",
|
||||||
|
"theme_color": "#0b0d14",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="theme-color" content="#0b0d14">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="监控">
|
||||||
|
<link rel="icon" href="/static/icons/favicon.ico" sizes="32x32">
|
||||||
|
<link rel="icon" href="/static/icons/icon.svg" type="image/svg+xml">
|
||||||
|
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
<style>
|
<style>
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Chrome 桌面快捷方式图标说明
|
||||||
|
|
||||||
|
## 图标从哪来?
|
||||||
|
|
||||||
|
用 Chrome **「创建快捷方式」** 或 **「安装应用」** 时,桌面/开始菜单图标**不是**操作系统自带的,而是浏览器从**你打开的网站**读取的,优先级大致为:
|
||||||
|
|
||||||
|
1. `manifest.webmanifest` 里的 `icons`(192×192、512×512)
|
||||||
|
2. `link rel="apple-touch-icon"`(约 180×180)
|
||||||
|
3. `link rel="icon"` / `favicon.ico`
|
||||||
|
4. 若都没有 → 灰色地球或网页标题首字
|
||||||
|
|
||||||
|
本仓库已在 **中控** 与 **四所监控页** 配置统一品牌图标(深色底 + 青绿渐变 K 线),与页面 UI 一致。
|
||||||
|
|
||||||
|
## 文件位置
|
||||||
|
|
||||||
|
| 位置 | 访问路径 |
|
||||||
|
|------|----------|
|
||||||
|
| 源稿 | `brand/icon.svg`、`brand/icons/*.png` |
|
||||||
|
| 中控 | `manual_trading_hub/static/icons/` → `/assets/icons/...` |
|
||||||
|
| 四所 | `crypto_monitor_*/static/icons/` → `/static/icons/...` |
|
||||||
|
|
||||||
|
## 重新生成 / 同步
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/generate_brand_icons.py
|
||||||
|
python scripts/sync_brand_icons.py
|
||||||
|
git pull # 服务器部署后
|
||||||
|
pm2 restart …
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快捷方式仍显示旧图标?
|
||||||
|
|
||||||
|
Chrome / Windows 会**缓存** favicon:
|
||||||
|
|
||||||
|
1. 浏览器打开站点,**Ctrl+F5** 强刷
|
||||||
|
2. 删除旧快捷方式,重新「创建快捷方式」
|
||||||
|
3. 必要时清除 Chrome 站点数据(该域名)后再创建
|
||||||
|
|
||||||
|
## 自定义图标
|
||||||
|
|
||||||
|
可替换 `brand/icon.svg` 后重新运行上面两条命令;或把设计好的 `icon-192.png`、`icon-512.png` 放入 `brand/icons/` 再 `sync_brand_icons.py`。
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 224 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Crypto Monitor">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#00d4ff"/>
|
||||||
|
<stop offset="55%" stop-color="#3d8bff"/>
|
||||||
|
<stop offset="100%" stop-color="#00ff9d"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="up" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#00ff9d"/>
|
||||||
|
<stop offset="100%" stop-color="#7dffc8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="down" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ff6b8a"/>
|
||||||
|
<stop offset="100%" stop-color="#ff4d6d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="512" height="512" rx="112" fill="#0b0e18"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="#12182a"/>
|
||||||
|
<rect x="28" y="28" width="456" height="456" rx="96" fill="none" stroke="url(#ring)" stroke-width="10" opacity="0.9"/>
|
||||||
|
<path d="M96 348 L176 268 L248 308 L352 168 L416 228" fill="none" stroke="url(#ring)" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" opacity="0.35"/>
|
||||||
|
<line x1="148" y1="332" x2="148" y2="228" stroke="url(#down)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="132" y="252" width="32" height="52" rx="6" fill="url(#down)"/>
|
||||||
|
<line x1="228" y1="352" x2="228" y2="196" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="212" y="220" width="32" height="88" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="308" y1="320" x2="308" y2="240" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="292" y="264" width="32" height="40" rx="6" fill="url(#up)"/>
|
||||||
|
<line x1="384" y1="300" x2="384" y2="180" stroke="url(#up)" stroke-width="16" stroke-linecap="round"/>
|
||||||
|
<rect x="368" y="208" width="32" height="64" rx="6" fill="url(#up)"/>
|
||||||
|
<circle cx="256" cy="256" r="52" fill="none" stroke="url(#ring)" stroke-width="6" opacity="0.55"/>
|
||||||
|
<path d="M236 276 L256 220 L276 276 Z" fill="url(#ring)" opacity="0.95"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "复盘系统中控",
|
||||||
|
"short_name": "中控",
|
||||||
|
"description": "四所交易监控与行情中控",
|
||||||
|
"start_url": "/monitor",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0b0e18",
|
||||||
|
"theme_color": "#0b0e18",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/assets/icons/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/assets/icons/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -3,6 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
|
<meta name="theme-color" content="#0b0e18" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="中控" />
|
||||||
|
<link rel="icon" href="/assets/icons/favicon.ico" sizes="32x32" />
|
||||||
|
<link rel="icon" href="/assets/icons/icon.svg" type="image/svg+xml" />
|
||||||
|
<link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png" />
|
||||||
|
<link rel="manifest" href="/assets/icons/manifest.webmanifest" />
|
||||||
<title>复盘系统中控</title>
|
<title>复盘系统中控</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
|
<meta name="theme-color" content="#0b0e18" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="中控" />
|
||||||
|
<link rel="icon" href="/assets/icons/favicon.ico" sizes="32x32" />
|
||||||
|
<link rel="icon" href="/assets/icons/icon.svg" type="image/svg+xml" />
|
||||||
|
<link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png" />
|
||||||
|
<link rel="manifest" href="/assets/icons/manifest.webmanifest" />
|
||||||
<title>登录 · 复盘系统中控</title>
|
<title>登录 · 复盘系统中控</title>
|
||||||
<link rel="stylesheet" href="/assets/app.css?v=20260530-hub-embed-login" />
|
<link rel="stylesheet" href="/assets/app.css?v=20260530-hub-embed-login" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ curl -s http://127.0.0.1:5100/api/ping
|
|||||||
|
|
||||||
## 4. 页面操作说明
|
## 4. 页面操作说明
|
||||||
|
|
||||||
|
Chrome **桌面快捷方式**图标来自站点 `favicon` / `manifest`(已配置统一品牌图),说明见 **[docs/shortcut-icon.md](../docs/shortcut-icon.md)**。
|
||||||
|
|
||||||
### 4.1 监控区 `/monitor`
|
### 4.1 监控区 `/monitor`
|
||||||
|
|
||||||
| 功能 | 说明 |
|
| 功能 | 说明 |
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""从 brand/icon.svg 逻辑绘制 PNG/ICO,供 Chrome 快捷方式、PWA manifest 使用。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
OUT = os.path.join(REPO, "brand", "icons")
|
||||||
|
|
||||||
|
|
||||||
|
def _png_chunk(tag: bytes, data: bytes) -> bytes:
|
||||||
|
return (
|
||||||
|
struct.pack(">I", len(data))
|
||||||
|
+ tag
|
||||||
|
+ data
|
||||||
|
+ struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_png(path: str, size: int, rgba: bytes) -> None:
|
||||||
|
raw = b""
|
||||||
|
stride = size * 4
|
||||||
|
for y in range(size):
|
||||||
|
raw += b"\x00" + rgba[y * stride : (y + 1) * stride]
|
||||||
|
ihdr = struct.pack(">IIBBBBB", size, size, 8, 6, 0, 0, 0)
|
||||||
|
idat = zlib.compress(raw, 9)
|
||||||
|
png = (
|
||||||
|
b"\x89PNG\r\n\x1a\n"
|
||||||
|
+ _png_chunk(b"IHDR", ihdr)
|
||||||
|
+ _png_chunk(b"IDAT", idat)
|
||||||
|
+ _png_chunk(b"IEND", b"")
|
||||||
|
)
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(png)
|
||||||
|
|
||||||
|
|
||||||
|
def _lerp(a: float, b: float, t: float) -> float:
|
||||||
|
return a + (b - a) * t
|
||||||
|
|
||||||
|
|
||||||
|
def _gradient(t: float) -> tuple[int, int, int]:
|
||||||
|
if t < 0.55:
|
||||||
|
u = t / 0.55
|
||||||
|
return (
|
||||||
|
int(_lerp(0, 0, u)),
|
||||||
|
int(_lerp(212, 139, u)),
|
||||||
|
int(_lerp(255, 255, u)),
|
||||||
|
)
|
||||||
|
u = (t - 0.55) / 0.45
|
||||||
|
return (
|
||||||
|
int(_lerp(61, 0, u)),
|
||||||
|
int(_lerp(255, 255, u)),
|
||||||
|
int(_lerp(157, 157, u)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _inside_round_rect(x: int, y: int, size: int, pad: int, radius: int) -> bool:
|
||||||
|
if x < pad or y < pad or x >= size - pad or y >= size - pad:
|
||||||
|
return False
|
||||||
|
r = radius
|
||||||
|
if x < pad + r and y < pad + r:
|
||||||
|
return (x - pad - r) ** 2 + (y - pad - r) ** 2 <= r * r
|
||||||
|
if x >= size - pad - r and y < pad + r:
|
||||||
|
return (x - (size - pad - r)) ** 2 + (y - pad - r) ** 2 <= r * r
|
||||||
|
if x < pad + r and y >= size - pad - r:
|
||||||
|
return (x - pad - r) ** 2 + (y - (size - pad - r)) ** 2 <= r * r
|
||||||
|
if x >= size - pad - r and y >= size - pad - r:
|
||||||
|
return (x - (size - pad - r)) ** 2 + (y - (size - pad - r)) ** 2 <= r * r
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_candle(
|
||||||
|
buf: bytearray,
|
||||||
|
size: int,
|
||||||
|
cx: int,
|
||||||
|
top: int,
|
||||||
|
bottom: int,
|
||||||
|
body_top: int,
|
||||||
|
body_bottom: int,
|
||||||
|
rgb: tuple[int, int, int],
|
||||||
|
wick_w: int = 2,
|
||||||
|
body_half: int = 7,
|
||||||
|
) -> None:
|
||||||
|
for y in range(max(0, top), min(size, bottom + 1)):
|
||||||
|
for x in range(cx - wick_w, cx + wick_w + 1):
|
||||||
|
if 0 <= x < size:
|
||||||
|
i = (y * size + x) * 4
|
||||||
|
buf[i : i + 3] = bytes((*rgb, 255))
|
||||||
|
for y in range(max(0, body_top), min(size, body_bottom + 1)):
|
||||||
|
for x in range(cx - body_half, cx + body_half + 1):
|
||||||
|
if 0 <= x < size:
|
||||||
|
i = (y * size + x) * 4
|
||||||
|
buf[i : i + 3] = bytes((*rgb, 255))
|
||||||
|
|
||||||
|
|
||||||
|
def render_icon_rgba(size: int) -> bytes:
|
||||||
|
buf = bytearray(size * size * 4)
|
||||||
|
pad = max(4, size // 14)
|
||||||
|
radius = size // 5
|
||||||
|
inner_pad = pad + max(2, size // 32)
|
||||||
|
ring_w = max(2, size // 48)
|
||||||
|
|
||||||
|
for y in range(size):
|
||||||
|
for x in range(size):
|
||||||
|
i = (y * size + x) * 4
|
||||||
|
if not _inside_round_rect(x, y, size, pad, radius):
|
||||||
|
buf[i : i + 4] = b"\x00\x00\x00\x00"
|
||||||
|
continue
|
||||||
|
if _inside_round_rect(x, y, size, inner_pad, radius - 4):
|
||||||
|
buf[i : i + 4] = bytes((18, 24, 42, 255))
|
||||||
|
else:
|
||||||
|
t = (x + y) / (2 * size)
|
||||||
|
r, g, b = _gradient(t)
|
||||||
|
buf[i : i + 4] = bytes((r, g, b, 220))
|
||||||
|
|
||||||
|
s = size / 512.0
|
||||||
|
def sc(v: int) -> int:
|
||||||
|
return int(v * s)
|
||||||
|
|
||||||
|
_draw_candle(buf, size, sc(148), sc(228), sc(332), sc(252), sc(304), (255, 77, 109), sc(8), sc(16))
|
||||||
|
_draw_candle(buf, size, sc(228), sc(196), sc(352), sc(220), sc(308), (0, 255, 157), sc(8), sc(16))
|
||||||
|
_draw_candle(buf, size, sc(308), sc(240), sc(320), sc(264), sc(304), (0, 255, 157), sc(8), sc(14))
|
||||||
|
_draw_candle(buf, size, sc(384), sc(180), sc(300), sc(208), sc(272), (0, 255, 157), sc(8), sc(16))
|
||||||
|
|
||||||
|
ccx, ccy = sc(256), sc(256)
|
||||||
|
cr = sc(52)
|
||||||
|
for y in range(size):
|
||||||
|
for x in range(size):
|
||||||
|
d = ((x - ccx) ** 2 + (y - ccy) ** 2) ** 0.5
|
||||||
|
if cr - ring_w <= d <= cr:
|
||||||
|
t = (x + y) / (2 * size)
|
||||||
|
r, g, b = _gradient(t)
|
||||||
|
i = (y * size + x) * 4
|
||||||
|
buf[i : i + 4] = bytes((r, g, b, 200))
|
||||||
|
tri = [
|
||||||
|
(sc(236), sc(276)),
|
||||||
|
(sc(256), sc(220)),
|
||||||
|
(sc(276), sc(276)),
|
||||||
|
]
|
||||||
|
|
||||||
|
def _in_tri(px: int, py: int) -> bool:
|
||||||
|
x1, y1 = tri[0]
|
||||||
|
x2, y2 = tri[1]
|
||||||
|
x3, y3 = tri[2]
|
||||||
|
d1 = (px - x2) * (y1 - y2) - (x1 - x2) * (py - y2)
|
||||||
|
d2 = (px - x3) * (y2 - y3) - (x2 - x3) * (py - y3)
|
||||||
|
d3 = (px - x1) * (y3 - y1) - (x3 - x1) * (py - y1)
|
||||||
|
has_neg = d1 < 0 or d2 < 0 or d3 < 0
|
||||||
|
has_pos = d1 > 0 or d2 > 0 or d3 > 0
|
||||||
|
return not (has_neg and has_pos)
|
||||||
|
|
||||||
|
for y in range(size):
|
||||||
|
for x in range(size):
|
||||||
|
if _in_tri(x, y):
|
||||||
|
t = (x + y) / (2 * size)
|
||||||
|
r, g, b = _gradient(t)
|
||||||
|
i = (y * size + x) * 4
|
||||||
|
buf[i : i + 4] = bytes((r, g, b, 255))
|
||||||
|
return bytes(buf)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_ico(path: str, sizes: list[int]) -> None:
|
||||||
|
images = []
|
||||||
|
for sz in sizes:
|
||||||
|
rgba = render_icon_rgba(sz)
|
||||||
|
# BGRA bottom-up for ICO
|
||||||
|
row = sz * 4
|
||||||
|
pixels = bytearray()
|
||||||
|
for y in range(sz - 1, -1, -1):
|
||||||
|
for x in range(sz):
|
||||||
|
i = (y * sz + x) * 4
|
||||||
|
pixels.extend([rgba[i + 2], rgba[i + 1], rgba[i], rgba[i + 3]])
|
||||||
|
and_row = (sz * 4 + 3) & ~3
|
||||||
|
if and_row > sz * 4:
|
||||||
|
pixels.extend(b"\x00" * (and_row - sz * 4))
|
||||||
|
bmp = b"BM" + struct.pack("<I", 40 + len(pixels)) + b"\x00\x00\x00\x00" + struct.pack(
|
||||||
|
"<I", 40
|
||||||
|
) + struct.pack("<i", sz) + struct.pack("<i", sz * 2) + struct.pack("<H", 1) + struct.pack(
|
||||||
|
"<H", 32
|
||||||
|
) + struct.pack("<I", 0) + struct.pack("<I", len(pixels)) + struct.pack("<i", 0) * 4
|
||||||
|
images.append((sz, bmp + bytes(pixels)))
|
||||||
|
|
||||||
|
offset = 6 + 16 * len(images)
|
||||||
|
parts = [struct.pack("<HHH", 0, 1, len(images))]
|
||||||
|
data_parts = []
|
||||||
|
for sz, data in images:
|
||||||
|
parts.append(
|
||||||
|
struct.pack("<BBBBHHII", 32, 32, 0, 0, 1, 32, len(data), offset)
|
||||||
|
)
|
||||||
|
offset += len(data)
|
||||||
|
data_parts.append(data)
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"".join(parts) + b"".join(data_parts))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
os.makedirs(OUT, exist_ok=True)
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
shutil.copy2(
|
||||||
|
os.path.join(REPO, "brand", "icon.svg"),
|
||||||
|
os.path.join(OUT, "icon.svg"),
|
||||||
|
)
|
||||||
|
for sz in (16, 32, 180, 192, 512):
|
||||||
|
_write_png(os.path.join(OUT, f"icon-{sz}.png"), sz, render_icon_rgba(sz))
|
||||||
|
shutil.copy2(os.path.join(OUT, "icon-180.png"), os.path.join(OUT, "apple-touch-icon.png"))
|
||||||
|
_write_ico(os.path.join(OUT, "favicon.ico"), [16, 32, 48])
|
||||||
|
print(f"DONE {OUT}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
将 brand/icons 同步到中控与各所 static/icons(Chrome 快捷方式 / 标签页图标)。
|
||||||
|
|
||||||
|
用法(仓库根目录):
|
||||||
|
python scripts/generate_brand_icons.py
|
||||||
|
python scripts/sync_brand_icons.py
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
SRC = os.path.join(REPO, "brand", "icons")
|
||||||
|
|
||||||
|
HUB_DEST = os.path.join(REPO, "manual_trading_hub", "static", "icons")
|
||||||
|
EXCHANGE_DIRS = (
|
||||||
|
"crypto_monitor_binance",
|
||||||
|
"crypto_monitor_okx",
|
||||||
|
"crypto_monitor_gate",
|
||||||
|
"crypto_monitor_gate_bot",
|
||||||
|
)
|
||||||
|
|
||||||
|
FILES = (
|
||||||
|
"icon.svg",
|
||||||
|
"favicon.ico",
|
||||||
|
"icon-16.png",
|
||||||
|
"icon-32.png",
|
||||||
|
"icon-192.png",
|
||||||
|
"icon-512.png",
|
||||||
|
"apple-touch-icon.png",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_dir(dest: str, url_prefix: str, manifest_template: str) -> str:
|
||||||
|
if not os.path.isdir(SRC):
|
||||||
|
return f"SKIP {dest}: 请先运行 python scripts/generate_brand_icons.py"
|
||||||
|
os.makedirs(dest, exist_ok=True)
|
||||||
|
for name in FILES:
|
||||||
|
shutil.copy2(os.path.join(SRC, name), os.path.join(dest, name))
|
||||||
|
manifest_src = os.path.join(REPO, "brand", manifest_template)
|
||||||
|
if os.path.isfile(manifest_src):
|
||||||
|
with open(manifest_src, encoding="utf-8") as f:
|
||||||
|
text = f.read().replace("__ICON_PREFIX__", url_prefix)
|
||||||
|
with open(
|
||||||
|
os.path.join(dest, "manifest.webmanifest"),
|
||||||
|
"w",
|
||||||
|
encoding="utf-8",
|
||||||
|
newline="\n",
|
||||||
|
) as f:
|
||||||
|
f.write(text)
|
||||||
|
return f"DONE {dest}"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
print(sync_dir(HUB_DEST, "/assets/icons", "manifest.webmanifest"))
|
||||||
|
for d in EXCHANGE_DIRS:
|
||||||
|
dest = os.path.join(REPO, d, "static", "icons")
|
||||||
|
print(sync_dir(dest, "/static/icons", "manifest.exchange.webmanifest"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||