197 lines
6.1 KiB
Markdown
197 lines
6.1 KiB
Markdown
---
|
||
name: figma-to-landpage
|
||
description: 将 Figma 设计稿转换为 Vue 2 落地页组件。当用户提供 Figma 链接并要求生成落地页、实现设计稿、创建新模板时触发。适用于移动端和 PC 端落地页开发。
|
||
---
|
||
|
||
# Figma 转落地页
|
||
|
||
将 Figma 设计稿翻译为本项目的 Vue 2 落地页组件,保持视觉还原度。
|
||
|
||
## 工作流程
|
||
|
||
### 第 1 步: 获取设计稿信息
|
||
|
||
从 Figma URL 中提取 `fileKey` 和 `nodeId`:
|
||
- URL 格式: `https://figma.com/design/:fileKey/:fileName?node-id=1-2`
|
||
- 将 `node-id` 中的 `-` 转为 `:`
|
||
|
||
依次调用:
|
||
1. `get_design_context(fileKey, nodeId)` — 获取布局、颜色、字体等结构化数据
|
||
2. `get_screenshot(fileKey, nodeId)` — 获取视觉截图作为还原参考
|
||
|
||
如果设计太复杂导致返回截断:
|
||
1. 先调 `get_metadata(fileKey, nodeId)` 获取节点树
|
||
2. 对各子节点分别调 `get_design_context`
|
||
|
||
### 第 2 步: 确定模板编号
|
||
|
||
查看 `src/views/land/components/` 下已有的模板文件:
|
||
- 移动端: `mobile1.vue`, `mobile2.vue`, ...
|
||
- PC 端: `pc1.vue`, `pc2.vue`, ...
|
||
|
||
新模板使用下一个序号。
|
||
|
||
### 第 3 步: 下载资源
|
||
|
||
将设计稿中的图片、图标导出并放入:
|
||
```
|
||
src/assets/images/{templateName}/
|
||
```
|
||
|
||
例如 `mobile3` 模板的资源放在 `src/assets/images/mobile3/`。
|
||
|
||
Figma MCP 返回的 localhost 资源链接可直接下载使用。
|
||
|
||
### 第 4 步: 创建组件文件
|
||
|
||
在 `src/views/land/components/` 下创建新的 `.vue` 文件。
|
||
|
||
**必须遵循的组件结构:**
|
||
|
||
```vue
|
||
<template>
|
||
<div class="mobilePage" @click="pageClick">
|
||
<!-- 顶部固定区域 -->
|
||
<div class="headerBox" @click.stop="getApkInfo(os.isPhone && switchData.shopIosDownUrl ? 'shopIos' : true, $event)">
|
||
<div class="header-bg"></div>
|
||
</div>
|
||
|
||
<!-- 主体内容区 -->
|
||
<div class="main">
|
||
<!-- Figma 设计内容转换到这里 -->
|
||
</div>
|
||
|
||
<!-- 社交浮窗 -->
|
||
<div class="floatingBox" v-if="configs && configs.length">
|
||
<div class="telegram" v-if="cTg" @click="jumpUrl(cTg)"></div>
|
||
<div class="potato" v-if="cPotato" @click="jumpUrl(cPotato)"></div>
|
||
<div class="business" v-if="cBusiness" @click="jumpUrl(cBusiness)"></div>
|
||
<div class="channel" v-if="cChannel" @click="jumpUrl(cChannel)"></div>
|
||
</div>
|
||
|
||
<!-- 下载按钮区 -->
|
||
<div class="bottomBox" @click.stop="getApkInfo(true, $event)"></div>
|
||
|
||
<!-- 安卓/iOS 提示弹窗 -->
|
||
<van-overlay :show="androidPromptShow" z-index="999" class="androidOverlay">
|
||
<!-- ... -->
|
||
</van-overlay>
|
||
<van-overlay :show="iosPromptShow" z-index="999" class="iosOverlay">
|
||
<!-- ... -->
|
||
</van-overlay>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { landingPageClick } from "@/utils/eventTracker";
|
||
export default {
|
||
props: ["os", "configs", "switchData"],
|
||
data() {
|
||
return {
|
||
androidPromptShow: false,
|
||
iosPromptShow: false,
|
||
showTip: false,
|
||
currUrl: window.location.href,
|
||
cTg: "",
|
||
cPotato: "",
|
||
cBusiness: "",
|
||
cChannel: "",
|
||
showIosTip: true,
|
||
};
|
||
},
|
||
watch: {
|
||
configs(configs) {
|
||
configs.forEach((item) => {
|
||
if (item.configType === "tg_group") this.cTg = item.link;
|
||
else if (item.configType === "potato_group") this.cPotato = item.link;
|
||
else if (item.configType === "sw_cooperate") this.cBusiness = item.link;
|
||
else if (item.configType === "qd_cooperate") this.cChannel = item.link;
|
||
});
|
||
},
|
||
},
|
||
methods: {
|
||
pageClick(event) {
|
||
landingPageClick(this, {
|
||
tab_key: "main",
|
||
tab_name: "主页",
|
||
click_coordinates_x: event.clientX,
|
||
click_coordinates_y: event.clientY,
|
||
});
|
||
},
|
||
doCopy() {
|
||
this.$copyText(this.currUrl).then(() => { this.showTip = false; });
|
||
},
|
||
async showInstallTutorial(type) {
|
||
if (type === "ios") this.iosPromptShow = true;
|
||
else this.androidPromptShow = true;
|
||
},
|
||
async getApkInfo(flag, event) {
|
||
let type = this.os.isPhone ? "ios" : "android";
|
||
if (flag === "shopIos") type = "shopIos";
|
||
if (flag === "androidSpare") type = "androidSpare";
|
||
if (flag && flag !== "shopIos") await this.showInstallTutorial(type);
|
||
this.$emit("recordClick", type);
|
||
setTimeout(() => { this.$emit("getApkInfo", type); }, 500);
|
||
landingPageClick(this, {
|
||
tab_name: "下载按钮",
|
||
tab_key: "download",
|
||
click_coordinates_x: event.clientX,
|
||
click_coordinates_y: event.clientY,
|
||
});
|
||
},
|
||
jumpUrl(url) { window.open(url); },
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
/* 样式使用 SCSS + scoped,px 值基于 375 设计稿 */
|
||
</style>
|
||
```
|
||
|
||
### 第 5 步: 样式转换要点
|
||
|
||
将 Figma 设计数据转为 SCSS 时注意:
|
||
|
||
1. **尺寸**: 基于 375px 设计稿直接写 px,px2rem 自动转换
|
||
2. **颜色**: 使用 Figma 给出的精确色值
|
||
3. **图片**: 大面积背景用 webp 格式,图标用 png
|
||
4. **字体**: Figma 字体映射到系统字体栈
|
||
5. **布局**: 优先 Flex 布局,配合 `position: fixed` 实现吸顶/吸底
|
||
6. **Vant 组件覆写**: 使用 `::v-deep` 覆盖 Vant 默认样式
|
||
7. **PC 端样式**: 选择器加 `pcm` 前缀以避免 px2rem 转换
|
||
|
||
### 第 6 步: 注册组件
|
||
|
||
在 `src/views/land/index.vue` 中导入并注册新模板:
|
||
|
||
```js
|
||
import mobileN from "./components/mobileN";
|
||
// 在 components 中注册
|
||
```
|
||
|
||
并在模板切换逻辑中添加对应条件。
|
||
|
||
### 第 7 步: 验证
|
||
|
||
- [ ] 视觉与 Figma 截图对比一致
|
||
- [ ] 移动端/PC 端分别测试
|
||
- [ ] 下载按钮、社交链接、弹窗功能正常
|
||
- [ ] 事件追踪正常上报
|
||
- [ ] 图片资源加载无误
|
||
|
||
## Figma 到 Vue 映射参考
|
||
|
||
| Figma 属性 | Vue/SCSS 对应 |
|
||
|------------|--------------|
|
||
| Auto Layout (横向) | `display: flex` |
|
||
| Auto Layout (纵向) | `display: flex; flex-direction: column` |
|
||
| Fill 颜色 | `background-color: #xxx` |
|
||
| 图片 Fill | `background: url("...") no-repeat; background-size: 100% 100%` |
|
||
| 固定位置 | `position: fixed` |
|
||
| 圆角 | `border-radius: Npx` |
|
||
| 文字样式 | `font-size`, `font-weight`, `line-height`, `color` |
|
||
| 阴影 | `box-shadow` |
|
||
| 间距 (Gap) | `gap: Npx` 或 `margin`/`padding` |
|
||
| 透明度 | `opacity` 或 `rgba()` |
|