@yl_lowcode/docs-theme 0.0.1
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/demo.vue +267 -0
- package/index.ts +13 -0
- package/layout.vue +263 -0
- package/package.json +35 -0
- package/playground.vue +235 -0
- package/plugins/shiki-raw.js +41 -0
- package/styles/custom.css +48 -0
- package/styles/vars.css +55 -0
package/demo.vue
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import { ref, onMounted, onBeforeUnmount } from "vue";
|
|
4
|
+
import { withBase } from 'vitepress'
|
|
5
|
+
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
component: any;
|
|
9
|
+
code: { raw: string; html: string; srcPath: string };
|
|
10
|
+
title?: string;
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const previewRef = ref<HTMLElement | null>(null);
|
|
14
|
+
const expanded = ref(false);
|
|
15
|
+
const copied = ref(false);
|
|
16
|
+
let previewRoot: any = null;
|
|
17
|
+
let copyTimer: ReturnType<typeof setTimeout> | null = null;
|
|
18
|
+
|
|
19
|
+
async function mountPreview() {
|
|
20
|
+
if (!previewRef.value) return;
|
|
21
|
+
const [{ default: React }, { createRoot }] = await Promise.all([
|
|
22
|
+
import("react"),
|
|
23
|
+
import("react-dom/client"),
|
|
24
|
+
]);
|
|
25
|
+
previewRoot?.unmount();
|
|
26
|
+
previewRoot = createRoot(previewRef.value);
|
|
27
|
+
previewRoot.render(React.createElement(props.component));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function openLive() {
|
|
31
|
+
const url = `/playground.html?demo=${props.code.srcPath}`;
|
|
32
|
+
window.open(withBase(url), "_blank");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toggleExpand() {
|
|
36
|
+
expanded.value = !expanded.value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function copyCode() {
|
|
40
|
+
await navigator.clipboard.writeText(props.code.raw);
|
|
41
|
+
copied.value = true;
|
|
42
|
+
if (copyTimer) clearTimeout(copyTimer);
|
|
43
|
+
copyTimer = setTimeout(() => {
|
|
44
|
+
copied.value = false;
|
|
45
|
+
}, 2000);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onMounted(() => mountPreview());
|
|
49
|
+
onBeforeUnmount(() => previewRoot?.unmount());
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<div class="aui-demo">
|
|
54
|
+
<!-- 预览区 -->
|
|
55
|
+
<div class="aui-demo-preview" ref="previewRef" />
|
|
56
|
+
|
|
57
|
+
<!-- 底部操作栏 -->
|
|
58
|
+
<div class="aui-demo-footer">
|
|
59
|
+
<span class="aui-demo-desc">{{ title ?? "" }}</span>
|
|
60
|
+
<div class="aui-demo-actions">
|
|
61
|
+
<!-- 在线调试(新 Tab) -->
|
|
62
|
+
<button class="aui-action-btn" title="在线调试" @click="openLive">
|
|
63
|
+
<svg viewBox="0 0 1024 1024" width="16" height="16">
|
|
64
|
+
<path
|
|
65
|
+
d="M746.222933 102.239573l-359.799466 330.820267L185.347413 281.4976 102.2464 329.864533l198.20544 182.132054-198.20544 182.132053 83.101013 48.510293 201.076054-151.558826 359.799466 330.676906 175.527254-85.251413V187.4944z m0 217.57952v384.341334l-255.040853-192.177494z"
|
|
66
|
+
fill="currentColor"
|
|
67
|
+
/>
|
|
68
|
+
</svg>
|
|
69
|
+
</button>
|
|
70
|
+
<!-- 展开/收起代码 -->
|
|
71
|
+
<button
|
|
72
|
+
class="aui-action-btn"
|
|
73
|
+
:class="{ active: expanded }"
|
|
74
|
+
:title="expanded ? '收起代码' : '展开代码'"
|
|
75
|
+
@click="toggleExpand"
|
|
76
|
+
>
|
|
77
|
+
<svg
|
|
78
|
+
width="15"
|
|
79
|
+
height="15"
|
|
80
|
+
viewBox="0 0 24 24"
|
|
81
|
+
fill="none"
|
|
82
|
+
stroke="currentColor"
|
|
83
|
+
stroke-width="2"
|
|
84
|
+
stroke-linecap="round"
|
|
85
|
+
stroke-linejoin="round"
|
|
86
|
+
>
|
|
87
|
+
<polyline v-if="!expanded" points="6 9 12 15 18 9" />
|
|
88
|
+
<polyline v-else points="18 15 12 9 6 15" />
|
|
89
|
+
</svg>
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<!-- 代码区(可展开) -->
|
|
95
|
+
<div v-show="expanded" class="aui-demo-code">
|
|
96
|
+
<span class="aui-lang-label">tsx</span>
|
|
97
|
+
<button
|
|
98
|
+
class="aui-copy-btn"
|
|
99
|
+
:class="{ copied }"
|
|
100
|
+
@click="copyCode"
|
|
101
|
+
title="复制代码"
|
|
102
|
+
>
|
|
103
|
+
<span v-if="copied" class="aui-copy-label">已复制</span>
|
|
104
|
+
<svg
|
|
105
|
+
v-if="copied"
|
|
106
|
+
viewBox="0 0 24 24"
|
|
107
|
+
width="20"
|
|
108
|
+
height="20"
|
|
109
|
+
fill="none"
|
|
110
|
+
stroke="currentColor"
|
|
111
|
+
stroke-width="2"
|
|
112
|
+
stroke-linecap="round"
|
|
113
|
+
stroke-linejoin="round"
|
|
114
|
+
>
|
|
115
|
+
<path
|
|
116
|
+
d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"
|
|
117
|
+
/>
|
|
118
|
+
<rect x="9" y="3" width="6" height="4" rx="1" />
|
|
119
|
+
<polyline points="9 12 11 14 15 10" />
|
|
120
|
+
</svg>
|
|
121
|
+
<svg
|
|
122
|
+
v-else
|
|
123
|
+
viewBox="0 0 24 24"
|
|
124
|
+
width="20"
|
|
125
|
+
height="20"
|
|
126
|
+
fill="none"
|
|
127
|
+
stroke="currentColor"
|
|
128
|
+
stroke-width="2"
|
|
129
|
+
stroke-linecap="round"
|
|
130
|
+
stroke-linejoin="round"
|
|
131
|
+
>
|
|
132
|
+
<path
|
|
133
|
+
d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"
|
|
134
|
+
/>
|
|
135
|
+
<rect x="9" y="3" width="6" height="4" rx="1" />
|
|
136
|
+
</svg>
|
|
137
|
+
</button>
|
|
138
|
+
<div v-html="code.html" />
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
142
|
+
|
|
143
|
+
<style scoped>
|
|
144
|
+
.aui-demo {
|
|
145
|
+
border: 1px solid var(--vp-c-divider);
|
|
146
|
+
border-radius: 10px;
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
margin: 20px 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.aui-demo-preview {
|
|
152
|
+
padding: 32px 24px;
|
|
153
|
+
min-height: 80px;
|
|
154
|
+
background: var(--vp-c-bg);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.aui-demo-footer {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: space-between;
|
|
161
|
+
gap: 12px;
|
|
162
|
+
padding: 6px 8px 6px 16px;
|
|
163
|
+
min-height: 40px;
|
|
164
|
+
border-top: 1px solid var(--vp-c-divider);
|
|
165
|
+
background: var(--vp-c-bg-soft);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.aui-demo-desc {
|
|
169
|
+
flex: 1;
|
|
170
|
+
font-size: 13px;
|
|
171
|
+
color: var(--vp-c-text-2);
|
|
172
|
+
line-height: 1.6;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.aui-demo-actions {
|
|
176
|
+
flex-shrink: 0;
|
|
177
|
+
display: flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: 4px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.aui-action-btn {
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
justify-content: center;
|
|
186
|
+
width: 28px;
|
|
187
|
+
height: 28px;
|
|
188
|
+
border: none;
|
|
189
|
+
border-radius: 6px;
|
|
190
|
+
background: transparent;
|
|
191
|
+
color: var(--vp-c-text-3);
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
transition: background 0.15s, color 0.15s;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.aui-action-btn:hover {
|
|
197
|
+
background: var(--vp-c-bg-mute);
|
|
198
|
+
color: var(--vp-c-text-1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.aui-action-btn.active {
|
|
202
|
+
background: var(--vp-c-brand-soft);
|
|
203
|
+
color: var(--vp-c-brand-1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.aui-demo-code {
|
|
207
|
+
position: relative;
|
|
208
|
+
border-top: 1px solid var(--vp-c-divider);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.aui-demo-code :deep(pre) {
|
|
212
|
+
margin: 0;
|
|
213
|
+
border-radius: 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.aui-lang-label {
|
|
217
|
+
position: absolute;
|
|
218
|
+
top: 2px;
|
|
219
|
+
right: 12px;
|
|
220
|
+
z-index: 2;
|
|
221
|
+
font-size: 12px;
|
|
222
|
+
font-weight: 500;
|
|
223
|
+
user-select: none;
|
|
224
|
+
color: var(--vp-code-lang-color);
|
|
225
|
+
transition: color 0.4s, opacity 0.4s;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.aui-demo-code:hover .aui-lang-label {
|
|
229
|
+
opacity: 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.aui-copy-btn {
|
|
233
|
+
position: absolute;
|
|
234
|
+
top: 8px;
|
|
235
|
+
right: 8px;
|
|
236
|
+
z-index: 10;
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: 6px;
|
|
240
|
+
padding: 6px 10px;
|
|
241
|
+
border: 1px solid var(--vp-c-divider);
|
|
242
|
+
border-radius: 8px;
|
|
243
|
+
background: var(--vp-c-bg);
|
|
244
|
+
color: var(--vp-c-text-2);
|
|
245
|
+
font-size: 13px;
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s,
|
|
248
|
+
box-shadow 0.15s;
|
|
249
|
+
opacity: 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.aui-demo-code:hover .aui-copy-btn {
|
|
253
|
+
opacity: 1;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.aui-copy-btn:hover {
|
|
257
|
+
color: var(--vp-c-text-1);
|
|
258
|
+
border-color: var(--vp-c-brand-1);
|
|
259
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.aui-copy-btn.copied {
|
|
263
|
+
opacity: 1;
|
|
264
|
+
color: var(--vp-c-brand-1);
|
|
265
|
+
border-color: var(--vp-c-brand-1);
|
|
266
|
+
}
|
|
267
|
+
</style>
|
package/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import DefaultTheme from "vitepress/theme";
|
|
2
|
+
import Layout from "./layout.vue";
|
|
3
|
+
import Demo from "./demo.vue";
|
|
4
|
+
import "./styles/vars.css";
|
|
5
|
+
import "./styles/custom.css";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
extends: DefaultTheme,
|
|
9
|
+
Layout,
|
|
10
|
+
enhanceApp({ app }: { app: import("vue").App }) {
|
|
11
|
+
app.component("Demo", Demo);
|
|
12
|
+
},
|
|
13
|
+
};
|
package/layout.vue
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import { useData } from "vitepress";
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import DefaultTheme from "vitepress/theme";
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import Playground from "./playground.vue";
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { nextTick, provide } from "vue";
|
|
10
|
+
|
|
11
|
+
const { frontmatter, isDark } = useData();
|
|
12
|
+
|
|
13
|
+
const enableTransitions = () =>
|
|
14
|
+
"startViewTransition" in document &&
|
|
15
|
+
window.matchMedia("(prefers-reduced-motion: no-preference)").matches;
|
|
16
|
+
|
|
17
|
+
provide("toggle-appearance", async ({ clientX: x, clientY: y }: MouseEvent) => {
|
|
18
|
+
if (!enableTransitions()) {
|
|
19
|
+
isDark.value = !isDark.value;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const clipPath = [
|
|
24
|
+
`circle(0px at ${x}px ${y}px)`,
|
|
25
|
+
`circle(${Math.hypot(
|
|
26
|
+
Math.max(x, innerWidth - x),
|
|
27
|
+
Math.max(y, innerHeight - y)
|
|
28
|
+
)}px at ${x}px ${y}px)`,
|
|
29
|
+
];
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
await document.startViewTransition(async () => {
|
|
32
|
+
isDark.value = !isDark.value;
|
|
33
|
+
await nextTick();
|
|
34
|
+
}).ready;
|
|
35
|
+
|
|
36
|
+
document.documentElement.animate(
|
|
37
|
+
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
|
|
38
|
+
{
|
|
39
|
+
duration: 300,
|
|
40
|
+
easing: "ease-in",
|
|
41
|
+
fill: "forwards",
|
|
42
|
+
pseudoElement: `::view-transition-${isDark.value ? "old" : "new"}(root)`,
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<Playground v-if="frontmatter.layout === 'playground'" />
|
|
50
|
+
<DefaultTheme.Layout v-else>
|
|
51
|
+
<template #nav-bar-content-after>
|
|
52
|
+
<slot name="nav-bar-content-after" />
|
|
53
|
+
</template>
|
|
54
|
+
<template #sidebar-nav-before>
|
|
55
|
+
<slot name="sidebar-nav-before" />
|
|
56
|
+
</template>
|
|
57
|
+
</DefaultTheme.Layout>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
::view-transition-old(root),
|
|
62
|
+
::view-transition-new(root) {
|
|
63
|
+
animation: none;
|
|
64
|
+
mix-blend-mode: normal;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
::view-transition-old(root),
|
|
68
|
+
.dark::view-transition-new(root) {
|
|
69
|
+
z-index: 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
::view-transition-new(root),
|
|
73
|
+
.dark::view-transition-old(root) {
|
|
74
|
+
z-index: 9999;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.VPSwitchAppearance {
|
|
78
|
+
width: 22px !important;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.VPSwitchAppearance .check {
|
|
82
|
+
transform: none !important;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 自定义样式 */
|
|
86
|
+
h3 {
|
|
87
|
+
margin-bottom: 16px !important;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
table {
|
|
91
|
+
display: table !important;
|
|
92
|
+
margin: 0 !important;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
a {
|
|
96
|
+
text-decoration: none !important;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.vp-doc li+li {
|
|
100
|
+
margin-top: 0 !important;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.vp-doc th,
|
|
104
|
+
.vp-doc td {
|
|
105
|
+
border: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.vp-doc ul,
|
|
109
|
+
.vp-doc ol {
|
|
110
|
+
padding-left: 0 !important;
|
|
111
|
+
margin: 0 !important;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.vp-doc blockquote {
|
|
115
|
+
border-left: 2px solid var(--primary-color);
|
|
116
|
+
padding-left: 10px;
|
|
117
|
+
color: var(--vp-c-text-1) !important;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#api+table {
|
|
121
|
+
width: 100%;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
blockquote+table {
|
|
125
|
+
width: 100%;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
p.text {
|
|
129
|
+
margin-bottom: 1em;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
h2.text {
|
|
133
|
+
margin-top: 20px;
|
|
134
|
+
margin-bottom: 0.5em;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* 调整样式 */
|
|
138
|
+
body {
|
|
139
|
+
--vp-code-font-size: 12px !important;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.VPDoc.has-aside .content-container {
|
|
143
|
+
max-width: 940px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.VPDoc .content {
|
|
147
|
+
min-width: 940px;
|
|
148
|
+
padding: 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.VPDoc .content .content-container {
|
|
152
|
+
max-width: 100% !important;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.VPDoc .prev-next {
|
|
156
|
+
padding-bottom: 40px !important;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.VPDocAside .content {
|
|
160
|
+
padding-left: 10px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.VPHomeHero .text {
|
|
164
|
+
margin-top: 12px !important;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#VPContent.has-sidebar {
|
|
168
|
+
padding-right: 0;
|
|
169
|
+
padding-left: 260px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.VPSidebar {
|
|
173
|
+
padding-left: 60px !important;
|
|
174
|
+
width: 280px !important;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.VPNavBar.has-sidebar .container>.title {
|
|
178
|
+
padding-left: 60px !important;
|
|
179
|
+
width: 280px !important;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.VPNavBar.has-sidebar .content {
|
|
183
|
+
padding-left: 280px !important;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.VPNavBar.has-sidebar .divider {
|
|
187
|
+
padding-left: 300px !important;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.content-container {
|
|
191
|
+
margin-left: 30px !important;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.vp-doc td,
|
|
195
|
+
.vp-doc th {
|
|
196
|
+
font-size: 12px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.custom-block {
|
|
200
|
+
padding: 12px 30px !important;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.custom-block-title {
|
|
204
|
+
display: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.VPHomeHero .image-bg {
|
|
208
|
+
background-image: linear-gradient(-45deg, #646cffcc 50%, #ffd14785 50%);
|
|
209
|
+
filter: blur(68px);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.vp-doc tr:nth-child(2n) {
|
|
213
|
+
background-color: transparent;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.vp-doc h2 {
|
|
217
|
+
border-top: none;
|
|
218
|
+
margin: 10px;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
h2.text {
|
|
222
|
+
margin-top: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
:root {
|
|
226
|
+
--soui-brand-6: #197afa;
|
|
227
|
+
--soui-font-14: 12px;
|
|
228
|
+
--soui-tabs-tab-font-size: 12px;
|
|
229
|
+
--soui-14-regular: 12px;
|
|
230
|
+
--vp-c-brand-1: var(--soui-brand-6);
|
|
231
|
+
--vp-c-brand-2: var(--soui-brand-6);
|
|
232
|
+
--vp-button-brand-bg: var(--soui-brand-6);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
body {
|
|
236
|
+
font-size: 12px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.VPNavBar.has-sidebar .content {
|
|
240
|
+
padding-right: 40px !important;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
h2 {
|
|
244
|
+
margin-left: 0 !important;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.VPImage.logo {
|
|
248
|
+
border-radius: 50%;
|
|
249
|
+
height: 30px;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.vp-doc[class*=" _interview"] .custom-block {
|
|
253
|
+
padding: 16px !important;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.vp-doc[class*=" _interview"] .custom-block summary {
|
|
257
|
+
margin-bottom: 0 !important;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.vp-doc[class*=" _interview"] .custom-block p {
|
|
261
|
+
margin: 0 !important;
|
|
262
|
+
}
|
|
263
|
+
</style>
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yl_lowcode/docs-theme",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "共享 VitePress 文档主题(React Demo 支持)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts",
|
|
9
|
+
"./plugins/shiki-raw": "./plugins/shiki-raw.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.ts",
|
|
13
|
+
"layout.vue",
|
|
14
|
+
"demo.vue",
|
|
15
|
+
"playground.vue",
|
|
16
|
+
"plugins",
|
|
17
|
+
"styles"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"sucrase": "3.35.1",
|
|
21
|
+
"@yl_lowcode/editor": "0.0.1"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"react": "^18.0.0",
|
|
25
|
+
"react-dom": "^18.0.0",
|
|
26
|
+
"vitepress": ">=1.6.0",
|
|
27
|
+
"vue": "^3.4.0"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"registry": "https://registry.npmjs.org/",
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {}
|
|
35
|
+
}
|
package/playground.vue
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import { ref, inject, onMounted, onBeforeUnmount } from "vue";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 通用 Playground 组件
|
|
7
|
+
*
|
|
8
|
+
* 使用方需通过 provide 注入以下配置:
|
|
9
|
+
* provide("playground-config", {
|
|
10
|
+
* codeGlobs: import.meta.glob("../../demos/**\/*.tsx", { query: "?raw", import: "default" }),
|
|
11
|
+
* loadModules: () => import("@yl_lowcode/editor").then(m => ({ "@yl_lowcode/editor": m })),
|
|
12
|
+
* })
|
|
13
|
+
*/
|
|
14
|
+
const config = inject<{
|
|
15
|
+
codeGlobs: Record<string, () => Promise<any>>;
|
|
16
|
+
loadModules: () => Promise<Record<string, any>>;
|
|
17
|
+
}>("playground-config");
|
|
18
|
+
|
|
19
|
+
const containerRef = ref<HTMLElement | null>(null);
|
|
20
|
+
let reactRoot: any = null;
|
|
21
|
+
|
|
22
|
+
onMounted(async () => {
|
|
23
|
+
if (!containerRef.value || !config) return;
|
|
24
|
+
|
|
25
|
+
const params = new URLSearchParams(window.location.search);
|
|
26
|
+
const demo = params.get("demo");
|
|
27
|
+
if (!demo) return;
|
|
28
|
+
|
|
29
|
+
// 在 globs 中查找匹配的 demo 文件
|
|
30
|
+
const matchKey = Object.keys(config.codeGlobs).find((k) => k.includes(`/${demo}.tsx`));
|
|
31
|
+
if (!matchKey) return;
|
|
32
|
+
|
|
33
|
+
const rawCode = (await config.codeGlobs[matchKey]()).raw as string;
|
|
34
|
+
const [{ default: React }, { createRoot }, { transform }, userModules] = await Promise.all([
|
|
35
|
+
import("react"),
|
|
36
|
+
import("react-dom/client"),
|
|
37
|
+
import("sucrase"),
|
|
38
|
+
config.loadModules(),
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// 编译函数:将 TSX 代码转换为可执行模块
|
|
42
|
+
function compileCode(code: string, moduleMap: Record<string, any>) {
|
|
43
|
+
try {
|
|
44
|
+
const normalizedCode = /\bimport\s+React\b/.test(code)
|
|
45
|
+
? code
|
|
46
|
+
: `import React from "react";\n${code}`;
|
|
47
|
+
const { code: compiled } = transform(normalizedCode, {
|
|
48
|
+
transforms: ["typescript", "jsx", "imports"],
|
|
49
|
+
jsxRuntime: "classic",
|
|
50
|
+
});
|
|
51
|
+
if (!compiled) return { component: null, error: "编译无输出" };
|
|
52
|
+
|
|
53
|
+
const require = (name: string) => {
|
|
54
|
+
const resolved = moduleMap[name];
|
|
55
|
+
if (resolved !== undefined) return resolved;
|
|
56
|
+
throw new Error(`未找到模块: "${name}"`);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const exports = {} as Record<string, unknown>;
|
|
60
|
+
new Function("require", "module", "exports", compiled)(
|
|
61
|
+
require,
|
|
62
|
+
{ exports },
|
|
63
|
+
exports,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const Component = (exports.default ?? exports) as React.ComponentType;
|
|
67
|
+
const component = typeof Component === "function" ? Component : null;
|
|
68
|
+
return { component, error: component ? null : "export default 不是函数" };
|
|
69
|
+
} catch (err: unknown) {
|
|
70
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
71
|
+
return { component: null, error };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ErrorBoundary
|
|
76
|
+
class ErrorBoundary extends React.Component<
|
|
77
|
+
React.PropsWithChildren<{}>,
|
|
78
|
+
{ error: string | null }
|
|
79
|
+
> {
|
|
80
|
+
constructor(props: React.PropsWithChildren<{}>) {
|
|
81
|
+
super(props);
|
|
82
|
+
this.state = { error: null };
|
|
83
|
+
}
|
|
84
|
+
static getDerivedStateFromError(err: unknown) {
|
|
85
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
86
|
+
}
|
|
87
|
+
render() {
|
|
88
|
+
if (this.state.error) {
|
|
89
|
+
return React.createElement(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
style: {
|
|
93
|
+
margin: 0,
|
|
94
|
+
padding: "12px 16px",
|
|
95
|
+
color: "#ef4444",
|
|
96
|
+
fontSize: "12px",
|
|
97
|
+
fontFamily: "monospace",
|
|
98
|
+
borderBottom: "1px solid rgba(239,68,68,.2)",
|
|
99
|
+
background: "rgba(239,68,68,.05)",
|
|
100
|
+
whiteSpace: "pre-wrap",
|
|
101
|
+
wordBreak: "break-all",
|
|
102
|
+
maxHeight: "160px",
|
|
103
|
+
overflow: "auto",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
"Runtime error: " + this.state.error,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return (this.props as any).children;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 合并模块映射(react 始终提供)
|
|
114
|
+
const moduleMap: Record<string, any> = {
|
|
115
|
+
react: React,
|
|
116
|
+
...userModules,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function App() {
|
|
120
|
+
const [code, setCode] = React.useState(rawCode);
|
|
121
|
+
const [Preview, setPreview] = React.useState<React.ComponentType | null>(
|
|
122
|
+
() => compileCode(rawCode, moduleMap).component,
|
|
123
|
+
);
|
|
124
|
+
const [error, setError] = React.useState<string | null>(
|
|
125
|
+
() => compileCode(rawCode, moduleMap).error,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const handleCodeChange = React.useCallback((newCode: string) => {
|
|
129
|
+
setCode(newCode);
|
|
130
|
+
const { component, error: err } = compileCode(newCode, moduleMap);
|
|
131
|
+
setPreview(() => component);
|
|
132
|
+
setError(err);
|
|
133
|
+
}, []);
|
|
134
|
+
|
|
135
|
+
// 动态获取 Monaco 组件(从 moduleMap 中取)
|
|
136
|
+
const MonacoEditor = userModules["@yl_lowcode/editor"]?.Monaco;
|
|
137
|
+
|
|
138
|
+
return React.createElement(
|
|
139
|
+
"div",
|
|
140
|
+
{ style: { display: "flex", height: "100vh", overflow: "hidden" } },
|
|
141
|
+
// 左侧:Monaco 编辑器
|
|
142
|
+
React.createElement(
|
|
143
|
+
"div",
|
|
144
|
+
{
|
|
145
|
+
style: {
|
|
146
|
+
flex: 1,
|
|
147
|
+
borderRight: "1px solid var(--vp-c-divider)",
|
|
148
|
+
overflow: "hidden",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
MonacoEditor
|
|
152
|
+
? React.createElement(MonacoEditor, {
|
|
153
|
+
value: code,
|
|
154
|
+
language: "javascript",
|
|
155
|
+
style: { height: "100%" },
|
|
156
|
+
onChange: handleCodeChange,
|
|
157
|
+
})
|
|
158
|
+
: React.createElement("textarea", {
|
|
159
|
+
value: code,
|
|
160
|
+
onChange: (e: any) => handleCodeChange(e.target.value),
|
|
161
|
+
style: { width: "100%", height: "100%", fontFamily: "monospace", padding: "16px" },
|
|
162
|
+
}),
|
|
163
|
+
),
|
|
164
|
+
// 右侧:预览 + 错误
|
|
165
|
+
React.createElement(
|
|
166
|
+
"div",
|
|
167
|
+
{
|
|
168
|
+
style: {
|
|
169
|
+
flex: 1,
|
|
170
|
+
display: "flex",
|
|
171
|
+
flexDirection: "column",
|
|
172
|
+
overflow: "hidden",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
error &&
|
|
176
|
+
React.createElement(
|
|
177
|
+
"div",
|
|
178
|
+
{
|
|
179
|
+
style: {
|
|
180
|
+
margin: 0,
|
|
181
|
+
padding: "12px 16px",
|
|
182
|
+
color: "#ef4444",
|
|
183
|
+
fontSize: "12px",
|
|
184
|
+
fontFamily: "monospace",
|
|
185
|
+
borderBottom: "1px solid rgba(239,68,68,.2)",
|
|
186
|
+
background: "rgba(239,68,68,.05)",
|
|
187
|
+
whiteSpace: "pre-wrap",
|
|
188
|
+
wordBreak: "break-all",
|
|
189
|
+
maxHeight: "160px",
|
|
190
|
+
overflow: "auto",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
error,
|
|
194
|
+
),
|
|
195
|
+
React.createElement(
|
|
196
|
+
ErrorBoundary,
|
|
197
|
+
{ key: code },
|
|
198
|
+
React.createElement(
|
|
199
|
+
"div",
|
|
200
|
+
{ style: { flex: 1, padding: "24px", overflow: "auto" } },
|
|
201
|
+
Preview && React.createElement(Preview),
|
|
202
|
+
),
|
|
203
|
+
),
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
reactRoot = createRoot(containerRef.value);
|
|
209
|
+
reactRoot.render(React.createElement(App));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
onBeforeUnmount(() => {
|
|
213
|
+
reactRoot?.unmount();
|
|
214
|
+
});
|
|
215
|
+
</script>
|
|
216
|
+
|
|
217
|
+
<template>
|
|
218
|
+
<div class="playground-page">
|
|
219
|
+
<div ref="containerRef" class="playground-container" />
|
|
220
|
+
</div>
|
|
221
|
+
</template>
|
|
222
|
+
|
|
223
|
+
<style scoped>
|
|
224
|
+
.playground-page {
|
|
225
|
+
width: 100vw;
|
|
226
|
+
height: 100vh;
|
|
227
|
+
overflow: hidden;
|
|
228
|
+
background: var(--vp-c-bg);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.playground-container {
|
|
232
|
+
width: 100%;
|
|
233
|
+
height: 100%;
|
|
234
|
+
}
|
|
235
|
+
</style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { createMarkdownRenderer } from "vitepress";
|
|
3
|
+
|
|
4
|
+
let mdPromise = null;
|
|
5
|
+
|
|
6
|
+
function getMd(srcDir, options) {
|
|
7
|
+
if (!mdPromise) {
|
|
8
|
+
mdPromise = createMarkdownRenderer(srcDir, options);
|
|
9
|
+
}
|
|
10
|
+
return mdPromise;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function shikiRawPlugin(options) {
|
|
14
|
+
let srcDir = process.cwd();
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
name: "vite-plugin-shiki-raw",
|
|
18
|
+
enforce: "pre",
|
|
19
|
+
configResolved(config) {
|
|
20
|
+
srcDir = config.root;
|
|
21
|
+
},
|
|
22
|
+
async load(id) {
|
|
23
|
+
if (!id.endsWith("?raw")) return null;
|
|
24
|
+
|
|
25
|
+
const filePath = id.slice(0, -4);
|
|
26
|
+
if (!filePath.endsWith(".tsx")) return null;
|
|
27
|
+
|
|
28
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
29
|
+
const md = await getMd(srcDir, options);
|
|
30
|
+
const html =
|
|
31
|
+
typeof md.renderAsync === "function"
|
|
32
|
+
? await md.renderAsync("```tsx\n" + raw + "\n```")
|
|
33
|
+
: md.render("```tsx\n" + raw + "\n```");
|
|
34
|
+
|
|
35
|
+
const demosIdx = filePath.indexOf("/demos/");
|
|
36
|
+
const srcPath = demosIdx >= 0 ? filePath.slice(demosIdx + 7, -4) : "";
|
|
37
|
+
|
|
38
|
+
return `export default ${JSON.stringify({ raw, html, srcPath })}`;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* 覆盖 VitePress 默认变量,接入主题色 */
|
|
2
|
+
:root {
|
|
3
|
+
--vp-c-brand-1: var(--aui-accent);
|
|
4
|
+
--vp-c-brand-2: var(--aui-accent-light);
|
|
5
|
+
--vp-c-brand-3: var(--aui-accent-dark);
|
|
6
|
+
--vp-c-brand-soft: rgba(99, 102, 241, 0.1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.dark .shiki span {
|
|
10
|
+
color: var(--shiki-dark, inherit);
|
|
11
|
+
font-size: 13px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
html:not(.dark) .shiki span {
|
|
15
|
+
color: var(--shiki-light, inherit);
|
|
16
|
+
font-size: 13px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.vp-doc .language-tsx {
|
|
20
|
+
margin: 0 !important;
|
|
21
|
+
border-radius: 0 !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.vp-doc .shiki-themes {
|
|
25
|
+
padding: 10px 0 !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.vp-doc img {
|
|
29
|
+
margin: 0 !important;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* demo 代码展示区:复用 VitePress 代码块,隐藏多余 UI */
|
|
33
|
+
.aui-demo-code .language-tsx {
|
|
34
|
+
margin: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.aui-demo-code .copy,
|
|
38
|
+
.aui-demo-code .lang {
|
|
39
|
+
display: none !important;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.aui-demo-code pre.shiki {
|
|
43
|
+
margin: 0;
|
|
44
|
+
padding: 20px 24px;
|
|
45
|
+
font-size: 13px;
|
|
46
|
+
line-height: 1.7;
|
|
47
|
+
border-radius: 0;
|
|
48
|
+
}
|
package/styles/vars.css
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* 文档主题 CSS 变量 */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* 高亮色(可动态修改) */
|
|
5
|
+
--aui-accent: #197afa;
|
|
6
|
+
--aui-accent-light: #3891fb;
|
|
7
|
+
--aui-accent-dark: #0563da;
|
|
8
|
+
--aui-accent-bg: rgba(25, 122, 250, 0.1);
|
|
9
|
+
|
|
10
|
+
/* 亮色主题 */
|
|
11
|
+
--aui-bg: #ffffff;
|
|
12
|
+
--aui-bg-soft: #f6f6f7;
|
|
13
|
+
--aui-bg-mute: #ebebed;
|
|
14
|
+
--aui-border: #e2e2e3;
|
|
15
|
+
--aui-text: #213547;
|
|
16
|
+
--aui-text-2: #4a5270;
|
|
17
|
+
--aui-text-3: #8b8b8f;
|
|
18
|
+
|
|
19
|
+
/* 组件尺寸 */
|
|
20
|
+
--aui-radius-sm: 4px;
|
|
21
|
+
--aui-radius: 6px;
|
|
22
|
+
--aui-radius-lg: 10px;
|
|
23
|
+
|
|
24
|
+
/* 间距 */
|
|
25
|
+
--aui-space-1: 4px;
|
|
26
|
+
--aui-space-2: 8px;
|
|
27
|
+
--aui-space-3: 12px;
|
|
28
|
+
--aui-space-4: 16px;
|
|
29
|
+
|
|
30
|
+
/* 字体 */
|
|
31
|
+
--aui-font-sm: 13px;
|
|
32
|
+
--aui-font-base: 14px;
|
|
33
|
+
--aui-font-lg: 16px;
|
|
34
|
+
|
|
35
|
+
/* 阴影 */
|
|
36
|
+
--aui-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
37
|
+
--aui-shadow-md:
|
|
38
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
39
|
+
--aui-shadow-focus: 0 0 0 3px rgba(25, 122, 250, 0.3);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dark {
|
|
43
|
+
--aui-bg: #0c0e13;
|
|
44
|
+
--aui-bg-soft: #161b27;
|
|
45
|
+
--aui-bg-mute: #1e2536;
|
|
46
|
+
--aui-border: #2a3147;
|
|
47
|
+
--aui-text: #e8ecf8;
|
|
48
|
+
--aui-text-2: #9ba8c9;
|
|
49
|
+
--aui-text-3: #5a6480;
|
|
50
|
+
--aui-accent-bg: rgba(25, 122, 250, 0.18);
|
|
51
|
+
--aui-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
52
|
+
--aui-shadow-md:
|
|
53
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
|
|
54
|
+
--aui-shadow-focus: 0 0 0 3px rgba(25, 122, 250, 0.4);
|
|
55
|
+
}
|