效果图
文章目录
一、分类页顶栏
pages.json
新增配置
,{
"path" : "pages/list/list",
"style" : {
"navigationBarBackgroundColor":"#FFFFFF",
"app-plus":{
"scrollIndicator":"none",
"titleNView":{
"searchInput":{
"placeholder":"请输入关键字",
"disabled":true,
"align":"left",
"borderRadius":"15px",
"backgroundColor":"#F7F7F7",
"placeholderColor":"#B3B3B3"
}
}
}
}
}
页面生命周期
onNavigationBarSearchinputClicked | 监听原生标题栏搜索输入框点击事件 |
---|
点击搜索栏,进入搜索页
list.vue
//input输入框点击事件
onNavigationBarSearchInputClicked(){
uni.navigateTo({
url:'../search/search'
})
}
引入lines
<template>
<view class='list'>
<Lines />
</view>
</template>
<script>
import Lines from '@/components/common/Lines.vue'
export default {
data() {
return {
}
},
components:{
Lines
}
二、构建商品分类页数据(后端)
1.1接口功能
获取商品分类
1.2URL
地址/api/goods/list
1.3支持格式
JSON
1.4 HTTP请求方式
GET
1.5 请求参数
参数 | 必选 | 类型 | 说明 |
---|
1.6 返回字段
返回字段 | 字段类型 | 说明 |
---|---|---|
code | string | 返回结果状态。0:正常;1:错误。 |
data | object | 商品分类数据 |
1.7 接口示例
{
“code”: ‘0’,
“data”: [
{
Id:1,
name:’家居家纺’,
data:[
{
name:’上装’,
List:[
{
id:1
Name:’夹克’,
imgUrl:’xx.jpeg’
}
]
}
]
}
]
}
在 server/routes/index.js
创建接口
var express = require('express');
var router = express.Router();
var connection = require('../db/sql.js');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/api/goods/list', function(req, res, next) {
res.json({
code:0,
data:[
{
id:1,
name:"家居家纺",
data:[
{
name:"家纺",
list:[
{
id:1,
name:"毛巾/浴巾",
imgUrl:"../../static/img/list1.jpg"
},
{
id:2,
name:"枕头",
imgUrl:"../../static/img/list1.jpg"
}
]
},
{
name:"生活用品",
list:[
{
id:1,
name:"浴室用品",
imgUrl:"../../static/img/list1.jpg"
},
{
id:2,
name:"洗晒",
imgUrl:"../../static/img/list1.jpg"
}
]
}
]
},
{
id:2,
name:"女装",
data:[
{
name:"裙装",
list:[
{
id:1,
name:"半身裙",
imgUrl:"../../static/img/list1.jpg"
},
{
id:2,
name:"连衣裙",
imgUrl:"../../static/img/list1.jpg"
}
]
},
{
name:"上衣",
list:[
{
id:1,
name:"T恤",
imgUrl:"../../static/img/list1.jpg"
},
{
id:2,
name:"衬衫",
imgUrl:"../../static/img/list1.jpg"
}
]
}
]
}
]
})
});
三、商品分类内容布局
<template>
<view>
<Lines />
<view class='list'>
<!--左侧滑动-->
<scroll-view scroll-y="true" class='list-left'>
<view v-for='i in 50' class='left-item'>
<view class='left-name'>{{i}}</view>
</view>
</scroll-view>
<!--右侧滑动-->
<scroll-view scroll-y="true" class='list-right'>
<view class='righ-list'>
<view class='list-title'>家纺</view>
<view class='right-content'>
<view class='right-item'>
<image class='right-img' src="../../static/img/list1.jpg" mode=""></image>
<view class='right-name'>毛巾</view>
</view>
<view class='right-item'>
<image class='right-img' src="../../static/img/list1.jpg" mode=""></image>
<view class='right-name'>毛巾</view>
</view>
<view class='right-item'>
<image class='right-img' src="../../static/img/list1.jpg" mode=""></image>
<view class='right-name'>毛巾</view>
</view>
<view class='right-item'>
<image class='right-img' src="../../static/img/list1.jpg" mode=""></image>
<view class='right-name'>毛巾</view>
</view>
</view>
</view>
<view class='righ-list'>
<view class='list-title'>家纺</view>
<view class='right-content'>
<view class='right-item'>
<image class='right-img' src="../../static/img/list1.jpg" mode=""></image>
<view class='right-name'>毛巾</view>
</view>
<view class='right-item'>
<image class='right-img' src="../../static/img/list1.jpg" mode=""></image>
<view class='right-name'>毛巾</view>
</view>
<view class='right-item'>
<image class='right-img' src="../../static/img/list1.jpg" mode=""></image>
<view class='right-name'>毛巾</view>
</view>
</view>
</view>
<view class='righ-list'>
<view class='list-title'>家纺</view>
<view class='right-content'>
<view class='right-item'>
<image class='right-img' src="../../static/img/list1.jpg" mode=""></image>
<view class='right-name'>毛巾</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import Lines from '@/components/common/Lines.vue'
export default {
data() {
return {
}
},
components:{
Lines
},
methods: {
},
//input输入框点击事件
onNavigationBarSearchInputClicked(){
uni.navigateTo({
url:'../search/search'
})
}
}
</script>
<style scoped>
.list{
display: flex;
}
.list-left{
width: 200rpx;
}
.left-item{
border-bottom:2rpx solid #FFFFFF;
font-size:28rpx;
font-weight: bold; /*粗体字*/
background-color: #F7F7F7;
}
.left-name{
padding:30rpx 6rpx;
text-align: center;
}
.left-name-active{
border-left:8rpx solid #49BDFB;
background-color: #FFFFFF;
}
.list-right{
flex:1;
padding-left:30rpx;
}
.list-title{
font-weight: bold;
padding:30rpx 0;
}
.right-content{
/*换行*/
display: flex;
flex-wrap: wrap;
}
.right-item{
/*图片大小*/
width: 150rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding:10rpx;
}
.right-name{
/*离图片空点*/
padding:16rpx 0;
}
.right-img{
width: 150rpx;
height: 150rpx;
}
</style>
四、获取高度和点击添加样式
问题:
1、左右滑动一起动,互相没有给一个对应的高度值
可视区域高度获取
<!--左侧滑动-->
<scroll-view scroll-y="true" class='list-left' :style="'height:'+clentHeight+'px;'">
<!--右侧滑动-->
<scroll-view scroll-y="true" class='list-right' :style="'height:'+clentHeight+'px;'">
//获取可视高度
onReady() {
uni.getSystemInfo({
success: (res) => {
this.clentHeight = res.windowHeight - this.getClientHeight();
}
})
},
methods: {
//获取可视区域高度【兼容】
getClientHeight(){
const res = uni.getSystemInfoSync();
const system = res.platform;
if( system ==='ios' ){
return 44+res.statusBarHeight;
}else if( system==='android' ){
return 48+res.statusBarHeight;
}else{
return 0;
}
},
左侧滑块点击显示事件
<!--左侧滑动-->
<scroll-view scroll-y="true" class='list-left' :style="'height:'+clentHeight+'px;'">
<view v-for='i in 50' class='left-item' @tap="changeLeftTab(i)">
<view class='left-name'
:class=' activeIndex ===i ?"left-name-active":"" '
>{{i}}</view>
</view>
</scroll-view>
/******/
data() {
return {
clentHeight:0,
activeIndex:1
}
},
//左侧点击事件
changeLeftTab(index){
this.activeIndex = index;
}
五、商品分类对应的数据渲染
list.vue
<!--左侧滑动-->
<scroll-view scroll-y="true" class='list-left' :style="'height:'+clentHeight+'px;'">
<view
v-for='(item,index) in leftData'
:key='index'
@tap="changeLeftTab(index,item.id)"
>
<view class='left-name'
:class=' activeIndex ===index ?"left-name-active":"left-item" '
>{{item.name}}</view>
</view>
</scroll-view>
<!--右侧滑动-->
<scroll-view scroll-y="true" class='list-right' :style="'height:'+clentHeight+'px;'">
<view
class='righ-list'
v-for='(item,index) in rightData'
:key='index'
> <!--遍历页-->
<block v-for='(k,i) in item'>
<!--细分类名-->
<view class='list-title'>{{k.name}}</view>
<view class='right-content'>
<view class='right-item' v-for='(j,idx) in k.list' :key='idx'>
<!--遍历获取数据中的图片-->
<image class='right-img' :src="j.imgUrl" mode=""></image>
<view class='right-name'>{{j.name}}</view>
</view>
</view>
</block>
</view>
data() {
return {
clentHeight:0,
activeIndex:0,
//左侧数据
leftData:[],
//右侧数据
rightData:[]
}
},
methods: {
//请求数据方法
getData(id){
if( id === (this.activeIndex+1) ){
return;
}
//避免重复获取数据
$http.request({
url:"/goods/list"
}).then((res)=>{
let leftData = [];
let rightData = []
res.forEach(v=>{
leftData.push({
id:v.id,
name:v.name
})
//如果点击的id值相同
if( v.id === (this.activeIndex+1) ){
rightData.push(v.data);
}
})
this.leftData = leftData;
this.rightData = rightData;
//重复点击左侧出对应的右侧
}).catch(()=>{
uni.showToast({
title:'请求失败',
icon:'none'
})
})
},
六、商品详情页
点击和跳转已经实现
mkdir /pages/details
vim details.vue
配置 pages.json
app-plus 里 title-view
type 导航栏样式。“default”-默认样式; “transparent”-滚动透明渐变;“float”-悬浮导航栏。
{
"path" : "pages/details/details",
"style" : {
"navigationBarTitleText": "商品详情",
"app-plus":{
"titleNView":{
"type":"transparent",
"buttons":[
{
"type":"menu"
},
{
"type":"share"
}
]
}
}
}
}
<!-- 单个商品组件 -->
<view class='commodity-item'
v-for="(item,index) in dataList"
:key='index'
:style="'width:'+itemW+';'"
@tap='goDetails'
>
methods:{
goDetails(){
uni.navigateTo({
url:'../../pages/details/details'
})
}
}
七、商品详情页内容块布局
<template>
<view class='details'>
<!--商品图-->
<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<swiper-item v-for='(item,index) in swiperList' :key='index'>
<view class="swiper-item">
<image class='swiper-img' :src="item.imgUrl" mode=""></image>
</view>
</swiper-item>
</swiper>
<!--价格和名称-->
<view class='details-goods'>
<view class='goods-pprice'>¥399.00</view>
<view class='goods-oprice'>¥599.00</view>
<view class='goods-name'>大姨绒毛大款2020年必须买,不买你就不行了,爆款疯狂GG008大姨绒毛大款2020年必须</view>
</view>
<!--商品详情图-->
<view>
<view><image class='details-img' src="../../static/img/detail1.jpg" mode=""></image></view>
<view><image class='details-img' src="../../static/img/detail2.jpg" mode=""></image></view>
<view><image class='details-img' src="../../static/img/detail3.jpg" mode=""></image></view>
<view><image class='details-img' src="../../static/img/detail4.jpg" mode=""></image></view>
<view><image class='details-img' src="../../static/img/detail5.jpg" mode=""></image></view>
</view>
<Card cardTitle='看了又看'></Card>
<CommodityList :dataList='dataList'></CommodityList>
</view>
</template>
script
data() {
return {
swiperList:[
{imgUrl:"../../static/img/details1.jpeg"},
{imgUrl:"../../static/img/details2.jpeg"},
{imgUrl:"../../static/img/details3.jpeg"}
],
style
.swiper-img{
width: 100%;
height: 700rpx;
}
.details-goods{
text-align: center;
font-weight: bold;
font-size:36rpx;
padding:10rpx 0;
}
.details-img{
width: 100%;
}
</style>
八、固定底部布局
<!--底部-->
<view class='details-foot'>
<view class='iconfont icon-xiaoxi'></view>
<view class='iconfont icon-gouwuche'></view>
<view class='add-shopcart'>加入购物车</view>
<view class='purchase'>立即购买</view>
</view>
style
.details-foot .iconfont{
width:60rpx;
height: 60rpx;
border-radius: 100%;
background-color: #000000;
color:#FFFFFF;
text-align: center;
margin:0 10rpx;
}
.add-shopcart{
margin:0 40rpx;
padding:6rpx 30rpx;
background-color: #000000;
color:#FFFFFF;
border-radius: 40rpx;
}
.purchase{
margin:0 40rpx;
padding:6rpx 30rpx;
background-color: #49BDFB;
color:#FFFFFF;
border-radius: 40rpx;
}
九、底部滑块动画效果
deatils.vue
template
<!--底部弹出层-->
<view class="pop" v-show='isShow' @touchmove.stop.prevent=''>
<!--蒙层-->
<view class='pop-mask' @tap='hidePop'></view>
<!--内容块-->
<view class='pop-box' :animation="animationData"></view>
</view>
script
api: uni.createAnimation 赋值 animationData 产生效果
methods: {
showPop(){
var animation = uni.createAnimation({
duration: 200
})
animation.translateY(600).step();
this.animationData = animation.export();
this.isShow = true;
setTimeout(()=>{
animation.translateY(0).step();
this.animationData = animation.export();
},200)
},
hidePop(){
var animation = uni.createAnimation({
duration: 200
})
animation.translateY(600).step();
this.animationData = animation.export();
this.isShow = true;
setTimeout(()=>{
animation.translateY(0).step();
this.isShow = false;
},200)
}
}
style
.pop{
position: fixed;
left:0;
top:0;
width: 100%;
height: 100%;
z-index: 9999;
}
.pop-mask{
position: absolute;
left:0;
top:0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.3);
}
.pop-box{
position: absolute;
left:0;
bottom:0;
width: 100%;
height: 400px;
background-color: #E80080;
}
小问题
:弹出动画后,详情页还在滑动,
@touchmove.stop.prevent=’ ’
十、完善购物车滑块
api:
UniNumberBox
<!--底部弹出层-->
<view class="pop" v-show='isShow' @touchmove.stop.prevent=''>
<!--蒙层-->
<view class='pop-mask' @tap='hidePop'></view>
<!--内容块-->
<view class='pop-box' :animation="animationData">
<view>
<image class='pop-img' src="../../static/img/Furnishing1.jpg" mode=""></image>
</view>
<view class='pop-num'>
<view>购买数量</view>
<UniNumberBox
min='1'
></UniNumberBox>
</view>
<view class='pop-sub'>
确定
</view>
</view>
</view>
.pop-img{
width: 260rpx;
height: 260rpx;
}
.pop-num{
padding:20rpx;
display: flex;
justify-content: space-between;
}
.pop-sub{
line-height: 80rpx;
background-color: #49BDFB;
color:#FFFFFF;
text-align: center;
}
十一、修改返回键行为
页面周期
//修改返回默认行为
onBackPress(){
if(this.isShow){
this.hidePop();
return true;
}
}
十二、商品详情对应数据
Commodity.vue
methods:{
goDetails(id){
uni.navigateTo({
url:'../../pages/details/details?id='+id+''
})
}
details.vue
onLoad(e) {
this.getData(e.id);
}
/server/route/index.js
router.get('/api/goods/id', function(req, res, next) {
let id = req.query.id;
connection.query("select * from goods_search where id="+id+"", function (error, results, fields) {
if (error) throw error;
res.send({
code:"0",
data:results
})
});
});
details.vue
methods: {
//请求商品
getData(id){
$http.request({
url:"/goods/id",
data:{
id:id
}
}).then((res)=>{
this.goodsContent = res[0];
}).catch(()=>{
uni.showToast({
title:'请求失败',
icon:'none'
})
})
},
data() {
return {
goodsContent:{},
<view class='details'>
<!--商品图-->
<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<swiper-item>
<view class="swiper-item">
<image class='swiper-img' :src="goodsContent.imgUrl" mode=""></image>
</view>
</swiper-item>
</swiper>
<!--价格和名称-->
<view class='details-goods'>
<view class='goods-pprice'>¥{{goodsContent.pprice}}</view>
<view class='goods-oprice'>¥{{goodsContent.oprice}}</view>
<view class='goods-name'>{{goodsContent.name}}</view>
</view>
跨域
express 接口设置
router.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
//Access-Control-Allow-Headers ,可根据浏览器的F12查看,把对应的粘贴在这里就行
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
十三、商品分享
使用 uni.share API方式调用社交sdk分享
//点击分享
onNavigationBarButtonTap(e) {
if(e.type==='share'){
uni.share({
"provider":"weixin",
"type":0,
"scene":"WXSceneSession",
"title":this.goodsContent.name,
"href":"http://192.168.8.6:8080/#/pages/details/details?id="+this.goodsContent.id+"",
imageUrl:this.goodsContent.imgUrl,
success: function (res) {
uni.showTabBar({
title:"分享成功"
})
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
}
})
}
},