@@ -1,137 +1,201 @@
< template >
< div class = "design-panel" >
<!-- 背景色 -- >
< section >
< h4 > 背景色 < / h4 >
< p class = "hint" > 整副牌默认用这个颜色 , 个别牌可在下面单独覆盖 < / p >
< div class = "row" >
< input type = "color" :value = "d esign.background_color" @input ="setBgColor($event.target.value)" / >
< input type = "text" :value = "d esign.background_color" @change ="setBgColor($event.target.value)" / >
< button @click ="uploadBg" class = "mini" > 上传背景图 < / button >
< / div >
< input ref = "bgFileInput" type = "file" accept = "image/*" @change ="onBgFile" hidden / >
< div v-if = "design.background_image" class="row" >
< img :src = "design.background_image" class = "thumb" / >
< button @click ="clearBgImage" class = "mini" > 清除 < / button >
< / div >
< / section >
<!-- === == 背面设计面板 === == -- >
< template v-if = "isBack" >
< section >
< h4 > 背面背景色 < / h4 >
< div class = "row" >
< input type = "color" :value = "backD esign.background_color" @input ="setBackB gColor($event.target.value)" / >
< input type = "text" :value = "backD esign.background_color" @change ="setBackB gColor($event.target.value)" / >
< / div >
< / section >
<!-- 边框 -- >
< section >
< h4 > 边框 < / h4 >
< div class = "row" >
< label class = "mini-label" > 颜色 < / label >
< input type = "color" :value = "design.border_color" @input ="setBorder('border_color', $event.target.value)" / >
< label class = "mini-la bel "> 粗细 < / label >
< input type = "number" min = "0" max = "40" :value = "d esign. border_width"
@change ="setBorder('border_width', parseInt($event.target.value) || 0)" / >
< / 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 = "num ber " min = "0" max = "40" :value = "backDesign.border_width"
@change ="setBackD esign(' border_width', parseInt($event.target.value) || 0)" / >
< / div >
< / section >
<!-- 花色符号 -- >
< section >
< h4 > 花色符号 < / h4 >
< p class = "hint" > 用系统字体显示一个 Unicode 符号 ; 想用图片可在右侧素材库上传后切换 < / p >
< div v-for = "s in suits" :key="s" class="suit-row" >
< div class = "suit-label" > { { suitSymbol ( s ) } } { { suitLabel ( s ) } } < / div >
< input type = "color" : value = "design.suit_symbols?.[s]?.color || '#000000'"
@input ="setSuit(s, 'color', $event.target.value)" / >
< select : value = "design.suit_symbols?.[s]?.type || 'text'"
@change ="setSuit(s, 'type', $event.target.value)" >
< option value = "text" > 字体符号 < / option >
< option value = "image" :disabled = "!design.suit_symbols?.[s]?.asset_id" > 图片素材 < / option >
< / select >
< / 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 >
< label class = "row" >
< span > 角标 < / span >
< input type = "range" min = "0.08" max = "0.20" step = "0.01"
:value = "d esign.corner_size_ratio"
@input ="setDesign('corner_size_ratio', parseFloat($event.target.value))" / >
< span class = "val" > { { Math . round ( design . corner _size _ratio * 100 ) } } % < / spa n>
< / label >
< label class = "row" >
< span > 中心花色 < / span >
< input type = "range" min = "0.08" max = "0.24" step = "0.01"
:value = "design.pip_size_ratio"
@input ="setDesign('pip_size_ratio', parseFloat($event.target.value))" / >
< span class = "val" > { { Math . round ( design . pip _size _ratio * 100 ) } } % < / span >
< / label >
< / 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 = "backD esign.image" class="row" >
< img :src = "backDesign.image" class = "thumb" / >
< button @click ="setBackDesign('image', null)" class = "mini ghost" > 清除 < / butto n>
< / div >
< / section >
<!-- 单牌覆盖 -- >
< section v-if = "canHaveOverride" >
< h4 > 本牌特殊设置 < / h4 >
< p class = "hint" > 只对当前选中的牌生效 < / p >
< div class = "row" >
< button @click ="setOverrideBg" class = "mini" > 设置独立背景色 < / button >
< button v-if = "hasOverride" @click="clearOverride" class="mini ghost" > 清除 < / button >
< / div >
< div v-if = "override?.background_color" class="row" >
< input type = "color" :value = "override.background_color"
@input ="patchOverride('background_color', $event.target.value)" / >
< input type = "text" :value = "override.background_color "
@change ="patchOverride('background_color ', $event.target.value)" / >
< / div >
< / secti on>
< section v-if = "backDesign.image" >
< h4 > 图片位置微调 < / h4 >
< p class = "hint" > 输入数值微调素材图片的位置与缩放 ( 0 = 默认 ) < / p >
< div class = "pip-row" >
< label class = "mini-label" > dx < / label >
< input type = "number" step = "0.005" min = "-0.05" max = "0.05"
:value = "backImageOffsetVal('image_dx')" @change ="setBackImageOffset('image_dx', $event.target.value)" / >
< label class = "mini-label" > dy < / label >
< input type = "number" step = "0.005" min = "-0.05" max = "0.05"
:value = "backImageOffsetVal('image_dy')" @change ="setBackImageOffset('image_dy', $event.target.value)" / >
< label class = "mini-label" > 缩放 < / label >
< input type = "number" step = "0.05" min = "0.6" max = "1.4 "
:value = "backImageOffsetVal('image_scale')" @change ="setBackImageOffset('image_scale ', $event.target.value)" / >
< / div >
< button @click ="resetBackImageOffset" class = "mini ghost" > 重置图片位置 < / butt on>
< / section >
< / template >
<!-- 对称方式 ( JQK / 大小王 ) -- >
< section v-if = "showSymmetryMode" >
<h4 > 对称方式 < / h4 >
< p class = "hint" > 下半身素材的生成方式 < / p >
< div class = "row" >
< select :value = "symmetryMode" @change ="setSymmetryMode($event.target.value)" >
< option value = "flip" > 垂直翻转 ( 左右不变 ) < / option >
< option value = "rotate" > 180 ° 旋转 ( 左右镜像 ) < / option >
< /select >
< / div >
< / section >
<!-- === == 正面设计面板 === == -- >
< template v-else >
<! - - 背景色 - - >
< section >
< h4 > 背景色 < / h4 >
< p class = "hint" > 整副牌默认用这个颜色 , 个别牌可在下面单独覆盖 < / p >
< div class = "row" >
< input type = "color" :value = "design.background_color" @input ="setBgColor($event.target.value)" / >
< input type = "text" :value = "design.background_color" @change ="setBgColor($event.target.value)" / >
< button @click ="uploadBg" class = "mini" > 上传背景图 < / button >
< / div >
< input ref = "bgFileInput" type = "file" accept = "image/*" @change ="onBgFile" hidden / >
< div v-if = "design.background_image" class="row" >
< img :src = "design.background_image" class = "thumb" / >
< button @click ="clearBgImage" class = "mini" > 清除 < / button >
< / div >
< / section >
<!-- 图片位置微调 ( 大小王 / 背面 ) -- >
< section v-if = "showImageOffset" >
< 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 = "imageOffsetVal('image_dx')" @input ="setImageOffset('image_dx', $event.target.value)" / >
< label class = "mini-label" > dy < / label >
< input type = "range" min = "-0.05" max = "0.05" step = "0.005"
:value = "imageOffsetVal('image_dy')" @input ="setImageOffset('image_dy', $event.target.value)" / >
< label class = "mini-label" > 缩放 < / label >
< input type = "range" min = "0.6" max = "1.4" step = "0.05"
:value = "imageOffsetVal('image_scale')" @input ="setImageOffset('image_scale', $event.target.value)" / >
< / div >
< button @click ="resetImageOffset" class = "mini ghost" > 重置图片位置 < / button >
< / section >
<!-- 边框 -- >
< section >
< h4 > 边框 < / h4 >
< div class = "row" >
< label class = "mini-label" > 颜色 < / label >
< input type = "color" :value = "design.border_color" @input ="setBorder('border_color', $event.target.value)" / >
< label class = "mini-label" > 粗细 < / label >
< input type = "number" min = "0" max = "40" :value = "design.border_width"
@change ="setBorder('border_width', parseInt($event.target.value) || 0)" / >
< / div >
< / section >
<!-- 数字牌花色位置微调 -- >
< section v-if = "isNumberCard" >
< h4 > 数字牌花色位置微调 < / h4 >
< p class = "hint" > 拖动滑块微调每个花色的位置 ( 占整张牌的比例 , 0 = 默认 ) < / p >
< div class = "rank-tabs" >
< button v-for = "r in [1,2,3,4,5,6,7,8,9,10]" :key="r"
: class = "{ active: selectedRank === r } "
@click ="selectedRank = r" > {{ r = = = 1 ? ' A ' : r }} < / button >
< / div >
< div v-for = "(pos, i) in positions" :key="i" class="pip-row ">
< div class = "pip-label" > # { { i + 1 } } < / div >
< label class = "mini-label "> dx < / label >
< input type = "range" min = "-0.05" max = "0.05" step = "0.005"
:value = "overrideFor(i).dx" @input ="setOffset(i, 'dx', $event.target.value)" / >
< label class = "mini-label" > dy < / label >
< input type = "range" min = "-0.05" max = "0.05" step = "0.005"
:value = "overrideFor(i).dy" @input ="setOffset(i, 'dy', $event.target.value)" / >
< label class = "mini-label" > 缩放 < / label >
< input type = "range" min = "0.6" max = "1.4" step = "0.05"
:value = "overrideFor(i).scale" @input ="setOffset(i, 'scale', $event.target.value)" / >
< / div >
< button @click ="resetLayout" class = "mini ghost" > 重置本点数布局 < / button >
< / section >
<!-- 花色符号 -- >
< section >
< h4 > 花色符号 < / h4 >
< p class = "hint" > 用系统字体显示一个 Unicode 符号 ; 想用图片可在右侧素材库上传后切换 < / p >
< div v-for = "s in suits" :key="s" class="suit-row" >
< div class = "suit-label" > { { suitSymbol ( s ) } } { { suitLabel ( s ) } } < / div >
< input type = "color" : value = "design.suit_symbols?.[s]?.color || '#000000' "
@input ="setSuit(s, 'color', $event.target.value)" / >
< select : value = "design.suit_symbols?.[s]?.type || 'text'"
@change ="setSuit(s, 'type', $event.target.value) " >
< option value = "text" > 字体符号 < / option >
< option value = "image" :disabled = "!design.suit_symbols?.[s]?.asset_id " > 图片素材 < / option >
< / select >
< / div >
< / section >
<!-- 尺寸 -- >
< section >
< h4 > 大小比例 < / h4 >
< label class = "row" >
< span > 角标 < / span >
< input type = "range" min = "0.08" max = "0.20" step = "0.01"
:value = "design.corner_size_ratio"
@input ="setDesign('corner_size_ratio', parseFloat($event.target.value))" / >
< span class = "val" > { { Math . round ( design . corner _size _ratio * 100 ) } } % < / span >
< / label >
< label class = "row" >
< span > 中心花色 < / span >
< input type = "range" min = "0.08" max = "0.24" step = "0.01"
:value = "design.pip_size_ratio"
@input ="setDesign('pip_size_ratio', parseFloat($event.target.value))" / >
< span class = "val" > { { Math . round ( design . pip _size _ratio * 100 ) } } % < / span >
< / label >
< / section >
<!-- 单牌覆盖 -- >
< section v-if = "canHaveOverride" >
< h4 > 本牌特殊设置 < / h4 >
< p class = "hint" > 只对当前选中的牌生效 < / p >
< div class = "row" >
< button @click ="setOverrideBg" class = "mini" > 设置独立背景色 < / button >
< button v-if = "hasOverride" @click="clearOverride" class="mini ghost" > 清除 < / button >
< / div >
< div v-if = "override?.background_color" class="row" >
< input type = "color" :value = "override.background_color"
@input ="patchOverride('background_color', $event.target.value)" / >
< input type = "text" :value = "override.background_color"
@change ="patchOverride('background_color', $event.target.value)" / >
< / div >
< / section >
<!-- 对称方式 ( JQK / 大小王 ) -- >
< section v-if = "showSymmetryMode" >
< h4 > 对称方式 < / h4 >
< p class = "hint" > 下半身素材的生成方式 < / p >
< div class = "row" >
< select :value = "symmetryMode" @change ="setSymmetryMode($event.target.value)" >
< option value = "flip" > 垂直翻转 ( 左右不变 ) < / option >
< option value = "rotate" > 180 ° 旋转 ( 左右镜像 ) < / option >
< / select >
< / div >
< / section >
<!-- 图片位置微调 ( 大小王 ) -- >
< section v-if = "showImageOffset" >
< h4 > 图片位置微调 < / h4 >
< p class = "hint" > 输入数值微调素材图片的位置与缩放 ( 0 = 默认 ) < / p >
< div class = "pip-row" >
< label class = "mini-label" > dx < / label >
< input type = "number" step = "0.005" min = "-0.05" max = "0.05"
:value = "imageOffsetVal('image_dx')" @change ="setImageOffset('image_dx', $event.target.value)" / >
< label class = "mini-label" > dy < / label >
< input type = "number" step = "0.005" min = "-0.05" max = "0.05"
:value = "imageOffsetVal('image_dy')" @change ="setImageOffset('image_dy', $event.target.value)" / >
< label class = "mini-label" > 缩放 < / label >
< input type = "number" step = "0.05" min = "0.6" max = "1.4"
:value = "imageOffsetVal('image_scale')" @change ="setImageOffset('image_scale', $event.target.value)" / >
< / div >
< button @click ="resetImageOffset" class = "mini ghost" > 重置图片位置 < / button >
< / section >
<!-- 数字牌花色位置微调 -- >
< section v-if = "isNumberCard" >
< h4 > 数字牌花色位置微调 < / h4 >
< p class = "hint" > 输入数值微调每个花色的位置 ( 占整张牌的比例 , 0 = 默认 ) < / p >
< div class = "rank-tabs" >
< button v-for = "r in [1,2,3,4,5,6,7,8,9,10]" :key="r"
: class = "{ active: selectedRank === r }"
@click ="selectedRank = r" > {{ r = = = 1 ? ' A ' : r }} < / button >
< / div >
< div v-for = "(pos, i) in positions" :key="i" class="pip-row" >
< div class = "pip-label" > # { { i + 1 } } < / div >
< label class = "mini-label" > dx < / label >
< input type = "number" step = "0.005" min = "-0.05" max = "0.05"
:value = "overrideFor(i).dx" @change ="setOffset(i, 'dx', $event.target.value)" / >
< label class = "mini-label" > dy < / label >
< input type = "number" step = "0.005" min = "-0.05" max = "0.05"
:value = "overrideFor(i).dy" @change ="setOffset(i, 'dy', $event.target.value)" / >
< label class = "mini-label" > 缩放 < / label >
< input type = "number" step = "0.05" min = "0.6" max = "1.4"
:value = "overrideFor(i).scale" @change ="setOffset(i, 'scale', $event.target.value)" / >
< / div >
< button @click ="resetLayout" class = "mini ghost" > 重置本点数布局 < / button >
< / section >
< / template >
< / div >
< / template >
@@ -142,6 +206,9 @@ import { SUITS, SUIT_TEXT, LAYOUT_POSITIONS, isJoker } from '@/utils/cardLayout'
const store = useProjectStore ( )
const design = computed ( ( ) => store . effectiveDesign )
const backDesign = computed ( ( ) => store . effectiveBackDesign )
const isBack = computed ( ( ) => store . currentCard === 'back' )
const override = computed ( ( ) => {
if ( ! store . project || ! store . currentCard ) return null
return ( store . project . card _overrides || { } ) [ store . currentCard ] || null
@@ -152,6 +219,7 @@ const suits = SUITS
const suitSymbol = ( s ) => SUIT _TEXT [ s ]
const suitLabel = ( s ) => ( { spade : '黑桃' , heart : '红桃' , club : '梅花' , diamond : '方块' } ) [ s ]
// ===== 正面相关 =====
const bgFileInput = ref ( null )
function uploadBg ( ) { bgFileInput . value ? . click ( ) }
function onBgFile ( e ) {
@@ -159,7 +227,6 @@ function onBgFile(e) {
if ( ! f ) return
const reader = new FileReader ( )
reader . onload = ( ev ) => {
// 这里只把 dataURL 临时存到 design; 正式应上传到后端
store . patchDesign ( 'background_image' , ev . target . result )
}
reader . readAsDataURL ( f )
@@ -190,7 +257,7 @@ const isNumberCard = computed(() => {
} )
const showImageOffset = computed ( ( ) => {
return isJoker ( store . currentCard ) || store . currentCard === 'back'
return isJoker ( store . currentCard )
} )
const isFaceCard = computed ( ( ) => {
@@ -206,8 +273,6 @@ const showSymmetryMode = computed(() => {
const symmetryMode = computed ( ( ) => design . value . symmetry _mode || 'flip' )
function setSymmetryMode ( v ) {
// JQK 和大小王都存到 design 里(因为它们是单牌设置)
// 但 design 是合并后的,所以用 patchCardOverride 写到当前牌更干净
store . patchCardOverride ( store . currentCard , 'symmetry_mode' , v )
}
@@ -251,6 +316,36 @@ function setOffset(i, key, val) {
function resetLayout ( ) {
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 >
< style scoped >