359 lines
10 KiB
Vue
359 lines
10 KiB
Vue
<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>
|