【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。
目录
一、优化移动端触摸事件处理
在移动端应用开发中,触摸事件的处理直接影响用户体验。接下来将针对 uniapp 开发中常见的点击延迟和滑动冲突问题进行优化,并给出具体的实现源码。
1.1 点击延迟问题
在移动端浏览器中,存在 300ms 点击延迟的问题。这是因为浏览器需要等待 300ms,以判断用户是否进行了双击操作。如果用户在 300ms 内再次点击,就会触发双击事件;如果没有,则触发点击事件。这种机制在 uniapp 开发中可能会导致点击响应不及时,影响用户体验。
解决这个问题,可以使用 fastclick 库。fastclick 的原理是在检测到 touchend 事件时,立即触发一个模拟的 click 事件,并阻止浏览器在 300ms 后触发的 click 事件。
在 uniapp 项目中使用 fastclick,首先需要安装 fastclick 库:
npm install fastclick
然后在 main.js 中引入并使用:
import FastClick from 'fastclick'
import Vue from 'vue'
// 在Vue实例化之前使用FastClick
FastClick.attach(document.body)
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
在上述代码中,通过FastClick.attach(document.body)将 fastclick 应用到整个页面,这样就可以解决点击延迟问题。在实际项目中,如果某个组件不需要 fastclick 的处理,可以在该组件的mounted钩子函数中使用FastClick.off方法关闭 fastclick。
1.2 滑动冲突问题
滑动冲突通常发生在页面中存在多个可滑动组件嵌套的场景,例如外层是一个可左右滑动的 swiper 组件,内层是一个可上下滑动的列表组件。当用户进行滑动操作时,两个组件可能会对触摸事件产生竞争,导致滑动效果异常。
在 uniapp 中解决滑动冲突,可以通过监听 touch 事件,判断滑动的方向和区域,从而决定由哪个组件来处理滑动事件。以下是一个示例代码:
<template>
<view class="container">
<view class="outer" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
<!-- 外层滑动区域内容 -->
<view class="inner" @touchstart.stop.prevent="innerTouchStart" @touchmove.stop.prevent="innerTouchMove" @touchend.stop.prevent="innerTouchEnd">
<!-- 内层滑动区域内容 -->
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
startX: 0,
startY: 0,
moveX: 0,
moveY: 0,
isInnerScroll: false
};
},
methods: {
touchStart(e) {
this.startX = e.changedTouches[0].pageX;
this.startY = e.changedTouches[0].pageY;
this.moveX = 0;
this.moveY = 0;
this.isInnerScroll = false;
},
touchMove(e) {
if (this.isInnerScroll) return;
const moveX = e.changedTouches[0].pageX - this.startX;
const moveY = e.changedTouches[0].pageY - this.startY;
if (Math.abs(moveX) > Math.abs(moveY)) {
// 左右滑动,交给外层组件处理
// 这里可以添加外层组件左右滑动的逻辑
} else {
// 上下滑动,交给外层组件处理
// 这里可以添加外层组件上下滑动的逻辑
}
},
touchEnd() {
// 触摸结束,重置状态
this.startX = 0;
this.startY = 0;
this.moveX = 0;
this.moveY = 0;
this.isInnerScroll = false;
},
innerTouchStart() {
this.isInnerScroll = true;
},
innerTouchMove() {
// 内层组件滑动逻辑
},
innerTouchEnd() {
// 内层组件触摸结束逻辑
}
}
};
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
}
.outer {
width: 100%;
height: 100%;
background-color: #f0f0f0;
}
.inner {
width: 100%;
height: 500px;
background-color: #fff;
overflow-y: scroll;
}
</style>
在上述代码中,通过@touchstart、@touchmove和@touchend监听外层组件的触摸事件,通过@touchstart.stop.prevent、@touchmove.stop.prevent和@touchend.stop.prevent监听内层组件的触摸事件。在内层组件的触摸事件中,设置this.isInnerScroll = true,表示内层组件正在处理滑动事件,外层组件不再处理。这样就可以有效地解决滑动冲突问题。
二、设计移动端友好的交互效果
在移动端电商应用中,友好的交互效果能够显著提升用户体验,增加用户的留存率和活跃度。接下来将介绍如何实现下拉刷新和上拉加载更多这两种常见且实用的交互效果。
2.1 下拉刷新
下拉刷新是移动端应用中常见的交互方式,它允许用户通过向下拉动页面来获取最新的数据。在电商应用中,这一功能尤为重要,用户可以通过下拉刷新获取商品的最新价格、库存信息等。
在 uniapp 中,可以通过配置enablePullDownRefresh开启下拉刷新功能,并在onPullDownRefresh生命周期函数中处理刷新逻辑。结合 SpringBoot 和 Mybatis - plus,后端提供数据更新接口,前端通过uni.request调用该接口。
前端代码示例:
<template>
<view class="container">
<!-- 页面内容 -->
</view>
</template>
<script>
export default {
data() {
return {
// 数据列表
list: []
};
},
onPullDownRefresh() {
this.getList();
},
methods: {
async getList() {
const res = await uni.request({
url: 'http://localhost:8080/api/goods/list', // 后端接口地址
method: 'GET'
});
if (res.statusCode === 200) {
this.list = res.data;
uni.stopPullDownRefresh(); // 停止下拉刷新动画
}
}
}
};
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
}
</style>
后端 SpringBoot 代码示例:
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.entity.Goods;
import com.example.demo.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@GetMapping("/list")
public List<Goods> getGoodsList() {
QueryWrapper<Goods> wrapper = new QueryWrapper<>();
return goodsService.list(wrapper);
}
}
在上述代码中,前端通过uni.request发送 GET 请求到后端的/api/goods/list接口,获取商品列表数据。后端使用 Mybatis - plus 的QueryWrapper构建查询条件,查询所有商品数据并返回。前端在获取到数据后,更新页面的数据列表,并停止下拉刷新动画。
2.2 上拉加载更多
上拉加载更多是另一种提升用户浏览体验的重要交互方式,尤其适用于商品列表、订单列表等数据量较大的场景。当用户滚动到页面底部时,通过上拉操作加载更多的数据,避免一次性加载大量数据导致页面加载缓慢。
在 uniapp 中,可以通过监听onReachBottom事件来实现上拉加载更多功能。结合后端 SpringBoot 的分页查询功能,实现数据的分页加载。
前端代码示例:
<template>
<view class="container">
<view v-for="item in list" :key="item.id">
<!-- 展示商品信息 -->
{{ item.name }} - {{ item.price }}
</view>
</view>
</template>
<script>
export default {
data() {
return {
list: [],
page: 1,
pageSize: 10
};
},
onLoad() {
this.getList();
},
onReachBottom() {
this.page++;
this.getList();
},
methods: {
async getList() {
const res = await uni.request({
url: 'http://localhost:8080/api/goods/list',
method: 'GET',
data: {
page: this.page,
pageSize: this.pageSize
}
});
if (res.statusCode === 200) {
if (this.page === 1) {
this.list = res.data;
} else {
this.list = this.list.concat(res.data);
}
}
}
}
};
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
}
</style>
后端 SpringBoot 代码示例:
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.Goods;
import com.example.demo.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@GetMapping("/list")
public List<Goods> getGoodsList(@RequestParam Integer page, @RequestParam Integer pageSize) {
IPage<Goods> goodsPage = new Page<>(page, pageSize);
return goodsService.page(goodsPage).getRecords();
}
}
在上述代码中,前端通过onReachBottom事件监听页面滚动到底部的操作,每次触发时将page加 1,并调用getList方法获取下一页数据。前端在请求参数中传递page和pageSize,后端根据这两个参数进行分页查询,返回对应页的数据。前端在获取到数据后,根据当前页码判断是追加数据还是替换数据,从而实现上拉加载更多的功能。
三、利用 uniapp 的动画库实现流畅的页面过渡动画
在移动端应用中,页面过渡动画能够增强用户体验,使界面切换更加自然流畅。UniApp 提供了丰富的动画实现方式,下面将介绍如何利用 CSS3 动画和 UniApp 动画 API 来实现页面过渡动画。
3.1 使用 CSS3 动画
CSS3 提供了强大的动画制作功能,通过关键帧@keyframes和动画属性animation,可以实现元素的平滑过渡。在 UniApp 中,开发者可以通过编写 CSS 样式和添加类来控制动画流程。
首先,使用@keyframes规则定义动画序列中的各个点,每个关键帧描述了动画序列的一种状态及其在整个动画过程中的时序位置。例如,定义一个淡入淡出的动画:
@keyframes fadeInOut {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
然后,通过animation属性将定义的关键帧动画关联到 HTML 元素,并设置动画时长、时序函数、延迟、迭代次数及方向等。在 UniApp 中,将动画应用到页面元素的示例如下:
<template>
<view class="page" :class="{'fade-in-out': isAnimated}">
<!-- 页面内容 -->
</view>
</template>
<script>
export default {
data() {
return {
isAnimated: false
};
},
mounted() {
this.isAnimated = true;
}
};
</script>
<style scoped>
.page {
width: 100%;
height: 100%;
}
.fade-in-out {
animation: fadeInOut 3s ease-in-out infinite;
}
</style>
在上述代码中,当isAnimated为true时,.page元素会应用fade-in-out类,从而触发fadeInOut动画,实现页面的淡入淡出效果。animation: fadeInOut 3s ease-in-out infinite;表示动画名称为fadeInOut,持续时间为 3 秒,动画速度曲线为ease-in-out,无限循环播放。
3.2 利用 UniApp 动画 API
UniApp 提供了动画 API,允许开发者编程创建动画对象,并对其进行细粒度控制。通过调用uni.createAnimation()方法可以创建一个动画实例,然后链式调用多个过渡方法定义复杂动画,如设置透明度、色彩、位置移动等诸多属性的变化,并通过动画选项控制时长、延迟等参数。
以下是一个利用 UniApp 动画 API 实现页面左右滑动切换过渡动画的示例:
<template>
<view class="container">
<view :animation="animationData" class="page">
<!-- 页面内容 -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
animationData: {}
};
},
methods: {
slideToNextPage() {
const animation = uni.createAnimation({
duration: 300,
timingFunction: 'ease'
});
animation.translateX('100%').step();
this.animationData = animation.export();
setTimeout(() => {
// 这里可以进行页面切换的逻辑,例如更新路由
// 假设后端提供了获取下一页数据的接口,在此处调用接口获取数据
// 数据加载完成后,重置动画
this.animationData = {};
}, 300);
},
slideToPreviousPage() {
const animation = uni.createAnimation({
duration: 300,
timingFunction: 'ease'
});
animation.translateX('-100%').step();
this.animationData = animation.export();
setTimeout(() => {
// 这里可以进行页面切换的逻辑,例如更新路由
// 假设后端提供了获取上一页数据的接口,在此处调用接口获取数据
// 数据加载完成后,重置动画
this.animationData = {};
}, 300);
}
}
};
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
}
.page {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
</style>
在上述代码中,slideToNextPage方法创建了一个向右滑动的动画,slideToPreviousPage方法创建了一个向左滑动的动画。通过animation.translateX(‘100%’).step()和animation.translateX(‘-100%’).step()分别设置了水平方向的位移。在实际应用中,当动画执行完成后(通过setTimeout模拟动画执行时间),可以进行页面切换的逻辑,例如更新路由。如果需要与后端交互获取数据,也可以在此处调用后端接口,数据加载完成后,重置动画,以便下次动画能够正常执行 。这样,通过合理利用 UniApp 的动画 API,能够实现流畅且与业务逻辑紧密结合的页面过渡动画效果。