我打造了一个工具,可通过一次 Drag & Drop 生成160种不同的代码组合——操作方法

发布: (2025年12月20日 GMT+8 11:45)
8 min read
原文: Dev.to

Source: Dev.to

TL;DR

我已经厌倦了一遍又一遍地编写相同的活动着陆页代码。因此我创建了 PromoKit ——将按钮拖到图片上,选择你的框架,即可获得可直接投产的代码。

10 种框架 × 16 种样式选项。
让我带你了解我是如何打造这个“怪兽”的。

起源故事:“能把那个按钮上移 3 像素吗?”

  1. 编写 HTML。
  2. 添加用于定位、按钮样式、悬停效果的 CSS。
  3. 完成。

随后收到邮件:

  • “能把 CTA 按钮稍微向右移动一下吗?” → 调整 CSS → 完成。
  • “其实,能把它上移 20 像素吗?” → 再次调整 CSS → 完成。

我构建的:PromoKit

  • 上传 你的宣传图片
  • 拖拽 按钮、文字和图片覆盖层
  • 选择 你的框架和样式方式
  • 复制 可直接用于生产的代码

就是这样。再也不需要手动计算坐标。再也不必为 React、Vue 和 Vanilla JS 重复编写相同的代码。

在线演示

https://promotion-page-editor.netlify.app/

源代码

GitHub – seadonggyun4/promo-kit

让我自豪的数字

类别细节
框架React, Vue, Svelte, Angular, Solid.js, Preact, Astro, Qwik, Lit, Vanilla HTML
样式选项CSS, SCSS, Tailwind, Styled Components, Emotion, CSS Modules, UnoCSS, Panda CSS, …
预设27 种按钮样式 + 5 种文字样式 + 12 种图片叠加 = 44
组合任意框架 × 任意样式 = 160 种可能的技术栈

Source:

技术深度解析:它是如何构建的

src/
├── features/
│   ├── button-editor/            # Button creation & customization
│   ├── text-editor/              # Text element management
│   ├── image-overlay-editor/    # Image overlay handling
│   ├── download/                 # The 2200+‑line code generator
│   └── version-history/         # Undo/redo system
└── shared/
    └── store/                    # Zustand state management

每个功能都是完全独立的。想要添加新的元素类型?新建一个 feature 文件夹——完成。无需触碰已有代码。

状态管理:带超能力的 Zustand

// Every mutation automatically logs to history
const updateElement = (id, data) => {
  set(state => {
    const updated = state.elements.map(el =>
      el.id === id ? { ...el, ...data } : el
    );

    // Automatic history tracking!
    pushToHistory(updated, 'Style Changed', 'element_style');

    return { elements: updated };
  });
};

**魔法所在:**用户根本不需要考虑保存状态。每一次操作都会自动可逆。Ctrl+Z 直接生效。

历史记录系统:正确实现的命令模式

interface HistoryStore {
  past: EditorSnapshot[];      // Previous states
  present: EditorSnapshot;    // Current state
  future: EditorSnapshot[];    // Redo states
}

每个快照记录:

  • 所有元素及其位置和样式
  • 背景图(已压缩)
  • 当前选中的元素
  • 时间戳和操作描述
  • 用于视觉区分的操作类型

防抖历史记录

// During drag operations we don't want 60 snapshots per second
debouncedPushToHistory(
  elements,
  'Position Changed',
  'element_move',
  500   // ms of inactivity before saving
);

当你拖动按钮时,会在 500 毫秒无操作后才保存——流畅的用户体验,且不影响性能。

代码生成引擎:2200+ 行框架魔法

function generateCode(
  framework: Framework,
  styling: StylingMethod,
  elements: Element[],
  options: CodeOptions
): string {
  // 1. Detect element types (gradient buttons need special handling)
  const hasGradients = elements.some(el => isGradientButton(el.style));

  // 2. Generate framework‑specific imports
  const imports = generateImports(framework, styling, hasGradients);

  // 3. Generate element markup per framework
  const markup = elements.map(el =>
    generateElementMarkup(el, framework, styling)
  );

  // 4. Generate styles per styling method
  const styles = generateStyles(elements, styling, options);

  // 5. Assemble into framework structure
  return assembleComponent(framework, imports, markup, styles);
}

