初始化
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
---
|
||||
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()` |
|
||||
Reference in New Issue
Block a user