v2体验版
@@ -11,7 +11,14 @@
|
||||
*/
|
||||
|
||||
// 基础路径:修改此处即可切换后端地址
|
||||
const BASE_URL = 'https://official.inkreach.cc/api/';
|
||||
// #ifdef H5
|
||||
// H5 端走 devServer 代理(manifest.json h5.devServer.proxy),规避浏览器 CORS 限制
|
||||
const BASE_URL = '/';
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
// 小程序端直连后端 v2 环境
|
||||
const BASE_URL = 'https://official.inkreach.cc/v2-api/';
|
||||
// #endif
|
||||
export { BASE_URL };
|
||||
|
||||
// 默认配置
|
||||
@@ -25,7 +32,16 @@ export function buildUrl(url) {
|
||||
if (/^https?:\/\//i.test(url)) return url;
|
||||
const base = BASE_URL.replace(/\/+$/, '');
|
||||
const path = url.replace(/^\/+/, '');
|
||||
return path ? `${base}/${path}` : base;
|
||||
let full = path ? `${base}/${path}` : base;
|
||||
// #ifdef H5
|
||||
// uni-app H5 的 image 组件会将以 / 开头的路径重写为 routerBase + path
|
||||
// (如 /assets/x.png → /h5/assets/x.png),导致 devServer 代理匹配不到而 404。
|
||||
// 这里拼成完整绝对地址(https://host/assets/x.png),image 组件对 http(s):// 开头的路径原样放行
|
||||
if (typeof window !== 'undefined' && full.charAt(0) === '/') {
|
||||
full = window.location.origin + full;
|
||||
}
|
||||
// #endif
|
||||
return full;
|
||||
}
|
||||
|
||||
// 统一取 header(注入 token)
|
||||
|
||||
@@ -69,8 +69,34 @@
|
||||
"vueVersion" : "2",
|
||||
"h5" : {
|
||||
"devServer" : {
|
||||
"https" : false,
|
||||
"host" : "192.168.124.19"
|
||||
"https" : true,
|
||||
"host" : "192.168.124.19",
|
||||
"proxy" : {
|
||||
"/public" : {
|
||||
"target" : "https://official.inkreach.cc",
|
||||
"changeOrigin" : true,
|
||||
"pathRewrite" : {
|
||||
"^/public" : "/v2-api/public"
|
||||
}
|
||||
},
|
||||
"/assets" : {
|
||||
"target" : "https://official.inkreach.cc",
|
||||
"changeOrigin" : true,
|
||||
"pathRewrite" : {
|
||||
"^/assets" : "/v2-api/assets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"router" : {
|
||||
"mode" : "history",
|
||||
"base" : "/h5/"
|
||||
},
|
||||
"title" : "印美达POD供应链",
|
||||
"optimization" : {
|
||||
"treeShaking" : {
|
||||
"enable" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,8 @@ export default {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 核心能力卡片 - 自适应2列(避免宽度+间距同时固定导致挤压) */
|
||||
/* 核心能力卡片 - 固定2列(345rpx×2 + 间距20rpx + 容器内边距40rpx = 750rpx)
|
||||
注意:不要用 flex-basis + calc(50%),部分手机浏览器解析失败会导致单列 */
|
||||
.ability-grid {
|
||||
padding: 0 20rpx;
|
||||
display: flex;
|
||||
@@ -231,7 +232,7 @@ export default {
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
.ability-card {
|
||||
flex: 1 1 calc(50% - 10rpx);
|
||||
width: 345rpx;
|
||||
min-width: 0;
|
||||
height: 180rpx;
|
||||
background: #ffffff;
|
||||
|
||||
@@ -165,7 +165,7 @@ export default {
|
||||
display: block;
|
||||
margin: 12rpx auto 0;
|
||||
width: fit-content;
|
||||
max-width: calc(100% - 80rpx);
|
||||
max-width: 670rpx; /* 750 - 两侧留白 80rpx,避免用 calc(部分手机浏览器解析失败) */
|
||||
font-size: 20rpx;
|
||||
color: #000000;
|
||||
line-height: 1.4;
|
||||
|
||||
@@ -127,7 +127,8 @@ export default {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 产品网格 - 自适应2列(避免宽度+间距同时固定导致挤压) */
|
||||
/* 产品网格 - 固定2列(345rpx×2 + 间距20rpx + 容器内边距40rpx = 750rpx)
|
||||
注意:不要用 flex-basis + calc(50%),部分手机浏览器解析失败会导致单列 */
|
||||
.pod-grid {
|
||||
padding: 0 20rpx;
|
||||
display: flex;
|
||||
@@ -135,7 +136,7 @@ export default {
|
||||
gap: 20rpx;
|
||||
}
|
||||
.pod-card {
|
||||
flex: 1 1 calc(50% - 10rpx);
|
||||
width: 345rpx;
|
||||
min-width: 0;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
|
||||
@@ -46,7 +46,8 @@ export default {
|
||||
margin-bottom: 64rpx;
|
||||
}
|
||||
|
||||
/* 2x2 网格 - 自适应2列(避免宽度+间距同时固定导致挤压) */
|
||||
/* 2x2 网格 - 固定2列(345rpx×2 + 间距20rpx + 容器内边距40rpx = 750rpx)
|
||||
注意:不要用 flex-basis + calc(50%),部分手机浏览器解析失败会导致单列 */
|
||||
.why-grid {
|
||||
padding: 0 20rpx;
|
||||
display: flex;
|
||||
@@ -54,7 +55,7 @@ export default {
|
||||
gap: 20rpx;
|
||||
}
|
||||
.why-card {
|
||||
flex: 1 1 calc(50% - 10rpx);
|
||||
width: 345rpx;
|
||||
min-width: 0;
|
||||
height: 160rpx;
|
||||
background: #ffffff;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<!-- 通用文本选项选择(物流/印花/工艺):胶囊样式,选中橙色边框+橙色文字 -->
|
||||
<view class="option-wrap">
|
||||
<text class="title">{{ title }}</text>
|
||||
|
||||
<view class="option-list">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="option-item"
|
||||
:class="{ active: selected === item }"
|
||||
@tap="selectOption(item)"
|
||||
>
|
||||
<text class="option-text">{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'OptionSelector',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selected: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectOption(item) {
|
||||
this.$emit('select', item)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.option-wrap {
|
||||
padding: 30rpx 20rpx; /* 15px 10px */
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 29rpx; /* 15px */
|
||||
color: #000000;
|
||||
line-height: 33rpx;
|
||||
margin-bottom: 30rpx; /* 15px */
|
||||
}
|
||||
|
||||
/* 选项列表 */
|
||||
.option-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx; /* 8px */
|
||||
}
|
||||
|
||||
/* 单个胶囊:与颜色选择样式一致 */
|
||||
.option-item {
|
||||
padding: 10rpx 24rpx; /* 5px 12px */
|
||||
border: 1rpx solid #f2f2f2;
|
||||
border-radius: 120rpx; /* 60px */
|
||||
background: #ffffff;
|
||||
}
|
||||
.option-item.active {
|
||||
border-color: #ff6800;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
font-size: 27rpx; /* 14px */
|
||||
color: #000000;
|
||||
line-height: 33rpx;
|
||||
}
|
||||
.option-item.active .option-text {
|
||||
color: #ff6800;
|
||||
}
|
||||
</style>
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<!-- 产品信息:名称 / SKU / 国家 / 价格 / 4 列快捷信息 -->
|
||||
<!-- 产品信息:名称 / 国家 / 发货时效 / 价格 -->
|
||||
<view class="info-wrap">
|
||||
<!-- 产品名称 18px Bold #1A1A1A -->
|
||||
<text class="name">{{ info.name }}</text>
|
||||
|
||||
<!-- SKU + 国家 同行 12px #979797,值黑色 -->
|
||||
<!-- 国家 + 发货时效 同行 12px #979797,值黑色 -->
|
||||
<view class="meta-row">
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">SKU: </text>
|
||||
<text class="meta-value">{{ info.sku }}</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">国家: </text>
|
||||
<text class="meta-value">{{ info.country }}</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">发货时效: </text>
|
||||
<text class="meta-value">{{ info.shipTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 价格:¥ 和 .00 为 10px,整数 24px,全部红色 -->
|
||||
@@ -22,17 +22,6 @@
|
||||
<text class="price-integer">{{ info.price.integer }}</text>
|
||||
<text class="price-decimal">{{ info.price.decimal }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 0.5px #F0F0F0 -->
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 4 列快捷信息:label 10px 灰,value 13px 黑 居中 -->
|
||||
<view class="quick-info">
|
||||
<view class="quick-col" v-for="(item, index) in info.quickInfo" :key="index">
|
||||
<text class="quick-label">{{ item.label }}</text>
|
||||
<text class="quick-value">{{ item.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -63,7 +52,7 @@ export default {
|
||||
line-height: 50rpx; /* 26px */
|
||||
}
|
||||
|
||||
/* SKU / 国家 同行 */
|
||||
/* 国家 / 发货时效 同行 */
|
||||
.meta-row {
|
||||
display: flex;
|
||||
margin-top: 10rpx;
|
||||
@@ -104,34 +93,4 @@ export default {
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 分隔线 */
|
||||
.divider {
|
||||
height: 1rpx; /* 0.5px */
|
||||
background: #f0f0f0;
|
||||
margin-top: 26rpx; /* 13px 间隙 */
|
||||
}
|
||||
|
||||
/* 4 列快捷信息 */
|
||||
.quick-info {
|
||||
display: flex;
|
||||
margin-top: 20rpx; /* 10px */
|
||||
}
|
||||
.quick-col {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
.quick-label {
|
||||
font-size: 19rpx; /* 10px */
|
||||
color: #979797;
|
||||
line-height: 27rpx;
|
||||
}
|
||||
.quick-value {
|
||||
font-size: 25rpx; /* 13px */
|
||||
color: #000000;
|
||||
line-height: 33rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -84,8 +84,10 @@ export default {
|
||||
|
||||
/* 单个尺码按钮 44x34,圆角 4px,背景 #F7F7F7 */
|
||||
.size-item {
|
||||
width: 85rpx; /* 44px */
|
||||
min-width: 85rpx; /* 44px */
|
||||
height: 65rpx; /* 34px */
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<!-- 产品图片轮播 + 指示点 -->
|
||||
<DetailSwiper :images="productImages" />
|
||||
|
||||
<!-- 产品信息:名称/SKU/国家/价格/4列快捷信息 -->
|
||||
<!-- 产品信息:名称/国家/发货时效/价格 -->
|
||||
<ProductInfo :info="product" />
|
||||
|
||||
<!-- 灰色分隔条 -->
|
||||
@@ -29,6 +29,42 @@
|
||||
@select="selectSize"
|
||||
/>
|
||||
|
||||
<!-- 灰色分隔条 -->
|
||||
<view class="section-divider" v-if="logisticsOptions.length"></view>
|
||||
|
||||
<!-- 物流选择(来自 family 价格矩阵维度) -->
|
||||
<OptionSelector
|
||||
v-if="logisticsOptions.length"
|
||||
title="物流"
|
||||
:list="logisticsOptions"
|
||||
:selected="selectedLogistics"
|
||||
@select="selectLogistics"
|
||||
/>
|
||||
|
||||
<!-- 灰色分隔条 -->
|
||||
<view class="section-divider" v-if="printCountOptions.length"></view>
|
||||
|
||||
<!-- 印花选择(来自 family 价格矩阵维度) -->
|
||||
<OptionSelector
|
||||
v-if="printCountOptions.length"
|
||||
title="印花"
|
||||
:list="printCountOptions"
|
||||
:selected="selectedPrintCount"
|
||||
@select="selectPrintCount"
|
||||
/>
|
||||
|
||||
<!-- 灰色分隔条 -->
|
||||
<view class="section-divider" v-if="craftOptions.length"></view>
|
||||
|
||||
<!-- 工艺选择(来自 family 价格矩阵维度) -->
|
||||
<OptionSelector
|
||||
v-if="craftOptions.length"
|
||||
title="工艺"
|
||||
:list="craftOptions"
|
||||
:selected="selectedCraft"
|
||||
@select="selectCraft"
|
||||
/>
|
||||
|
||||
<!-- 灰色分隔条 -->
|
||||
<view class="section-divider" v-if="productParams.length"></view>
|
||||
|
||||
@@ -54,6 +90,7 @@ import DetailSwiper from './components/DetailSwiper.vue'
|
||||
import ProductInfo from './components/ProductInfo.vue'
|
||||
import ColorSelector from './components/ColorSelector.vue'
|
||||
import SizeSelector from './components/SizeSelector.vue'
|
||||
import OptionSelector from './components/OptionSelector.vue'
|
||||
import ParamsTable from './components/ParamsTable.vue'
|
||||
import SizeChart from './components/SizeChart.vue'
|
||||
import PackagingSpec from './components/PackagingSpec.vue'
|
||||
@@ -66,6 +103,7 @@ export default {
|
||||
ProductInfo,
|
||||
ColorSelector,
|
||||
SizeSelector,
|
||||
OptionSelector,
|
||||
ParamsTable,
|
||||
SizeChart,
|
||||
PackagingSpec
|
||||
@@ -77,10 +115,9 @@ export default {
|
||||
loading: false,
|
||||
product: {
|
||||
name: '',
|
||||
sku: '',
|
||||
country: '',
|
||||
price: { symbol: '¥', integer: '0', decimal: '' },
|
||||
quickInfo: []
|
||||
shipTime: '',
|
||||
price: { symbol: '¥', integer: '0', decimal: '' }
|
||||
},
|
||||
productImages: [],
|
||||
colors: [],
|
||||
@@ -95,7 +132,12 @@ export default {
|
||||
mediaByColor: {},
|
||||
// 默认轮播图(无颜色或 mediaByColor 缺失时使用,来自 detail.media.images)
|
||||
defaultImages: [],
|
||||
packaging: null
|
||||
packaging: null,
|
||||
// 产品族价格矩阵(来自 detail.family,含 尺码×颜色×工艺×物流×印花 五维)
|
||||
family: null,
|
||||
selectedLogistics: '',
|
||||
selectedPrintCount: '',
|
||||
selectedCraft: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
@@ -129,21 +171,12 @@ export default {
|
||||
* 将接口详情数据映射为各组件所需结构
|
||||
*/
|
||||
applyDetail(detail) {
|
||||
// 1. 产品信息:名称 / SKU / 国家 / 价格 / 4 列快捷信息
|
||||
// 1. 产品信息:名称 / 国家 / 发货时效 / 价格
|
||||
this.product = {
|
||||
name: detail.goodName || '—',
|
||||
sku: detail.productCode || '—',
|
||||
country: (detail.country && detail.country.countryName) || '—',
|
||||
price: this.parsePrice(detail.price),
|
||||
quickInfo: [
|
||||
{ label: '生产工艺', value: (detail.details && detail.details.productionProcess) || '—' },
|
||||
{
|
||||
label: '发货时效',
|
||||
value: detail.productionCycleHours ? detail.productionCycleHours + '小时' : '—'
|
||||
},
|
||||
{ label: '印花', value: (detail.details && detail.details.designArea) || '—' },
|
||||
{ label: '产品编码', value: detail.productCode || '—' }
|
||||
]
|
||||
shipTime: detail.productionCycleHours ? detail.productionCycleHours + '小时' : '—',
|
||||
price: this.parsePrice(detail.price)
|
||||
}
|
||||
|
||||
// 2. 图片画廊:
|
||||
@@ -195,10 +228,17 @@ export default {
|
||||
// 8. 包装规格(从 variants 按尺码去重生成)
|
||||
this.packaging = this.buildPackaging(this.variants)
|
||||
|
||||
// 9. 初始价格(按默认选中的 尺码+颜色 匹配变体价格)
|
||||
// 9. 产品族价格矩阵:解析 family 五维选项(物流/印花/工艺),默认选中第一项
|
||||
const family = detail.family && detail.family.priceMatrix ? detail.family : null
|
||||
this.family = family
|
||||
this.selectedLogistics = family && family.logistics && family.logistics.length ? family.logistics[0] : ''
|
||||
this.selectedPrintCount = family && family.printCounts && family.printCounts.length ? family.printCounts[0] : ''
|
||||
this.selectedCraft = family && family.crafts && family.crafts.length ? family.crafts[0] : ''
|
||||
|
||||
// 10. 初始价格(family 优先按价格矩阵取,否则按默认选中的 尺码+颜色 匹配变体价格)
|
||||
this.syncPriceByVariant()
|
||||
|
||||
// 10. 初始轮播图(按默认选中颜色取 mediaByColor,无则回退 defaultImages)
|
||||
// 11. 初始轮播图(按默认选中颜色取 mediaByColor,无则回退 defaultImages)
|
||||
this.syncImagesByColor()
|
||||
},
|
||||
|
||||
@@ -372,7 +412,41 @@ export default {
|
||||
) || null
|
||||
)
|
||||
},
|
||||
/**
|
||||
* 在 family.priceMatrix.rows 中查找当前选中组合(尺码+颜色+物流+印花+工艺)的价格行
|
||||
* 未选全或无 family 时返回 null
|
||||
*/
|
||||
findMatrixRow() {
|
||||
const f = this.family
|
||||
if (!f || !f.priceMatrix || !Array.isArray(f.priceMatrix.rows) || !f.priceMatrix.rows.length) {
|
||||
return null
|
||||
}
|
||||
const sizeName = this.selectedSize
|
||||
const colorId = this.selectedColorId
|
||||
return (
|
||||
f.priceMatrix.rows.find(
|
||||
(row) =>
|
||||
row &&
|
||||
(!sizeName || String(row.sizeName) === String(sizeName)) &&
|
||||
(!colorId || String(row.colorId) === String(colorId)) &&
|
||||
(!this.selectedLogistics || String(row.logistics) === String(this.selectedLogistics)) &&
|
||||
(!this.selectedPrintCount || String(row.printCount) === String(this.selectedPrintCount)) &&
|
||||
(!this.selectedCraft || String(row.craft) === String(this.selectedCraft))
|
||||
) || null
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据当前选中组合同步价格:
|
||||
* - 有 family 价格矩阵:按 尺码+颜色+物流+印花+工艺 匹配矩阵行
|
||||
* - 否则回退:按 尺码+颜色 匹配 variant
|
||||
*/
|
||||
syncPriceByVariant() {
|
||||
const row = this.findMatrixRow()
|
||||
if (row && row.price != null) {
|
||||
this.$set(this.product, 'price', this.parsePrice(row.price))
|
||||
return
|
||||
}
|
||||
const v = this.findVariant()
|
||||
if (v && v.price != null) {
|
||||
this.$set(this.product, 'price', this.parsePrice(v.price))
|
||||
@@ -395,20 +469,62 @@ export default {
|
||||
selectSize(size) {
|
||||
this.selectedSize = size
|
||||
this.syncPriceByVariant()
|
||||
},
|
||||
|
||||
/**
|
||||
* 物流/印花/工艺选择:来自 family 价格矩阵维度,选中后联动价格
|
||||
*/
|
||||
selectLogistics(value) {
|
||||
this.selectedLogistics = value
|
||||
this.syncPriceByVariant()
|
||||
},
|
||||
selectPrintCount(value) {
|
||||
this.selectedPrintCount = value
|
||||
this.syncPriceByVariant()
|
||||
},
|
||||
selectCraft(value) {
|
||||
this.selectedCraft = value
|
||||
this.syncPriceByVariant()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 可用性判断数据源:有 family 价格矩阵时用矩阵行,否则用 variants
|
||||
* 两者都含 sizeName / colorId 字段
|
||||
*/
|
||||
availabilitySource() {
|
||||
const f = this.family
|
||||
if (f && f.priceMatrix && Array.isArray(f.priceMatrix.rows) && f.priceMatrix.rows.length) {
|
||||
return f.priceMatrix.rows
|
||||
}
|
||||
return this.variants
|
||||
},
|
||||
|
||||
/**
|
||||
* 物流/印花/工艺选项列表(来自 family)
|
||||
*/
|
||||
logisticsOptions() {
|
||||
return this.family && Array.isArray(this.family.logistics) ? this.family.logistics : []
|
||||
},
|
||||
printCountOptions() {
|
||||
return this.family && Array.isArray(this.family.printCounts) ? this.family.printCounts : []
|
||||
},
|
||||
craftOptions() {
|
||||
return this.family && Array.isArray(this.family.crafts) ? this.family.crafts : []
|
||||
},
|
||||
|
||||
/**
|
||||
* 颜色列表 + 可用态标记(给 ColorSelector 用,用于 disabled 样式)
|
||||
* - 如果没选尺码:所有已 enabled 的颜色都可选
|
||||
* - 如果已选尺码:仅保留存在 (colorId + sizeName) 组合且 variant.enabled 的颜色
|
||||
* - 如果已选尺码:仅保留存在 (colorId + sizeName) 组合的项
|
||||
*/
|
||||
colorListWithAvailability() {
|
||||
const sizeName = this.selectedSize
|
||||
const source = this.availabilitySource
|
||||
return this.colors.map((c) => {
|
||||
let enabled = c.enabled !== false
|
||||
if (sizeName && this.variants.length) {
|
||||
enabled = this.variants.some(
|
||||
if (sizeName && source.length) {
|
||||
enabled = source.some(
|
||||
(v) =>
|
||||
v &&
|
||||
v.enabled !== false &&
|
||||
@@ -425,10 +541,11 @@ export default {
|
||||
*/
|
||||
sizeListWithAvailability() {
|
||||
const colorId = this.selectedColorId
|
||||
const source = this.availabilitySource
|
||||
return this.sizes.map((s) => {
|
||||
let enabled = true
|
||||
if (colorId && this.variants.length) {
|
||||
enabled = this.variants.some(
|
||||
if (colorId && source.length) {
|
||||
enabled = source.some(
|
||||
(v) =>
|
||||
v &&
|
||||
v.enabled !== false &&
|
||||
|
||||
@@ -35,21 +35,13 @@
|
||||
class="product-image"
|
||||
:src="item.image"
|
||||
mode="aspectFill"
|
||||
lazy-load
|
||||
v-if="item.image"
|
||||
></image>
|
||||
</view>
|
||||
<!-- 商品信息 -->
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ item.name }}</text>
|
||||
<!-- 标签 - 商品名下方、价格上方 (Figma 1523:344) -->
|
||||
<view class="product-badges" v-if="item.tags && item.tags.length > 0">
|
||||
<view
|
||||
class="product-badge"
|
||||
:style="{ background: tag.bg, color: tag.color }"
|
||||
v-for="(tag, tIdx) in item.tags"
|
||||
:key="tIdx"
|
||||
>{{ tag.text }}</view>
|
||||
</view>
|
||||
<!-- 价格 + "起"字 -->
|
||||
<view class="price-row">
|
||||
<text class="product-price">¥{{ item.price }}</text>
|
||||
@@ -188,19 +180,6 @@ export default {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
/* 标签 - 位于商品名下方、价格上方,横向排列 */
|
||||
.product-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
.product-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 18rpx;
|
||||
}
|
||||
|
||||
/* 商品信息 - 内边距 10px */
|
||||
.product-info {
|
||||
padding: 20rpx;
|
||||
|
||||
@@ -145,9 +145,12 @@ export default {
|
||||
} catch (e) {
|
||||
this.windowHeight = 667
|
||||
}
|
||||
// 首屏商品请求与基础数据并行发出(goods 无筛选时不依赖国家/分类数据),
|
||||
// 避免串行等待拖慢首屏(goods 是最慢的接口,应最先发出)
|
||||
const goodsPromise = this.loadGoods(1)
|
||||
await this.initData()
|
||||
this.loadSubCategories('all')
|
||||
await this.loadGoods(1)
|
||||
await goodsPromise
|
||||
// 首屏数据渲染完成后,子组件会通过 mounted 自行测量并 emit 高度
|
||||
// 这里兜底触发一次重算(防止某些端 mounted 比父组件 onLoad 晚)
|
||||
this.recomputeScrollHeight()
|
||||
@@ -252,14 +255,7 @@ export default {
|
||||
id: item.goodId,
|
||||
name: truncateProductTitle(item.goodName),
|
||||
price: item.price,
|
||||
image: item.image,
|
||||
tags: (item.tags || []).map(function (t) {
|
||||
return {
|
||||
text: t.tagName,
|
||||
bg: t.tagColor,
|
||||
color: t.tagFontColor
|
||||
}
|
||||
})
|
||||
image: item.image
|
||||
}))
|
||||
|
||||
const total = data.total || 0
|
||||
|
||||
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 31 KiB |