@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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
* fanUI · React 适配 Hook(React 16.8+ 全版本通用)
|
|
3
|
+
* -----------------------------------------------------------------------------
|
|
4
|
+
* 设计要点:
|
|
5
|
+
* 1. fanUI 的样式挂在 <html> 的 data-fanui-* 属性上,与 React 树无关 ——
|
|
6
|
+
* 所以三轴状态用一个「全局单例」保存,React 只负责把状态映射到 DOM。
|
|
7
|
+
* 2. SSR 下 FanUI 可能为 null(服务端无 DOM),全部走空值兜底。
|
|
8
|
+
* 3. init() 幂等(内部有 __fanuiInit 防重复标记),React 18 StrictMode
|
|
9
|
+
* 的 double-effect 也不会重复初始化。
|
|
10
|
+
* ========================================================================== */
|
|
11
|
+
import { useEffect, useState, useCallback } from "react";
|
|
12
|
+
import FanUI from "@zhang_jifan/fanui";
|
|
13
|
+
|
|
14
|
+
/** 挂载一次:初始化声明式组件 + 主题工作台;SSR 安全 */
|
|
15
|
+
export function useFanUI(options) {
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!FanUI) return; // SSR / 非浏览器环境
|
|
18
|
+
FanUI.init(); // 幂等:重复调用无副作用
|
|
19
|
+
if (options && options.studio) FanUI.studio.mount({ defaultOpen: false });
|
|
20
|
+
}, []);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 订阅主题三轴状态(12 主题色 / 深浅模式 / 玻璃),返回 [状态, 操作方法] */
|
|
24
|
+
export function useFanTheme() {
|
|
25
|
+
const read = useCallback(() => {
|
|
26
|
+
if (!FanUI) return null;
|
|
27
|
+
const s = FanUI.theme.snapshot();
|
|
28
|
+
return {
|
|
29
|
+
...s,
|
|
30
|
+
accents: FanUI.theme.accents, // 12 套主题元数据
|
|
31
|
+
presets: FanUI.theme.glassPresets, // 5 个玻璃预设
|
|
32
|
+
};
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const [state, setState] = useState(read);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!FanUI) return;
|
|
39
|
+
const sync = () => setState(read());
|
|
40
|
+
["fanui:accentchange", "fanui:themechange", "fanui:glasschange"]
|
|
41
|
+
.forEach((ev) => document.addEventListener(ev, sync));
|
|
42
|
+
return () => {
|
|
43
|
+
["fanui:accentchange", "fanui:themechange", "fanui:glasschange"]
|
|
44
|
+
.forEach((ev) => document.removeEventListener(ev, sync));
|
|
45
|
+
};
|
|
46
|
+
}, [read]);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
state,
|
|
50
|
+
setAccent: (k) => FanUI && FanUI.theme.setAccent(k),
|
|
51
|
+
nextAccent: () => FanUI && FanUI.theme.nextAccent(),
|
|
52
|
+
setMode: (m) => FanUI && FanUI.theme.setMode(m),
|
|
53
|
+
toggleMode: () => FanUI && FanUI.theme.toggleMode(),
|
|
54
|
+
setGlass: (on) => FanUI && FanUI.theme.setGlass(on),
|
|
55
|
+
setGlassIntensity: (v) => FanUI && FanUI.theme.setGlassIntensity(v),
|
|
56
|
+
setGlassPreset: (p) => FanUI && FanUI.theme.setGlassPreset(p),
|
|
57
|
+
createAccent: (hex, name) => FanUI && FanUI.theme.createAccent(hex, name),
|
|
58
|
+
reset: () => FanUI && FanUI.theme.reset(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 命令式浮层:返回 { open, close },组件卸载时自动关闭 */
|
|
63
|
+
export function useFanOverlay(selector, options) {
|
|
64
|
+
const [inst, setInst] = useState(null);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!FanUI) return;
|
|
67
|
+
const o = new FanUI.overlay(selector, options);
|
|
68
|
+
setInst(o);
|
|
69
|
+
return () => o.close(); // 卸载时收尾,防滚动锁残留
|
|
70
|
+
}, [selector]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
71
|
+
return {
|
|
72
|
+
open: () => inst && inst.open(),
|
|
73
|
+
close: () => inst && inst.close(),
|
|
74
|
+
toggle: () => inst && inst.toggle(),
|
|
75
|
+
instance: inst,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Toast 捷径 */
|
|
80
|
+
export function useToast() {
|
|
81
|
+
return {
|
|
82
|
+
success: (t, d) => FanUI && FanUI.toast.success(t, d),
|
|
83
|
+
error: (t, d) => FanUI && FanUI.toast.error(t, d),
|
|
84
|
+
info: (t, d) => FanUI && FanUI.toast.info(t, d),
|
|
85
|
+
warning: (t, d) => FanUI && FanUI.toast.warning(t, d),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# fanUI × Vue 3 示例(Vite + Vue 3.5)
|
|
2
|
+
|
|
3
|
+
## 运行
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd examples/03-vue3
|
|
7
|
+
npm install # fanui 通过 "file:../.." 直接引用仓库源码
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Vue 2 的写法差异
|
|
12
|
+
|
|
13
|
+
fanUI 本身与 Vue 版本无关(CSS 全局 + 命令式 JS),差异只在「初始化时机」:
|
|
14
|
+
|
|
15
|
+
**Vue 2.7(内置组合式 API)**:本目录的 `useFanUI.js` 把 `vue` 的导入换成 Vue 2.7 的
|
|
16
|
+
`import { ref, onMounted, onBeforeUnmount, reactive } from "vue"` 即可,其余不变。
|
|
17
|
+
|
|
18
|
+
**Vue 2.6 及以下(Options API)**:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// main.js
|
|
22
|
+
import Vue from "vue";
|
|
23
|
+
import App from "./App.vue";
|
|
24
|
+
import "@zhang_jifan/fanui/style.css";
|
|
25
|
+
|
|
26
|
+
Vue.config.productionTip = false;
|
|
27
|
+
|
|
28
|
+
new Vue({
|
|
29
|
+
mounted() {
|
|
30
|
+
if (window.FanUI) window.FanUI.init(); // 初始化声明式组件
|
|
31
|
+
},
|
|
32
|
+
beforeDestroy() {
|
|
33
|
+
// 组件卸载前关闭浮层,避免滚动锁残留
|
|
34
|
+
},
|
|
35
|
+
render: (h) => h(App),
|
|
36
|
+
}).$mount("#app");
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<!-- App.vue 里的用法与 Vue 3 完全一致 -->
|
|
41
|
+
<template>
|
|
42
|
+
<button class="fanui-btn fanui-btn--primary" @click="$fanui.toast.success('ok')">确定</button>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
export default {
|
|
47
|
+
data: () => ({ theme: null }),
|
|
48
|
+
mounted() {
|
|
49
|
+
this.theme = window.FanUI ? window.FanUI.theme.snapshot() : null;
|
|
50
|
+
// 订阅主题变化(可选)
|
|
51
|
+
if (window.FanUI) {
|
|
52
|
+
this._onAccent = () => { this.theme = window.FanUI.theme.snapshot(); };
|
|
53
|
+
document.addEventListener("fanui:accentchange", this._onAccent);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
beforeDestroy() {
|
|
57
|
+
if (this._onAccent) document.removeEventListener("fanui:accentchange", this._onAccent);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
</script>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Nuxt 3 / Nuxt 2
|
|
64
|
+
|
|
65
|
+
- 样式:`nuxt.config.ts` → `css: ["@zhang_jifan/fanui/style.css"]`。
|
|
66
|
+
- JS:写一个客户端插件 `plugins/fanui.client.ts`,内容为 `import FanUI from "@zhang_jifan/fanui"; export default () => FanUI && FanUI.init();`
|
|
67
|
+
- fanUI 在无 DOM 环境安全导出 `null`,SSR 阶段不会报错。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" data-fanui-theme="light" data-fanui-accent="cyan" data-fanui-glass="on">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>fanUI · Vue 3 示例</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fanui-example-vue3",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "2.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@zhang_jifan/fanui": "file:../..",
|
|
13
|
+
"vue": "^3.5.13"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
17
|
+
"vite": "^5.4.11"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/* =============================================================================
|
|
3
|
+
* fanUI × Vue 3(<script setup> 组合式 API)
|
|
4
|
+
* 模板里的 class 与官方文档完全一致,无需任何改写。
|
|
5
|
+
* ========================================================================== */
|
|
6
|
+
import { ref } from "vue";
|
|
7
|
+
import { useFanUI, useFanTheme, useFanOverlay, useToast } from "./useFanUI.js";
|
|
8
|
+
|
|
9
|
+
useFanUI({ studio: true });
|
|
10
|
+
const theme = useFanTheme();
|
|
11
|
+
const toast = useToast();
|
|
12
|
+
const modal = useFanOverlay("#demoModal");
|
|
13
|
+
const brand = ref("#ff6b35");
|
|
14
|
+
|
|
15
|
+
function applyBrand() {
|
|
16
|
+
const name = theme.createAccent(brand.value);
|
|
17
|
+
toast.success("已生成主题:" + name);
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<main class="fanui-container" style="padding: 32px 16px">
|
|
23
|
+
<h1 class="fanui-fs-3xl fanui-fw-bold">fanUI × Vue 3 示例</h1>
|
|
24
|
+
<p class="fanui-text-muted" v-if="theme.ready.value">
|
|
25
|
+
主题色 {{ theme.state.accent }} · 模式 {{ theme.state.mode }}(实际
|
|
26
|
+
{{ theme.state.effectiveMode }})· 玻璃 {{ theme.state.glass ? "on" : "off" }}
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<!-- 主题色 12 选 1 -->
|
|
30
|
+
<section class="fanui-card fanui-p-6" style="margin-top: 24px">
|
|
31
|
+
<h2 class="fanui-fs-xl fanui-fw-semibold">主题色</h2>
|
|
32
|
+
<div class="fanui-flex fanui-flex-wrap fanui-gap-2" style="margin-top: 12px">
|
|
33
|
+
<button
|
|
34
|
+
v-for="a in theme.state.accents"
|
|
35
|
+
:key="a.key"
|
|
36
|
+
type="button"
|
|
37
|
+
class="fanui-btn fanui-btn--sm"
|
|
38
|
+
:class="theme.state.accent === a.key ? 'fanui-btn--primary' : 'fanui-btn--neutral'"
|
|
39
|
+
:title="a.vibe"
|
|
40
|
+
@click="theme.setAccent(a.key)"
|
|
41
|
+
>
|
|
42
|
+
{{ a.label }} {{ a.en }}
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="fanui-flex fanui-flex-wrap fanui-gap-2" style="margin-top: 16px">
|
|
47
|
+
<button class="fanui-btn fanui-btn--neutral" @click="theme.toggleMode">浅色 / 深色</button>
|
|
48
|
+
<button class="fanui-btn fanui-btn--neutral" @click="theme.setGlass(!theme.state.glass)">
|
|
49
|
+
玻璃{{ theme.state.glass ? "关" : "开" }}
|
|
50
|
+
</button>
|
|
51
|
+
<button class="fanui-btn fanui-btn--ghost" @click="theme.nextAccent">下一个主题色</button>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<!-- 任意品牌色 → 主题 -->
|
|
55
|
+
<div class="fanui-flex fanui-gap-2" style="margin-top: 16px">
|
|
56
|
+
<input v-model="brand" class="fanui-form-control" style="max-width: 200px" placeholder="#ff6b35" />
|
|
57
|
+
<button class="fanui-btn fanui-btn--primary" @click="applyBrand">由品牌色生成主题</button>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<!-- 玻璃卡片 -->
|
|
62
|
+
<section class="fanui-card fanui-card--glass fanui-p-6" style="margin-top: 20px">
|
|
63
|
+
<h2 class="fanui-fs-xl fanui-fw-semibold">液态玻璃卡片</h2>
|
|
64
|
+
<div class="fanui-flex fanui-flex-wrap fanui-gap-2" style="margin-top: 12px">
|
|
65
|
+
<span class="fanui-badge fanui-badge--primary">Vue 3.0+</span>
|
|
66
|
+
<span class="fanui-badge fanui-badge--success fanui-badge--soft">组合式 API</span>
|
|
67
|
+
<span class="fanui-badge fanui-badge--warning fanui-badge--outline">模板零改写</span>
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<div class="fanui-flex fanui-gap-2" style="margin-top: 20px">
|
|
72
|
+
<button class="fanui-btn fanui-btn--primary" @click="modal.open">打开模态框</button>
|
|
73
|
+
<button class="fanui-btn fanui-btn--neutral" @click="toast.success('操作成功', '来自 Vue 事件')">弹 Toast</button>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- 浮层 DOM:class 与官方文档一致 -->
|
|
77
|
+
<div class="fanui-modal fanui-modal--md" id="demoModal" aria-hidden="true">
|
|
78
|
+
<div class="fanui-modal__backdrop"></div>
|
|
79
|
+
<div class="fanui-modal__dialog">
|
|
80
|
+
<header class="fanui-modal__header">
|
|
81
|
+
<h3 class="fanui-modal__title">Vue 3 中的模态框</h3>
|
|
82
|
+
<button class="fanui-modal__close" data-fanui-dismiss aria-label="关闭">×</button>
|
|
83
|
+
</header>
|
|
84
|
+
<div class="fanui-modal__body">
|
|
85
|
+
<p>Esc / 遮罩 / 关闭按钮都能退出;组件卸载时 Hook 会自动 close。</p>
|
|
86
|
+
</div>
|
|
87
|
+
<footer class="fanui-modal__footer">
|
|
88
|
+
<button class="fanui-btn fanui-btn--primary" data-fanui-dismiss>知道了</button>
|
|
89
|
+
</footer>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</main>
|
|
93
|
+
</template>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
* fanUI · Vue 3 组合式 API 适配(Vue 3.0+)
|
|
3
|
+
* ========================================================================== */
|
|
4
|
+
import { ref, onMounted, onBeforeUnmount, reactive } from "vue";
|
|
5
|
+
import FanUI from "@zhang_jifan/fanui";
|
|
6
|
+
|
|
7
|
+
/** 初始化一次:挂到组件的 onMounted 里(幂等,重复调用无副作用) */
|
|
8
|
+
export function useFanUI(options) {
|
|
9
|
+
onMounted(() => {
|
|
10
|
+
if (!FanUI) return; // SSR 空值兜底
|
|
11
|
+
FanUI.init();
|
|
12
|
+
if (options && options.studio) FanUI.studio.mount({ defaultOpen: false });
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 主题三轴响应式状态(12 主题色 / 深浅 / 玻璃) */
|
|
17
|
+
export function useFanTheme() {
|
|
18
|
+
const ready = ref(!!FanUI);
|
|
19
|
+
const state = reactive({
|
|
20
|
+
accent: "cyan",
|
|
21
|
+
mode: "auto",
|
|
22
|
+
effectiveMode: "light",
|
|
23
|
+
glass: true,
|
|
24
|
+
glassIntensity: 1,
|
|
25
|
+
glassPreset: "regular",
|
|
26
|
+
accents: [],
|
|
27
|
+
presets: {},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function sync() {
|
|
31
|
+
if (!FanUI) return;
|
|
32
|
+
Object.assign(state, FanUI.theme.snapshot());
|
|
33
|
+
state.accents = FanUI.theme.accents;
|
|
34
|
+
state.presets = FanUI.theme.glassPresets;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onMounted(() => {
|
|
38
|
+
if (!FanUI) return;
|
|
39
|
+
sync();
|
|
40
|
+
["fanui:accentchange", "fanui:themechange", "fanui:glasschange"]
|
|
41
|
+
.forEach((ev) => document.addEventListener(ev, sync));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
onBeforeUnmount(() => {
|
|
45
|
+
if (!FanUI) return;
|
|
46
|
+
["fanui:accentchange", "fanui:themechange", "fanui:glasschange"]
|
|
47
|
+
.forEach((ev) => document.removeEventListener(ev, sync));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
ready,
|
|
52
|
+
state,
|
|
53
|
+
setAccent: (k) => FanUI && FanUI.theme.setAccent(k),
|
|
54
|
+
nextAccent: () => FanUI && FanUI.theme.nextAccent(),
|
|
55
|
+
setMode: (m) => FanUI && FanUI.theme.setMode(m),
|
|
56
|
+
toggleMode: () => FanUI && FanUI.theme.toggleMode(),
|
|
57
|
+
setGlass: (on) => FanUI && FanUI.theme.setGlass(on),
|
|
58
|
+
setGlassIntensity: (v) => FanUI && FanUI.theme.setGlassIntensity(v),
|
|
59
|
+
setGlassPreset: (p) => FanUI && FanUI.theme.setGlassPreset(p),
|
|
60
|
+
createAccent: (hex, name) => FanUI && FanUI.theme.createAccent(hex, name),
|
|
61
|
+
reset: () => FanUI && FanUI.theme.reset(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 命令式浮层:组件卸载自动 close,防滚动锁残留 */
|
|
66
|
+
export function useFanOverlay(selector, options) {
|
|
67
|
+
let inst = null;
|
|
68
|
+
onMounted(() => { if (FanUI) inst = new FanUI.overlay(selector, options); });
|
|
69
|
+
onBeforeUnmount(() => { if (inst) inst.close(); });
|
|
70
|
+
return {
|
|
71
|
+
open: () => inst && inst.open(),
|
|
72
|
+
close: () => inst && inst.close(),
|
|
73
|
+
toggle: () => inst && inst.toggle(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Toast 捷径 */
|
|
78
|
+
export function useToast() {
|
|
79
|
+
return {
|
|
80
|
+
success: (t, d) => FanUI && FanUI.toast.success(t, d),
|
|
81
|
+
error: (t, d) => FanUI && FanUI.toast.error(t, d),
|
|
82
|
+
info: (t, d) => FanUI && FanUI.toast.info(t, d),
|
|
83
|
+
warning: (t, d) => FanUI && FanUI.toast.warning(t, d),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<!-- =============================================================================
|
|
3
|
+
fanUI · 示例 04:Vue 2 全局构建(CDN)+ fanUI script 引入
|
|
4
|
+
-----------------------------------------------------------------------------
|
|
5
|
+
- 无需打包器:Vue 2 用官方全局构建(vue@2.7),fanUI 用 dist 产物
|
|
6
|
+
- 除 fanUI 本地文件外,Vue 来自 CDN(首次打开需要联网)
|
|
7
|
+
- 直接双击即可运行;适用于老旧 Vue 2.6/2.7 项目无法升级构建链的场景
|
|
8
|
+
========================================================================== -->
|
|
9
|
+
<html lang="zh-CN" data-fanui-theme="light" data-fanui-accent="cyan" data-fanui-glass="on">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="UTF-8">
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
13
|
+
<title>fanUI 示例 04 · Vue 2(CDN 全局构建)</title>
|
|
14
|
+
<link rel="stylesheet" href="../dist/fanui.css">
|
|
15
|
+
<style>
|
|
16
|
+
body { padding: 32px; max-width: 900px; margin: 0 auto; }
|
|
17
|
+
.muted { color: var(--fanui-color-text-muted); font-size: .875rem; }
|
|
18
|
+
</style>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<div id="app">
|
|
22
|
+
<!-- Vue 2 模板语法 + fanUI 类名混用,完全无冲突 -->
|
|
23
|
+
<main class="fanui-container">
|
|
24
|
+
<h1 class="fanui-fs-3xl fanui-fw-bold">fanUI × Vue 2(CDN)</h1>
|
|
25
|
+
<p class="muted">
|
|
26
|
+
主题色 {{ snapshot.accent }} · 模式 {{ snapshot.mode }} · 玻璃 {{ snapshot.glass ? 'on' : 'off' }}
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<section class="fanui-card fanui-p-6" style="margin-top: 24px">
|
|
30
|
+
<h2 class="fanui-fs-xl fanui-fw-semibold">12 主题色</h2>
|
|
31
|
+
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:12px">
|
|
32
|
+
<button v-for="a in accents" :key="a.key" type="button"
|
|
33
|
+
class="fanui-btn fanui-btn--sm"
|
|
34
|
+
:class="snapshot.accent === a.key ? 'fanui-btn--primary' : 'fanui-btn--neutral'"
|
|
35
|
+
:title="a.vibe" @click="setAccent(a.key)">
|
|
36
|
+
{{ a.label }} {{ a.en }}
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px">
|
|
41
|
+
<button class="fanui-btn fanui-btn--neutral" @click="toggleMode">浅色 / 深色</button>
|
|
42
|
+
<button class="fanui-btn fanui-btn--neutral" @click="toggleGlass">玻璃 开 / 关</button>
|
|
43
|
+
</div>
|
|
44
|
+
</section>
|
|
45
|
+
|
|
46
|
+
<section class="fanui-card fanui-card--glass fanui-p-6" style="margin-top: 20px">
|
|
47
|
+
<h2 class="fanui-fs-xl fanui-fw-semibold">液态玻璃卡片</h2>
|
|
48
|
+
<p class="muted" style="margin-top:8px">
|
|
49
|
+
Vue 的响应式只负责「自己的数据」;fanUI 的主题状态走它自己的事件系统,
|
|
50
|
+
两者互不干扰、按需同步。
|
|
51
|
+
</p>
|
|
52
|
+
<span class="fanui-badge fanui-badge--primary">Vue 2.7</span>
|
|
53
|
+
<span class="fanui-badge fanui-badge--success fanui-badge--soft" style="margin-left:8px">Vue 2.6 同样可用</span>
|
|
54
|
+
</section>
|
|
55
|
+
</main>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- fanUI 运行时(可选,用于主题控制与声明式组件) -->
|
|
59
|
+
<script src="../dist/fanui.js"></script>
|
|
60
|
+
<!-- Vue 2 官方全局构建(首次打开需联网;内网环境请改为本地文件) -->
|
|
61
|
+
<script src="https://unpkg.com/vue@2.7.16/dist/vue.min.js"></script>
|
|
62
|
+
<script>
|
|
63
|
+
new Vue({
|
|
64
|
+
el: "#app",
|
|
65
|
+
data: function () {
|
|
66
|
+
return {
|
|
67
|
+
accents: window.FanUI ? window.FanUI.theme.accents : [],
|
|
68
|
+
snapshot: window.FanUI
|
|
69
|
+
? window.FanUI.theme.snapshot()
|
|
70
|
+
: { accent: "-", mode: "-", glass: false },
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
mounted: function () {
|
|
74
|
+
var vm = this;
|
|
75
|
+
if (!window.FanUI) return;
|
|
76
|
+
window.FanUI.init(); // 初始化声明式组件(幂等)
|
|
77
|
+
// 把 fanUI 主题事件同步进 Vue 响应式数据
|
|
78
|
+
["fanui:accentchange", "fanui:themechange", "fanui:glasschange"].forEach(function (ev) {
|
|
79
|
+
document.addEventListener(ev, function () {
|
|
80
|
+
vm.snapshot = window.FanUI.theme.snapshot();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
window.FanUI.studio.mount({ defaultOpen: false });
|
|
84
|
+
},
|
|
85
|
+
methods: {
|
|
86
|
+
setAccent: function (k) { window.FanUI && window.FanUI.theme.setAccent(k); },
|
|
87
|
+
toggleMode: function () { window.FanUI && window.FanUI.theme.toggleMode(); },
|
|
88
|
+
toggleGlass: function () { window.FanUI && window.FanUI.theme.toggleGlass(); },
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
</script>
|
|
92
|
+
</body>
|
|
93
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# fanUI · 示例总览
|
|
2
|
+
|
|
3
|
+
| 目录 / 文件 | 引入方式 | 技术栈 | 运行方式 |
|
|
4
|
+
|---|---|---|---|
|
|
5
|
+
| [`01-script-tag.html`](./01-script-tag.html) | `<script>` + `<link>` 本地文件 | 原生 HTML/JS | **双击直接打开,不需要构建、不需要联网** ✅ |
|
|
6
|
+
| [`02-react/`](./02-react/) | npm(`file:../..` 本地包) | React 18 + Vite | `npm install && npm run dev` |
|
|
7
|
+
| [`03-vue3/`](./03-vue3/) | npm(`file:../..` 本地包) | Vue 3 + Vite | `npm install && npm run dev` |
|
|
8
|
+
| [`04-vue2-cdn.html`](./04-vue2-cdn.html) | `<script>` 全局构建 | Vue 2.7(CDN) | 双击打开(Vue 来自 CDN,需联网) |
|
|
9
|
+
|
|
10
|
+
> 建议先跑 `01`(保证零门槛跑通),再按项目技术栈选 `02/03/04`。
|
|
11
|
+
|
|
12
|
+
> ⚠️ **两个实测结论(避免踩坑)**
|
|
13
|
+
>
|
|
14
|
+
> 1. **`<script type="module">` 不能用 `file://` 双击打开。** 浏览器对 `file://` 下的
|
|
15
|
+
> ES Module 有 CORS 限制,会直接报
|
|
16
|
+
> `Access to script ... has been blocked by CORS policy`,
|
|
17
|
+
> 页面表现为「样式正常但 JS 完全没生效」。示例 `02` / `03` 请用 `npm run dev`
|
|
18
|
+
> (或任意 http 服务)打开;只有用经典 `<script src>` 的 `01` / `04` 才能双击运行。
|
|
19
|
+
> 2. **走 npm 引入时用这些路径**(都已实测):样式 `@zhang_jifan/fanui/dist/fanui.css`
|
|
20
|
+
> (或 `@zhang_jifan/fanui/style.css` / `@zhang_jifan/fanui/style.min.css`);JS 用 `@zhang_jifan/fanui/dist/fanui.js`
|
|
21
|
+
> (`<script>` 标签)或 `import ... from "@zhang_jifan/fanui"`(ESM/打包器)。
|
|
22
|
+
> 包内自带可直接打开的示例:`node_modules/@zhang_jifan/fanui/examples/01-script-tag.html`。
|
|
23
|
+
|
|
24
|
+
## 各示例演示内容
|
|
25
|
+
|
|
26
|
+
- **01 / 04**:三轴主题控制(12 主题色、深浅模式、玻璃开关与强度)、液态玻璃卡片、
|
|
27
|
+
表单校验、声明式模态框、Toast、12 色卡快捷切换、主题工作台悬浮面板。
|
|
28
|
+
- **02(React)**:React Hook 适配层(`useFanUI` / `useFanTheme` / `useFanOverlay` / `useToast`)、
|
|
29
|
+
品牌色一键生成主题、React 16/17/18/19 版本差异说明。
|
|
30
|
+
- **03(Vue 3)**:组合式 API 适配层、Vue 2 Options API 等价写法、Nuxt 3 接入说明。
|
|
31
|
+
|
|
32
|
+
## 截图
|
|
33
|
+
|
|
34
|
+
浏览器回归产物由 `node scripts/verify-examples.cjs` 生成到 `shots/`(属测试产物,**不随 npm 包发布**)。
|
package/package.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zhang_jifan/fanui",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "fanUI —— 一套 UI 设计系统,内置 12 套可在运行时切换的主题配色,支持液态玻璃质感、明暗双模式;适配官网、博客与 B 端控制台场景。下载即可使用:可直接用 script/link 引入编译产物,也可通过 npm + ESM / SCSS 接入项目。",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"css",
|
|
7
|
+
"scss",
|
|
8
|
+
"ui",
|
|
9
|
+
"framework",
|
|
10
|
+
"design-system",
|
|
11
|
+
"design-tokens",
|
|
12
|
+
"theme",
|
|
13
|
+
"dark-mode",
|
|
14
|
+
"glassmorphism",
|
|
15
|
+
"grid",
|
|
16
|
+
"components",
|
|
17
|
+
"vanilla-js",
|
|
18
|
+
"react",
|
|
19
|
+
"vue"
|
|
20
|
+
],
|
|
21
|
+
"license": "AGPL-3.0-or-later",
|
|
22
|
+
"author": {
|
|
23
|
+
"name": "zhang_jifan",
|
|
24
|
+
"url": "https://gitee.com/zhang_jifan"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://gitee.com/zhang_jifan/fan-ui#readme",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://gitee.com/zhang_jifan/fan-ui.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://gitee.com/zhang_jifan/fan-ui/issues"
|
|
33
|
+
},
|
|
34
|
+
"main": "dist/fanui.cjs",
|
|
35
|
+
"module": "dist/fanui.esm.js",
|
|
36
|
+
"browser": "dist/fanui.esm.js",
|
|
37
|
+
"types": "dist/fanui.d.ts",
|
|
38
|
+
"unpkg": "dist/fanui.js",
|
|
39
|
+
"jsdelivr": "dist/fanui.js",
|
|
40
|
+
"style": "dist/fanui.css",
|
|
41
|
+
"sass": "src/fanui.scss",
|
|
42
|
+
"sideEffects": [
|
|
43
|
+
"dist/fanui.js",
|
|
44
|
+
"dist/fanui.mjs",
|
|
45
|
+
"dist/fanui.esm.js",
|
|
46
|
+
"dist/fanui.cjs",
|
|
47
|
+
"**/*.css",
|
|
48
|
+
"**/*.scss"
|
|
49
|
+
],
|
|
50
|
+
"exports": {
|
|
51
|
+
".": {
|
|
52
|
+
"types": "./dist/fanui.d.ts",
|
|
53
|
+
"sass": "./src/fanui.scss",
|
|
54
|
+
"import": "./dist/fanui.mjs",
|
|
55
|
+
"require": "./dist/fanui.cjs",
|
|
56
|
+
"default": "./dist/fanui.mjs"
|
|
57
|
+
},
|
|
58
|
+
"./style": "./dist/fanui.css",
|
|
59
|
+
"./style.min": "./dist/fanui.min.css",
|
|
60
|
+
"./style.css": "./dist/fanui.css",
|
|
61
|
+
"./style.min.css": "./dist/fanui.min.css",
|
|
62
|
+
"./js": "./dist/fanui.js",
|
|
63
|
+
"./scss": "./src/fanui.scss",
|
|
64
|
+
"./src/*": "./src/*",
|
|
65
|
+
"./dist/*": "./dist/*",
|
|
66
|
+
"./package.json": "./package.json"
|
|
67
|
+
},
|
|
68
|
+
"files": [
|
|
69
|
+
"src",
|
|
70
|
+
"dist",
|
|
71
|
+
"docs",
|
|
72
|
+
"examples",
|
|
73
|
+
"!examples/shots",
|
|
74
|
+
"_index.scss",
|
|
75
|
+
"!**/*.map",
|
|
76
|
+
"README.md",
|
|
77
|
+
"CHANGELOG.md",
|
|
78
|
+
"COMMERCIAL-LICENSE.md",
|
|
79
|
+
"ACCESSIBILITY.md",
|
|
80
|
+
"DESIGN-SYSTEM.md",
|
|
81
|
+
"USAGE.md"
|
|
82
|
+
],
|
|
83
|
+
"publishConfig": {
|
|
84
|
+
"access": "public",
|
|
85
|
+
"registry": "https://registry.npmjs.org/",
|
|
86
|
+
"tag": "latest"
|
|
87
|
+
},
|
|
88
|
+
"scripts": {
|
|
89
|
+
"build": "node scripts/build.cjs",
|
|
90
|
+
"watch": "node scripts/build.cjs --watch",
|
|
91
|
+
"serve": "npx --yes serve .",
|
|
92
|
+
"audit": "node scripts/audit-coverage.cjs",
|
|
93
|
+
"audit:contrast": "node scripts/audit-theme.cjs",
|
|
94
|
+
"lint": "node scripts/lint.cjs",
|
|
95
|
+
"verify": "node scripts/verify-demo.cjs",
|
|
96
|
+
"verify:matrix": "node scripts/verify-matrix.cjs",
|
|
97
|
+
"verify:examples": "node scripts/verify-examples.cjs",
|
|
98
|
+
"verify:ssr": "node scripts/verify-ssr.cjs",
|
|
99
|
+
"verify:entries": "node scripts/probe-entries.cjs",
|
|
100
|
+
"verify:install": "node scripts/verify-install.cjs",
|
|
101
|
+
"verify:components": "node scripts/verify-components.cjs",
|
|
102
|
+
"verify:browsers": "node scripts/verify-browsers.cjs",
|
|
103
|
+
"verify:visual": "node scripts/verify-visual.cjs",
|
|
104
|
+
"verify:edge": "node scripts/verify-edge.cjs",
|
|
105
|
+
"audit:a11y": "node scripts/audit-a11y.cjs",
|
|
106
|
+
"test:unit": "node scripts/test-unit.cjs",
|
|
107
|
+
"check:size": "node scripts/check-size.cjs",
|
|
108
|
+
"check": "npm run build && npm run lint && npm run audit && npm run verify && npm run verify:matrix && npm run verify:examples && npm run verify:ssr && npm run verify:entries && npm run verify:install && npm run verify:components && npm run check:size && npm run test:unit",
|
|
109
|
+
"check:full": "npm run check && npm run audit:a11y && npm run verify:browsers && npm run verify:visual && npm run verify:edge",
|
|
110
|
+
"gates": "node scripts/run-gates.cjs",
|
|
111
|
+
"preflight": "node scripts/preflight-publish.cjs",
|
|
112
|
+
"prepublishOnly": "npm run build && npm run lint && npm run check:size && npm run verify:install"
|
|
113
|
+
},
|
|
114
|
+
"devDependencies": {
|
|
115
|
+
"sass": "^1.90.0"
|
|
116
|
+
},
|
|
117
|
+
"engines": {
|
|
118
|
+
"node": ">=20",
|
|
119
|
+
"npm": ">=9"
|
|
120
|
+
},
|
|
121
|
+
"browserslist": [
|
|
122
|
+
"last 2 Chrome versions",
|
|
123
|
+
"last 2 Firefox versions",
|
|
124
|
+
"last 2 Safari versions",
|
|
125
|
+
"last 2 Edge versions"
|
|
126
|
+
]
|
|
127
|
+
}
|