custom-components-skill
指导在活动 H5 中生成与维护「活动积木组件」。根据 page-center 下发的 uiMeta 渲染可配置玩法块(礼盒、盲盒、榜单规则、榜单礼物、单榜 Top3 等)。代码规范与 ui2 组件一致,遵循 ui2-upgrade-guide。适用场景:新建积木组件、从已有积木复制改版、按沉淀规范补齐 types/config/composables。
How do I install this agent skill?
npx skills add https://mercury-api.wepieoa.com --skill fe-custom-components-skillIs this agent skill safe to install?
No partner audit is available yet. Read the source before installing.
What does this agent skill do?
活动积木组件生成规范
代码规范对齐:积木组件的代码规范与 ui2 组件一致,生成或修改积木时须同时遵循 ui2-upgrade-guide 中的核心原则、Vue/TS 语法、样式与 pageCenter 数据结构约定。下文在 ui2 规范基础上补充积木特有的目录、props、use-meta 与页面使用方式。
何时使用本 Skill
- 用户要求「生成/新增一个积木组件」「做一个类似礼盒/盲盒/榜单规则的玩法块」
- 需要在新活动中复用或改版
apps/short/20260401-i/recharge/components下已有积木(礼盒、盲盒、榜单规则、榜单礼物、single-top3 等) - 需要统一积木的目录结构、props、样式与多区服文案接入方式
积木组件标准结构
活动内积木放在 活动目录 components/ 下,每个积木一个子目录,命名 kebab-case(如 gift-box、rank-points)。与 ui2 组件结构对齐:composables、types、子组件、utils 分目录放置。
components/
└── <积木名>/ # 如 gift-box、blind-box、rank-points、rank-rewards
├── components/ # 见下文「子组件目录(强制约定)」
│ └── *.vue # 从主文件拆出的布局块、卡片等,与主文件同级会违反规范
├── composables/
│ └── use-meta.ts # 从 uiMeta + context 派生展示与样式
├── config/
│ ├── index.ts # 可选,工具函数(如 crownMap、isA 等常量)
│ ├── default-styles.ts # 必需,主题样式配置(themeConfig 对象)
│ ├── default-ui-meta.ts # 必需,组件默认元数据(不含活动资源)
│ └── ui-meta.ts # 必需,页面配置示例(供 page-center 使用)
├── types/
│ └── index.ts # UiMeta、Props、Emits 等 TypeScript 类型(对外类型统一放此)
├── styles/
│ └── index.scss # 必需,固定样式文件(布局、尺寸等)
└── custom-<积木名>.vue # 主入口组件,对外暴露
- 业务特有逻辑:可增加 composable(如礼盒的
use-guarantee.ts)或子组件目录。
子组件目录 components/(积木拆分强制约定)
为控制单文件行数(业务约定 ≤300 行 / 模板可读性),从主入口 custom-<积木名>.vue 拆出的 Vue 子块(多布局、重复 UI 卡片、颁奖台与列表分区等)必须放在本积木目录下的 components/ 内,禁止与 custom-*.vue 平级散落。
| 要求 | 说明 |
|---|---|
| 路径 | apps/.../components/<积木名>/components/<子组件>.vue |
| 命名 | 文件名 kebab-case,建议带积木前缀避免与页面其他组件混淆,如 lucky-gift-layout-arrow.vue、rank-single-top3-podium.vue |
| 引用 | 主入口使用相对路径 ./components/xxx.vue;子组件内引用积木根下 types / config / composables 用 ../types、../config 等 |
| 样式 | 子组件 一般不单独建全局样式文件,类名仍挂在积木根 BEM(如 .lucky-gift-wrapper__*),由主入口或积木 styles/index.scss 统一维护 |
| 参考实现 | custom-lucky-gift/components/(lucky-gift-gift-card、lucky-gift-layout-* 等)、custom-rank-single-top3/components/(rank-single-top3-podium、rank-single-top3-rest-list) |
- 主组件命名:入口 Vue 文件必须为
custom-<积木名>.vue,与config/default-ui-meta.ts中的type: 'custom-xxx'一致。 - config/ 三文件规范:必须包含
default-styles.ts(主题样式)、default-ui-meta.ts(组件默认配置)、ui-meta.ts(页面配置示例),详见下文「config/ 配置文件规范」。
文案与角标(强制)
- 对用户可见的文案(标题、按钮、角标文字、连接符占位、玩法说明等)须来自 page-center 下发的
uiMeta(如props.text.*、礼物上的tag/attachedTag等),并在default-ui-meta.ts(及ui-meta.ts示例)里保留可编辑默认值,禁止只在源码里写死一处、搭建侧无法覆盖。 custom-*.vue模板中禁止硬编码具体语言文案(例如写死New、附赠、主页特效、|/+/OR等展示串);应绑定配置字段或textConfig/use-meta派生值。注释、无语义的结构符号若与配置重复,以配置为准。- 礼物角标(示例:小礼物爆大礼物):小礼物 / 大礼物主卡角标文案用
tag(空字符串表示不展示),可选配图images.newTag;主页特效预览卡角标文案用attachedTag,可选配图images.attachedTag。旧字段showNew、tagType仅作兼容,新配置以tag/attachedTag为准;仅有tagType而无文案时可用props.giftCornerTagFallback由 page-center 配置兜底文案。
命名规范(与 ui2 一致)
| 类别 | 规范 | 示例 |
|---|---|---|
| 组件名 | PascalCase | CustomGiftBox |
| 文件名 | kebab-case | custom-gift-box.vue |
| Props | camelCase | uiMeta、context |
| Events | kebab-case | @update-value |
| 样式 | scoped + 组件前缀/ BEM | .rank-rule-wrapper__title-row |
config/ 配置文件规范
积木组件的 config/ 目录必须包含以下三个文件(三文件规范):
1. config/default-styles.ts — 主题样式配置
职责:定义多套主题色系配置,导出 themeConfig 对象。
规范:
- 导出类型接口(如
ThemeStyleConfig),定义主题对象的结构 - 导出
themeConfig: Record<string, ThemeStyleConfig>,key 为主题名 - 每个主题包含完整的样式变量(颜色、背景、边框等)
- 纯样式配置,不包含业务逻辑或图片 URL
主题命名规范(强制):
-
按颜色命名(推荐主题名):
blue- 蓝色主题(深蓝/紫蓝色调)red- 红色主题(红色/粉色调)green- 绿色主题(绿色调)purple- 紫色主题(紫色调)gold- 金色主题(金黄色调)brown- 棕色主题(棕色调)
-
禁止按区服命名:
- ❌ 不使用
india、brazil、usa、indonesia等区服名 - 原因:不同区服可共用同一颜色主题,减少重复配置
- ❌ 不使用
-
配置项用途说明(以榜单组件为例):
listContainerBg- 整体弹框背景(容器背景),通常为transparentlistItemBg- Top4-n 榜单项背景,多为渐变色:linear-gradient(...)mineBg- 我的排名背景(非固定时),半透明色:rgba(...)fixedBg- 当前用户吸底背景(固定时),多为渐变色:linear-gradient(...)textColor*- 文字颜色,固定色,各主题可复用borderColor*- 边框颜色,固定色,各主题可复用
示例:
export interface ThemeStyleConfig {
/** 整体弹框背景 */
listContainerBg: string
/** Top4-n 榜单项背景(多为渐变色) */
listItemBg: string
/** 我的排名背景 */
mineBg: string
/** 吸底背景(多为渐变色) */
fixedBg: string
/** 文字颜色(固定色,可各主题复用) */
textColorFirst: string
textColorSecond: string
textColorThird: string
textColorOther: string
textColorScore: string
textColorStar: string
/** 边框颜色(固定色,可各主题复用) */
borderColorTop1: string
borderColorTop2: string
borderColorTop3: string
borderColorOther: string
borderColorMine: string
}
export const themeConfig: Record<string, ThemeStyleConfig> = {
/** 蓝色主题(深蓝紫色调) */
blue: {
listContainerBg: 'transparent',
// Top4-n 榜单项背景使用渐变色
listItemBg: 'linear-gradient(90deg, rgba(78, 91, 186, 0.6) 0%, rgba(98, 111, 206, 0.6) 100%)',
mineBg: 'rgba(98, 111, 206, 0.8)',
// 吸底背景使用渐变色
fixedBg: 'linear-gradient(90deg, rgba(78, 91, 186, 0.8) 0%, rgba(98, 111, 206, 0.8) 100%)',
// 文字与边框颜色(固定色)
textColorFirst: '#8B4513',
textColorSecond: '#1E5A8E',
textColorThird: '#8B3A1E',
textColorOther: '#FFFFFF',
textColorScore: '#FFFFFF',
textColorStar: '#FFFFFF',
borderColorTop1: 'rgba(255, 215, 0, 0.6)',
borderColorTop2: 'rgba(192, 192, 192, 0.6)',
borderColorTop3: 'rgba(205, 127, 50, 0.6)',
borderColorOther: 'rgba(255, 255, 255, 0.2)',
borderColorMine: 'rgba(255, 255, 255, 0.4)'
},
/** 红色主题 */
red: {
listContainerBg: 'transparent',
listItemBg: 'linear-gradient(90deg, rgba(180, 60, 60, 0.6) 0%, rgba(200, 80, 80, 0.6) 100%)',
mineBg: 'rgba(200, 80, 80, 0.8)',
fixedBg: 'linear-gradient(90deg, rgba(180, 60, 60, 0.8) 0%, rgba(200, 80, 80, 0.8) 100%)',
// 文字与边框颜色(固定色,与 blue 相同)
textColorFirst: '#8B4513',
// ... 其他颜色
},
/** 绿色主题 */
green: { /* ... */ },
/** 紫色主题 */
purple: { /* ... */ }
}
要点:
- 主题命名规范(强制):
- ✅ 按颜色命名:
blue、red、green、purple、gold、brown等 - ❌ 禁止按区服命名:不使用
india、brazil、usa等区服名 - 原因:不同区服可共用同一颜色主题,减少重复配置
- ✅ 按颜色命名:
- 所有主题的 key 必须保持一致(通过类型接口约束)
- 使用标准 CSS 属性名 + rem 单位,禁止简写(见后文样式规范)
- 参考实现:
components/custom-rank-single-top3/config/default-styles.ts、components/custom-lucky-gift/config/default-styles.ts
2. config/default-ui-meta.ts — 组件默认元数据
职责:定义组件级默认配置,不包含具体活动资源(图片 URL、act_id 等)。
规范:
- 导出
defaultUiMeta对象,类型为该组件的 UiMeta 类型 - 从
./default-styles引入 themeConfig,theme 字段引用默认主题 - 图片资源使用空字符串占位
- 所有必填字段都有默认值
- 组件内部使用此配置作为 fallback(props 默认值)
示例:
import type { CustomRankSingleTop3UiMeta } from '../types'
import { themeConfig } from './default-styles'
export const defaultUiMeta: CustomRankSingleTop3UiMeta = {
key: 'custom-rank-single-top3',
type: 'custom-rank-single-top3',
props: {
act_id: 0,
rankKey: 'total',
images: {
empty: '',
crown1: '',
// ... 空字符串占位
},
theme: {
themeType: 'blue',
...themeConfig.blue // 引用蓝色主题作为默认
},
rankConfig: {
rankLength: 10,
// ... 默认配置
}
}
}
要点:
- 不包含活动特定资源(图片 URL 为空串、act_id 为 0)
- theme 引用 default-styles.ts 中的主题配置
- 在组件中作为 props 默认值使用:
uiMeta: () => defaultUiMeta - 参考实现:
components/custom-rank-single-top3/config/default-ui-meta.ts
3. config/ui-meta.ts — 页面配置示例
职责:供 page-center 使用的实际配置示例,包含完整的活动资源。
规范:
- 导出
uiMeta对象(可导出多个示例配置) - 包含完整的活动资源(图片 URL、act_id 等)
- theme 引用特定主题(如 'india')
- 可导出多个配置示例(如 luckyGiftArrowExample, luckyGiftPlusExample)
- 必须包含使用说明注释
示例:
import type { CustomRankSingleTop3UiMeta } from '../types'
import { themeConfig } from './default-styles'
/**
* 印巴活动 20260401-i 榜单配置示例(使用 blue 蓝色主题)
*/
export const uiMeta: CustomRankSingleTop3UiMeta = {
key: 'custom-rank-single-top3',
type: 'custom-rank-single-top3',
props: {
act_id: 12345,
rankKey: 'total',
images: {
empty: 'https://res.weplayapp.com/.../default-avatar.png',
crown1: 'https://res.weplayapp.com/.../crown-1.png',
// ... 实际图片 URL
},
theme: {
themeType: 'blue',
...themeConfig.blue // 引用蓝色主题
},
rankConfig: {
rankLength: 10,
// ... 页面配置
}
}
}
/**
* 使用说明:
*
* 在 page-center 配置中使用:
* ```ts
* compUiMeta: {
* rankSingleTop3: uiMeta
* }
* ```
*
* 或在页面中直接引入:
* ```ts
* import { uiMeta as rankMeta } from './components/custom-rank-single-top3/config/ui-meta'
* ```
*/
要点:
- 完整的活动配置,可直接用于 page-center 或页面引入
- theme 引用具体主题(按颜色:
blue、red、green、purple等) - 必须包含使用说明注释(在 page-center 中如何配置、在页面中如何引入)
- 可导出多个配置示例(针对不同场景,如 arrow/plus/or 布局)
- 参考实现:
components/custom-rank-single-top3/config/ui-meta.ts、components/custom-lucky-gift/config/ui-meta.ts
4. config/index.ts — 工具函数(可选)
职责:导出组件内部使用的工具函数和常量配置。
规范:
- 可选文件,仅在有工具函数或常量时创建
- 导出工具函数(如 formatItem、getDefaultValue 等)
- 导出常量配置(如 crownMap、isA 等)
示例:
export const crownMap = {
1: 'default-crown-1.png',
2: 'default-crown-2.png',
3: 'default-crown-3.png'
}
export const isA = region === 'A'
export const formatItem = (item: any) => {
// ... 格式化逻辑
}
要点:
- 仅在需要时创建,不强制要求
- 不包含样式配置(样式配置在 default-styles.ts)
- 不包含 UiMeta 配置(UiMeta 在 default-ui-meta.ts 和 ui-meta.ts)
配置文件使用规范
在组件中引入
<script setup lang="ts">
// 从 config/ 引入默认配置
import { defaultUiMeta } from './config/default-ui-meta'
import { crownMap, isA } from './config' // 工具函数和常量
import type { CustomRankSingleTop3UiMeta } from './types'
// 使用 defaultUiMeta 作为 props 默认值
const props = withDefaults(defineProps<Props>(), {
uiMeta: () => defaultUiMeta,
context: () => ({})
})
// 合并主题配置
const themeConfig = computed(() => ({
...defaultUiMeta.props.theme,
...props.uiMeta?.props?.theme
}))
</script>
在页面中引入
<script setup lang="ts">
// 从 config/ 引入页面配置
import CustomRankSingleTop3 from './components/custom-rank-single-top3/custom-rank-single-top3.vue'
import { uiMeta as rankMeta } from './components/custom-rank-single-top3/config/ui-meta'
// 使用配置
</script>
<template>
<custom-rank-single-top3 :ui-meta="rankMeta" :context="context" />
</template>
在 page-center 中使用
// page-center 配置文件中
import { uiMeta as rankMeta } from './components/custom-rank-single-top3/config/ui-meta'
export const pageConfig = {
compUiMeta: {
rankSingleTop3: rankMeta
}
}
必须遵守的约定
1. Props、Emits 与类型(对齐 ui2)
- 主组件仅接收 uiMeta、context 两个 props,使用 声明式类型 + withDefaults,类型定义在
types/index.ts,不使用Record<string, unknown>或any。
// types/index.ts 中定义
export interface XxxUiMeta { type: string; props: { ... } }
export interface XxxProps {
/** 通过 page-center 配置的组件元信息 */
uiMeta?: XxxUiMeta
/** 上下文(区服等) */
context?: Record<string, unknown>
}
// custom-xxx.vue 中
const props = withDefaults(defineProps<XxxProps>(), {
uiMeta: () => defaultUiMeta,
context: () => ({})
})
- 若有事件,使用 defineEmits + 调用签名形式(Vue 2.7 不支持具名元组):
interface Emits { (e: 'click', id: string): void },const emit = defineEmits<Emits>()。 - 接口字段使用
/** */注释;服务端返回类型保持 snake_case,前端 pageCenter 静态配置用 camelCase。
2. this → ins、全局 API 与生命周期(对齐 ui2)
- 所有
this.$xxx统一用 useInstance() 得到ins,例如:ins.$base.amBasic()、ins.$propsConfig(id)、ins.$toast()。 - ref、computed、watch、useInstance、useCompUiMeta、useLifeCycle 等已 auto-import,不要在积木内再写
import { ref } from 'vue'。 - 生命周期用 useLifeCycle():
const { onAfterInit, onPageShow, onPageHide } = useLifeCycle(),弹窗等 DOM 操作放在onAfterInit之后。
3. use-meta composable
- 入参:
props: { uiMeta: UiMeta; context?: Record<string, unknown> },避免any。 - 从
props.uiMeta?.props读取业务配置;样式优先走 主题(如themeType+themeConfig),再回退到props.styles。 - 通用字段:
outerStyle、titleStyle、rewardBorderStyle、rewardBgStyle、stretchConfig、images、textConfig;若积木带「去送礼」按钮,需增加xxxBtnStyle(如goGiftBtnStyle、sendGiftBtnStyle),来自themeConfig[themeType].goGiftBtn/sendGiftBtn或styles.sendGiftBtn。 - 文本统一用 genComponentCurRegionText(uiMeta.type, context) 与 default 合并,再与
props.text合并,禁止硬编码文案。 - 读取配置用 可选链与空值合并:
props.uiMeta?.props?.images ?? {},不使用 lodashget()。
3.1 多主题配置规范(themeConfig)
强制标准:所有积木组件的主题配置必须统一使用 themeConfig 对象格式
每个积木组件的 config/default-styles.ts 文件必须按以下规范导出主题配置:
/**
* 组件名称主题色系配置
* 支持多主题:blue(默认)/ red / purple / gold / brown
* 每个主题必含核心字段:outer、title + 组件特定字段
*/
export const themeConfig = {
blue: {
outer: {
background: '#061B29' // 外层容器背景色
},
title: {
color: 'linear-gradient(0deg, #D3DBFF, #ffffff)', // 标题渐变色
fontSize: '0.4rem', // 可选:字号
fontWeight: '700' // 可选:字重
},
// 组件特定字段:根据组件实际需要配置
rewardBorder: {
background: 'linear-gradient(to bottom, #93EFFF, #57B9F6)'
},
rewardBg: {
background: 'linear-gradient(to bottom, #0D399F, #2176DFCC)'
},
// 其他组件特定样式...
},
red: {
outer: {
background: '#3a1322'
},
title: {
color: 'linear-gradient(0deg, #ffd3e5, #ffffff)',
fontSize: '0.4rem',
fontWeight: '700'
},
rewardBorder: {
background: 'linear-gradient(to bottom, #ffb5b5, #d05959)'
},
rewardBg: {
background: 'linear-gradient(to bottom, #5d1f1f 0%, #7c2b2c 42%, #983738 100%)'
}
},
purple: {
outer: {
background: '#291048'
},
title: {
color: 'linear-gradient(0deg, #E5D3FF, #ffffff)',
fontSize: '0.4rem',
fontWeight: '700'
},
rewardBorder: {
background: 'linear-gradient(to bottom, #EBDBFF, #C991FD)'
},
rewardBg: {
background: 'linear-gradient(to bottom, #311EA7, #8B6BFFCC)'
}
}
// gold、brown 等主题按需添加
}
主题命名约定:
| 主题名称 | 说明 | 主色调 |
|---|---|---|
blue | 蓝色主题(默认) | #061B29(深蓝) |
red | 红色主题 | #3a1322(深红) |
purple | 紫色主题 | #291048(深紫) |
gold | 金色主题 | #C4956A(金棕色) |
brown | 棕色主题 | #B5997A(浅棕色) |
核心字段约定:
每个主题必须包含以下核心字段(使用标准 CSS 属性):
outer:外层容器样式,至少包含backgroundtitle:标题样式,至少包含color(渐变色),可选fontSize、fontWeight
组件特定字段:
根据组件类型添加必需的样式字段(参考同类组件):
- 礼盒/盲盒/榜单类:
rewardBorder(奖励边框)、rewardBg(奖励背景)、按钮样式 - 红包雨类:
stageIconOuter(阶段icon外圈)、stageIconInner(阶段icon内圈)、rewardCard(红包卡片)、rewardCardInner(红包卡片内层) - 小礼物爆大礼物类:
giftCardBorder(礼物卡片边框)、giftCardBg(礼物卡片背景)、sendGiftBtn(去送礼按钮)
在 use-meta 中使用主题配置:
import { themeConfig } from '../config/default-styles'
export const useMeta = ({ uiMeta, context }: UseMetaParams) => {
const props = uiMeta.props
// 获取当前主题配置,默认为 blue
const currentTheme = themeConfig[props.themeType] || themeConfig.blue
// 样式:主题配置 + props.styles 合并覆盖
const outerStyle = computed(() => ({
...currentTheme.outer,
...props.styles?.outer
}))
const titleStyle = computed(() => ({
...currentTheme.title,
...props.styles?.title
}))
// 其他样式字段同理...
}
禁止的做法:
- ❌ 导出独立的主题变量(如
export const defaultTheme = {...},export const redTheme = {...}) - ❌ 使用
getThemeStyles函数进行主题选择(直接用对象索引:themeConfig[themeType]) - ❌ 主题名称不一致(如混用
default、blue作为默认主题) - ❌ 核心字段缺失或命名不一致
参考实现:礼盒 components/box/gift-box/config/default-styles.ts、榜单奖励 components/rank-rewards/config/default-styles.ts。
4. 布局与样式规范(与 ui2 一致)
4.1 样式标准化:强烈禁止简写
积木组件内所有样式(含 default-styles.ts、themeConfig、use-meta 导出的 xxxStyle)禁止使用简写属性。必须使用标准 CSS 属性名且数值显式加 rem 单位。完整映射与规则见 ui2-upgrade-guide 第四章节「样式标准化」(约 4.1–4.5)。
禁止使用的简写(仅列举,全部禁止):
| 禁止简写 | 必须使用标准属性 | 说明 |
|---|---|---|
pt / pb / pl / pr | top / bottom / left / right | 定位,单位 rem |
pc | 展开为定位组合 | pc: 'x' → { left: '50%', transform: 'translateX(-50%)' },pc: 'y' / 'xy' 同理 |
pz | zIndex | |
w / h | width / height | 如 '1.9rem' |
wh | width + height | 拆成两个属性 |
f | fontSize + color + fontWeight | 合写必须拆开,注意是 color 不是 fontColor |
fs / fc / fw | fontSize / color / fontWeight | |
m / mt / mb / ml / mr | margin / marginTop 等 | |
pd / pdt / pdb / pdl / pdr | padding / paddingTop 等 | |
bg-i / bg-c / bg-s / bg-p | backgroundImage / backgroundColor / backgroundSize / backgroundPosition | 若通过 :style 绑定须用标准名 |
b / bw / bc / bdr | border / borderWidth / borderColor / borderRadius |
约定:
- 所有样式对象通过
:style绑定时,一律使用上表中的标准属性名 + rem 单位(如top: '0.28rem'、fontSize: '0.24rem'、color: '#ffffff')。 - :style 与 v-bind:纯 CSS 走
:style;组件/元素自有 prop(如bg-i在模板上直接写)可保留,但 config/default-styles 等配置里的样式对象禁止出现简写。 - 合写必须拆分:例如
f: '.22 #FFFFFF 600'改为{ fontSize: '0.22rem', color: '#FFFFFF', fontWeight: '600' }。
4.2 样式文件组织:固定样式与动态样式分离
为提高可维护性,积木组件的样式采用 SCSS 文件 + :style 动态绑定 相结合的方式:
- 固定样式(布局、尺寸、间距、圆角等不随主题变化的样式)→ 独立的
.scss文件 - 动态样式(主题色、渐变、根据配置变化的样式)→
:style绑定
目录结构:
components/<积木名>/
├── styles/
│ └── index.scss # 固定样式文件
├── custom-<积木名>.vue # 主组件
└── ...
在 Vue 文件中引入(强制):
积木组件的 styles/index.scss 必须在 <script> 内通过 import 引入,否则在插槽、ui2-stretch 等场景下样式可能不生效。
<script setup lang="ts">
// 必须:在 script 内引入样式,确保固定样式生效
import './styles/index.scss'
// ... 其他导入(useMeta、defaultUiMeta、types 等)
</script>
<template>...</template>
<style scoped lang="scss">
/* 仅保留无法抽离到 styles/index.scss 的少量样式;主样式勿用 <style src="..."> 替代 script import */
</style>
禁止仅使用 <style src="./styles/index.scss" scoped></style> 而不在 script 中 import,否则可能导致内容区、标题行等内边距/布局不生效。
SCSS 文件编写规范:
- 使用 BEM 命名:
.组件名__元素名--修饰符(如.gift-box-wrapper__title-row) - 嵌套层级 ≤3 层
- 使用 rem 单位:所有尺寸值显式加
rem - 不使用
!important - 固定样式包括:
- 宽高、内外边距、圆角
- 布局属性(display、flex、position 等)
- 文字大小、粗细(不含颜色,颜色可能随主题变)
- 固定的颜色(如
#ffffff白色文字)
动态样式通过 :style 绑定:
- 主题色、渐变(来自
themeConfig) - 背景色、边框色(根据
themeType动态变化) - 需要通过 props 传入的样式
示例:
<template>
<!-- class 控制固定样式,:style 绑定动态样式 -->
<a-section class="gift-box-wrapper" :style="outerStyle">
<a-section class="gift-box-wrapper__title-row">
<ui-gradient-text
class="gift-box-wrapper__title"
f="0.4 transparent 700"
:gradient-color="titleStyle.color"
:content="textConfig.title"
/>
</a-section>
<a-section class="gift-box-wrapper__reward-border" :style="rewardBorderStyle">
<a-section class="gift-box-wrapper__reward-bg" :style="rewardBgStyle">
<ui-prop-wrapper wh="100% 100%" :id="rewardId" />
</a-section>
</a-section>
</a-section>
</template>
<script setup lang="ts">
import './styles/index.scss'
// ...
</script>
// styles/index.scss
.gift-box-wrapper {
width: 100%;
&__title-row {
display: flex;
justify-content: center;
align-items: center;
padding-top: 0.28rem;
}
&__title {
margin: 0 0.16rem;
}
&__reward-border {
width: 1.6rem;
height: 1.6rem;
padding: 0.02rem;
border-radius: 0.16rem;
box-sizing: border-box;
// 背景、边框色通过 :style="rewardBorderStyle" 绑定
}
&__reward-bg {
width: 100%;
height: 100%;
border-radius: 0.16rem;
display: flex;
align-items: center;
justify-content: center;
// 背景色通过 :style="rewardBgStyle" 绑定
}
}
4.3 布局与 pageCenter 约定
- 最外层:
<a-section>+<ui2-stretch>(九宫格拉伸),stretch 使用stretchConfig.stretchBg、minHeight、centralAreaHeight。 - 玩法标题:
ui-gradient-text+ 左右装饰图(images.leftIcon/rightIcon);上下与水平间距的固定 rem 见下节 4.3.1(与礼盒、盲盒、榜单礼物沉淀一致)。 - 玩法说明:单一块用
v-html="textConfig.explain";外边距见 4.3.1(礼盒/盲盒与榜单礼物略有差异),字号 0.24rem、颜色 #ffffff。 - 奖励区:外框用
rewardBorderStyle,内层用rewardBgStyle;道具用<ui-prop-wrapper :id="rewardId" />;名称/价值等从ins.$propsConfig(id)或 props 取,不写死常量。
4.3.1 玩法标题上下间距固定样式(礼盒 / 盲盒 / 榜单礼物沉淀)
以下数值来自 components/box/gift-box、components/box/blind-box、components/rank-rewards 的 styles/index.scss,新建或改版带玩法标题的积木应优先对齐,避免同一活动内多块间距不一致。
强制标准:玩法标题部分固定样式
带玩法标题的积木必须在 styles/index.scss 中为标题行与标题文案使用下列固定样式,类名与属性不可省略或改写(可随积木前缀命名,如 lucky-gift-wrapper__title-row):
// 标题行:居中、仅上内边距
&__title-row {
display: flex;
justify-content: center;
align-items: center;
padding-top: 0.28rem;
}
// 标题文案(ui-gradient-text 等):与左右装饰水平间距
&__title {
margin: 0 0.16rem;
}
模板约定:标题行容器使用 xxx-wrapper__title-row,标题文案节点(如 <ui-gradient-text>)使用 xxx-wrapper__title;若有左右装饰图,使用 xxx-wrapper__title-icon(宽高 1.08rem × 0.34rem),与 __title 同排即可。
| 层级 | 类名约定 | 固定样式(rem) | 说明 |
|---|---|---|---|
| 玩法块内容区 | xxx-wrapper__content | padding-top: 0.4rem、padding-bottom: 0.4rem | 盲盒/礼盒写法;榜单礼物等价于 padding: 0.4rem 0 |
| 标题行 | xxx-wrapper__title-row | padding-top: 0.28rem | 仅上内边距,不设 padding-bottom;标题与下方说明/首块内容的垂直间距由下一区块的 margin 承担 |
| 左右装饰图 | xxx-wrapper__title-icon | width: 1.08rem、height: 0.34rem | 与三处参考实现一致 |
| 标题文案与装饰图水平间距 | 标题文案节点使用 xxx-wrapper__title(强制) | margin: 0 0.16rem | 见上文强制标准代码块,与 __title-row 配套使用 |
| 标题行下方 → 玩法说明 | xxx-wrapper__explain | 礼盒 / 盲盒:margin: 0.4rem(四边一致) | 说明块顶边距即标题行下方的视觉间距 0.4rem |
| 标题行下方 → 玩法说明 | xxx-wrapper__explain | 榜单礼物:margin: 0.4rem 0.4rem 0.3rem 0.4rem | 上/左/右仍为 0.4rem,下边距 0.3rem(略紧于礼盒);新积木若无设计特殊要求,推荐与礼盒/盲盒统一为 margin: 0.4rem |
垂直关系小结(从 ui2-stretch 内容顶到标题文字):
- 先经过
__content的padding-top: 0.4rem; - 再经过
__title-row的padding-top: 0.28rem; - 标题行下方到说明文案:由
__explain的margin-top(0.4rem) 形成统一留白。
样式与 pageCenter 对齐 ui2:
- images:使用复数
images,为完整 URL 或资源 key,不写死单数image。 - text:统一收口到
props.text(title、explain、tip 等)。 - styles:仅使用标准 CSS 属性 + rem,禁止简写(见 4.1);
xxxStyle通过:style绑定。
4.3.2 礼物/奖励卡片布局规范(通用沉淀)
根据礼盒、盲盒、榜单奖励、小礼物爆大礼物等积木的实践,礼物/奖励卡片布局分为两类:
A. 奖励卡片(榜单奖励、礼盒周边奖励等)
用于展示多个小奖励,尺寸 1.6rem × 1.6rem(正方形):
// 奖励外框
&__reward-box-outer {
position: relative;
width: 1.6rem;
height: 1.6rem;
padding: 0.02rem;
border-radius: 0.16rem; // 或 50% 圆形
box-sizing: border-box;
}
// 奖励内层
&__reward-box-inner {
width: 100%;
height: 100%;
border-radius: 0.16rem; // 或 50% 圆形
display: flex;
align-items: center;
justify-content: center;
}
- 外框 padding 0.02rem,borderRadius 0.16rem(圆形用 50%)
- 背景/边框通过
:style="rewardBorderStyle"和:style="rewardBgStyle"绑定 - 名称字号 0.24rem、颜色 #ffffff、font-weight 400
- 价值字号 0.22rem、颜色 #fffa00、font-weight 700,金币图标 0.24rem × 0.24rem
B. 礼物卡片(小礼物爆大礼物、单独礼物展示等)
用于展示较大的礼物,卡片宽度 2.6-2.8rem(根据一行放几个调整):
// 礼物卡片
&__gift-card {
position: relative;
flex-shrink: 0;
box-sizing: border-box;
padding: 0.04rem; // 边框粗细
}
&__gift-card-inner {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0.24rem 0.16rem; // 内边距
box-sizing: border-box;
}
// 礼物道具区域:固定正方形
&__gift-prop {
width: 1.6rem;
height: 1.6rem;
flex-shrink: 0;
margin-bottom: 0.16rem;
}
- 卡片 padding 0.04rem(边框),borderRadius 0.16rem
- 内容区 padding 0.24rem 0.16rem(上下 左右)
- 道具区域固定 1.6rem × 1.6rem,margin-bottom 0.16rem
- 名称字号 0.26rem、颜色 #ffffff、font-weight 400,margin-bottom 0.08rem
- 价值字号 0.32rem、颜色 #FFFA00、font-weight 700,金币图标 0.28rem × 0.28rem
- 背景/边框通过
:style="giftCardBorderStyle"和:style="giftCardBgStyle"绑定
角标通用规范(适用于两类卡片):
&__gift-tag { // 或 __reward-tag
position: absolute;
top: -0.08rem;
left: -0.08rem;
z-index: 2;
}
&__gift-tag-img {
width: 0.8rem;
height: 0.4rem;
}
&__gift-tag-text {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.22rem;
color: #57280f;
font-weight: 700;
padding: 0.04rem 0.16rem;
}
- 角标位置:左上角外溢(top/left -0.08rem)
- 角标图片:宽 0.8rem、高 0.4rem
- 角标文字(无图片时):字号 0.22rem、颜色 #57280f、padding 0.04rem 0.16rem、圆角 0.08rem、渐变背景
linear-gradient(to bottom, #f5be28, #f4ea7b)
强制:礼物/奖励角标 tag 必须有背景样式
- 角标(如 New、热等)禁止仅设字色无背景,否则在深色卡片上可读性差。
- 两种写法二选一:
- 组件 SCSS 写死:在
styles/index.scss中为__reward-tag/__gift-tag设置默认背景(如background: linear-gradient(to bottom, #f5be28, #f4ea7b))、padding、border-radius、position: absolute; top: 0; left: 0; z-index: 1,与礼盒/盲盒一致; - 配置传入:通过
uiMeta.props.styles.rewardTag(或等价字段)传入完整样式对象,用:style="rewardTagStyle"绑定。
- 组件 SCSS 写死:在
- 若组件内已写死背景,config 中的
rewardTag仍可叠加覆盖(如主题色差异)。
强制:礼物/奖励边框仅包裹切图,名称与金币在框下展示
- 边框容器(如
__reward-item)仅包裹:道具切图(ui-prop-wrapper)+ 角标 tag;不包裹名称、金币数。 - 名称与金币数放在边框下方,使用独立信息块(如
__reward-info),通过margin-top或定位在框下展示。 - DOM 结构约定:定位外层(
__reward-wrap,位置由 layout 传入)→ 边框盒(__reward-item,内层__reward-inner+ 切图 + tag)→ 信息块(__reward-info,名称 + 价值行)。 - 参考实现:礼盒
components/box/gift-box(reward-wrap/reward-item/reward-info分离)。
4.3.3 红包雨玩法布局规范(节点列表 + 镜像区域)
红包雨玩法采用横向两栏布局:左侧镜像区域(可选)+ 右侧节点列表。节点纵向排列,每个节点包含左侧图标/数量、连接线、右侧红包雨奖励卡片列表(多个奖励用 OR 连接)。
主布局结构:
// 主内容区域:左侧镜像区 + 右侧节点列表
&__main-section {
display: flex;
flex-direction: row;
align-items: flex-start;
margin: 0.4rem 0.4rem;
gap: 0.32rem;
}
// 左侧镜像区域(可选)
&__mirror-section {
flex-shrink: 0;
width: 3rem; // 固定宽度
}
// 右侧节点列表
&__nodes-section {
flex: 1;
display: flex;
flex-direction: column;
}
节点结构(纵向排列):
// 单个节点项
&__node-item {
display: flex;
flex-direction: row;
align-items: flex-start;
position: relative;
margin-bottom: 0.32rem;
&:last-child {
margin-bottom: 0;
}
}
// 节点左侧:图标 + 数量标签
&__node-left {
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
width: 1.2rem;
margin-right: 0.24rem;
position: relative;
}
&__node-icon {
width: 0.8rem;
height: 0.8rem;
// 背景、边框等通过 :style 绑定
}
&__node-quantity {
margin-top: 0.08rem;
display: inline-block;
white-space: nowrap;
text-align: center;
// 背景、字号等通过 :style 绑定
}
// 节点连接线(垂直线条)
&__node-connector {
position: absolute;
left: 0.6rem; // 图标中心位置
top: 0.8rem; // 从图标底部开始
bottom: -0.32rem; // 连接到下一个节点
width: 0.04rem;
transform: translateX(-50%);
z-index: 1;
// 背景色通过 :style 绑定
}
红包雨奖励卡片列表(横向排列,OR 连接):
// 节点右侧:红包雨奖励卡片列表
&__node-rewards {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
gap: 0.16rem;
}
// 红包雨奖励卡片
&__reward-card {
position: relative;
flex-shrink: 0;
box-sizing: border-box;
width: 2.4rem; // 固定宽度
// 背景、边框等通过 :style 绑定
}
&__reward-card-inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
// padding 等通过 :style 绑定
}
// 红包雨图片
&__reward-image {
width: 1.6rem;
height: 1.6rem;
flex-shrink: 0;
margin-bottom: 0.12rem;
}
// 金币数量文本
&__coins-label {
text-align: center;
white-space: nowrap;
// 字号、颜色通过 :style 绑定
}
// OR连接符
&__or-text {
flex-shrink: 0;
padding: 0 0.16rem;
// 字号、颜色通过 :style 绑定
}
左侧镜像区域(可选展示):
&__mirror-container {
box-sizing: border-box;
padding: 0.24rem;
// 边框等通过 :style 绑定
}
&__mirror-title {
margin-bottom: 0.16rem;
text-align: center;
// 字号、颜色通过 :style 绑定
}
&__mirror-description {
margin-bottom: 0.24rem;
text-align: left;
white-space: pre-line; // 支持换行符
// 字号、颜色通过 :style 绑定
}
&__mirror-icons {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.24rem;
}
&__mirror-icon {
width: 1.2rem;
height: 1.2rem;
flex-shrink: 0;
}
配置要点:
- 节点图标(
nodes[].icon)可配置为任意图标,不限于特定样式 - 节点数量(
nodes[].quantity)可根据实际需求配置任意数值 - 每个节点可配置单个或多个红包雨奖励(
nodes[].rewards),多个奖励用 OR 连接 - 左侧镜像区域通过
showMirrorSection控制是否显示 - 幸运加倍角标(
rewards[].showLuckyBonus)显示在卡片左上角 - 支持多主题切换(
themeType),节点图标、连接线、按钮等颜色随主题变化
参考实现:components/custom-red-envelope-rain/(红包雨玩法积木)
4.4 【去送礼】按钮规范(带送礼入口的积木统一遵守)
凡积木需要「去送礼」入口的(如礼盒、盲盒、小礼物爆大礼物等),按钮的布局与事件逻辑按以下约定实现,与礼盒/盲盒保持一致。
强制标准:玩法底部【去送礼】按钮样式与结构
带「去送礼」入口的积木必须在 styles/index.scss 与模板中按下述写法实现,类名与属性不可省略或改写(前缀随积木命名,如 lucky-gift-wrapper__btn-container)。
SCSS 固定样式(styles/index.scss):
// 送礼按钮容器
&__btn-container {
margin: 0.36rem;
}
&__btn {
width: 3.4rem;
height: 0.88rem;
border-radius: 0.44rem;
}
不在 __btn-container / __btn 中写背景、字号、颜色等视觉样式,由 :style="xxxBtnStyle" 动态绑定;水平居中由模板上的 fx-center、fx-c-center 提供。
模板结构(必须一致):
<a-section class="xxx-wrapper__btn-container fx-center">
<a-section
class="xxx-wrapper__btn fx-center fx-c-center"
:style="xxxBtnStyle"
v-html="textConfig.sendGiftBtnText"
@a-tap="toSendGift"
/>
</a-section>
- 结构:按钮区域放在玩法内容与保底说明之后、
</ui2-stretch>之前;外层__btn-container+ 内层__btn,容器与按钮均带fx-center,按钮再带fx-c-center。 - 动态样式:通过 use-meta 导出
xxxBtnStyle(如goGiftBtnStyle、sendGiftBtnStyle),来自themeConfig[themeType].goGiftBtn/sendGiftBtn或回退到styles.sendGiftBtn;在 default-styles / themeConfig 中使用标准 CSS 属性 + rem(如background、fontSize、color、fontWeight),禁止简写。 - 文案:按钮文案来自
textConfig.sendGiftBtnText(或该积木约定的字段如sendblindBtnText),由 default-ui-meta + genComponentCurRegionText + props.text 合并得到,禁止硬编码「去送礼」等文案。
事件逻辑规范
- 点击事件:使用
@a-tap="toSendGift"(或语义一致的方法名,如handleSendGift)。 - 默认行为:跳转语音房列表,统一使用
ins.$we('jumpDeepLink', 'wespydeeplink://voice_room_list');ins通过useInstance()获取。 - 可配置扩展:若后续需按活动或 page-center 配置跳转链接,应从
uiMeta.props或 jsonConfig 读取链接,不在组件内硬编码;未配置时使用上述默认 deeplink。
参考实现:礼盒 components/box/gift-box/(goGiftBtnStyle、toSendGift)、盲盒 components/box/blind-box/(goblindBtnStyle、toSendGift)。
5. script setup 编写顺序(与 ui2 一致)
- 导入(仅需写非 auto-import 的)
- Props(withDefaults(defineProps<Props>(), …))
- Emits(defineEmits<Emits>())
- Composables(useInstance、useMeta、useLifeCycle 等)
- 响应式数据(ref/reactive)
- 计算属性(computed)
- 监听器(watch)
- 方法
- 生命周期钩子(onMounted、onAfterInit 等)
- defineExpose(如有需要)
6. 在页面中的使用
- 页面中
const compUiMeta = useCompUiMeta(),compUiMeta的 key 与 page-center 配置的组件 key 一致(如rankPoints、giftBox、blindBox)。 - 模板中:
<custom-xxx :uiMeta="compUiMeta.xxxKey" />;需在页面或父组件中 import 并注册 该 custom 组件(局部引用即可)。
7. 其他编码与规范
- 使用
<script setup lang="ts">,禁止 options API 与 mixin;复杂逻辑抽到 composable。 - 禁止硬编码商品/道具 id、文案、金额;从 jsonConfig 或 uiMeta 读取。
- 单文件 ≤300 行,SCSS 嵌套 ≤3 层,无
!important;圈复杂度 <10。 - Vue 2.7 兼容:不使用
defineOptions()、defineModel()、Emits 具名元组、Teleport、Suspense 等;Emits 用调用签名形式。
参考实现位置
| 积木类型 | 路径(相对活动 recharge) |
|---|---|
| 礼盒 | components/box/gift-box/ |
| 盲盒 | components/box/blind-box/ |
| 榜单规则 | components/rank-points/ |
| 榜单礼物 | components/rank-rewards/ |
| 单榜 Top3 | components/single-top3/ |
| 小礼物爆大礼物 | components/custom-lucky-gift/ |
| 红包雨 | components/custom-red-envelope-rain/ |
生成新积木时,可复制最相近的积木目录,再按业务改 type、props、布局与 composable。
清单(生成新积木时自检)
结构与类型
- 目录名 kebab-case,入口文件
custom-<积木名>.vue -
types/index.ts定义 UiMeta/Props(及 Emits 若有),与 config/default-ui-meta.ts 结构一致 - config/ 三文件规范:
-
config/default-styles.ts导出 ThemeStyleConfig 类型 + themeConfig 对象(多主题样式配置) -
config/default-ui-meta.ts导出 defaultUiMeta(组件默认配置,图片为空串,theme 引用 default-styles) -
config/ui-meta.ts导出 uiMeta(页面配置示例,包含实际图片 URL,有使用说明注释) -
config/index.ts(可选)导出工具函数和常量
-
-
config/default-ui-meta.ts中type: 'custom-<积木名>' - Props 使用
withDefaults(defineProps<Props>(), …),类型来自 types,defaultUiMeta 从./config/default-ui-meta引入,无any - 创建
styles/index.scss文件,在 Vue 的<script>内通过import './styles/index.scss'引入(禁止仅用<style src="...">,否则样式可能不生效)
use-meta 与数据
-
use-meta.ts接收 props(类型明确),导出 outerStyle、titleStyle、rewardBorder/Bg、stretchConfig、images、textConfig 等 - 配置读取用可选链
?.与??,文本通过 genComponentCurRegionText + default + props.text 合并
布局与样式(对齐 ui2,固定样式与动态样式分离)
- 最外层 a-section + ui2-stretch;玩法标题强制标准(4.3.1):SCSS 必须含
__title-row(display flex、居中、padding-top 0.28rem)与__title(margin 0 0.16rem),模板使用xxx-wrapper__title-row、xxx-wrapper__title;content 上下 0.4rem、装饰 1.08×0.34rem、explain 与礼盒对齐 0.4rem;奖励用 rewardBorder/Bg + ui-prop-wrapper - 【去送礼】按钮(若积木带该入口):强制标准(4.4)SCSS 仅
__btn-container { margin: 0.36rem }、__btn { width: 3.4rem; height: 0.88rem; border-radius: 0.44rem },模板为__btn-container fx-center+__btn fx-center fx-c-center+:style="xxxBtnStyle"+v-html="textConfig.sendGiftBtnText"+@a-tap="toSendGift";点击逻辑默认ins.$we('jumpDeepLink', 'wespydeeplink://voice_room_list') - 礼物/奖励角标 tag:强制(4.3.2)角标必须有背景样式,在组件 SCSS 中写死(黄底、padding、border-radius、左上角定位)或通过
styles.rewardTag传入,禁止仅字色无背景 - 边框仅包切图:强制(4.3.2)奖励/礼物边框容器仅包裹切图+tag,名称与金币在框下展示,结构为
__reward-wrap→__reward-item(切图+tag)→__reward-info(名称+价值) - 样式禁止简写:default-styles / themeConfig / 所有 xxxStyle 仅用标准 CSS 属性(top、width、fontSize、color、margin 等)+ rem,禁止 pt/pb/wh/f/pc 等简写;合写已拆分
- 固定样式写在 SCSS:布局、尺寸、间距、圆角等写在
styles/index.scss,使用 BEM 命名,嵌套 ≤3 层 - 动态样式用 :style:主题色、渐变等来自 themeConfig 的样式通过
:style绑定 - class + :style 结合:模板中通过
class应用固定样式,通过:style绑定动态样式
页面与规范
- 页面中通过 useCompUiMeta() 取配置,
:uiMeta="compUiMeta.xxxKey"传入;组件局部 import 注册 - 使用
ins = useInstance()替代 this;无 mixin;Vue 2.7 兼容(无 defineModel、Emits 具名元组等)
更细的字段说明见 reference.md;Vue/TS/样式/ pageCenter 详细规范见 ui2-upgrade-guide。
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/mercury-api.wepieoa.com/fe-custom-components-skill">View custom-components-skill on skillZs</a>