@@ -1,748 +0,0 @@
< template >
< view >
< u-popup v-model = "localVisible" mode="bottom" :height="height" :closeable="true" @close="handleClose" >
< view class = "print-popup" >
<!-- 标题栏 -- >
< view class = "print-popup__header" >
< text class = "print-popup__title" > 标签打印 < / text >
< / view >
<!-- 隐藏画布 : 标签绘制 -- >
< canvas
:id = "canvasId"
:canvas-id = "canvasId"
type = "2d"
:style = "canvasStyles"
/ >
<!-- 隐藏画布 : 二维码生成 ( 独立 , 避免占用标签画布导致只导出左上角 ) -- >
< canvas
:id = "barcodeCanvasId"
:canvas-id = "barcodeCanvasId"
type = "2d"
:style = "barcodeCanvasStyles"
/ >
< scroll-view scroll -y = " true " class = "print-popup__body" >
<!-- 预览区域 -- >
< view class = "preview-section" v-if = "previewList.length > 0" >
< text class = "preview-section__title" > 打印预览 < / text >
< view class = "preview-section__list" >
< image
v-for = "item in previewList"
:src = "item.value"
:key = "item.key"
class = "preview-img"
mode = "widthFix"
/ >
< / view >
< / view >
<!-- 打印机选择 -- >
< view class = "setting-row" >
< text class = "setting-row__label" > 打印机 < / text >
< view class = "setting-row__right" >
< picker :value = "deviceIndex" :range = "deviceList" @change ="onDeviceChanged" range -key = " name " >
< view class = "setting-row__picker" >
< text
class = "setting-row__value"
: class = "{ 'setting-row__value--empty': !deviceList[deviceIndex]?.deviceId }"
>
{ { deviceList [ deviceIndex ] ? . name || '未检测到打印机' } }
< / text >
< u-icon name = "arrow-down" :size = "24" color = "#999" / >
< / view >
< / picker >
< / view >
< / view >
<!-- 出纸方向 -- >
< view class = "setting-row" >
< text class = "setting-row__label" > 出纸方向 < / text >
< view class = "setting-row__right" >
< picker :value = "rotationIndex" :range = "rotationList" @change ="onRotationChanged" range -key = " name " >
< view class = "setting-row__picker" >
< text class = "setting-row__value" > { { rotationList [ rotationIndex ] ? . name || '' } } < / text >
< u-icon name = "arrow-down" :size = "24" color = "#999" / >
< / view >
< / picker >
< / view >
< / view >
<!-- 纸张类型 -- >
< view class = "setting-row" >
< text class = "setting-row__label" > 纸张类型 < / text >
< view class = "setting-row__right" >
< picker :value = "gapIndex" :range = "gapList" @change ="onGapTypeChanged" range -key = " name " >
< view class = "setting-row__picker" >
< text class = "setting-row__value" > { { gapList [ gapIndex ] ? . name || '' } } < / text >
< u-icon name = "arrow-down" :size = "24" color = "#999" / >
< / view >
< / picker >
< / view >
< / view >
<!-- 打印浓度 -- >
< view class = "setting-row" >
< text class = "setting-row__label" > 打印浓度 < / text >
< view class = "setting-row__right" >
< picker :value = "darknessIndex" :range = "darknessList" @change ="onDarknessChanged" range -key = " name " >
< view class = "setting-row__picker" >
< text class = "setting-row__value" > { { darknessList [ darknessIndex ] ? . name || '' } } < / text >
< u-icon name = "arrow-down" :size = "24" color = "#999" / >
< / view >
< / picker >
< / view >
< / view >
<!-- 打印速度 -- >
< view class = "setting-row" >
< text class = "setting-row__label" > 打印速度 < / text >
< view class = "setting-row__right" >
< picker :value = "speedIndex" :range = "speedList" @change ="onPrintSpeedChanged" range -key = " name " >
< view class = "setting-row__picker" >
< text class = "setting-row__value" > { { speedList [ speedIndex ] ? . name || '' } } < / text >
< u-icon name = "arrow-down" :size = "24" color = "#999" / >
< / view >
< / picker >
< / view >
< / view >
<!-- 灰度阈值 -- >
< view class = "setting-row" >
< text class = "setting-row__label" > 灰度阈值 < / text >
< view class = "setting-row__right" >
< input class = "setting-row__input" type = "number" v-model = "threshold" / >
< / view >
< / view >
<!-- JSON配置模式 -- >
< view class = "setting-row" >
< text class = "setting-row__label" > JSON配置 < / text >
< view class = "setting-row__right" >
< switch :checked = "jsonMode" @change ="onPrintModeChanged" style = "transform: scale(0.75)" / >
< / view >
< / view >
< / scroll-view >
<!-- 底部操作按钮 -- >
< view class = "print-popup__footer" >
< view class = "footer-grid" >
< view class = "footer-btn" @click ="handleStartDiscovery" >
< u -icon name = "search" :size = "30" color = "#2979ff" / >
< text > 搜索打印机 < / text >
< / view >
< view class = "footer-btn" @click ="handleStopDiscovery" >
< u -icon name = "close-circle" :size = "30" color = "#f56c6c" / >
< text > 停止搜索 < / text >
< / view >
< view class = "footer-btn" @click ="handleOpenPrinter" >
< u -icon name = "link" :size = "30" color = "#2979ff" / >
< text > 连接打印机 < / text >
< / view >
< view class = "footer-btn" @click ="handleClosePrinter" >
< u -icon name = "minus-circle" :size = "30" color = "#f56c6c" / >
< text > 断开连接 < / text >
< / view >
< view class = "footer-btn footer-btn--print" @click ="handlePrintLabel" >
< u -icon v-if = "!printing" name="printer" :size="30" color="#fff" / >
< u-icon v-else name = "reload" :size = "30" color = "#fff" class = "spinning" / >
< text > { { printing ? '打印中...' : '打印' } } < / text >
< / view >
< / view >
< / view >
< / view >
< / u-popup >
< / view >
< / template >
< script setup >
import { ref , watch , onBeforeUnmount , nextTick , getCurrentInstance } from 'vue'
import {
DrawType ,
LPAPIFactory
} from '@/assets/js/js_sdk'
import '@/utils/bwipjs-polyfill.js'
import bwipjs from '@/scanpages/assets/bwip-js/bwip-js.mjs'
// ===== Props =====
const props = defineProps ( {
modelValue : { type : Boolean , default : false } ,
height : { type : String , default : '85%' } ,
labelWidth : { type : Number , default : 50 } ,
labelHeight : { type : Number , default : 30 } ,
margin : { type : Number , default : 2 } ,
barcodeText : { type : String , default : '12345678' } ,
barcodeTopText : { type : String , default : '' } ,
barcodeBottomText : { type : String , default : '' }
} )
const emit = defineEmits ( [ 'update:modelValue' , 'close' , 'printed' , 'error' ] )
const instance = getCurrentInstance ( )
// ===== 响应式数据 =====
const canvasId = 'lpapi-ble-popup-qr'
const barcodeCanvasId = 'lpapi-ble-barcode-qr'
const labelW = ref ( 960 )
const labelH = ref ( 960 )
const barcodeCanvasPx = ref ( 320 )
const canvasStyles = ref ( { } )
const barcodeCanvasStyles = ref ( { } )
const localVisible = ref ( false )
const deviceList = ref ( [ { name : '未检测到打印机' } ] )
const deviceIndex = ref ( 0 )
const rotationIndex = ref ( 1 )
const rotationList = [
{ name : '横向打印' , value : 0 } ,
{ name : '右转90度' , value : 90 } ,
{ name : '转转180度' , value : 180 } ,
{ name : '左转90度' , value : 270 }
]
const gapList = [
{ name : '随打印机设置' , value : 255 } ,
{ name : '小票纸' , value : 0 } ,
{ name : '不干胶' , value : 2 } ,
{ name : '卡纸' , value : 3 }
]
const gapIndex = ref ( 0 )
const darknessList = [
{ name : '随打印机设置' , value : 255 } ,
{ name : '6 (正常)' , value : 6 } ,
{ name : '7' , value : 7 } ,
{ name : '8' , value : 8 } ,
{ name : '9' , value : 9 } ,
{ name : '10 (较浓)' , value : 10 } ,
{ name : '11' , value : 11 } ,
{ name : '12' , value : 12 } ,
{ name : '13' , value : 13 } ,
{ name : '14' , value : 14 } ,
{ name : '15 (最浓)' , value : 15 }
]
const darknessIndex = ref ( 0 )
const speedList = [
{ name : '随打印机设置' , value : 255 } ,
{ name : '最慢' , value : 1 } ,
{ name : '较慢' , value : 2 } ,
{ name : '正常' , value : 3 } ,
{ name : '较快' , value : 4 } ,
{ name : '最快' , value : 5 }
]
const speedIndex = ref ( 0 )
const threshold = ref ( 128 )
const jsonMode = ref ( false )
const printing = ref ( false )
const isWeiXin = ref ( false )
const isAlipay = ref ( false )
const lpapi = ref ( undefined )
const context = ref ( undefined )
const previewList = ref ( [ ] )
// ===== 取值辅助 =====
const getDevice = ( ) => {
return deviceList . value [ deviceIndex . value ]
}
const getOrientation = ( ) => {
return rotationList [ rotationIndex . value ] ? . value || 0
}
const getGapType = ( ) => {
return gapList [ gapIndex . value ] ? . value || 255
}
const getPrintDarkness = ( ) => {
return darknessList [ darknessIndex . value ] ? . value || 255
}
const getPrintSpeed = ( ) => {
return speedList [ speedIndex . value ] ? . value || 255
}
const getThreshold = ( ) => {
return Number ( threshold . value )
}
// ===== Canvas 样式 =====
const updateCanvasStyle = ( ) => {
var pos = { position : 'absolute' }
if ( isWeiXin . value ) {
pos . left = '-999999rpx'
pos . top = '-999999rpx'
pos . opacity = '0'
pos . pointerEvents = 'none'
pos . zIndex = '-1'
} else {
pos . left = '-999999rpx'
pos . top = '-999999rpx'
}
canvasStyles . value = Object . assign (
{ width : labelW . value + 'px' , height : labelH . value + 'px' } ,
pos
)
barcodeCanvasStyles . value = Object . assign (
{
width : barcodeCanvasPx . value + 'px' ,
height : barcodeCanvasPx . value + 'px' ,
} ,
pos
)
}
// ===== Canvas 更新 =====
const updateCanvas = ( jobInfo ) => {
return new Promise ( ( resolve ) => {
if ( jobInfo && jobInfo . canvas ) {
labelW . value = jobInfo . canvas . width
labelH . value = jobInfo . canvas . height
updateCanvasStyle ( )
}
setTimeout ( ( ) => resolve ( true ) , 100 )
} )
}
// ===== 二维码生成(bwip-js qrcode,保持正方形不拉伸)=====
const _renderBwipjs = ( canvas , opts ) => {
try {
bwipjs . toCanvas ( canvas , opts )
} catch ( e ) {
if ( typeof canvas . getContext === 'function' ) {
var proto = Object . getPrototypeOf ( canvas )
var ctorName = proto && proto . constructor && proto . constructor . name
if ( ctorName && ! ( /HTMLCanvasElement|OffscreenCanvas/ ) . test ( ctorName ) ) {
try {
Object . defineProperty ( proto . constructor , 'name' , { configurable : true , value : 'HTMLCanvasElement' } )
bwipjs . toCanvas ( canvas , opts )
Object . defineProperty ( proto . constructor , 'name' , { configurable : true , value : ctorName } )
return
} catch ( e2 ) { }
}
}
throw e
}
}
/**
* 生成正方形二维码图片(使用独立画布,避免与标签画布尺寸冲突)
* @param {string} text
* @param {number} sizeMM 边长(毫米)
*/
const generateBarcodeImage = ( text , sizeMM ) => {
return new Promise ( ( resolve , reject ) => {
var dpi = 203
var pxPerMM = dpi / 25.4
var tw = Math . max ( 160 , Math . round ( sizeMM * pxPerMM ) )
barcodeCanvasPx . value = tw
updateCanvasStyle ( )
var baseScale = Math . max ( 4 , Math . floor ( tw / 33 ) )
var bwipOpts = {
bcid : 'qrcode' ,
text : text || '' ,
scale : baseScale ,
includetext : false ,
padding : 2
}
nextTick ( ( ) => {
setTimeout ( ( ) => {
var query = uni . createSelectorQuery ( ) . in ( instance )
query . select ( '#' + barcodeCanvasId ) . node ( ) . exec ( ( res ) => {
try {
if ( ! res || ! res [ 0 ] || ! res [ 0 ] . node ) {
return reject ( new Error ( '二维码 canvas 未找到' ) )
}
var canvas = res [ 0 ] . node
// 先固定位图尺寸,再交给 bwip 绘制(微信 2d canvas 不独立设置会沿用错误尺寸)
canvas . width = tw
canvas . height = tw
_renderBwipjs ( canvas , bwipOpts )
var outW = canvas . width || tw
var outH = canvas . height || tw
uni . canvasToTempFilePath ( {
canvas : canvas ,
x : 0 ,
y : 0 ,
width : outW ,
height : outH ,
destWidth : outW ,
destHeight : outH ,
success : ( t ) => {
if ( isWeiXin . value ) {
var img = canvas . createImage ( )
img . src = t . tempFilePath
img . onload = ( ) => { resolve ( img ) }
img . onerror = ( e ) => { reject ( e ) }
} else {
resolve ( t . tempFilePath )
}
} ,
fail : ( e ) => { reject ( e ) }
} )
} catch ( e ) { reject ( e ) }
} )
} , 50 )
} )
} )
}
/**
* 在可用区域内居中放置正方形二维码
* @param {number} areaX
* @param {number} areaY
* @param {number} areaW
* @param {number} areaH
*/
const getQrDrawRect = ( areaX , areaY , areaW , areaH ) => {
var size = Math . min ( areaW , areaH )
return {
x : areaX + ( areaW - size ) / 2 ,
y : areaY + ( areaH - size ) / 2 ,
size
}
}
// ===== 标签绘制(总入口) =====
const printLabel = ( isPreview ) => {
if ( jsonMode . value ) {
return printLabelWithJsonMode ( isPreview )
} else {
return printLabelWithDrawMode ( isPreview )
}
}
const printLabelWithDrawMode = async ( isPreview ) => {
var api = lpapi . value
var lW = props . labelWidth
var lH = props . labelHeight
var m = props . margin
var uW = lW - m * 2
var jobInfo = api . startJob ( {
context : context . value ,
width : lW ,
height : lH ,
orientation : getOrientation ( ) ,
isPreview : isPreview
} )
await updateCanvas ( jobInfo )
if ( props . barcodeTopText ) {
api . drawText ( {
text : props . barcodeTopText ,
x : m ,
y : m ,
width : uW ,
height : 5 ,
fontHeight : 4 ,
horizontalAlignment : 1
} )
}
var barcodeY = m + ( props . barcodeTopText ? 7 : 0 )
var barcodeH = lH - m * 2 - ( props . barcodeTopText ? 7 : 0 ) - ( props . barcodeBottomText ? 7 : 0 )
try {
var rect = getQrDrawRect ( m , barcodeY , uW , barcodeH )
var img = await generateBarcodeImage ( props . barcodeText , rect . size )
api . drawImage ( { img : img , x : rect . x , y : rect . y , width : rect . size , height : rect . size } )
} catch ( e ) {
console . error ( '二维码绘制失败:' , e )
}
if ( props . barcodeBottomText ) {
var by = lH - m - 5
api . drawText ( {
text : props . barcodeBottomText ,
x : m ,
y : by ,
width : uW ,
height : 5 ,
fontHeight : 3.5 ,
horizontalAlignment : 1
} )
}
return api . commitJob ( {
gapType : getGapType ( ) ,
printDarkness : getPrintDarkness ( ) ,
printSpeed : getPrintSpeed ( )
} )
}
const printLabelWithJsonMode = ( isPreview ) => {
var device = getDevice ( )
var lW = props . labelWidth
var lH = props . labelHeight
var m = props . margin
var uW = lW - m * 2
var jobPage = [ ]
if ( props . barcodeTopText ) {
jobPage . push ( {
type : DrawType . text ,
text : props . barcodeTopText ,
x : m ,
y : m ,
width : uW ,
height : 5 ,
fontHeight : 4 ,
horizontalAlignment : 1
} )
}
var barcodeY = m + ( props . barcodeTopText ? 7 : 0 )
var barcodeH = lH - m * 2 - ( props . barcodeTopText ? 7 : 0 ) - ( props . barcodeBottomText ? 7 : 0 )
var rect = getQrDrawRect ( m , barcodeY , uW , barcodeH )
return generateBarcodeImage ( props . barcodeText , rect . size ) . then ( ( img ) => {
jobPage . push ( {
type : DrawType . image ,
img : img ,
x : rect . x ,
y : rect . y ,
width : rect . size ,
height : rect . size
} )
if ( props . barcodeBottomText ) {
jobPage . push ( {
type : DrawType . text ,
text : props . barcodeBottomText ,
x : m ,
y : lH - m - 5 ,
width : uW ,
height : 5 ,
fontHeight : 3.5 ,
horizontalAlignment : 1
} )
}
return lpapi . value . print ( {
jobInfo : {
jobWidth : lW ,
jobHeight : lH ,
orientation : getOrientation ( ) ,
isPreview : isPreview
} ,
printerInfo : {
name : device ? device . name : undefined ,
deviceId : device ? device . deviceId : undefined
} ,
jobPage : jobPage ,
onJobCreated : ( jobInfo ) => {
return updateCanvas ( jobInfo )
} ,
onPageComplete : ( res ) => {
// 标签页面渲染完毕时的回调
} ,
onPagePrintComplete : ( res ) => {
// 标签页面打印完毕时的回调
}
} )
} )
}
// ===== 初始化 =====
const initPrinter = ( ) => {
// #ifdef MP-WEIXIN
isWeiXin . value = true
// #endif
// #ifdef MP-ALIPAY
isAlipay . value = true
// #endif
updateCanvasStyle ( )
lpapi . value = LPAPIFactory . getInstance ( { showLog : 4 } )
lpapi . value . startDiscovery ( {
timeout : 0 ,
deviceFound : ( devices ) => {
if ( devices && devices . length > 0 ) {
deviceList . value . splice ( 0 )
deviceList . value . push ( ... devices )
}
}
} )
if ( isWeiXin . value ) {
context . value = lpapi . value . createDrawContext ( )
} else {
context . value = lpapi . value . createDrawContext ( { canvasId : canvasId } )
}
if ( context . value ) {
lpapi . value . setDrawContext ( context . value )
handlePreviewLabel ( )
}
}
// ===== 选择器变更 =====
const onDeviceChanged = ( e ) => {
deviceIndex . value = e ? e . detail . value : 0
}
const onRotationChanged = ( e ) => {
rotationIndex . value = e ? e . detail . value : 0
}
const onGapTypeChanged = ( e ) => {
gapIndex . value = e ? e . detail . value : 0
}
const onDarknessChanged = ( e ) => {
darknessIndex . value = e ? e . detail . value : 0
}
const onPrintSpeedChanged = ( e ) => {
speedIndex . value = e ? e . detail . value : 0
}
const onPrintModeChanged = ( e ) => {
jsonMode . value = e . detail . value
handlePreviewLabel ( )
}
// ===== 打印机操作 =====
const handleStartDiscovery = ( ) => {
uni . showLoading ( { title : '正在搜索打印机...' } )
lpapi . value . startDiscovery ( {
timeout : 5000 ,
deviceFound : ( devices ) => {
if ( devices && devices . length > 0 ) {
deviceList . value . splice ( 0 )
deviceList . value . push ( ... devices )
}
} ,
adapterStateChange : ( result ) => {
if ( ! result . discovering ) {
uni . hideLoading ( )
}
}
} )
}
const handleStopDiscovery = ( ) => {
lpapi . value . stopDiscovery ( )
uni . hideLoading ( )
}
const handleOpenPrinter = ( ) => {
const currDevice = getDevice ( )
if ( ! currDevice || ! currDevice . deviceId ) {
uni . showToast ( { title : '未检测到打印机' , icon : 'none' } )
return
}
uni . showLoading ( { title : '正在连接打印机...' } )
lpapi . value . openPrinter ( {
name : currDevice . name ,
deviceId : currDevice . deviceId ,
tryTimes : isAlipay . value ? 1 : 5 ,
success : ( resp ) => {
console . log ( '---- 【打印机连接成功】' )
uni . hideLoading ( )
uni . showToast ( { title : '打印机连接成功!' , icon : 'success' } )
} ,
fail : ( resp ) => {
console . warn ( '---- 【打印机连接失败】:' , JSON . stringify ( resp ) )
uni . hideLoading ( )
uni . showToast ( { title : '打印机连接失败!' , icon : 'error' } )
}
} )
}
const handleClosePrinter = ( ) => {
console . log ( '---- 关闭打印机!' )
lpapi . value . closePrinter ( )
}
// ===== 预览 =====
const handlePreviewLabel = ( ) => {
previewList . value . splice ( 0 )
printLabel ( true ) . then ( ( res ) => {
if ( res . statusCode === 0 ) {
res . previewData . forEach ( ( v ) => {
previewList . value . push ( {
value : v ,
key : Date . now ( ) + '-' + Math . random ( ) * 1000
} )
} )
} else {
console . warn ( '预览失败:' , res )
}
} )
}
// ===== 打印 =====
const handlePrintLabel = async ( ) => {
printing . value = true
try {
var d = getDevice ( )
if ( ! d || ! d . deviceId ) {
printing . value = false
return uni . showToast ( { title : '未检测到打印机' , icon : 'none' } )
}
await new Promise ( ( resolve , reject ) => {
lpapi . value . openPrinter ( {
name : d . name ,
deviceId : d . deviceId ,
tryTimes : 5 ,
success : resolve ,
fail : reject
} )
} )
var res = await printLabel ( false )
if ( res . statusCode === 0 ) {
uni . showToast ( { title : '打印成功!' , icon : 'success' } )
emit ( 'printed' , { statusCode : 0 } )
} else {
uni . showToast ( { title : res . errMsg || '打印失败!' , icon : 'error' } )
emit ( 'error' , res )
}
} catch ( e ) {
console . error ( '打印异常:' , e )
uni . showToast ( { title : '打印机连接失败!' , icon : 'error' } )
emit ( 'error' , e )
} finally {
printing . value = false
}
}
// ===== 关闭弹窗 =====
const handleClose = ( ) => {
handleClosePrinter ( )
emit ( 'close' )
}
// ===== 监听器 =====
watch ( ( ) => props . modelValue , ( val ) => {
localVisible . value = val
if ( val ) nextTick ( ( ) => initPrinter ( ) )
} )
watch ( localVisible , ( val ) => {
emit ( 'update:modelValue' , val )
} )
onBeforeUnmount ( ( ) => {
if ( lpapi . value ) {
lpapi . value . closePrinter ( )
lpapi . value . stopDiscovery ( )
}
} )
< / script >
< style lang = "scss" scoped >
. print - popup { display : flex ; flex - direction : column ; height : 100 % ; background : # f5f6f8 ;
& _ _header { flex - shrink : 0 ; padding : 28 rpx 32 rpx 20 rpx ; text - align : center ; border - bottom : 1 rpx solid # e8ecf1 ; }
& _ _title { font - size : 32 rpx ; font - weight : 700 ; color : # 1 a1a2e ; }
& _ _body { flex : 1 ; overflow - y : auto ; overflow - x : hidden ; box - sizing : border - box ; padding : 20 rpx 24 rpx ; }
& _ _footer { flex - shrink : 0 ; padding : 12 rpx 24 rpx ; padding - bottom : calc ( 12 rpx + env ( safe - area - inset - bottom ) ) ; background : # fff ; border - top : 1 rpx solid # e8ecf1 ; }
}
. footer - grid { display : grid ; grid - template - columns : 1 fr 1 fr ; gap : 12 rpx ; }
. footer - btn { display : flex ; align - items : center ; justify - content : center ; gap : 8 rpx ; height : 76 rpx ; font - size : 26 rpx ; font - weight : 500 ; border - radius : 12 rpx ; background : # f5f6f8 ; color : # 1 a1a2e ; transition : transform .15 s , box - shadow .15 s ;
& : active { transform : scale ( .95 ) ; opacity : .85 ; }
& -- print { background : linear - gradient ( 135 deg , # 2979 ff , # 5 a9aff ) ; color : # fff ; box - shadow : 0 4 rpx 16 rpx rgba ( 41 , 121 , 255 , .35 ) ; grid - column : 1 / - 1 ; }
}
. spinning { animation : spin .8 s linear infinite ; }
@ keyframes spin { from { transform : rotate ( 0 deg ) ; } to { transform : rotate ( 360 deg ) ; } }
. setting - row { display : flex ; align - items : center ; justify - content : space - between ; box - sizing : border - box ; padding : 22 rpx 24 rpx ; margin - bottom : 12 rpx ; background : # fff ; border - radius : 12 rpx ;
& _ _label { font - size : 26 rpx ; color : # 6 b7c93 ; flex - shrink : 0 ; width : 160 rpx ; }
& _ _right { flex : 1 ; display : flex ; justify - content : flex - end ; }
& _ _picker { display : flex ; align - items : center ; gap : 8 rpx ; }
& _ _value { font - size : 26 rpx ; color : # 1 a1a2e ; font - weight : 500 ;
& -- empty { color : # b0bec5 ; }
}
& _ _input { flex : 1 ; font - size : 26 rpx ; color : # 1 a1a2e ; text - align : right ; padding : 4 rpx 0 ; }
}
. preview - section { margin - bottom : 24 rpx ; padding : 20 rpx ; background : # fff ; border - radius : 12 rpx ; overflow : hidden ;
& _ _title { display : block ; font - size : 26 rpx ; font - weight : 600 ; color : # 1 a1a2e ; margin - bottom : 16 rpx ; }
& _ _list { display : flex ; flex - wrap : wrap ; justify - content : center ; gap : 16 rpx ; }
}
. preview - img { width : 100 % ; border : 1 rpx dashed # ccc ; border - radius : 4 rpx ; }
< / style >