feat: DesignPanel switches between front and back design

This commit is contained in:
Poker Design Developer
2026-06-03 22:48:14 +08:00
parent 1da35e4eab
commit ffc5e3d0fc

View File

@@ -1,5 +1,68 @@
<template> <template>
<div class="design-panel"> <div class="design-panel">
<!-- ===== 背面设计面板 ===== -->
<template v-if="isBack">
<section>
<h4>背面背景色</h4>
<div class="row">
<input type="color" :value="backDesign.background_color" @input="setBackBgColor($event.target.value)" />
<input type="text" :value="backDesign.background_color" @change="setBackBgColor($event.target.value)" />
</div>
</section>
<section>
<h4>背面边框</h4>
<div class="row">
<label class="mini-label">颜色</label>
<input type="color" :value="backDesign.border_color" @input="setBackDesign('border_color', $event.target.value)" />
<label class="mini-label">粗细</label>
<input type="number" min="0" max="40" :value="backDesign.border_width"
@change="setBackDesign('border_width', parseInt($event.target.value) || 0)" />
</div>
</section>
<section>
<h4>图案色调</h4>
<p class="hint">为背面图案叠加一层半透明色调</p>
<div class="row">
<input type="color" :value="backDesign.pattern_color || '#C0A050'"
@input="setBackDesign('pattern_color', $event.target.value)" />
<button v-if="backDesign.pattern_color" @click="setBackDesign('pattern_color', null)" class="mini ghost">清除色调</button>
</div>
</section>
<section>
<h4>背面图片</h4>
<div class="row">
<button @click="uploadBackImage" class="mini">上传背面图</button>
</div>
<input ref="backImageInput" type="file" accept="image/*" @change="onBackImageFile" hidden />
<div v-if="backDesign.image" class="row">
<img :src="backDesign.image" class="thumb" />
<button @click="setBackDesign('image', null)" class="mini ghost">清除</button>
</div>
</section>
<section v-if="backDesign.image">
<h4>图片位置微调</h4>
<p class="hint">拖动滑块微调素材图片的位置与缩放0 = 默认</p>
<div class="pip-row">
<label class="mini-label">dx</label>
<input type="range" min="-0.05" max="0.05" step="0.005"
:value="backImageOffsetVal('image_dx')" @input="setBackImageOffset('image_dx', $event.target.value)" />
<label class="mini-label">dy</label>
<input type="range" min="-0.05" max="0.05" step="0.005"
:value="backImageOffsetVal('image_dy')" @input="setBackImageOffset('image_dy', $event.target.value)" />
<label class="mini-label">缩放</label>
<input type="range" min="0.6" max="1.4" step="0.05"
:value="backImageOffsetVal('image_scale')" @input="setBackImageOffset('image_scale', $event.target.value)" />
</div>
<button @click="resetBackImageOffset" class="mini ghost">重置图片位置</button>
</section>
</template>
<!-- ===== 正面设计面板 ===== -->
<template v-else>
<!-- 背景色 --> <!-- 背景色 -->
<section> <section>
<h4>背景色</h4> <h4>背景色</h4>
@@ -91,7 +154,7 @@
</div> </div>
</section> </section>
<!-- 图片位置微调大小王 / 背面 --> <!-- 图片位置微调大小王 -->
<section v-if="showImageOffset"> <section v-if="showImageOffset">
<h4>图片位置微调</h4> <h4>图片位置微调</h4>
<p class="hint">拖动滑块微调素材图片的位置与缩放0 = 默认</p> <p class="hint">拖动滑块微调素材图片的位置与缩放0 = 默认</p>
@@ -132,6 +195,7 @@
</div> </div>
<button @click="resetLayout" class="mini ghost">重置本点数布局</button> <button @click="resetLayout" class="mini ghost">重置本点数布局</button>
</section> </section>
</template>
</div> </div>
</template> </template>
@@ -142,6 +206,9 @@ import { SUITS, SUIT_TEXT, LAYOUT_POSITIONS, isJoker } from '@/utils/cardLayout'
const store = useProjectStore() const store = useProjectStore()
const design = computed(() => store.effectiveDesign) const design = computed(() => store.effectiveDesign)
const backDesign = computed(() => store.effectiveBackDesign)
const isBack = computed(() => store.currentCard === 'back')
const override = computed(() => { const override = computed(() => {
if (!store.project || !store.currentCard) return null if (!store.project || !store.currentCard) return null
return (store.project.card_overrides || {})[store.currentCard] || null return (store.project.card_overrides || {})[store.currentCard] || null
@@ -152,6 +219,7 @@ const suits = SUITS
const suitSymbol = (s) => SUIT_TEXT[s] const suitSymbol = (s) => SUIT_TEXT[s]
const suitLabel = (s) => ({ spade: '黑桃', heart: '红桃', club: '梅花', diamond: '方块' })[s] const suitLabel = (s) => ({ spade: '黑桃', heart: '红桃', club: '梅花', diamond: '方块' })[s]
// ===== 正面相关 =====
const bgFileInput = ref(null) const bgFileInput = ref(null)
function uploadBg() { bgFileInput.value?.click() } function uploadBg() { bgFileInput.value?.click() }
function onBgFile(e) { function onBgFile(e) {
@@ -159,7 +227,6 @@ function onBgFile(e) {
if (!f) return if (!f) return
const reader = new FileReader() const reader = new FileReader()
reader.onload = (ev) => { reader.onload = (ev) => {
// 这里只把 dataURL 临时存到 design正式应上传到后端
store.patchDesign('background_image', ev.target.result) store.patchDesign('background_image', ev.target.result)
} }
reader.readAsDataURL(f) reader.readAsDataURL(f)
@@ -190,7 +257,7 @@ const isNumberCard = computed(() => {
}) })
const showImageOffset = computed(() => { const showImageOffset = computed(() => {
return isJoker(store.currentCard) || store.currentCard === 'back' return isJoker(store.currentCard)
}) })
const isFaceCard = computed(() => { const isFaceCard = computed(() => {
@@ -206,8 +273,6 @@ const showSymmetryMode = computed(() => {
const symmetryMode = computed(() => design.value.symmetry_mode || 'flip') const symmetryMode = computed(() => design.value.symmetry_mode || 'flip')
function setSymmetryMode(v) { function setSymmetryMode(v) {
// JQK 和大小王都存到 design 里(因为它们是单牌设置)
// 但 design 是合并后的,所以用 patchCardOverride 写到当前牌更干净
store.patchCardOverride(store.currentCard, 'symmetry_mode', v) store.patchCardOverride(store.currentCard, 'symmetry_mode', v)
} }
@@ -251,6 +316,36 @@ function setOffset(i, key, val) {
function resetLayout() { function resetLayout() {
store.resetNumberLayout(String(selectedRank.value)) store.resetNumberLayout(String(selectedRank.value))
} }
// ===== 背面相关 =====
const backImageInput = ref(null)
function uploadBackImage() { backImageInput.value?.click() }
function onBackImageFile(e) {
const f = e.target.files[0]
if (!f) return
const reader = new FileReader()
reader.onload = (ev) => {
store.patchBackDesign('image', ev.target.result)
}
reader.readAsDataURL(f)
}
function setBackBgColor(v) { store.patchBackDesign('background_color', v) }
function setBackDesign(path, v) { store.patchBackDesign(path, v) }
function backImageOffsetVal(key) {
const v = Number(backDesign.value[key])
if (Number.isNaN(v)) return key === 'image_scale' ? 1 : 0
return v
}
function setBackImageOffset(key, val) {
store.patchBackDesign(key, parseFloat(val))
}
function resetBackImageOffset() {
store.patchBackDesign('image_dx', 0)
store.patchBackDesign('image_dy', 0)
store.patchBackDesign('image_scale', 1)
}
</script> </script>
<style scoped> <style scoped>