feat: redesign miniapp tabbar icons
|
After Width: | Height: | Size: 33 KiB |
@@ -9,10 +9,10 @@
|
||||
| 属性 | 规范值 |
|
||||
|------|--------|
|
||||
| 图标尺寸 | 81 × 81 px (基础) |
|
||||
| 安全区域 | 56 × 56 px (边距 12px) |
|
||||
| 线宽 | 2px |
|
||||
| 安全区域 | 53 × 53 px(最小边距 14px) |
|
||||
| 线宽 | 4px(缩放到导航栏后约 1.3px) |
|
||||
| 圆角 | 端点圆角 + 转折圆角 |
|
||||
| 填充 | 无(纯线性) |
|
||||
| 填充 | 默认态线性;选中态填充核心爱心 |
|
||||
|
||||
## 颜色规范
|
||||
|
||||
@@ -25,28 +25,28 @@
|
||||
## 图标清单
|
||||
|
||||
### 1. 首页 (home)
|
||||
- **意象**: 温馨小屋
|
||||
- **元素**: 屋顶三角 + 房屋主体 + 门 + 烟囱
|
||||
- **意象**: 爱心小屋
|
||||
- **元素**: 圆角屋顶 + 房屋主体 + 门内爱心
|
||||
- **文件**: `home.png` / `home-active.png`
|
||||
|
||||
### 2. 找对象 (discover)
|
||||
- **意象**: 双心相连,象征缘分匹配
|
||||
- **元素**: 两个相交的爱心
|
||||
- **意象**: 双人缘分
|
||||
- **元素**: 两个人物轮廓 + 中央爱心
|
||||
- **文件**: `discover.png` / `discover-active.png`
|
||||
|
||||
### 3. 消息 (message)
|
||||
- **意象**: 对话沟通
|
||||
- **元素**: 圆角气泡 + 小三角 + 文字线条
|
||||
- **意象**: 真诚沟通
|
||||
- **元素**: 圆角气泡 + 爱心
|
||||
- **文件**: `message.png` / `message-active.png`
|
||||
|
||||
### 4. 活动 (activity)
|
||||
- **意象**: 日程安排
|
||||
- **元素**: 日历主体 + 挂环 + 日期标记点
|
||||
- **意象**: 公益相遇
|
||||
- **元素**: 日历主体 + 挂环 + 爱心
|
||||
- **文件**: `activity.png` / `activity-active.png`
|
||||
|
||||
### 5. 我的 (mine)
|
||||
- **意象**: 个人用户
|
||||
- **元素**: 圆形头部 + 半圆身体轮廓
|
||||
- **意象**: 个人守护
|
||||
- **元素**: 单人轮廓 + 胸前爱心
|
||||
- **文件**: `mine.png` / `mine-active.png`
|
||||
|
||||
## 导出尺寸
|
||||
|
||||
|
Before Width: | Height: | Size: 436 B After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 741 B After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 737 B After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 528 B After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 819 B After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 527 B After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 816 B After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 865 B After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 452 B After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 419 B After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 826 B After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 821 B After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 10 KiB |
@@ -1,12 +1,5 @@
|
||||
<!-- 活动 - 日历图标 (选中状态) -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="25" cy="18" r="5" stroke="#E96B6B" stroke-width="2"/>
|
||||
<circle cx="56" cy="18" r="5" stroke="#E96B6B" stroke-width="2"/>
|
||||
<rect x="12" y="26" width="57" height="40" rx="8" stroke="#E96B6B" stroke-width="2"/>
|
||||
<line x1="16" y1="36" x2="65" y2="36" stroke="#E96B6B" stroke-width="2"/>
|
||||
<circle cx="25" cy="46" r="3" fill="#E96B6B"/>
|
||||
<circle cx="40.5" cy="46" r="3" fill="#E96B6B"/>
|
||||
<circle cx="56" cy="46" r="3" fill="#E96B6B"/>
|
||||
<circle cx="33" cy="56" r="3" fill="#E96B6B"/>
|
||||
<circle cx="48" cy="56" r="3" fill="#E96B6B"/>
|
||||
<title>公益日历</title>
|
||||
<g stroke="#E96B6B" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><rect x="17" y="20" width="47" height="44" rx="8"/><path d="M17 33h47M28 15v10M53 15v10"/><path class="heart" d="M40.5 57c-6.5-4.3-9.5-7.2-9.5-10.7 0-2.9 2.3-5.1 5.1-5.1 1.9 0 3.5.9 4.4 2.4.9-1.5 2.5-2.4 4.5-2.4 2.8 0 5.1 2.2 5.1 5.1 0 3.5-3.1 6.4-9.6 10.7Z"/></g>
|
||||
<style>.heart{fill:#E96B6B;stroke:#E96B6B}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 555 B |
@@ -1,16 +1,5 @@
|
||||
<!-- 活动 - 日历图标 -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 挂环 -->
|
||||
<circle cx="25" cy="18" r="5" stroke="#999999" stroke-width="2"/>
|
||||
<circle cx="56" cy="18" r="5" stroke="#999999" stroke-width="2"/>
|
||||
<!-- 日历主体 -->
|
||||
<rect x="12" y="26" width="57" height="40" rx="8" stroke="#999999" stroke-width="2"/>
|
||||
<!-- 分隔线 -->
|
||||
<line x1="16" y1="36" x2="65" y2="36" stroke="#999999" stroke-width="2"/>
|
||||
<!-- 日期点 -->
|
||||
<circle cx="25" cy="46" r="3" fill="#999999"/>
|
||||
<circle cx="40.5" cy="46" r="3" fill="#999999"/>
|
||||
<circle cx="56" cy="46" r="3" fill="#999999"/>
|
||||
<circle cx="33" cy="56" r="3" fill="#999999"/>
|
||||
<circle cx="48" cy="56" r="3" fill="#999999"/>
|
||||
<title>公益日历</title>
|
||||
<g stroke="#999999" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><rect x="17" y="20" width="47" height="44" rx="8"/><path d="M17 33h47M28 15v10M53 15v10"/><path class="heart" d="M40.5 57c-6.5-4.3-9.5-7.2-9.5-10.7 0-2.9 2.3-5.1 5.1-5.1 1.9 0 3.5.9 4.4 2.4.9-1.5 2.5-2.4 4.5-2.4 2.8 0 5.1 2.2 5.1 5.1 0 3.5-3.1 6.4-9.6 10.7Z"/></g>
|
||||
<style>.heart{fill:none;stroke:#999999}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 765 B After Width: | Height: | Size: 552 B |
@@ -1,5 +1,5 @@
|
||||
<!-- 找对象 - 双心图标 (选中状态) -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28 32C28 26 32 22 36 22C38 22 40 23 41 25C42 23 44 22 46 22C50 22 54 26 54 32C54 38 48 44 41 50C34 44 28 38 28 32Z" stroke="#E96B6B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M42 36C42 30 46 26 50 26C52 26 54 27 55 29C56 27 58 26 60 26C64 26 68 30 68 36C68 42 62 48 55 54C48 48 42 42 42 36Z" stroke="#E96B6B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<title>双人缘分</title>
|
||||
<g stroke="#E96B6B" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><circle cx="26" cy="29" r="7"/><circle cx="55" cy="29" r="7"/><path d="M14 55c1-10 6-15 15-15 4 0 7 1 9 3M67 55c-1-10-6-15-15-15-4 0-7 1-9 3"/><path class="heart" d="M40.5 58c-6.8-4.5-10-7.6-10-11.2 0-3 2.3-5.3 5.3-5.3 2 0 3.7 1 4.7 2.5 1-1.5 2.7-2.5 4.7-2.5 3 0 5.3 2.3 5.3 5.3 0 3.6-3.2 6.7-10 11.2Z"/></g>
|
||||
<style>.heart{fill:#E96B6B;stroke:#E96B6B}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 574 B After Width: | Height: | Size: 599 B |
@@ -1,7 +1,5 @@
|
||||
<!-- 找对象 - 双心图标 -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 左心 -->
|
||||
<path d="M28 32C28 26 32 22 36 22C38 22 40 23 41 25C42 23 44 22 46 22C50 22 54 26 54 32C54 38 48 44 41 50C34 44 28 38 28 32Z" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<!-- 右心 -->
|
||||
<path d="M42 36C42 30 46 26 50 26C52 26 54 27 55 29C56 27 58 26 60 26C64 26 68 30 68 36C68 42 62 48 55 54C48 48 42 42 42 36Z" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<title>双人缘分</title>
|
||||
<g stroke="#999999" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><circle cx="26" cy="29" r="7"/><circle cx="55" cy="29" r="7"/><path d="M14 55c1-10 6-15 15-15 4 0 7 1 9 3M67 55c-1-10-6-15-15-15-4 0-7 1-9 3"/><path class="heart" d="M40.5 58c-6.8-4.5-10-7.6-10-11.2 0-3 2.3-5.3 5.3-5.3 2 0 3.7 1 4.7 2.5 1-1.5 2.7-2.5 4.7-2.5 3 0 5.3 2.3 5.3 5.3 0 3.6-3.2 6.7-10 11.2Z"/></g>
|
||||
<style>.heart{fill:none;stroke:#999999}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 595 B After Width: | Height: | Size: 596 B |
@@ -1,7 +1,5 @@
|
||||
<!-- 首页 - 房子图标 (选中状态) -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="58" y="18" width="8" height="14" rx="2" stroke="#E96B6B" stroke-width="2"/>
|
||||
<path d="M40.5 16L22 34H59L40.5 16Z" stroke="#E96B6B" stroke-width="2" stroke-linejoin="round"/>
|
||||
<rect x="24" y="34" width="33" height="22" rx="4" stroke="#E96B6B" stroke-width="2"/>
|
||||
<rect x="35" y="46" width="11" height="10" rx="2" stroke="#E96B6B" stroke-width="2"/>
|
||||
<title>爱心小屋</title>
|
||||
<g stroke="#E96B6B" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 38 40.5 19 63 38"/><rect x="22" y="35" width="37" height="28" rx="7"/><path d="M31 63V49h19v14"/><path class="heart" d="M40.5 54c-5-3.4-7-5.4-7-8.2 0-2.3 1.8-4.1 4.1-4.1 1.3 0 2.4.6 2.9 1.6.6-1 1.7-1.6 3-1.6 2.3 0 4.1 1.8 4.1 4.1 0 2.8-2.1 4.8-7.1 8.2Z"/></g>
|
||||
<style>.heart{fill:#E96B6B;stroke:#E96B6B}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 563 B |
@@ -1,11 +1,5 @@
|
||||
<!-- 首页 - 房子图标 -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 烟囱 -->
|
||||
<rect x="58" y="18" width="8" height="14" rx="2" stroke="#999999" stroke-width="2"/>
|
||||
<!-- 屋顶 -->
|
||||
<path d="M40.5 16L22 34H59L40.5 16Z" stroke="#999999" stroke-width="2" stroke-linejoin="round"/>
|
||||
<!-- 房屋主体 -->
|
||||
<rect x="24" y="34" width="33" height="22" rx="4" stroke="#999999" stroke-width="2"/>
|
||||
<!-- 门 -->
|
||||
<rect x="35" y="46" width="11" height="10" rx="2" stroke="#999999" stroke-width="2"/>
|
||||
<title>爱心小屋</title>
|
||||
<g stroke="#999999" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 38 40.5 19 63 38"/><rect x="22" y="35" width="37" height="28" rx="7"/><path d="M31 63V49h19v14"/><path class="heart" d="M40.5 54c-5-3.4-7-5.4-7-8.2 0-2.3 1.8-4.1 4.1-4.1 1.3 0 2.4.6 2.9 1.6.6-1 1.7-1.6 3-1.6 2.3 0 4.1 1.8 4.1 4.1 0 2.8-2.1 4.8-7.1 8.2Z"/></g>
|
||||
<style>.heart{fill:none;stroke:#999999}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 571 B After Width: | Height: | Size: 560 B |
@@ -1,8 +1,5 @@
|
||||
<!-- 消息 - 对话气泡图标 (选中状态) -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="12" y="18" width="57" height="36" rx="10" stroke="#E96B6B" stroke-width="2"/>
|
||||
<path d="M22 54L16 64L28 54" stroke="#E96B6B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="20" y1="28" x2="61" y2="28" stroke="#E96B6B" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="20" y1="36" x2="53" y2="36" stroke="#E96B6B" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="20" y1="44" x2="45" y2="44" stroke="#E96B6B" stroke-width="2" stroke-linecap="round"/>
|
||||
<title>爱心对话</title>
|
||||
<g stroke="#E96B6B" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><path d="M23 60 15 66l2.4-11.2A12 12 0 0 1 14 46V29a12 12 0 0 1 12-12h29a12 12 0 0 1 12 12v17a12 12 0 0 1-12 12H27c-1.4 0-2.7.7-4 2Z"/><path class="heart" d="M40.5 48c-7.5-5-11-8.4-11-12.4 0-3.3 2.6-5.9 5.9-5.9 2.2 0 4.1 1.1 5.1 2.8 1.1-1.7 3-2.8 5.2-2.8 3.3 0 5.9 2.6 5.9 5.9 0 4-3.6 7.4-11.1 12.4Z"/></g>
|
||||
<style>.heart{fill:#E96B6B;stroke:#E96B6B}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 655 B After Width: | Height: | Size: 597 B |
@@ -1,11 +1,5 @@
|
||||
<!-- 消息 - 对话气泡图标 -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 气泡主体 -->
|
||||
<rect x="12" y="18" width="57" height="36" rx="10" stroke="#999999" stroke-width="2"/>
|
||||
<!-- 小三角 -->
|
||||
<path d="M22 54L16 64L28 54" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<!-- 文字线条 -->
|
||||
<line x1="20" y1="28" x2="61" y2="28" stroke="#999999" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="20" y1="36" x2="53" y2="36" stroke="#999999" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="20" y1="44" x2="45" y2="44" stroke="#999999" stroke-width="2" stroke-linecap="round"/>
|
||||
<title>爱心对话</title>
|
||||
<g stroke="#999999" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><path d="M23 60 15 66l2.4-11.2A12 12 0 0 1 14 46V29a12 12 0 0 1 12-12h29a12 12 0 0 1 12 12v17a12 12 0 0 1-12 12H27c-1.4 0-2.7.7-4 2Z"/><path class="heart" d="M40.5 48c-7.5-5-11-8.4-11-12.4 0-3.3 2.6-5.9 5.9-5.9 2.2 0 4.1 1.1 5.1 2.8 1.1-1.7 3-2.8 5.2-2.8 3.3 0 5.9 2.6 5.9 5.9 0 4-3.6 7.4-11.1 12.4Z"/></g>
|
||||
<style>.heart{fill:none;stroke:#999999}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 594 B |
@@ -1,7 +1,5 @@
|
||||
<!-- 我的 - 用户图标 (选中状态) -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="40.5" cy="28" r="14" stroke="#E96B6B" stroke-width="2"/>
|
||||
<path d="M22 62C22 50 30 44 40.5 44C51 44 59 50 59 62" stroke="#E96B6B" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="22" y1="52" x2="22" y2="62" stroke="#E96B6B" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="59" y1="52" x2="59" y2="62" stroke="#E96B6B" stroke-width="2" stroke-linecap="round"/>
|
||||
<title>个人守护</title>
|
||||
<g stroke="#E96B6B" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><circle cx="40.5" cy="27" r="8"/><path d="M20 64c.7-14 8-21 20.5-21S60.3 50 61 64"/><path class="heart" d="M40.5 59c-5.2-3.5-7.7-5.8-7.7-8.6 0-2.3 1.9-4.2 4.2-4.2 1.6 0 2.8.8 3.5 2 .8-1.2 2-2 3.6-2 2.3 0 4.2 1.9 4.2 4.2 0 2.8-2.5 5.1-7.8 8.6Z"/></g>
|
||||
<style>.heart{fill:#E96B6B;stroke:#E96B6B}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 534 B After Width: | Height: | Size: 540 B |
@@ -1,10 +1,5 @@
|
||||
<!-- 我的 - 用户图标 -->
|
||||
<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 头部 -->
|
||||
<circle cx="40.5" cy="28" r="14" stroke="#999999" stroke-width="2"/>
|
||||
<!-- 身体 -->
|
||||
<path d="M22 62C22 50 30 44 40.5 44C51 44 59 50 59 62" stroke="#999999" stroke-width="2" stroke-linecap="round"/>
|
||||
<!-- 身体两侧 -->
|
||||
<line x1="22" y1="52" x2="22" y2="62" stroke="#999999" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="59" y1="52" x2="59" y2="62" stroke="#999999" stroke-width="2" stroke-linecap="round"/>
|
||||
<title>个人守护</title>
|
||||
<g stroke="#999999" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><circle cx="40.5" cy="27" r="8"/><path d="M20 64c.7-14 8-21 20.5-21S60.3 50 61 64"/><path class="heart" d="M40.5 59c-5.2-3.5-7.7-5.8-7.7-8.6 0-2.3 1.9-4.2 4.2-4.2 1.6 0 2.8.8 3.5 2 .8-1.2 2-2 3.6-2 2.3 0 4.2 1.9 4.2 4.2 0 2.8-2.5 5.1-7.8 8.6Z"/></g>
|
||||
<style>.heart{fill:none;stroke:#999999}.badge{fill:white}</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 579 B After Width: | Height: | Size: 537 B |
@@ -1,236 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
丹阳公益婚恋平台 - 底部导航栏图标生成脚本
|
||||
圆润线性风格,两种状态(未选中/选中),三种尺寸
|
||||
"""
|
||||
"""生成善缘丹阳小程序底部导航 SVG 与透明 PNG。"""
|
||||
|
||||
from pathlib import Path
|
||||
import math
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
import math
|
||||
import os
|
||||
|
||||
# 主题色配置
|
||||
COLOR_UNSELECTED = (153, 153, 153) # #999999
|
||||
COLOR_SELECTED = (233, 107, 107) # #E96B6B
|
||||
COLOR_BG = (255, 255, 255) # 白色背景
|
||||
ROOT = Path(__file__).resolve().parents[1] / "miniapp" / "assets" / "tabbar"
|
||||
COLORS = {"normal": "#999999", "active": "#E96B6B"}
|
||||
TITLES = {
|
||||
"home": "爱心小屋",
|
||||
"discover": "双人缘分",
|
||||
"message": "爱心对话",
|
||||
"activity": "公益日历",
|
||||
"mine": "个人守护",
|
||||
}
|
||||
|
||||
|
||||
def sin_deg(x):
|
||||
return math.sin(math.radians(x))
|
||||
|
||||
def cos_deg(x):
|
||||
return math.cos(math.radians(x))
|
||||
|
||||
|
||||
def draw_heart_points(cx, cy, scale):
|
||||
"""计算心形路径点"""
|
||||
pts = []
|
||||
for t in range(0, 360, 3):
|
||||
rad = t * 3.14159 / 180
|
||||
x = scale * 16 * (sin_deg(rad) ** 3)
|
||||
y = -scale * (13 * cos_deg(rad) - 5 * cos_deg(2*rad) - 2 * cos_deg(3*rad) - cos_deg(4*rad))
|
||||
pts.append((cx + x, cy + y))
|
||||
return pts
|
||||
|
||||
|
||||
def draw_rounded_rect(draw, x, y, w, h, r, color, width=2):
|
||||
"""绘制圆角矩形(无填充,只描边)"""
|
||||
r = min(r, w // 2, h // 2)
|
||||
draw.rounded_rectangle([x, y, x + w, y + h], radius=r, outline=color, width=width)
|
||||
|
||||
|
||||
def draw_house(draw, cx, cy, size, color, width=2):
|
||||
"""绘制房子图标 - 首页"""
|
||||
s = size * 0.35
|
||||
# 房子主体
|
||||
body_x, body_y = cx - s * 0.7, cy - s * 0.1
|
||||
body_w, body_h = s * 1.4, s * 0.7
|
||||
draw_rounded_rect(draw, body_x, body_y, body_w, body_h, 4, color, width)
|
||||
|
||||
# 屋顶(三角形)
|
||||
roof_points = [
|
||||
(cx, body_y - s * 0.5),
|
||||
(body_x - s * 0.15, body_y),
|
||||
(body_x + body_w + s * 0.15, body_y)
|
||||
]
|
||||
draw.polygon(roof_points, outline=color, width=width)
|
||||
|
||||
# 门
|
||||
door_w, door_h = s * 0.28, s * 0.4
|
||||
door_x = cx - door_w / 2
|
||||
door_y = body_y + body_h - door_h
|
||||
draw_rounded_rect(draw, door_x, door_y, door_w, door_h, 2, color, width)
|
||||
|
||||
# 烟囱
|
||||
chimney_w, chimney_h = s * 0.12, s * 0.22
|
||||
chimney_x = cx + s * 0.35
|
||||
chimney_y = body_y - s * 0.35
|
||||
draw_rounded_rect(draw, chimney_x, chimney_y, chimney_w, chimney_h, 2, color, width)
|
||||
|
||||
|
||||
def draw_hearts(draw, cx, cy, size, color, width=2):
|
||||
"""绘制双心图标 - 找对象"""
|
||||
s = size * 0.016
|
||||
offset = s * 4
|
||||
|
||||
# 左心
|
||||
left_pts = draw_heart_points(cx - offset, cy - offset * 0.3, s)
|
||||
right_pts = draw_heart_points(cx + offset, cy - offset * 0.3, s)
|
||||
|
||||
for pts in [left_pts, right_pts]:
|
||||
for i in range(len(pts)):
|
||||
p1 = pts[i]
|
||||
p2 = pts[(i + 1) % len(pts)]
|
||||
draw.line([p1, p2], fill=color, width=width)
|
||||
|
||||
|
||||
def draw_chat_bubble(draw, cx, cy, size, color, width=2):
|
||||
"""绘制对话气泡图标 - 消息"""
|
||||
s = size * 0.28
|
||||
# 气泡主体
|
||||
bubble_x, bubble_y = cx - s, cy - s * 0.6
|
||||
bubble_w, bubble_h = s * 2, s * 1.2
|
||||
draw_rounded_rect(draw, bubble_x, bubble_y, bubble_w, bubble_h, 8, color, width)
|
||||
|
||||
# 小三角
|
||||
tail_points = [
|
||||
(bubble_x + s * 0.1, bubble_y + bubble_h),
|
||||
(bubble_x + s * 0.3, bubble_y + bubble_h + s * 0.25),
|
||||
(bubble_x + s * 0.5, bubble_y + bubble_h)
|
||||
]
|
||||
draw.polygon(tail_points, outline=color, width=width)
|
||||
|
||||
# 内部文字线条
|
||||
line_y_start = bubble_y + s * 0.2
|
||||
for i in range(3):
|
||||
line_y = line_y_start + i * s * 0.22
|
||||
draw.line([(bubble_x + s * 0.15, line_y), (bubble_x + s * 1.5, line_y)], fill=color, width=width)
|
||||
|
||||
|
||||
def draw_calendar(draw, cx, cy, size, color, width=2):
|
||||
"""绘制日历图标 - 活动"""
|
||||
s = size * 0.26
|
||||
# 日历主体
|
||||
cal_x, cal_y = cx - s, cy - s * 0.55
|
||||
cal_w, cal_h = s * 2, s * 1.3
|
||||
draw_rounded_rect(draw, cal_x, cal_y, cal_w, cal_h, 6, color, width)
|
||||
|
||||
# 顶部横线(分隔年月和日期)
|
||||
draw.line([(cal_x + s * 0.05, cal_y + s * 0.25), (cal_x + cal_w - s * 0.05, cal_y + s * 0.25)], fill=color, width=width)
|
||||
|
||||
# 日期点
|
||||
dot_r = 2 if size >= 162 else 1
|
||||
positions = [
|
||||
(cal_x + s * 0.3, cal_y + s * 0.55),
|
||||
(cal_x + s * 0.7, cal_y + s * 0.55),
|
||||
(cal_x + s * 1.1, cal_y + s * 0.55),
|
||||
(cal_x + s * 0.5, cal_y + s * 0.9),
|
||||
(cal_x + s * 0.9, cal_y + s * 0.9),
|
||||
]
|
||||
for dx, dy in positions:
|
||||
draw.ellipse([dx - dot_r, dy - dot_r, dx + dot_r, dy + dot_r], fill=color)
|
||||
|
||||
# 左上角挂环
|
||||
ring_r = 4 if size >= 162 else 2
|
||||
draw.ellipse([cal_x + s * 0.25 - ring_r, cal_y - ring_r, cal_x + s * 0.25 + ring_r, cal_y + ring_r], outline=color, width=width)
|
||||
draw.ellipse([cal_x + s * 1.75 - ring_r, cal_y - ring_r, cal_x + s * 1.75 + ring_r, cal_y + ring_r], outline=color, width=width)
|
||||
|
||||
|
||||
def draw_user(draw, cx, cy, size, color, width=2):
|
||||
"""绘制用户图标 - 我的"""
|
||||
s = size * 0.3
|
||||
|
||||
# 头部(圆形)
|
||||
head_r = s * 0.38
|
||||
head_x, head_y = cx, cy - s * 0.25
|
||||
draw.ellipse([head_x - head_r, head_y - head_r, head_x + head_r, head_y + head_r], outline=color, width=width)
|
||||
|
||||
# 身体(半椭圆)
|
||||
body_top = cy + s * 0.15
|
||||
body_bottom = cy + s * 0.75
|
||||
body_left = cx - s * 0.45
|
||||
body_right = cx + s * 0.45
|
||||
draw.arc([body_left, body_top, body_right, body_bottom], start=180, end=360, fill=color, width=width)
|
||||
|
||||
# 身体两侧竖线
|
||||
side_offset = s * 0.4
|
||||
draw.line([(cx - side_offset, body_top), (cx - side_offset, body_bottom)], fill=color, width=width)
|
||||
draw.line([(cx + side_offset, body_top), (cx + side_offset, body_bottom)], fill=color, width=width)
|
||||
|
||||
|
||||
ICON_FUNCTIONS = {
|
||||
"home": draw_house,
|
||||
"discover": draw_hearts,
|
||||
"message": draw_chat_bubble,
|
||||
"activity": draw_calendar,
|
||||
"mine": draw_user,
|
||||
SVG_BODY = {
|
||||
"home": """<path d="M18 38 40.5 19 63 38"/><rect x="22" y="35" width="37" height="28" rx="7"/><path d="M31 63V49h19v14"/><path class="heart" d="M40.5 54c-5-3.4-7-5.4-7-8.2 0-2.3 1.8-4.1 4.1-4.1 1.3 0 2.4.6 2.9 1.6.6-1 1.7-1.6 3-1.6 2.3 0 4.1 1.8 4.1 4.1 0 2.8-2.1 4.8-7.1 8.2Z"/>""",
|
||||
"discover": """<circle cx="26" cy="29" r="7"/><circle cx="55" cy="29" r="7"/><path d="M14 55c1-10 6-15 15-15 4 0 7 1 9 3M67 55c-1-10-6-15-15-15-4 0-7 1-9 3"/><path class="heart" d="M40.5 58c-6.8-4.5-10-7.6-10-11.2 0-3 2.3-5.3 5.3-5.3 2 0 3.7 1 4.7 2.5 1-1.5 2.7-2.5 4.7-2.5 3 0 5.3 2.3 5.3 5.3 0 3.6-3.2 6.7-10 11.2Z"/>""",
|
||||
"message": """<path d="M23 60 15 66l2.4-11.2A12 12 0 0 1 14 46V29a12 12 0 0 1 12-12h29a12 12 0 0 1 12 12v17a12 12 0 0 1-12 12H27c-1.4 0-2.7.7-4 2Z"/><path class="heart" d="M40.5 48c-7.5-5-11-8.4-11-12.4 0-3.3 2.6-5.9 5.9-5.9 2.2 0 4.1 1.1 5.1 2.8 1.1-1.7 3-2.8 5.2-2.8 3.3 0 5.9 2.6 5.9 5.9 0 4-3.6 7.4-11.1 12.4Z"/>""",
|
||||
"activity": """<rect x="17" y="20" width="47" height="44" rx="8"/><path d="M17 33h47M28 15v10M53 15v10"/><path class="heart" d="M40.5 57c-6.5-4.3-9.5-7.2-9.5-10.7 0-2.9 2.3-5.1 5.1-5.1 1.9 0 3.5.9 4.4 2.4.9-1.5 2.5-2.4 4.5-2.4 2.8 0 5.1 2.2 5.1 5.1 0 3.5-3.1 6.4-9.6 10.7Z"/>""",
|
||||
"mine": """<circle cx="40.5" cy="27" r="8"/><path d="M20 64c.7-14 8-21 20.5-21S60.3 50 61 64"/><path class="heart" d="M40.5 59c-5.2-3.5-7.7-5.8-7.7-8.6 0-2.3 1.9-4.2 4.2-4.2 1.6 0 2.8.8 3.5 2 .8-1.2 2-2 3.6-2 2.3 0 4.2 1.9 4.2 4.2 0 2.8-2.5 5.1-7.8 8.6Z"/>""",
|
||||
}
|
||||
|
||||
|
||||
def create_icon(icon_name, size, color):
|
||||
"""创建单个图标"""
|
||||
img = Image.new('RGBA', (size, size), color=(255, 255, 255, 0)) # 透明背景
|
||||
draw = ImageDraw.Draw(img)
|
||||
def svg(name: str, active: bool) -> str:
|
||||
color = COLORS["active" if active else "normal"]
|
||||
fill = color if active else "none"
|
||||
return f"""<svg width="81" height="81" viewBox="0 0 81 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>{TITLES[name]}</title>
|
||||
<g stroke="{color}" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">{SVG_BODY[name]}</g>
|
||||
<style>.heart{{fill:{fill};stroke:{color}}}.badge{{fill:white}}</style>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
func = ICON_FUNCTIONS.get(icon_name)
|
||||
if func:
|
||||
func(draw, size // 2, size // 2, size, color)
|
||||
|
||||
return img
|
||||
def heart_points(cx: float, cy: float, scale: float):
|
||||
points = []
|
||||
for degree in range(0, 361, 3):
|
||||
angle = math.radians(degree)
|
||||
x = scale * 16 * math.sin(angle) ** 3
|
||||
y = -scale * (13 * math.cos(angle) - 5 * math.cos(2 * angle) - 2 * math.cos(3 * angle) - math.cos(4 * angle))
|
||||
points.append((cx + x, cy + y))
|
||||
return points
|
||||
|
||||
|
||||
def render(name: str, size: int, active: bool) -> Image.Image:
|
||||
aa = 4
|
||||
factor = size / 81 * aa
|
||||
image = Image.new("RGBA", (size * aa, size * aa), (255, 255, 255, 0))
|
||||
draw = ImageDraw.Draw(image)
|
||||
color = COLORS["active" if active else "normal"]
|
||||
width = max(1, round(4 * factor))
|
||||
p = lambda value: round(value * factor)
|
||||
|
||||
def line(points):
|
||||
draw.line([(p(x), p(y)) for x, y in points], fill=color, width=width, joint="curve")
|
||||
|
||||
def ellipse(box, fill=None):
|
||||
draw.ellipse(tuple(p(v) for v in box), outline=color, fill=fill, width=width)
|
||||
|
||||
def rounded(box, radius):
|
||||
draw.rounded_rectangle(tuple(p(v) for v in box), radius=p(radius), outline=color, width=width)
|
||||
|
||||
def heart(cx, cy, scale):
|
||||
points = [(p(x), p(y)) for x, y in heart_points(cx, cy, scale)]
|
||||
if active:
|
||||
draw.polygon(points, fill=color)
|
||||
else:
|
||||
draw.line(points, fill=color, width=width, joint="curve")
|
||||
|
||||
if name == "home":
|
||||
line([(18, 38), (40.5, 19), (63, 38)])
|
||||
rounded((22, 35, 59, 63), 7)
|
||||
line([(31, 63), (31, 49), (50, 49), (50, 63)])
|
||||
heart(40.5, 47.5, .32)
|
||||
elif name == "discover":
|
||||
ellipse((19, 22, 33, 36))
|
||||
ellipse((48, 22, 62, 36))
|
||||
draw.arc((p(14), p(39), p(43), p(67)), 190, 315, fill=color, width=width)
|
||||
draw.arc((p(38), p(39), p(67), p(67)), 225, 350, fill=color, width=width)
|
||||
heart(40.5, 48, .48)
|
||||
elif name == "message":
|
||||
rounded((14, 17, 67, 58), 12)
|
||||
line([(27, 58), (15, 66), (18, 53)])
|
||||
heart(40.5, 37, .52)
|
||||
elif name == "activity":
|
||||
rounded((17, 20, 64, 64), 8)
|
||||
line([(17, 33), (64, 33)])
|
||||
line([(28, 15), (28, 25)])
|
||||
line([(53, 15), (53, 25)])
|
||||
heart(40.5, 48, .45)
|
||||
else:
|
||||
ellipse((32.5, 19, 48.5, 35))
|
||||
draw.arc((p(20), p(39), p(61), p(78)), 185, 355, fill=color, width=width)
|
||||
heart(40.5, 52, .36)
|
||||
|
||||
return image.resize((size, size), Image.Resampling.LANCZOS)
|
||||
|
||||
|
||||
def main():
|
||||
output_dir = r"E:\develop\公益婚恋服务平台\miniapp\assets\tabbar"
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
icons = [
|
||||
("home", "首页"),
|
||||
("discover", "找对象"),
|
||||
("message", "消息"),
|
||||
("activity", "活动"),
|
||||
("mine", "我的"),
|
||||
]
|
||||
|
||||
sizes_map = [
|
||||
(81, ""), # 1x
|
||||
(162, "_2x"), # 2x
|
||||
(243, "_3x"), # 3x
|
||||
]
|
||||
|
||||
print("=" * 50)
|
||||
print("丹阳公益婚恋平台 - 底部导航栏图标生成")
|
||||
print("=" * 50)
|
||||
print()
|
||||
|
||||
total = 0
|
||||
for icon_name, label in icons:
|
||||
print(f"🎨 {label} ({icon_name}):")
|
||||
|
||||
for size, suffix in sizes_map:
|
||||
# 未选中状态
|
||||
img_u = create_icon(icon_name, size, color=COLOR_UNSELECTED)
|
||||
path_u = os.path.join(output_dir, f"{icon_name}{suffix}.png")
|
||||
img_u.save(path_u, 'PNG')
|
||||
|
||||
# 选中状态
|
||||
img_s = create_icon(icon_name, size, color=COLOR_SELECTED)
|
||||
path_s = os.path.join(output_dir, f"{icon_name}-active{suffix}.png")
|
||||
img_s.save(path_s, 'PNG')
|
||||
|
||||
print(f" ✓ {os.path.basename(path_u)} ({size}×{size}, {os.path.getsize(path_u)} bytes)")
|
||||
print(f" ✓ {os.path.basename(path_s)} ({size}×{size}, {os.path.getsize(path_s)} bytes)")
|
||||
total += 2
|
||||
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print(f"✅ 共生成 {total} 个图标文件")
|
||||
print(f"📁 保存路径: {output_dir}")
|
||||
print()
|
||||
print("文件清单:")
|
||||
for f in sorted(os.listdir(output_dir)):
|
||||
if f.endswith('.png'):
|
||||
filepath = os.path.join(output_dir, f)
|
||||
print(f" {f} ({os.path.getsize(filepath)} bytes)")
|
||||
(ROOT / "svg").mkdir(parents=True, exist_ok=True)
|
||||
for name in TITLES:
|
||||
for active in (False, True):
|
||||
state = "-active" if active else ""
|
||||
(ROOT / "svg" / f"{name}{state}.svg").write_text(svg(name, active), encoding="utf-8")
|
||||
for size, suffix in ((81, ""), (162, "_2x"), (243, "_3x")):
|
||||
render(name, size, active).save(ROOT / f"{name}{state}{suffix}.png")
|
||||
print("已生成 10 个 SVG 和 30 个 PNG 导航图标")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const root = join(process.cwd(), 'miniapp', 'assets', 'tabbar')
|
||||
const icons = {
|
||||
home: '爱心小屋',
|
||||
discover: '双人缘分',
|
||||
message: '爱心对话',
|
||||
activity: '公益日历',
|
||||
mine: '个人守护',
|
||||
}
|
||||
|
||||
for (const [name, title] of Object.entries(icons)) {
|
||||
const normal = readFileSync(join(root, 'svg', `${name}.svg`), 'utf8')
|
||||
const active = readFileSync(join(root, 'svg', `${name}-active.svg`), 'utf8')
|
||||
|
||||
assert.match(normal, new RegExp(`<title>${title}</title>`), `${name} 缺少主题化标题`)
|
||||
assert.match(normal, /#999999/i, `${name} 默认态颜色错误`)
|
||||
assert.match(active, /#E96B6B/i, `${name} 选中态颜色错误`)
|
||||
|
||||
for (const suffix of ['', '_2x', '_3x']) {
|
||||
const expectedSize = suffix === '' ? 81 : suffix === '_2x' ? 162 : 243
|
||||
for (const state of ['', '-active']) {
|
||||
const png = readFileSync(join(root, `${name}${state}${suffix}.png`))
|
||||
assert.equal(png.readUInt32BE(16), expectedSize)
|
||||
assert.equal(png.readUInt32BE(20), expectedSize)
|
||||
assert.equal(png[25], 6, `${name}${state}${suffix} 必须为透明 RGBA PNG`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Tabbar icon contracts passed')
|
||||