ByteNoteByteNote

字节笔记本

2026年7月20日

用 React 构建一个 Midjourney Prompt 提示词生成器

API中转
¥120

用 React 构建一个 Midjourney Prompt 提示词生成器

Midjourney 的提示词语法有自己的规则,参数多、选项杂。对于刚接触 AI 绘画的人来说,要记住 --v--niji--ar--chaos--stylize 这些参数的含义和取值范围并不容易。本文介绍如何用 React + shadcn/ui 构建一个可视化的提示词生成器,通过表单选择自动组装规范提示词。

核心功能设计

工具的目标是让用户不需要记任何参数,通过界面操作就能生成规范的 Midjourney 提示词。功能包括:

  • 主题描述输入框
  • 风格、媒介、光线、画家等分类选项
  • 版本和数值参数的滑块/选择
  • 开关类参数的切换
  • 否定提示词(--no
  • 一键生成、一键复制

状态管理

用一个聚合对象管理所有参数,比多个独立 useState 更清晰:

javascript
const [parameters, setParameters] = useState({
  prompt: '',
  version: '6.1',
  nijiVersion: '6',
  aspectRatio: '1:1',
  chaos: 0,
  stylize: 100,
  seed: 0,
  quality: 1,
  weird: 0,
  imageWeight: 25,
  negativePrompt: '',
});

const [toggles, setToggles] = useState({
  uplight: false,
  beta: false,
  hd: false,
  srefRandom: false,
});

const [selectedOptions, setSelectedOptions] = useState({
  medium: '',
  lighting: '',
  artist: '',
  style: '',
});

选项数据结构

每个选项用 { value, label } 对象数组定义,方便后续扩展和国际化:

javascript
const artMediums = [
  { value: 'acrylic', label: '丙烯' },
  { value: 'watercolor', label: '水彩' },
  { value: 'oil painting', label: '油画' },
  { value: 'digital art', label: '数字艺术' },
  { value: 'pencil sketch', label: '铅笔素描' },
  { value: 'ink drawing', label: '水墨画' },
  { value: 'photography', label: '摄影' },
  { value: '3d render', label: '3D 渲染' },
];

const lightings = [
  { value: 'golden hour', label: '黄金时刻' },
  { value: 'cinematic lighting', label: '电影光效' },
  { value: 'studio lighting', label: '棚拍光' },
  { value: 'neon lighting', label: '霓虹灯光' },
  { value: 'natural light', label: '自然光' },
  { value: 'dramatic lighting', label: '戏剧光效' },
  { value: 'soft lighting', label: '柔光' },
  { value: 'backlight', label: '逆光' },
];

提示词生成逻辑

核心是字符串拼接,只有当参数偏离默认值时才写入提示词:

javascript
function generatePrompt() {
  let prompt = parameters.prompt;

  // 追加风格相关选项
  if (selectedOptions.medium) prompt += `, ${selectedOptions.medium}`;
  if (selectedOptions.lighting) prompt += `, ${selectedOptions.lighting}`;
  if (selectedOptions.artist) prompt += `, by ${selectedOptions.artist}`;
  if (selectedOptions.style) prompt += `, ${selectedOptions.style}`;

  // 收集参数
  let paramString = '';

  // 版本参数(MJ 和 Niji 互斥)
  if (parameters.useNiji) {
    paramString += ` --niji ${parameters.nijiVersion}`;
  } else {
    paramString += ` --v ${parameters.version}`;
  }

  // 宽高比(非默认 1:1 时才添加)
  if (parameters.aspectRatio !== '1:1') {
    paramString += ` --ar ${parameters.aspectRatio}`;
  }

  // 数值参数(偏离默认值时添加)
  if (parameters.chaos > 0) paramString += ` --chaos ${parameters.chaos}`;
  if (parameters.stylize !== 100) paramString += ` --s ${parameters.stylize}`;
  if (parameters.quality !== 1) paramString += ` --q ${parameters.quality}`;
  if (parameters.weird > 0) paramString += ` --weird ${parameters.weird}`;
  if (parameters.seed !== 0) paramString += ` --seed ${parameters.seed}`;

  // 图像权重
  if (parameters.imageWeight !== 25) {
    paramString += ` --iw ${parameters.imageWeight / 100}`;
  }

  // 开关参数
  if (toggles.uplight) paramString += ' --uplight';
  if (toggles.beta) paramString += ' --beta';
  if (toggles.hd) paramString += ' --hd';
  if (toggles.srefRandom) paramString += ' --sref random';

  // 否定提示词
  if (parameters.negativePrompt) {
    paramString += ` --no ${parameters.negativePrompt}`;
  }

  return (prompt + paramString).trim();
}

UI 布局

用 Tabs 将选项分成四个区域,避免页面过长:

基础设置

jsx
<TabsContent value="basic">
  <div className="space-y-4">
    <div>
      <Label>主题描述</Label>
      <Textarea
        placeholder="描述你想要生成的画面..."
        value={parameters.prompt}
        onChange={(e) => updateParam('prompt', e.target.value)}
      />
    </div>
    <div className="grid grid-cols-2 gap-4">
      <div>
        <Label>MJ 版本</Label>
        <Select value={parameters.version}
          onValueChange={(v) => updateParam('version', v)}>
          <SelectItem value="6.1">V6.1 (推荐)</SelectItem>
          <SelectItem value="6.0">V6.0</SelectItem>
          <SelectItem value="5.2">V5.2</SelectItem>
        </Select>
      </div>
      <div>
        <Label>Niji 版本</Label>
        <Select value={parameters.nijiVersion}
          onValueChange={(v) => updateParam('nijiVersion', v)}>
          <SelectItem value="6">Niji 6</SelectItem>
          <SelectItem value="5">Niji 5</SelectItem>
        </Select>
      </div>
    </div>
    <div>
      <Label>宽高比</Label>
      <div className="grid grid-cols-4 gap-2">
        {['1:1', '16:9', '9:16', '4:3'].map(ratio => (
          <Button key={ratio}
            variant={parameters.aspectRatio === ratio ? 'default' : 'outline'}
            onClick={() => updateParam('aspectRatio', ratio)}>
            {ratio}
          </Button>
        ))}
      </div>
    </div>
  </div>
</TabsContent>

风格与效果

jsx
<TabsContent value="style">
  <div className="space-y-4">
    <div>
      <Label>艺术媒介</Label>
      <Select value={selectedOptions.medium}
        onValueChange={(v) => updateOption('medium', v)}>
        {artMediums.map(item => (
          <SelectItem key={item.value} value={item.value}>
            {item.label}
          </SelectItem>
        ))}
      </Select>
    </div>
    <div>
      <Label>光线效果</Label>
      <Select value={selectedOptions.lighting}
        onValueChange={(v) => updateOption('lighting', v)}>
        {lightings.map(item => (
          <SelectItem key={item.value} value={item.value}>
            {item.label}
          </SelectItem>
        ))}
      </Select>
    </div>
  </div>
</TabsContent>

技术参数

数值参数用 Slider 组件:

jsx
<TabsContent value="params">
  <div className="space-y-6">
    <div>
      <Label>Chaos (混乱度): {parameters.chaos}</Label>
      <Slider min={0} max={100} step={1}
        value={[parameters.chaos]}
        onValueChange={([v]) => updateParam('chaos', v)} />
      <p className="text-sm text-muted-foreground">
        值越高,结果越不可预测。默认 0。
      </p>
    </div>
    <div>
      <Label>Stylize (风格化): {parameters.stylize}</Label>
      <Slider min={0} max={1000} step={10}
        value={[parameters.stylize]}
        onValueChange={([v]) => updateParam('stylize', v)} />
      <p className="text-sm text-muted-foreground">
        低值更贴近提示词,高值更偏 Midjourney 自己的美学。默认 100。
      </p>
    </div>
    <div>
      <Label>Weird (奇特度): {parameters.weird}</Label>
      <Slider min={0} max={3000} step={10}
        value={[parameters.weird]}
        onValueChange={([v]) => updateParam('weird', v)} />
      <p className="text-sm text-muted-foreground">
        V5.2+ 可用,值越高越出格。默认 0。
      </p>
    </div>
  </div>
</TabsContent>

结果区域

jsx
<Card className="mt-6">
  <CardHeader>
    <CardTitle>生成结果</CardTitle>
  </CardHeader>
  <CardContent>
    <div className="bg-muted p-4 rounded-lg font-mono text-sm break-all">
      {generatedPrompt || '请填写主题描述'}
    </div>
    <div className="flex gap-2 mt-4">
      <Button onClick={() => setGeneratedPrompt(generatePrompt())}>
        生成提示词
      </Button>
      <Button variant="outline" onClick={handleCopy}>
        复制到剪贴板
      </Button>
    </div>
  </CardContent>
</Card>

复制功能

javascript
async function handleCopy() {
  try {
    await navigator.clipboard.writeText(generatedPrompt);
    // 可以加一个短暂的反馈提示
  } catch {
    // fallback: textarea + execCommand
    const textarea = document.createElement('textarea');
    textarea.value = generatedPrompt;
    document.body.appendChild(textarea);
    textarea.select();
    document.execCommand('copy');
    document.body.removeChild(textarea);
  }
}

Midjourney 常用参数速查

参数说明默认值范围
--vMJ 版本6.15.2 / 6.0 / 6.1
--nijiNiji 二次元版本65 / 6
--ar宽高比1:1任意比例
--chaos混乱度00-100
--stylize风格化程度1000-1000
--weird奇特度00-3000
--seed随机种子随机0-4294967295
--iw图像权重0.250-2
--q质量10.25 / 0.5 / 1 / 2
--no否定提示词文本
--uplightUpscale Light 模式off开关
--sref randomV6.1 风格随机化off开关

注意 --v--niji 是互斥的,不能同时使用。

扩展思路

  • 把选项数据抽成独立的 JSON 文件,方便维护和更新
  • 加入提示词历史记录,用 localStorage 持久化
  • 支持从已有提示词反向解析到表单
  • 预设一些常用的风格组合(如"赛博朋克"、"吉卜力风格"等),一键填入
  • 加一个随机组合按钮,帮助探索新风格
分享: