+1
-1
@@ -42,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
"quickapp" : {},
|
"quickapp" : {},
|
||||||
"mp-weixin" : {
|
"mp-weixin" : {
|
||||||
"appid" : "wxd0e6eff3c220337b",
|
"appid" : "wx962684512df4e5fd",
|
||||||
"setting" : {
|
"setting" : {
|
||||||
"urlCheck" : false
|
"urlCheck" : false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,8 +3,15 @@
|
|||||||
<view class="feature-section">
|
<view class="feature-section">
|
||||||
<text class="section-title">用强大的功能,开启盈利快车道</text>
|
<text class="section-title">用强大的功能,开启盈利快车道</text>
|
||||||
|
|
||||||
<!-- 功能卡片横向滚动 -->
|
<!-- 功能卡片横向滚动
|
||||||
<scroll-view scroll-x class="feat-scroll" show-scrollbar="false">
|
:scroll-left 绑定 featScrollLeft,activeFeat 变化时尽量把选中项滚到左侧 -->
|
||||||
|
<scroll-view
|
||||||
|
scroll-x
|
||||||
|
class="feat-scroll"
|
||||||
|
show-scrollbar="false"
|
||||||
|
:scroll-left="featScrollLeft"
|
||||||
|
:scroll-with-animation="true"
|
||||||
|
>
|
||||||
<view class="feat-list">
|
<view class="feat-list">
|
||||||
<view
|
<view
|
||||||
class="feat-card"
|
class="feat-card"
|
||||||
@@ -22,11 +29,12 @@
|
|||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<!-- 集成平台大图 - 370x257 8px圆角 -->
|
<!-- 集成平台大图 - 370x257 8px圆角
|
||||||
|
图片来源:features[activeFeat].img(4张卡片分别对应一张大图;占位时 4 张同图,设计师供图后换 mock.js 即可) -->
|
||||||
<view class="integration-wrap">
|
<view class="integration-wrap">
|
||||||
<image
|
<image
|
||||||
class="integration-img"
|
class="integration-img"
|
||||||
src="/static/images/home/Rectangle_64_1484_1735.png"
|
:src="integrationImg"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
></image>
|
></image>
|
||||||
</view>
|
</view>
|
||||||
@@ -34,6 +42,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// 功能卡片尺寸(与 CSS 保持一致,用于计算「选中卡片滚到左边」的 scrollLeft)
|
||||||
|
// CSS: .feat-card { width:332rpx }, .feat-list gap:16rpx
|
||||||
|
// 1rpx = 0.5px,所以 1 张卡片+gap = (332 + 16) * 0.5 = 174px
|
||||||
|
const CARD_WIDTH_PX = 332 * 0.5 // 166
|
||||||
|
const CARD_GAP_PX = 16 * 0.5 // 8
|
||||||
|
const CARD_STEP_PX = CARD_WIDTH_PX + CARD_GAP_PX // 174
|
||||||
|
|
||||||
|
const AUTO_PLAY_INTERVAL = 3000 // 3 秒自动轮播一次
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PlatformFeatures',
|
name: 'PlatformFeatures',
|
||||||
props: {
|
props: {
|
||||||
@@ -44,13 +61,75 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeFeat: 3
|
activeFeat: 0, // 默认从第一张开始轮播
|
||||||
|
featScrollLeft: 0, // scroll-view 的 scroll-left(px)
|
||||||
|
_timer: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
integrationImg() {
|
||||||
|
const f = this.features[this.activeFeat]
|
||||||
|
return (f && f.img) || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
activeFeat(idx) {
|
||||||
|
// 选中项变化时:把选中卡片「尽量滚到左边」
|
||||||
|
// index * step = 选中卡片起始 x
|
||||||
|
this.scrollToCard(idx)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.startAutoPlay()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopAutoPlay()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onFeatTap(index) {
|
onFeatTap(index) {
|
||||||
|
// 用户手动点击:切换 + 先停轮播,再隔 3 秒重启(避免刚点完又被立即切走)
|
||||||
|
this.stopAutoPlay()
|
||||||
this.activeFeat = index
|
this.activeFeat = index
|
||||||
this.$emit('change', index)
|
this.$emit('change', index)
|
||||||
|
this.startAutoPlay()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 让横滚容器把第 idx 张卡片「尽量」滚到左边贴边。
|
||||||
|
* scroll-view 的 scroll-left 有最大上限(内容总长 - 视口宽度),
|
||||||
|
* 当 idx 是最后几张时,scroll-left 超过最大值,scroll-view 会自动截断到可滚的最大值,
|
||||||
|
* 因此卡片虽无法真的贴左,但已经是「滚到最接近左侧」的位置了。
|
||||||
|
*/
|
||||||
|
scrollToCard(idx) {
|
||||||
|
const target = Math.max(0, idx) * CARD_STEP_PX
|
||||||
|
// 先赋值一个不同的值,保证 uni-app scroll-view 每次都能识别到属性变化
|
||||||
|
// (否则从 0 → 0 这种无变化赋值不触发滚动)
|
||||||
|
if (this.featScrollLeft === target) {
|
||||||
|
this.featScrollLeft = target + 0.001
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.featScrollLeft = target
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.featScrollLeft = target
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startAutoPlay() {
|
||||||
|
this.stopAutoPlay()
|
||||||
|
if (!this.features || this.features.length <= 1) return
|
||||||
|
this._timer = setInterval(() => {
|
||||||
|
const total = this.features.length
|
||||||
|
const next = (this.activeFeat + 1) % total
|
||||||
|
this.activeFeat = next
|
||||||
|
this.$emit('change', next)
|
||||||
|
}, AUTO_PLAY_INTERVAL)
|
||||||
|
},
|
||||||
|
|
||||||
|
stopAutoPlay() {
|
||||||
|
if (this._timer) {
|
||||||
|
clearInterval(this._timer)
|
||||||
|
this._timer = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +165,7 @@ export default {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
}
|
}
|
||||||
/* 卡片 - 166x80 圆角6px */
|
/* 卡片 - 166x80 圆角6px (332rpx × 160rpx) */
|
||||||
.feat-card {
|
.feat-card {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 332rpx;
|
width: 332rpx;
|
||||||
|
|||||||
@@ -79,10 +79,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 区块 - 灰色背景 #F8F8F8 */
|
/* 区块 - 灰色背景 #F8F8F8
|
||||||
|
上下留白对称:顶部标题距离区块上沿 96rpx,底部「更多产品」距离区块下沿也保持同区块背景延续
|
||||||
|
因此这里统一用 padding: 96rpx 0 40rpx 0,让卡片底部→按钮→区块底部都在同一块灰色背景里 */
|
||||||
.pod-section {
|
.pod-section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 96rpx 0 0 0;
|
padding: 96rpx 0 40rpx 0;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
@@ -171,13 +173,14 @@ export default {
|
|||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 更多产品按钮 - padding 10px 30px 圆角8px */
|
/* 更多产品按钮 - 与产品网格底部保持 20rpx,上方下方视觉对称
|
||||||
|
按钮本身无背景,属于灰色 #F8F8F8 区块背景的一部分 */
|
||||||
.more-products {
|
.more-products {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8rpx;
|
gap: 8rpx;
|
||||||
margin: 24rpx auto 0;
|
margin: 20rpx auto 0;
|
||||||
padding: 20rpx 60rpx;
|
padding: 20rpx 60rpx;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import WhyChoose from './components/WhyChoose.vue'
|
|||||||
import CompanyIntro from './components/CompanyIntro.vue'
|
import CompanyIntro from './components/CompanyIntro.vue'
|
||||||
|
|
||||||
import { getCountries, getGoods } from '@/api/public.js'
|
import { getCountries, getGoods } from '@/api/public.js'
|
||||||
|
import { truncateProductTitle } from '@/utils/product.js'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
trustStats,
|
trustStats,
|
||||||
@@ -94,7 +95,7 @@ export default {
|
|||||||
const items = (data.items || []).slice(0, 4)
|
const items = (data.items || []).slice(0, 4)
|
||||||
this.podProducts = items.map((item) => ({
|
this.podProducts = items.map((item) => ({
|
||||||
id: item.goodId,
|
id: item.goodId,
|
||||||
name: item.goodName,
|
name: truncateProductTitle(item.goodName),
|
||||||
image: item.image
|
image: item.image
|
||||||
}))
|
}))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+10
-4
@@ -70,26 +70,32 @@ export const podProducts = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
// 平台功能卡片
|
// 平台功能卡片
|
||||||
|
// 每一张卡片对应一张「集成平台」大图(img 字段目前暂用同一张占位图,后端/设计师提供后替换即可)
|
||||||
|
const FEAT_IMG = '/static/images/home/Rectangle_64_1484_1735.png'
|
||||||
export const featureList = [
|
export const featureList = [
|
||||||
{
|
{
|
||||||
step: '01',
|
step: '01',
|
||||||
title: '设计工具',
|
title: '设计工具',
|
||||||
desc: '轻松设计,快速定制'
|
desc: '轻松设计,快速定制',
|
||||||
|
img: FEAT_IMG
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step: '02',
|
step: '02',
|
||||||
title: '自动化履约',
|
title: '自动化履约',
|
||||||
desc: '从生产到配送,全程无忧,准时交付'
|
desc: '从生产到配送,全程无忧,准时交付',
|
||||||
|
img: FEAT_IMG
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step: '03',
|
step: '03',
|
||||||
title: '全球配送',
|
title: '全球配送',
|
||||||
desc: '全球11国自营本土工厂'
|
desc: '全球11国自营本土工厂',
|
||||||
|
img: FEAT_IMG
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
step: '04',
|
step: '04',
|
||||||
title: '店铺集成',
|
title: '店铺集成',
|
||||||
desc: '与全球主流电商平台无缝对接'
|
desc: '与全球主流电商平台无缝对接',
|
||||||
|
img: FEAT_IMG
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ import FilterDrawer from './components/FilterDrawer.vue'
|
|||||||
|
|
||||||
import { getCountries, getCategories, getTagGroups, getGoods } from '@/api/public.js'
|
import { getCountries, getCategories, getTagGroups, getGoods } from '@/api/public.js'
|
||||||
import { buildUrl } from '@/api/request.js'
|
import { buildUrl } from '@/api/request.js'
|
||||||
|
import { truncateProductTitle } from '@/utils/product.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -221,7 +222,7 @@ export default {
|
|||||||
|
|
||||||
const products = items.map((item) => ({
|
const products = items.map((item) => ({
|
||||||
id: item.goodId,
|
id: item.goodId,
|
||||||
name: item.goodName,
|
name: truncateProductTitle(item.goodName),
|
||||||
price: item.price,
|
price: item.price,
|
||||||
image: item.image,
|
image: item.image,
|
||||||
tags: (item.tags || []).map(function (t) {
|
tags: (item.tags || []).map(function (t) {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 商品标题处理工具
|
||||||
|
*
|
||||||
|
* 背景:后端返回的 goodName 常包含「前半段品牌/系列 + 空格 + 后半段 SKU/描述」,
|
||||||
|
* 首页/列表页卡片空间有限,只显示空格前的前半部分。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截断商品标题:返回第一个空格(或全角空格)之前的内容。
|
||||||
|
* - 防错:null/undefined/非字符串/空串 → 返回 ''
|
||||||
|
* - 没有空格 → 原样返回(去掉首尾空白后)
|
||||||
|
* - 多个连续空格 → 只看第一个
|
||||||
|
* - 全角空格 " " 也视为空格
|
||||||
|
*
|
||||||
|
* @param {string | any} title 原始商品标题(goodName)
|
||||||
|
* @returns {string} 截断后的标题
|
||||||
|
*/
|
||||||
|
function truncateProductTitle(title) {
|
||||||
|
if (title == null) return ''
|
||||||
|
if (typeof title !== 'string') {
|
||||||
|
try {
|
||||||
|
title = String(title)
|
||||||
|
} catch (e) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 去掉首尾空白
|
||||||
|
const s = title.trim()
|
||||||
|
if (!s) return ''
|
||||||
|
|
||||||
|
// 找第一个空格(半角 / 全角 / Tab / NBSP 都算)
|
||||||
|
const match = s.match(/[\s\u3000\u00a0]/)
|
||||||
|
if (!match) return s
|
||||||
|
const idx = match.index
|
||||||
|
if (idx <= 0) return s
|
||||||
|
return s.slice(0, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
truncateProductTitle
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user