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 };
|
export { BASE_URL };
|
||||||
|
|
||||||
// 默认配置
|
// 默认配置
|
||||||
@@ -25,7 +32,16 @@ export function buildUrl(url) {
|
|||||||
if (/^https?:\/\//i.test(url)) return url;
|
if (/^https?:\/\//i.test(url)) return url;
|
||||||
const base = BASE_URL.replace(/\/+$/, '');
|
const base = BASE_URL.replace(/\/+$/, '');
|
||||||
const path = 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)
|
// 统一取 header(注入 token)
|
||||||
|
|||||||
@@ -69,8 +69,34 @@
|
|||||||
"vueVersion" : "2",
|
"vueVersion" : "2",
|
||||||
"h5" : {
|
"h5" : {
|
||||||
"devServer" : {
|
"devServer" : {
|
||||||
"https" : false,
|
"https" : true,
|
||||||
"host" : "192.168.124.19"
|
"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;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 核心能力卡片 - 自适应2列(避免宽度+间距同时固定导致挤压) */
|
/* 核心能力卡片 - 固定2列(345rpx×2 + 间距20rpx + 容器内边距40rpx = 750rpx)
|
||||||
|
注意:不要用 flex-basis + calc(50%),部分手机浏览器解析失败会导致单列 */
|
||||||
.ability-grid {
|
.ability-grid {
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -231,7 +232,7 @@ export default {
|
|||||||
margin-bottom: 60rpx;
|
margin-bottom: 60rpx;
|
||||||
}
|
}
|
||||||
.ability-card {
|
.ability-card {
|
||||||
flex: 1 1 calc(50% - 10rpx);
|
width: 345rpx;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 180rpx;
|
height: 180rpx;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export default {
|
|||||||
display: block;
|
display: block;
|
||||||
margin: 12rpx auto 0;
|
margin: 12rpx auto 0;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
max-width: calc(100% - 80rpx);
|
max-width: 670rpx; /* 750 - 两侧留白 80rpx,避免用 calc(部分手机浏览器解析失败) */
|
||||||
font-size: 20rpx;
|
font-size: 20rpx;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ export default {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 产品网格 - 自适应2列(避免宽度+间距同时固定导致挤压) */
|
/* 产品网格 - 固定2列(345rpx×2 + 间距20rpx + 容器内边距40rpx = 750rpx)
|
||||||
|
注意:不要用 flex-basis + calc(50%),部分手机浏览器解析失败会导致单列 */
|
||||||
.pod-grid {
|
.pod-grid {
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -135,7 +136,7 @@ export default {
|
|||||||
gap: 20rpx;
|
gap: 20rpx;
|
||||||
}
|
}
|
||||||
.pod-card {
|
.pod-card {
|
||||||
flex: 1 1 calc(50% - 10rpx);
|
width: 345rpx;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ export default {
|
|||||||
margin-bottom: 64rpx;
|
margin-bottom: 64rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2x2 网格 - 自适应2列(避免宽度+间距同时固定导致挤压) */
|
/* 2x2 网格 - 固定2列(345rpx×2 + 间距20rpx + 容器内边距40rpx = 750rpx)
|
||||||
|
注意:不要用 flex-basis + calc(50%),部分手机浏览器解析失败会导致单列 */
|
||||||
.why-grid {
|
.why-grid {
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -54,7 +55,7 @@ export default {
|
|||||||
gap: 20rpx;
|
gap: 20rpx;
|
||||||
}
|
}
|
||||||
.why-card {
|
.why-card {
|
||||||
flex: 1 1 calc(50% - 10rpx);
|
width: 345rpx;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 160rpx;
|
height: 160rpx;
|
||||||
background: #ffffff;
|
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>
|
<template>
|
||||||
<!-- 产品信息:名称 / SKU / 国家 / 价格 / 4 列快捷信息 -->
|
<!-- 产品信息:名称 / 国家 / 发货时效 / 价格 -->
|
||||||
<view class="info-wrap">
|
<view class="info-wrap">
|
||||||
<!-- 产品名称 18px Bold #1A1A1A -->
|
<!-- 产品名称 18px Bold #1A1A1A -->
|
||||||
<text class="name">{{ info.name }}</text>
|
<text class="name">{{ info.name }}</text>
|
||||||
|
|
||||||
<!-- SKU + 国家 同行 12px #979797,值黑色 -->
|
<!-- 国家 + 发货时效 同行 12px #979797,值黑色 -->
|
||||||
<view class="meta-row">
|
<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">
|
<view class="meta-item">
|
||||||
<text class="meta-label">国家: </text>
|
<text class="meta-label">国家: </text>
|
||||||
<text class="meta-value">{{ info.country }}</text>
|
<text class="meta-value">{{ info.country }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="meta-item">
|
||||||
|
<text class="meta-label">发货时效: </text>
|
||||||
|
<text class="meta-value">{{ info.shipTime }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 价格:¥ 和 .00 为 10px,整数 24px,全部红色 -->
|
<!-- 价格:¥ 和 .00 为 10px,整数 24px,全部红色 -->
|
||||||
@@ -22,17 +22,6 @@
|
|||||||
<text class="price-integer">{{ info.price.integer }}</text>
|
<text class="price-integer">{{ info.price.integer }}</text>
|
||||||
<text class="price-decimal">{{ info.price.decimal }}</text>
|
<text class="price-decimal">{{ info.price.decimal }}</text>
|
||||||
</view>
|
</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>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -63,7 +52,7 @@ export default {
|
|||||||
line-height: 50rpx; /* 26px */
|
line-height: 50rpx; /* 26px */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SKU / 国家 同行 */
|
/* 国家 / 发货时效 同行 */
|
||||||
.meta-row {
|
.meta-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 10rpx;
|
margin-top: 10rpx;
|
||||||
@@ -104,34 +93,4 @@ export default {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -84,8 +84,10 @@ export default {
|
|||||||
|
|
||||||
/* 单个尺码按钮 44x34,圆角 4px,背景 #F7F7F7 */
|
/* 单个尺码按钮 44x34,圆角 4px,背景 #F7F7F7 */
|
||||||
.size-item {
|
.size-item {
|
||||||
width: 85rpx; /* 44px */
|
min-width: 85rpx; /* 44px */
|
||||||
height: 65rpx; /* 34px */
|
height: 65rpx; /* 34px */
|
||||||
|
padding: 0 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<!-- 产品图片轮播 + 指示点 -->
|
<!-- 产品图片轮播 + 指示点 -->
|
||||||
<DetailSwiper :images="productImages" />
|
<DetailSwiper :images="productImages" />
|
||||||
|
|
||||||
<!-- 产品信息:名称/SKU/国家/价格/4列快捷信息 -->
|
<!-- 产品信息:名称/国家/发货时效/价格 -->
|
||||||
<ProductInfo :info="product" />
|
<ProductInfo :info="product" />
|
||||||
|
|
||||||
<!-- 灰色分隔条 -->
|
<!-- 灰色分隔条 -->
|
||||||
@@ -29,6 +29,42 @@
|
|||||||
@select="selectSize"
|
@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>
|
<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 ProductInfo from './components/ProductInfo.vue'
|
||||||
import ColorSelector from './components/ColorSelector.vue'
|
import ColorSelector from './components/ColorSelector.vue'
|
||||||
import SizeSelector from './components/SizeSelector.vue'
|
import SizeSelector from './components/SizeSelector.vue'
|
||||||
|
import OptionSelector from './components/OptionSelector.vue'
|
||||||
import ParamsTable from './components/ParamsTable.vue'
|
import ParamsTable from './components/ParamsTable.vue'
|
||||||
import SizeChart from './components/SizeChart.vue'
|
import SizeChart from './components/SizeChart.vue'
|
||||||
import PackagingSpec from './components/PackagingSpec.vue'
|
import PackagingSpec from './components/PackagingSpec.vue'
|
||||||
@@ -66,6 +103,7 @@ export default {
|
|||||||
ProductInfo,
|
ProductInfo,
|
||||||
ColorSelector,
|
ColorSelector,
|
||||||
SizeSelector,
|
SizeSelector,
|
||||||
|
OptionSelector,
|
||||||
ParamsTable,
|
ParamsTable,
|
||||||
SizeChart,
|
SizeChart,
|
||||||
PackagingSpec
|
PackagingSpec
|
||||||
@@ -77,10 +115,9 @@ export default {
|
|||||||
loading: false,
|
loading: false,
|
||||||
product: {
|
product: {
|
||||||
name: '',
|
name: '',
|
||||||
sku: '',
|
|
||||||
country: '',
|
country: '',
|
||||||
price: { symbol: '¥', integer: '0', decimal: '' },
|
shipTime: '',
|
||||||
quickInfo: []
|
price: { symbol: '¥', integer: '0', decimal: '' }
|
||||||
},
|
},
|
||||||
productImages: [],
|
productImages: [],
|
||||||
colors: [],
|
colors: [],
|
||||||
@@ -95,7 +132,12 @@ export default {
|
|||||||
mediaByColor: {},
|
mediaByColor: {},
|
||||||
// 默认轮播图(无颜色或 mediaByColor 缺失时使用,来自 detail.media.images)
|
// 默认轮播图(无颜色或 mediaByColor 缺失时使用,来自 detail.media.images)
|
||||||
defaultImages: [],
|
defaultImages: [],
|
||||||
packaging: null
|
packaging: null,
|
||||||
|
// 产品族价格矩阵(来自 detail.family,含 尺码×颜色×工艺×物流×印花 五维)
|
||||||
|
family: null,
|
||||||
|
selectedLogistics: '',
|
||||||
|
selectedPrintCount: '',
|
||||||
|
selectedCraft: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
@@ -129,21 +171,12 @@ export default {
|
|||||||
* 将接口详情数据映射为各组件所需结构
|
* 将接口详情数据映射为各组件所需结构
|
||||||
*/
|
*/
|
||||||
applyDetail(detail) {
|
applyDetail(detail) {
|
||||||
// 1. 产品信息:名称 / SKU / 国家 / 价格 / 4 列快捷信息
|
// 1. 产品信息:名称 / 国家 / 发货时效 / 价格
|
||||||
this.product = {
|
this.product = {
|
||||||
name: detail.goodName || '—',
|
name: detail.goodName || '—',
|
||||||
sku: detail.productCode || '—',
|
|
||||||
country: (detail.country && detail.country.countryName) || '—',
|
country: (detail.country && detail.country.countryName) || '—',
|
||||||
price: this.parsePrice(detail.price),
|
shipTime: detail.productionCycleHours ? detail.productionCycleHours + '小时' : '—',
|
||||||
quickInfo: [
|
price: this.parsePrice(detail.price)
|
||||||
{ label: '生产工艺', value: (detail.details && detail.details.productionProcess) || '—' },
|
|
||||||
{
|
|
||||||
label: '发货时效',
|
|
||||||
value: detail.productionCycleHours ? detail.productionCycleHours + '小时' : '—'
|
|
||||||
},
|
|
||||||
{ label: '印花', value: (detail.details && detail.details.designArea) || '—' },
|
|
||||||
{ label: '产品编码', value: detail.productCode || '—' }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 图片画廊:
|
// 2. 图片画廊:
|
||||||
@@ -195,10 +228,17 @@ export default {
|
|||||||
// 8. 包装规格(从 variants 按尺码去重生成)
|
// 8. 包装规格(从 variants 按尺码去重生成)
|
||||||
this.packaging = this.buildPackaging(this.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()
|
this.syncPriceByVariant()
|
||||||
|
|
||||||
// 10. 初始轮播图(按默认选中颜色取 mediaByColor,无则回退 defaultImages)
|
// 11. 初始轮播图(按默认选中颜色取 mediaByColor,无则回退 defaultImages)
|
||||||
this.syncImagesByColor()
|
this.syncImagesByColor()
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -372,7 +412,41 @@ export default {
|
|||||||
) || null
|
) || 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() {
|
syncPriceByVariant() {
|
||||||
|
const row = this.findMatrixRow()
|
||||||
|
if (row && row.price != null) {
|
||||||
|
this.$set(this.product, 'price', this.parsePrice(row.price))
|
||||||
|
return
|
||||||
|
}
|
||||||
const v = this.findVariant()
|
const v = this.findVariant()
|
||||||
if (v && v.price != null) {
|
if (v && v.price != null) {
|
||||||
this.$set(this.product, 'price', this.parsePrice(v.price))
|
this.$set(this.product, 'price', this.parsePrice(v.price))
|
||||||
@@ -395,20 +469,62 @@ export default {
|
|||||||
selectSize(size) {
|
selectSize(size) {
|
||||||
this.selectedSize = size
|
this.selectedSize = size
|
||||||
this.syncPriceByVariant()
|
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: {
|
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 样式)
|
* 颜色列表 + 可用态标记(给 ColorSelector 用,用于 disabled 样式)
|
||||||
* - 如果没选尺码:所有已 enabled 的颜色都可选
|
* - 如果没选尺码:所有已 enabled 的颜色都可选
|
||||||
* - 如果已选尺码:仅保留存在 (colorId + sizeName) 组合且 variant.enabled 的颜色
|
* - 如果已选尺码:仅保留存在 (colorId + sizeName) 组合的项
|
||||||
*/
|
*/
|
||||||
colorListWithAvailability() {
|
colorListWithAvailability() {
|
||||||
const sizeName = this.selectedSize
|
const sizeName = this.selectedSize
|
||||||
|
const source = this.availabilitySource
|
||||||
return this.colors.map((c) => {
|
return this.colors.map((c) => {
|
||||||
let enabled = c.enabled !== false
|
let enabled = c.enabled !== false
|
||||||
if (sizeName && this.variants.length) {
|
if (sizeName && source.length) {
|
||||||
enabled = this.variants.some(
|
enabled = source.some(
|
||||||
(v) =>
|
(v) =>
|
||||||
v &&
|
v &&
|
||||||
v.enabled !== false &&
|
v.enabled !== false &&
|
||||||
@@ -425,10 +541,11 @@ export default {
|
|||||||
*/
|
*/
|
||||||
sizeListWithAvailability() {
|
sizeListWithAvailability() {
|
||||||
const colorId = this.selectedColorId
|
const colorId = this.selectedColorId
|
||||||
|
const source = this.availabilitySource
|
||||||
return this.sizes.map((s) => {
|
return this.sizes.map((s) => {
|
||||||
let enabled = true
|
let enabled = true
|
||||||
if (colorId && this.variants.length) {
|
if (colorId && source.length) {
|
||||||
enabled = this.variants.some(
|
enabled = source.some(
|
||||||
(v) =>
|
(v) =>
|
||||||
v &&
|
v &&
|
||||||
v.enabled !== false &&
|
v.enabled !== false &&
|
||||||
|
|||||||
@@ -35,21 +35,13 @@
|
|||||||
class="product-image"
|
class="product-image"
|
||||||
:src="item.image"
|
:src="item.image"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
|
lazy-load
|
||||||
v-if="item.image"
|
v-if="item.image"
|
||||||
></image>
|
></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- 商品信息 -->
|
<!-- 商品信息 -->
|
||||||
<view class="product-info">
|
<view class="product-info">
|
||||||
<text class="product-name">{{ item.name }}</text>
|
<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">
|
<view class="price-row">
|
||||||
<text class="product-price">¥{{ item.price }}</text>
|
<text class="product-price">¥{{ item.price }}</text>
|
||||||
@@ -188,19 +180,6 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 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 */
|
/* 商品信息 - 内边距 10px */
|
||||||
.product-info {
|
.product-info {
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
|
|||||||
@@ -145,9 +145,12 @@ export default {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.windowHeight = 667
|
this.windowHeight = 667
|
||||||
}
|
}
|
||||||
|
// 首屏商品请求与基础数据并行发出(goods 无筛选时不依赖国家/分类数据),
|
||||||
|
// 避免串行等待拖慢首屏(goods 是最慢的接口,应最先发出)
|
||||||
|
const goodsPromise = this.loadGoods(1)
|
||||||
await this.initData()
|
await this.initData()
|
||||||
this.loadSubCategories('all')
|
this.loadSubCategories('all')
|
||||||
await this.loadGoods(1)
|
await goodsPromise
|
||||||
// 首屏数据渲染完成后,子组件会通过 mounted 自行测量并 emit 高度
|
// 首屏数据渲染完成后,子组件会通过 mounted 自行测量并 emit 高度
|
||||||
// 这里兜底触发一次重算(防止某些端 mounted 比父组件 onLoad 晚)
|
// 这里兜底触发一次重算(防止某些端 mounted 比父组件 onLoad 晚)
|
||||||
this.recomputeScrollHeight()
|
this.recomputeScrollHeight()
|
||||||
@@ -252,14 +255,7 @@ export default {
|
|||||||
id: item.goodId,
|
id: item.goodId,
|
||||||
name: truncateProductTitle(item.goodName),
|
name: truncateProductTitle(item.goodName),
|
||||||
price: item.price,
|
price: item.price,
|
||||||
image: item.image,
|
image: item.image
|
||||||
tags: (item.tags || []).map(function (t) {
|
|
||||||
return {
|
|
||||||
text: t.tagName,
|
|
||||||
bg: t.tagColor,
|
|
||||||
color: t.tagFontColor
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const total = data.total || 0
|
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 |