1. 礼物特效
This commit is contained in:
@@ -608,6 +608,45 @@
|
||||
:uploading="uploading"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="照片展示焦点">
|
||||
<div
|
||||
v-if="posterCfg.heroImg"
|
||||
ref="heroFocusPreviewEl"
|
||||
class="hero-focus-preview hero-focus-preview--drag"
|
||||
:class="{ dragging: heroFocusDragging }"
|
||||
@pointerdown="onHeroFocusPointerDown"
|
||||
>
|
||||
<img
|
||||
:src="posterCfg.heroImg"
|
||||
alt=""
|
||||
draggable="false"
|
||||
:style="{ objectPosition: heroFocusCss(posterCfg) }"
|
||||
/>
|
||||
<span
|
||||
class="hero-focus-crosshair"
|
||||
:style="{ left: posterCfg.heroFocusX + '%', top: posterCfg.heroFocusY + '%' }"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="hero-focus-preview hero-focus-preview--empty">
|
||||
请先上传海报图片,再拖拽设置焦点
|
||||
</div>
|
||||
<div class="hero-focus-pad mt-2" :style="{ '--hero-focus-n': HERO_FOCUS_GRID }">
|
||||
<button
|
||||
v-for="pt in heroFocusGrid"
|
||||
:key="pt.key"
|
||||
type="button"
|
||||
class="hero-focus-cell"
|
||||
:class="{ active: isHeroFocusGridActive(pt) }"
|
||||
:title="`${Math.round(pt.x)}%, ${Math.round(pt.y)}%`"
|
||||
@click="setHeroFocus(pt.x, pt.y)"
|
||||
>
|
||||
<span class="hero-focus-dot" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-[11px] text-[#8A8680] mt-1">
|
||||
在预览上拖拽十字准星,或点下方 9×9 网格;当前 {{ Math.round(posterCfg.heroFocusX) }}% · {{ Math.round(posterCfg.heroFocusY) }}%
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="二维码中心图">
|
||||
<image-field
|
||||
v-model="posterCfg.qrCenterImg"
|
||||
@@ -1041,7 +1080,8 @@ import { GIFT_TYPES, emptyGiftCosts, DEFAULT_GIFT_COSTS } from '@/utils/giftType
|
||||
import { getSlotResizeSpecs, resizeSlotToFile, probeResizedMeta, metaHasFileSize } from '@/composables/resizeImage'
|
||||
import {
|
||||
defaultPosterBundle, normalizePosterBundle, normalizePosterConfig,
|
||||
LOADING_EFFECTS, ENTER_EFFECTS, POSTER_SIDES, POSTER_TEMPLATES, MOBILE_LAYOUTS, sideKey, posterSideMeta,
|
||||
LOADING_EFFECTS, ENTER_EFFECTS, POSTER_SIDES, POSTER_TEMPLATES, MOBILE_LAYOUTS,
|
||||
HERO_FOCUS_GRID, heroFocusGridPoints, clampHeroFocus, heroFocusCss, sideKey, posterSideMeta,
|
||||
} from '@/composables/posterDefaults'
|
||||
|
||||
echarts.use([PieChart, TooltipComponent, LegendComponent, CanvasRenderer])
|
||||
@@ -1147,6 +1187,58 @@ const uploadCfg = reactive(defaultUploadConfig())
|
||||
const posterSideTab = ref('1')
|
||||
const posterBundle = reactive(defaultPosterBundle())
|
||||
const posterCfg = computed(() => posterBundle[sideKey(posterSideTab.value)] || posterBundle['1'])
|
||||
|
||||
const heroFocusGrid = heroFocusGridPoints(HERO_FOCUS_GRID)
|
||||
const heroFocusPreviewEl = ref(null)
|
||||
const heroFocusDragging = ref(false)
|
||||
|
||||
function setHeroFocus(x, y) {
|
||||
const cfg = posterCfg.value
|
||||
if (!cfg) return
|
||||
cfg.heroFocusX = clampHeroFocus(x)
|
||||
cfg.heroFocusY = clampHeroFocus(y)
|
||||
}
|
||||
|
||||
function isHeroFocusGridActive(pt) {
|
||||
const cfg = posterCfg.value
|
||||
if (!cfg) return false
|
||||
return Math.abs(cfg.heroFocusX - pt.x) < 0.6
|
||||
&& Math.abs(cfg.heroFocusY - pt.y) < 0.6
|
||||
}
|
||||
|
||||
function heroFocusFromClient(clientX, clientY) {
|
||||
const el = heroFocusPreviewEl.value
|
||||
if (!el) return
|
||||
const rect = el.getBoundingClientRect()
|
||||
if (rect.width <= 0 || rect.height <= 0) return
|
||||
const x = ((clientX - rect.left) / rect.width) * 100
|
||||
const y = ((clientY - rect.top) / rect.height) * 100
|
||||
setHeroFocus(x, y)
|
||||
}
|
||||
|
||||
function onHeroFocusPointerMove(e) {
|
||||
if (!heroFocusDragging.value) return
|
||||
heroFocusFromClient(e.clientX, e.clientY)
|
||||
}
|
||||
|
||||
function onHeroFocusPointerUp() {
|
||||
if (!heroFocusDragging.value) return
|
||||
heroFocusDragging.value = false
|
||||
window.removeEventListener('pointermove', onHeroFocusPointerMove)
|
||||
window.removeEventListener('pointerup', onHeroFocusPointerUp)
|
||||
window.removeEventListener('pointercancel', onHeroFocusPointerUp)
|
||||
}
|
||||
|
||||
function onHeroFocusPointerDown(e) {
|
||||
if (!posterCfg.value?.heroImg) return
|
||||
e.preventDefault()
|
||||
heroFocusDragging.value = true
|
||||
heroFocusFromClient(e.clientX, e.clientY)
|
||||
window.addEventListener('pointermove', onHeroFocusPointerMove)
|
||||
window.addEventListener('pointerup', onHeroFocusPointerUp)
|
||||
window.addEventListener('pointercancel', onHeroFocusPointerUp)
|
||||
}
|
||||
|
||||
const posterMusicSelectOptions = computed(() => {
|
||||
const list = Array.isArray(posterMusicOptions.value) ? posterMusicOptions.value : []
|
||||
const opts = list
|
||||
@@ -2048,6 +2140,7 @@ onMounted(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', onResize)
|
||||
onHeroFocusPointerUp()
|
||||
disposeVisitCharts()
|
||||
})
|
||||
</script>
|
||||
@@ -2407,4 +2500,81 @@ onUnmounted(() => {
|
||||
width: 10px; height: 10px; border-radius: 9999px; flex-shrink: 0;
|
||||
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.hero-focus-pad {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--hero-focus-n, 9), 22px);
|
||||
gap: 4px;
|
||||
width: fit-content;
|
||||
padding: 8px;
|
||||
border-radius: 10px;
|
||||
background: #f7f4ef;
|
||||
border: 1px solid #e7e3db;
|
||||
}
|
||||
.hero-focus-cell {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ddd6cb;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
.hero-focus-cell:hover { border-color: #a88c6b; }
|
||||
.hero-focus-cell.active {
|
||||
border-color: #a88c6b;
|
||||
background: rgba(168, 140, 107, 0.14);
|
||||
}
|
||||
.hero-focus-dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 999px;
|
||||
background: #c4b8a4;
|
||||
}
|
||||
.hero-focus-cell.active .hero-focus-dot { background: #a88c6b; }
|
||||
.hero-focus-preview {
|
||||
position: relative;
|
||||
width: min(100%, 280px);
|
||||
aspect-ratio: 16 / 11;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e7e3db;
|
||||
background: #f0ebe3;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.hero-focus-preview--drag { cursor: crosshair; }
|
||||
.hero-focus-preview--drag.dragging { cursor: grabbing; }
|
||||
.hero-focus-preview--empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #8A8680;
|
||||
font-size: 12px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.hero-focus-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hero-focus-crosshair {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: -9px 0 0 -9px;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 0 0 1px rgba(168, 140, 107, 0.85), 0 1px 4px rgba(0, 0, 0, 0.25);
|
||||
background: rgba(168, 140, 107, 0.35);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user