perf: replace shell icon bundle
This commit is contained in:
+11
-22
@@ -1,15 +1,3 @@
|
||||
import {
|
||||
ArrowDownOutlined,
|
||||
ArrowUpOutlined,
|
||||
CheckCircleOutlined,
|
||||
FlagOutlined,
|
||||
InfoCircleOutlined,
|
||||
LoginOutlined,
|
||||
LogoutOutlined,
|
||||
PlusCircleOutlined,
|
||||
UserOutlined,
|
||||
WalletOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { publicConfigClient, type WebPublicConfig } from "../api/publicConfigClient";
|
||||
@@ -23,6 +11,7 @@ import { AnimatedPanel } from "./AnimatedPanel";
|
||||
import AdminMonitor from "./AdminMonitor";
|
||||
import CookieConsentBanner from "./CookieConsentBanner";
|
||||
import { loadRechargeModal, type RechargeModalComponent } from "./RechargeModal/loadRechargeModal";
|
||||
import { ShellIcon } from "./ShellIcon";
|
||||
import { loadDarkGreenTheme } from "../styles/loadDarkGreenTheme";
|
||||
|
||||
interface AppShellProps {
|
||||
@@ -330,7 +319,7 @@ function AppShell({
|
||||
aria-label="返回页面顶部"
|
||||
onClick={() => scrollActivePage("top")}
|
||||
>
|
||||
<ArrowUpOutlined />
|
||||
<ShellIcon name="arrow-up" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -339,7 +328,7 @@ function AppShell({
|
||||
aria-label="到达页面底部"
|
||||
onClick={() => scrollActivePage("bottom")}
|
||||
>
|
||||
<ArrowDownOutlined />
|
||||
<ShellIcon name="arrow-down" />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -369,7 +358,7 @@ function AppShell({
|
||||
aria-label="网站信息"
|
||||
onClick={() => setInfoOpen((c) => !c)}
|
||||
>
|
||||
<InfoCircleOutlined />
|
||||
<ShellIcon name="info-circle" />
|
||||
</button>
|
||||
<AnimatedPanel open={infoOpen} className="info-popover panel-surface">
|
||||
<dl>
|
||||
@@ -392,7 +381,7 @@ function AppShell({
|
||||
aria-label={`积分余额 ${displayedBalanceLabel}`}
|
||||
onClick={() => toast.info("充值功能即将开放,敬请期待")}
|
||||
>
|
||||
<WalletOutlined />
|
||||
<ShellIcon name="wallet" />
|
||||
<span className="member-button__label">{displayedBalanceLabel}</span>
|
||||
</button>
|
||||
<div className="profile-popover-anchor" ref={profileRef}>
|
||||
@@ -416,7 +405,7 @@ function AppShell({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<LoginOutlined />
|
||||
<ShellIcon name="login" />
|
||||
<span>登录 / 注册</span>
|
||||
</>
|
||||
)}
|
||||
@@ -444,7 +433,7 @@ function AppShell({
|
||||
<div className="profile-popover__footer">
|
||||
<span>{session?.source === "server" ? "服务器会话" : "预览会话"}</span>
|
||||
<button type="button" onClick={onLogout}>
|
||||
<LogoutOutlined />
|
||||
<ShellIcon name="logout" />
|
||||
退出
|
||||
</button>
|
||||
</div>
|
||||
@@ -456,7 +445,7 @@ function AppShell({
|
||||
onSelectView("login");
|
||||
}}
|
||||
>
|
||||
<UserOutlined />
|
||||
<ShellIcon name="user" />
|
||||
个人中心
|
||||
</button>
|
||||
<button
|
||||
@@ -467,7 +456,7 @@ function AppShell({
|
||||
onSelectView("report");
|
||||
}}
|
||||
>
|
||||
<FlagOutlined />
|
||||
<ShellIcon name="flag" />
|
||||
投诉举报
|
||||
</button>
|
||||
{showCommunityReview ? (
|
||||
@@ -480,7 +469,7 @@ function AppShell({
|
||||
onSelectView("communityReview");
|
||||
}}
|
||||
>
|
||||
<CheckCircleOutlined />
|
||||
<ShellIcon name="check-circle" />
|
||||
社区审核
|
||||
</button>
|
||||
</>
|
||||
@@ -495,7 +484,7 @@ function AppShell({
|
||||
onSelectView("communityCaseAdd");
|
||||
}}
|
||||
>
|
||||
<PlusCircleOutlined />
|
||||
<ShellIcon name="plus-circle" />
|
||||
添加案例
|
||||
</button>
|
||||
</>
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
import {
|
||||
BellOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
DeleteOutlined,
|
||||
DislikeOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
LikeOutlined,
|
||||
LockOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { WebNotification, WebNotificationType, WebViewKey } from "../types";
|
||||
import { AnimatedPanel } from "./AnimatedPanel";
|
||||
import { ShellIcon } from "./ShellIcon";
|
||||
|
||||
const NOTIFICATION_ICONS: Record<WebNotificationType, React.ReactNode> = {
|
||||
task_completed: <CheckCircleOutlined style={{ color: "#10b981" }} />,
|
||||
task_failed: <CloseCircleOutlined style={{ color: "#ef4444" }} />,
|
||||
review_pending: <ExclamationCircleOutlined style={{ color: "#f59e0b" }} />,
|
||||
review_passed: <LikeOutlined style={{ color: "#10b981" }} />,
|
||||
review_rejected: <DislikeOutlined style={{ color: "#f59e0b" }} />,
|
||||
credits_low: <ExclamationCircleOutlined style={{ color: "#f59e0b" }} />,
|
||||
session_expired: <LockOutlined style={{ color: "#ef4444" }} />,
|
||||
info: <BellOutlined style={{ color: "#2563eb" }} />,
|
||||
task_completed: <ShellIcon name="check-circle" style={{ color: "#10b981" }} />,
|
||||
task_failed: <ShellIcon name="close-circle" style={{ color: "#ef4444" }} />,
|
||||
review_pending: <ShellIcon name="exclamation-circle" style={{ color: "#f59e0b" }} />,
|
||||
review_passed: <ShellIcon name="like" style={{ color: "#10b981" }} />,
|
||||
review_rejected: <ShellIcon name="dislike" style={{ color: "#f59e0b" }} />,
|
||||
credits_low: <ShellIcon name="exclamation-circle" style={{ color: "#f59e0b" }} />,
|
||||
session_expired: <ShellIcon name="lock" style={{ color: "#ef4444" }} />,
|
||||
info: <ShellIcon name="bell" style={{ color: "#2563eb" }} />,
|
||||
};
|
||||
|
||||
function parseTimestamp(dateStr: string): number {
|
||||
@@ -111,7 +102,7 @@ function NotificationCenter({ items, onNavigate, onMarkRead, onMarkAllRead, onCl
|
||||
aria-label={`通知中心${unreadCount > 0 ? `,${unreadCount}条未读` : ""}`}
|
||||
onClick={() => { setOpen((v) => !v); setNow(Date.now()); }}
|
||||
>
|
||||
<BellOutlined />
|
||||
<ShellIcon name="bell" />
|
||||
{unreadCount > 0 && (
|
||||
<span className="notification-center__badge">{unreadCount > 99 ? "99+" : unreadCount}</span>
|
||||
)}
|
||||
@@ -127,7 +118,7 @@ function NotificationCenter({ items, onNavigate, onMarkRead, onMarkAllRead, onCl
|
||||
)}
|
||||
{notifications.length > 0 && onClear && (
|
||||
<button className="notification-center__clear" type="button" onClick={() => { onClear(); setOpen(false); }}>
|
||||
<DeleteOutlined /> 清空
|
||||
<ShellIcon name="delete" /> 清空
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -135,7 +126,7 @@ function NotificationCenter({ items, onNavigate, onMarkRead, onMarkAllRead, onCl
|
||||
<div className="notification-center__list">
|
||||
{notifications.length === 0 ? (
|
||||
<div className="notification-center__empty">
|
||||
<BellOutlined style={{ fontSize: 28, opacity: 0.3 }} />
|
||||
<ShellIcon name="bell" style={{ fontSize: 28, opacity: 0.3 }} />
|
||||
<span>暂无通知</span>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
export type ShellIconName =
|
||||
| "arrow-down"
|
||||
| "arrow-left"
|
||||
| "arrow-up"
|
||||
| "bar-chart"
|
||||
| "bell"
|
||||
| "branches"
|
||||
| "check-circle"
|
||||
| "chevron-left"
|
||||
| "chevron-right"
|
||||
| "close-circle"
|
||||
| "copy"
|
||||
| "customer-service"
|
||||
| "delete"
|
||||
| "dislike"
|
||||
| "download"
|
||||
| "exclamation-circle"
|
||||
| "flag"
|
||||
| "file-text"
|
||||
| "folder"
|
||||
| "global"
|
||||
| "heart"
|
||||
| "home"
|
||||
| "info-circle"
|
||||
| "like"
|
||||
| "line-chart"
|
||||
| "lock"
|
||||
| "login"
|
||||
| "logout"
|
||||
| "loading"
|
||||
| "plus-circle"
|
||||
| "reload"
|
||||
| "robot"
|
||||
| "shopping"
|
||||
| "swap"
|
||||
| "team"
|
||||
| "thunderbolt"
|
||||
| "tool"
|
||||
| "upload"
|
||||
| "user"
|
||||
| "wallet"
|
||||
| "warning";
|
||||
|
||||
interface ShellIconProps {
|
||||
name: ShellIconName;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
function renderIcon(name: ShellIconName) {
|
||||
switch (name) {
|
||||
case "arrow-down":
|
||||
return <path d="M12 5v14m0 0 6-6m-6 6-6-6" />;
|
||||
case "arrow-left":
|
||||
return <path d="M19 12H5m0 0 6-6m-6 6 6 6" />;
|
||||
case "arrow-up":
|
||||
return <path d="M12 19V5m0 0 6 6m-6-6-6 6" />;
|
||||
case "bar-chart":
|
||||
return (
|
||||
<>
|
||||
<path d="M4 19V5" />
|
||||
<path d="M4 19h16" />
|
||||
<path d="M8 16v-5" />
|
||||
<path d="M12 16V8" />
|
||||
<path d="M16 16v-9" />
|
||||
</>
|
||||
);
|
||||
case "bell":
|
||||
return (
|
||||
<>
|
||||
<path d="M18 9a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9" />
|
||||
<path d="M10 21h4" />
|
||||
</>
|
||||
);
|
||||
case "branches":
|
||||
return (
|
||||
<>
|
||||
<circle cx="6" cy="6" r="2" />
|
||||
<circle cx="18" cy="6" r="2" />
|
||||
<circle cx="12" cy="18" r="2" />
|
||||
<path d="M8 7.5 12 12l4-4.5" />
|
||||
<path d="M12 12v4" />
|
||||
</>
|
||||
);
|
||||
case "check-circle":
|
||||
return (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="m8 12 2.5 2.5L16 9" />
|
||||
</>
|
||||
);
|
||||
case "chevron-left":
|
||||
return <path d="m15 18-6-6 6-6" />;
|
||||
case "chevron-right":
|
||||
return <path d="m9 18 6-6-6-6" />;
|
||||
case "close-circle":
|
||||
return (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="m9 9 6 6m0-6-6 6" />
|
||||
</>
|
||||
);
|
||||
case "copy":
|
||||
return (
|
||||
<>
|
||||
<rect x="8" y="8" width="11" height="11" rx="2" />
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1" />
|
||||
</>
|
||||
);
|
||||
case "customer-service":
|
||||
return (
|
||||
<>
|
||||
<path d="M4 13a8 8 0 0 1 16 0" />
|
||||
<path d="M5 13h3v5H5a2 2 0 0 1-2-2v-1a2 2 0 0 1 2-2Z" />
|
||||
<path d="M16 13h3a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2h-3v-5Z" />
|
||||
<path d="M18 18c0 2-2 3-6 3" />
|
||||
</>
|
||||
);
|
||||
case "delete":
|
||||
return (
|
||||
<>
|
||||
<path d="M4 7h16" />
|
||||
<path d="M10 11v6" />
|
||||
<path d="M14 11v6" />
|
||||
<path d="M6 7l1 14h10l1-14" />
|
||||
<path d="M9 7V4h6v3" />
|
||||
</>
|
||||
);
|
||||
case "download":
|
||||
return (
|
||||
<>
|
||||
<path d="M12 4v11" />
|
||||
<path d="m7 10 5 5 5-5" />
|
||||
<path d="M5 20h14" />
|
||||
</>
|
||||
);
|
||||
case "dislike":
|
||||
return (
|
||||
<>
|
||||
<path d="M7 3v12" />
|
||||
<path d="M7 15h9l-1 5a2 2 0 0 1-3 1l-3-6H5a2 2 0 0 1-2-2V6a3 3 0 0 1 3-3h1" />
|
||||
<path d="M17 3h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-3" />
|
||||
</>
|
||||
);
|
||||
case "exclamation-circle":
|
||||
return (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 7v6" />
|
||||
<path d="M12 17h.01" />
|
||||
</>
|
||||
);
|
||||
case "flag":
|
||||
return (
|
||||
<>
|
||||
<path d="M5 21V4" />
|
||||
<path d="M5 5h11l-1.5 4L16 13H5" />
|
||||
</>
|
||||
);
|
||||
case "file-text":
|
||||
return (
|
||||
<>
|
||||
<path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9Z" />
|
||||
<path d="M14 3v6h6" />
|
||||
<path d="M8 13h8" />
|
||||
<path d="M8 17h6" />
|
||||
</>
|
||||
);
|
||||
case "folder":
|
||||
return <path d="M3 7h7l2 2h9v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z" />;
|
||||
case "global":
|
||||
return (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M3 12h18" />
|
||||
<path d="M12 3c3 3 3 15 0 18" />
|
||||
<path d="M12 3c-3 3-3 15 0 18" />
|
||||
</>
|
||||
);
|
||||
case "heart":
|
||||
return <path d="M20 8.5c0 5-8 10.5-8 10.5S4 13.5 4 8.5A4.5 4.5 0 0 1 12 6a4.5 4.5 0 0 1 8 2.5Z" />;
|
||||
case "home":
|
||||
return (
|
||||
<>
|
||||
<path d="M3 11 12 4l9 7" />
|
||||
<path d="M5 10v10h14V10" />
|
||||
<path d="M10 20v-6h4v6" />
|
||||
</>
|
||||
);
|
||||
case "info-circle":
|
||||
return (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 11v6" />
|
||||
<path d="M12 7h.01" />
|
||||
</>
|
||||
);
|
||||
case "like":
|
||||
return (
|
||||
<>
|
||||
<path d="M7 21V9" />
|
||||
<path d="M7 9h3l3-6a2 2 0 0 1 3 1l-1 5h4a2 2 0 0 1 2 2l-2 8a3 3 0 0 1-3 2H7" />
|
||||
<path d="M3 10h4v10H3z" />
|
||||
</>
|
||||
);
|
||||
case "line-chart":
|
||||
return (
|
||||
<>
|
||||
<path d="M4 19V5" />
|
||||
<path d="M4 19h16" />
|
||||
<path d="m7 15 4-4 3 3 5-7" />
|
||||
</>
|
||||
);
|
||||
case "lock":
|
||||
return (
|
||||
<>
|
||||
<rect x="5" y="10" width="14" height="10" rx="2" />
|
||||
<path d="M8 10V7a4 4 0 0 1 8 0v3" />
|
||||
</>
|
||||
);
|
||||
case "login":
|
||||
return (
|
||||
<>
|
||||
<path d="M14 4h5v16h-5" />
|
||||
<path d="M4 12h10" />
|
||||
<path d="m10 8 4 4-4 4" />
|
||||
</>
|
||||
);
|
||||
case "logout":
|
||||
return (
|
||||
<>
|
||||
<path d="M10 4H5v16h5" />
|
||||
<path d="M20 12H10" />
|
||||
<path d="m14 8-4 4 4 4" />
|
||||
</>
|
||||
);
|
||||
case "loading":
|
||||
return (
|
||||
<>
|
||||
<path d="M12 3a9 9 0 1 1-8 5" />
|
||||
<path d="M4 3v5h5" />
|
||||
</>
|
||||
);
|
||||
case "plus-circle":
|
||||
return (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 8v8" />
|
||||
<path d="M8 12h8" />
|
||||
</>
|
||||
);
|
||||
case "reload":
|
||||
return (
|
||||
<>
|
||||
<path d="M20 12a8 8 0 1 1-2.3-5.7" />
|
||||
<path d="M20 4v6h-6" />
|
||||
</>
|
||||
);
|
||||
case "robot":
|
||||
return (
|
||||
<>
|
||||
<rect x="5" y="8" width="14" height="11" rx="3" />
|
||||
<path d="M12 8V4" />
|
||||
<path d="M8 13h.01" />
|
||||
<path d="M16 13h.01" />
|
||||
<path d="M9 17h6" />
|
||||
</>
|
||||
);
|
||||
case "shopping":
|
||||
return (
|
||||
<>
|
||||
<path d="M6 7h15l-2 8H8L6 7Z" />
|
||||
<path d="M6 7 5 4H2" />
|
||||
<circle cx="9" cy="20" r="1.5" />
|
||||
<circle cx="18" cy="20" r="1.5" />
|
||||
</>
|
||||
);
|
||||
case "swap":
|
||||
return (
|
||||
<>
|
||||
<path d="M7 7h13m0 0-4-4m4 4-4 4" />
|
||||
<path d="M17 17H4m0 0 4-4m-4 4 4 4" />
|
||||
</>
|
||||
);
|
||||
case "team":
|
||||
return (
|
||||
<>
|
||||
<circle cx="9" cy="8" r="3" />
|
||||
<path d="M3 20a6 6 0 0 1 12 0" />
|
||||
<path d="M16 11a3 3 0 1 0-1-5.8" />
|
||||
<path d="M17 20a5 5 0 0 0-3-4.6" />
|
||||
</>
|
||||
);
|
||||
case "thunderbolt":
|
||||
return <path d="M13 2 4 14h7l-1 8 10-13h-7l1-7Z" />;
|
||||
case "tool":
|
||||
return <path d="M14.5 5.5a5 5 0 0 0 4 6.5L9 21l-6-6 9-9.5a5 5 0 0 0 2.5 0Z" />;
|
||||
case "upload":
|
||||
return (
|
||||
<>
|
||||
<path d="M12 20V9" />
|
||||
<path d="m7 14 5-5 5 5" />
|
||||
<path d="M5 4h14" />
|
||||
</>
|
||||
);
|
||||
case "user":
|
||||
return (
|
||||
<>
|
||||
<circle cx="12" cy="8" r="4" />
|
||||
<path d="M4 21a8 8 0 0 1 16 0" />
|
||||
</>
|
||||
);
|
||||
case "wallet":
|
||||
return (
|
||||
<>
|
||||
<path d="M4 7h15a2 2 0 0 1 2 2v10H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h12" />
|
||||
<path d="M16 13h5" />
|
||||
<path d="M17 16h.01" />
|
||||
</>
|
||||
);
|
||||
case "warning":
|
||||
return (
|
||||
<>
|
||||
<path d="M12 3 2 20h20L12 3Z" />
|
||||
<path d="M12 9v5" />
|
||||
<path d="M12 17h.01" />
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return <circle cx="12" cy="12" r="8" />;
|
||||
}
|
||||
}
|
||||
|
||||
export function ShellIcon({ name, className, style }: ShellIconProps) {
|
||||
return (
|
||||
<span className={["anticon", "shell-icon", className].filter(Boolean).join(" ")} style={style} aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
{renderIcon(name)}
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user