输出适配所有情况:

框架事件语法样式集成
ReactonClick={...}Styled Components、Tailwind 等
Vue@click="..."Scoped CSS、CSS Modules
Svelteon:click={...}“ blocks 或 Tailwind
Angular(click)="..."ngClassngStyle
Solid.js, Preact, Astro, Qwik, Lit, Vanilla类似约定

示例输出

React

export const PromoPage: React.FC = () => {
  return (
    <button onClick={() => {
      window.location.href = '/deal';
    }}>
      Get 50% Off
    </button>
  );
};

Vue(Script Setup)

<template>
  <button @click="handleClick">Get 50% Off</button>
</template>

<script setup>
const handleClick = () => {
  window.location.href = '/deal';
};
</script>

可视化历史时间轴

不仅仅是撤销/重做按钮——一个完整的可视化时间轴,您可以:

  • 查看每个操作是什么(带图标)
  • 点击任意点跳转
  • 查看存在多少过去/未来状态

它就像是您设计的 Git

智能预设

44 个按类别组织的预设:

类别数量示例
简约按钮11简洁、极简设计
渐变按钮10抢眼的渐变
动画按钮6弹跳、发光、脉冲、抖动、滑动、涟漪

每个预设在放置后都可以完全自定义。

实时预览

(当您编辑时,实时预览窗格会更新。)

可访问性内置

  • 语义标签(<button><a> 等)
  • aria-label 属性
  • 用于扩展描述的 aria-describedby
  • 需要时的 role 属性
  • 为键盘用户提供的 :focus-visible 样式
  • 屏幕阅读器专用内容

响应式代码生成

切换 “responsive”,生成的代码会自动包含媒体查询:

@media (max-width: 768px) {
  .button {
    transform: scale(0.85);
    font-size: 0.9rem;
  }
}

@media (max-width: 640px) {
  .button {
    transform: scale(0.7);
    font-size: 0.8rem;
  }
}

您的着陆页可在任何地方自动运行。

SEO 元标签

需要 Open Graph 标签?Twitter 卡片?规范 URL?一次切换。完成。

架构选择

Zustand 优于 Redux

  • 更简洁的 API
  • 内置历史记录跟踪(如上所示)

功能切片设计

  • 明确的关注点分离
  • 易于扩展

组合优于继承(用于 Hook)

// Base form logic
const baseForm = useButtonForm();

// Extended for animated buttons
const animatedForm = useAnimatedBtn(); // Internally uses useButtonForm

清晰、可测试且遵循 DRY 原则。

动态导入用于循环依赖

const getBackgroundImage = async () => {
  const module = await import('./background');
  return module.default;
};

杂项代码片段

// Retrieve the uploaded image from the Zustand store
() => {
  const { useUploadImageStore } = require('./uploadImageStore');
  return useUploadImageStore.getState().uploadedImage;
};

虽然不够优雅,但它巧妙地解决了一个真实的问题。

技术栈

  • React 18 with TypeScript
  • Vite – blazing‑fast development server
  • Zustand – state management
  • styled‑components – styling
  • dnd‑kit – drag‑and‑drop
  • react‑i18next – Korean, English, Japanese support
  • JSZip + FileSaver – ZIP downloads

演示与来源


⭐️ 如果你觉得这很酷,请点星。
🐞 如有想看到的缺失框架,请提交 issue。
🔧 非常欢迎提交 Pull Request!

本项目在大量的挫败感、咖啡以及坚信开发者不应重复编写相同 CSS 的信念下构建。

Back to Blog

相关文章

阅读更多 »

最佳 SaaS 仪表盘模板

现代 SaaS 仪表板模板 – 开发者决策指南 现代 SaaS 产品的成败取决于它们展示数据的清晰程度。创始人、增长团队以及客户……