Files
product-uniapp/pages/publish/index.vue
T
2025-12-26 11:53:13 +08:00

1016 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="publish-page">
<uni-nav-bar leftText=" " :fixed="true" :border="false" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="样机发布"></uni-nav-bar>
<view class="form-card">
<view class="select-input gradient-bg position" @click="onSelectCategory()">
<view class="category-content">
<text class="category-text">{{form.category || '请输入/选择型号'}}</text>
</view>
<u-icon name="arrow-down" color="#3BC8BE" size="20"></u-icon>
<!-- 型号选择弹层 -->
<view v-if="showSelect1" class="select-popup">
<!-- 点击空白处关闭下拉 -->
<view class="popup-mask" @click="showSelect1=false"></view>
<view class="popup-panel" @click.stop>
<view class="popup-header">
<input class="popup-search" v-model="searchKey1" placeholder="搜索型号" />
<text class="popup-close" @click="showSelect1=false">取消</text>
</view>
<scroll-view scroll-y class="popup-list">
<view
v-for="item in filteredCategories"
:key="item.id"
class="popup-item"
@click.stop="selectCategory(item)"
>
{{ item.model }} ({{item.product_name}})
</view>
<view v-if="!filteredCategories.length" class="popup-empty">暂无数据</view>
</scroll-view>
</view>
</view>
</view>
<view class="conts mag-top">
<view class="">
<textarea v-model="form.content" class="content-textarea" placeholder="例如:西门子 超氧 十字门冰箱,门店展示样机,位于郑州市商都路品牌店,展示年限不足1年外观全新、配件齐全;"
:maxlength="maxWords" @input="handleContentInput" @focus="checkLogin()" />
</view>
<view class="media-upload-wrapper">
<uni-file-picker ref="mediaPicker" fileMediatype="image" limit="9" :disabled="!isLogin" @select="selectMedia" @delete="delfn" :imageStyles="imageStyles">
<view class="upload-icon-wrapper">
<image src="/static/image/photo.png" mode="" class="addimg"></image>
</view>
</uni-file-picker>
</view>
<view class="help-text">
<u-icon name="info-circle" size="18" color="#A6ABB7"></u-icon>
<text>请上传产品实物的正、侧、背多角度照片(禁止效果图),图片不多于9张</text>
</view>
</view>
<view class="mag-top">
<picker mode="selector" :range="conditionOptions" range-key="name" :disabled="!isLogin" @tap="checkLogin()" @change="onCategoryChange">
<view class="select-input gradient-bg">
<view class="category-content">
<text class="category-text">{{form.agename || '请选择样机新旧程度'}}</text>
</view>
<u-icon name="arrow-down" color="#3BC8BE" size="20"></u-icon>
</view>
</picker>
</view>
<view class="mag-top prices">
<input
class="uni-input"
type="digit"
:value="form.price"
placeholder="请输入折扣价格"
@input="onPriceInput"
@blur="onPriceBlur"
/>
<text>元</text>
</view>
<view class="mag-top">
<view style="margin-bottom: 10rpx;"><span style="color: red;">*</span> 是否包邮:</view>
<uni-data-checkbox v-model="form.is_free" :localdata="freeshipping" />
</view>
<view class="detils" v-if="skudetils!=''">
<view class="product-card">
<view class="title">产品名称:{{skudetils.product_name}}</view>
<view class="price-row">
<text class="spec-label">零售价:</text>
<text class="spec-value">{{ skudetils.price }}</text>
<!-- <text class="model">产品型号:{{ skudetils.model }}</text> -->
</view>
<view class="spec-row">
<text class="spec-label">品牌:</text>
<text class="spec-value">{{ skudetils.brand }}</text>
</view>
<view class="spec-row" v-for="(item, idx) in skudetils.spec_json" :key="idx">
<text class="spec-label">{{item.attr_name}}:</text>
<text class="spec-value">{{ item.attr_value }}</text>
</view>
</view>
<rich-text :nodes="productcontents"></rich-text>
</view>
</view>
<view class="foot">
<view class="agreement">
<label class="agree-label" @click="toggleAgreement">
<checkbox :checked="agree" style="transform:scale(0.8)"></checkbox>
<text>我已阅读并同意</text>
<text class="link" @click.stop="viewAgreement">《发布协议》</text>
</label>
<button class="primary-btn gradient-btn" :disabled="userinfo.is_release!=1" @click="submitForm">立即发布</button>
</view>
</view>
<u-popup :show="show" :closeOnClickOverlay="false" :round="10" mode="center">
<view class="cont-text" >
<rich-text :nodes="viewAgreementcont"></rich-text>
</view>
<view class="cents">
<button class="btns1" @click="show = !show">取消</button>
<button class="btns" @click="agreementbtn">确定</button>
</view>
</u-popup>
<u-popup :show="showlogin" :closeOnClickOverlay="false" :round="20" mode="center">
<view class="popupcontent">
<image class="log1" src="/static/image/log1.png"></image>
<view class="title">您还未登录!登录后方可发布内容!</view>
<view class="yhxy">
<checkbox-group name="" style="width: 28px;display: inline-block;">
<label @tap="selfChangde()">
<checkbox :checked="selfChecked" activeBackgroundColor="#3F435B"
style="transform:scale(0.7);border-radius: 50%" />
</label>
</checkbox-group>
{{$t('home.argin')}}<text class="xy" @click="gouser">{{$t('home.UserAgreement')}}</text>{{$t('home.and')}}<text
class="xy" @click="goprivacy">{{$t('home.Privacy')}}</text>
</view>
</view>
<view class="cent">
<button class="btn1" @click="showlogin = !showlogin">{{$t('cancel')}}</button>
<button v-if="selfChecked" class="btn" open-type="getPhoneNumber" @click="showlogin = !showlogin"
@getphonenumber="getPhoneNumber">{{$t('login')}}</button>
<button v-else class="btn" @click="loginbtn">{{$t('login')}}</button>
</view>
</u-popup>
</view>
</template>
<script>
import { request,showToast } from '@/api/apiT.js'
export default {
name: 'PublishPage',
data(){
return {
showSelect1:false,
showSelect2:false,
searchKey1:'',
productcontents:'',
userinfo:'',
skudetils:'',
// 单选数据源
freeshipping: [ {
text: '全国包邮',
value: 1
}, {
text: '省内包邮',
value: 2
}, {
text: '邮费电联',
value: 3
}],
categoryOptions:[],
conditionOptions:[
{name:'全新(未开箱)',value:'10'},
{name:'95%新(开箱无瑕)',value:'9'},
{name:'90%新(外观轻损<2处)',value:'8'},
{name:'85%新(外观轻损<3处)',value:'7'},
{name:'75%新(外观轻损<5处)',value:'6'},
{name:'70%新(外观轻损<6处)',value:'5'},
{name:'60%新(外观轻损<7处)',value:'4'},
{name:'不足60%新(外观损伤>7处)',value:'3'}
],
form:{
agename:'',
age:'',
category:'',
sku_id:'',
content:'',
media:[],
price:'',
is_free:''
},
selfChecked: false,
showlogin:false,
viewAgreementcont:'',
iconurl:'',
maxWords:2000,
maxMedia:9,
agree:false,
show:false,
}
},
computed:{
isLogin() {
return !!uni.getStorageSync('is_login')
},
canSubmit(){
return this.agree && this.form.content.trim().length>0 && this.form.media.length>0
},
filteredCategories(){
if(!this.searchKey1) return this.categoryOptions
return this.categoryOptions.filter(i => (i.model || '').toLowerCase().includes(this.searchKey1.toLowerCase()))
}
},
onLoad() {
this.getinfo()
},
onShow() {
// this.getset()
// this.syncTabbar()
},
methods:{
getinfo(){
request({
url:'common/user/info',
method:'POST',
}).then(res => {
this.userinfo=res.data.data.user
uni.setStorageSync('is_release', res.data.data.user.is_release);
})
},
onSelectCategory() {
if (!uni.getStorageSync('is_login')) {
this.showlogin = true
return
}
this.getcategory()
this.showSelect1 = true
},
checkLogin(cb) {
if (!this.isLogin) {
this.showlogin = true
return
}
if (typeof cb === 'function') {
cb()
}
},
getlocation(){
wx.getFuzzyLocation({
type: 'wgs84',
success(res) {
console.log("模糊定位:", res)
},
fail(err) {
console.log("模糊定位失败:", err)
}
})
},
// 处理富文本HTML,让图片自适应
processRichText(html) {
if (!html) return ''
// 使用正则表达式给所有 img 标签添加样式
return html.replace(/<img([^>]*)>/gi, (match, attrs) => {
// 检查是否已有 style 属性
if (attrs.includes('style=')) {
// 如果已有 style,添加 max-width
return match.replace(/style=["']([^"']*)["']/i, (styleMatch, styleContent) => {
// 移除已有的 max-width 和 width
let newStyle = styleContent.replace(/max-width\s*:\s*[^;]+;?/gi, '').replace(/width\s*:\s*[^;]+;?/gi, '')
newStyle = newStyle.trim()
if (newStyle && !newStyle.endsWith(';')) {
newStyle += ';'
}
newStyle += 'max-width: 100%; height: auto;'
return `style="${newStyle}"`
})
} else {
// 如果没有 style,添加 style 属性
return `<img${attrs} style="max-width: 100%; height: auto;">`
}
})
},
selectCategory(item){
this.skudetils = item
this.form.category = item.model || ''
this.form.sku_id = item.id || ''
// 处理富文本,让图片自适应
if(item.desc !=''&& item.desc !=null){
this.productcontents = this.processRichText(item.desc || '')
console.log(this.productcontents)
}
this.showSelect1 = false
},
goprivacy(){
uni.navigateTo({
url: '/pages/login/agreement',// 替换为你要跳转的页面路径
});
},
gouser(){
uni.navigateTo({
url: '/pages/login/agreementuser',// 替换为你要跳转的页面路径
});
},
selfChangde(e) {
this.selfChecked = !this.selfChecked;
},
loginbtn(){
if(!this.selfChecked){
uni.showToast({
title:this.$t('home.arginimg'),
icon: 'none'
});
return false
}
},
getPhoneNumber(e) {
let that = this;
request({
url:'miniapp/phone',
method:'POST',
data:{
phone_code: e.detail.code,
}
}).then(res => {
console.log(res)
if (res.data.code == -1) {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
} else if (res.data.code == 200) {
uni.showToast({
title: '登录成功',
icon: 'none'
});
//返回数据接收成功
uni.setStorageSync('is_login', true);
}
})
},
//商品下拉
getcategory(){
request({
url:'common/sku/preset',
method:'GET'
}).then(res => {
if (res.data.code == 200) {
this.categoryOptions = res.data.data|| [];
}
})
},
syncTabbar(){
uni.setTabBarItem({ index:0, text:this.$t('tabbar.home') })
uni.setTabBarItem({ index:1, text:this.$t('tabbar.square') })
uni.setTabBarItem({ index:2, text:this.$t('tabbar.publish') })
uni.setTabBarItem({ index:3, text:this.$t('tabbar.mine') })
},
goBack(){
uni.navigateBack()
},
onCategoryChange(e){
console.log(e)
const index = e.detail.value; // picker 返回的 index
const selectCity = this.conditionOptions[index]; // 直接取整条数据
this.form.agename = selectCity.name
this.form.age = selectCity.name
},
handleContentInput(e){
if(e.detail.value.length>this.maxWords){
this.form.content = e.detail.value.slice(0,this.maxWords)
}
},
sanitize(v) {
v = String(v || '');
v = v.replace(/[^\d.]/g, ''); // 只留数字和点
const i = v.indexOf('.');
if (i !== -1) {
v = v.slice(0, i + 1) + v.slice(i + 1).replace(/\./g, '');
}
return v;
},
onPriceInput(e) {
const v = this.sanitize(e.detail.value);
// 关键:先更新数据,再 nextTick 强制触发视图回写
if (v !== this.form.price) {
this.form.price = v;
this.$nextTick(() => {
// 再写一次,逼迫输入框用受控值覆盖(对合成态更稳)
this.form.price = v;
});
}
},
onPriceBlur(e) {
// 兜底:失焦时彻底清洗,保证最终不会残留中文
const v = this.sanitize(e.detail.value);
if (v !== this.form.price) this.form.price = v;
},
uploadfn(imgUrl){
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://bsh.info.yw.hnzhwlkj.cn/upload/image?scene=postlist',
filePath: imgUrl,
name: 'image',
header:{'content-type': 'application/x-www-form-urlencoded','Fdesk-Auth-Token': 'Bearer ' + uni.getStorageSync('token'),'Fdesk-Language': uni.getStorageSync('i18locale')?uni.getStorageSync('i18locale'):'zh','Fdesk-Login-Type': 'wechat','Fdesk-System-Code':'wechat'},
success: (uploadFileRes) => {
const res = JSON.parse(uploadFileRes.data || '{}')
if(res && res.data && res.data.href){
resolve(res.data.href)
}else{
reject('上传失败')
}
},
fail: (err) => reject(err)
});
})
},
selectMedia(e){
const allowCount = this.maxMedia - this.form.media.length
if(allowCount <= 0){
uni.showToast({
title:`最多只能上传${this.maxMedia}张图片`,
icon:'none'
})
return
}
const tempFilePaths = e.tempFilePaths || (e.tempFiles || []).map(item => item.path || item.url)
const paths = tempFilePaths.slice(0, allowCount)
if(paths.length === 0){
return
}
Promise.all(paths.map(p => this.uploadfn(p))).then(urls => {
urls.forEach(url => {
this.form.media.push(url)
})
}).catch(() => {
uni.showToast({
title:'图片上传失败,请重试',
icon:'none'
})
})
},
delfn(e){
this.form.media.splice(e.index, 1)
},
deleteMedia(index){
this.form.media.splice(index, 1)
},
toggleAgreement(){
this.agree = !this.agree
},
viewAgreement(){
request({
url:'common/agreement?type=release_agreement',
method:'GET'
}).then(res => {
if (res.data.code == 200) {
this.viewAgreementcont = res.data.data.content
this.show = true
}
})
},
agreementbtn(){
this.agree = true
this.show = false
},
reset(){
this.form.agename='';
this.form.age='';
this.form.category='';
this.form.sku_id='';
this.form.content='';
this.form.media=[];
this.form.price='';
this.form.is_free='';
this.productcontents=''
// 同步清理文件选择器内部的文件列表
this.$nextTick(() => {
if(this.$refs.mediaPicker && this.$refs.mediaPicker.clearFiles){
this.$refs.mediaPicker.clearFiles()
}
})
},
submitForm(){
// 必填校验
if(!this.form.category || !this.form.sku_id){
uni.showToast({
title: '请选择型号',
icon: 'none'
});
return
}
if(!this.form.content || !this.form.content.trim()){
uni.showToast({
title: '请输入文字内容',
icon: 'none'
});
return
}
if(!this.form.agename || !this.form.age){
uni.showToast({
title: '请选择样机新旧程度',
icon: 'none'
});
return
}
if(!this.form.price || !this.form.price.trim()){
uni.showToast({
title: '请输入折扣价格',
icon: 'none'
});
return
}
if(this.form.is_free==''){
uni.showToast({
title: '请选择包邮方式',
icon: 'none'
});
return
}
if(!this.form.media || this.form.media.length === 0){
uni.showToast({
title: '请至少上传一张图片',
icon: 'none'
});
return
}
if(!this.agree){
uni.showToast({
title: '请先同意发布协议!',
icon: 'none'
});
return
}
if(uni.getStorageSync('is_login')){
request({
url:'common/post/store',
method:'post',
data:this.form
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
if(res.data.code ==200){
this.reset()
setTimeout(() => {
uni.navigateTo({
url: '/pages/mine/my_favorites',// 替换为你要跳转的页面路径
});
}, 1000);
}
})
}else{
this.showlogin = true
}
}
}
}
</script>
<style lang="scss">
.prices{
display: flex;
align-items: center;
gap: 10px;
}
.detils{
margin-top: 40rpx;
overflow: hidden;
word-wrap: break-word;
word-break: break-all;
}
// 确保富文本中的图片自适应
.rich-wrap{
width: 100%;
overflow: hidden;
}
.cent{
display: flex;
align-items: center;
justify-content: space-around;
padding: 0px 10px 20px;
.btn1{
background-color: #fff;
border: 1px solid #206ff4;
color: #206ff4;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 40px;
line-height: 40px;
}
.btn{
background-color: #206ff4;
color: white;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 40px;
line-height: 40px;
}
}
.custom-navbar{
position: fixed;
top: 0;
left: 0;
right: 0;
height: calc(var(--status-bar-height) + 88rpx);
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--status-bar-height) 30rpx 0;
z-index: 999;
border-bottom: 1rpx solid #F0F0F0;
}
.form-card{
background: #fff;
padding: 30rpx;
margin-bottom: 400rpx;
box-shadow: 0 12rpx 30rpx rgba(0,0,0,0.04);
}
.section-label{
font-size: 30rpx;
color: #2F323A;
margin-bottom: 20rpx;
font-weight: 500;
}
.select-input{
height: 88rpx;
border-radius: 12rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.gradient-bg{
background: #F4F4F4;
color: #999999;
}
.conts{
background: #F4F4F4;
color: #999999;
padding: 20rpx;
border-radius: 10rpx;
}
.uni-input{
background: #F4F4F4;
padding: 20rpx 25rpx;
border-radius: 10rpx;
}
.category-content{
display: flex;
align-items: center;
flex: 1;
}
.fridge-icon{
width: 48rpx;
height: 48rpx;
margin-right: 16rpx;
position: relative;
}
.fridge-icon image{
width: 20px;
height: 25px;
}
.fridge-body{
width: 32rpx;
height: 40rpx;
border: 2rpx solid #fff;
border-radius: 4rpx;
position: absolute;
left: 8rpx;
top: 4rpx;
}
.fridge-line{
width: 28rpx;
height: 2rpx;
background: #fff;
position: absolute;
left: 10rpx;
top: 18rpx;
}
.fridge-line2{
width: 28rpx;
height: 2rpx;
background: #fff;
position: absolute;
left: 10rpx;
top: 28rpx;
}
.category-text{
font-size: 28rpx;
color: #999999;
font-weight: 500;
}
.mag-top{
margin-top: 15px;
}
.free{
display: flex;
align-items: center;
gap: 10px;
}
.content-textarea{
width: 100%;
// background: #F7F8FB;
border-radius: 16rpx;
max-height: 160rpx;
padding: 24rpx 0px;
font-size: 28rpx;
color: #2F323A;
box-sizing: border-box;
}
.media-upload-wrapper{
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.upload-area{
width: 200rpx;
height: 200rpx;
background: #F7F8FB;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.upload-icon-wrapper{
display: flex;
flex-direction: column;
align-items: center;
}
.camera-icon{
position: relative;
margin-bottom: 12rpx;
}
.addimg{
width: 215rpx;
height: 215rpx;
}
.upload-text{
font-size: 26rpx;
color: #3F75FF;
}
.media-preview{
width: 200rpx;
height: 200rpx;
border-radius: 16rpx;
overflow: hidden;
position: relative;
}
.preview-image{
width: 100%;
height: 100%;
}
.delete-btn{
position: absolute;
top: 8rpx;
right: 8rpx;
width: 48rpx;
height: 48rpx;
background: rgba(0,0,0,0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.help-text{
font-size: 24rpx;
color: #A6ABB7;
margin-top: 40rpx;
display: flex;
align-items: center;
gap: 8rpx;
}
.foot{
background: #fff;
position: fixed;
bottom: 0;
width: 100%;
z-index: 11;
}
.agreement{
margin: 20px 12px;
}
.agree-label{
display: flex;
align-items: center;
font-size: 26rpx;
color: #6A6E7F;
}
.agree-label text{
margin-right: 6rpx;
}
.agree-label .link{
color: #51B6FF;
}
.primary-btn{
color: #fff;
border-radius: 20rpx;
height: 88rpx;
line-height: 88rpx;
font-size: 32rpx;
border: none;
}
.gradient-btn{
background: #3BC8BE;
margin-top: 20rpx;
}
.position{
position: relative;
}
.select-popup{
position: absolute;
left: 0;
right: 0;
top: 100%; /* 紧贴选择框下方 */
z-index: 1000;
}
.popup-panel{
position: absolute;
left: 0;
right: 0;
top: 8rpx; /* 与选择框留一点间距 */
background: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
border-bottom-left-radius: 20rpx;
border-bottom-right-radius: 20rpx;
max-height: 40vh; /* 控制整体可视高度,下拉列表形式 */
display: flex;
flex-direction: column;
padding: 20rpx;
box-sizing: border-box;
z-index: 1001;
}
.popup-header{
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.popup-search{
flex: 1;
height: 72rpx;
border-radius: 10rpx;
background: #f4f4f4;
padding: 0 20rpx;
font-size: 28rpx;
color: #333;
margin-right: 12rpx;
box-sizing: border-box;
}
.popup-mask{
position: fixed;
inset: 0;
background: transparent;
}
.popup-title{
font-size: 28rpx;
color: #333;
}
.popup-close{
font-size: 28rpx;
color: #3BC8BE;
}
.popup-list{
flex: 1;
max-height: calc(40vh - 140rpx); /* 预留头部高度,保证可滚动 */
}
.popup-item{
padding: 20rpx 12rpx;
border-bottom: 1px solid #f0f0f0;
font-size: 28rpx;
color: #333;
}
.popup-item:last-child{
border-bottom: none;
}
.popup-empty{
text-align: center;
color: #999;
font-size: 26rpx;
padding: 30rpx 0;
}
.cont-text{
max-width: 300px;
max-height: 480px;
overflow: scroll;
padding: 10px 20px;
}
.popupcontent{
background-size: 100% 100%;
padding: 26px 15px;
text-align: center;
background-repeat: no-repeat;
.title{
color: #333333;
font-size: 15px;
margin-bottom: 15px;
}
}
.log1{
width: 180px;
height: 180px;
}
.yhxy{
color: #999999;
font-size: 12px;
margin-bottom: 5px;
.xy{
color: #297EFF;
}
}
.cent{
display: flex;
align-items: center;
justify-content: space-around;
padding: 0px 10px 20px;
.btn1{
background-color: #fff;
border: 1px solid #206ff4;
color: #206ff4;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 50px;
line-height: 50px;
}
.btn{
background-color: #206ff4;
color: white;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 50px;
line-height: 50px;
}
}
.cents{
display: flex;
align-items: center;
justify-content: space-around;
padding: 0px 10px 20px;
.btns1{
background-color: #fff;
border: 1px solid #206ff4;
color: #206ff4;
border-radius: 50px;
width: 46%;
margin: 10px 0px 0px;
height: 40px;
line-height: 40px;
}
.btns{
background-color: #206ff4;
color: white;
border-radius: 50px;
width: 46%;
margin: 10px 0px 0px;
height: 40px;
line-height: 40px;
}
}
.product-card {
background: #fff;
margin-top: 16rpx;
padding: 24rpx 24rpx 12rpx;
.title {
font-size: 32rpx;
color: #111;
font-weight: 600;
line-height: 1.4;
}
.price-row {
margin-top: 12rpx;
display: flex;
align-items: center;
gap: 16rpx;
}
.price {
font-size: 36rpx;
color: #111;
font-weight: 700;
}
.retail {
font-size: 22rpx;
color: #888;
}
.model {
margin-left: auto;
font-size: 22rpx;
color: #777;
}
.spec-row {
margin-top: 18rpx;
display: flex;
flex-wrap: wrap;
font-size: 26rpx;
color: #555;
gap: 6rpx;
}
.spec-label {
color: #777;
}
.spec-value {
color: #444;
margin-right: 20rpx;
}
}
</style>