@opentiny/next-sdk 0.2.7 → 0.2.8

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * page-tool-bridge - Web MCP 页面工具桥接模块(框架无关)
2
+ * page-tools/bridge - Web MCP 页面工具桥接模块(框架无关)
3
3
  *
4
4
  * 解决 Web-MCP 工具动态加载问题:工具定义(mcp-servers/)不直接写业务逻辑,
5
5
  * 而是通过 window.postMessage 将调用转发给目标页面,页面处理后返回结果。
@@ -10,61 +10,15 @@
10
10
  * 包装 WebMcpServer,让 registerTool 第三个参数
11
11
  * 同时支持原始回调函数和路由配置对象(RouteConfig)
12
12
  * - registerPageTool() 在目标页面激活工具处理器,返回 cleanup 函数
13
- *
14
- * 使用方式:
15
- * // mcp-servers/index.ts
16
- * const server = withPageTools(new WebMcpServer())
17
- *
18
- * // mcp-servers/product-guide/tools.ts
19
- * server.registerTool('product-guide', { title, description, inputSchema },
20
- * { route: '/comprehensive', timeout: 15000 } // ← 路由配置:route 必填,timeout 可选(ms,默认 30000)
21
- * )
22
- * // 或仍然使用普通回调(完全兼容)
23
- * server.registerTool('simple-tool', { ... }, async (input) => { ... })
24
- *
25
- * // 目标页面(Vue)— route 可省略,默认取 window.location.pathname
26
- * onMounted(() => { cleanup = registerPageTool({ handlers }) })
27
- * onUnmounted(() => cleanup())
28
- *
29
- * // 目标页面(Vue)— 当页面路由与 pathname 不一致时,手动指定 route
30
- * onMounted(() => { cleanup = registerPageTool({ route: '/my-page', handlers }) })
31
- * onUnmounted(() => cleanup())
32
- *
33
- * // 目标页面(React)
34
- * useEffect(() => registerPageTool({ handlers }), [])
35
- *
36
- * // 目标页面(Angular)
37
- * export class MyComponent implements OnInit, OnDestroy {
38
- * private cleanupPageTool!: () => void
39
- * ngOnInit() { this.cleanupPageTool = registerPageTool({ handlers }) }
40
- * ngOnDestroy() { this.cleanupPageTool() }
41
- * }
42
- *
43
- * setNavigator 在不同框架中的注册方式:
44
- * // Vue(main.ts)
45
- * const router = createRouter(...)
46
- * app.use(router)
47
- * setNavigator((route) => router.push(route))
48
- *
49
- * // React(App.tsx,使用 react-router-dom)
50
- * function AppNavigator() {
51
- * const navigate = useNavigate()
52
- * useEffect(() => { setNavigator((route) => navigate(route)) }, [navigate])
53
- * return null
54
- * }
55
- *
56
- * // Angular(AppComponent,使用 @angular/router)
57
- * export class AppComponent {
58
- * constructor(private router: Router) {
59
- * setNavigator((route) => this.router.navigateByUrl(route))
60
- * }
61
- * }
62
13
  */
14
+
63
15
  import type { ZodRawShape } from 'zod'
64
16
  import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'
65
17
  import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'
66
- import type { WebMcpServer } from './WebMcpServer'
67
- import { randomUUID } from './utils/uuid'
18
+ import type { WebMcpServer } from '../WebMcpServer'
19
+ import { randomUUID } from '../utils/uuid'
20
+ import type { ToolInvokeEffectConfig } from './effects'
21
+ import { hideToolInvokeEffect, resolveRuntimeEffectConfig, showToolInvokeEffect } from './effects'
68
22
 
69
23
  // 消息类型常量,使用命名空间前缀避免冲突
70
24
  const MSG_TOOL_CALL = 'next-sdk:tool-call'
@@ -168,8 +122,19 @@ export type RouteConfig = {
168
122
  route: string
169
123
  /** 等待页面响应的超时时间(ms),默认 30000 */
170
124
  timeout?: number
125
+ /**
126
+ * 是否在调用该工具时启用页面级调用提示效果。
127
+ *
128
+ * - false / 未配置:不启用任何额外效果(保持现有行为)
129
+ * - true:使用默认提示文案(优先取工具标题,其次为工具名)
130
+ * - 对象:可自定义提示文案
131
+ */
132
+ invokeEffect?: boolean | ToolInvokeEffectConfig
171
133
  }
172
134
 
135
+ // 对外暴露调用提示配置类型,便于业务方在 RouteConfig 外单独复用
136
+ export type { ToolInvokeEffectConfig }
137
+
173
138
  /**
174
139
  * PageAwareServer 的 registerTool 配置对象类型,与 WebMcpServer.registerTool 保持一致。
175
140
  */
@@ -205,7 +170,12 @@ export type PageAwareServer = Omit<WebMcpServer, 'registerTool'> & {
205
170
  * 2. 若未激活 → 调用导航函数跳转,等待 page-ready 信号后再发送
206
171
  * 3. 页面处理后回传结果,Promise resolve
207
172
  */
208
- function buildPageHandler(name: string, route: string, timeout = 30000) {
173
+ function buildPageHandler(
174
+ name: string,
175
+ route: string,
176
+ timeout = 30000,
177
+ effectConfig?: ReturnType<typeof resolveRuntimeEffectConfig>
178
+ ) {
209
179
  return (input: any): Promise<any> => {
210
180
  const callId = randomUUID()
211
181
 
@@ -220,6 +190,10 @@ function buildPageHandler(name: string, route: string, timeout = 30000) {
220
190
  if (readyHandler) {
221
191
  window.removeEventListener('message', readyHandler)
222
192
  }
193
+ // 工具调用完成(成功 / 失败 / 超时 / 导航异常)后,无论结果如何都需要关闭调用提示效果
194
+ if (effectConfig) {
195
+ hideToolInvokeEffect()
196
+ }
223
197
  }
224
198
 
225
199
  // 超时兜底,防止页面永远不响应
@@ -255,6 +229,11 @@ function buildPageHandler(name: string, route: string, timeout = 30000) {
255
229
  // 导航失败时显式 reject,防止外层 Promise 永远挂起。
256
230
  const run = async () => {
257
231
  try {
232
+ // 一旦真正发起工具调用(无论页面是否已激活),优先开启页面调用提示效果
233
+ if (effectConfig) {
234
+ showToolInvokeEffect(effectConfig)
235
+ }
236
+
258
237
  if (activePages.get(route)) {
259
238
  // 页面已激活,直接发送
260
239
  sendCallOnce()
@@ -302,15 +281,6 @@ function buildPageHandler(name: string, route: string, timeout = 30000) {
302
281
  * - 第三个参数为**回调函数**:与原始 registerTool 完全一致,直接透传
303
282
  * - 第三个参数为 **RouteConfig 对象**:自动生成转发 handler,工具调用时
304
283
  * 先导航到目标路由,再通过 postMessage 与页面通信
305
- *
306
- * @example
307
- * const server = withPageTools(new WebMcpServer())
308
- *
309
- * // 路由模式:第三个参数传路由配置(route 必填,timeout 可选,单位 ms,默认 30000)
310
- * server.registerTool('product-guide', { title, inputSchema }, { route: '/comprehensive', timeout: 15000 })
311
- *
312
- * // 普通模式:第三个参数传回调(兼容原有写法)
313
- * server.registerTool('simple-tool', { title }, async (input) => ({ content: [...] }))
314
284
  */
315
285
  export function withPageTools(server: WebMcpServer): PageAwareServer {
316
286
  return new Proxy(server, {
@@ -325,9 +295,10 @@ export function withPageTools(server: WebMcpServer): PageAwareServer {
325
295
  return rawRegister(name, config, handlerOrRoute)
326
296
  }
327
297
  // 第三个参数是路由配置对象 → 自动生成转发 handler,并记录 tool → route 映射
328
- const { route, timeout } = handlerOrRoute
298
+ const { route, timeout, invokeEffect } = handlerOrRoute
329
299
  toolRouteMap.set(name, route)
330
- return rawRegister(name, config, buildPageHandler(name, route, timeout))
300
+ const effectConfig = resolveRuntimeEffectConfig(name, config?.title, invokeEffect)
301
+ return rawRegister(name, config, buildPageHandler(name, route, timeout, effectConfig))
331
302
  }
332
303
  }
333
304
  return Reflect.get(target, prop, receiver)
@@ -344,37 +315,6 @@ export function withPageTools(server: WebMcpServer): PageAwareServer {
344
315
  * - 广播 page-ready 信号,通知正在等待导航完成的工具
345
316
  *
346
317
  * 返回 cleanup 函数,页面销毁时调用。
347
- *
348
- * @example
349
- * // Vue(Composition API)— 省略 route,默认读 window.location.pathname
350
- * let cleanup: () => void
351
- * onMounted(() => { cleanup = registerPageTool({ handlers: { ... } }) })
352
- * onUnmounted(() => cleanup())
353
- *
354
- * // Vue — 当页面路由与 pathname 不一致时,手动指定 route
355
- * onMounted(() => { cleanup = registerPageTool({ route: '/comprehensive', handlers: { ... } }) })
356
- * onUnmounted(() => cleanup())
357
- *
358
- * // React(Hooks)
359
- * useEffect(() => registerPageTool({ handlers: { ... } }), [])
360
- * // useEffect 直接返回 cleanup 函数,React 会在组件卸载时自动调用
361
- *
362
- * // Angular(实现 OnInit / OnDestroy 接口)
363
- * export class PriceProtectionComponent implements OnInit, OnDestroy {
364
- * private cleanupPageTool!: () => void
365
- *
366
- * ngOnInit(): void {
367
- * this.cleanupPageTool = registerPageTool({
368
- * handlers: {
369
- * 'price-protection-query': async ({ status }) => { ... },
370
- * }
371
- * })
372
- * }
373
- *
374
- * ngOnDestroy(): void {
375
- * this.cleanupPageTool()
376
- * }
377
- * }
378
318
  */
379
319
  export function registerPageTool(options: {
380
320
  /**
@@ -439,3 +379,4 @@ export function registerPageTool(options: {
439
379
  broadcastRouteChange(MSG_PAGE_LEAVE, route)
440
380
  }
441
381
  }
382
+
@@ -0,0 +1,343 @@
1
+ /**
2
+ * page-tools/effects - 页面工具调用提示效果模块(框架无关)
3
+ *
4
+ * 作用:
5
+ * - 在调用通过 withPageTools 注册的页面工具时,显示页面级的调用状态提示
6
+ * - 以左下角小 tip 形式展示“当前正在调用的工具”文案,尽量不打扰用户操作
7
+ * - 纯 DOM + CSS 实现,不依赖 Vue/React 等框架
8
+ */
9
+
10
+ export type ToolInvokeEffectConfig = {
11
+ /**
12
+ * 自定义提示文案,默认使用“工具标题 || 工具名称”
13
+ * 例如:"正在为你整理订单数据"
14
+ */
15
+ label?: string
16
+ }
17
+
18
+ type RuntimeEffectConfig = {
19
+ label: string
20
+ }
21
+
22
+ let overlayElement: HTMLDivElement | null = null
23
+ let labelElement: HTMLDivElement | null = null
24
+ let styleElement: HTMLStyleElement | null = null
25
+ let activeCount = 0
26
+
27
+ const BODY_GLOW_CLASS = 'next-sdk-tool-body-glow'
28
+
29
+ function ensureDomReady() {
30
+ return typeof window !== 'undefined' && typeof document !== 'undefined'
31
+ }
32
+
33
+ function ensureStyleElement() {
34
+ if (!ensureDomReady()) return
35
+ if (styleElement) return
36
+
37
+ const style = document.createElement('style')
38
+ style.textContent = `
39
+ .${BODY_GLOW_CLASS} {
40
+ position: relative;
41
+ }
42
+
43
+ .next-sdk-tool-overlay {
44
+ position: fixed;
45
+ inset: 0;
46
+ z-index: 999999;
47
+ pointer-events: none;
48
+ display: flex;
49
+ align-items: flex-end;
50
+ justify-content: flex-start;
51
+ padding: 0 0 18px 18px;
52
+ background: transparent;
53
+ animation: next-sdk-overlay-fade-in 260ms ease-out;
54
+ }
55
+
56
+ .next-sdk-tool-overlay--exit {
57
+ animation: next-sdk-overlay-fade-out 220ms ease-in forwards;
58
+ }
59
+
60
+ .next-sdk-tool-overlay__glow-ring {
61
+ display: none;
62
+ }
63
+
64
+ .next-sdk-tool-overlay__panel {
65
+ position: relative;
66
+ min-width: min(320px, 78vw);
67
+ max-width: min(420px, 82vw);
68
+ padding: 10px 14px;
69
+ border-radius: 999px;
70
+ background:
71
+ linear-gradient(135deg, rgba(15, 23, 42, 0.9), rgba(17, 24, 39, 0.9)),
72
+ radial-gradient(circle at top left, rgba(96, 165, 250, 0.25), transparent 55%),
73
+ radial-gradient(circle at bottom right, rgba(45, 212, 191, 0.22), transparent 60%);
74
+ box-shadow:
75
+ 0 12px 28px rgba(15, 23, 42, 0.78),
76
+ 0 0 0 1px rgba(148, 163, 184, 0.26);
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 10px;
80
+ pointer-events: none;
81
+ transform-origin: center;
82
+ animation: next-sdk-panel-pop-in 260ms cubic-bezier(0.18, 0.89, 0.32, 1.28);
83
+ color: #e5e7eb;
84
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
85
+ }
86
+
87
+ .next-sdk-tool-overlay__indicator {
88
+ width: 26px;
89
+ height: 26px;
90
+ border-radius: 999px;
91
+ background: radial-gradient(circle at 30% 10%, #f9fafb, #93c5fd);
92
+ box-shadow:
93
+ 0 0 0 1px rgba(191, 219, 254, 0.6),
94
+ 0 8px 18px rgba(37, 99, 235, 0.8),
95
+ 0 0 28px rgba(56, 189, 248, 0.9);
96
+ position: relative;
97
+ flex-shrink: 0;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ overflow: hidden;
102
+ }
103
+
104
+ .next-sdk-tool-overlay__indicator-orbit {
105
+ position: absolute;
106
+ inset: 2px;
107
+ border-radius: inherit;
108
+ border: 1px solid rgba(248, 250, 252, 0.6);
109
+ box-sizing: border-box;
110
+ opacity: 0.9;
111
+ }
112
+
113
+ .next-sdk-tool-overlay__indicator-orbit::before {
114
+ content: '';
115
+ position: absolute;
116
+ width: 6px;
117
+ height: 6px;
118
+ border-radius: 999px;
119
+ background: #f9fafb;
120
+ box-shadow:
121
+ 0 0 12px rgba(248, 250, 252, 0.9),
122
+ 0 0 24px rgba(250, 249, 246, 0.9);
123
+ top: 0;
124
+ left: 50%;
125
+ transform: translate(-50%, -50%);
126
+ transform-origin: 50% 18px;
127
+ animation: next-sdk-indicator-orbit 1.4s linear infinite;
128
+ }
129
+
130
+ .next-sdk-tool-overlay__indicator-core {
131
+ width: 14px;
132
+ height: 14px;
133
+ border-radius: inherit;
134
+ background: radial-gradient(circle at 30% 20%, #f9fafb, #bfdbfe);
135
+ box-shadow:
136
+ 0 0 12px rgba(248, 250, 252, 0.9),
137
+ 0 0 32px rgba(191, 219, 254, 0.8);
138
+ opacity: 0.96;
139
+ }
140
+
141
+ .next-sdk-tool-overlay__content {
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: 1px;
145
+ min-width: 0;
146
+ }
147
+
148
+ .next-sdk-tool-overlay__title {
149
+ font-size: 11px;
150
+ letter-spacing: 0.08em;
151
+ text-transform: uppercase;
152
+ color: rgba(156, 163, 175, 0.96);
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 6px;
156
+ }
157
+
158
+ .next-sdk-tool-overlay__title-dot {
159
+ width: 6px;
160
+ height: 6px;
161
+ border-radius: 999px;
162
+ background: #22c55e;
163
+ box-shadow:
164
+ 0 0 8px rgba(34, 197, 94, 0.9),
165
+ 0 0 14px rgba(22, 163, 74, 0.9);
166
+ }
167
+
168
+ .next-sdk-tool-overlay__label {
169
+ font-size: 12px;
170
+ font-weight: 500;
171
+ color: #e5e7eb;
172
+ white-space: nowrap;
173
+ text-overflow: ellipsis;
174
+ overflow: hidden;
175
+ }
176
+
177
+ @keyframes next-sdk-overlay-fade-in {
178
+ from { opacity: 0; }
179
+ to { opacity: 1; }
180
+ }
181
+
182
+ @keyframes next-sdk-overlay-fade-out {
183
+ from { opacity: 1; }
184
+ to { opacity: 0; }
185
+ }
186
+
187
+ @keyframes next-sdk-indicator-orbit {
188
+ from {
189
+ transform: translate(-50%, -50%) rotate(0deg) translateY(0);
190
+ }
191
+ to {
192
+ transform: translate(-50%, -50%) rotate(360deg) translateY(0);
193
+ }
194
+ }
195
+
196
+ @keyframes next-sdk-panel-pop-in {
197
+ 0% {
198
+ opacity: 0;
199
+ transform: scale(0.92) translateY(10px);
200
+ }
201
+ 100% {
202
+ opacity: 1;
203
+ transform: scale(1) translateY(0);
204
+ }
205
+ }
206
+ `
207
+
208
+ document.head.appendChild(style)
209
+ styleElement = style
210
+ }
211
+
212
+ function ensureOverlayElement() {
213
+ if (!ensureDomReady()) return
214
+ if (overlayElement) return
215
+
216
+ ensureStyleElement()
217
+
218
+ const overlay = document.createElement('div')
219
+ overlay.className = 'next-sdk-tool-overlay'
220
+
221
+ const glowRing = document.createElement('div')
222
+ glowRing.className = 'next-sdk-tool-overlay__glow-ring'
223
+
224
+ const panel = document.createElement('div')
225
+ panel.className = 'next-sdk-tool-overlay__panel'
226
+
227
+ const indicator = document.createElement('div')
228
+ indicator.className = 'next-sdk-tool-overlay__indicator'
229
+
230
+ const indicatorOrbit = document.createElement('div')
231
+ indicatorOrbit.className = 'next-sdk-tool-overlay__indicator-orbit'
232
+
233
+ const indicatorCore = document.createElement('div')
234
+ indicatorCore.className = 'next-sdk-tool-overlay__indicator-core'
235
+
236
+ const content = document.createElement('div')
237
+ content.className = 'next-sdk-tool-overlay__content'
238
+
239
+ const titleRow = document.createElement('div')
240
+ titleRow.className = 'next-sdk-tool-overlay__title'
241
+ titleRow.textContent = 'AI 正在调用页面工具'
242
+
243
+ const titleDot = document.createElement('span')
244
+ titleDot.className = 'next-sdk-tool-overlay__title-dot'
245
+
246
+ const label = document.createElement('div')
247
+ label.className = 'next-sdk-tool-overlay__label'
248
+
249
+ titleRow.prepend(titleDot)
250
+ content.appendChild(titleRow)
251
+ content.appendChild(label)
252
+
253
+ indicator.appendChild(indicatorOrbit)
254
+ indicator.appendChild(indicatorCore)
255
+ panel.appendChild(indicator)
256
+ panel.appendChild(content)
257
+ overlay.appendChild(glowRing)
258
+ overlay.appendChild(panel)
259
+
260
+ document.body.appendChild(overlay)
261
+
262
+ overlayElement = overlay
263
+ labelElement = label
264
+ }
265
+
266
+ function updateOverlay(config: RuntimeEffectConfig) {
267
+ if (!ensureDomReady()) return
268
+ ensureOverlayElement()
269
+ if (!overlayElement || !labelElement) return
270
+ overlayElement.classList.remove('next-sdk-tool-overlay--exit')
271
+ labelElement.textContent = config.label
272
+ }
273
+
274
+ function removeOverlayWithAnimation() {
275
+ if (!overlayElement) return
276
+ overlayElement.classList.add('next-sdk-tool-overlay--exit')
277
+ const localOverlay = overlayElement
278
+ let handled = false
279
+ let timerId: ReturnType<typeof setTimeout> | undefined
280
+ const handle = () => {
281
+ if (handled) return
282
+ handled = true
283
+ if (timerId !== undefined) {
284
+ clearTimeout(timerId)
285
+ timerId = undefined
286
+ }
287
+ if (localOverlay.parentNode) {
288
+ localOverlay.parentNode.removeChild(localOverlay)
289
+ }
290
+ if (overlayElement === localOverlay) {
291
+ overlayElement = null
292
+ labelElement = null
293
+ }
294
+ localOverlay.removeEventListener('animationend', handle)
295
+ }
296
+ localOverlay.addEventListener('animationend', handle)
297
+ // 兜底:若 animationend 未触发(如 prefers-reduced-motion 或样式被覆盖),确保清理
298
+ timerId = setTimeout(handle, 500)
299
+ }
300
+
301
+ /**
302
+ * 在页面上显示工具调用提示效果。
303
+ * - 若当前已有其他工具在执行,仅更新文案,不重复创建 DOM
304
+ * - 会增加 activeCount 计数,需配对调用 hideToolInvokeEffect
305
+ */
306
+ export function showToolInvokeEffect(config: RuntimeEffectConfig) {
307
+ if (!ensureDomReady()) return
308
+ activeCount += 1
309
+ updateOverlay(config)
310
+ }
311
+
312
+ /**
313
+ * 隐藏工具调用提示效果。
314
+ * - 使用引用计数:只有当所有调用结束后才真正移除 Overlay
315
+ */
316
+ export function hideToolInvokeEffect() {
317
+ if (!ensureDomReady() || activeCount <= 0) return
318
+ activeCount -= 1
319
+ if (activeCount === 0) {
320
+ removeOverlayWithAnimation()
321
+ }
322
+ }
323
+
324
+ /**
325
+ * 将 RouteConfig 中的 invokeEffect 配置编译成运行时效果配置。
326
+ * - boolean / undefined:关闭或开启默认文案
327
+ * - 对象:允许自定义 label
328
+ */
329
+ export function resolveRuntimeEffectConfig(
330
+ toolName: string,
331
+ toolTitle: string | undefined,
332
+ value: boolean | ToolInvokeEffectConfig | undefined
333
+ ): RuntimeEffectConfig | undefined {
334
+ if (!value) return undefined
335
+ const baseLabel = toolTitle || toolName
336
+ if (typeof value === 'boolean') {
337
+ return value ? { label: baseLabel } : undefined
338
+ }
339
+ return {
340
+ label: value.label || baseLabel
341
+ }
342
+ }
343
+
@@ -159,18 +159,17 @@ class FloatingBlock {
159
159
  return getDefaultMenuItems(this.options)
160
160
  }
161
161
 
162
- return getDefaultMenuItems(this.options)
163
- .map((defaultItem) => {
164
- const userItem = userMenuItems.find((item) => item.action === defaultItem.action)
165
- if (userItem) {
166
- return {
167
- ...defaultItem,
168
- ...userItem,
169
- show: userItem.show !== undefined ? userItem.show : defaultItem.show
170
- }
162
+ return getDefaultMenuItems(this.options).map((defaultItem) => {
163
+ const userItem = userMenuItems.find((item) => item.action === defaultItem.action)
164
+ if (userItem) {
165
+ return {
166
+ ...defaultItem,
167
+ ...userItem,
168
+ show: userItem.show !== undefined ? userItem.show : defaultItem.show
171
169
  }
172
- return defaultItem
173
- })
170
+ }
171
+ return defaultItem
172
+ })
174
173
  }
175
174
 
176
175
  private init(): void {
@@ -913,6 +912,21 @@ class FloatingBlock {
913
912
  this.dropdownMenu.parentNode.removeChild(this.dropdownMenu)
914
913
  }
915
914
  }
915
+
916
+ // 隐藏组件
917
+ public hide(): void {
918
+ if (this.floatingBlock) {
919
+ this.floatingBlock.style.display = 'none'
920
+ }
921
+ this.closeDropdown()
922
+ }
923
+
924
+ // 显示组件
925
+ public show(): void {
926
+ if (this.floatingBlock) {
927
+ this.floatingBlock.style.display = 'flex'
928
+ }
929
+ }
916
930
  }
917
931
 
918
932
  // 导出组件