v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)
This commit is contained in:
@@ -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`);
|
||||
Reference in New Issue
Block a user