diff --git a/api/request.js b/api/request.js index a9bc839..5ee239f 100644 --- a/api/request.js +++ b/api/request.js @@ -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) diff --git a/manifest.json b/manifest.json index f723e92..88efd2a 100644 --- a/manifest.json +++ b/manifest.json @@ -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 + } } } } diff --git a/pages/index/components/CompanyIntro.vue b/pages/index/components/CompanyIntro.vue index 6abd06f..2a2f392 100644 --- a/pages/index/components/CompanyIntro.vue +++ b/pages/index/components/CompanyIntro.vue @@ -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; diff --git a/pages/index/components/HeroBanner.vue b/pages/index/components/HeroBanner.vue index 013e083..aca75da 100644 --- a/pages/index/components/HeroBanner.vue +++ b/pages/index/components/HeroBanner.vue @@ -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; diff --git a/pages/index/components/PodCatalog.vue b/pages/index/components/PodCatalog.vue index b587d5d..6b0d803 100644 --- a/pages/index/components/PodCatalog.vue +++ b/pages/index/components/PodCatalog.vue @@ -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; diff --git a/pages/index/components/WhyChoose.vue b/pages/index/components/WhyChoose.vue index 5d3fe4d..0ddf274 100644 --- a/pages/index/components/WhyChoose.vue +++ b/pages/index/components/WhyChoose.vue @@ -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; diff --git a/pages/product-detail/components/OptionSelector.vue b/pages/product-detail/components/OptionSelector.vue new file mode 100644 index 0000000..e84ce2e --- /dev/null +++ b/pages/product-detail/components/OptionSelector.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/pages/product-detail/components/ProductInfo.vue b/pages/product-detail/components/ProductInfo.vue index cc93152..802cd4a 100644 --- a/pages/product-detail/components/ProductInfo.vue +++ b/pages/product-detail/components/ProductInfo.vue @@ -1,19 +1,19 @@ @@ -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; -} diff --git a/pages/product-detail/components/SizeSelector.vue b/pages/product-detail/components/SizeSelector.vue index f9c869d..18175c4 100644 --- a/pages/product-detail/components/SizeSelector.vue +++ b/pages/product-detail/components/SizeSelector.vue @@ -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; diff --git a/pages/product-detail/product-detail.vue b/pages/product-detail/product-detail.vue index cacde54..447a220 100644 --- a/pages/product-detail/product-detail.vue +++ b/pages/product-detail/product-detail.vue @@ -4,7 +4,7 @@ - + @@ -29,6 +29,42 @@ @select="selectSize" /> + + + + + + + + + + + + + + + + + + @@ -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 && diff --git a/pages/products/components/ProductGrid.vue b/pages/products/components/ProductGrid.vue index edf50bb..81809b7 100644 --- a/pages/products/components/ProductGrid.vue +++ b/pages/products/components/ProductGrid.vue @@ -35,21 +35,13 @@ class="product-image" :src="item.image" mode="aspectFill" + lazy-load v-if="item.image" > {{ item.name }} - - - {{ tag.text }} - ¥{{ item.price }} @@ -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; diff --git a/pages/products/products.vue b/pages/products/products.vue index 8c34e3e..8c82bde 100644 --- a/pages/products/products.vue +++ b/pages/products/products.vue @@ -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 diff --git a/static/images/home/Rectangle_178_1426_1248.png b/static/images/home/Rectangle_178_1426_1248.png index 2d6d3d8..9bfcdf5 100644 Binary files a/static/images/home/Rectangle_178_1426_1248.png and b/static/images/home/Rectangle_178_1426_1248.png differ diff --git a/static/images/home/Rectangle_181_1455_1450.jpg b/static/images/home/Rectangle_181_1455_1450.jpg index 4ccddd7..a7a1e8c 100644 Binary files a/static/images/home/Rectangle_181_1455_1450.jpg and b/static/images/home/Rectangle_181_1455_1450.jpg differ diff --git a/static/images/home/Rectangle_181_1455_1456.jpg b/static/images/home/Rectangle_181_1455_1456.jpg index 4ccddd7..a7a1e8c 100644 Binary files a/static/images/home/Rectangle_181_1455_1456.jpg and b/static/images/home/Rectangle_181_1455_1456.jpg differ diff --git a/static/images/home/Rectangle_187_1470_1653.jpg b/static/images/home/Rectangle_187_1470_1653.jpg index 96957bc..29dac9a 100644 Binary files a/static/images/home/Rectangle_187_1470_1653.jpg and b/static/images/home/Rectangle_187_1470_1653.jpg differ diff --git a/static/images/home/Rectangle_63_1446_1437.jpg b/static/images/home/Rectangle_63_1446_1437.jpg index 4ccddd7..a7a1e8c 100644 Binary files a/static/images/home/Rectangle_63_1446_1437.jpg and b/static/images/home/Rectangle_63_1446_1437.jpg differ diff --git a/static/images/home/Rectangle_63_1455_1461.jpg b/static/images/home/Rectangle_63_1455_1461.jpg index 4ccddd7..a7a1e8c 100644 Binary files a/static/images/home/Rectangle_63_1455_1461.jpg and b/static/images/home/Rectangle_63_1455_1461.jpg differ diff --git a/static/images/home/contact_us.png b/static/images/home/contact_us.png index 7f41f14..daa5501 100644 Binary files a/static/images/home/contact_us.png and b/static/images/home/contact_us.png differ diff --git a/static/images/home/feat_auto.jpg b/static/images/home/feat_auto.jpg index 9bb6de6..d839f64 100644 Binary files a/static/images/home/feat_auto.jpg and b/static/images/home/feat_auto.jpg differ diff --git a/static/images/home/feat_design.jpg b/static/images/home/feat_design.jpg index 133872f..5654a73 100644 Binary files a/static/images/home/feat_design.jpg and b/static/images/home/feat_design.jpg differ diff --git a/static/images/home/feat_global.jpg b/static/images/home/feat_global.jpg index 7ee7107..9c573f3 100644 Binary files a/static/images/home/feat_global.jpg and b/static/images/home/feat_global.jpg differ diff --git a/static/images/home/feat_store.jpg b/static/images/home/feat_store.jpg index 2216392..0281d5c 100644 Binary files a/static/images/home/feat_store.jpg and b/static/images/home/feat_store.jpg differ diff --git a/static/images/home/image_26_1496_1761.jpg b/static/images/home/image_26_1496_1761.jpg index 0a63ab6..e001c84 100644 Binary files a/static/images/home/image_26_1496_1761.jpg and b/static/images/home/image_26_1496_1761.jpg differ diff --git a/static/images/home/image_28_1498_1767.png b/static/images/home/image_28_1498_1767.png index 535a61a..46ebf68 100644 Binary files a/static/images/home/image_28_1498_1767.png and b/static/images/home/image_28_1498_1767.png differ diff --git a/static/images/home/step_01_select.jpg b/static/images/home/step_01_select.jpg index 9ae0411..96ab872 100644 Binary files a/static/images/home/step_01_select.jpg and b/static/images/home/step_01_select.jpg differ diff --git a/static/images/home/step_02_upload.jpg b/static/images/home/step_02_upload.jpg index 74da055..6487049 100644 Binary files a/static/images/home/step_02_upload.jpg and b/static/images/home/step_02_upload.jpg differ diff --git a/static/images/home/step_04_produce.jpg b/static/images/home/step_04_produce.jpg index f548abe..f11a44d 100644 Binary files a/static/images/home/step_04_produce.jpg and b/static/images/home/step_04_produce.jpg differ