4df8dc6f87
新建 web/website/(Astro 零 SSR): - 组件迁移:交互块保留 .jsx 经 @astrojs/react(Header/AnnouncementBar/SignupForm/PricingPlans),纯展示块转 .astro 去运行时 JS;像素以原型为基准。 - 令牌同源:build-tokens.mjs 从 design/colors_and_type.css 生成 tokens.gen.css,仅剔除第三方 Google Fonts @import,数值不改。 - 字体自托管:@fontsource(Sora/Manrope/Noto Sans SC/JetBrains Mono),无第三方 CDN。 - 图标:Lucide 构建期内联 SVG(替代 unpkg CDN),零运行时、零 CDN。 - i18n:/(zh)与 /en/(en)双路由单显,语言切换组件;文案沿用 ui_kits 脱敏文案。 - 安全:public/_headers 严格 CSP(全 self + 自托管资源 + 内联片段 sha256,无 unsafe-inline)+ HSTS;无支付表单。 - CI:.gitea/workflows/website.yml 构建(红线扫描+lint+CSP 哈希)→ 同时发布 Cloudflare Pages 主站与镜像。 - 灾备:README 写明干净环境 npm ci && npm run build 可直接部署到任意静态托管;dist 指纹确定性,主站镜像一致。 测试:npm run lint(0 error)/ npm test(build+CSP 哈希注入+红线扫描 0 命中)/ 两次干净构建 dist 指纹一致。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
38 lines
1.2 KiB
Plaintext
38 lines
1.2 KiB
Plaintext
---
|
||
/**
|
||
* Icon.astro — 把原型里的 `<i data-lucide="name">` 机械替换为「构建期内联 SVG」。
|
||
* 使用 lucide-react,但**不**水合(无 client: 指令)→ Astro 在构建期渲染成静态 <svg>,
|
||
* 零运行时 JS、零第三方 CDN(原型用的是 unpkg.com 的 lucide,已按任务要求去除)。
|
||
* 尺寸/描边颜色沿用 website.css 里对 `svg` 的选择器控制,故此处不强加尺寸。
|
||
*/
|
||
import * as Lucide from 'lucide-react';
|
||
|
||
interface Props {
|
||
name: string;
|
||
class?: string;
|
||
[key: string]: unknown;
|
||
}
|
||
|
||
const { name, class: className, ...rest } = Astro.props;
|
||
|
||
// kebab-case → PascalCase(lucide-react 导出名)
|
||
const pascal = name
|
||
.split('-')
|
||
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
||
.join('');
|
||
|
||
// 个别图标的兜底别名(lucide 改名历史)。
|
||
const ALIASES: Record<string, string> = {
|
||
CheckCircle: 'CircleCheckBig',
|
||
CircleHelp: 'CircleHelp',
|
||
};
|
||
|
||
const lib = Lucide as unknown as Record<string, any>;
|
||
const Comp = lib[pascal] ?? lib[ALIASES[pascal] ?? ''] ?? null;
|
||
|
||
if (!Comp) {
|
||
throw new Error(`[Icon] 未知 lucide 图标: "${name}" (尝试 ${pascal})`);
|
||
}
|
||
---
|
||
<Comp class={['lucide', className].filter(Boolean).join(' ')} {...rest} />
|