Files
lgp-wx-plus/components/theme/ThemeCanvasCard.vue
2026-08-20 19:15:41 +08:00

359 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- card_scheme 图层还原商品卡没有方案时走默认插槽 -->
<view v-if="!schemeLayers.length" class="tcc-fallback">
<slot />
</view>
<view v-else class="tcc" :style="shellStyle">
<view class="tcc-stage">
<view
v-for="layer in visibleLayers"
:key="layer.id"
class="tcc-layer"
:style="layerStyle(layer)"
>
<view
v-if="layer.type === 'shape'"
class="tcc-fill"
:style="{ background: fillOf(layer) }"
/>
<image
v-else-if="layer.type === 'photo' && photoOf(layer)"
class="tcc-photo"
:src="photoOf(layer)"
mode="aspectFill"
/>
<view
v-else-if="layer.type === 'photo'"
class="tcc-photo-ph"
:style="{ background: primary }"
/>
<text
v-else-if="layer.type === 'text'"
class="tcc-txt"
:style="{ color: textColor(layer), fontSize: fontSize(layer) }"
>
<text
v-for="(run, ri) in textRuns(layer)"
:key="ri"
:style="{ fontFamily: faceOf(layer, run.kind) }"
>{{ run.text }}</text>
</text>
<view
v-else-if="layer.type === 'deco'"
class="tcc-deco"
:class="'tcc-deco--' + (layer.asset || 'gold-corner')"
:style="{ color: toneOf(layer) }"
/>
</view>
</view>
</view>
</template>
<script>
import { getActiveLayout, getActiveSchemes, getActiveVars } from '@/utils/theme'
export default {
name: 'ThemeCanvasCard',
props: {
cover: { type: String, default: '' },
name: { type: String, default: '' },
price: { type: String, default: '' },
gallery: { type: String, default: '' },
scheme: { type: String, default: '' },
caption: { type: String, default: '' },
// 页码:没传 scheme 时读 pages.{page}.card_scheme
page: { type: String, default: '' },
// 列表/搜索原页面没有报价,画布里 bind=price 的图层也要跳过
showPrice: { type: Boolean, default: true },
},
computed: {
vars() {
return getActiveVars()
},
primary() {
return this.vars['--color-primary'] || '#B08D57'
},
schemeLayers() {
const layout = getActiveLayout() || {}
const pages = layout.pages || {}
const pageKey = this.page || ''
const pageScheme = pageKey && pages[pageKey] ? pages[pageKey].card_scheme : ''
const code = this.scheme || pageScheme || layout.card_scheme || ''
const map = getActiveSchemes()
const item = map[code]
return (item && item.layers) || []
},
visibleLayers() {
return this.schemeLayers
.filter((l) => l.visible !== false)
.filter((l) => this.showPrice || l.bind !== 'price')
.slice()
.sort((a, b) => (a.z || 0) - (b.z || 0))
},
shellStyle() {
return {
background: this.vars['--color-bg'] || '#FAF7F2',
}
},
},
methods: {
/**
* 画布坐标按 340×420 设计稿写成百分比,列变窄时图层一起缩小,不会撑破格子。
*/
layerStyle(layer) {
return {
left: (((layer.x || 0) / 340) * 100) + '%',
top: (((layer.y || 0) / 420) * 100) + '%',
width: (((layer.w || 40) / 340) * 100) + '%',
height: (((layer.h || 40) / 420) * 100) + '%',
transform: 'rotate(' + (layer.rotate || 0) + 'deg)',
zIndex: layer.z || 1,
}
},
tokenColor(name) {
const key = '--color-' + String(name || 'primary').replace(/_/g, '-')
return this.vars[key] || this.primary
},
fillOf(layer) {
const fill = layer.fill || 'surface'
if (String(fill).indexOf('#') === 0) return fill
return this.tokenColor(fill)
},
/** 配饰跟主题 token禁止纯黑兜底 */
toneOf(layer) {
return this.tokenColor(layer.tone || 'primary')
},
photoOf(layer) {
if (layer.bind === 'gallery') return this.gallery || this.cover
if (layer.src) return layer.src
return this.cover
},
textOf(layer) {
if (layer.bind === 'name') return this.name || '型号'
if (layer.bind === 'price') return this.price || ''
if (this.caption && (!layer.bind || layer.bind === 'custom')) return this.caption
return layer.text || ''
},
faceOf(layer, kind) {
const key = kind === 'zh' ? (layer.font_zh || 'sans') : (layer.font_en || 'sans')
const zh = {
sans: 'PingFang SC, Microsoft YaHei, sans-serif',
serif: 'Songti SC, Noto Serif SC, serif',
kai: 'Kaiti SC, STKaiti, serif',
script: 'Kaiti SC, STKaiti, serif',
}
const en = {
sans: '-apple-system, Helvetica, sans-serif',
serif: 'Georgia, Times New Roman, serif',
kai: 'Georgia, serif',
script: 'Snell Roundhand, Segoe Script, cursive',
}
return kind === 'zh' ? (zh[key] || zh.sans) : (en[key] || en.sans)
},
textRuns(layer) {
const raw = this.textOf(layer)
const parts = []
String(raw).split(/([\u3400-\u9fff]+)/).forEach((chunk) => {
if (!chunk) return
parts.push({ text: chunk, kind: /[\u3400-\u9fff]/.test(chunk) ? 'zh' : 'en' })
})
return parts.length ? parts : [{ text: raw, kind: 'en' }]
},
fontSize(layer) {
const map = { title: '32rpx', body: '28rpx', caption: '22rpx', price: '30rpx' }
return map[layer.font] || '28rpx'
},
textColor(layer) {
if (layer.font === 'price') return this.vars['--color-price'] || this.primary
return this.vars['--color-text'] || '#3F2F24'
},
},
}
</script>
<style lang="scss" scoped>
.tcc-fallback {
width: 100%;
}
.tcc {
position: relative;
width: 100%;
height: 0;
padding-bottom: 123.53%;
overflow: hidden;
}
.tcc-stage {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.tcc-layer {
position: absolute;
box-sizing: border-box;
}
.tcc-fill,
.tcc-photo,
.tcc-photo-ph,
.tcc-txt,
.tcc-deco {
width: 100%;
height: 100%;
}
.tcc-photo {
display: block;
}
.tcc-photo-ph {
opacity: 0.35;
}
.tcc-txt {
overflow: hidden;
line-height: 1.3;
white-space: nowrap;
}
.tcc-deco--paper-edge {
border: 2rpx dashed currentColor;
background: repeating-linear-gradient(-8deg, currentColor 0 2rpx, transparent 2rpx 10rpx);
box-shadow: inset 0 0 0 12rpx currentColor, inset 0 0 24rpx currentColor;
opacity: 0.28;
}
.tcc-deco--pearl-line,
.tcc-deco--folio-line,
.tcc-deco--double-frame {
box-shadow: inset 0 0 0 2rpx currentColor, inset 0 0 0 8rpx transparent, inset 0 0 0 12rpx currentColor;
background: linear-gradient(135deg, currentColor, transparent 42%);
opacity: 0.22;
}
.tcc-deco--walnut-frame {
border: 22rpx solid currentColor;
background:
repeating-linear-gradient(90deg, currentColor 0 5rpx, transparent 5rpx 14rpx),
repeating-linear-gradient(0deg, currentColor 0 2rpx, transparent 2rpx 18rpx);
box-shadow: inset 0 0 0 8rpx currentColor, inset 0 0 28rpx currentColor, 0 6rpx 0 currentColor;
opacity: 0.55;
}
.tcc-deco--gold-foil {
border: 3rpx solid currentColor;
background: linear-gradient(125deg, transparent, currentColor 28%, transparent 48%, currentColor 72%, transparent);
box-shadow: inset 0 0 0 10rpx currentColor, 0 2rpx 0 currentColor;
opacity: 0.52;
}
.tcc-deco--washi-1 {
background: currentColor;
opacity: 0.38;
}
.tcc-deco--botanical {
border-radius: 60% 40% 70% 30%;
background: currentColor;
opacity: 0.3;
}
.tcc-deco--museum-mat {
box-shadow: inset 0 0 0 28rpx currentColor;
opacity: 0.2;
}
.tcc-deco--wax-seal {
border-radius: 999rpx;
background:
radial-gradient(circle at 32% 28%, #fff 0 14%, currentColor 58%),
repeating-conic-gradient(from 16deg, currentColor 0 14deg, transparent 14deg 28deg);
box-shadow: inset 0 0 0 8rpx #fff, 0 8rpx 14rpx currentColor;
opacity: 0.9;
}
.tcc-deco--ribbon {
background: currentColor;
opacity: 0.48;
}
.tcc-deco--newsprint {
opacity: 0.12;
background: repeating-linear-gradient(-12deg, currentColor 0 2rpx, transparent 2rpx 10rpx);
}
.tcc-deco--gold-corner,
.tcc-deco--corner-bracket {
border: 2rpx solid currentColor;
opacity: 0.55;
}
.tcc-deco--handwriting {
border-bottom: 2rpx solid currentColor;
opacity: 0.5;
}
.tcc-deco--arch-mat {
border: 4rpx solid currentColor;
border-radius: 200rpx 200rpx 12rpx 12rpx;
opacity: 0.55;
}
.tcc-deco--inset-shadow {
box-shadow: inset 0 6rpx 20rpx currentColor;
opacity: 0.28;
}
.tcc-deco--torn-edge {
border: 2rpx solid currentColor;
opacity: 0.28;
}
.tcc-deco--film-sprocket {
border-left: 16rpx dotted currentColor;
border-right: 16rpx dotted currentColor;
box-shadow: inset 0 0 0 2rpx currentColor;
opacity: 0.55;
}
.tcc-deco--emboss-plate {
border: 2rpx solid currentColor;
opacity: 0.55;
}
.tcc-deco--circle-crop {
border: 6rpx solid currentColor;
border-radius: 999rpx;
opacity: 0.65;
}
.tcc-deco--vignette {
background: radial-gradient(ellipse at center, transparent 40%, currentColor 100%);
opacity: 0.28;
}
.tcc-deco--folio-number {
border: 3rpx solid currentColor;
box-shadow: inset 0 0 0 6rpx currentColor;
letter-spacing: 2rpx;
opacity: 0.7;
}
.tcc-deco--stamp-ring {
border: 4rpx dashed currentColor;
border-radius: 999rpx;
opacity: 0.5;
}
.tcc-deco--lace-corner {
background:
radial-gradient(circle at 0 0, currentColor 0 16rpx, transparent 17rpx),
radial-gradient(circle at 100% 0, currentColor 0 16rpx, transparent 17rpx),
radial-gradient(circle at 0 100%, currentColor 0 16rpx, transparent 17rpx),
radial-gradient(circle at 100% 100%, currentColor 0 16rpx, transparent 17rpx);
box-shadow: inset 0 0 0 2rpx currentColor, inset 0 0 0 12rpx transparent, inset 0 0 0 16rpx currentColor;
border-radius: 8rpx 32rpx;
opacity: 0.55;
}
.tcc-deco--thin-rule {
background: currentColor;
opacity: 0.65;
}
.tcc-deco--shadow-deck {
background: currentColor;
opacity: 0.16;
}
.tcc-deco--cap-rail {
background: currentColor;
opacity: 0.7;
}
.tcc-deco--gutter-fold {
background: currentColor;
opacity: 0.22;
}
.tcc-deco--pearl-bead {
border: 8rpx dotted currentColor;
border-radius: 24rpx;
opacity: 0.4;
}
.tcc-deco--vert-caption {
border-left: 2rpx solid currentColor;
opacity: 0.5;
}
</style>