Files

166 lines
8.0 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* 纪念日分类的图标(v62 用户 2026-10-18 第 2 条)。
*
* 用户原话:「设置按钮,点击后弹菜单,第一个菜单是分类设置,进去之后可以设置分类,
* 每一类纪念日都有不同的图标……如果是用的 fontawesome,那么如果用户
* 添加了自己的分类,就让用户自己选。在列表中、日历中,都要根据分类,
* 显示不同的图标。」
*
* ⇒ 为什么**不自带 FontAwesome**(两条硬理由,别改回去):
* ① 体积:整套 webfont(woff2 + css)≈ 100~300 KB,而这个项目是"点开就是一个
* 家庭站点"的自建站,首页已经背了 antd(1.8 MB)。为了十几个图标再背一整套
* 字体不划算,而且中文字形根本用不上。
* ② 离线/内网:本项目所有静态资源都是本地 vendor 目录,不能挂 CDN(记忆里
* "静态资源地址必须带版本号"那一条的同一个前提)——引 CDN 会在没外网的
* 家庭内网里直接变成空白方块。
* ⇒ 用**内置的行内 SVG 图标集**:一个图标一条 `<path>`(stroke 描边、currentColor
* 取文字颜色),列表 / 卡片里直接内联;日历那种纯文本的地方用同一图标的
* emoji 兜底(`emo()`)—— 日历的事件标题是纯文本,塞不进 SVG。
*
* ⚠️ **图标 key 是**契约**:它存在 settings(anniv_kinds)里,用户自己选。
* 所以:① 已有 key 永远不要改名(老数据会变成"认不出的图标");
* ② 新增图标只能往后加;③ 认不出的 key 一律回落 `dot`,不抛异常
* (用户可能手改过库、或跨版本导入过配置)。
*
* @see \App\Anniversary::kindsFull() 分类清单(名称 + 图标)的读写
*/
declare(strict_types=1);
namespace App;
final class AnnivIcon
{
/** 认不出/没配时用哪个 */
public const FALLBACK = 'dot';
/**
* key => [中文名, emoji, SVG 内容(24×24 画布,stroke 描边)]
*
* ⚠️ SVG 内容里**不要**写 `fill` / `stroke` —— 由 svg() 统一给
* `fill="none" stroke="currentColor"`,这样图标颜色永远跟着所在位置的文字色,
* 浅底深底都不会变成"看不见的黑块"。
*/
public const LIST = [
'cake' => ['蛋糕(生日)', '🎂',
'<path d="M4 20h16"/>'
. '<path d="M5 20v-4.6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2V20"/>'
. '<path d="M9 13.4V10M15 13.4V10"/>'
. '<path d="M9 7.4c.9-.9.2-2-.2-2.4-.5.6-1.1 1.5-.2 2.4Z"/>'
. '<path d="M15 7.4c.9-.9.2-2-.2-2.4-.5.6-1.1 1.5-.2 2.4Z"/>'],
'ring' => ['戒指(结婚)', '💍',
'<circle cx="12" cy="15" r="5.2"/>'
. '<path d="M8.8 9.4 12 5.2l3.2 4.2"/>'
. '<path d="M8.8 9.4h6.4"/>'],
'heart' => ['爱心(相识/恋爱)', '❤️',
'<path d="M12 20s-7-4.4-7-9.2A3.9 3.9 0 0 1 12 8.2a3.9 3.9 0 0 1 7 2.6C19 15.6 12 20 12 20Z"/>'],
'candle' => ['蜡烛(忌日/追思)', '🕯️',
'<path d="M9 21h6"/>'
. '<rect x="9" y="10.5" width="6" height="10.5" rx="1"/>'
. '<path d="M12 10.5V8.4"/>'
. '<path d="M12 8.4c1-1 1-2.2 0-3-1 .8-1 2 0 3Z"/>'],
'star' => ['星星(节日)', '⭐',
'<path d="M12 3.6l2.6 5.3 5.8.8-4.2 4.1 1 5.8L12 16.9l-5.2 2.7 1-5.8-4.2-4.1 5.8-.8Z"/>'],
'gift' => ['礼物', '🎁',
'<rect x="4" y="11" width="16" height="9" rx="1"/>'
. '<path d="M3 7.4h18v3.6H3zM12 7.4V20"/>'
. '<path d="M12 7.4S10.6 3 8 3a2.2 2.2 0 0 0 0 4.4Z"/>'
. '<path d="M12 7.4S13.4 3 16 3a2.2 2.2 0 0 1 0 4.4Z"/>'],
'baby' => ['孩子(满月/出生)', '👶',
'<circle cx="12" cy="7.6" r="3.4"/>'
. '<path d="M6.4 20c0-3.6 2.5-6 5.6-6s5.6 2.4 5.6 6"/>'
. '<path d="M10.5 7.2h.01M13.5 7.2h.01"/>'],
'home' => ['家(搬家/乔迁)', '🏠',
'<path d="M4 10.6 12 4l8 6.6V20H4z"/>'
. '<path d="M9.6 20v-5.2h4.8V20"/>'],
'book' => ['书(毕业/升学)', '📖',
'<path d="M12 7.2c-1.6-1.4-3.6-2-6-2v11c2.4 0 4.4.6 6 2 1.6-1.4 3.6-2 6-2v-11c-2.4 0-4.4.6-6 2Z"/>'
. '<path d="M12 7.2v11"/>'],
'plane' => ['飞机(远行/旅行)', '✈️',
'<path d="M21 4 3 11.2l6.4 2.2L11.6 20 21 4Z"/>'],
'camera' => ['相机(合影/留念)', '📷',
'<rect x="3.4" y="7.4" width="17.2" height="12" rx="2"/>'
. '<circle cx="12" cy="13.4" r="3.4"/>'
. '<path d="M8.6 7.4 10 4.8h4l1.4 2.6"/>'],
'music' => ['音符(音乐/演出)', '🎵',
'<circle cx="8" cy="17.4" r="2.6"/>'
. '<circle cx="17" cy="15.4" r="2.6"/>'
. '<path d="M10.6 17.4V7l9-2v10.4"/>'],
'trophy' => ['奖杯(成就/晋升)', '🏆',
'<path d="M8 4.6h8v4.8a4 4 0 0 1-8 0Z"/>'
. '<path d="M8 6.2H5.4v1.4a3.2 3.2 0 0 0 3.2 3.2"/>'
. '<path d="M16 6.2h2.6v1.4a3.2 3.2 0 0 1-3.2 3.2"/>'
. '<path d="M12 13.4V17M8.6 20h6.8"/>'],
'clock' => ['时钟(周年/入职)', '⏰',
'<circle cx="12" cy="12" r="7.6"/>'
. '<path d="M12 7.6V12l3 1.8"/>'],
'flower' => ['花(表白/祝福)', '🌸',
'<circle cx="12" cy="12" r="1.6"/>'
. '<circle cx="12" cy="6.6" r="2.3"/>'
. '<circle cx="16.9" cy="10.2" r="2.3"/>'
. '<circle cx="15" cy="16" r="2.3"/>'
. '<circle cx="9" cy="16" r="2.3"/>'
. '<circle cx="7.1" cy="10.2" r="2.3"/>'],
'leaf' => ['叶(自然而然)', '🌿',
'<path d="M20 4C10 4 4.4 9 4.4 15.4c0 1.8.6 3.4 1.6 4.6C9 20 20 16 20 4Z"/>'
. '<path d="M6 20C8.6 14 12 10.6 17 8"/>'],
'bell' => ['铃铛(提醒/纪念)', '🔔',
'<path d="M9 18a3 3 0 0 0 6 0"/>'
. '<path d="M6.6 18h10.8c-1.4-1.4-1.8-2.6-1.8-4.6V11a4.6 4.6 0 0 0-9.2 0v2.4c0 2-.4 3.2-1.8 4.6Z"/>'
. '<path d="M12 6.4V4.8"/>'],
'dot' => ['圆点(其它)', '⚪',
'<circle cx="12" cy="12" r="4"/>'],
];
public static function has(string $key): bool
{
return $key !== '' && isset(self::LIST[$key]);
}
/** 认不出的 key 一律回落,绝不抛异常(配置可能来自旧版本/手改) */
public static function normalize(string $key): string
{
$key = strtolower(trim($key));
return self::has($key) ? $key : self::FALLBACK;
}
/** 中文名(给设置页的下拉用) */
public static function label(string $key): string
{
return self::LIST[self::normalize($key)][0];
}
public static function emo(string $key): string
{
return self::LIST[self::normalize($key)][1];
}
/**
* 行内 SVG。
*
* ⚠️ 返回值是**可信 HTML**(不含任何用户输入:key 先 normalize,size 转 int),
* 模板里直接 `<?= ... ?>` 输出,不要 e() 一遍(那会把标签转义成文字)。
* aria-hidden:图标永远是"文字旁边的装饰",读屏器念分类名就够了。
*/
public static function svg(string $key, int $size = 15, string $cls = ''): string
{
$k = self::normalize($key);
$n = max(8, min(96, $size));
return '<svg class="ai ai-' . e($k) . ($cls !== '' ? ' ' . e($cls) : '')
. '" viewBox="0 0 24 24" width="' . $n . '" height="' . $n . '"'
. ' aria-hidden="true" focusable="false" fill="none" stroke="currentColor"'
. ' stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">'
. self::LIST[$k][2] . '</svg>';
}
/** 设置页下拉里能选的全部图标:key => "emoji 中文名" */
public static function choices(): array
{
$out = [];
foreach (self::LIST as $k => $v) {
$out[$k] = $v[1] . ' ' . $v[0];
}
return $out;
}
}