关于 wix-cli-dashboard-modal
用于 Wix CLI 的技能,介绍如何借助 Dashboard SDK 构建仪表板弹窗扩展,涵盖弹窗配置、与调用方交换数据、生命周期控制及注册。
主要功能
- 01三文件弹窗结构
- 02使用 SDK 打开和关闭弹窗
- 03通过 params 传递数据
- 04观察弹窗状态
- 05向调用方返回数据
- 06注册仪表板扩展
适用场景
- 01在仪表板页面添加编辑弹窗
- 02向弹窗传入选中项目
- 03关闭弹窗时返回保存的数据
- 04配置弹窗尺寸和标题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wix/ skills --skill wix-cli-dashboard-modal安装后在 Claude Code 中输入/wix-cli-dashboard-modal确认已加载。
详细介绍
概述
仪表板模态框是从仪表板页面或插件触发的弹出对话框。它们由三个文件组成,并使用 Dashboard SDK 通过 openModal() 和 closeModal() 控制生命周期。
快速参考
| 任务 | 方法 | 示例 |
|---|---|---|
| 创建模态框 | 在 src/extensions/dashboard/modals/<folder>/ 中创建 3 个文件 | 请参阅下文的文件结构 |
| 打开模态框 | dashboard.openModal() | openModal({ modalId: "modal-id" }) |
| 向模态框传递数据 | openModal() 中的 params | params: { userId: "123" } |
| 在模态框中读取数据 | observeState() | dashboard.observeState((state) => { ... }) |
| 关闭模态框 | dashboard.closeModal() | closeModal() |
| 将数据返回给父级 | 将数据传递给 closeModal() | closeModal({ ... }) |
| 等待模态框关闭 | modalClosed Promise | const { modalClosed } = openModal(...); |
文件结构
在 src/extensions/dashboard/modals/<folder-name>/ 中创建 三个文件:
extensions.ts- 包含模态框 ID、标题、尺寸和组件路径的构建器配置<modal-name>.tsx- 用于渲染模态框内容的 React 组件<modal-name>.config.ts- 可配置的模态框属性(标题、宽度、高度)
实现
创建模态框
创建三个必需文件:
1. extensions.ts - 模态框构建器配置:
import { extensions } from '@wix/astro/builders';
import config from './<modal-name>.config.ts';
export default extensions.dashboardModal({
id: "{{GENERATE_UUID}}",
title: config.title,
width: config.width,
height: config.height,
component: './extensions/dashboard/modals/<modal-name>/<modal-name>.tsx',
});
重要:生成 UUID
id 必须是唯一且静态的 UUID v4 字符串。为每个扩展生成一个新的 UUID——不要使用 randomUUID(),也不要复制示例中的 UUID。将 {{GENERATE_UUID}} 替换为新生成的 UUID,例如 "a1b2c3d4-e5f6-7890-abcd-ef1234567890"。
构建器字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 唯一的模态框 ID(GUID)。与 openModal() 配合使用 |
| title | string | 在项目仪表板中显示的模态框标题 |
| width | number | 加载期间的初始宽度 |
| height | number | 加载期间的初始高度 |
| component | string | 模态框内容 .tsx 文件的路径 |
2. <modal-name>.tsx - 模态框内容组件:
import type { FC } from 'react';
import { dashboard } from '@wix/dashboard';
import {
WixDesignSystemProvider,
Text,
Box,
CustomModalLayout,
} from '@wix/design-system';
import '@wix/design-system/styles.global.css';
import config from './<modal-name>.config.ts';
const { width, height, title } = config;
// To open your modal, call `openModal` with your modal id.
// e.g.
// import { dashboard } from '@wix/dashboard';
// function MyComponent() {
// return <button onClick={() => dashboard.openModal({ modalId: '8ef4d434-9c80-44f5-a3f5-6f15f3a34be7' })}>Open Modal</button>;
// }
const Modal: FC = () => {
return (
<WixDesignSystemProvider features={{ newColorsBranding: true }}>
<CustomModalLayout
width={width}
maxHeight={height}
primaryButtonText="Save"
secondaryButtonText="Cancel"
primaryButtonOnClick={() => dashboard.closeModal()}
secondaryButtonOnClick={() => dashboard.closeModal()}
title={title}
subtitle="Edit this file to customize your modal"
content={
<Box direction="vertical" align="center">
<Text>Wix CLI Modal</Text>
</Box>
}
/>
</WixDesignSystemProvider>
);
};
export default Modal;
3. <modal-name>.config.ts - 可配置属性:
export default {
title: "My Modal",
width: 600,
height: 400,
};
然后在 src/extensions.ts 中注册:
import { dashboardmodalYourModal } from './extensions/dashboard/modals/<modal-name>/extensions.ts';
export default app()
.use(dashboardmodalYourModal)
// ... other extensions
打开模态框
import { dashboard } from "@wix/dashboard";
// Simple open
const result = await dashboard.openModal({
modalId: "your-modal-id", // From .extension.ts id field
});
// Pass data to modal via params
const result = await dashboard.openModal({
modalId: "your-modal-id",
params: {
userId: user.id,
itemData: complexObject, // Objects are passed directly, no encoding needed
},
});
// Get notified when the modal is closed
const { modalClosed } = dashboard.openModal({
modalId: "your-modal-id",
});
const result = await modalClosed; // Resolves with data from closeModal()
在模态框中接收数据
使用 observeState() 访问通过 openModal() 中的 params 传递的数据:
import { dashboard } from "@wix/dashboard";
import { useEffect, useState } from "react";
function MyModal() {
const [modalData, setModalData] = useState<{ userId?: string; itemData?: any }>({});
useEffect(() => {
dashboard.observeState((state) => {
// Access custom data passed through openModal params
if (state.userId) {
setModalData({
userId: state.userId,
itemData: state.itemData,
});
}
});
}, []);
return <div>User ID: {modalData.userId}</div>;
}
关闭模态框
在模态框扩展中调用 closeModal() 以关闭模态框。也可以选择将数据返回给打开模态框的一方。
import { dashboard } from "@wix/dashboard";
// Close without returning data
dashboard.closeModal();
// Close with custom return data
dashboard.closeModal({ saved: true, itemId: "123" });
| 参数 | 类型 | 说明 |
|---|---|---|
| closeData | Serializable(可选) | 要传回给模态框打开方的数据。必须可通过结构化克隆算法进行克隆。 |
**支持的类型:**字符串、数字、布尔值、普通对象、数组、Dates、Maps、Sets、ArrayBuffers。 **不支持:**函数、DOM 节点、带有方法的类实例、Symbols、Promises。
返回值:void
自定义模态框
在 .config.ts 中编辑整理后的设置:
export default {
title: 'User Settings',
width: 600,
height: 500,
}
在 .tsx 中导入:
import config from './modal.config.ts';
export default function MyModal() {
return (
<CustomModalLayout
title={config.title}
// ... rest of component
/>
);
}
常见错误
| 错误 | 修复方法 |
|---|---|
| 找不到模态框 ID | 检查 .extension.ts 文件的 id 字段 |
忘记在 extensions.ts 中注册 | 导入模态框并调用 .use() |
使用 extensionId 而不是 modalId | 在 openModal() 中使用 modalId |
| 无法在模态框中访问 params | 使用 dashboard.observeState() 读取传递的数据 |
| 模态框无法关闭 | 使用来自 @wix/dashboard 的 dashboard.closeModal() |
实际示例
// Dashboard Page: Opening edit modal
const handleEdit = async (item: Item) => {
dashboard.openModal({
modalId: "edit-item-modal-guid",
params: {
itemId: item._id,
item: item, // Objects passed directly via params
},
});
};
// Modal: Receiving and saving data
export default function ItemEditModal() {
const [formData, setFormData] = useState<Item | null>(null);
useEffect(() => {
dashboard.observeState((state) => {
if (state.item) {
setFormData(state.item);
}
});
}, []);
const handleSave = async () => {
// Save logic
dashboard.showToast({ message: "Saved!", type: "success" });
dashboard.closeModal();
};
return (
<CustomModalLayout
title="Edit Item"
primaryButtonText="Save"
onCloseButtonClick={() => dashboard.closeModal()}
primaryButtonOnClick={handleSave}
content={/* form fields */}
/>
);
}