@zhang_jifan/fanui 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ACCESSIBILITY.md +61 -0
- package/CHANGELOG.md +112 -0
- package/COMMERCIAL-LICENSE.md +71 -0
- package/DESIGN-SYSTEM.md +294 -0
- package/LICENSE +661 -0
- package/README.md +348 -0
- package/USAGE.md +1926 -0
- package/_index.scss +20 -0
- package/dist/fanui.cjs +15 -0
- package/dist/fanui.css +28209 -0
- package/dist/fanui.d.ts +353 -0
- package/dist/fanui.esm.js +71 -0
- package/dist/fanui.js +4782 -0
- package/dist/fanui.min.css +1 -0
- package/dist/fanui.mjs +71 -0
- package/dist/modules/fanui-advanced.css +830 -0
- package/dist/modules/fanui-advanced.min.css +129 -0
- package/dist/modules/fanui-alert.css +575 -0
- package/dist/modules/fanui-alert.min.css +122 -0
- package/dist/modules/fanui-badge.css +426 -0
- package/dist/modules/fanui-badge.min.css +77 -0
- package/dist/modules/fanui-base.css +1394 -0
- package/dist/modules/fanui-base.min.css +236 -0
- package/dist/modules/fanui-blog.css +762 -0
- package/dist/modules/fanui-blog.min.css +108 -0
- package/dist/modules/fanui-button.css +526 -0
- package/dist/modules/fanui-button.min.css +56 -0
- package/dist/modules/fanui-card.css +291 -0
- package/dist/modules/fanui-card.min.css +52 -0
- package/dist/modules/fanui-choice.css +539 -0
- package/dist/modules/fanui-choice.min.css +83 -0
- package/dist/modules/fanui-dashboard.css +1057 -0
- package/dist/modules/fanui-dashboard.min.css +160 -0
- package/dist/modules/fanui-data.css +687 -0
- package/dist/modules/fanui-data.min.css +125 -0
- package/dist/modules/fanui-feedback.css +432 -0
- package/dist/modules/fanui-feedback.min.css +79 -0
- package/dist/modules/fanui-form.css +779 -0
- package/dist/modules/fanui-form.min.css +123 -0
- package/dist/modules/fanui-glass.css +497 -0
- package/dist/modules/fanui-glass.min.css +46 -0
- package/dist/modules/fanui-input-plus.css +799 -0
- package/dist/modules/fanui-input-plus.min.css +124 -0
- package/dist/modules/fanui-interactive.css +960 -0
- package/dist/modules/fanui-interactive.min.css +138 -0
- package/dist/modules/fanui-layout.css +4399 -0
- package/dist/modules/fanui-layout.min.css +1172 -0
- package/dist/modules/fanui-marketing.css +1125 -0
- package/dist/modules/fanui-marketing.min.css +144 -0
- package/dist/modules/fanui-nav.css +458 -0
- package/dist/modules/fanui-nav.min.css +76 -0
- package/dist/modules/fanui-overlay.css +547 -0
- package/dist/modules/fanui-overlay.min.css +91 -0
- package/dist/modules/fanui-table.css +290 -0
- package/dist/modules/fanui-table.min.css +62 -0
- package/dist/modules/fanui-tabs.css +333 -0
- package/dist/modules/fanui-tabs.min.css +58 -0
- package/dist/modules/fanui-themes.css +2728 -0
- package/dist/modules/fanui-themes.min.css +40 -0
- package/dist/modules/fanui-tokens.css +587 -0
- package/dist/modules/fanui-tokens.min.css +3 -0
- package/dist/modules/fanui-utilities.css +6557 -0
- package/dist/modules/fanui-utilities.min.css +1762 -0
- package/dist/modules/sizes.json +103 -0
- package/docs/MIGRATION-v2.md +179 -0
- package/examples/01-script-tag.html +149 -0
- package/examples/02-react/README.md +39 -0
- package/examples/02-react/index.html +12 -0
- package/examples/02-react/package.json +20 -0
- package/examples/02-react/src/App.jsx +108 -0
- package/examples/02-react/src/main.jsx +13 -0
- package/examples/02-react/src/useFanUI.js +87 -0
- package/examples/02-react/vite.config.js +6 -0
- package/examples/03-vue3/README.md +67 -0
- package/examples/03-vue3/index.html +12 -0
- package/examples/03-vue3/package.json +19 -0
- package/examples/03-vue3/src/App.vue +93 -0
- package/examples/03-vue3/src/main.js +8 -0
- package/examples/03-vue3/src/useFanUI.js +85 -0
- package/examples/03-vue3/vite.config.js +6 -0
- package/examples/04-vue2-cdn.html +93 -0
- package/examples/README.md +34 -0
- package/package.json +127 -0
- package/src/_functions.scss +319 -0
- package/src/_mixins.scss +1202 -0
- package/src/_tokens.scss +180 -0
- package/src/_variables.scss +491 -0
- package/src/base/_animations.scss +210 -0
- package/src/base/_reset.scss +359 -0
- package/src/base/_rtl.scss +133 -0
- package/src/base/_typography.scss +353 -0
- package/src/components/_advanced.scss +763 -0
- package/src/components/_alert.scss +322 -0
- package/src/components/_badge.scss +279 -0
- package/src/components/_blog.scss +693 -0
- package/src/components/_button.scss +351 -0
- package/src/components/_capabilities.scss +463 -0
- package/src/components/_card.scss +281 -0
- package/src/components/_choice.scss +444 -0
- package/src/components/_dashboard.scss +950 -0
- package/src/components/_data.scss +618 -0
- package/src/components/_feedback.scss +403 -0
- package/src/components/_form.scss +408 -0
- package/src/components/_glass.scss +374 -0
- package/src/components/_input-plus.scss +730 -0
- package/src/components/_interactive.scss +885 -0
- package/src/components/_marketing.scss +982 -0
- package/src/components/_nav.scss +480 -0
- package/src/components/_overlay.scss +517 -0
- package/src/components/_table.scss +276 -0
- package/src/components/_tabs.scss +338 -0
- package/src/fanui.d.ts +353 -0
- package/src/fanui.js +4782 -0
- package/src/fanui.scss +79 -0
- package/src/glass/_switches.scss +36 -0
- package/src/glass/_tokens.scss +259 -0
- package/src/layout/_container.scss +111 -0
- package/src/layout/_flex.scss +164 -0
- package/src/layout/_grid.scss +180 -0
- package/src/themes/_accents.scss +201 -0
- package/src/themes/_palettes.scss +180 -0
- package/src/utilities/_borders.scss +104 -0
- package/src/utilities/_display.scss +145 -0
- package/src/utilities/_effects.scss +210 -0
- package/src/utilities/_sizing.scss +123 -0
- package/src/utilities/_spacing.scss +118 -0
- package/src/utilities/_texture.scss +323 -0
package/USAGE.md
ADDED
|
@@ -0,0 +1,1926 @@
|
|
|
1
|
+
# fanUI 调用文档(v2.1 · 零基础开箱即用)
|
|
2
|
+
|
|
3
|
+
> 本文档的目标:**不要求任何前置知识**,照抄代码即可跑通。
|
|
4
|
+
> 遇到问题时先看 [第 8 章 常见问题](#8-常见问题排错速查),90% 的问题一句话就能解决。
|
|
5
|
+
|
|
6
|
+
**30 秒最小示例**(新建 `hello.html`,双击打开):
|
|
7
|
+
|
|
8
|
+
```html
|
|
9
|
+
<!DOCTYPE html>
|
|
10
|
+
<html lang="zh-CN" data-fanui-theme="light" data-fanui-accent="cyan" data-fanui-glass="on">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<link rel="stylesheet" href="dist/fanui.css"> <!-- ① 样式:必引 -->
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<button class="fanui-btn fanui-btn--primary">你好,fanUI</button>
|
|
17
|
+
<script src="dist/fanui.js"></script> <!-- ② 运行时:可选 -->
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 目录
|
|
25
|
+
|
|
26
|
+
- [1. 引入方式](#1-引入方式)
|
|
27
|
+
- [1.1 方式一:本地文件 `<script>` 引入(最简单,推荐小白)](#11-方式一本地文件-script-引入最简单推荐小白)
|
|
28
|
+
- [1.2 方式二:CDN 引入](#12-方式二cdn-引入)
|
|
29
|
+
- [1.3 方式三:包管理器安装 + import(Vite / webpack / CRA)](#13-方式三包管理器安装--importvite--webpack--cra)
|
|
30
|
+
- [1.4 方式四:SCSS 源码定制编译](#14-方式四scss-源码定制编译)
|
|
31
|
+
- [1.5 React 兼容说明与写法(React 16 / 17 / 18 / 19)](#15-react-兼容说明与写法react-16--17--18--19)
|
|
32
|
+
- [1.6 Vue 2 / Vue 3 兼容说明与写法](#16-vue-2--vue-3-兼容说明与写法)
|
|
33
|
+
- [1.7 其他环境(Node / SSR / Svelte / 原生 ESM)](#17-其他环境node--ssr--svelte--原生-esm)
|
|
34
|
+
- [2. 三条运行时轴:先建立全局认识](#2-三条运行时轴先建立全局认识)
|
|
35
|
+
- [3. JS API 完整参考](#3-js-api-完整参考)
|
|
36
|
+
- [4. 事件总表](#4-事件总表)
|
|
37
|
+
- [5. data-* 属性总表](#5-data--属性总表)
|
|
38
|
+
- [6. 主题色与玻璃类名速查](#6-主题色与玻璃类名速查)
|
|
39
|
+
- [7. 依赖说明与版本要求](#7-依赖说明与版本要求)
|
|
40
|
+
- [8. 常见问题(排错速查)](#8-常见问题排错速查)
|
|
41
|
+
- [9. 完整可运行示例索引](#9-完整可运行示例索引)
|
|
42
|
+
- [10. 进阶与结构组件速查](#10-进阶与结构组件速查)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 1. 引入方式
|
|
47
|
+
|
|
48
|
+
fanUI 由**两个文件**组成,引入时只需要关心它们:
|
|
49
|
+
|
|
50
|
+
| 文件 | 作用 | 是否必引 |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `dist/fanui.css`(或 `dist/fanui.min.css`) | 全部视觉:主题、组件、玻璃、工具类 | **必引** |
|
|
53
|
+
| `dist/fanui.js` | 全部交互与主题控制(`FanUI` 全局对象) | 可选;用到交互组件或 JS 换肤时才需要 |
|
|
54
|
+
|
|
55
|
+
> 判断标准:页面上只有按钮、卡片这类「纯看的东西」→ 只引 CSS;
|
|
56
|
+
> 需要模态框、Toast、切换主题色、命令面板 → 加引 JS。
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### 1.1 方式一:本地文件 `<script>` 引入(最简单,推荐小白)
|
|
61
|
+
|
|
62
|
+
**适用**:任何项目、任何框架、甚至没有框架。双击 HTML 就能跑,**不需要 npm、不需要联网**。
|
|
63
|
+
|
|
64
|
+
**第 1 步**:把 `dist/` 目录复制到你的项目里(和你的 HTML 同级或下级均可)。
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
你的项目/
|
|
68
|
+
├── index.html
|
|
69
|
+
└── dist/
|
|
70
|
+
├── fanui.css
|
|
71
|
+
├── fanui.min.css
|
|
72
|
+
└── fanui.js
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**第 2 步**:在 HTML 里写两行引入代码。注意两条路径规则:
|
|
76
|
+
|
|
77
|
+
- CSS 放在 `<head>` 里(**必须在页面内容之前**);
|
|
78
|
+
- JS 放在 `</body>` 之前(保证执行时页面元素已经存在)。
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<!DOCTYPE html>
|
|
82
|
+
<html lang="zh-CN"
|
|
83
|
+
data-fanui-theme="light"
|
|
84
|
+
data-fanui-accent="cyan"
|
|
85
|
+
data-fanui-glass="on">
|
|
86
|
+
<head>
|
|
87
|
+
<meta charset="UTF-8">
|
|
88
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
89
|
+
<title>我的页面</title>
|
|
90
|
+
|
|
91
|
+
<!-- ① 样式(必引)。开发用 fanui.css,上线可换 fanui.min.css -->
|
|
92
|
+
<link rel="stylesheet" href="dist/fanui.css">
|
|
93
|
+
</head>
|
|
94
|
+
<body>
|
|
95
|
+
|
|
96
|
+
<!-- ===== 你的内容:直接使用 fanui-* 类名 ===== -->
|
|
97
|
+
<div class="fanui-card fanui-card--glass" style="max-width:420px;padding:24px">
|
|
98
|
+
<h2 class="fanui-fs-xl fanui-fw-semibold">玻璃卡片</h2>
|
|
99
|
+
<p class="fanui-text-muted">关掉玻璃开关后,这张卡会自动退回实体表面。</p>
|
|
100
|
+
<button class="fanui-btn fanui-btn--primary"
|
|
101
|
+
onclick="FanUI.theme.setAccent('emerald')">一键换成翡翠绿</button>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- ② 运行时(可选;用到交互/主题时引入) -->
|
|
105
|
+
<script src="dist/fanui.js"></script>
|
|
106
|
+
<script>
|
|
107
|
+
// FanUI 已挂到 window 上,任何地方都可以直接用
|
|
108
|
+
FanUI.toast.success('页面就绪');
|
|
109
|
+
FanUI.studio.mount({ defaultOpen: false }); // 右下角主题工作台(可选)
|
|
110
|
+
</script>
|
|
111
|
+
</body>
|
|
112
|
+
</html>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**路径写法**(相对路径以 HTML 文件所在目录为基准):
|
|
116
|
+
|
|
117
|
+
| 你的文件位置 | 引入写法 |
|
|
118
|
+
|---|---|
|
|
119
|
+
| 与 dist 同级 | `href="dist/fanui.css"` / `src="dist/fanui.js"` |
|
|
120
|
+
| 在 dist 上一级 | `href="../dist/fanui.css"` / `src="../dist/fanui.js"` |
|
|
121
|
+
| 在子目录 pages/ 里 | `href="../dist/fanui.css"`(按层级补 `../`) |
|
|
122
|
+
|
|
123
|
+
> ⚠️ **注意**:`href` / `src` 写错路径时浏览器不会弹窗报错,只会「没样式」。排错方法:按 F12 打开开发者工具 → Console(控制台)→ 看有没有红色 `Failed to load resource`。
|
|
124
|
+
|
|
125
|
+
完整可运行版本见 [`examples/01-script-tag.html`](./examples/01-script-tag.html)(双击即跑,已通过浏览器验证)。
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### 1.2 方式二:CDN 引入
|
|
130
|
+
|
|
131
|
+
> ⚠️ **当前不可用(2026-09-21 实测核实):fanUI 尚未成功发布到 npm,本节的几条 CDN 链接一律返回 404。**
|
|
132
|
+
> 症状是页面**完全没样式、且浏览器不报错**(`<link>` 加载失败是静默的,只看到裸 HTML)。
|
|
133
|
+
> 在包正式发布之前,请改用「方式一:本地文件」或「方式三:本地安装」。
|
|
134
|
+
> 想确认包是否已发布:`npm view @zhang_jifan/fanui version`(返回 404 即尚未发布)。发布流程见 [PUBLISHING.md](./PUBLISHING.md)。
|
|
135
|
+
|
|
136
|
+
**适用**:快速验证、演示页、内网无法 npm 的环境。**需要联网**(首次会下载文件)。
|
|
137
|
+
**前提**:包已发布到 npm —— 未发布时本节写法不可用。
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<!-- jsDelivr(需包已发布) -->
|
|
141
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@zhang_jifan/fanui@2.1.0/dist/fanui.min.css">
|
|
142
|
+
<script src="https://cdn.jsdelivr.net/npm/@zhang_jifan/fanui@2.1.0/dist/fanui.js"></script>
|
|
143
|
+
|
|
144
|
+
<!-- 或 unpkg(需包已发布) -->
|
|
145
|
+
<link rel="stylesheet" href="https://unpkg.com/@zhang_jifan/fanui@2.1.0/dist/fanui.min.css">
|
|
146
|
+
<script src="https://unpkg.com/@zhang_jifan/fanui@2.1.0/dist/fanui.js"></script>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
引入后的用法与「方式一」**完全相同**(同样是全局 `FanUI` 对象)。
|
|
150
|
+
|
|
151
|
+
> 说明:`@2.1.0` 是版本号。写 `@2` 可自动跟进 2.x 小版本;正式项目建议锁定完整版本号。
|
|
152
|
+
> 若你所在环境无法访问公网 CDN,请回到「方式一」使用本地文件。
|
|
153
|
+
|
|
154
|
+
**发布前想「像 CDN 那样」引用?** 把 `dist/` 拷到任意静态服务器即可,效果与 CDN 等价:
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<!-- 例:把 fanUI/dist 整个拷到站点下的 /fanui/ 目录 -->
|
|
158
|
+
<link rel="stylesheet" href="/fanui/fanui.css">
|
|
159
|
+
<script src="/fanui/fanui.js"></script>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> 内网分发也可 `npm pack` 打出 tarball(`zhang_jifan-fanui-2.1.0.tgz`)放到制品库,
|
|
163
|
+
> 他人 `npm install ./zhang_jifan-fanui-2.1.0.tgz` 后即可按「方式三」引用 —— 这条链路正是 `npm run verify:install` 每次门禁都会实测的。
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### 1.3 方式三:包管理器安装 + import(Vite / webpack / CRA)
|
|
168
|
+
|
|
169
|
+
**适用**:使用构建工具的现代前端项目(React / Vue / Svelte / 原生 TS 均可)。
|
|
170
|
+
|
|
171
|
+
**第 1 步:安装**
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# npm(发布到 npm 源后)
|
|
175
|
+
npm install @zhang_jifan/fanui
|
|
176
|
+
|
|
177
|
+
# 或:还没发布时,直接把本仓库作为本地包安装(推荐,见 examples/)
|
|
178
|
+
npm install ../fanUI # 路径写 fanUI 仓库目录
|
|
179
|
+
# 或先打包再安装:
|
|
180
|
+
# cd fanUI && npm pack → 生成 zhang_jifan-fanui-2.1.0.tgz
|
|
181
|
+
# npm install ./zhang_jifan-fanui-2.1.0.tgz
|
|
182
|
+
|
|
183
|
+
# pnpm / yarn 同理
|
|
184
|
+
pnpm add @zhang_jifan/fanui
|
|
185
|
+
yarn add @zhang_jifan/fanui
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**第 2 步:在入口文件引入 CSS + JS**
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
// main.js / main.ts / index.js(项目入口,只写一次)
|
|
192
|
+
import "@zhang_jifan/fanui/style.css"; // 全部样式(等价于 import "@zhang_jifan/fanui/dist/fanui.css")
|
|
193
|
+
import FanUI from "@zhang_jifan/fanui"; // 交互运行时(可选)
|
|
194
|
+
|
|
195
|
+
// 用具名导入也可以(推荐 TS 项目,IDE 提示更友好)
|
|
196
|
+
// import { theme, toast, overlay } from "@zhang_jifan/fanui";
|
|
197
|
+
|
|
198
|
+
FanUI.studio.mount({ defaultOpen: false }); // 可选:主题工作台
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
> CSS 引入写法对照:
|
|
202
|
+
> - `import "@zhang_jifan/fanui/style.css"` → 开发版 CSS(推荐,报错时可读)
|
|
203
|
+
> - `import "@zhang_jifan/fanui/style.min.css"` → 压缩版 CSS(上线用)
|
|
204
|
+
|
|
205
|
+
**第 3 步(可选):在页面/组件里使用**
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<!-- 类名与方式一完全一致,不需要任何学习成本 -->
|
|
209
|
+
<button class="fanui-btn fanui-btn--primary">主要按钮</button>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
// JS 控制样式也完全一致
|
|
214
|
+
FanUI.theme.setAccent("violet");
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**各构建工具注意事项**:
|
|
218
|
+
|
|
219
|
+
| 工具 | 需要做的 | 说明 |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| Vite 4 / 5 | 无 | 开箱即用;`main.jsx`/`main.js` 顶部 import |
|
|
222
|
+
| webpack 4 / 5(含 vue-cli、CRA 5) | 无 | CSS 由 `style-loader`/`css-loader` 处理;CRA 无需 eject |
|
|
223
|
+
| Rollup | 装 `@rollup/plugin-commonjs` | 处理 CJS 分支 |
|
|
224
|
+
| Next.js / Nuxt | 见 [1.7](#17-其他环境node--ssr--svelte--原生-esm) | SSR 场景 |
|
|
225
|
+
|
|
226
|
+
**为什么 `import FanUI from "@zhang_jifan/fanui"` 能拿到对象?**
|
|
227
|
+
包内提供了三种入口,按环境自动选择(都指向同一份运行时,行为完全一致):
|
|
228
|
+
|
|
229
|
+
| 入口 | 文件 | 被谁使用 |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| ESM | `dist/fanui.esm.js` | 打包器与浏览器原生 ESM(`<script type="module">`、Vite/webpack/Rollup) |
|
|
232
|
+
| ESM(`.mjs`) | `dist/fanui.mjs` | `import`(**Node 原生 ESM**;内容与 `fanui.esm.js` 完全相同,只是后缀无歧义) |
|
|
233
|
+
| CommonJS | `dist/fanui.cjs` | `require()`(Node / 老工具链;非浏览器环境**返回服务端门面**,不是 `null`,见 [1.7](#17-其他环境node--ssr--svelte--原生-esm)) |
|
|
234
|
+
| IIFE | `dist/fanui.js` | `<script>` 标签 / CDN |
|
|
235
|
+
|
|
236
|
+
> 为什么 Node 侧要单独一个 `.mjs`?本包 `package.json` 没有 `"type": "module"`,
|
|
237
|
+
> 所以 `.js` 在 Node 眼里是 CommonJS。Node 22.7+ 能靠「模块语法探测」兜住,
|
|
238
|
+
> 但 Node 20(本包 `engines` 声明支持)会直接报
|
|
239
|
+
> `Named export 'x' not found. The requested module '@zhang_jifan/fanui' is a CommonJS module`。
|
|
240
|
+
> `.mjs` 后缀没有这个问题,`exports` 的 `import` 条件已指向它,使用者无需关心。
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### 1.4 方式四:SCSS 源码定制编译
|
|
245
|
+
|
|
246
|
+
**适用**:想改设计变量(圆角、字号、主色…)后自己编译出 CSS 的团队。
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npm install -D sass # 唯一开发依赖,要求 sass >= 1.79
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**写法 A(推荐 · dart-sass ≥ 1.79 内置包导入,无需 load-path)**
|
|
253
|
+
|
|
254
|
+
```scss
|
|
255
|
+
// my-theme.scss
|
|
256
|
+
@use "pkg:@zhang_jifan/fanui" with (
|
|
257
|
+
$fanui-radius-base: 10px, // 全局圆角
|
|
258
|
+
$fanui-font-size-root: 15px, // 根字号
|
|
259
|
+
$fanui-enable-dark-mode: true // 深色主题
|
|
260
|
+
);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
sass my-theme.scss dist/my-theme.css --no-source-map
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**写法 B(传统 load-path,任何 sass 版本通用)**
|
|
268
|
+
|
|
269
|
+
```scss
|
|
270
|
+
// my-theme.scss
|
|
271
|
+
@use "@zhang_jifan/fanui" with (
|
|
272
|
+
$fanui-radius-base: 10px,
|
|
273
|
+
$fanui-font-size-root: 15px,
|
|
274
|
+
$fanui-enable-dark-mode: true
|
|
275
|
+
);
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
sass my-theme.scss dist/my-theme.css --load-path=node_modules --no-source-map
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**按需只引一个模块**(体积最小)
|
|
283
|
+
|
|
284
|
+
```scss
|
|
285
|
+
@use "@zhang_jifan/fanui/src/components/button";
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
sass my-theme.scss dist/my-theme.css --load-path=node_modules --no-source-map
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
> **三个易踩的坑(均已实测)**
|
|
293
|
+
> 1. **Sass 的 `load-path` 不读 npm 的 `exports` 字段** —— 所以 `@use "@zhang_jifan/fanui/scss"`
|
|
294
|
+
> (exports 别名)在 Sass 里会报 `Can't find stylesheet to import`。请改用
|
|
295
|
+
> 写法 A 的 `pkg:@zhang_jifan/fanui`,或写法 B 的 `@use "@zhang_jifan/fanui"`。
|
|
296
|
+
> 2. **写法 B 必须带 `--load-path=node_modules`**,否则同样报找不到样式表。
|
|
297
|
+
> (包根提供了 `_index.scss`,所以 `@use "@zhang_jifan/fanui"` 才能被解析到。)
|
|
298
|
+
> 3. 变量名以 `src/_variables.scss` 为准 —— 共 **90 个**可配置变量。
|
|
299
|
+
> 例如根字号是 `$fanui-font-size-root`(不是 `$fanui-font-size-base`),
|
|
300
|
+
> 写错会直接报 `not declared with !default`。
|
|
301
|
+
>
|
|
302
|
+
> 上面三组命令均已实测可编译,且自定义颜色会真实生效。
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### 1.5 React 兼容说明与写法(React 16 / 17 / 18 / 19)
|
|
307
|
+
|
|
308
|
+
**结论先说:fanUI 与 React 全版本兼容,不需要任何适配包。**
|
|
309
|
+
原因是 fanUI 只有「全局 CSS + 命令式 JS」两层,不提供 React 组件,因此不存在「React 组件库常见的版本绑定问题」。
|
|
310
|
+
|
|
311
|
+
| React 版本 | 兼容 | 你需要做的 |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| React 19 | ✅ | 与 18 完全一致 |
|
|
314
|
+
| React 18 | ✅ | `createRoot` 挂载;`StrictMode` 双调用安全(`FanUI.init()` 幂等) |
|
|
315
|
+
| React 17 | ✅ | 入口改回 `ReactDOM.render(<App/>, root)` |
|
|
316
|
+
| React 16.8+ | ✅ | 同 17;可用官方 Hook 写法 |
|
|
317
|
+
| React < 16.8 | ✅ | 不用 Hook,改在 `componentDidMount` 调 `FanUI.init()` |
|
|
318
|
+
|
|
319
|
+
**React 18 完整可复制示例**(`main.jsx`):
|
|
320
|
+
|
|
321
|
+
```jsx
|
|
322
|
+
import React from "react";
|
|
323
|
+
import ReactDOM from "react-dom/client";
|
|
324
|
+
import App from "./App.jsx";
|
|
325
|
+
import "@zhang_jifan/fanui/style.css"; // ① 全局样式,只引一次
|
|
326
|
+
|
|
327
|
+
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
328
|
+
<React.StrictMode>
|
|
329
|
+
<App />
|
|
330
|
+
</React.StrictMode>
|
|
331
|
+
);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**组件内使用**(`App.jsx`,可直接复制):
|
|
335
|
+
|
|
336
|
+
```jsx
|
|
337
|
+
import { useEffect, useState } from "react";
|
|
338
|
+
import FanUI from "@zhang_jifan/fanui";
|
|
339
|
+
|
|
340
|
+
export default function App() {
|
|
341
|
+
const [snap, setSnap] = useState(null);
|
|
342
|
+
|
|
343
|
+
useEffect(() => {
|
|
344
|
+
FanUI.init(); // ② 初始化声明式组件(幂等)
|
|
345
|
+
setSnap(FanUI.theme.snapshot());
|
|
346
|
+
const sync = () => setSnap(FanUI.theme.snapshot());
|
|
347
|
+
["fanui:accentchange", "fanui:themechange", "fanui:glasschange"]
|
|
348
|
+
.forEach((ev) => document.addEventListener(ev, sync));
|
|
349
|
+
return () =>
|
|
350
|
+
["fanui:accentchange", "fanui:themechange", "fanui:glasschange"]
|
|
351
|
+
.forEach((ev) => document.removeEventListener(ev, sync));
|
|
352
|
+
}, []);
|
|
353
|
+
|
|
354
|
+
return (
|
|
355
|
+
<main style={{ padding: 32 }}>
|
|
356
|
+
{/* ③ 类名照抄官方文档:className 而不是 class */}
|
|
357
|
+
<button className="fanui-btn fanui-btn--primary"
|
|
358
|
+
onClick={() => FanUI.theme.setAccent("emerald")}>
|
|
359
|
+
换翡翠绿
|
|
360
|
+
</button>
|
|
361
|
+
<p>当前主题:{snap ? snap.accent : "加载中"}</p>
|
|
362
|
+
</main>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**三个 React 专属注意点**:
|
|
368
|
+
|
|
369
|
+
1. `class` 要写成 `className`;自闭合标签要加 `/`(如 `<div className="fanui-modal__backdrop" />`)。
|
|
370
|
+
2. **受控组件与 fanUI 增强控件并存时**:`.fanui-tag-input`、`.fanui-combobox` 这类增强控件内部直接操作 DOM。
|
|
371
|
+
若需要把值接回 React state,监听对应事件即可(如 `fanui:tagschange`),不要试图用 `value` 双向绑定它们。
|
|
372
|
+
3. **React 18 StrictMode** 开发模式下 effect 会执行两次 → `FanUI.init()` 内部有 `__fanuiInit` 防重复标记,**不会**重复绑定事件,可放心使用。
|
|
373
|
+
|
|
374
|
+
完整可运行项目见 [`examples/02-react/`](./examples/02-react/)(含 `useFanUI` / `useFanTheme` / `useFanOverlay` / `useToast` 四个现成 Hook)。
|
|
375
|
+
|
|
376
|
+
### 1.6 Vue 2 / Vue 3 兼容说明与写法
|
|
377
|
+
|
|
378
|
+
**结论先说:fanUI 与 Vue 2、Vue 3 都完全兼容**,因为 fanUI 的类名是普通 class、JS 是命令式 API,
|
|
379
|
+
Vue 的模板编译与响应式不会干扰它们。两个版本唯一的差异是「初始化调用放哪」。
|
|
380
|
+
|
|
381
|
+
**Vue 3 · `<script setup>` 写法**(`App.vue`,可直接复制):
|
|
382
|
+
|
|
383
|
+
```vue
|
|
384
|
+
<script setup>
|
|
385
|
+
import { ref, onMounted, onBeforeUnmount } from "vue";
|
|
386
|
+
import FanUI from "@zhang_jifan/fanui"; // 或全局引入后用 window.FanUI
|
|
387
|
+
|
|
388
|
+
const accent = ref("cyan");
|
|
389
|
+
|
|
390
|
+
onMounted(() => {
|
|
391
|
+
FanUI.init(); // 初始化声明式组件(幂等)
|
|
392
|
+
FanUI.studio.mount({ defaultOpen: false }); // 可选:主题工作台
|
|
393
|
+
document.addEventListener("fanui:accentchange", sync);
|
|
394
|
+
});
|
|
395
|
+
onBeforeUnmount(() => {
|
|
396
|
+
document.removeEventListener("fanui:accentchange", sync);
|
|
397
|
+
});
|
|
398
|
+
function sync() { accent.value = FanUI.theme.getAccent(); }
|
|
399
|
+
function setAccent(k) { FanUI.theme.setAccent(k); }
|
|
400
|
+
</script>
|
|
401
|
+
|
|
402
|
+
<template>
|
|
403
|
+
<!-- 模板里 class 照抄官方文档即可 -->
|
|
404
|
+
<button class="fanui-btn fanui-btn--primary" @click="setAccent('emerald')">翡翠绿</button>
|
|
405
|
+
<p>当前主题色:{{ accent }}</p>
|
|
406
|
+
</template>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Vue 3 · main.js 入口**:
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
import { createApp } from "vue";
|
|
413
|
+
import App from "./App.vue";
|
|
414
|
+
import "@zhang_jifan/fanui/style.css"; // 全局样式,只引一次
|
|
415
|
+
|
|
416
|
+
createApp(App).mount("#app");
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Vue 2.6 / 2.7 · Options API 写法**:
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
// main.js
|
|
423
|
+
import Vue from "vue";
|
|
424
|
+
import App from "./App.vue";
|
|
425
|
+
import "@zhang_jifan/fanui/style.css";
|
|
426
|
+
|
|
427
|
+
new Vue({
|
|
428
|
+
mounted() {
|
|
429
|
+
if (window.FanUI) window.FanUI.init(); // 初始化声明式组件
|
|
430
|
+
},
|
|
431
|
+
render: (h) => h(App),
|
|
432
|
+
}).$mount("#app");
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
```html
|
|
436
|
+
<!-- 组件里 -->
|
|
437
|
+
<template>
|
|
438
|
+
<button class="fanui-btn fanui-btn--primary"
|
|
439
|
+
@click="$root && FanUI.theme.setAccent('emerald')">翡翠绿</button>
|
|
440
|
+
</template>
|
|
441
|
+
<script>
|
|
442
|
+
export default {
|
|
443
|
+
computed: { FanUI: () => window.FanUI }, // 全局对象挂到计算属性,模板可用
|
|
444
|
+
};
|
|
445
|
+
</script>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**两个 Vue 专属注意点**:
|
|
449
|
+
|
|
450
|
+
1. **不要把 fanUI 的增强控件放进 `v-if` 频繁切换后指望自动重新初始化**:
|
|
451
|
+
`v-if` 销毁重建 DOM 后,需要对新节点再调一次 `FanUI.init(container)`(传入容器元素即可,内部有防重复标记,安全)。
|
|
452
|
+
用 `v-show` 则无此问题。
|
|
453
|
+
2. **`<style scoped>` 不影响 fanUI**:scoped 只给当前组件元素加 `data-v-xxx` 属性,fanUI 类名照常生效;
|
|
454
|
+
但若你要在 scoped 样式里覆盖 fanUI 的内部元素,需要用 `:deep(.fanui-xxx)`(Vue 3)或 `::v-deep`(Vue 2)。
|
|
455
|
+
|
|
456
|
+
完整可运行项目见 [`examples/03-vue3/`](./examples/03-vue3/) 与 [`examples/04-vue2-cdn.html`](./examples/04-vue2-cdn.html)。
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
### 1.7 其他环境(Node / SSR / Svelte / 原生 ESM)
|
|
461
|
+
|
|
462
|
+
**一条规则**:fanUI 的**交互能力**需要浏览器 DOM,但**引入本身永远安全**。
|
|
463
|
+
|
|
464
|
+
在 Node / SSR(服务端)下,`require("@zhang_jifan/fanui")` / `import FanUI from "@zhang_jifan/fanui"` **不会报错,也不再返回 `null`**,
|
|
465
|
+
而是返回一个「**服务端门面**」:
|
|
466
|
+
|
|
467
|
+
| 门面字段 | 说明 |
|
|
468
|
+
|---|---|
|
|
469
|
+
| `FanUI.isServer === true` | 明确标识当前运行在服务端 |
|
|
470
|
+
| `FanUI.serverRenderable === true` | 支持服务端直出 |
|
|
471
|
+
| `FanUI.ssr.*` | **服务端渲染器**,直接产出组件标记 |
|
|
472
|
+
| `FanUI.theme` / `modal` / `toast` / `studio` / `lightbox` / … | 浏览器专属命名空间退化为**空实现**:调用不抛错,只 `console.warn` 一次并提示改用客户端生命周期;可用 `FanUI.theme.__isServerStub === true` 判断 |
|
|
473
|
+
|
|
474
|
+
#### 1.7.1 服务端直出(推荐)
|
|
475
|
+
|
|
476
|
+
把首屏标记在服务端生成,SEO 与首屏速度都更好。
|
|
477
|
+
|
|
478
|
+
```jsx
|
|
479
|
+
// Next.js App Router:app/layout.js(服务端组件即可,无需 "use client")
|
|
480
|
+
import FanUI from "@zhang_jifan/fanui";
|
|
481
|
+
import "@zhang_jifan/fanui/style.css";
|
|
482
|
+
|
|
483
|
+
export default function RootLayout({ children }) {
|
|
484
|
+
return (
|
|
485
|
+
<html lang="zh-CN" data-fanui-accent="cyan" data-fanui-theme="auto">
|
|
486
|
+
<body>
|
|
487
|
+
{children}
|
|
488
|
+
{/* 服务端产出「防首屏主题闪烁(FOUC)」片段 */}
|
|
489
|
+
<script
|
|
490
|
+
dangerouslySetInnerHTML={{
|
|
491
|
+
__html: FanUI.ssr.themeBootstrap({ accent: "cyan", theme: "auto" }),
|
|
492
|
+
}}
|
|
493
|
+
/>
|
|
494
|
+
</body>
|
|
495
|
+
</html>
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
```jsx
|
|
501
|
+
// 任意服务端组件:直接产出组件标记
|
|
502
|
+
import FanUI from "@zhang_jifan/fanui";
|
|
503
|
+
|
|
504
|
+
export default function OrderList() {
|
|
505
|
+
const markup = FanUI.ssr.render("virtualList", {
|
|
506
|
+
items: ["订单 A", "订单 B", "订单 C"],
|
|
507
|
+
itemHeight: 44,
|
|
508
|
+
label: "订单列表",
|
|
509
|
+
});
|
|
510
|
+
return <div dangerouslySetInnerHTML={{ __html: markup }} />;
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
`FanUI.ssr` 的完整能力:
|
|
515
|
+
|
|
516
|
+
| 方法 | 用途 |
|
|
517
|
+
|---|---|
|
|
518
|
+
| `render(name, props)` | 渲染单个组件;未知组件会抛错并列出可用名 |
|
|
519
|
+
| `renderList([["badge", { text: "新" }], …])` | 批量渲染 |
|
|
520
|
+
| `escape(v)` | HTML 转义(所有插值已自动转义,防注入) |
|
|
521
|
+
| `htmlAttrs({ accent, theme, glass, lang, dir })` | 生成 `<html>` 主题属性,如 ` data-fanui-accent="cyan" data-fanui-theme="dark"` |
|
|
522
|
+
| `document({ title, description, css, js, accent, theme, glass, bodyHtml })` | 直出完整 HTML 骨架(演示页 / 静态站 / 首屏) |
|
|
523
|
+
| `themeBootstrap({ accent, theme })` | 防首屏主题闪烁(FOUC)的脚本片段 |
|
|
524
|
+
| `components` | 支持的服务端渲染组件名数组(24 个) |
|
|
525
|
+
|
|
526
|
+
> **直出内容是真的可见**:`ssr.render("virtualList", …)` 产出的行在**没有 JS** 时就是可读的普通流式内容,
|
|
527
|
+
> 客户端 `FanUI.init()` 之后再切换为虚拟化渲染(渐进增强)。
|
|
528
|
+
> 门禁 `node scripts/verify-ssr.cjs` 会真实断言「无 JS 时直出元素 `offsetHeight > 0`」。
|
|
529
|
+
|
|
530
|
+
#### 1.7.2 客户端补挂交互
|
|
531
|
+
|
|
532
|
+
交互(弹窗、命令面板、灯箱等)仍需在浏览器里初始化:
|
|
533
|
+
|
|
534
|
+
```jsx
|
|
535
|
+
// components/ThemePanel.jsx
|
|
536
|
+
"use client"; // App Router 必须声明客户端组件
|
|
537
|
+
import { useEffect } from "react";
|
|
538
|
+
import FanUI from "@zhang_jifan/fanui";
|
|
539
|
+
|
|
540
|
+
export default function ThemePanel() {
|
|
541
|
+
useEffect(() => {
|
|
542
|
+
FanUI.init(); // 服务端是空实现不会崩,浏览器里才真正生效
|
|
543
|
+
FanUI.studio.mount({ defaultOpen: false });
|
|
544
|
+
}, []);
|
|
545
|
+
return (
|
|
546
|
+
<button className="fanui-btn fanui-btn--primary"
|
|
547
|
+
onClick={() => FanUI.theme.toggleMode()}>深浅切换</button>
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
#### 1.7.3 环境对照
|
|
553
|
+
|
|
554
|
+
| 环境 | 样式引入 | JS 引入 | 说明 |
|
|
555
|
+
|---|---|---|---|
|
|
556
|
+
| Next.js(App Router) | `app/layout.js` 顶部 `import "@zhang_jifan/fanui/style.css"` | 服务端用 `FanUI.ssr` 直出;交互在客户端组件 `useEffect` 里 `FanUI.init()` | 两端都能安全 import,不再是 `null` |
|
|
557
|
+
| Next.js(Pages Router) | `pages/_app.js` 顶部 import | 同上 | |
|
|
558
|
+
| Nuxt 3 | `nuxt.config.ts` → `css: ["@zhang_jifan/fanui/style.css"]` | `plugins/fanui.client.ts` | 插件加 `.client` 后缀只在浏览器执行 |
|
|
559
|
+
| Nuxt 2 | `nuxt.config.js` → `css: ["@zhang_jifan/fanui/style.css"]` | 同上 | |
|
|
560
|
+
| Svelte / Solid | `import "@zhang_jifan/fanui/style.css"` | `import FanUI from "@zhang_jifan/fanui"` | 用法与 React / Vue 相同 |
|
|
561
|
+
| 原生 ESM(浏览器) | `<link rel="stylesheet" href="…/fanui.css">` | `<script type="module">import FanUI from "…/fanui.esm.js"` | 需经静态服务器打开 |
|
|
562
|
+
| Node 脚本(无浏览器) | — | `require("@zhang_jifan/fanui")` → 服务端门面(`isServer: true`) | 可在 CI 里生成静态 HTML |
|
|
563
|
+
|
|
564
|
+
**浏览器原生 ESM**(无构建工具,注意 `type="module"`):
|
|
565
|
+
|
|
566
|
+
```html
|
|
567
|
+
<link rel="stylesheet" href="./node_modules/@zhang_jifan/fanui/dist/fanui.css">
|
|
568
|
+
<script type="module">
|
|
569
|
+
import FanUI from "./node_modules/@zhang_jifan/fanui/dist/fanui.esm.js";
|
|
570
|
+
FanUI.toast.success("原生 ESM 也能跑");
|
|
571
|
+
</script>
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
> ⚠️ 原生 ESM 走 `file://` 双击打开会被浏览器 CORS 拦截。请用任意静态服务器打开,例如:
|
|
575
|
+
> `npx serve .` 或 `python -m http.server`,然后访问 `http://localhost:3000`。
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## 2. 三条运行时轴:先建立全局认识
|
|
580
|
+
|
|
581
|
+
fanUI v2 的「样式控制」全部围绕**三条互相独立的轴**展开。它们既可以写在 HTML 根元素上(声明式),
|
|
582
|
+
也可以用 JS 在任意时刻切换(命令式),**即时生效、无需刷新、无需重新编译**:
|
|
583
|
+
|
|
584
|
+
| 轴 | HTML 属性 | JS 方法 | 取值 | 默认 |
|
|
585
|
+
|---|---|---|---|---|
|
|
586
|
+
| 主题色 | `data-fanui-accent` | `FanUI.theme.setAccent(name)` | `cyan sky blue indigo violet purple pink rose red orange emerald teal`(12 选 1,或自定义名) | `cyan` |
|
|
587
|
+
| 深浅模式 | `data-fanui-theme` | `FanUI.theme.setMode(mode)` | `light` `dark` `auto`(auto 跟随系统) | `light` |
|
|
588
|
+
| 液态玻璃 | `data-fanui-glass` | `FanUI.theme.setGlass(bool)` | `on` `off` | `on` |
|
|
589
|
+
|
|
590
|
+
```html
|
|
591
|
+
<!-- 声明式:写在 <html> 或任意子树的根元素上 -->
|
|
592
|
+
<html data-fanui-theme="dark" data-fanui-accent="violet" data-fanui-glass="on">
|
|
593
|
+
|
|
594
|
+
<!-- 子树级主题:只影响这个区块 -->
|
|
595
|
+
<section data-fanui-accent="rose">这一块是玫红主题</section>
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
```js
|
|
599
|
+
// 命令式:任何时刻、任何地方
|
|
600
|
+
FanUI.theme.setMode("dark");
|
|
601
|
+
FanUI.theme.setAccent("emerald");
|
|
602
|
+
FanUI.theme.setGlass(false);
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
三个补充开关(同样即时生效):
|
|
606
|
+
|
|
607
|
+
```js
|
|
608
|
+
FanUI.theme.setGlassIntensity(1.6); // 液态强度 0 ~ 2,默认 1
|
|
609
|
+
FanUI.theme.setGlassPreset("thick"); // 厚度预设 thin/regular/thick/ultra/lens
|
|
610
|
+
FanUI.theme.createAccent("#ff6b35"); // 任意品牌色 → 一套合规主题(自动保证对比度)
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
用户的选择会自动写入 `localStorage`(键名 `fanui-prefs`),刷新后自动恢复;
|
|
614
|
+
`localStorage` 不可用(如隐私模式)时静默降级,功能不受影响。
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## 3. JS API 完整参考
|
|
619
|
+
|
|
620
|
+
所有方法都挂在全局对象 `FanUI` 上(`<script>` 引入后是 `window.FanUI`;ESM 下 `import FanUI from "@zhang_jifan/fanui"`)。
|
|
621
|
+
|
|
622
|
+
**通用约定**(适用于全部 API):
|
|
623
|
+
|
|
624
|
+
- 命名空间:`FanUI.模块.方法()`,例如 `FanUI.theme.setAccent("cyan")`。
|
|
625
|
+
- 返回值:多数设置类方法会**返回设置后的值**,方便链式判断。
|
|
626
|
+
- 事件:fanUI 把事件派发到 `document` 上(个别组件派发到自身元素),用 `document.addEventListener("事件名", 回调)` 监听,参数在 `event.detail` 里。
|
|
627
|
+
- 幂等:所有 `init(scope)` 方法内部都有防重复标记,重复调用无副作用。
|
|
628
|
+
- 空值安全:传入不存在的选择器/元素时**静默返回**(不抛错),方便与前端框架共存。
|
|
629
|
+
|
|
630
|
+
### 3.1 `FanUI.theme` —— 主题三轴控制中枢(核心)
|
|
631
|
+
|
|
632
|
+
#### 3.1.1 主题色(12 选 1 + 自定义)
|
|
633
|
+
|
|
634
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
635
|
+
|---|---|---|---|
|
|
636
|
+
| `getAccent()` | 无 | `string` | 当前主题色名,如 `"cyan"` |
|
|
637
|
+
| `setAccent(name)` | `name`:主题色名。12 个内置值之一,或 `createAccent` 生成的自定义名。传空/不传时不做任何改变 | `string` 生效后的主题色名 | **切换主题色**。同时做 4 件事:① 在 `<html>` 上写 `data-fanui-accent`;② 写入 localStorage;③ 同步浏览器地址栏颜色(`meta[name=theme-color]`);④ 派发 `fanui:accentchange` |
|
|
638
|
+
| `nextAccent(step)` | `step`(可选,数字,默认 `1`):往后跳几个;传 `-1` 即上一个 | `string` 新主题色名 | 在「12 内置 + 已创建的自定义」列表里循环切换 |
|
|
639
|
+
| `readAccentColors(name, mode)` | `name`(可选):主题色名,默认当前;`mode`(可选):`"light"` / `"dark"`,默认当前生效模式 | `{ primary, primaryFg, vivid, vivid2, accent, accentVivid }` | 读取某主题在指定模式下的**实际色值**(直接从 CSS 计算样式读取,保证与所见一致)。常用于取色画 logo、生成图表配色 |
|
|
640
|
+
| `accents` | 属性,非方法 | `Array<{key, label, en, hue, vibe, secondary}>` | 12 套内置主题的元数据。`key` 用于 `setAccent`;`label/en` 是中英文名;`vibe` 是气质描述(如「科技 · 清爽」)。渲染主题选择器的数据源 |
|
|
641
|
+
| `customAccents` | 属性,非方法 | `Array<{key, label, en, hue, vibe, custom}>` | 由 `createAccent` 生成的自定义主题列表 |
|
|
642
|
+
|
|
643
|
+
**12 个内置主题色一览**(`key` 即 `setAccent` 的参数):
|
|
644
|
+
|
|
645
|
+
| key | 名称 | 气质 | key | 名称 | 气质 |
|
|
646
|
+
|---|---|---|---|---|---|
|
|
647
|
+
| `cyan` | 青 Cyan | 科技 · 清爽 | `rose` | 玫红 Rose | 浪漫 · 亲和 |
|
|
648
|
+
| `sky` | 天蓝 Sky | 轻盈 · 开放 | `red` | 赤红 Red | 强烈 · 行动 |
|
|
649
|
+
| `blue` | 经典蓝 Blue | 稳健 · 通用 | `orange` | 橙 Orange | 活力 · 温暖 |
|
|
650
|
+
| `indigo` | 靛蓝 Indigo | 专业 · 前沿 | `emerald` | 翡翠 Emerald | 成长 · 健康 |
|
|
651
|
+
| `violet` | 紫罗兰 Violet | 创意 · 智能 | `teal` | 青碧 Teal | 沉静 · 高效 |
|
|
652
|
+
| `purple` | 紫 Purple | 艺术 · 张扬 | `pink` | 品粉 Pink | 年轻 · 潮流 |
|
|
653
|
+
|
|
654
|
+
```js
|
|
655
|
+
// 例 1:切换并拿到结果
|
|
656
|
+
const now = FanUI.theme.setAccent("emerald"); // now === "emerald"
|
|
657
|
+
|
|
658
|
+
// 例 2:渲染 12 个色卡按钮
|
|
659
|
+
FanUI.theme.accents.forEach(a => {
|
|
660
|
+
const btn = document.createElement("button");
|
|
661
|
+
btn.textContent = a.label + " " + a.en;
|
|
662
|
+
btn.onclick = () => FanUI.theme.setAccent(a.key);
|
|
663
|
+
document.body.appendChild(btn);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// 例 3:取当前主题在深色模式下的主色,喂给 canvas 图表
|
|
667
|
+
const c = FanUI.theme.readAccentColors(null, "dark");
|
|
668
|
+
chart.setColor(c.primary); // 如 "#06b6d4"
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
#### 3.1.2 深浅模式
|
|
672
|
+
|
|
673
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
674
|
+
|---|---|---|---|
|
|
675
|
+
| `getMode()` | 无 | `"light" \| "dark" \| "auto"` | 用户设置的模式(auto 原样返回) |
|
|
676
|
+
| `getEffectiveMode()` | 无 | `"light" \| "dark"` | **实际生效**的模式:把 `auto` 按 `prefers-color-scheme` 解析后的结果 |
|
|
677
|
+
| `setMode(mode)` | `mode`:`"light"` / `"dark"` / `"auto"`。传其他值时回退为 `"light"` | `string` 生效后的模式 | 设置模式 + 写属性 + 持久化 + 同步地址栏颜色 + 派发 `fanui:themechange` |
|
|
678
|
+
| `toggleMode()` | 无 | `string` | 在 light / dark 之间切换(当前是 auto 时按实际模式取反) |
|
|
679
|
+
| `cycleMode()` | 无 | `string` | 按 light → dark → auto → light 轮换 |
|
|
680
|
+
|
|
681
|
+
```js
|
|
682
|
+
FanUI.theme.setMode("dark"); // 切深色
|
|
683
|
+
FanUI.theme.setMode("auto"); // 跟随系统(白天浅色、晚上深色)
|
|
684
|
+
console.log(FanUI.theme.getMode()); // "auto"
|
|
685
|
+
console.log(FanUI.theme.getEffectiveMode()); // "dark"(此刻系统是深色)
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
#### 3.1.3 液态玻璃(开关 / 强度 / 预设)
|
|
689
|
+
|
|
690
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
691
|
+
|---|---|---|---|
|
|
692
|
+
| `isGlassEnabled()` | 无 | `boolean` | 玻璃是否开启 |
|
|
693
|
+
| `setGlass(enabled)` | `enabled`:`true` 开 / `false` 关。传 `0`/`null`/`undefined` 等假值按 `false` 处理 | `boolean` | **总开关**。关闭后所有玻璃(含卡片/导航/弹窗的玻璃形态)自动退回实体表面,布局不变 |
|
|
694
|
+
| `toggleGlass()` | 无 | `boolean` | 开 ↔ 关 |
|
|
695
|
+
| `getGlassIntensity()` | 无 | `number` | 当前液态强度(0 ~ 2) |
|
|
696
|
+
| `setGlassIntensity(value)` | `value`:数字,**0 ~ 2**。超出范围会被截断到边界;非数字按 1 处理 | `number` | **液态强度**:一个旋钮统一驱动模糊半径、底色透明度、饱和度、边缘浓度、高光与投影。与预设的基准强度**相乘**,所以「先调预设再调强度」互不覆盖 |
|
|
697
|
+
| `getGlassPreset()` | 无 | `string` | 当前预设名 |
|
|
698
|
+
| `setGlassPreset(name)` | `name`:`"thin"` / `"regular"` / `"thick"` / `"ultra"` / `"lens"`。传未知值回退 `"regular"` | `string` | **厚度预设**,见下表 |
|
|
699
|
+
| `glassPresets` | 属性,非方法 | `{ thin: {...}, regular: {...}, thick: {...}, ultra: {...}, lens: {...} }` | 预设详情(label / intensity / blur / tintBase / tintK / edge / specular),用于自渲染选择器 |
|
|
700
|
+
|
|
701
|
+
**5 个预设的含义**(`intensity` 为基准强度,会再乘上你设置的 `glassIntensity`):
|
|
702
|
+
|
|
703
|
+
| 预设 | 基准强度 | 模糊 | 适合场景 |
|
|
704
|
+
|---|---|---|---|
|
|
705
|
+
| `thin` 薄 | 0.5 | 10px | 信息条、工具条(轻通透) |
|
|
706
|
+
| `regular` 标准 | 1 | 22px | 卡片、导航(默认) |
|
|
707
|
+
| `thick` 厚 | 1.45 | 30px | 面板、强调区域 |
|
|
708
|
+
| `ultra` 超厚 | 1.9 | 44px | 全屏遮罩、沉浸弹窗 |
|
|
709
|
+
| `lens` 透镜 | 2 | 36px | 透镜特化(高折射、低底色) |
|
|
710
|
+
|
|
711
|
+
```js
|
|
712
|
+
FanUI.theme.setGlass(true); // 打开
|
|
713
|
+
FanUI.theme.setGlassPreset("lens"); // 透镜质感
|
|
714
|
+
FanUI.theme.setGlassIntensity(1.5); // 再浓 50%
|
|
715
|
+
FanUI.theme.setGlass(false); // 全部关掉 → 实体表面
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
#### 3.1.4 任意品牌色 → 一套合规主题
|
|
719
|
+
|
|
720
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
721
|
+
|---|---|---|---|
|
|
722
|
+
| `createAccent(color, name)` | `color`:**必填**,`"#rrggbb"` 或 `"#rgb"` 十六进制色;`name`(可选):自定义主题名,不传则按色相自动生成(如 `"custom-25"`) | `string` 新主题名 | **把品牌色变成第 13 套主题**。内部流程:① 按固定明度阶梯生成 11 阶色板;② 在候选阶中自动挑选「与白字对比度 ≥ 4.5:1」的阶作为实心主色(浅色模式 600→700→800→900→500,深色模式 500→400→600→300);③ 派生 hover/active/soft/border/ink/ring/glow/渐变 全套角色令牌并注入 `<style>`;④ 注册进主题列表。**生成即合规**,深浅两种模式都自动达标 |
|
|
723
|
+
|
|
724
|
+
```js
|
|
725
|
+
const name = FanUI.theme.createAccent("#0aa2c0", "brand"); // → "brand"
|
|
726
|
+
FanUI.theme.setAccent(name); // 全组件立即换装
|
|
727
|
+
console.log(FanUI.theme.customAccents); // 已创建的自定义主题
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
#### 3.1.5 状态管理与生命周期
|
|
731
|
+
|
|
732
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
733
|
+
|---|---|---|---|
|
|
734
|
+
| `snapshot()` | 无 | `{ accent, mode, effectiveMode, glass, glassIntensity, glassPreset }` | 当前全部主题状态,适合存档/上报/调试 |
|
|
735
|
+
| `config(opts)` | `opts`:`{ accent, mode, glass, glassPreset, glassIntensity }`,全部可省 | `snapshot()` 结果 | **一次性设置多项**,只触发必要的事件 |
|
|
736
|
+
| `restore()` | 无 | 恢复出的偏好对象 | 从 localStorage 恢复偏好。**页面加载时自动调用**,一般无需手动调。恢复优先级:localStorage > HTML 根属性 > 默认值 |
|
|
737
|
+
| `reset()` | 无 | `snapshot()` 结果 | 清除持久化偏好并恢复出厂:cyan + light + 玻璃开 + regular + 强度 1 |
|
|
738
|
+
| `get()` / `set(mode)` / `toggle()` | 同 v1 | — | v1 兼容别名,等价于 `getMode` / `setMode` / `toggleMode` |
|
|
739
|
+
|
|
740
|
+
```js
|
|
741
|
+
FanUI.theme.config({ accent: "violet", mode: "dark", glassPreset: "thick", glassIntensity: 1.4 });
|
|
742
|
+
console.log(FanUI.theme.snapshot());
|
|
743
|
+
// { accent:"violet", mode:"dark", effectiveMode:"dark", glass:true, glassIntensity:1.4, glassPreset:"thick" }
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### 3.2 `FanUI.studio` —— 主题工作台(可视化换肤面板)
|
|
747
|
+
|
|
748
|
+
一个开箱即用的**悬浮主题面板**:12 色卡、深浅分段、玻璃开关 + 强度滑杆 + 5 预设、当前令牌预览、恢复默认。
|
|
749
|
+
交给非开发同学做「所见即所得」换肤非常好用。
|
|
750
|
+
|
|
751
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
752
|
+
|---|---|---|---|
|
|
753
|
+
| `mount(opts)` | `opts`(可选对象):`{ position: "right" \| "left", defaultOpen: boolean }`。`position` 面板停靠边,默认右下;`defaultOpen` 是否挂载即展开,默认 `false` | `{ el, isOpen, open, close, toggle, destroy, refresh }` | 挂载工作台。**同一页面只挂一次**,重复调用直接返回已挂载实例 |
|
|
754
|
+
| `api()` | 无 | 同上(未挂载时 `null`) | 获取当前实例 |
|
|
755
|
+
|
|
756
|
+
实例方法:
|
|
757
|
+
|
|
758
|
+
| 方法 | 说明 |
|
|
759
|
+
|---|---|
|
|
760
|
+
| `open()` / `close()` / `toggle()` / `isOpen()` | 展开与收起面板 |
|
|
761
|
+
| `refresh()` | 手动刷新面板显示(一般不用,主题变化时自动同步) |
|
|
762
|
+
| `destroy()` | 移除面板 DOM 与事件(单页应用路由切换时调用) |
|
|
763
|
+
|
|
764
|
+
**内置快捷键**:`Alt + T` 开合面板;`Alt + D` 切换深浅模式。
|
|
765
|
+
|
|
766
|
+
```js
|
|
767
|
+
FanUI.studio.mount(); // 默认右下角、默认收起
|
|
768
|
+
FanUI.studio.mount({ defaultOpen: true }); // 挂载即展开
|
|
769
|
+
FanUI.studio.api().open(); // 程序化打开
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### 3.3 浮层:`FanUI.overlay` 与 `FanUI.modal`
|
|
773
|
+
|
|
774
|
+
`FanUI.overlay(selector, options)` 创建一个**可编程控制的浮层实例**(模态框 / 抽屉 / 底部面板通用)。
|
|
775
|
+
|
|
776
|
+
**参数**:
|
|
777
|
+
|
|
778
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
779
|
+
|---|---|---|---|
|
|
780
|
+
| `selector` | `string` 或 `HTMLElement` | ✅ | 浮层根元素,如 `"#loginModal"`。**必须是** `.fanui-modal` / `.fanui-drawer` / `.fanui-sheet` 这类结构 |
|
|
781
|
+
| `options` | `object` | ❌ | 配置项,见下表 |
|
|
782
|
+
|
|
783
|
+
**options 全部字段**(都有默认值,可整包省略):
|
|
784
|
+
|
|
785
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
786
|
+
|---|---|---|---|
|
|
787
|
+
| `closeOnBackdrop` | `boolean` | `true` | 点击遮罩是否关闭。设 `false` 可强制用户点按钮(表单防误触) |
|
|
788
|
+
| `closeOnEsc` | `boolean` | `true` | 按 Esc 是否关闭 |
|
|
789
|
+
| `lockScroll` | `boolean` | `true` | 打开时是否锁定页面滚动(内部用引用计数,多层浮层嵌套也正确) |
|
|
790
|
+
| `onOpen(rootEl)` | `function` | 无 | 打开后的回调,参数是浮层根元素 |
|
|
791
|
+
| `onClose(rootEl)` | `function` | 无 | 关闭后的回调 |
|
|
792
|
+
|
|
793
|
+
**实例方法**:
|
|
794
|
+
|
|
795
|
+
| 方法 | 返回 | 说明 |
|
|
796
|
+
|---|---|---|
|
|
797
|
+
| `open()` | 实例(可链式) | 打开浮层:加 `is-open`、写 `aria-hidden="false"`、锁滚动、记录并恢复焦点、派发 `fanui:open` |
|
|
798
|
+
| `close()` | 实例 | 关闭:解锁滚动、焦点还给触发元素、派发 `fanui:close` |
|
|
799
|
+
| `toggle()` | 实例 | 开 ↔ 关 |
|
|
800
|
+
| `isOpen()` | `boolean` | 当前是否打开 |
|
|
801
|
+
|
|
802
|
+
```js
|
|
803
|
+
const modal = FanUI.overlay("#loginModal", {
|
|
804
|
+
closeOnBackdrop: false, // 登录框不允许点遮罩关闭
|
|
805
|
+
onOpen: (el) => el.querySelector("input").focus(),
|
|
806
|
+
});
|
|
807
|
+
modal.open();
|
|
808
|
+
// 稍后……
|
|
809
|
+
modal.close();
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
**声明式用法(零 JS)**:给任意元素加触发属性即可,运行时会自动绑定:
|
|
813
|
+
|
|
814
|
+
```html
|
|
815
|
+
<button data-fanui-modal="#loginModal">登录</button> <!-- 打开模态框 -->
|
|
816
|
+
<button data-fanui-drawer="#menu">菜单</button> <!-- 打开抽屉 -->
|
|
817
|
+
<button data-fanui-sheet="#share">分享</button> <!-- 打开底部面板 -->
|
|
818
|
+
|
|
819
|
+
<!-- 浮层内部:点击即关闭的元素 -->
|
|
820
|
+
<div class="fanui-modal" id="loginModal">
|
|
821
|
+
<div class="fanui-modal__backdrop"></div>
|
|
822
|
+
<div class="fanui-modal__dialog">
|
|
823
|
+
<button class="fanui-modal__close" data-fanui-dismiss>×</button>
|
|
824
|
+
...
|
|
825
|
+
</div>
|
|
826
|
+
</div>
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
| 声明式属性 | 作用 |
|
|
830
|
+
|---|---|
|
|
831
|
+
| `data-fanui-modal="#id"` | 点击打开 `#id` 模态框(支持 `.fanui-modal--sm/md/lg/xl/full/top/bottom` 尺寸修饰) |
|
|
832
|
+
| `data-fanui-drawer="#id"` | 打开抽屉 |
|
|
833
|
+
| `data-fanui-sheet="#id"` | 打开底部面板 |
|
|
834
|
+
| `data-fanui-dismiss` | 点击关闭所在浮层(关闭按钮、取消按钮用) |
|
|
835
|
+
| `data-close-on-backdrop="false"` | 写在浮层根元素上:禁止点遮罩关闭 |
|
|
836
|
+
|
|
837
|
+
`FanUI.modal` 还提供快捷方法:`FanUI.modal.open("#id")`、`FanUI.modal.close("#id")`(参数带不带 `#` 都可以)、`FanUI.modal.create(el, options)`。
|
|
838
|
+
|
|
839
|
+
### 3.4 `FanUI.toast` —— 轻提示
|
|
840
|
+
|
|
841
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
842
|
+
|---|---|---|---|
|
|
843
|
+
| `show(content, type, duration)` | 见下 | Toast 元素 | 通用入口。`content` 传**字符串**时作为标题(此时可用第 2、3 个参数传类型和时长);传**对象**时用完整配置 |
|
|
844
|
+
| `success(msg, desc)` / `info(msg, desc)` / `warning(msg, desc)` / `error(msg, desc)` | `msg`:标题文本;`desc`(可选):副标题 | Toast 元素 | 四种语义快捷方法 |
|
|
845
|
+
| `hide(el)` | `el`:`show()` 返回的元素 | 无 | 立即关闭某条 Toast |
|
|
846
|
+
|
|
847
|
+
**`show()` 的对象配置**:
|
|
848
|
+
|
|
849
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
850
|
+
|---|---|---|---|
|
|
851
|
+
| `title` | `string` | 无 | 主文本(建议必填) |
|
|
852
|
+
| `desc` | `string` | 无 | 副文本,字号更小 |
|
|
853
|
+
| `type` | `string` | 无 | `success` / `info` / `warning` / `danger`,决定左侧色条与图标色 |
|
|
854
|
+
| `icon` | `string` | 无 | 自定义图标(HTML 字符串或 emoji,如 `"🎉"`) |
|
|
855
|
+
| `duration` | `number` | `3000` | 自动关闭毫秒数;传 `0` 表示不自动关(需手动 `hide`) |
|
|
856
|
+
|
|
857
|
+
交互细节:鼠标悬停会暂停自动关闭,移开后 1.2 秒再关。提示区域(`.fanui-toast-region`)首次调用时自动创建,无需手写。
|
|
858
|
+
|
|
859
|
+
```js
|
|
860
|
+
FanUI.toast.success("已保存");
|
|
861
|
+
FanUI.toast.error("网络异常", "请检查连接后重试");
|
|
862
|
+
FanUI.toast.show({ title: "上传完成", desc: "3 个文件", icon: "📦", duration: 5000 });
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### 3.5 `FanUI.command` —— 命令面板(⌘K)
|
|
866
|
+
|
|
867
|
+
两种用法:**全局快捷键** `Ctrl/⌘ + K` 唤起(自动生效);或点 `[data-fanui-command-open]` 元素。
|
|
868
|
+
|
|
869
|
+
| 调用 | 参数 | 返回 | 说明 |
|
|
870
|
+
|---|---|---|---|
|
|
871
|
+
| `FanUI.command(sel, opts)` | `sel`:面板根元素选择器;`opts.items`(可选):条目数组,传入即整体替换数据源 | 实例或 `null` | 取得某个面板的实例(没有就惰性创建) |
|
|
872
|
+
| `FanUI.command.api()` | 无 | 第一个面板实例 | 便捷取实例 |
|
|
873
|
+
| `FanUI.command.open()` / `close()` / `toggle()` | 无 | — | 操作第一个面板 |
|
|
874
|
+
|
|
875
|
+
**实例方法**:
|
|
876
|
+
|
|
877
|
+
| 方法 | 参数 | 说明 |
|
|
878
|
+
|---|---|---|
|
|
879
|
+
| `open()` | 无 | 打开面板:清空搜索词、显示全部条目、聚焦输入框、锁页面滚动、派发 `fanui:commandopen` |
|
|
880
|
+
| `close()` | 无 | 关闭并解锁滚动 |
|
|
881
|
+
| `toggle()` / `isOpen()` | 无 | 开 ↔ 关 / 状态查询 |
|
|
882
|
+
| `setItems(list)` | 条目数组 | **动态重建数据源**(适合从后端拉取后注入)。条目字段见下 |
|
|
883
|
+
|
|
884
|
+
**条目字段**(`setItems` 数组元素,或直接写在 HTML 的 `data-*` 上):
|
|
885
|
+
|
|
886
|
+
| 字段 / HTML 属性 | 必填 | 说明 |
|
|
887
|
+
|---|---|---|
|
|
888
|
+
| `id` / `data-fanui-command` | 推荐 | 条目唯一标识。执行时通过 `fanui:command` 事件的 `detail.id` 回传给你 |
|
|
889
|
+
| `title` | ✅ | 显示标题 |
|
|
890
|
+
| `desc` | ❌ | 次要说明 |
|
|
891
|
+
| `group` / `data-fanui-group` | ❌ | 分组名,同名自动归组并渲染分组标题 |
|
|
892
|
+
| `icon` | ❌ | 图标字符,默认 `"◆"` |
|
|
893
|
+
| `hint` | ❌ | 右侧快捷键提示(如 `"⌘T"`) |
|
|
894
|
+
| `keywords` / `data-fanui-keywords` | ❌ | 额外搜索关键词(不显示,只参与匹配) |
|
|
895
|
+
| `data-href` | ❌ | 执行后跳转的地址(如 `"/admin"`) |
|
|
896
|
+
|
|
897
|
+
**搜索与键盘**:输入即时过滤,支持「子序列模糊匹配」(输 `emld` 也能命中 Emerald);`↑` `↓` 移动高亮,`Enter` 执行,`Esc` 关闭。
|
|
898
|
+
|
|
899
|
+
**接收执行结果**(必写):
|
|
900
|
+
|
|
901
|
+
```js
|
|
902
|
+
document.addEventListener("fanui:command", function (e) {
|
|
903
|
+
const id = e.detail.id; // 你在条目上定义的 id
|
|
904
|
+
if (id === "open-settings") FanUI.studio.api().open();
|
|
905
|
+
else if (id.indexOf("accent-") === 0) FanUI.theme.setAccent(id.slice(7));
|
|
906
|
+
});
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
面板 HTML 结构(`.fanui-command` 根 + `__input` + `__body` + 条目)见 `demo/index.html` 与 `demo/components.html`,直接复制改条目即可。
|
|
910
|
+
|
|
911
|
+
### 3.6 `FanUI.carousel` —— 轮播
|
|
912
|
+
|
|
913
|
+
HTML 结构(缺一不可的部分只有 `__viewport`):
|
|
914
|
+
|
|
915
|
+
```html
|
|
916
|
+
<div class="fanui-carousel" data-autoplay="4000">
|
|
917
|
+
<div class="fanui-carousel__viewport">
|
|
918
|
+
<div class="fanui-carousel__slide">1</div>
|
|
919
|
+
<div class="fanui-carousel__slide">2</div>
|
|
920
|
+
<div class="fanui-carousel__slide">3</div>
|
|
921
|
+
</div>
|
|
922
|
+
<button class="fanui-carousel__nav fanui-carousel__nav--prev">‹</button>
|
|
923
|
+
<button class="fanui-carousel__nav fanui-carousel__nav--next">›</button>
|
|
924
|
+
<div class="fanui-carousel__dots"></div> <!-- 留空即可,分页点自动生成 -->
|
|
925
|
+
<div class="fanui-carousel__progress"></div> <!-- 进度条,可选 -->
|
|
926
|
+
</div>
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
| 配置 / API | 说明 |
|
|
930
|
+
|---|---|
|
|
931
|
+
| `data-autoplay="毫秒"` | 自动播放间隔;悬停、聚焦、切走标签页时自动暂停 |
|
|
932
|
+
| `el.__fanuiApi.index()` | 当前页索引(从 0 开始) |
|
|
933
|
+
| `el.__fanuiApi.go(i, smooth)` | 跳到第 `i` 页;`smooth` 传 `false` 表示无动画 |
|
|
934
|
+
| `el.__fanuiApi.next()` / `prev()` | 下一页 / 上一页 |
|
|
935
|
+
| `el.__fanuiApi.play()` / `stop()` | 开始 / 停止自动播放 |
|
|
936
|
+
| 事件 `fanui:slide` | 派发到面板根元素,`detail: { index, count, root }` |
|
|
937
|
+
|
|
938
|
+
支持触摸滑动与桌面鼠标拖拽。变体类名:`fanui-carousel--peek`(露出下一张)、`--2` / `--3`(一屏多张)。
|
|
939
|
+
|
|
940
|
+
### 3.7 增强表单控件(Rate / Tags / Stepper / Pin / Swatch / Upload / Combobox)
|
|
941
|
+
|
|
942
|
+
这些控件都是「**写好 HTML → 运行时自动增强**」,JS 只在需要读值/监听时出场。
|
|
943
|
+
每个控件的完整 HTML 结构可在 `demo/components.html` 里搜索对应类名直接复制。
|
|
944
|
+
|
|
945
|
+
#### 3.7.1 `.fanui-rate` 评分
|
|
946
|
+
|
|
947
|
+
```html
|
|
948
|
+
<div class="fanui-rate" id="myRate">
|
|
949
|
+
<input type="radio" name="rate" value="1"><input type="radio" name="rate" value="2">
|
|
950
|
+
<input type="radio" name="rate" value="3" checked><input type="radio" name="rate" value="4">
|
|
951
|
+
<input type="radio" name="rate" value="5">
|
|
952
|
+
<span data-fanui-rate-value>3</span>
|
|
953
|
+
</div>
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
| 项 | 说明 |
|
|
957
|
+
|---|---|
|
|
958
|
+
| 结构 | 根元素 `.fanui-rate` 内放 5 个(任意个)同 `name` 的 `radio`,`value` 即分值 |
|
|
959
|
+
| 读值 | 输出元素 `[data-fanui-rate-value]`(或 `.fanui-rate__value`)自动回显;半星场景偶数个输入按 1 位小数显示 |
|
|
960
|
+
| 变体 | `.fanui-rate--sm` / `--lg` / `--readonly`(只读) |
|
|
961
|
+
| 事件 | `fanui:rate`,派发在**评分元素**上,`detail: { value }` |
|
|
962
|
+
|
|
963
|
+
```js
|
|
964
|
+
document.getElementById("myRate").addEventListener("fanui:rate", (e) => {
|
|
965
|
+
console.log("用户打了", e.detail.value, "分");
|
|
966
|
+
});
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
#### 3.7.2 `.fanui-tag-input` 标签输入
|
|
970
|
+
|
|
971
|
+
```html
|
|
972
|
+
<div class="fanui-tag-input" data-max="5">
|
|
973
|
+
<input class="fanui-tag-input__field" placeholder="回车添加">
|
|
974
|
+
<input type="hidden" data-fanui-tags-value value="vue,react">
|
|
975
|
+
</div>
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
| 项 | 说明 |
|
|
979
|
+
|---|---|
|
|
980
|
+
| 结构 | 根元素 + `__field` 文本框 +(推荐)`[data-fanui-tags-value]` 隐藏域存值 |
|
|
981
|
+
| `data-max="N"` | 最多 N 个标签,超出后忽略输入 |
|
|
982
|
+
| 交互 | `Enter` 或输入逗号 → 添加;`Backspace` 在空输入时删除最后一个;点标签上的 `×` 删除 |
|
|
983
|
+
| 建议下拉 | 根元素内加 `<div class="fanui-tag-input__suggest">` 并放若干 `<button data-value="xxx">`,输入时自动过滤显示,点击即添加 |
|
|
984
|
+
| 取值 | `el.__fanuiApi.values()` 返回字符串数组;`el.__fanuiApi.add("新标签")` 编程添加 |
|
|
985
|
+
| 事件 | `fanui:tagschange`,派发在输入框根元素上,`detail: { values: string[] }` |
|
|
986
|
+
|
|
987
|
+
#### 3.7.3 `.fanui-stepper` 步进器
|
|
988
|
+
|
|
989
|
+
```html
|
|
990
|
+
<div class="fanui-stepper">
|
|
991
|
+
<button type="button" class="fanui-stepper__btn" aria-label="减少">−</button>
|
|
992
|
+
<input type="number" value="1" min="1" max="9" step="1">
|
|
993
|
+
<button type="button" class="fanui-stepper__btn" aria-label="增加">+</button>
|
|
994
|
+
</div>
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
| 项 | 说明 |
|
|
998
|
+
|---|---|
|
|
999
|
+
| 结构 | 两个 `__btn`(**第 1 个是减、第 2 个是加**)夹一个 `input` |
|
|
1000
|
+
| `min` / `max` / `step` | 全部取自 input 的原生属性;支持小数步进(`step="0.1"` 自动按 1 位小数显示) |
|
|
1001
|
+
| 行为 | 到边界自动禁用对应按钮;每次变更向 input 派发原生 `input` 和 `change` 事件(框架监听原生事件即可) |
|
|
1002
|
+
|
|
1003
|
+
#### 3.7.4 `.fanui-pin` 验证码输入
|
|
1004
|
+
|
|
1005
|
+
```html
|
|
1006
|
+
<div class="fanui-pin" data-target="#codeField">
|
|
1007
|
+
<input class="fanui-pin__cell" maxlength="1" inputmode="numeric">
|
|
1008
|
+
<input class="fanui-pin__cell" maxlength="1" inputmode="numeric">
|
|
1009
|
+
<input class="fanui-pin__cell" maxlength="1" inputmode="numeric">
|
|
1010
|
+
<input class="fanui-pin__cell" maxlength="1" inputmode="numeric">
|
|
1011
|
+
<input type="hidden" id="codeField">
|
|
1012
|
+
</div>
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
| 项 | 说明 |
|
|
1016
|
+
|---|---|
|
|
1017
|
+
| `data-target="#id"` | (可选)把拼好的验证码同步进指定隐藏域/表单字段 |
|
|
1018
|
+
| 交互 | 自动跳格、退格回删、左右方向键、**整段粘贴自动分配** |
|
|
1019
|
+
| 事件 | `fanui:pinchange`(派发在 pin 元素,`detail:{value}`);`fanui:pincomplete`(输满时派发到 `document`,`detail:{value, root}`) |
|
|
1020
|
+
|
|
1021
|
+
```js
|
|
1022
|
+
document.addEventListener("fanui:pincomplete", (e) => {
|
|
1023
|
+
fetch("/api/verify", { method: "POST", body: e.detail.value }); // 自动提交验证码
|
|
1024
|
+
});
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
#### 3.7.5 `.fanui-swatch` 色板选择
|
|
1028
|
+
|
|
1029
|
+
```html
|
|
1030
|
+
<div class="fanui-swatch">
|
|
1031
|
+
<button type="button" class="fanui-swatch__item is-selected" data-value="cyan" style="--fanui-swatch-color:#06b6d4"></button>
|
|
1032
|
+
<button type="button" class="fanui-swatch__item" data-value="rose" style="--fanui-swatch-color:#f43f5e"></button>
|
|
1033
|
+
<input type="hidden" data-fanui-swatch-value value="cyan">
|
|
1034
|
+
</div>
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
| 项 | 说明 |
|
|
1038
|
+
|---|---|
|
|
1039
|
+
| 取值优先级 | `data-value` > 内联变量 `--fanui-swatch-color` |
|
|
1040
|
+
| 变体 | `--lg`(大号)、`--round`(圆形) |
|
|
1041
|
+
| 事件 | `fanui:swatch`,派发到 `document`,`detail: { value, root }` |
|
|
1042
|
+
|
|
1043
|
+
#### 3.7.6 `.fanui-upload` 上传
|
|
1044
|
+
|
|
1045
|
+
```html
|
|
1046
|
+
<div class="fanui-upload" data-url="/api/upload" data-headers='{"X-Token":"abc"}'>
|
|
1047
|
+
<div class="fanui-upload__drop">点击或拖拽文件到这里<input type="file" name="file" multiple hidden></div>
|
|
1048
|
+
<ul class="fanui-upload__list"></ul> <!-- 可省略,会自动创建 -->
|
|
1049
|
+
</div>
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
| 项 | 说明 |
|
|
1053
|
+
|---|---|
|
|
1054
|
+
| `data-url` | 上传接口地址;**不填 = 纯本地选择**(列表显示 ✓,不发请求) |
|
|
1055
|
+
| `data-headers` | JSON 字符串,逐项设为请求头(如鉴权 token) |
|
|
1056
|
+
| 文件字段名 | 取 `input[name]`,默认 `"file"` |
|
|
1057
|
+
| 列表项 | 自动显示文件名、大小、进度百分比 / ✓ / !,附「移除」按钮 |
|
|
1058
|
+
| 事件 | `fanui:upload` / `fanui:uploaderror`,派发到 `document`,`detail: { file, xhr, root }` |
|
|
1059
|
+
|
|
1060
|
+
#### 3.7.7 `.fanui-combobox` 联想下拉
|
|
1061
|
+
|
|
1062
|
+
```html
|
|
1063
|
+
<div class="fanui-combobox">
|
|
1064
|
+
<input class="fanui-combobox__input" placeholder="选择城市">
|
|
1065
|
+
<div class="fanui-combobox__list">
|
|
1066
|
+
<div class="fanui-combobox__option" data-value="hz" data-label="杭州">杭州</div>
|
|
1067
|
+
<div class="fanui-combobox__option" data-value="sh" data-label="上海">上海</div>
|
|
1068
|
+
</div>
|
|
1069
|
+
<input type="hidden" data-fanui-combobox-value>
|
|
1070
|
+
</div>
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
| 项 | 说明 |
|
|
1074
|
+
|---|---|
|
|
1075
|
+
| 交互 | 聚焦/输入展开,输入即时过滤并**高亮命中片段**,`↑↓` 选择、`Enter` 确认、`Esc` 收起、点外部收起 |
|
|
1076
|
+
| 取值 | 隐藏域 `[data-fanui-combobox-value]` 写入 `data-value` |
|
|
1077
|
+
| 事件 | `fanui:combobox`,派发到 `document`,`detail: { value, label, root }` |
|
|
1078
|
+
|
|
1079
|
+
### 3.8 基础交互组件(Tabs / Dropdown / Collapse / Switch / Form)
|
|
1080
|
+
|
|
1081
|
+
全部**声明式自动初始化**,通常无需写 JS:
|
|
1082
|
+
|
|
1083
|
+
| 组件 | HTML 要点 | JS / 事件 |
|
|
1084
|
+
|---|---|---|
|
|
1085
|
+
| 选项卡 `.fanui-tabs` | 触发项 `role="tab"`(或 `.fanui-tabs__item`)+ `href="#panelId"`(或 `data-fanui-target`);面板 `role="tabpanel"` | 事件 `fanui:tabchange` 派发在 tabs 根元素,`detail: { tab, panel }`;禁用项加 `.is-disabled` 或 `disabled` |
|
|
1086
|
+
| 下拉 `.fanui-dropdown` | `__trigger` + `__menu`(内含 `.fanui-menu__item`) | 点击外部 / Esc 自动收起;`FanUI.dropdown.closeAll()` 手动全收 |
|
|
1087
|
+
| 折叠 `.fanui-accordion` | 触发器 `data-fanui-toggle="collapse"` + `data-fanui-target="#id"` | 手风琴默认互斥;容器加 `data-multiple="true"` 允许多开 |
|
|
1088
|
+
| 开关 `.fanui-switch` | `label.fanui-switch` 内包一个 checkbox | 状态类 `is-checked` 与 `aria-checked` 自动同步 |
|
|
1089
|
+
| 表单校验 | `<form data-fanui-validate>` | 失焦校验单字段、提交时全量校验;错误信息写进 `.fanui-form-feedback`;`FanUI.form.validate(inputEl)` 可手动校验单个字段,返回布尔值 |
|
|
1090
|
+
|
|
1091
|
+
### 3.9 B 端组件(Kanban / Notify / FilterBar)
|
|
1092
|
+
|
|
1093
|
+
| 组件 | HTML 要点 | JS / 事件 |
|
|
1094
|
+
|---|---|---|
|
|
1095
|
+
| 看板 `.fanui-kanban` | `__column[data-column="todo"]` → `__list` → `__card`;列头计数徽标 `__count` 自动更新 | 卡片自动可拖拽(HTML5 DnD)。事件(派发到 `document`):`fanui:kanbanchange { from, root }` 拖拽结束;`fanui:kanbancolumnchange { column, root }` 落入新列 |
|
|
1096
|
+
| 通知中心 `.fanui-notify` | `__trigger`(带 `__dot` 红点)+ `__panel`(内含 `__item.is-unread`) | 实例 `el.__fanuiApi.open()/close()/toggle()`;`[data-fanui-notify-read]` 一键已读并隐藏红点;事件 `fanui:notifyread { root }` |
|
|
1097
|
+
| 筛选栏 `.fanui-filter-bar` | `__chip[data-group="状态"][data-value="open"]`;视图切换 `__views button[data-view]`;搜索 `input` | 同 `data-group` 的 chip 单选互斥,无 group 的可多选。事件(派发到 `document`):`fanui:filter { value, root }`、`fanui:viewchange { view, root }`、`fanui:search { query, root }`(输入节流) |
|
|
1098
|
+
|
|
1099
|
+
### 3.10 滚动与动效(Scroll / Counter / ripple / Copy / Tracker)
|
|
1100
|
+
|
|
1101
|
+
| 能力 | HTML 写法 | 说明 |
|
|
1102
|
+
|---|---|---|
|
|
1103
|
+
| 阅读进度条 | 任意元素加 `data-fanui-read-progress` | 滚动时写 CSS 变量 `--fanui-scroll-progress`(0~1),配合 `.fanui-scroll-progress` 使用 |
|
|
1104
|
+
| 返回顶部 | 元素加 `data-fanui-scroll-top`(可加 `data-threshold="400"`) | 滚过阈值出现 `is-visible`,点击平滑回顶 |
|
|
1105
|
+
| 导航滚动加深 | 导航栏加 `data-fanui-navbar-scrolled`(可加 `data-threshold="12"`) | 滚动后加 `is-scrolled` |
|
|
1106
|
+
| 目录高亮 scrollspy | 目录容器加 `data-fanui-scrollspy`(内部是 `a[href="#xxx"]`) | 滚动时给对应链接加 `is-active` |
|
|
1107
|
+
| 入场动画 | 元素加 `data-fanui-reveal`;可加 `data-fanui-reveal-delay="200"` 或 `data-fanui-reveal-stagger` | 进入视口加 `is-visible`;`stagger` 按出现顺序自动 60ms 递增延迟 |
|
|
1108
|
+
| 数字滚动 | 元素加 `data-fanui-count-to="1284"`,可选 `data-fanui-count-duration="1200"` / `-decimals="2"` / `-prefix` / `-suffix` | 进入视口时从 0 滚动到目标值;**嵌套在 reveal 容器内也能触发** |
|
|
1109
|
+
| 涟漪 | 元素加 `data-fanui-ripple`;或 JS 调 `FanUI.ripple(el, event)` | 点击处扩散水波纹 |
|
|
1110
|
+
| 一键复制 | 元素加 `data-fanui-copy="要复制的文本"` 或 `data-fanui-copy="#sourceId"`;可加 `data-fanui-copy-message="已复制"` | 优先走 Clipboard API,失败自动降级 `execCommand`;成功后弹 Toast 并派发 `fanui:copy { text }` |
|
|
1111
|
+
| 指针追踪 | 容器加 `data-fanui-glass-track`(玻璃高光跟随鼠标)/ `.fanui-spotlight`(光斑卡片)/ `data-fanui-cursor-glow`(全局光标辉光) | 全局 pointermove 驱动,写入 `--fanui-glass-mx/my`、`--fanui-spot-x/y`、`--fanui-cursor-x/y` |
|
|
1112
|
+
|
|
1113
|
+
`FanUI.scroll.reveal(container)` 可对**后插入的 DOM**(如框架渲染的列表)手动补一次入场动画扫描。
|
|
1114
|
+
|
|
1115
|
+
### 3.11 工具方法
|
|
1116
|
+
|
|
1117
|
+
| 方法 | 参数 | 说明 |
|
|
1118
|
+
|---|---|---|
|
|
1119
|
+
| `FanUI.init(scope)` | `scope`(可选):容器元素或 document | **全量初始化**:扫描容器内全部声明式组件并绑定。自动在 DOMContentLoaded 时对整个文档执行过一次;框架动态渲染后可对新容器手动调用。幂等 |
|
|
1120
|
+
| `FanUI.$(sel, scope)` | 同上 | `querySelector` 快捷方式 |
|
|
1121
|
+
| `FanUI.$$(sel, scope)` | 同上 | `querySelectorAll` 快捷方式,返回**真数组** |
|
|
1122
|
+
| `FanUI.lockScroll()` / `unlockScroll()` | 无 | 手动锁定/解锁页面滚动(引用计数,可嵌套) |
|
|
1123
|
+
| `FanUI.ripple(host, event?)` | `host`:元素或选择器;`event`:点击事件(可不传,取元素中心) | 程序化触发涟漪 |
|
|
1124
|
+
| `FanUI.version` / `FanUI.prefix` | 属性 | 当前版本号(`"2.1.0"`)/ 类名前缀(`"fanui"`) |
|
|
1125
|
+
|
|
1126
|
+
---
|
|
1127
|
+
|
|
1128
|
+
## 4. 事件总表
|
|
1129
|
+
|
|
1130
|
+
监听方式:派发到 `document` 的用 `document.addEventListener(事件名, cb)`;标注「元素」的先拿到组件根元素再监听。参数一律在 `event.detail`。
|
|
1131
|
+
|
|
1132
|
+
| 事件名 | 派发位置 | `detail` 字段 | 触发时机 |
|
|
1133
|
+
|---|---|---|---|
|
|
1134
|
+
| `fanui:accentchange` | document | `{ accent, previous }` | 主题色切换 |
|
|
1135
|
+
| `fanui:accentcreated` | document | `{ accent, color, palette, lightTone, darkTone }` | `createAccent()` 生成新主题 |
|
|
1136
|
+
| `fanui:themechange` | document | `{ mode, effective, previous }` | 深浅模式切换 |
|
|
1137
|
+
| `fanui:glasschange` | document | `{ enabled, intensity, preset }` | 玻璃开关 / 强度 / 预设变化 |
|
|
1138
|
+
| `fanui:open` / `fanui:close` | document | `{ root }` | 任意浮层开 / 关 |
|
|
1139
|
+
| `fanui:command` | document | `{ id, item }` | 命令面板条目被执行 |
|
|
1140
|
+
| `fanui:commandopen` | document | `{ root }` | 命令面板打开 |
|
|
1141
|
+
| `fanui:tabchange` | tabs 根元素 | `{ tab, panel }` | 选项卡切换 |
|
|
1142
|
+
| `fanui:slide` | carousel 根元素 | `{ index, count, root }` | 轮播页变化 |
|
|
1143
|
+
| `fanui:rate` | rate 根元素 | `{ value }` | 评分变化 |
|
|
1144
|
+
| `fanui:tagschange` | tag-input 根元素 | `{ values: string[] }` | 标签增删 |
|
|
1145
|
+
| `fanui:pinchange` | pin 根元素 | `{ value }` | 验证码输入变化 |
|
|
1146
|
+
| `fanui:pincomplete` | document | `{ value, root }` | 验证码输满 |
|
|
1147
|
+
| `fanui:swatch` | document | `{ value, root }` | 色板选中 |
|
|
1148
|
+
| `fanui:upload` / `fanui:uploaderror` | document | `{ file, xhr, root }` | 上传成功 / 失败 |
|
|
1149
|
+
| `fanui:combobox` | document | `{ value, label, root }` | 联想下拉选中 |
|
|
1150
|
+
| `fanui:kanbanchange` | document | `{ from, root }` | 看板卡片拖拽结束 |
|
|
1151
|
+
| `fanui:kanbancolumnchange` | document | `{ column, root }` | 卡片落入新列 |
|
|
1152
|
+
| `fanui:notifyread` | document | `{ root }` | 通知全部已读 |
|
|
1153
|
+
| `fanui:filter` | document | `{ value, root }` | 筛选 chip 点击 |
|
|
1154
|
+
| `fanui:viewchange` | document | `{ view, root }` | 视图切换 |
|
|
1155
|
+
| `fanui:search` | document | `{ query, root }` | 筛选栏搜索输入(节流) |
|
|
1156
|
+
| `fanui:copy` | document | `{ text }` | 复制成功 |
|
|
1157
|
+
|
|
1158
|
+
---
|
|
1159
|
+
|
|
1160
|
+
## 5. data-* 属性总表
|
|
1161
|
+
|
|
1162
|
+
按用途分组,**全部可零 JS 使用**(写在 HTML 上即生效):
|
|
1163
|
+
|
|
1164
|
+
**主题三轴**
|
|
1165
|
+
|
|
1166
|
+
| 属性 | 取值 | 作用 |
|
|
1167
|
+
|---|---|---|
|
|
1168
|
+
| `data-fanui-theme` | `light` / `dark` / `auto` | 深浅模式 |
|
|
1169
|
+
| `data-fanui-accent` | 12 个主题色名或自定义名 | 主题色(可写根元素或任意子树) |
|
|
1170
|
+
| `data-fanui-glass` | `on` / `off` | 玻璃总开关 |
|
|
1171
|
+
| `data-fanui-auto` | `"false"` | 写在 `<html>` 上:关闭自动初始化,改手动 `FanUI.init()` |
|
|
1172
|
+
| 等价类名 | `.fanui-accent-<名>` | 与 `data-fanui-accent` 等价的类名写法 |
|
|
1173
|
+
|
|
1174
|
+
**组件触发与配置**
|
|
1175
|
+
|
|
1176
|
+
| 属性 | 作用 |
|
|
1177
|
+
|---|---|
|
|
1178
|
+
| `data-fanui-modal="#id"` / `data-fanui-drawer="#id"` / `data-fanui-sheet="#id"` | 点击打开对应浮层 |
|
|
1179
|
+
| `data-fanui-dismiss` | 点击关闭所在浮层 |
|
|
1180
|
+
| `data-close-on-backdrop="false"` | 浮层禁用遮罩关闭 |
|
|
1181
|
+
| `data-fanui-toggle="collapse"` + `data-fanui-target="#id"` | 折叠触发器;容器 `data-multiple="true"` 多开 |
|
|
1182
|
+
| `data-fanui-validate` | 表单启用自动校验 |
|
|
1183
|
+
| `data-fanui-switch` | 开关元素标记(`label.fanui-switch` 可省略) |
|
|
1184
|
+
| `data-fanui-command-open` | 点击打开命令面板 |
|
|
1185
|
+
| `data-fanui-command-item` / `data-fanui-command="id"` / `data-fanui-group` / `data-fanui-keywords` / `data-href` | 命令面板静态条目(见 3.5) |
|
|
1186
|
+
| `data-autoplay="毫秒"` | 轮播自动播放 |
|
|
1187
|
+
| `data-fanui-rate-value` | 评分回显元素 |
|
|
1188
|
+
| `data-fanui-tags-value` / `data-max` | 标签输入存值域 / 上限 |
|
|
1189
|
+
| `data-target="#id"` | 验证码同步目标 |
|
|
1190
|
+
| `data-fanui-swatch-value` / `data-value` | 色板存值域 / 选项值 |
|
|
1191
|
+
| `data-url` / `data-headers` | 上传接口与请求头 |
|
|
1192
|
+
| `data-fanui-combobox-value` / `data-label` | 联想下拉存值域 / 选项文案 |
|
|
1193
|
+
| `data-column` / `data-group` / `data-view` | 看板列名 / 筛选分组 / 视图名 |
|
|
1194
|
+
| `data-fanui-notify-read` | 一键已读按钮 |
|
|
1195
|
+
| `data-threshold="数值"` | 滚动相关组件的触发阈值 |
|
|
1196
|
+
|
|
1197
|
+
**滚动与动效**
|
|
1198
|
+
|
|
1199
|
+
| 属性 | 作用 |
|
|
1200
|
+
|---|---|
|
|
1201
|
+
| `data-fanui-read-progress` | 阅读进度条 |
|
|
1202
|
+
| `data-fanui-scroll-top` | 返回顶部按钮 |
|
|
1203
|
+
| `data-fanui-navbar-scrolled` | 导航滚动加深 |
|
|
1204
|
+
| `data-fanui-scrollspy` | 目录高亮容器 |
|
|
1205
|
+
| `data-fanui-reveal` / `-delay` / `-stagger` | 入场动画 |
|
|
1206
|
+
| `data-fanui-count-to` / `-duration` / `-decimals` / `-prefix` / `-suffix` | 数字滚动 |
|
|
1207
|
+
| `data-fanui-ripple` | 涟漪 |
|
|
1208
|
+
| `data-fanui-copy` / `data-fanui-copy-message` | 一键复制 |
|
|
1209
|
+
| `data-fanui-glass-track` / `data-fanui-cursor-glow` | 指针追踪高光 / 光标辉光 |
|
|
1210
|
+
|
|
1211
|
+
**进阶 / 表单 / 数据组件属性**(以下此前未列入总表,均可在 `src/fanui.js` 找到对应实现)
|
|
1212
|
+
|
|
1213
|
+
| 属性 | 取值 | 作用 |
|
|
1214
|
+
|---|---|---|
|
|
1215
|
+
| `data-fanui-datepicker` | 无值即可 | 标记日期选择器,点击输入框弹出月历面板 |
|
|
1216
|
+
| `data-fanui-datepicker-range` | 无值即可 | 开启区间选择(两个端点) |
|
|
1217
|
+
| `data-fanui-timepicker` | 无值即可 | 标记时间选择器,弹出时间列表 |
|
|
1218
|
+
| `data-fanui-timepicker-step` | 分钟数,默认 `30` | 时间步进(如 `15` / `30`) |
|
|
1219
|
+
| `data-fanui-colorpicker` | 无值即可 | 标记颜色选择器,弹出取色面板 |
|
|
1220
|
+
| `data-fanui-cascader` | 无值即可 | 标记级联选择器,数据来自相邻 `<script type="application/json">` 或 `FanUI.cascader(el,{items})` |
|
|
1221
|
+
| `data-fanui-tree` | 无值即可 | 标记树形控件 |
|
|
1222
|
+
| `data-fanui-tree-check` | 无值即可 | 开启复选框(三态联动) |
|
|
1223
|
+
| `data-fanui-transfer` | 无值即可 | 标记穿梭框 |
|
|
1224
|
+
| `data-fanui-count-decimals` | 小数位数,默认 `0` | 数字滚动保留小数(补充 3.10) |
|
|
1225
|
+
| `data-fanui-count-prefix` | 前缀文本 | 数字滚动前导文案 |
|
|
1226
|
+
| `data-fanui-count-suffix` | 后缀文本 | 数字滚动后缀文案 |
|
|
1227
|
+
| `data-fanui-dropdown-toggle` | 无值即可 | 下拉触发器标记(等价于 `.fanui-dropdown__trigger`) |
|
|
1228
|
+
| `data-fanui-scrollable` | 无值即可 | 标记自定义滚动区域,供滚动增强识别(`pre, .fanui-scroll-area, [data-fanui-scrollable]`) |
|
|
1229
|
+
| `data-fanui-title` | 文本 | 命令面板条目的标题文案 |
|
|
1230
|
+
| `data-fanui-raw` | 自动写入 | 自动记录元素原始文本,供复制 / 数字组件回退(一般无需手写) |
|
|
1231
|
+
| `data-fanui-reveal-ready` | 自动写入 | reveal 就绪后置于根元素(一般无需手写) |
|
|
1232
|
+
|
|
1233
|
+
---
|
|
1234
|
+
|
|
1235
|
+
## 6. 主题色与玻璃类名速查
|
|
1236
|
+
|
|
1237
|
+
**主题色角色令牌**(在任意 CSS 里直接消费,换主题自动跟随):
|
|
1238
|
+
|
|
1239
|
+
```css
|
|
1240
|
+
.my-widget {
|
|
1241
|
+
color: var(--fanui-primary); /* 实心主色 */
|
|
1242
|
+
background: var(--fanui-primary-soft); /* 半透明底色 */
|
|
1243
|
+
border-color: var(--fanui-primary-border);
|
|
1244
|
+
}
|
|
1245
|
+
.my-widget b { color: var(--fanui-primary-ink); } /* soft 底上的文字色 */
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
| 令牌 | 用途 | 令牌 | 用途 |
|
|
1249
|
+
|---|---|---|---|
|
|
1250
|
+
| `--fanui-primary` | 实心主色 | `--fanui-primary-fg` | 实心色上的文字色 |
|
|
1251
|
+
| `--fanui-primary-hover` / `-active` | 悬停 / 按下 | `--fanui-primary-vivid` / `-vivid-2` | 高饱和装饰色(渐变端点) |
|
|
1252
|
+
| `--fanui-primary-soft` / `-soft-2` | 半透明底色 | `--fanui-primary-border` | 半透明描边 |
|
|
1253
|
+
| `--fanui-primary-ink` | soft 底上的文字 | `--fanui-primary-ring` | 焦点环 |
|
|
1254
|
+
| `--fanui-primary-glow` | 品牌光晕(阴影) | `--fanui-primary-grad-a/b` | 品牌渐变端点 |
|
|
1255
|
+
| `--fanui-accent-*` | 副色(同构 13 个) | `--fanui-brand-grad` | 品牌渐变(合成) |
|
|
1256
|
+
| `--fanui-{success,warning,danger,info}-*` | 语义色(同构) | `--fanui-primary-50…950` | 11 阶原始色板 |
|
|
1257
|
+
|
|
1258
|
+
**玻璃类名**:
|
|
1259
|
+
|
|
1260
|
+
| 类名 | 作用 |
|
|
1261
|
+
|---|---|
|
|
1262
|
+
| `.fanui-glass` | 基础玻璃材质 |
|
|
1263
|
+
| `.fanui-glass--thin / --regular / --thick / --ultra / --lens` | 5 档厚度预设 |
|
|
1264
|
+
| `.fanui-glass--card / --bar / --panel / --square / --sharp / --reactive` | 常见形态(圆角/直角/响应式) |
|
|
1265
|
+
| `.fanui-glass--flow` | 流光动画玻璃 |
|
|
1266
|
+
| `.fanui-card--glass` / `.fanui-navbar--glass` / `.fanui-modal--glass` / `.fanui-drawer--glass` / `.fanui-dropdown--glass` / `.fanui-toast--glass` / `.fanui-alert--glass` / `.fanui-btn--glass` / `.fanui-table--glass` | 已有组件的玻璃形态(自动遵守全局开关) |
|
|
1267
|
+
|
|
1268
|
+
---
|
|
1269
|
+
|
|
1270
|
+
## 7. 依赖说明与版本要求
|
|
1271
|
+
|
|
1272
|
+
**运行时依赖:无。** fanUI 的 CSS 与 JS 均为零第三方依赖,不需要 jQuery、不需要 Popper、不需要任何 polyfill 服务。
|
|
1273
|
+
|
|
1274
|
+
| 项 | 要求 | 说明 |
|
|
1275
|
+
|---|---|---|
|
|
1276
|
+
| fanui | `2.1.0` | 本文档对应版本 |
|
|
1277
|
+
| 浏览器 | Chrome / Edge / Firefox / Safari **最近两个大版本** | 常青浏览器 |
|
|
1278
|
+
| Node.js(仅自行编译 SCSS 时需要) | ≥ 18 | 只用 dist 产物则完全不需要 Node |
|
|
1279
|
+
| sass(仅自行编译 SCSS 时需要) | `>= 1.79.0` | devDependencies 里声明为 `^1.90.0`;`npm i -D sass` 即装 |
|
|
1280
|
+
| React | ≥ 16.8 可用 Hook;更低版本用 class 生命周期 | 无 peerDependencies,不会与项目 React 版本冲突 |
|
|
1281
|
+
| Vue | 2.6+ / 3.0+ | 同上,无 peerDependencies |
|
|
1282
|
+
| 打包器 | Vite 4/5、webpack 4/5、Rollup、esbuild 均可 | 包已声明 `sideEffects`,不会被 tree-shaking 误删 |
|
|
1283
|
+
|
|
1284
|
+
**注意事项(逐条)**:
|
|
1285
|
+
|
|
1286
|
+
1. **CSS 必须先于内容加载**——放在 `<head>`;JS 放 `</body>` 前。
|
|
1287
|
+
2. **JS 需要浏览器 DOM**:SSR/Node 环境安全导出 `null`(不会抛错),初始化请放客户端生命周期。
|
|
1288
|
+
3. **玻璃效果**依赖 `backdrop-filter`:不支持的浏览器自动回退为实色(`@supports` 处理),**无需你写任何兼容代码**;系统开启「减少透明度 / 减弱动态效果」时也会自动降级。
|
|
1289
|
+
4. **`file://` 协议**:`<script>` 引入方式可直接双击运行;但**原生 ESM 方式**(`type="module"`)会被 CORS 拦截,需本地服务器(`npx serve .`)。
|
|
1290
|
+
5. **剪贴板**:`data-fanui-copy` 优先用 Clipboard API(需 HTTPS 或 localhost),非安全上下文自动降级 `execCommand`。
|
|
1291
|
+
6. **localStorage**:偏好持久化在键 `fanui-prefs`;隐私模式下自动静默降级(不记忆但不报错)。
|
|
1292
|
+
7. **升级版本**:类名与 CSS 变量都有 `fanui` 前缀隔离,升级一般只需替换 dist 文件;建议锁定版本号(如 CDN 写 `@2.1.0`)。
|
|
1293
|
+
|
|
1294
|
+
---
|
|
1295
|
+
|
|
1296
|
+
## 8. 常见问题(排错速查)
|
|
1297
|
+
|
|
1298
|
+
**Q1:页面完全没有样式。**
|
|
1299
|
+
CSS 路径错了。F12 → Console 看有没有 `Failed to load resource`。逐级核对 `href` 相对路径(参照 1.1 的路径表)。
|
|
1300
|
+
|
|
1301
|
+
**Q2:控制台报 `FanUI is not defined`。**
|
|
1302
|
+
JS 没引入或引入顺序错了。确认 `<script src="dist/fanui.js">` 存在,且你的代码写在它**之后**。
|
|
1303
|
+
|
|
1304
|
+
**Q3:`setAccent` / `setMode` 调了没反应。**
|
|
1305
|
+
① 检查 HTML 里是否有**另一份旧版 fanui.css** 同时被引入(两份 CSS 变量打架);② 确认传入的名字在 12 个内置值里(区分大小写,全小写);③ F12 查看 `<html>` 的 `data-fanui-accent` 是否已更新——属性变了但视觉没变,说明 CSS 没加载对。
|
|
1306
|
+
|
|
1307
|
+
**Q4:声明式组件(模态框/选项卡/看板…)点不动。**
|
|
1308
|
+
运行时没扫描到节点。三种可能:① JS 没引入;② 节点是在 JS 执行**之后**才插入的(常见于前端框架异步渲染)——对容器手动补一次 `FanUI.init(containerEl)`;③ HTML 上写了 `<html data-fanui-auto="false">` 关闭了自动初始化——那就必须手动 `FanUI.init()`。
|
|
1309
|
+
|
|
1310
|
+
**Q5:React/Vue 里重复渲染后事件绑了两次 / 行为怪异。**
|
|
1311
|
+
不会。所有 `init` 都有 `__fanuiInit` 防重复标记。如果确实出现,检查是否引入了两份不同来源的 fanui.js(比如既 `import` 又写了 `<script>`)。
|
|
1312
|
+
|
|
1313
|
+
**Q6:SSR 部署后报 `window is not defined` / `FanUI` 是 `null`。**
|
|
1314
|
+
你把初始化写在了模块顶层(服务端也会执行)。移到 `useEffect` / `onMounted` 里;Next 记得 `"use client"`;Nuxt 用 `.client.ts` 插件。
|
|
1315
|
+
|
|
1316
|
+
**Q7:玻璃效果在我的浏览器里没效果。**
|
|
1317
|
+
看 `document.documentElement.getAttribute("data-fanui-glass")` 是否为 `"off"`(被用户或代码关了);再确认浏览器支持 `backdrop-filter`(不支持会自动回退实色,这是预期行为)。
|
|
1318
|
+
|
|
1319
|
+
**Q8:怎么恢复默认主题?**
|
|
1320
|
+
`FanUI.theme.reset()`(会清掉用户在本机的持久化偏好)。
|
|
1321
|
+
|
|
1322
|
+
**Q9:想要自己的品牌色但怕对比度不达标。**
|
|
1323
|
+
用 `FanUI.theme.createAccent("#你的品牌色")`,实心色与前景色是自动挑选的合规组合(≥4.5:1),深浅模式都达标。
|
|
1324
|
+
|
|
1325
|
+
**Q10:能只引入部分组件的样式吗?**
|
|
1326
|
+
可以,用 SCSS 方式(1.4):`@use "@zhang_jifan/fanui/src/components/button";` 按模块引入;主题令牌在 `@zhang_jifan/fanui/src/tokens` + `@zhang_jifan/fanui/src/themes/accents`。
|
|
1327
|
+
|
|
1328
|
+
---
|
|
1329
|
+
|
|
1330
|
+
## 9. 完整可运行示例索引
|
|
1331
|
+
|
|
1332
|
+
| 示例 | 引入方式 | 技术栈 | 如何运行 |
|
|
1333
|
+
|---|---|---|---|
|
|
1334
|
+
| [`examples/01-script-tag.html`](./examples/01-script-tag.html) | 本地 `<script>` | 原生 HTML | **双击直接打开**(不需要构建、不需要联网) |
|
|
1335
|
+
| [`examples/02-react/`](./examples/02-react/) | npm | React 18 + Vite | `cd examples/02-react && npm install && npm run dev` |
|
|
1336
|
+
| [`examples/03-vue3/`](./examples/03-vue3/) | npm | Vue 3 + Vite | `cd examples/03-vue3 && npm install && npm run dev` |
|
|
1337
|
+
| [`examples/04-vue2-cdn.html`](./examples/04-vue2-cdn.html) | CDN + `<script>` | Vue 2.7 | 双击打开(Vue 走 CDN,需联网) |
|
|
1338
|
+
| [`demo/index.html`](./demo/index.html) | — | 5 大场景演示站 | 双击或 `npm run serve` |
|
|
1339
|
+
| [`demo/components.html`](./demo/components.html) | — | 全组件百科 | 同上 |
|
|
1340
|
+
|
|
1341
|
+
> 示例的浏览器回归由 `node scripts/verify-examples.cjs` 守护(零 console error + 结构断言 + 截图)。
|
|
1342
|
+
> 更深入的设计原理(三轴架构 / 自动可达性算法 / 玻璃材质分层)见 [DESIGN-SYSTEM.md](./DESIGN-SYSTEM.md)。
|
|
1343
|
+
|
|
1344
|
+
---
|
|
1345
|
+
|
|
1346
|
+
## 10. 进阶与结构组件速查
|
|
1347
|
+
|
|
1348
|
+
本节补齐此前未写入文档的 15 个组件:进阶数据录入(日期 / 时间 / 颜色 / 级联 / 树 / 穿梭)、加载条,以及表单 / 布局 / 基础 / 导航 / 营销的结构型组件。每个组件按「用途 → 最小 HTML → `data-fanui-*` 属性 → 命令式 API → 键盘/ARIA」组织;类名为源码真实类名,可直接复制。
|
|
1349
|
+
|
|
1350
|
+
### 10.1 进阶数据组件(DatePicker / TimePicker / ColorPicker / Cascader / Tree / Transfer / LoadingBar)
|
|
1351
|
+
|
|
1352
|
+
#### 10.1.1 日期选择面板 `fanui-datepanel`(FanUI.datePicker)
|
|
1353
|
+
|
|
1354
|
+
点击输入框弹出月历面板,选择单个日期或日期区间。周一为每周起点。
|
|
1355
|
+
|
|
1356
|
+
```html
|
|
1357
|
+
<!-- 单日期 -->
|
|
1358
|
+
<input class="fanui-form-control" data-fanui-datepicker>
|
|
1359
|
+
<!-- 区间 -->
|
|
1360
|
+
<input class="fanui-form-control" data-fanui-datepicker data-fanui-datepicker-range>
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1363
|
+
| 属性 | 取值 | 作用 |
|
|
1364
|
+
|---|---|---|
|
|
1365
|
+
| `data-fanui-datepicker` | 无值即可 | 标记为日期选择器,自动绑定 click 弹出面板 |
|
|
1366
|
+
| `data-fanui-datepicker-range` | 无值即可 | 开启区间选择(两个端点,面板内高亮区间) |
|
|
1367
|
+
|
|
1368
|
+
**命令式 API**:`FanUI.datePicker(el, opts)`
|
|
1369
|
+
|
|
1370
|
+
- `opts.range`:`Boolean`,是否区间(缺省读 `data-fanui-datepicker-range`)。
|
|
1371
|
+
- 返回实例:`open()` 打开面板;`close()` 关闭;`value()` → `String`(区间形如 `2026-01-01 ~ 2026-01-07`);`setValue(v)` 回填(支持区间字符串);`destroy()` 销毁并解绑。
|
|
1372
|
+
- 事件:`fanui:datechange`,`detail = { value, start, end }`(元素派发,冒泡)。
|
|
1373
|
+
- 面板顶部有「近 7 天 / 近 30 天 / 今天 / 清除」快捷项。
|
|
1374
|
+
|
|
1375
|
+
**可达性**:面板 `role="dialog"` `aria-label="选择日期"`;上一月 / 下一月按钮带 `aria-label`;关闭后焦点回到输入框;`is-today` / `is-selected` / `is-in-range` / `is-other` 表达语义状态。
|
|
1376
|
+
|
|
1377
|
+
#### 10.1.2 时间选择面板 `fanui-timepanel`(FanUI.timePicker)
|
|
1378
|
+
|
|
1379
|
+
按分钟步进生成时间列表,选中即回填。
|
|
1380
|
+
|
|
1381
|
+
```html
|
|
1382
|
+
<input class="fanui-form-control" data-fanui-timepicker data-fanui-timepicker-step="30">
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
| 属性 | 取值 | 作用 |
|
|
1386
|
+
|---|---|---|
|
|
1387
|
+
| `data-fanui-timepicker` | 无值即可 | 标记为时间选择器 |
|
|
1388
|
+
| `data-fanui-timepicker-step` | 分钟数,默认 `30` | 时间步进(如 `15` / `30` / `60`) |
|
|
1389
|
+
|
|
1390
|
+
**命令式 API**:`FanUI.timePicker(el, opts)`
|
|
1391
|
+
|
|
1392
|
+
- `opts.step`:`Number`(缺省读 `data-fanui-timepicker-step`,默认 `30`)。
|
|
1393
|
+
- 返回实例:`open()` / `close()` / `value()` → `String`(如 `09:30`)/ `setValue(v)`。
|
|
1394
|
+
- 事件:`fanui:timechange`,`detail = { value }`。
|
|
1395
|
+
|
|
1396
|
+
**可达性**:面板 `role="listbox"` `aria-label="选择时间"`;已选项加 `is-selected` 并在右侧显示 `✓`,打开时自动滚动到选中项。
|
|
1397
|
+
|
|
1398
|
+
#### 10.1.3 颜色选择面板 `fanui-colorpanel`(FanUI.colorPicker)
|
|
1399
|
+
|
|
1400
|
+
可视化选色:色相 / 明度滑杆 + 16 色预设板 + 手动输入,可一键把所选颜色生成合规主题。
|
|
1401
|
+
|
|
1402
|
+
```html
|
|
1403
|
+
<input class="fanui-form-control" data-fanui-colorpicker value="#0e7490">
|
|
1404
|
+
```
|
|
1405
|
+
|
|
1406
|
+
| 属性 | 取值 | 作用 |
|
|
1407
|
+
|---|---|---|
|
|
1408
|
+
| `data-fanui-colorpicker` | 无值即可 | 标记为颜色选择器 |
|
|
1409
|
+
|
|
1410
|
+
**命令式 API**:`FanUI.colorPicker(el, opts)`
|
|
1411
|
+
|
|
1412
|
+
- `opts.asTheme`:`Boolean`,是否允许「设为主题色」(默认 `true`,调用 `FanUI.theme.createAccent`)。
|
|
1413
|
+
- 返回实例:`open()` / `close()` / `value()` → `#rrggbb`(`setValue(v)` 回填并解析)。
|
|
1414
|
+
- 事件:`fanui:colorchange`,`detail = { value }`。
|
|
1415
|
+
- 内置 `ColorPicker.PRESETS`(16 色);最近使用存 `localStorage` 键 `fanui-color-recent`。
|
|
1416
|
+
|
|
1417
|
+
**可达性**:面板 `role="dialog"` `aria-label="选择颜色"`;色值输入框 `aria-label="颜色值"`;预设按钮 `aria-label` 为颜色值。
|
|
1418
|
+
|
|
1419
|
+
#### 10.1.4 级联选择器 `fanui-cascader`(FanUI.cascader)
|
|
1420
|
+
|
|
1421
|
+
多列逐级下钻选择,支持搜索;数据来自相邻 `<script type="application/json">` 或 `opts.items`。
|
|
1422
|
+
|
|
1423
|
+
```html
|
|
1424
|
+
<div data-fanui-cascader>
|
|
1425
|
+
<script type="application/json">
|
|
1426
|
+
[{"label":"浙江","value":"zj","children":[{"label":"杭州","value":"hz"},{"label":"宁波","value":"nb"}]}]
|
|
1427
|
+
</script>
|
|
1428
|
+
</div>
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
| 属性 | 取值 | 作用 |
|
|
1432
|
+
|---|---|---|
|
|
1433
|
+
| `data-fanui-cascader` | 无值即可 | 标记为级联选择器(缺省自动生成触发器) |
|
|
1434
|
+
|
|
1435
|
+
**命令式 API**:`FanUI.cascader(el, opts)`
|
|
1436
|
+
|
|
1437
|
+
- `opts.items`:`Array`(缺省读相邻 JSON);`opts.searchable`:`Boolean`,默认 `true`。
|
|
1438
|
+
- 返回实例:`open()` / `close()` / `value()` → `string[]`(各级 value)/ `labels()` → `string[]`(各级文案)/ `setValue(vals)` 回填。
|
|
1439
|
+
- 事件:`fanui:cascaderchange`,`detail = { labels, value, path }`。
|
|
1440
|
+
|
|
1441
|
+
**可达性**:触发器为原生 `<button>`,可 Tab 聚焦、回车打开;搜索框 `aria-label="搜索选项"`;逐层 `col` / `opt` 为标准按钮语义。
|
|
1442
|
+
|
|
1443
|
+
#### 10.1.5 树形控件 `fanui-tree`(FanUI.tree)
|
|
1444
|
+
|
|
1445
|
+
多级树,可选复选框(父子三态联动);数据来自相邻 JSON 或 `opts.items`。
|
|
1446
|
+
|
|
1447
|
+
```html
|
|
1448
|
+
<ul class="fanui-tree" data-fanui-tree data-fanui-tree-check>
|
|
1449
|
+
<script type="application/json">
|
|
1450
|
+
[{"label":"前端","value":"fe","children":[{"label":"Vue","value":"vue"},{"label":"React","value":"react"}]}]
|
|
1451
|
+
</script>
|
|
1452
|
+
</ul>
|
|
1453
|
+
```
|
|
1454
|
+
|
|
1455
|
+
| 属性 | 取值 | 作用 |
|
|
1456
|
+
|---|---|---|
|
|
1457
|
+
| `data-fanui-tree` | 无值即可 | 标记为树形控件(根元素即 `role="tree"`) |
|
|
1458
|
+
| `data-fanui-tree-check` | 无值即可 | 开启复选框(三态:选 / 半选 / 空) |
|
|
1459
|
+
|
|
1460
|
+
**命令式 API**:`FanUI.tree(el, opts)`
|
|
1461
|
+
|
|
1462
|
+
- `opts.items`:`Array`;`opts.checkable`:`Boolean`(缺省读 `data-fanui-tree-check`)。
|
|
1463
|
+
- 返回实例:`getChecked()` → `string[]`;`checkAll(v)` 全选 / 全清;`expandAll(v)` 展开 / 折叠全部。
|
|
1464
|
+
- 静态方法:`FanUI.tree.value(el)` → 已选 `string[]`(忽略半选项)。
|
|
1465
|
+
- 事件:`fanui:treeselect` `{ value, label }`;`fanui:treechange` `{ detail }`。
|
|
1466
|
+
|
|
1467
|
+
**可达性(原生 ARIA 树)**:根 `role="tree"`;直接子项 `li` `role="treeitem"` `aria-level`;子级 `ul` `role="group"`;展开态同步 `aria-expanded`,折叠态 `li.is-collapsed` 隐藏子级;复选框为标准 `<input type="checkbox">`,父节点半选通过 `indeterminate` 表达。
|
|
1468
|
+
|
|
1469
|
+
#### 10.1.6 穿梭框 `fanui-transfer`(FanUI.transfer)
|
|
1470
|
+
|
|
1471
|
+
双栏带搜索的列表搬运,左右移动选中项;数据来自相邻 JSON 或 `opts.items`。
|
|
1472
|
+
|
|
1473
|
+
```html
|
|
1474
|
+
<div data-fanui-transfer>
|
|
1475
|
+
<script type="application/json">
|
|
1476
|
+
[{"value":"1","label":"选项一","tag":"A"},{"value":"2","label":"选项二","disabled":true}]
|
|
1477
|
+
</script>
|
|
1478
|
+
</div>
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
| 属性 | 取值 | 作用 |
|
|
1482
|
+
|---|---|---|
|
|
1483
|
+
| `data-fanui-transfer` | 无值即可 | 标记为穿梭框(自动加 `fanui-transfer` 类并渲染双栏) |
|
|
1484
|
+
|
|
1485
|
+
**命令式 API**:`FanUI.transfer(el, opts)`
|
|
1486
|
+
|
|
1487
|
+
- `opts.items`:`Array`(每项 `{ value, label, tag?, disabled? }`,缺省读 JSON);`opts.value`:初始已选项 `string[]`。
|
|
1488
|
+
- 返回实例:`value()` → `string[]`;`set(vals)` 重设已选项。
|
|
1489
|
+
- 事件:`fanui:transferchange`,`detail = { value }`。
|
|
1490
|
+
|
|
1491
|
+
**可达性**:每项复选框 `aria-label="选择 X"`;禁用项 `is-disabled` 不响应点击且保持 AA 可读(底色 + 文字令牌,而非整体 `opacity`);左右「全部 / 清除」按钮为原生 `<button>`。
|
|
1492
|
+
|
|
1493
|
+
#### 10.1.7 顶部加载条 `fanui-loading-bar`(FanUI.loadingBar)
|
|
1494
|
+
|
|
1495
|
+
纯 JS API 的页面 / 请求进度条,无需任何 HTML 标记,首次调用自动向 `<body>` 注入。
|
|
1496
|
+
|
|
1497
|
+
```js
|
|
1498
|
+
FanUI.loadingBar.start(); // 开始:自动缓动爬升(≤92%)
|
|
1499
|
+
FanUI.loadingBar.set(60); // 设置确定进度 0~100
|
|
1500
|
+
FanUI.loadingBar.indeterminate(); // 不确定态(CSS 缓动爬升)
|
|
1501
|
+
FanUI.loadingBar.done(); // 填充到 100% 后淡出
|
|
1502
|
+
FanUI.loadingBar.isLoading(); // → Boolean 当前是否在加载
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
- 所有方法均返回 API 自身,可链式调用。
|
|
1506
|
+
- 修饰类:`fanui-loading-bar--indeterminate`(不确定态,纯 CSS 备选动画)。
|
|
1507
|
+
- **可达性**:根元素自动 `role="progressbar"` `aria-label="页面加载进度"`,并随进度更新 `aria-valuenow`。
|
|
1508
|
+
|
|
1509
|
+
### 10.2 表单结构组件(form-item / form-label / textarea)
|
|
1510
|
+
|
|
1511
|
+
#### 10.2.1 表单项 `fanui-form-item` 与标签 `fanui-form-label`
|
|
1512
|
+
|
|
1513
|
+
组合「标签 + 控件 + 反馈」的表单行;标签支持必填 / 选填标记,行支持校验状态着色。
|
|
1514
|
+
|
|
1515
|
+
```html
|
|
1516
|
+
<div class="fanui-form-item fanui-form-item--error">
|
|
1517
|
+
<label class="fanui-form-label fanui-form-label--required">邮箱</label>
|
|
1518
|
+
<input class="fanui-form-control" type="email">
|
|
1519
|
+
<span class="fanui-form-feedback">请输入有效邮箱</span>
|
|
1520
|
+
</div>
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
| 修饰类 | 作用 |
|
|
1524
|
+
|---|---|
|
|
1525
|
+
| `fanui-form-item--error` / `--success` / `--warning` | 校验状态(驱动边框色与反馈文字色,见 3.8 / 5 的 `data-fanui-validate`) |
|
|
1526
|
+
| `fanui-form-label--required`(或 `is-required`) | 必填:标签后加红色 `*` |
|
|
1527
|
+
| `fanui-form-label--optional` | 选填:标签后加「(选填)」 |
|
|
1528
|
+
| `.fanui-form--horizontal` / `.fanui-form--inline` | 容器水平 / 内联排布表单 |
|
|
1529
|
+
|
|
1530
|
+
无需 JS;校验状态也可由 `data-fanui-validate` 自动增删 `--error` / `--success`(见 3.8)。
|
|
1531
|
+
|
|
1532
|
+
#### 10.2.2 多行文本域 `fanui-textarea`
|
|
1533
|
+
|
|
1534
|
+
多行输入,与输入框共用尺寸体系。
|
|
1535
|
+
|
|
1536
|
+
```html
|
|
1537
|
+
<textarea class="fanui-textarea fanui-textarea--md" rows="4"></textarea>
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1540
|
+
| 修饰类 | 作用 |
|
|
1541
|
+
|---|---|
|
|
1542
|
+
| `fanui-textarea--xs` / `--sm` / `--md` / `--lg` / `--xl` | 高度尺寸(与 `fanui-form-control--*` 同套) |
|
|
1543
|
+
|
|
1544
|
+
其余同标准 `<textarea>`:`min-height: 88px`、`resize: vertical`、`:focus` 焦点环随主题色。
|
|
1545
|
+
|
|
1546
|
+
### 10.3 布局组件(stack / cluster)
|
|
1547
|
+
|
|
1548
|
+
#### 10.3.1 纵向堆叠 `fanui-stack`
|
|
1549
|
+
|
|
1550
|
+
纵向 flex 排列,子元素从上到下;配合 spacing 变体自动在相邻子项间加垂直间距(用 `> * + *` 选择器,首尾不溢出)。
|
|
1551
|
+
|
|
1552
|
+
```html
|
|
1553
|
+
<div class="fanui-stack fanui-stack-space">
|
|
1554
|
+
<div class="fanui-card">块一</div>
|
|
1555
|
+
<div class="fanui-card">块二</div>
|
|
1556
|
+
</div>
|
|
1557
|
+
```
|
|
1558
|
+
|
|
1559
|
+
| 修饰类 | 作用 |
|
|
1560
|
+
|---|---|
|
|
1561
|
+
| `fanui-stack-space` | 默认间距(令牌 `space-4`) |
|
|
1562
|
+
| `fanui-stack-space-sm` | 更紧凑(`space-2` 级别) |
|
|
1563
|
+
| `fanui-stack-space-lg` | 更宽松(`space-6` 级别) |
|
|
1564
|
+
|
|
1565
|
+
#### 10.3.2 横向簇状排布 `fanui-cluster`
|
|
1566
|
+
|
|
1567
|
+
横向、可自动换行的簇状布局;用负外边距抵消首尾 gap,使内容与容器边缘对齐。
|
|
1568
|
+
|
|
1569
|
+
```html
|
|
1570
|
+
<div class="fanui-cluster fanui-cluster--between">
|
|
1571
|
+
<button class="fanui-btn">左</button>
|
|
1572
|
+
<button class="fanui-btn">右</button>
|
|
1573
|
+
</div>
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
| 修饰类 | 作用 |
|
|
1577
|
+
|---|---|
|
|
1578
|
+
| `fanui-cluster--center` | 主轴居中 |
|
|
1579
|
+
| `fanui-cluster--between` | 两端对齐 |
|
|
1580
|
+
| `fanui-cluster--end` | 主轴靠右 |
|
|
1581
|
+
| `fanui-cluster--tight` | 间距收紧(`space-2`) |
|
|
1582
|
+
| `fanui-cluster--loose` | 间距放宽(`space-5`) |
|
|
1583
|
+
|
|
1584
|
+
### 10.4 基础 / 导航 / 营销组件(code / anchor / logo-cloud)
|
|
1585
|
+
|
|
1586
|
+
#### 10.4.1 行内代码 `fanui-code`
|
|
1587
|
+
|
|
1588
|
+
行内代码高亮:等宽字体、危险色文字、浅底圆角;代码块可用 `<pre>` 包裹。
|
|
1589
|
+
|
|
1590
|
+
```html
|
|
1591
|
+
在终端执行 <code class="fanui-code">npm i @zhang_jifan/fanui</code> 即可安装。
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
- 也自动作用于 `.fanui-prose code`(长文排版里的行内代码同款样式)。
|
|
1595
|
+
- 代码块:`<pre class="fanui-code">…</pre>` 或包裹 `<pre><code class="fanui-code">…</code></pre>`,保留等宽与浅底。
|
|
1596
|
+
|
|
1597
|
+
#### 10.4.2 锚点导航 `fanui-anchor` 与 `fanui-anchor__link`
|
|
1598
|
+
|
|
1599
|
+
`fanui-anchor` 给滚动锚点加 `scroll-margin-top`,避免被吸顶导航遮挡;`fanui-anchor__link` 是标题悬停时浮现的永久链接图标;配合 `data-fanui-scrollspy`(见 3.10)做目录高亮。
|
|
1600
|
+
|
|
1601
|
+
```html
|
|
1602
|
+
<nav data-fanui-scrollspy>
|
|
1603
|
+
<a href="#sec-a">章节 A</a>
|
|
1604
|
+
<a href="#sec-b">章节 B</a>
|
|
1605
|
+
</nav>
|
|
1606
|
+
|
|
1607
|
+
<h2 id="sec-a" class="fanui-anchor">
|
|
1608
|
+
章节 A <a class="fanui-anchor__link" href="#sec-a" aria-label="本节永久链接">#</a>
|
|
1609
|
+
</h2>
|
|
1610
|
+
```
|
|
1611
|
+
|
|
1612
|
+
- `data-fanui-scrollspy`:目录容器,内部 `a[href^="#"]` 在滚动时获得 `is-active`。
|
|
1613
|
+
- `fanui-anchor` 目标元素需有 `id`,与导航 `href="#id"` 对应。
|
|
1614
|
+
- 纯 CSS / 声明式,无需额外 JS。
|
|
1615
|
+
|
|
1616
|
+
#### 10.4.3 客户 Logo 墙 `fanui-logo-cloud`
|
|
1617
|
+
|
|
1618
|
+
营销区客户 Logo 网格,默认灰度、悬停去灰高亮并轻微上浮。
|
|
1619
|
+
|
|
1620
|
+
```html
|
|
1621
|
+
<div class="fanui-logo-cloud">
|
|
1622
|
+
<div class="fanui-logo-cloud__item"><img src="logo-a.svg" alt="客户 A"></div>
|
|
1623
|
+
<div class="fanui-logo-cloud__item">文字品牌名</div>
|
|
1624
|
+
</div>
|
|
1625
|
+
```
|
|
1626
|
+
|
|
1627
|
+
| 子元素 / 令牌 | 作用 |
|
|
1628
|
+
|---|---|
|
|
1629
|
+
| `fanui-logo-cloud__item` | 单个 Logo 单元(图片或文字品牌名均可) |
|
|
1630
|
+
| `--fanui-logo-min`(默认 `150px`) | 控制网格最小列宽,自动 `auto-fit` 适配 |
|
|
1631
|
+
|
|
1632
|
+
- 图片默认 `grayscale(1)` `opacity: .62`,悬停恢复并 `translateY(-2px)`;文字品牌名用令牌着色,保证 AA 可读(不靠整体 `opacity` 压暗)。
|
|
1633
|
+
|
|
1634
|
+
---
|
|
1635
|
+
|
|
1636
|
+
## 11. v2.1 能力补齐(虚拟滚动 / 灯箱 / 水印 / 引导 / 分割 / 固钉 / 回顶)
|
|
1637
|
+
|
|
1638
|
+
七项能力的共同约定:
|
|
1639
|
+
|
|
1640
|
+
- **声明式优先**:写 `data-fanui-*` 即生效,`FanUI.init()` 会统一初始化(幂等)。
|
|
1641
|
+
- **渐进增强**:**没有 JS 时内容依然可读**,SSR 直出即可见(见 §1.7)。
|
|
1642
|
+
- **RTL 友好**:内部一律用逻辑属性(`inset-inline` / `padding-inline` 等)。
|
|
1643
|
+
- **可访问**:键盘可达 + 焦点管理;全部处理 `prefers-reduced-motion`(关闭动画而非失去功能)。
|
|
1644
|
+
|
|
1645
|
+
### 11.1 虚拟滚动 `fanui-virtual-list`
|
|
1646
|
+
|
|
1647
|
+
只渲染视口内的行,支撑「上万行列表 / 大表格」而不卡死。
|
|
1648
|
+
|
|
1649
|
+
```html
|
|
1650
|
+
<div class="fanui-virtual-list" data-fanui-virtual-list
|
|
1651
|
+
data-fanui-item-height="44" data-fanui-vl-height="320px" aria-label="订单列表">
|
|
1652
|
+
<!-- 原始数据源:服务端直出 / 无 JS 时直接可读 -->
|
|
1653
|
+
<div class="fanui-virtual-list__source">
|
|
1654
|
+
<div class="fanui-virtual-list__item">第 1 行</div>
|
|
1655
|
+
<div class="fanui-virtual-list__item">第 2 行</div>
|
|
1656
|
+
</div>
|
|
1657
|
+
</div>
|
|
1658
|
+
```
|
|
1659
|
+
|
|
1660
|
+
```js
|
|
1661
|
+
const vl = FanUI.virtualList.create(document.querySelector("#list"), {
|
|
1662
|
+
itemHeight: 44, // 行高(px),默认 44
|
|
1663
|
+
overscan: 4, // 视口外预渲染行数,默认 4
|
|
1664
|
+
});
|
|
1665
|
+
vl.setItems(["a", "b", "c"]); // 换数据
|
|
1666
|
+
vl.scrollTo(500); // 滚到第 500 行
|
|
1667
|
+
vl.visibleRange(); // → { start, end }
|
|
1668
|
+
vl.count(); // 总行数
|
|
1669
|
+
vl.refresh(); // 重读原始源并重绘
|
|
1670
|
+
vl.destroy(); // 撤销增强,退回原始源
|
|
1671
|
+
|
|
1672
|
+
FanUI.virtualList.refreshAll(); // 窗口 resize 时库会自动调用
|
|
1673
|
+
```
|
|
1674
|
+
|
|
1675
|
+
| 类名 | 作用 |
|
|
1676
|
+
|---|---|
|
|
1677
|
+
| `fanui-virtual-list` | 滚动容器(固定高 + `overflow:auto`) |
|
|
1678
|
+
| `fanui-virtual-list__source` | **原始数据源**:SSR 直出 / 无 JS 时可见;JS 接管后隐藏 |
|
|
1679
|
+
| `fanui-virtual-list__inner` | 虚拟滚动层,高度 = 总行数 × 行高 |
|
|
1680
|
+
| `fanui-virtual-list__item` | 单行;未虚拟化时是普通流式块,虚拟化后绝对定位 |
|
|
1681
|
+
| `fanui-virtual-list__empty` | 空态 |
|
|
1682
|
+
| `.is-virtualized` | JS 接管标记(**加上它才隐藏 `__source`**) |
|
|
1683
|
+
| `.is-selected` | 选中行 |
|
|
1684
|
+
|
|
1685
|
+
| 属性 | 作用 |
|
|
1686
|
+
|---|---|
|
|
1687
|
+
| `data-fanui-virtual-list` | 声明式初始化开关 |
|
|
1688
|
+
| `data-fanui-item-height` | 行高(px),默认 `44` |
|
|
1689
|
+
| `data-fanui-overscan` | 视口外预渲染行数,默认 `4` |
|
|
1690
|
+
| `data-fanui-vl-height` | 容器高度(等价于 `--fanui-vl-height`) |
|
|
1691
|
+
| `data-fanui-vl-index` | 行序号(运行时写入,用于定位/调试) |
|
|
1692
|
+
|
|
1693
|
+
> **渐进增强语义(重要)**:`__source` 默认**可见**,`FanUI.ssr.render("virtualList", { items })`
|
|
1694
|
+
> 直出的行**不需要 JS 就能读**;`FanUI.init()` 给容器加上 `.is-virtualized` 后才隐藏原始源、
|
|
1695
|
+
> 改由 `__inner` 承担渲染。
|
|
1696
|
+
|
|
1697
|
+
### 11.2 图片灯箱 `fanui-lightbox`
|
|
1698
|
+
|
|
1699
|
+
点击缩略图放大预览,支持缩放、平移、前后切换与键盘操作(`Esc` 关闭、`←/→` 切换)。
|
|
1700
|
+
|
|
1701
|
+
```html
|
|
1702
|
+
<!-- 声明式:容器上写 data-fanui-lightbox,img 上写 src/thumb/caption -->
|
|
1703
|
+
<div class="fanui-lightbox-group" data-fanui-lightbox-group>
|
|
1704
|
+
<img class="fanui-lightbox-trigger" data-fanui-lightbox
|
|
1705
|
+
data-fanui-lightbox-src="big-1.jpg" data-fanui-lightbox-thumb="thumb-1.jpg"
|
|
1706
|
+
data-fanui-lightbox-caption="图一" src="thumb-1.jpg" alt="图一">
|
|
1707
|
+
<img class="fanui-lightbox-trigger" data-fanui-lightbox
|
|
1708
|
+
data-fanui-lightbox-src="big-2.jpg" src="thumb-2.jpg" alt="图二">
|
|
1709
|
+
</div>
|
|
1710
|
+
```
|
|
1711
|
+
|
|
1712
|
+
```js
|
|
1713
|
+
// 命令式:items = [{ src, thumb?, alt?, caption? }]
|
|
1714
|
+
FanUI.lightbox.open([
|
|
1715
|
+
{ src: "big-1.jpg", thumb: "thumb-1.jpg", alt: "图一", caption: "图一" },
|
|
1716
|
+
{ src: "big-2.jpg", alt: "图二" },
|
|
1717
|
+
], 0);
|
|
1718
|
+
FanUI.lightbox.go(1); // 切到第 2 张
|
|
1719
|
+
FanUI.lightbox.close(); // 关闭(焦点归还触发元素)
|
|
1720
|
+
FanUI.lightbox.collect(el); // 从 el 收集同组图片,返回 { list, index }
|
|
1721
|
+
```
|
|
1722
|
+
|
|
1723
|
+
| 类名 | 作用 |
|
|
1724
|
+
|---|---|
|
|
1725
|
+
| `fanui-lightbox` | 灯箱根浮层 |
|
|
1726
|
+
| `fanui-lightbox-trigger` | 缩略图触发元素 |
|
|
1727
|
+
| `__backdrop` / `__stage` / `__img` | 遮罩 / 舞台 / 图片 |
|
|
1728
|
+
| `__caption` / `__counter` | 说明文字 / 页码 |
|
|
1729
|
+
| `__toolbar` / `__btn` | 工具栏与按钮(缩放、关闭) |
|
|
1730
|
+
| `__nav` / `__nav--prev` / `__nav--next` | 前后切换 |
|
|
1731
|
+
| `__thumbstrip` | 底部缩略图条 |
|
|
1732
|
+
|
|
1733
|
+
### 11.3 防篡改水印 `fanui-watermark`
|
|
1734
|
+
|
|
1735
|
+
在容器上叠加文字/图片水印,定期校验并在被移除时自动重建(防篡改)。
|
|
1736
|
+
|
|
1737
|
+
```html
|
|
1738
|
+
<div class="fanui-card fanui-p-4" data-fanui-watermark="内部资料"
|
|
1739
|
+
data-fanui-watermark-sub="张三 · 2026-09-18"
|
|
1740
|
+
data-fanui-watermark-gap="120,80" data-fanui-watermark-rotate="-20"
|
|
1741
|
+
data-fanui-watermark-opacity="0.12" style="min-height:240px">
|
|
1742
|
+
正文内容……
|
|
1743
|
+
</div>
|
|
1744
|
+
```
|
|
1745
|
+
|
|
1746
|
+
```js
|
|
1747
|
+
const wm = FanUI.watermark.create(document.querySelector("#panel"), {
|
|
1748
|
+
text: "内部资料",
|
|
1749
|
+
subtext: "张三 · 2026-09-18",
|
|
1750
|
+
gap: [120, 80], // [x, y] 间距
|
|
1751
|
+
rotate: -20, // 角度
|
|
1752
|
+
opacity: 0.12,
|
|
1753
|
+
color: "#000", // 或 image: "logo.png"(图片水印,配 imageWidth/imageHeight)
|
|
1754
|
+
zIndex: 9,
|
|
1755
|
+
});
|
|
1756
|
+
wm.update(); // 参数变更后重绘
|
|
1757
|
+
wm.destroy(); // 移除水印与校验
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
| 属性 | 作用 |
|
|
1761
|
+
|---|---|
|
|
1762
|
+
| `data-fanui-watermark` | 主水印文案 |
|
|
1763
|
+
| `data-fanui-watermark-sub` | 副文案 |
|
|
1764
|
+
| `data-fanui-watermark-gap` | 间距,`x,y` 或单值 |
|
|
1765
|
+
| `data-fanui-watermark-rotate` | 旋转角度(度) |
|
|
1766
|
+
| `data-fanui-watermark-opacity` | 不透明度(`0–1`) |
|
|
1767
|
+
| `data-fanui-watermark-image` | 图片水印 URL(替代文字) |
|
|
1768
|
+
|
|
1769
|
+
| 类名 | 作用 |
|
|
1770
|
+
|---|---|
|
|
1771
|
+
| `fanui-watermark` | 水印层 |
|
|
1772
|
+
| `fanui-watermark__host` | 宿主容器(水印挂载点) |
|
|
1773
|
+
|
|
1774
|
+
### 11.4 引导 Tour `fanui-tour`
|
|
1775
|
+
|
|
1776
|
+
分步高亮引导,自动选边(优先下方,空间不足则上/右/左),带进度圆点与键盘关闭。
|
|
1777
|
+
|
|
1778
|
+
```html
|
|
1779
|
+
<!-- 声明式:按钮上写 JSON 步骤,点击即启动 -->
|
|
1780
|
+
<button type="button" data-fanui-tour='[
|
|
1781
|
+
{"target":"#nav-theme","title":"主题切换","desc":"这里可以切换明暗模式。","placement":"bottom"},
|
|
1782
|
+
{"target":"#accent-grid","title":"主题色","desc":"12 套主题色任选。","padding":12}
|
|
1783
|
+
]'>开始引导</button>
|
|
1784
|
+
```
|
|
1785
|
+
|
|
1786
|
+
```js
|
|
1787
|
+
const tour = FanUI.tour.create([
|
|
1788
|
+
{ target: "#nav-theme", title: "主题切换", desc: "这里可以切换明暗模式。", placement: "bottom" },
|
|
1789
|
+
{ target: () => document.querySelector("#accent-grid"), title: "主题色", desc: "12 套主题色任选。", padding: 12, radius: 12 },
|
|
1790
|
+
], {
|
|
1791
|
+
scrollBehavior: "smooth",
|
|
1792
|
+
onFinish: () => console.log("引导完成"),
|
|
1793
|
+
});
|
|
1794
|
+
tour.start();
|
|
1795
|
+
tour.next(); tour.prev(); tour.go(1); tour.stop(); tour.destroy();
|
|
1796
|
+
|
|
1797
|
+
// 声明式触发完成时会派发事件
|
|
1798
|
+
document.addEventListener("fanui:tour:finish", () => {});
|
|
1799
|
+
```
|
|
1800
|
+
|
|
1801
|
+
| 步骤字段 | 作用 |
|
|
1802
|
+
|---|---|
|
|
1803
|
+
| `target` | 选择器字符串 / 元素 / 返回元素的函数;缺省则居中显示(无高亮) |
|
|
1804
|
+
| `title` / `desc` | 标题与说明 |
|
|
1805
|
+
| `padding` | 高亮圈外扩像素,默认 `8` |
|
|
1806
|
+
| `radius` | 高亮圈圆角(px) |
|
|
1807
|
+
| `placement` | `auto`(默认)/ `bottom` / `top` / `left` / `right` |
|
|
1808
|
+
|
|
1809
|
+
| 项 | 值 |
|
|
1810
|
+
|---|---|
|
|
1811
|
+
| 实例方法 | `start()` / `next()` / `prev()` / `go(i)` / `stop()` / `destroy()` |
|
|
1812
|
+
| 事件 | `fanui:tour:change`、`fanui:tour:finish` |
|
|
1813
|
+
| 类名 | `fanui-tour`、`__mask`、`__spotlight`、`__popover`、`__arrow`、`__head`、`__title`、`__desc`、`__footer`、`__actions`、`__dots`、`__dot`、`__close` |
|
|
1814
|
+
|
|
1815
|
+
### 11.5 分割面板 `fanui-splitter`
|
|
1816
|
+
|
|
1817
|
+
可拖拽调整两栏比例;键盘可聚焦拖柄并用方向键调整(无障碍)。
|
|
1818
|
+
|
|
1819
|
+
```html
|
|
1820
|
+
<div class="fanui-splitter" data-fanui-splitter data-fanui-splitter-direction="horizontal" style="height:320px">
|
|
1821
|
+
<div class="fanui-splitter__pane">左栏</div>
|
|
1822
|
+
<div class="fanui-splitter__bar" role="separator" aria-orientation="vertical" tabindex="0">
|
|
1823
|
+
<span class="fanui-splitter__bar-handle" aria-hidden="true"></span>
|
|
1824
|
+
</div>
|
|
1825
|
+
<div class="fanui-splitter__pane">右栏</div>
|
|
1826
|
+
</div>
|
|
1827
|
+
```
|
|
1828
|
+
|
|
1829
|
+
```js
|
|
1830
|
+
FanUI.splitter.create(document.querySelector("#split"), {
|
|
1831
|
+
direction: "horizontal", // 或 "vertical"
|
|
1832
|
+
min: 15, // 每栏最小百分比
|
|
1833
|
+
max: 85, // 每栏最大百分比
|
|
1834
|
+
value: 50, // 初始比例(%)
|
|
1835
|
+
});
|
|
1836
|
+
```
|
|
1837
|
+
|
|
1838
|
+
| 项 | 值 |
|
|
1839
|
+
|---|---|
|
|
1840
|
+
| 类名 | `fanui-splitter`、`fanui-splitter--vertical`、`__pane`、`__bar`、`__bar-handle` |
|
|
1841
|
+
| 属性 | `data-fanui-splitter`、`data-fanui-splitter-direction="horizontal\|vertical"` |
|
|
1842
|
+
| CSS 变量 | `--fanui-splitter-size`(首栏尺寸) |
|
|
1843
|
+
|
|
1844
|
+
### 11.6 固钉 `Affix` → `fanui-affix`
|
|
1845
|
+
|
|
1846
|
+
元素滚动到阈值后吸顶(或吸附到指定容器),并插入占位块避免页面跳动。
|
|
1847
|
+
|
|
1848
|
+
```html
|
|
1849
|
+
<div class="fanui-affix" data-fanui-affix data-fanui-affix-offset="0"
|
|
1850
|
+
data-fanui-affix-target="#scroll-host">
|
|
1851
|
+
<div class="fanui-toolbar">吸顶工具栏</div>
|
|
1852
|
+
</div>
|
|
1853
|
+
```
|
|
1854
|
+
|
|
1855
|
+
```js
|
|
1856
|
+
FanUI.affix.create(document.querySelector("#toolbar"), {
|
|
1857
|
+
offsetTop: 0, // 距顶部多少像素时吸住
|
|
1858
|
+
target: "#scroll-host", // 相对某个滚动容器(默认视口)
|
|
1859
|
+
});
|
|
1860
|
+
FanUI.affix.updateAll(); // 布局变化后重新测量(resize 时库会自动调用)
|
|
1861
|
+
```
|
|
1862
|
+
|
|
1863
|
+
| 项 | 值 |
|
|
1864
|
+
|---|---|
|
|
1865
|
+
| 类名 | `fanui-affix`、`__placeholder`(占位,防跳动)、`__inner`、`.is-affixed`(吸住时) |
|
|
1866
|
+
| 属性 | `data-fanui-affix`、`data-fanui-affix-offset`、`data-fanui-affix-target` |
|
|
1867
|
+
| 实例方法 | `update()` / `destroy()` / `el` |
|
|
1868
|
+
|
|
1869
|
+
### 11.7 回到顶部 `fanui-backtop`
|
|
1870
|
+
|
|
1871
|
+
滚动超过阈值后浮现,带环形进度;未指定宿主时**自动创建一个按钮**。
|
|
1872
|
+
|
|
1873
|
+
```html
|
|
1874
|
+
<!-- 方式一:自己给按钮 -->
|
|
1875
|
+
<button type="button" class="fanui-backtop" data-fanui-backtop
|
|
1876
|
+
data-fanui-backtop-offset="320" data-fanui-backtop-container="#scroll-host"
|
|
1877
|
+
aria-label="回到顶部">↑</button>
|
|
1878
|
+
|
|
1879
|
+
<!-- 方式二:一个都不写,让库自动创建 -->
|
|
1880
|
+
<html data-fanui-backtop-auto="true">
|
|
1881
|
+
```
|
|
1882
|
+
|
|
1883
|
+
```js
|
|
1884
|
+
const bt = FanUI.backTop.create(null, {
|
|
1885
|
+
container: "#scroll-host", // 默认整页
|
|
1886
|
+
offset: 320, // 超过该滚动距离才显示,默认 320
|
|
1887
|
+
duration: 420, // 回滚动画时长(ms),默认 420
|
|
1888
|
+
});
|
|
1889
|
+
bt.show(); bt.hide(); bt.update();
|
|
1890
|
+
document.addEventListener("fanui:backtop:click", () => {});
|
|
1891
|
+
```
|
|
1892
|
+
|
|
1893
|
+
| 项 | 值 |
|
|
1894
|
+
|---|---|
|
|
1895
|
+
| 类名 | `fanui-backtop`、`__ring`(进度环)、`__icon`、`.is-visible`(浮现时)、`.is-with-ring` |
|
|
1896
|
+
| 属性 | `data-fanui-backtop`、`data-fanui-backtop-offset`、`data-fanui-backtop-container`、`data-fanui-backtop-auto="true"`(写在 `<html>` 上自动创建) |
|
|
1897
|
+
| CSS 变量 | `--fanui-backtop-progress`(进度百分比,运行时写入) |
|
|
1898
|
+
| 事件 | `fanui:backtop:click` |
|
|
1899
|
+
|
|
1900
|
+
### 11.8 服务端直出(SSR)与这七项能力
|
|
1901
|
+
|
|
1902
|
+
这七项都提供了 `FanUI.ssr` 渲染入口,可在 Next / Nuxt / Astro 的服务端直接产出标记:
|
|
1903
|
+
|
|
1904
|
+
```js
|
|
1905
|
+
// 服务端(Node)—— require("@zhang_jifan/fanui") 不再返回 null
|
|
1906
|
+
import FanUI from "@zhang_jifan/fanui";
|
|
1907
|
+
if (FanUI.isServer) {
|
|
1908
|
+
const html = FanUI.ssr.document({
|
|
1909
|
+
title: "订单列表",
|
|
1910
|
+
css: "/fanui.css",
|
|
1911
|
+
accent: "cyan", theme: "light",
|
|
1912
|
+
bodyHtml: FanUI.ssr.render("virtualList", { items: ["A", "B", "C"], itemHeight: 44 }),
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
```
|
|
1916
|
+
|
|
1917
|
+
`FanUI.ssr` 覆盖 24 个组件:`button` `badge` `tag` `avatar` `divider` `code` `kbd` `alert`
|
|
1918
|
+
`progress` `spinner` `skeleton` `empty` `result` `card` `stat` `breadcrumb` `descriptions`
|
|
1919
|
+
`listGroup` `table` `timeline` `virtualList` `splitter` `backTop` `watermark`。详见 §1.7。
|
|
1920
|
+
|
|
1921
|
+
---
|
|
1922
|
+
|
|
1923
|
+
|
|
1924
|
+
|
|
1925
|
+
|
|
1926
|
+
|