v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)

This commit is contained in:
BigCode
2026-11-01 00:15:29 +08:00
commit 5b83e71ad9
128 changed files with 20008 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
/**
* # 脚本 烘焙 AntD 静态样式
* 背景:AntD v6 的 CSS-in-JS 样式只在客户端渲染时注入,SSR 首屏会出现
* "无样式闪烁"(后台打开瞬间按钮/表单是浏览器默认样式)。
* 本脚本把后台用到的全部 AntD 组件以与 admin.tsx 一致的主题渲染一遍,
* 用 @ant-design/cssinjs 的 extractStyle 导出静态 CSS,写入 src/styles/antd-baked.css,
* 由 admin.tsx 直接 import,使 SSR 首屏就带上完整 AntD 样式。
*
* 运行:bun scripts/gen-antd-css.tsx(主题变更后需重新执行)
* 输出到 public/antd-baked.css(静态文件,admin 路由 <link> 引入)。
* 注意:不能以 JS import 方式引入 —— dev 模式 vite 会把 658KB CSS 注入成
* <style> 标签,且 dev 预载全部路由模块会把它带到前台每个页面,F12 卡死。
*/
import { mkdirSync, writeFileSync } from "node:fs";
import { renderToString } from "react-dom/server";
import { renderToString as _r } from "react-dom/server";
import {
App,
Avatar,
Button,
Card,
Col,
ConfigProvider,
Descriptions,
Drawer,
Empty,
Form,
Image,
Input,
InputNumber,
Layout,
Menu,
Modal,
Popconfirm,
Row,
Select,
Space,
Statistic,
Switch,
Table,
Tabs,
Tag,
Typography,
} from "antd";
import { createCache, StyleProvider, extractStyle } from "@ant-design/cssinjs";
/** # 成员变量 与 admin.tsx 完全一致的主题配置(改动后必须重新烘焙)
* cssVar.key 固定为 "dusun":变量作用域类名确定,烘焙 CSS 与运行时 DOM 一致 **/
const THEME = { token: { colorPrimary: "#1677ff" }, cssVar: { key: "dusun" } };
/** # 组件 覆盖后台全部 AntD 组件的样例树(每个组件至少渲染一次以注册其样式) **/
function Showcase() {
return (
<ConfigProvider theme={THEME}>
<App>
<Layout style={{ minHeight: 10 }}>
<Layout.Sider width={10}>
<Menu
mode="inline"
theme="dark"
items={[
{ key: "a", label: "A" },
{ key: "b", label: "B" },
{ type: "submenu", key: "c", label: "C", children: [{ key: "d", label: "D" }] },
]}
/>
</Layout.Sider>
<Layout>
<Layout.Header style={{ height: 10 }}>H</Layout.Header>
<Layout.Content style={{ padding: 4 }}>
<Card title="Card" extra={<Button type="primary">主要</Button>}>
<Space wrap>
<Button>默认</Button>
<Button type="primary">主要</Button>
<Button type="dashed">虚线</Button>
<Button type="link">链接</Button>
<Button type="text">文本</Button>
<Button danger>危险</Button>
<Button size="small">小</Button>
<Button type="primary" loading>
加载
</Button>
<Avatar icon={<span />} size="small" />
<Tag color="blue">蓝</Tag>
<Tag>默认</Tag>
<Switch defaultChecked />
<InputNumber defaultValue={1} />
</Space>
<Row gutter={8}>
<Col span={12}>
<Form layout="vertical">
<Form.Item label="邮箱" required>
<Input placeholder="email" />
</Form.Item>
<Form.Item label="数量" validateStatus="error" extra="提示">
<InputNumber />
</Form.Item>
<Form.Item label="选择">
<Select
options={[
{ value: 1, label: "一" },
{ value: 2, label: "二" },
]}
defaultValue={1}
style={{ width: 120 }}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
提交
</Button>
</Form.Item>
</Form>
</Col>
<Col span={12}>
<Tabs
items={[
{ key: "1", label: "Tab1", children: <p>内容1</p> },
{ key: "2", label: "Tab2", children: <p>内容2</p> },
]}
/>
<Descriptions title="详情" bordered size="small" column={1}>
<Descriptions.Item label="订单号">DH-001</Descriptions.Item>
</Descriptions>
<Statistic title="询盘" value={12} />
<Typography.Title level={5}>标题</Typography.Title>
<Typography.Text type="secondary">说明</Typography.Text>
<Image src="data:image/gif;base64,R0lGODlhAQABAAAAACw=" width={8} height={8} alt="" />
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="空" />
</Col>
</Row>
<Table
size="small"
pagination={{ pageSize: 5, total: 6 }}
dataSource={[{ k: 1, name: "行" }]}
rowKey="k"
columns={[
{ title: "名称", dataIndex: "name" },
{
title: "操作",
render: () => (
<Space>
<Button size="small">编辑</Button>
<Popconfirm title="确认删除?">
<Button danger size="small">
删除
</Button>
</Popconfirm>
</Space>
),
},
]}
/>
</Card>
<Modal open title="弹窗" okText="确定" cancelText="取消">
内容
</Modal>
<Drawer open title="抽屉" width={120}>
内容
</Drawer>
</Layout.Content>
<Layout.Footer>F</Layout.Footer>
</Layout>
</Layout>
</App>
</ConfigProvider>
);
}
// 渲染样例树并提取样式(hashPriority=high → 生成无哈希的 .ant-* 选择器,
// 与 admin.tsx 的 StyleProvider hashPriority="high" 保持一致)
const cache = createCache();
renderToString(
<StyleProvider cache={cache} hashPriority="high">
<Showcase />
</StyleProvider>,
);
const css = extractStyle(cache, { plain: true }) as string;
if (!css || css.length < 10000 || !css.includes(".ant-btn")) {
console.error("提取结果异常,中止写入。length:", css?.length);
process.exit(1);
}
mkdirSync(new URL("../public/", import.meta.url), { recursive: true });
const out = new URL("../public/antd-baked.css", import.meta.url);
writeFileSync(out, `/* 本文件由 scripts/gen-antd-css.tsx 生成(AntD SSR 静态样式,防首屏闪烁),请勿手改 */\n${css}\n`);
console.log("written:", out.pathname, `${Math.round(css.length / 1024)}KB`);