Pixso → Vue 标准工作流
> 本 Skill 由 pixso-to-miniprogram/SKILL.md 适配而来。
> 核心方法论(grep 提取 + top 归属图 + 两阶段还原 + 三遍走查)完全保留,
> 同时支持 Vue 2(Options API) 和 Vue 3(Composition API / <script setup>),
> 具体使用哪种由项目现有代码决定。
前置条件
- Pixso 桌面端已打开目标设计稿(版本 ≥ 2.2.0)
- 已选中需要转换的页面/组件(item-id 自动获取)
- Pixso MCP 已启用(
http://localhost:3667/mcp)
核心工具
| 工具 | 调用方式 | 输出 | 用途 |
|---|---|---|---|
design_to_code |
mcp_Pixso_MCP_design_to_code() |
HTML manifest (含临时 URL) | 首选:阅读布局结构、提取文本内容、反查 CSS 样式 |
get_node_dsl |
mcp_Pixso_MCP_get_node_dsl() |
JSON 图层树 | 辅助:查精确色值、字号、阴影参数、交互事件、组件语义 |
get_screenshot |
mcp_Pixso_MCP_get_screenshot() |
PNG 预览 | 验证:开发完成后对照设计稿确认视觉效果 |
refine_generated_code |
mcp_Pixso_MCP_refine_generated_code() |
优化后代码 | 可选:对已生成代码做二次优化 |
流程总览
① 检测版本 → ② 获取数据 → ③ 提取信息 → ④ 搜索复用组件 → ⑤ 一阶段 Template
↓
⑩ 输出计划报告 ← ⑨ 走查验证 ← ⑧ 无障碍+状态 ← ⑦ 对接组件 ← ⑥ 二阶段 Style
① 检测 Vue 版本(必须最先执行)
> 不要猜。项目用 Vue 2 还是 Vue 3,看项目代码说了算。
检测方法(按优先级):
# 方法 A:看 package.json 的 vue 版本
grep '"vue"' package.json
# "vue": "^2.x.x" → Vue 2
# "vue": "^3.x.x" → Vue 3
# 方法 B:看现有 .vue 文件的 <script> 写法
# 如果大量出现 <script setup> → Vue 3
# 如果都是 export default { ... } → Vue 2(但也可能是 Vue 3 的 Options API 兼容模式)
grep -r '<script setup>' src/ --include="*.vue" | head -5
# 方法 C:看状态管理库
grep '"vuex"' package.json # Vuex → 大概率 Vue 2
grep '"pinia"' package.json # Pinia → 肯定 Vue 3
判定后,全文遵循以下版本对照表。
② 获取设计稿数据
同时调用两个工具,然后立即下载 HTML:
# Step 1: 调用两个 MCP 工具
mcp_Pixso_MCP_design_to_code() → 返回 manifest,提取 pageEntries[0].url
mcp_Pixso_MCP_get_node_dsl() → 返回图层 JSON(参考用)
# Step 2: 立即下载 HTML(URL 是临时的,会随时失效)
# 方式A(推荐): Playwright HTTP GET
mcp_Playwright_playwright_get(url="{manifest.pageEntries[0].url}")
# 方式B: PowerShell
powershell -Command "Invoke-WebRequest -Uri '{url}' -OutFile 'src/views/{page}/design-code.html'"
> ⚠️ design_to_code 会自动生成 Vue 3 代码。我们不用它生成的 .vue 文件,只用它的 HTML 产物来反查结构和 CSS。布局和样式一律从 design-code.html 手写还原。
保存文件布局:
src/views/{page}/
├── design-code.html # 完整 HTML(CSS在前,body在后)
├── design-dsl.json # 精简的 DSL 数据摘要
└── index.vue # Vue 单文件组件
③ 提取关键信息(防止遗漏)
⚠️ 必须执行的两步提取
> 经验教训:不执行这两步是绝大多数遗漏和偏差的根源。
第一步 — 提取所有文本(防止遗漏元素):
grep -n 'class="paragraph-\|class="text-' src/views/{page}/design-code.html
建立清单逐项核对:✅ 已实现 | ❌ 遗漏
第二步 — 提取每个区域的归属关系(防止放错位置):
用 top 值画归属图(HTML 中每个 position:absolute 的容器都有 top 值):
top:0~44 → 状态栏
top:44~97 → 导航栏
top:97+ → 页面内容区(注意:已超出 header)
关键判断:如果一个容器的 top > header高度,那它不在 header 里!
④ 搜索可复用组件(写代码前必做)
> 不要重复造轮子。 设计稿中的 UI 元素,项目里很可能已经有了。
搜索清单
| 设计稿元素 | 搜索关键词 | 预期位置 |
|---|---|---|
| 按钮 | grep -r "Button\|<button" src/components/ |
src/components/ |
| 输入框/选择器 | grep -r "Input\|Select\|SearchBar" src/components/ |
同上 |
| 弹窗/抽屉 | grep -r "Modal\|Dialog\|Drawer\|Popup" src/components/ |
同上 |
| 列表/卡片 | grep -r "Card\|List\|Cell" src/components/ |
同上 |
| Tab/标签 | grep -r "Tab\|Tag\|Badge" src/components/ |
同上 |
| 导航/页头 | grep -r "Header\|Nav\|TabBar" src/components/ |
同上 |
| 空状态/加载 | grep -r "Empty\|Loading\|Skeleton" src/components/ |
同上 |
决策规则
找到匹配的组件 → 复用,只传 props / slot
找不到 → 创建新组件,命名遵循项目现有规范
完成后输出一份简表:
| 设计稿元素 | 复用 | 组件路径 | 备注 |
|-----------|:---:|---------|------|
| 搜索框 | ✅ | src/components/SearchBar.vue | — |
| 主题卡片 | ❌ | — | 新建 src/views/{page}/TopicCard.vue |
⑤ 一阶段:<template> 布局骨架
> 目标:只确认"有哪些元素、在哪里、文字对不对",不关心颜色/间距。
操作
- 打开
design-code.html的<body>…</body>部分,逐段对照编写 Vue template - 按
top值从小到大顺序处理每个frame-content-容器 - 每个容器只关注:
- 文本内容(第③步的 grep 清单)
- 嵌套层次(外层<div>对应外层<div>,Vue 不强制标签替换)
- 是否有条件渲染(v-if/v-show)
- 是否应替换为第④步找到的复用组件 - 完成后对照清单逐项确认,全部 ✅ 才进入下一阶段
模板语法(Vue 2 / Vue 3 通用)
> template 层 99% 一致,下面只标不同的地方。
| 设计稿 HTML | → Vue <template> |
说明 |
|---|---|---|
<div> |
<div> |
Vue 不强制标签替换 |
<p> / <span>(纯文本) |
<span> |
emoji 直接放文本内 |
<img> |
<img :src="...""> |
动态 src |
<a href=""> |
<router-link to="..."> 或 @click + router |
导航链接 |
| 水平滚动容器 | <div class="scroll-x"> + CSS overflow-x: auto |
不用小程序 <scroll-view> |
| 垂直滚动 | <div class="scroll-y"> + CSS overflow-y: auto |
padding 放在 inner 元素上 |
| 条件渲染 | v-if / v-else / v-show |
通用 |
| 列表渲染 | v-for="(item, i) in list" :key="item.id" |
通用 |
| 点击事件 | @click |
通用 |
| 变更事件 | @change / @input |
通用 |
| 动态属性 | :attr="value" |
通用 |
停止条件
- [ ] grep 出的每个文本段落都在
<template>中有对应 - [ ] 每个区域的容器嵌套与 HTML body 结构一致
- [ ] 所有
@click/@change/@input绑在了正确的容器上 - [ ] 条件渲染逻辑正确(
v-if/v-else/v-show) - [ ] 列表渲染正确(
v-for+:key) - [ ] 第④步标记为"可复用"的元素已替换为对应组件
⑥ 二阶段:<style> 像素级还原
> 目标:从设计稿 CSS 反查每个属性,零直觉值。
CSS 单位策略(项目启动时先确认)
| 项目类型 | 推荐方案 | 配置 | 写代码时 |
|---|---|---|---|
| 移动端 H5 | postcss-px-to-viewport |
viewportWidth: 375 或 750 |
直接写 px,自动转 vw |
| PC 端 | 原生 px | 无需 PostCSS 插件 | 直接写 px |
| 响应式(PC+移动) | postcss-pxtorem + amfe-flexible |
rootValue: 37.5 或 75 |
直接写 px,自动转 rem |
PostCSS 配置参考(移动端 vw 方案,推荐):
// .postcssrc.js
module.exports = {
plugins: {
autoprefixer: {},
'postcss-px-to-viewport': {
viewportWidth: 375, // 设计稿宽度(375 或 750)
unitPrecision: 6,
propList: ['*'],
viewportUnit: 'vw',
selectorBlackList: ['ignore'],
minPixelValue: 1,
mediaQuery: true,
exclude: [/node_modules/],
}
}
}
> ⚠️ 无论哪种方案,源代码里统一写 px。插件编译时自动转换。设计稿 px 值直接抄入代码,零手工换算。
操作
-
为
<template>中每个区域找到 CSS — 在design-code.html的<style>中,用容器类名定位:
bash grep -A 15 'frame-2_10827' design-code.html -
逐属性对照翻译成
<style scoped>:
| 设计稿 CSS 属性 | → Vue <style> |
说明 |
|---|---|---|
width/height px |
直接写 px(PostCSS 自动转 vw) | PC 端保持 px |
font-size px |
直接写 px | 同上 |
border-radius px |
直接写 px | 同上 |
border px |
直接写 px | 同上 |
padding / margin px |
直接写 px | 同上 |
box-shadow px |
直接写 px | 注意 blur 和 spread |
background-color / color |
同值 | rgba(R,G,B,A) 可直接用,也可转 #HEX |
position: absolute + top |
判断归属 | 见第③步的归属关系图 |
- 特别注意的视觉属性(最容易错):
-background-color— 必须从 CSS 读,不能凭印象写
-border— 看有没有border-width/border-color,不要漏
-overflow: hidden→<style>中写overflow: hidden
-box-sizing: border-box— 检查设计稿是否有 padding 内含
设计令牌(Design Tokens,强烈推荐)
> 从 get_node_dsl 或 Pixso 样式变量中提取颜色/字体/间距为 CSS 变量,
> 统一管理,一处改全局生效。这是硬要求,不是建议。
/* src/styles/tokens.css — 从设计稿提取 */
:root {
/* 颜色 — 语义化命名,不用视觉命名 */
--color-primary: #4F46E5;
--color-text-primary: #1E293B;
--color-text-secondary: #64748B;
--color-text-helper: #94A3B8;
--color-bg-page: #F8FAFC;
--color-bg-card: #FFFFFF;
--color-border: #E2E8F0;
/* 字号 */
--font-size-title: 18px;
--font-size-body: 15px;
--font-size-desc: 14px;
--font-size-helper: 12px;
/* 间距 */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 12px;
--spacing-lg: 16px;
--spacing-xl: 24px;
/* 圆角 */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
}
<!-- 在组件中使用 — 不硬编码颜色/字号/间距 -->
<style scoped>
.card {
background: var(--color-bg-card);
border-radius: var(--radius-md);
padding: var(--spacing-lg);
}
.card-title {
color: var(--color-text-primary);
font-size: var(--font-size-title);
}
</style>
对照示例
设计稿CSS: → Vue <style scoped>:
.frame-2_10827 { .search-box {
width: 343px; width: 343px;
height: 40px; height: 40px;
border-radius: 10px; border-radius: var(--radius-md);
box-shadow: 0px 1px 3px box-shadow: 0 1px 3px
rgba(0,0,0,0.06); rgba(0,0,0,0.06);
background-color: background: var(--color-bg-card);
rgba(255,255,255,1); }
}
停止条件
- [ ] 每个 CSS 类名的
background-color都从设计稿 CSS 中查过 - [ ] 每个类名的
border属性与设计稿 CSS 一致 - [ ] 没有将普通
overflow: hidden误改为 scroll 容器 - [ ] 所有数值都是从设计稿得来,没有凭直觉手写的
- [ ] 颜色/字号/间距已提取为 CSS 变量(
var(--...))
⑦ 对接组件和交互
<script> 写法(根据第①步检测结果选择)
Vue 2 — Options API
<script>
export default {
name: 'SearchPage',
components: { SearchBar, TopicCard },
data() {
return {
keyword: '',
list: [],
loading: false,
activeTab: 0,
}
},
computed: {
filteredList() {
return this.list.filter(/* ... */)
}
},
created() {
this.fetchData()
},
mounted() {
// DOM 就绪
},
methods: {
goDetail(id) {
this.$router.push(`/detail/${id}`)
},
async fetchData() {
this.loading = true
try {
// await axios.get(...)
} finally {
this.loading = false
}
}
}
}
</script>
Vue 3 — Composition API (<script setup>)
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const keyword = ref('')
const list = ref([])
const loading = ref(false)
const activeTab = ref(0)
const filteredList = computed(() => list.value.filter(/* ... */))
onMounted(() => {
fetchData()
})
function goDetail(id) {
router.push(`/detail/${id}`)
}
async function fetchData() {
loading.value = true
try {
// await axios.get(...)
} finally {
loading.value = false
}
}
</script>
路由导航
| 操作 | Vue 2 | Vue 3 |
|---|---|---|
| 跳转 | this.$router.push('/path') |
router.push('/path') |
| 替换 | this.$router.replace('/path') |
router.replace('/path') |
| 返回 | this.$router.back() |
router.back() |
| 取路由参数 | this.$route.query.xxx |
route.query.xxx |
| 声明式导航 | <router-link to="/path"> |
<router-link to="/path">(一致) |
生命周期对照
| 阶段 | Vue 2 | Vue 3 |
|---|---|---|
| 创建(取路由参数) | created |
setup() 顶层直接执行 |
| DOM 就绪 | mounted |
onMounted(() => {}) |
| 销毁前 | beforeDestroy |
onBeforeUnmount(() => {}) |
| 更新后 | updated |
onUpdated(() => {}) |
| keep-alive 激活 | activated |
onActivated(() => {}) |
| keep-alive 停用 | deactivated |
onDeactivated(() => {}) |
状态管理
| 场景 | Vue 2 | Vue 3 |
|---|---|---|
| 页面内状态 | this.xxx = v |
xxx.value = v |
| 全局状态 | Vuex this.$store.state |
Pinia store.xxx |
| 跨组件通信 | EventBus / $emit |
mitt / provide/inject |
Tab Bar / 底部导航
两种方案,与 Vue 版本无关:
方案 A:自定义组件(推荐,可精确还原设计稿)
<template>
<div class="tab-bar">
<div
v-for="(tab, i) in tabs"
:key="tab.key"
class="tab-item"
:class="{ active: i === activeIndex }"
@click="switchTab(i)"
>
<span class="tab-icon">{{ tab.icon }}</span>
<span class="tab-label">{{ tab.label }}</span>
</div>
</div>
</template>
// Vue 2
data() { return { activeIndex: 0 } }
methods: { switchTab(i) { this.activeIndex = i; this.$router.push(this.tabs[i].route) } }
// Vue 3
const activeIndex = ref(0)
function switchTab(i) { activeIndex.value = i; router.push(tabs[i].route) }
方案 B:使用 UI 框架自带 TabBar
# Vue 2
npm i vant@latest-v2
# Vue 3
npm i vant
交互事件
根据 get_node_dsl 中的 interactions 字段绑定:
- ON_CLICK + NAVIGATE → @click + router 跳转
- ON_CLICK + 状态变化 → @click + 变量赋值
- 表单输入 → v-model(Vue 2/3 通用语法)
深度选择器(穿透 scoped 修改子组件样式)
| 场景 | Vue 2 | Vue 3 |
|---|---|---|
| 修改子组件内部样式 | ::v-deep .child-class |
:deep(.child-class) |
| 备选写法 | /deep/ 或 >>> |
仅 :deep() |
scroll-view 替代方案(Vue 2/3 通用)
<template>
<!-- ✅ 水平滚动 -->
<div class="scroll-x">
<div class="scroll-x-inner">
<!-- 滚动内容 -->
</div>
</div>
<!-- ✅ 垂直滚动 -->
<div class="scroll-y">
<div class="scroll-y-inner">
<!-- 滚动内容 -->
</div>
</div>
</template>
<style scoped>
.scroll-x {
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE */
}
.scroll-x::-webkit-scrollbar { display: none; }
.scroll-x-inner { display: inline-flex; }
.scroll-y { overflow-y: auto; height: 100%; }
/* ⚠️ padding 放 inner 上,不放 scroll 容器上 */
.scroll-y-inner { padding: var(--spacing-lg); }
</style>
⑧ 无障碍 + 状态覆盖(不可跳过)
语义化 HTML
| 设计稿元素 | 优先使用 | 备选 |
|---|---|---|
| 按钮 | <button> |
<div role="button" tabindex="0"> |
| 导航区域 | <nav> |
<div role="navigation"> |
| 列表 | <ul> / <ol> / <li> |
<div role="list"> |
| 图片 | <img :alt="desc"> |
<div role="img" :aria-label="desc"> |
| 表单输入 | <label> + <input> |
aria-label |
| 主内容区 | <main> |
<div role="main"> |
必须覆盖的状态
> 设计稿通常只画了 happy path。以下状态每个交互元素都要处理:
| 状态 | 要求 | 示例 |
|---|---|---|
:hover |
所有可点击元素 | cursor: pointer + 背景/边框变化 |
:active |
按钮/链接按下态 | 颜色加深或轻微缩放 |
:focus |
所有可聚焦元素 | 可见的 focus 轮廓,不能 outline: none 裸奔 |
disabled |
不可操作状态 | opacity: 0.5 + cursor: not-allowed + pointer-events: none |
loading |
异步操作中 | 骨架屏或 spinner,按钮 disabled + 文案"加载中…" |
empty |
列表/搜索结果为空 | 空状态插图 + "暂无数据" 文案 |
error |
请求失败 | 错误提示 + 重试按钮 |
键盘可操作性
- [ ] 弹窗打开时焦点移入,关闭后焦点回到触发元素
- [ ] Tab 键可以遍历所有可交互元素(按钮、链接、输入框)
- [ ] Escape 键关闭弹窗/下拉菜单
- [ ] Enter 键激活当前聚焦的按钮/链接
⑨ 走查验证(‼️ 必须执行,不可跳过)
走查流程(按页面从上到下)
第 1 遍:逐区域核对 <template>
打开 design-code.html 的 <body> 部分和编写的 .vue 的 <template>,并排对照,按 top 值从小到大逐个区域检查:
核对清单(每个区域都要过):
□ 该区域在 <template> 中是否存在
□ 文本内容是否与设计稿一致(一个字都不能差)
□ 容器嵌套层次是否正确
□ 条件渲染逻辑是否与设计稿状态一致(v-if / v-show)
□ 列表中每一项的数据是否对应(v-for + :key)
□ 有交互的按钮/链接是否绑定了正确的事件(@click / @change)
□ 是否使用了语义化 HTML 标签或 role 属性
第 2 遍:逐区域核对 <style>
打开 design-code.html 的 <style> 部分和编写的 .vue 的 <style scoped>,并排对照每个区域:
核对清单(每个区域样式都要过):
□ background-color 是否从设计稿 CSS 精确提取(不是凭印象写的)
□ border 有无/粗细/颜色是否正确
□ border-radius 是否精确
□ box-shadow 是否精确
□ font-size 是否精确
□ color 是否精确
□ padding/margin 是否精确
□ overflow 处理是否正确(scroll-x vs hidden vs visible)
□ 元素在哪个背景区域内(header 内 vs 浅色背景)
□ 是否使用设计令牌变量(var(--xxx)),而非硬编码
第 3 遍:整体状态 + 视效走查
用 mcp_Pixso_MCP_get_screenshot() 获取设计稿截图,与浏览器渲染结果对照:
核对清单(全局):
□ 页面整体颜色氛围是否与设计稿一致
□ 各区域间距留白是否合理
□ 字体大小层级是否正确(标题>描述>辅助文字)
□ 圆角体系是否统一
□ hover / active / focus 状态是否正确
□ disabled / loading / empty / error 状态是否已处理
□ 列表项间距是否均匀
□ Tab 键能否遍历所有可交互元素
□ Escape 能否关闭弹窗/菜单
□ 底部 Tab Bar / 导航是否正确展示
□ 页面是否有右侧溢出或内容被截断
□ 响应式表现(如有要求)
走查停止条件
- [ ] 第 1 遍中每个区域的
<template>都已核对,标记为 ✅ - [ ] 第 2 遍中每个区域的
<style>都已核对,标记为 ✅ - [ ] 第 3 遍中所有状态+视效项已核对,标记为 ✅
- [ ] 与截图对照无明显视觉差异
- [ ] 每项核对结果记录为清单(Markdown 表格),方便追溯
走查记录模板
## 走查记录:{页面名}
### 第 1 遍:Template 核对
| # | 区域 | top | 文本 | 容器 | 事件 | 语义化 | 状态 |
|---|------|:---:|:----:|:----:|:----:|:------:|:----:|
| 1 | 状态栏 | 0-44 | ✅ | ✅ | - | ✅ | ✅ |
| 2 | 导航栏 | 44-97 | ✅ | ✅ | ✅ | ✅ | ✅ |
| ... | ... | ... | ... | ... | ... | ... | ... |
### 第 2 遍:Style 核对
| # | 区域 | bg-color | border | radius | shadow | font | token | 状态 |
|---|------|:--------:|:------:|:------:|:------:|:----:|:-----:|:----:|
| 1 | 搜索框 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 2 | 筛选标签 | ✅ | ✅ | ✅ | - | ✅ | ✅ | ✅ |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
### 第 3 遍:状态 + 视效核对
| # | 检查项 | 结果 |
|---|--------|:----:|
| 1 | hover 状态 | ✅ |
| 2 | active 状态 | ✅ |
| 3 | focus 可见 | ✅ |
| 4 | disabled 状态 | ✅ |
| 5 | loading 状态 | ✅ |
| 6 | empty 状态 | ✅ |
| 7 | error 状态 | ✅ |
| 8 | Tab 键遍历 | ✅ |
| 9 | Escape 关闭 | ✅ |
| 10 | 截图对照 | ✅ |
⑩ 输出设计实现计划报告
> 走查通过后,生成一份简短的实现报告,方便 CR 和团队追溯。
保存路径:reports/design-plan-YYYY-MM-DD-HHmmss.md
# 设计实现计划
> 生成时间:YYYY-MM-DD HH:mm
> 设计工具:Pixso
> 目标框架:Vue 2 / Vue 3(根据第①步检测结果填写)
## 实现摘要
- 设计稿来源:Pixso, page={页面名}
- 目标页面/组件:src/views/{page}/index.vue
## 复用的组件(第④步结果)
| 设计稿元素 | 组件路径 | 备注 |
|-----------|---------|------|
| ... | ... | ... |
## 新建的组件
| 组件名 | 路径 | 说明 |
|-------|------|------|
| ... | ... | ... |
## 组件拆分方案
- ...
## 状态/数据流
- 页面状态:keyword, list, loading, activeTab
- 数据来源:...
- 用户交互:搜索 → 更新 keyword → 刷新 list
## Design Token 映射
- 颜色:全部提取到 src/styles/tokens.css
- 字号:var(--font-size-title) 等
- 间距:var(--spacing-lg) 等
## 与设计稿的偏差
- 无(或说明哪些地方因工程约束做了调整)
## 缺失资源/未决项
- ...
## 变更文件列表
- src/views/{page}/index.vue
- src/styles/tokens.css(如新增 token)
- ...
附录 A:Vue 2 / Vue 3 语法速查总表
| 功能 | Vue 2(Options API) | Vue 3(<script setup>) |
|---|---|---|
| 组件定义 | export default { ... } |
<script setup> 顶层代码 |
| 响应式数据 | data() { return { x: 1 } } |
const x = ref(1) |
| 计算属性 | computed: { d() { ... } } |
const d = computed(() => ...) |
| 侦听器 | watch: { x(v) { ... } } |
watch(x, (v) => { ... }) |
| 方法 | methods: { f() { ... } } |
function f() { ... } |
| Props | props: { x: Number } |
const props = defineProps({ x: Number }) |
| Emits | this.$emit('e', v) |
const emit = defineEmits(['e']); emit('e', v) |
| 路由跳转 | this.$router.push('/p') |
useRouter().push('/p') |
| 路由参数 | this.$route.query.x |
useRoute().query.x |
| 模板 ref | this.$refs.xxx |
const xxx = ref(null); // <div ref="xxx"> |
| 插槽 | <slot> |
<slot>(一致) |
| 动态组件 | <component :is="cmp"> |
<component :is="cmp">(一致) |
| v-model | v-model="x" |
v-model="x"(一致) |
| 深度选择器 | ::v-deep .cls |
:deep(.cls) |
| 全局挂载 | Vue.prototype.$xxx |
app.config.globalProperties.$xxx |
| 过滤器 | {{ x | filter }} |
❌ 已移除,用 computed |
| 事件总线 | new Vue() 作 EventBus |
用 mitt 或 provide/inject |
| Fragment | ❌ 单根节点 | ✅ 支持多根节点 |
附录 B:小程序迁移速查(如果有双端需求)
B.1 文件结构
小程序 (MINA) Vue SFC
├── page.wxml ┌── index.vue
├── page.wxss │ ├── <template>
├── page.js │ ├── <script>
├── page.json │ └── <style scoped>
└── (配置在 router 中)
B.2 语法对照
| 小程序 | Vue |
|---|---|
<view> |
<div> |
<text> |
<span> |
<image> |
<img> |
wx:if |
v-if |
wx:for |
v-for |
bindtap |
@click |
setData |
Vue 2: this.x = v / Vue 3: x.value = v |
B.3 单位换算
| 场景 | 小程序 | Vue 移动端 | Vue PC 端 |
|---|---|---|---|
| 设计稿 375 | px × 2 = rpx |
px(PostCSS → vw) | px |
| 设计稿 750 | px × 1 = rpx |
px(PostCSS → vw) | px |
| 需手工换算? | ✅ | ❌ PostCSS 自动 | ❌ 保持 px |
附录 C:实战坑点
- grep + top 归属图:下载 HTML 后第一件事就做,别跳过
- scroll 容器 padding:只用 inner 做 padding,scroll 容器本身不设 padding
- emoji 直接用字符:
<span>📖</span>即可,不需要图片 - 渐变从 SVG data URI 提取:看 HTML 中
<stop stop-color='...'> - 装饰圆:
position: absolute + border-radius: 50% + rgba(255,255,255,0.06) - MINGW64 下 curl 常失败:用 Playwright HTTP GET 或 PowerShell
- 设计稿 HTML 文件名不要变:一律命名为
design-code.html v-for必须配:key:Vue 硬性要求,不像小程序可选- scoped 穿透:Vue 2 用
::v-deep,Vue 3 用:deep() v-model是语法糖::value+@input的简写,复杂场景可拆开手写- 路由参数:Vue 2 用
this.$route.query,Vue 3 用useRoute().query - 图片路径:Vue 2 用
require(),Vue 3 + Vite 用import或直接/src/assets/ design_to_code生成的 .vue 不要直接用:它生成 Vue 3 代码且布局可能依赖绝对定位,一律从 HTML 反查手写- Token 不要悄悄硬编码:每个颜色/字号/间距都应该来自
var(--xxx),新增的 token 记入报告
附录 D:PostCSS 完整配置示例
D.1 移动端 vw 方案(推荐)
npm i postcss-px-to-viewport -D
// .postcssrc.js — 项目根目录
module.exports = {
plugins: {
autoprefixer: {},
'postcss-px-to-viewport': {
viewportWidth: 375, // 设计稿宽度
unitToConvert: 'px',
unitPrecision: 5,
propList: ['*'],
viewportUnit: 'vw',
fontViewportUnit: 'vw',
selectorBlackList: ['.ignore', '.hairlines'],
minPixelValue: 1,
mediaQuery: false,
replace: true,
exclude: [/node_modules/],
landscape: false,
landscapeUnit: 'vw',
landscapeWidth: 667,
}
}
}
D.2 PC 端 — 无需 PostCSS
直接在 <style> 中写 px,无需任何插件。
D.3 响应式 rem 方案(PC+移动)
npm i amfe-flexible postcss-pxtorem -S
// main.js
import 'amfe-flexible'
// .postcssrc.js
module.exports = {
plugins: {
autoprefixer: {},
'postcss-pxtorem': {
rootValue: 37.5, // 375 设计稿 → 37.5; 750 设计稿 → 75
propList: ['*'],
selectorBlackList: ['.norem'],
minPixelValue: 2,
}
}
}
相关文档
- 原始小程序版:pixso-to-miniprogram/SKILL.md
- Pixso MCP 工具参考:Pixso MCP Tools
- Pixso MCP 实践指南:Pixso MCP + Cursor 落地指南
- Vue 2 风格指南:Vue 2 Style Guide
- Vue 3 文档:Vue 3 Docs
- vue-router v3:Vue Router v3
- vue-router v4:Vue Router v4
- PostCSS px-to-viewport:npm
- 设计令牌命名指南:Smashing Magazine — Naming Design Tokens