阅读视图

发现新文章,点击刷新页面。

【AI 编程实战】第 4 篇:一次完美 vs 五轮对话 - UnoCSS 配置的正确姿势

很多教程告诉你"写好提示词就能让 AI 一次给出正确答案",但现实是:你不可能一开始就知道所有细节。真正高效的 AI 协作,是在对话中逐步明确需求。这篇文章通过一个完整的 UnoCSS 配置案例,展示我和 TRAE SOLO 的真实交互过程——从模糊想法到完整方案,中间经历了哪些沟通和调整。

这是《AI 编程实战:TRAE SOLO 全栈开发指南》专栏的第四篇文章,带你学会如何与 AI 正确的沟通协作。

一、开篇:别被"完美提示词"骗了

1.1 一个常见的误区

网上很多 AI 教程会给你看这样的"完美提示词":

帮我配置 UnoCSS,项目环境和需求如下:
【项目环境】UniApp + Vue3 + Vite,目标平台微信小程序
【功能需求】支持 attributify、@apply、图标、安全区适配
【已知问题】小程序 transform 类需要预设 CSS 变量
【输出要求】完整的 uno.config.ts 配置文件

然后告诉你:"看,一次就能得到完美答案!"

但问题是

  • 你一开始怎么知道需要"安全区适配"?
  • 你怎么知道"transform 类需要预设 CSS 变量"?
  • 这些都是踩坑之后才知道的

1.2 真实的 AI 协作是什么样的

真实的 AI 协作是一个渐进式对话的过程:

第 1 轮:我有个模糊的需求
    ↓
AI:给出初步方案 + 执行计划
    ↓
第 2 轮:我发现问题 / 补充细节
    ↓
AI:调整方案
    ↓
第 3 轮:遇到报错 / 样式不生效
    ↓
AI:排查问题 + 修复
    ↓
最终:得到完整可用的方案

接下来,我会完整展示和 TRAE SOLO 配置 UnoCSS 的真实对话过程

二、第一轮对话:从模糊需求开始

2.1 我的第一句话

刚开始,我对 UnoCSS 的了解很有限,只知道它是"原子化 CSS"。我的第一句话很简单:

我:项目需要用 UnoCSS,帮我配置一下

2.2 TRAE SOLO 的响应:先给执行计划

TRAE SOLO 没有直接开始写代码,而是先给出了执行计划

TRAE SOLO:好的,我来帮你配置 UnoCSS。在开始之前,我需要先了解一些信息:

📋 执行计划:
1. 分析项目结构,确定技术栈
2. 选择合适的 UnoCSS 预设
3. 生成配置文件
4. 配置 Vite 插件

🤔 我有几个问题:
1. 这是什么类型的项目?(Vue/React/UniApp)
2. 目标运行平台是什么?(Web/小程序/App)
3. 有没有特别想要的功能?(图标/attributify 模式等)

请告诉我这些信息,或者我可以先分析你的项目结构来判断。

关键点:TRAE SOLO 主动询问上下文,而不是瞎猜。

2.3 我补充信息

我:UniApp + Vue3 项目,主要跑微信小程序,也要支持 H5。
    功能的话,我也不太清楚,你看着配吧,常用的都加上。

2.4 TRAE SOLO 分析项目后给出方案

TRAE SOLO:我分析了你的项目结构,发现:
- 框架:UniApp + Vue3 + Vite
- UI 库:已安装 wot-design-uni
- 目标:微信小程序 + H5

📋 更新执行计划:

1. 安装依赖
   - unocss
   - @uni-helper/unocss-preset-uni (UniApp 专用预设,必须)
   - @iconify-json/carbon (图标库,可选)

2. 创建 uno.config.ts,包含:
   - presetUni:小程序适配
   - presetAttributify:支持属性化写法
   - presetIcons:图标支持
   - transformerDirectives:支持 @apply 指令

3. 修改 vite.config.ts,添加 UnoCSS 插件

4. 在 main.ts 中引入 uno.css

是否按这个计划执行?或者你想调整什么?

2.5 我确认执行

我:可以,就按这个来
TRAE SOLO:好的,开始执行...

✅ 正在安装依赖...
✅ 创建 uno.config.ts...
✅ 修改 vite.config.ts...
✅ 修改 main.ts...

配置完成!你可以这样使用:

<view class="flex items-center gap-4 p-4 bg-white rounded-xl">
  <text class="text-lg font-bold">Hello UnoCSS</text>
</view>

运行 pnpm dev:mp-weixin 试试看效果。

三、第二轮对话:遇到问题,继续沟通

3.1 我发现样式不生效

运行项目后,我发现部分样式不生效:

我:样式大部分生效了,但是 translate-x-2 这个类没效果,
    元素没有移动

3.2 TRAE SOLO 排查问题

TRAE SOLO:让我检查一下...

我查看了生成的 CSS,发现 translate-x-2 生成的代码是:

transform: translateX(var(--un-translate-x));

问题在于:小程序环境没有预设 --un-translate-x 这个 CSS 变量。

在浏览器中,UnoCSS 会通过 :root 选择器预设这些变量,
但小程序不支持 :root 选择器。

🔧 修复方案:
在 uno.config.ts 中添加 preflights,手动为小程序元素预设变量。

是否需要我帮你修复?

3.3 我让它修复

我:帮我修复吧
TRAE SOLO:好的,我来修改 uno.config.ts...

添加了以下配置:

preflights: [
  {
    layer: 'unocss-transform-fix',
    getCSS: () => `
page, view, text, image {
  --un-translate-x: 0;
  --un-translate-y: 0;
  --un-rotate: 0;
  --un-scale-x: 1;
  --un-scale-y: 1;
  /* ... 其他变量 */
}
`,
  },
],

✅ 已修复,重新运行项目试试。

3.4 我验证修复成功

我:好了,translate 生效了

四、第三轮对话:追加新需求

4.1 我想添加安全区适配

开发过程中,我发现 iPhone 底部有安全区域遮挡内容:

我:iPhone 底部有个横条会挡住内容,UnoCSS 能处理这个吗?

4.2 TRAE SOLO 给出方案

TRAE SOLO:这是 iPhone 的安全区域问题。可以通过自定义 UnoCSS 规则来解决。

📋 方案:
添加以下自定义规则:
- pb-safe:底部安全区内边距
- pt-safe:顶部安全区内边距(刘海屏)
- p-safe:四边安全区内边距

使用方式:
<view class="pb-safe">底部内容会避开安全区</view>

是否需要我添加这些规则?
我:加上吧
TRAE SOLO:好的,在 uno.config.ts 的 rules 中添加:

rules: [
  ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
  ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
  ['p-safe', {
    padding: 'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
  }],
],

✅ 已添加,现在可以使用 pb-safe 类了。

五、第四轮对话:优化开发体验

5.1 我觉得类名太长了

我:flex items-center justify-between 每次都要写这么长,
    有没有办法简化?

5.2 TRAE SOLO 介绍 shortcuts

TRAE SOLO:可以用 UnoCSS 的 shortcuts 功能,把常用组合定义成短类名。

比如:
- center = flex justify-center items-center
- between = flex justify-between items-center

你想定义哪些快捷方式?或者我根据你的代码分析常用组合?
我:你分析一下吧,看哪些用得多

5.3 TRAE SOLO 分析代码后给出建议

TRAE SOLO:我扫描了你的代码,发现以下组合出现频率较高:

📊 使用频率分析:
1. flex items-center (出现 23 次)
2. flex items-center justify-between (出现 15 次)
3. flex items-center justify-center (出现 12 次)
4. flex flex-col (出现 8 次)
5. bg-white rounded-xl p-4 (出现 7 次)

🔧 建议添加的 shortcuts:

shortcuts: [
  { 'center': 'flex justify-center items-center' },
  { 'between': 'flex justify-between items-center' },
  { 'col': 'flex flex-col' },
  { 'card': 'bg-white rounded-xl p-4' },
],

是否按这个配置?或者你想调整?
我:可以,就这样

六、第五轮对话:处理动态类名问题

6.1 我遇到动态类名不生效

我:我有个地方根据状态切换颜色:

    <text :class="`text-${status}-500`">状态</text>

    status 可能是 red、green、blue,但样式不生效

6.2 TRAE SOLO 解释原因并给方案

TRAE SOLO:这是 UnoCSS 的一个限制。

🔍 原因:
UnoCSS 在构建时静态扫描代码,`text-${status}-500` 这种
动态拼接的类名无法被识别,所以不会生成对应的 CSS。

🔧 解决方案有三种:

【方案一】对象语法(推荐)
<text :class="{
  'text-red-500': status === 'red',
  'text-green-500': status === 'green',
  'text-blue-500': status === 'blue',
}">状态</text>

【方案二】safelist 预声明
在 uno.config.ts 中添加:
safelist: ['text-red-500', 'text-green-500', 'text-blue-500']

【方案三】CSS 变量
<text class="text-[var(--status-color)]"
      :style="{ '--status-color': statusColor }">
  状态
</text>

你的场景用哪种方案比较合适?
我:颜色就这三种,用方案二吧,简单
TRAE SOLO:好的,已添加到 safelist:

safelist: [
  'text-red-500',
  'text-green-500',
  'text-blue-500',
  'i-carbon-code', // 之前已有的图标
],

✅ 现在动态类名可以正常工作了。

七、最终配置:多轮对话的成果

经过 5 轮对话,最终的 uno.config.ts 是这样的:

// uno.config.ts
// 这个配置是通过多轮对话逐步完善的,不是一次性写出来的

import { presetUni } from '@uni-helper/unocss-preset-uni'
import {
  defineConfig,
  presetAttributify,
  presetIcons,
  transformerDirectives,
  transformerVariantGroup,
} from 'unocss'

export default defineConfig({
  // 【第 1 轮】基础预设
  presets: [
    presetUni({
      attributify: {
        prefixedOnly: true,
      },
    }),
    presetIcons({
      scale: 1.2,
      warn: true,
      extraProperties: {
        'display': 'inline-block',
        'vertical-align': 'middle',
      },
    }),
    presetAttributify(),
  ],

  // 【第 1 轮】转换器
  transformers: [
    transformerDirectives(),
    transformerVariantGroup(),
  ],

  // 【第 2 轮】修复小程序 transform 不生效
  preflights: [
    {
      layer: 'unocss-transform-fix',
      getCSS: () => `
page, view, text, image {
  --un-translate-x: 0;
  --un-translate-y: 0;
  --un-translate-z: 0;
  --un-rotate: 0;
  --un-rotate-x: 0;
  --un-rotate-y: 0;
  --un-rotate-z: 0;
  --un-skew-x: 0;
  --un-skew-y: 0;
  --un-scale-x: 1;
  --un-scale-y: 1;
  --un-scale-z: 1;
}
`,
    },
  ],

  // 【第 3 轮】安全区适配
  rules: [
    ['p-safe', {
      padding: 'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
    }],
    ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
    ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
  ],

  // 【第 4 轮】常用快捷方式
  shortcuts: [
    { 'center': 'flex justify-center items-center' },
    { 'between': 'flex justify-between items-center' },
    { 'col': 'flex flex-col' },
    { 'card': 'bg-white rounded-xl p-4' },
  ],

  // 【第 5 轮】动态类名预声明
  safelist: [
    'text-red-500',
    'text-green-500',
    'text-blue-500',
    'i-carbon-code',
  ],

  // 【第 1 轮】主题配置
  theme: {
    colors: {
      primary: 'var(--wot-color-theme, #0957DE)',
    },
    fontSize: {
      '2xs': ['20rpx', '28rpx'],
      '3xs': ['18rpx', '26rpx'],
    },
  },
})

八、组件开发:同样是对话式协作

8.1 开发回复卡片组件

配置完成后,我开始开发业务组件。同样是对话式协作:

我:帮我写一个回复卡片组件,用来展示 AI 生成的回复。
    左边是语气标签(比如"高情商"),中间是回复文字,右边是复制按钮。
TRAE SOLO:好的,我来帮你开发这个组件。

📋 执行计划:
1. 创建 ReplyCard.vue 组件
2. 定义 Props:tone(语气)、text(文本)
3. 定义 Events:copy(复制)
4. 使用 UnoCSS 原子类实现样式

🤔 几个问题:
1. 语气标签的颜色是固定的还是根据内容变化?
2. 复制按钮是图标还是文字?
3. 有没有设计稿可以参考?
我:标签颜色根据语气类型自动变,用哈希算法分配吧。
    复制按钮用图标。设计稿没有,你自己设计一个好看的。
TRAE SOLO:明白了,我来实现:

【设计方案】
- 卡片:白色背景、圆角、内边距
- 标签:小圆角、根据文字哈希分配 5 种颜色
- 文本:深灰色、支持多行
- 按钮:圆形点击区域、图标居中

开始生成代码...

8.2 TRAE SOLO 生成的组件

<!-- components/ReplyCard.vue -->
<template>
  <view class="flex items-center gap-2.5 bg-white rounded-xl p-4 pr-2">
    <!-- 语气标签 -->
    <view
      v-show="!hideTag"
      class="text-xs font-bold rounded-sm px-2 py-1 flex-shrink-0"
      :style="{ backgroundColor: tagStyle.bg, color: tagStyle.text }"
    >
      {{ tone }}
    </view>

    <!-- 回复文本 -->
    <view class="text-sm text-#1F2937 flex-1">
      {{ text }}
    </view>

    <!-- 复制按钮 -->
    <view
      class="w-9 h-9 center cursor-pointer"
      @click="$emit('copy', text)"
    >
      <image src="/static/images/home/copy.svg" mode="aspectFit" class="w-5 h-5" />
    </view>
  </view>
</template>

<script setup lang="ts">
import { computed } from 'vue';

interface Props {
  tone: string;
  text: string;
  hideTag?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  hideTag: false,
});

defineEmits<{
  copy: [text: string];
}>();

// 5 种预设颜色
const COLORS = [
  { bg: '#F3E8FF', text: '#8B5CF6' },  // 紫色
  { bg: '#E6FFFA', text: '#319795' },  // 青色
  { bg: '#FFF5E6', text: '#F56500' },  // 橙色
  { bg: '#FFE4E6', text: '#E11D48' },  // 粉色
  { bg: '#E0F2FE', text: '#0284C7' },  // 蓝色
];

// 根据语气文字哈希分配颜色
const tagStyle = computed(() => {
  let hash = 0;
  for (let i = 0; i < props.tone.length; i++) {
    hash = (hash + props.tone.charCodeAt(i)) % COLORS.length;
  }
  return COLORS[hash];
});
</script>

8.3 我提出修改意见

我:不错,但我想加一个功能:有时候不需要显示标签,能不能加个 prop 控制?
TRAE SOLO:已经有了,就是 hideTag 这个 prop。

使用方式:
<ReplyCard tone="高情商" text="回复内容" :hide-tag="true" />

设置 hide-tag 为 true 就会隐藏标签。
我:哦对,我没注意看。那再加个需求:复制成功后给个反馈
TRAE SOLO:复制反馈建议在父组件处理,因为:
1. 卡片组件应该保持简单,只负责 UI
2. 复制逻辑和 toast 提示是业务逻辑,放父组件更合适

父组件这样用:

<ReplyCard
  tone="高情商"
  text="回复内容"
  @copy="handleCopy"
/>

const handleCopy = async (text: string) => {
  await copyText(text);
  toast.success('已复制');
};

你觉得这样合理吗?还是希望把 toast 放在组件内部?
我:你说得对,放父组件更好

九、核心经验:与 AI 协作的正确姿势

9.1 不要追求"一次完美"

错误做法 正确做法
花 30 分钟写一个"完美提示词" 花 1 分钟说出需求,让 AI 提问
一次性想清楚所有细节 边做边发现问题,逐步完善
把 AI 当执行工具 把 AI 当协作伙伴,可以对话

9.2 让 AI 先给执行计划

每次提需求后,让 AI 先给出执行计划

我:[需求描述]

AI:📋 执行计划:
    1. xxx
    2. xxx
    3. xxx

    🤔 有几个问题:
    1. xxx?
    2. xxx?

    确认后我开始执行。

好处

  • 确保 AI 理解正确
  • 发现遗漏的需求
  • 可以在执行前调整方向

9.3 遇到问题时提供上下文

❌ 错误方式

我:样式不生效

✅ 正确方式

我:translate-x-2 这个类在小程序里不生效,但在 H5 正常。
    我看了生成的 CSS,是 transform: translateX(var(--un-translate-x))。
    是不是变量没定义?

9.4 让 AI 解释它的方案

当 AI 给出方案时,追问原理:

我:为什么要用 preflights?直接写 CSS 不行吗?
TRAE SOLO:preflights 是 UnoCSS 的预设样式注入机制,和直接写 CSS 的区别:

1. preflights 会被注入到所有生成的 CSS 之前,确保优先级正确
2. 可以通过 layer 控制加载顺序
3. 统一管理,修改时只需要改配置文件

直接写 CSS 也可以,但需要确保加载顺序正确,否则可能被覆盖。

好处:理解原理后,下次遇到类似问题可以自己判断。

十、总结:对话式协作 > 完美提示词

10.1 本文的核心观点

  1. 不要追求"一次完美的提示词",真实的 AI 协作是多轮对话
  2. 让 AI 先给执行计划,确认后再执行
  3. 遇到问题时提供上下文,帮助 AI 定位问题
  4. 追问原理,理解 AI 的方案,下次才能举一反三

10.2 5 轮对话完成的工作

轮次 需求 成果
第 1 轮 基础配置 presets、transformers、theme
第 2 轮 修复 transform 问题 preflights
第 3 轮 安全区适配 自定义 rules
第 4 轮 简化常用类名 shortcuts
第 5 轮 动态类名支持 safelist

10.3 下一篇预告

《【AI 编程实战】第 5 篇:Pinia 状态管理不会写?让 AI 手把手教你最佳实践》

下一篇继续展示对话式协作,教你:

  • 如何让 AI 设计 Store 结构
  • 状态持久化的对话过程
  • 用户登录态管理的多轮沟通

与 AI 协作的关键不是"写出完美提示词",而是学会和 AI 对话

如果这篇文章对你有帮助,请点赞、收藏!

❌