【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...配置完成!你可以这样使用:<viewclass="flex items-center gap-4 p-4 bg-white rounded-xl"><textclass="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><viewclass="flex items-center gap-2.5 bg-white rounded-xl p-4 pr-2"><!-- 语气标签 --><viewv-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><!-- 回复文本 --><viewclass="text-sm text-#1F2937 flex-1">{{ text }}</view><!-- 复制按钮 --><viewclass="w-9 h-9 center cursor-pointer"@click="$emit('copy', text)"><imagesrc="/static/images/home/copy.svg"mode="aspectFit"class="w-5 h-5" /></view></view></template><scriptsetuplang="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. 卡片组件应该保持简单,只负责 UI2. 复制逻辑和 toast 提示是业务逻辑,放父组件更合适父组件这样用:<ReplyCardtone="高情商"text="回复内容"@copy="handleCopy"/>const handleCopy = async (text: string) => {await copyText(text);toast.success('已复制');};你觉得这样合理吗?还是希望把 toast 放在组件内部?
我:你说得对,放父组件更好九、核心经验:与 AI 协作的正确姿势
9.1 不要追求"一次完美"
9.2 让 AI 先给执行计划
每次提需求后,让 AI 先给出「执行计划」:
我:[需求描述]AI:📋 执行计划:1. xxx2. xxx3. 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 本文的核心观点
「不要追求"一次完美的提示词"」,真实的 AI 协作是多轮对话 「让 AI 先给执行计划」,确认后再执行 「遇到问题时提供上下文」,帮助 AI 定位问题 「追问原理」,理解 AI 的方案,下次才能举一反三
10.2 5 轮对话完成的工作
10.3 下一篇预告
「《【AI 编程实战】第 5 篇:Pinia 状态管理不会写?让 AI 手把手教你最佳实践》」
下一篇继续展示对话式协作,教你:
如何让 AI 设计 Store 结构 状态持久化的对话过程 用户登录态管理的多轮沟通
❝与 AI 协作的关键不是"写出完美提示词",而是「学会和 AI 对话」。
如果这篇文章对你有帮助,请点赞、收藏!
❞