758 lines
26 KiB
Vue
758 lines
26 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* 首页**唯一**卡片渲染组件(P3 瓷片区重做)。
|
||
*
|
||
* 设计:**版式(template)与皮肤(variant)正交**
|
||
* - 版式:iconTop 图标墙 / iconLeft 行卡 / lead 通栏主卡 / duo 双列渐变卡 / group 分组容器卡;
|
||
* - 皮肤:porcelain / tint / glass / solid / line(沿用 P2,已验过);
|
||
* - 色系:gold / blue / mint / rose / violet(tone,走 CSS 变量注入)。
|
||
* 根元素同时带 `.ct--<template>` 与 `.ct--skin-<variant>` 两个 class,二者互不耦合。
|
||
*
|
||
* `group` 版式用默认插槽承载子卡网格;其余版式为单卡自渲染。
|
||
* 本组件不涉及浮层,**禁止写裸 z-index**(S15)。
|
||
*/
|
||
import { computed } from 'vue'
|
||
import Sticker from '@/components/Sticker.vue'
|
||
import { CARD_TONES } from '@/config/card-styles'
|
||
|
||
const props = withDefaults(defineProps<{
|
||
/** 版式(见 CARD_TEMPLATES) */
|
||
template?: string
|
||
/** 皮肤(见 CARD_VARIANTS) */
|
||
variant?: string
|
||
/** 色系(见 CARD_TONES) */
|
||
tone?: string
|
||
/** wot 图标名 */
|
||
icon?: string
|
||
/** 标题 */
|
||
title?: string
|
||
/** 描述 */
|
||
desc?: string
|
||
/** 右上角角标文案(locked 时被「未解锁」覆盖) */
|
||
badge?: string
|
||
/** 尺寸 */
|
||
size?: 'sm' | 'md' | 'lg'
|
||
/** 是否未解锁(灰化 + 角标「未解锁」) */
|
||
locked?: boolean
|
||
/** 插画 / 贴纸 key(见 Sticker.vue 的 SVGS;用于 lead/duo/group/iconTop 版式) */
|
||
illustration?: string
|
||
/** 行卡(iconLeft)右端是否显示箭头(用于「我的」菜单行) */
|
||
arrow?: boolean
|
||
/** 分组容器卡(group)子卡网格列数(默认 2;工具楼层用 4,纵向列表用 1) */
|
||
columns?: number
|
||
/**
|
||
* group 版式是否由本组件自带子卡网格(默认 true)。
|
||
* 置 false 时渲染裸 slot,由**调用方用自己的 scoped 网格**承接 —— P5 实证:
|
||
* 同样的子卡在工作台自管网格(.wb-grid)里横排正常,在组件内联 style 的 .ct__grid 里
|
||
* 却逐字竖排(MP 环境差异),故提供逃生口让调用方复用「已被真机验证」的网格写法。
|
||
*/
|
||
grid?: boolean
|
||
|
||
// ===== v2 版式原子(全部**可选**,缺省 = 现状,零回归)=====
|
||
/** 角标位置:`tr` 右上贴边(默认,除 tile 外)/ `tl` 左上小 pill(tile 默认,参考图 B) */
|
||
badgePos?: 'tr' | 'tl'
|
||
/** 图标块形状:`square` 圆角方(默认)/ `circle` 圆形(参考图 C1) */
|
||
iconShape?: 'square' | 'circle'
|
||
/** 插画位置:`top` 顶部(默认,除 duo/tile 外)/ `rightBottom` 右下大插画(duo/tile 默认,参考图 C2/B) */
|
||
artPos?: 'top' | 'rightBottom'
|
||
/** 胶囊 CTA 文案(非空时在标题下方渲染 `.ct__cta`;默认空串不渲染,参考图 C4) */
|
||
cta?: string
|
||
/** group 版式子卡网格是否用 mosaic(2 列 + 通栏行;默认 false = 按 `columns`)。参考图 A */
|
||
mosaic?: boolean
|
||
/** 本卡作为容器内「通栏行」渲染(根元素加 `.ct__row`,跨整行;默认 false) */
|
||
row?: boolean
|
||
/** group 版式右上胶囊文案(非空时渲染 `.ct__pill`;默认空串不渲染) */
|
||
pill?: string
|
||
}>(), {
|
||
template: 'iconTop',
|
||
variant: 'porcelain',
|
||
tone: 'gold',
|
||
icon: '',
|
||
title: '',
|
||
desc: '',
|
||
badge: '',
|
||
size: 'md',
|
||
locked: false,
|
||
illustration: '',
|
||
arrow: false,
|
||
columns: 2,
|
||
grid: true,
|
||
})
|
||
|
||
/** 色系元数据(未知 tone 回落首个,绝不抛错) */
|
||
const toneMeta = computed(() => CARD_TONES.find(t => t.id === props.tone) || CARD_TONES[0])
|
||
|
||
/** 色系 → CSS 变量(供各 skin / 版式 class 消费) */
|
||
const toneVars = computed<Record<string, string>>(() => ({
|
||
'--ct-from': toneMeta.value.from,
|
||
'--ct-to': toneMeta.value.to,
|
||
'--ct-accent': toneMeta.value.accent,
|
||
'--ct-tint': toneMeta.value.tintBg,
|
||
'--ct-icon-bg': toneMeta.value.iconBg,
|
||
'--ct-line': toneMeta.value.lineBorder,
|
||
// 强渐变(solid 皮肤 / duo 版式专用):饱和端点,避免用 accent(文字深色)起笔而发暗
|
||
'--ct-grad-from': toneMeta.value.gradFrom,
|
||
'--ct-grad-to': toneMeta.value.gradTo,
|
||
// 角标:随色系派生(高对比),勿用固定金色(会与 gold 卡同色不可见)
|
||
'--ct-badge-bg': toneMeta.value.badgeBg,
|
||
'--ct-badge-fg': toneMeta.value.badgeFg,
|
||
// 彩色环境投影(参考图质感关键:随本卡色相的低透明色,替代灰黑阴影)
|
||
'--ct-shadow': toneMeta.value.shadow,
|
||
// 层叠卡(stack)两层厚度色
|
||
'--ct-layer': toneMeta.value.layer,
|
||
'--ct-layer-soft': toneMeta.value.layerSoft,
|
||
}))
|
||
|
||
/** 深色皮肤/强渐变版式用白图标 */
|
||
const darkSkin = computed(() => props.variant === 'solid' || props.template === 'duo')
|
||
const iconColor = computed(() => {
|
||
if (props.locked)
|
||
return '#BDA794'
|
||
return darkSkin.value ? '#FFFFFF' : toneMeta.value.accent
|
||
})
|
||
|
||
/** 插画尺寸随 size 放大(lg ≥ 128rpx) */
|
||
const artSize = computed(() => (props.size === 'lg' ? 128 : props.size === 'sm' ? 72 : 96))
|
||
|
||
/** 图标墙(iconTop)插画尺寸(rpx;与放大的图标块同档,参考图插画占比更大) */
|
||
const iconArtPx = computed(() => (props.size === 'lg' ? 120 : props.size === 'sm' ? 68 : 96))
|
||
|
||
/** 角标文案:未解锁优先 */
|
||
const badgeText = computed(() => (props.locked ? '未解锁' : props.badge))
|
||
|
||
/**
|
||
* v2 版式原子的「生效值」:显式 prop 优先,否则按版式给默认(缺省即现状)。
|
||
* - 角标:tile 默认左上小 pill(参考图 B),其余右上贴边(现状);
|
||
* - 插画:duo(参考图 C2)与 tile(参考图 B)默认右下大插画,其余顶部(现状);
|
||
* - 图标形状:默认圆角方(现状)。
|
||
*/
|
||
const effBadgePos = computed<'tr' | 'tl'>(() => props.badgePos || (props.template === 'tile' ? 'tl' : 'tr'))
|
||
const effIconShape = computed<'square' | 'circle'>(() => props.iconShape || 'square')
|
||
const effArtPos = computed<'top' | 'rightBottom'>(() =>
|
||
props.artPos || ((props.template === 'duo' || props.template === 'tile') ? 'rightBottom' : 'top'),
|
||
)
|
||
</script>
|
||
|
||
<template>
|
||
<view
|
||
class="ct"
|
||
:class="[`ct--${template}`, `ct--skin-${variant}`, `ct--${size}`, { 'ct--lock': locked, 'ct__row': row }]"
|
||
:style="toneVars"
|
||
>
|
||
<!-- ===== 分组容器卡:白底 + 标题 + 右上角标 + 子卡插槽 ===== -->
|
||
<template v-if="template === 'group'">
|
||
<view v-if="title || badgeText || pill" class="ct__head">
|
||
<view class="ct__headlead">
|
||
<Sticker v-if="illustration" :type="illustration" :size="52" float />
|
||
<text class="ct__headtitle">
|
||
{{ title }}
|
||
</text>
|
||
</view>
|
||
<!-- 通栏标题行右胶囊(参考图 D;非空才渲染,默认不渲染 → 零回归) -->
|
||
<text v-if="pill" class="ct__pill">
|
||
{{ pill }}
|
||
</text>
|
||
<text v-if="badgeText" class="ct__headmore">
|
||
{{ badgeText }}
|
||
</text>
|
||
</view>
|
||
<!-- grid=false 时渲染裸 slot:由调用方用自己的 scoped 网格承接(P5 实证 MP 对内联 style 的 repeat() 网格支持不可靠,scoped class 已被工作台真机验证可用)。
|
||
网格铁律:`grid-template-columns` 只允许出现在 scoped class(`.ct__grid--c<N>` / `.ct__grid--mosaic`),**禁止内联 `repeat()`**(MP 不生效,P5 真事故)。 -->
|
||
<view v-if="grid" class="ct__grid" :class="mosaic ? 'ct__grid--mosaic' : `ct__grid--c${columns}`">
|
||
<slot />
|
||
</view>
|
||
<slot v-else />
|
||
</template>
|
||
|
||
<!-- ===== 通栏主卡:左文案 + 右大插画 ===== -->
|
||
<template v-else-if="template === 'lead'">
|
||
<view class="ct__lead-main">
|
||
<text class="ct__title">
|
||
{{ title }}
|
||
</text>
|
||
<text v-if="desc" class="ct__desc">
|
||
{{ desc }}
|
||
</text>
|
||
<view class="ct__extra">
|
||
<slot />
|
||
</view>
|
||
</view>
|
||
<view v-if="illustration || icon" class="ct__art ct__art--lead">
|
||
<Sticker v-if="illustration" :type="illustration" :size="artSize" float />
|
||
<view v-else class="ct__icon ct__icon--art">
|
||
<wd-icon :name="icon" :size="`${artSize * 0.5}rpx`" :color="iconColor" />
|
||
</view>
|
||
</view>
|
||
<text v-if="badgeText" class="ct__badge">
|
||
{{ badgeText }}
|
||
</text>
|
||
</template>
|
||
|
||
<!-- ===== 双列渐变卡:强渐变底 + 右下插画(参考图 C2)+ 标题 + 描述 ===== -->
|
||
<template v-else-if="template === 'duo'">
|
||
<view class="ct__art" :class="effArtPos === 'rightBottom' ? 'ct__art--br' : 'ct__art--duo'">
|
||
<Sticker v-if="illustration" :type="illustration" :size="artSize" />
|
||
<view v-else-if="icon" class="ct__icon ct__icon--art" :class="{ 'ct__icon--circle': effIconShape === 'circle' }">
|
||
<wd-icon :name="icon" :size="`${artSize * 0.5}rpx`" :color="iconColor" />
|
||
</view>
|
||
</view>
|
||
<text class="ct__title">
|
||
{{ title }}
|
||
</text>
|
||
<text v-if="desc" class="ct__desc">
|
||
{{ desc }}
|
||
</text>
|
||
<view v-if="cta" class="ct__cta">
|
||
{{ cta }}
|
||
</view>
|
||
<text v-if="badgeText" class="ct__badge" :class="{ 'ct__badge--tl': effBadgePos === 'tl' }">
|
||
{{ badgeText }}
|
||
</text>
|
||
</template>
|
||
|
||
<!-- ===== 行卡:图标左置 + 文案在右 ===== -->
|
||
<template v-else-if="template === 'iconLeft'">
|
||
<view class="ct__icon">
|
||
<wd-icon v-if="icon" :name="icon" size="44rpx" :color="iconColor" />
|
||
</view>
|
||
<view class="ct__body">
|
||
<text class="ct__title">
|
||
{{ title }}
|
||
</text>
|
||
<text v-if="desc" class="ct__desc">
|
||
{{ desc }}
|
||
</text>
|
||
</view>
|
||
<text v-if="badgeText" class="ct__badge ct__badge--inline">
|
||
{{ badgeText }}
|
||
</text>
|
||
<wd-icon v-if="arrow" name="arrow-right" size="30rpx" color="#D9C3B2" class="ct__go" />
|
||
</template>
|
||
|
||
<!-- ===== 图标墙(默认,含 tile 瓷片):插画/图标在上 + 标题 + 描述 ===== -->
|
||
<template v-else>
|
||
<view v-if="illustration" class="ct__art" :class="effArtPos === 'rightBottom' ? 'ct__art--br' : 'ct__art--top'">
|
||
<Sticker :type="illustration" :size="iconArtPx" float />
|
||
</view>
|
||
<view v-else class="ct__icon" :class="{ 'ct__icon--circle': effIconShape === 'circle' }">
|
||
<wd-icon v-if="icon" :name="icon" size="46rpx" :color="iconColor" />
|
||
</view>
|
||
<text class="ct__title">
|
||
{{ title }}
|
||
</text>
|
||
<text v-if="desc" class="ct__desc">
|
||
{{ desc }}
|
||
</text>
|
||
<view v-if="cta" class="ct__cta">
|
||
{{ cta }}
|
||
</view>
|
||
<text v-if="badgeText" class="ct__badge" :class="{ 'ct__badge--tl': effBadgePos === 'tl' }">
|
||
{{ badgeText }}
|
||
</text>
|
||
</template>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- ⚠️ :host 必须放在【非 scoped】style 块:uni-app 的 scoped 编译会把它改写成
|
||
`.data-v-<id>:host`(scope 后缀加在 :host 之前),而宿主 <card-tile> 上的 class
|
||
是【引用方】的 scope id(如 data-v-dde6cf0f),永远不带自己的 data-v-<id>
|
||
⇒ 该规则匹配不到任何节点、静默失效(2026-09-24 用户真机实测确证:
|
||
`.cttap` 满格 159px 但卡片没撑满它)。非 scoped 的规则不加后缀,:host 才能命中宿主。 -->
|
||
<style>
|
||
/* ============================== 缺陷 A:MP 宿主撑满(唯一落点) ==============================
|
||
MP 下自定义组件宿主 <card-tile> 是 `.cttap`(flex 容器)的 flex 项,不显式撑满会
|
||
退化为 max-content → 卡片塌成内容宽(≈4 个中文字)。用 `:host` 匹配宿主
|
||
(微信自定义组件 wxss 原生支持,基础库 1.7.2+),**一处规则覆盖全部调用点**,不依赖标签名。
|
||
⚠️ 三条禁令:
|
||
① 不能在页面/全局侧写 `.cttap > *` 兜底 —— WXSS 不支持通配符 `*`,
|
||
微信开发者工具报 `error at token '*'`,整个 app.wxss 编译失败;
|
||
② 本规则不能放进 <style scoped>(会被改写成 .data-v-<id>:host 而静默失效,见上);
|
||
③ 产物验收必须断言「纯 :host{」(前面不得是 .data-v- 前缀),见 r7-assert-mp-v2.py A4 v4。
|
||
H5 端 :host 不匹配任何元素(非 shadow DOM),撑满由 App.vue 的 `.cttap > .ct` 承担。 */
|
||
:host {
|
||
display: block;
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
width: 100%;
|
||
}
|
||
</style>
|
||
|
||
<style scoped>
|
||
/* ============================== 骨架 ============================== */
|
||
.ct {
|
||
position: relative;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
/* 缺陷 A:撑满宿主槽位。MP 下自定义组件宿主包裹本节点,宿主侧由上方 `:host` 规则撑满,
|
||
宿主内部的 `.ct` 仍需自身显式 100%。
|
||
height:100% 用于「同排等高」:宿主为 flex-stretch 项时高度确定 → 生效;
|
||
父高为 auto 时百分比高度按 CSS 规范退化为 auto → 安全(不会塌高)。 */
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 10rpx;
|
||
/* 尺寸基准走 CSS 变量(默认 = 基准值):尺寸档 / 容器档只改变量,不改直接值,
|
||
避免 `.ct.ct--group`(0,2,0) 吞掉 `.ct--sm`/`.ct--lg`(0,1,0) 的 padding/圆角(R-2 特异度连锁)。 */
|
||
--ct-radius: 32rpx;
|
||
--ct-pad: 26rpx 26rpx 24rpx;
|
||
border-radius: var(--ct-radius);
|
||
padding: var(--ct-pad);
|
||
/* 双色渐变底 + 内高光 + 外层柔和投影(默认皮肤 porcelain 在此覆写底色/描边) */
|
||
}
|
||
.ct__title {
|
||
font-size: 28rpx;
|
||
font-weight: 800;
|
||
line-height: 1.25;
|
||
color: #3d2a1e;
|
||
}
|
||
.ct__desc {
|
||
font-size: 22rpx;
|
||
line-height: 1.45;
|
||
color: #6b4f3a;
|
||
max-width: 100%;
|
||
}
|
||
.ct--iconTop .ct__desc,
|
||
.ct--duo .ct__desc {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.ct__body {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
}
|
||
|
||
/* ===== 图标块(视觉精致化):渐变底 + 内高光 + 多层柔和投影 ===== */
|
||
.ct__icon {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
width: 96rpx;
|
||
height: 96rpx;
|
||
border-radius: 28rpx;
|
||
margin-bottom: 6rpx;
|
||
background: linear-gradient(160deg, rgba(255, 255, 255, 0.98), var(--ct-icon-bg));
|
||
box-shadow:
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.95),
|
||
inset 0 -6rpx 12rpx rgba(255, 255, 255, 0.6),
|
||
0 8rpx 18rpx var(--ct-shadow),
|
||
0 2rpx 4rpx rgba(107, 47, 15, 0.08);
|
||
}
|
||
.ct__icon--art {
|
||
width: auto;
|
||
height: auto;
|
||
padding: 16rpx;
|
||
border-radius: 28rpx;
|
||
margin-bottom: 0;
|
||
}
|
||
/* 宫格①·圆形图标(参考图 C1):圆形描边质感 + 内高光 + 彩色投影 */
|
||
.ct__icon--circle {
|
||
border-radius: 50%;
|
||
box-shadow:
|
||
inset 0 2rpx 6rpx #fff,
|
||
0 8rpx 18rpx var(--ct-shadow);
|
||
}
|
||
|
||
/* ===== 插画位 ===== */
|
||
.ct__art {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
/* 图标墙(iconTop)插画:大号彩色插画(参考图那组卡的视觉主体) */
|
||
.ct__art--top {
|
||
margin-bottom: 8rpx;
|
||
}
|
||
/* 宫格②/③ 右下大插画(参考图 C2/C3):贴右下角、轻微旋转,饱和渐变卡专用 */
|
||
.ct__art--br {
|
||
position: absolute;
|
||
right: 16rpx;
|
||
bottom: 10rpx;
|
||
opacity: 0.96;
|
||
transform: rotate(-6deg);
|
||
}
|
||
|
||
/* ===== 角标(随色系派生:白底 + 同色系深字 + 轻投,任意卡底色上都高对比)===== */
|
||
.ct__badge {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
padding: 6rpx 18rpx;
|
||
font-size: 18rpx;
|
||
font-weight: 700;
|
||
border-radius: 0 32rpx 0 22rpx;
|
||
background: var(--ct-badge-bg);
|
||
color: var(--ct-badge-fg);
|
||
box-shadow: 0 4rpx 10rpx rgba(107, 47, 15, 0.22);
|
||
}
|
||
.ct__badge--inline {
|
||
position: static;
|
||
align-self: center;
|
||
border-radius: 999rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
/* 子卡(tile)左上小 pill 角标(参考图 B):与右上贴边的 `.ct__badge` 区分开 */
|
||
.ct__badge--tl {
|
||
top: 12rpx;
|
||
left: 12rpx;
|
||
right: auto;
|
||
border-radius: 999rpx;
|
||
padding: 4rpx 14rpx;
|
||
font-size: 18rpx;
|
||
box-shadow: 0 4rpx 10rpx var(--ct-shadow);
|
||
}
|
||
/* 胶囊 CTA 按钮(参考图 C4):白底 + 同色系文字,浮在卡片上 */
|
||
.ct__cta {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6rpx;
|
||
margin-top: 10rpx;
|
||
padding: 10rpx 26rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 800;
|
||
background: rgba(255, 255, 255, 0.92);
|
||
color: var(--ct-accent);
|
||
box-shadow: 0 6rpx 14rpx var(--ct-shadow);
|
||
}
|
||
|
||
/* 行卡右端箭头 */
|
||
.ct__go {
|
||
flex-shrink: 0;
|
||
margin-left: 6rpx;
|
||
}
|
||
|
||
/* ============================== 版式 1 · iconTop 图标墙 ============================== */
|
||
.ct--iconTop {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
/* ============================== 版式 2 · iconLeft 行卡 ============================== */
|
||
.ct--iconLeft {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
.ct--iconLeft .ct__icon {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
/* ============================== 版式 3 · lead 通栏主卡 ============================== */
|
||
.ct--lead {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
padding: 30rpx 26rpx;
|
||
}
|
||
.ct__lead-main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10rpx;
|
||
}
|
||
.ct--lead .ct__title {
|
||
font-size: 34rpx;
|
||
/* 缺陷 D:主卡文案不可被右端插画位挤成逐字换行 → 单行 + 省略 */
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
max-width: 100%;
|
||
}
|
||
.ct__extra:empty {
|
||
display: none;
|
||
}
|
||
.ct__art--lead {
|
||
/* 缺陷 D:原为刚性 width:168rpx,窄屏/长名会把左侧文案挤到逐字换行。
|
||
改为可收缩(flex-basis auto + shrink 1)并保留 168rpx 上限,仅在宽裕时占满。 */
|
||
flex: 0 1 auto;
|
||
min-width: 0;
|
||
max-width: 168rpx;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* ============================== 版式 4 · duo 双列渐变卡 ==============================
|
||
强渐变底(色系饱和端点)+ 白字;皮肤仍叠加描边/阴影细微差异。
|
||
特异性 0,2,0(.ct.ct--duo)> 皮肤 0,1,0 → 版式底色不被主题皮肤覆盖。 */
|
||
.ct.ct--duo {
|
||
background:
|
||
radial-gradient(120% 92% at 8% -14%, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0) 60%),
|
||
linear-gradient(150deg, var(--ct-grad-from), var(--ct-grad-to));
|
||
align-items: flex-start;
|
||
padding: 28rpx 24rpx 26rpx;
|
||
}
|
||
.ct--duo .ct__title {
|
||
color: #ffffff;
|
||
/* 插画移到右下(参考图 C2)→ 文本右侧留白避让,防压字。
|
||
⚠️ 落到**真实存在的**文本节点(duo 分支刻意没有 .ct__body 包裹),别写永不匹配的选择器。 */
|
||
padding-right: 88rpx;
|
||
}
|
||
.ct--duo .ct__desc {
|
||
color: rgba(255, 255, 255, 0.88);
|
||
padding-right: 88rpx;
|
||
}
|
||
.ct__art--duo {
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
/* ============================== 版式 5 · group 分组容器卡 ==============================
|
||
白底容器 + 区块标题 + 右上「更多」角标 + 子卡网格(子卡由默认插槽渲染)。
|
||
特异性 0,2,0(.ct.ct--group)> 皮肤 0,1,0 → 恒为白底,不被主题皮肤染色。 */
|
||
.ct.ct--group {
|
||
background: #ffffff;
|
||
border: 1rpx solid rgba(255, 195, 155, 0.55);
|
||
/* 容器尺寸基准也走变量:与尺寸档同特异度时靠**源序**(尺寸档在后)让窄列尺寸档胜出(R-2) */
|
||
--ct-radius: 34rpx;
|
||
--ct-pad: 26rpx 24rpx 28rpx;
|
||
box-shadow: 0 3rpx 10rpx rgba(107, 47, 15, 0.05), 0 16rpx 38rpx rgba(107, 47, 15, 0.1);
|
||
gap: 20rpx;
|
||
align-items: stretch;
|
||
}
|
||
/* tinted 变体(可选):容器非纯白时用浅色 tint 底(参考图 A「容器通栏」的柔和变体) */
|
||
.ct.ct--group--tinted {
|
||
background:
|
||
radial-gradient(120% 92% at 8% -14%, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0) 62%),
|
||
var(--ct-tint);
|
||
border-color: var(--ct-line);
|
||
}
|
||
.ct__head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12rpx;
|
||
}
|
||
/* 悬浮胶囊标题(参考图 A):半透明白胶囊 + 轻模糊,浮在容器底色之上 */
|
||
.ct__headlead {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
min-width: 0;
|
||
background: rgba(255, 255, 255, 0.86);
|
||
border-radius: 999rpx;
|
||
padding: 4rpx 16rpx;
|
||
backdrop-filter: blur(6rpx);
|
||
}
|
||
.ct__headtitle {
|
||
font-size: 32rpx;
|
||
font-weight: 800;
|
||
color: #3d2a1e;
|
||
/* 缺陷 B 防御性加固:group 版式标题长期没有断行策略 → 窄列里会逐字竖排。
|
||
统一 nowrap + 省略号,即使将来再有人把窄列配成 group 也不会竖排。 */
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
max-width: 100%;
|
||
}
|
||
.ct__headmore {
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
color: #ff6b35;
|
||
padding: 4rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
background: #fff4ec;
|
||
border: 1rpx solid #ffd9c4;
|
||
}
|
||
/* 通栏标题行右胶囊(参考图 D,`.ct__headmore` 的泛化版):橙底白字,醒目 */
|
||
.ct__pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6rpx;
|
||
padding: 8rpx 22rpx;
|
||
border-radius: 999rpx;
|
||
background: linear-gradient(135deg, #ff9a4d, #fb4e14);
|
||
color: #ffffff;
|
||
font-weight: 700;
|
||
}
|
||
.ct__grid {
|
||
display: grid;
|
||
/* 铁律:`grid-template-columns` 只允许出现在 scoped class(禁止内联 repeat(),MP 不生效)。 */
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 16rpx;
|
||
}
|
||
/* 列数档:由调用方传入的 columns 决定(模板用 `:class="ct__grid--c<N>"`),彻底避免内联 repeat() */
|
||
.ct__grid--c1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||
.ct__grid--c2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
.ct__grid--c3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||
.ct__grid--c4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||
/* mosaic(参考图 A 容器内网格):2 列子卡 + 通栏行 `.ct__row`(跨整行) */
|
||
.ct__grid--mosaic {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 14rpx;
|
||
}
|
||
.ct__grid--mosaic > .ct__row {
|
||
grid-column: 1 / -1;
|
||
}
|
||
/* 通栏行子卡(参考图 A:mosaic 内跨整行的横条) */
|
||
.ct__row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
border-radius: 24rpx;
|
||
padding: 18rpx 20rpx;
|
||
background: var(--ct-tint);
|
||
}
|
||
|
||
/* ============================== 皮肤(skin)============================== */
|
||
/* porcelain:釉面——左上白色高光叠在双色渐变上 + 彩色环境投影 + 顶部内高光 */
|
||
.ct--skin-porcelain {
|
||
background:
|
||
radial-gradient(120% 92% at 8% -14%, rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0) 62%),
|
||
linear-gradient(140deg, var(--ct-from), var(--ct-to));
|
||
box-shadow:
|
||
inset 0 2rpx 8rpx rgba(255, 255, 255, 0.75),
|
||
inset 0 -10rpx 20rpx rgba(255, 255, 255, 0.32),
|
||
inset 0 0 0 1rpx rgba(255, 255, 255, 0.38),
|
||
0 4rpx 10rpx rgba(107, 47, 15, 0.06),
|
||
0 14rpx 30rpx var(--ct-shadow);
|
||
}
|
||
/* tint:淡彩平铺 + 同款釉面高光 + 彩色投影 */
|
||
.ct--skin-tint {
|
||
background:
|
||
radial-gradient(120% 92% at 8% -14%, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0) 62%),
|
||
var(--ct-tint);
|
||
box-shadow: 0 10rpx 24rpx var(--ct-shadow);
|
||
}
|
||
/* glass:半透明 + 1rpx 白描边 + 内高光 */
|
||
.ct--skin-glass {
|
||
background: rgba(255, 255, 255, 0.55);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.9);
|
||
box-shadow:
|
||
inset 0 2rpx 8rpx rgba(255, 255, 255, 0.85),
|
||
0 8rpx 22rpx rgba(107, 47, 15, 0.1);
|
||
backdrop-filter: blur(10rpx);
|
||
}
|
||
/* solid:饱和渐变 + 釉面高光 + 彩色投影(不是灰黑) */
|
||
.ct--skin-solid {
|
||
background:
|
||
radial-gradient(120% 92% at 8% -14%, rgba(255, 255, 255, 0.35), rgba(255, 255, 255, 0) 60%),
|
||
linear-gradient(140deg, var(--ct-grad-from), var(--ct-grad-to));
|
||
box-shadow: 0 6rpx 14rpx rgba(107, 47, 15, 0.1), 0 16rpx 32rpx var(--ct-shadow);
|
||
}
|
||
.ct--skin-solid .ct__title {
|
||
color: #ffffff;
|
||
}
|
||
.ct--skin-solid .ct__desc {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
.ct--skin-solid .ct__icon {
|
||
background: rgba(255, 255, 255, 0.26);
|
||
box-shadow: inset 0 2rpx 6rpx rgba(255, 255, 255, 0.4);
|
||
}
|
||
/* line:极简描边无底 */
|
||
.ct--skin-line {
|
||
background: transparent;
|
||
border: 2rpx solid var(--ct-line);
|
||
box-shadow: none;
|
||
}
|
||
.ct--skin-line .ct__icon {
|
||
background: var(--ct-tint);
|
||
}
|
||
|
||
/* ============================== 尺寸 ============================== */
|
||
/* 尺寸档提特异度为 `.ct.ct--sm`/`.ct.ct--lg`(0,2,0),与容器档 `.ct.ct--group`(0,2,0) 同级;
|
||
源序在后 → 窄列尺寸档胜出;圆角/内边距统一走 --ct-* 变量,彻底避免互相吞并(R-2 特异度连锁)。 */
|
||
.ct.ct--sm {
|
||
--ct-radius: 26rpx;
|
||
/* 左右内边距对齐 P3 前基线(.floor__tile 的 8rpx):4 列窄卡内容宽须容得下 5 个中文字 */
|
||
--ct-pad: 18rpx 8rpx 16rpx;
|
||
gap: 8rpx;
|
||
}
|
||
.ct--sm .ct__icon {
|
||
width: 68rpx;
|
||
height: 68rpx;
|
||
border-radius: 22rpx;
|
||
}
|
||
.ct--sm .ct__title {
|
||
font-size: 24rpx;
|
||
/* 窄列防竖排:基线 .floor__tile-name 同款策略(nowrap + 省略号),中文不再逐字断行 */
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
max-width: 100%;
|
||
}
|
||
.ct--sm .ct__desc {
|
||
font-size: 20rpx;
|
||
}
|
||
.ct.ct--lg {
|
||
--ct-radius: 36rpx;
|
||
--ct-pad: 32rpx 30rpx 30rpx;
|
||
gap: 12rpx;
|
||
}
|
||
.ct--lg .ct__icon {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 32rpx;
|
||
}
|
||
.ct--lg .ct__title {
|
||
font-size: 32rpx;
|
||
}
|
||
.ct--lg .ct__desc {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
/* ============================== 异形版式(只改形状/质感,内容走 iconTop 骨架)==============================
|
||
统一用 .ct.ct--* 双类(0,2,0),确保赢过 .ct 基础圆角与 .ct--sm/.ct--lg 的圆角。 */
|
||
/* 缺角卡:左上直角,其余大圆角 */
|
||
.ct.ct--notch {
|
||
border-radius: 0 44rpx 44rpx 44rpx;
|
||
}
|
||
/* 拱形卡:顶部高拱 */
|
||
.ct.ct--arch {
|
||
border-radius: 64rpx 64rpx 36rpx 36rpx / 96rpx 96rpx 36rpx 36rpx;
|
||
}
|
||
/* 叶形卡:对角异形 */
|
||
.ct.ct--leaf {
|
||
border-radius: 48rpx 10rpx 48rpx 10rpx;
|
||
}
|
||
/* 层叠卡:多层位移投影做出堆叠厚度(不用 z-index / 伪元素,规避小程序层叠风险) */
|
||
.ct.ct--stack {
|
||
border-radius: 36rpx;
|
||
box-shadow:
|
||
0 10rpx 0 -2rpx var(--ct-layer),
|
||
0 22rpx 0 -8rpx var(--ct-layer-soft),
|
||
0 30rpx 40rpx var(--ct-shadow);
|
||
}
|
||
|
||
/* ============================== 版式 6 · tile 瓷片子卡 ==============================
|
||
规格硬约束(§2 :99):**不新增渲染分支**,tile 走「else 分支的 iconTop 骨架 + 本修饰 class」。
|
||
角标左上 pill / 插画右下,均由模板按 effBadgePos / effArtPos 挂 `.ct__badge--tl` / `.ct__art--br`,
|
||
本类只负责「文案左对齐」等骨架差异。 */
|
||
.ct--tile {
|
||
align-items: flex-start;
|
||
}
|
||
.ct--tile .ct__title,
|
||
.ct--tile .ct__desc {
|
||
text-align: left;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* ============================== 未解锁 ============================== */
|
||
.ct--lock {
|
||
/* 强渐变底色本身饱和,0.72 几乎看不出灰化 → 调狠并略提亮,明显「未解锁」 */
|
||
filter: saturate(0.35) brightness(1.04);
|
||
}
|
||
.ct--lock .ct__title {
|
||
color: #bda794;
|
||
}
|
||
.ct--lock .ct__desc {
|
||
color: #c6b3a4;
|
||
}
|
||
.ct--lock .ct__icon {
|
||
background: #f3eee9;
|
||
box-shadow: none;
|
||
}
|
||
.ct--lock .ct__badge {
|
||
background: #f3eee9;
|
||
color: #bda794;
|
||
box-shadow: none;
|
||
}
|
||
</style>
|