项目源码地址:https://github.com/whynot-todo/uniapp_demo.git
1 项目搭建
1.1 新建项目并运行
进行项目的搭建,详情点击
- 这里我们创建的名称为
uniapp_demo
- 运行,这里我们看上面的详情点击就可以,初次运行注意路径的配置
- 当我们运行起来收的界面式这样的
1.2 按照项目效果配置基本外观
- 打开
pages.json
,在globalStyle
下配置全局的样式,你可以参照官网来进行配置,详情点击
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "商城",
"navigationBarBackgroundColor": "##b50e03",
"backgroundColor": "#F8F8F8"
}
配置好后是这个样子的,因为局部配置的优先级较高,所以navigationBar
显示的是uniapp
2 搭建框架
2.1 配置tabbar
1. 新建页面
根据效果图,tabber联系着四个页面,分别是首页、咨询、购物车、会员,我们新建四个页面分别是
pages/index/index
pages/cart/cart
pages/member/member
pages/news/news
2. 新建页面步骤
- 在
pages
文件夹下右击,选择新建页面
- 然后对页面进行少许的配置
需要注意的是
- 选择模板,这里是使用了
scss
- 是否在
pages.json
中注册路径,这里的四个我们都需要
- 按照上面的步骤新建完四个文件
3. 配置tabber
关于tabbar的配置,详情点击
"tabBar": {
"selectedColor": "#b50e03",
"color": "#ccc",
"list": [{
"text": "首页",
"pagePath": "pages/index/index",
"iconPath": "static/icon/home.png",
"selectedIconPath": "static/icon/home-active.png"
},
{
"text": "资讯",
"pagePath": "pages/news/news",
"iconPath": "static/icon/news.png",
"selectedIconPath": "static/icon/news-active.png"
},
{
"text": "购物车",
"pagePath": "pages/cart/cart",
"iconPath": "static/icon/cart.png",
"selectedIconPath": "static/icon/cart-active.png"
},
{
"text": "会员",
"pagePath": "pages/member/member",
"iconPath": "static/icon/member.png",
"selectedIconPath": "static/icon/member-active.png"
}
]
}
需要注意的是
- 配置的顺序,
pages
数组的第一个元素表示启动时的默认页 - 路径的书写
当我们把上面配置好后,就会出现以下效果,表示已经配置成功了
3 从后台获取数据
3.1 封装请求组件
- 我们在根目录下新建
utils
文件夹,并新建api.js
文件,写上如下代码
const BASE_URL = 'http://localhost:8082'
export const myRequest = (options)=>{
return new Promise((resolve,reject)=>{
uni.request({
url:BASE_URL+options.url,
method: options.method || 'GET',
data: options.data || {},
success: (res)=>{
if(res.data.status !== 0) {
return uni.showToast({
title: '获取数据失败'
})
}
resolve(res)
},
fail: (err)=>{
uni.showToast({
title: '请求接口失败'
})
reject(err)
}
})
})
}
需要注意以下几点
- 我们要返回一个
promise
对象 - 函数接收的参数是一个配置对象,他应该包括接口文档中的所需要的三个数据
{
url,//请求的路径,
method,//请求方法
data//请求需要的参数
}
- 将请求方法添加到全局
打开mian.js
=> 引入组件 => 将方法添加到原形中
import {myRequest} from 'utils/api.js'
Vue.prototype.$myRequest = myRequest
3.2 搭建后台应用
- 下载
phpStudy
,并打开,这里使用的版本为8.1.0.1 - 启动MYSQL服务
- 点击
打开
,点击SQL_Front
- 点击打开
- 右击
localhost
,选择新建
,选择数据库
- 输入数据库名称(要与我们的本地sql文件相同),点击确定
- 在我们新建的数据库上右击,点击输入,点击sql文件,
- 将本地文件进行导入,注意字符集为utf-8
- 导入之后我们可以关闭,当前的页面,但不能退出phpStudy
- 打开服务器文件夹 ,运行命令
node app.js
,将后台的项目运行起来。
4 首页页面
4.1 配置首页基础数据
navigationBarTitleText
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "商城首页"
}
}
配置之后
4.2 轮播图
获取轮播图数据
- 接口文档
- 根据接口文档,定义方法
data() {
return {
swipers: []
},
methods: {
async getSwipers () {
const res = await this.$myRequest({
url: '/api/getlunbo'
})
this.swipers = res.data.message
},
}
- 在生命周期函数中进行调用
onLoad() {
this.getSwipers()
/* console.log(this.swipers) 得不到相应的数据 */
},
- 有的小伙伴可能会在
onLoad
函数中直接打印this.swipers
注意这样是无法得到相应的数据的,因为他是一个异步函数
将获取到的数据显示到网页中
- 我们需要使用swiper组件,详情点击
<swiper indicator-dots circular autoplay indicator-color='rgba(249,180,141,.7)'>
<swiper-item v-for="item in swipers" :key="item.id">
<image :src="item.img"></image>
</swiper-item>
</swiper>
- 我们还需要给swiper一个宽度和高度
swiper{
width: 750rpx;
height: 380rpx;
image{
height: 100%;
width: 100%;
}
}
说明
indicator-dots
为指示小圆点circular
循环滑动autoplay
自动轮播indicator-color
原点背景颜色
4.3 页面中的导航
1. 静态的导航
- 打开
素材/font_nav
将我们下载好的字体图标放到static/fonts
文件夹下,我们只需要其中的五个文件- iconfont.woff
- iconfont.woff2
- iconfont.ttf
- svg
- eot
2. 打开素材/font_nav
中的iconfont.css
文件,将代码复制,放到App.vue
文件下,注意要修改引用的路径以~@开头,写路径,@后面不能跟 .,我们使用的时候就可以class="iconfont icon-name"
@font-face {font-family: "iconfont";
src: url('~@/static/fonts/iconfont.eot?t=1576335469449'); /* IE9 */
src: url('~@/static/fonts/iconfont.eot?t=1576335469449#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAVMAAsAAAAAChwAAAT/AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDKAqHQIYhATYCJAMUCwwABCAFhG0HUBurCFGUTk6f7EeBu6eIlFFajBY208XLlGHx/6/hiYfvx76d+2xFJLlUn75pp4tCoyQ8NgiJUJjuJfzNpSrpXIDoeDbs3GjyH7Lfv+zTy1mok+GyEhYAW8AKaPvz/6eeG1QvDUo6SQW8bE4KYTDSMRj1R/5x7/TP88Dmo7Jc/misYXOelwWYYEBjbGxrAQ7KEvQW8SwuJBO4nkCvRQ21q/PbB5hXQLdAvDU6gHmbRQnhhG598xlb1lEvsu70PvEBfFU/H/+hF1IkNYO+149nMRz+6v7/fwrv4JAqI6T5uXCbRMYWUIhnX8+tKT22ZUrvJ3PaCdCNryR+Sb2RfRPT+c9kbdF0s788siIRDWj6GXkWL/ySEsVkfsmyftoYKiIZsiQfJ+Y2ll7MgdgDiE/AMF+CumLWsCSpdFXpjsNkefoatW8WSY8xapRhreWm1a9vWvVEM6edaQdPapImTzTLLG4BwEBG/HJsK4bVEKkStJbxbUYy3othvO2ofJA7jBARcUZ4illDuEL29TB2C/3AGWj4WUfGYbG25LTICaEk1jWBR8KWC0bqQJ+EjJYmOnvisiHzqtifvZ08X7NYzBaJWAJBglDIRIi3Fd/C3cbZjqEKvkh9Qmx2SKCgMAhD15VHVkCQMCWOP72YnLB0euHUpROzJ/PEYiR6wxK8ShC+ZsryVibGlbIzorOjqRKnhwyOKTIlMRoPSLgMlYvInDIKdwsQ8K2YbO2PReRsyxzZvrK0dWxUYUrIE2sPj8+sHNw6tUVhj0AgslvelUfhDnNG4k0YwgedbyX/ovPRL8SnqwOt/atMII/RGGMacyXGJOaq+MtGYDFMOL1Hk8OSJdfqARNMZkPmnDVh5DP0bjDpx6XAB6RO0JkqM4F0fNSNZMKXB20QqI//n+swdzCvnv1/JszOzMEszPsskD8RZtrarNB14gGhubjWlduokVnWD9e9PyCQd1jtvPjStKTGs6VZFT/xjhhW0hpkG6PoC0wW8iGUrPK9prFY4AM+9GF8APhKfIvr5tct+IoAxU/eE3ILa4yrDxTgUfkrG0lPTtshU4pGtXT9UJqywWkDhzyi/5ka15D+WlPn1Gd+QYF7s0j3dXTuS0oStZLum6on86NOwIbPcydIP3+STiDFaYk1Z6WFLPt7b7M6EhMA+Gr+qtDilfia4mPFr0p8JZUS973QHulzYJxsPjJ6xdeW7Wd75WHHrZsGDXaUb1hacCuQVmrOkt1DjdgTxSeY3EcWDqCzRLxB9PmH2DNplYhzhCDbNLfzY7dKy1jb/dqNFfBTXbWg8vhWsH1JP9IN0VQmIcif4isHay1vtVDzNIiyxeirbpZ0JKEXg3QAAqN+8jFMLN9E6DZhQtJlAbJuS2ght6DqcwRNt1Potelicp8xbEqUHmz4IBCGfUIy6DtkwyZoIb+hmvQLzXAQoddNbM3ZZyWKFk+YAqEYwxGayJZKsMuiVnxH2ucBW+Uq94m4MkHstjrO5AWVxHkMqPq6J6JQsS3wnGxGeW7RsU0pklYi4rbbbeV3plZkC1h0iZGAIDEUGkFGxCopubpYdL3/DtG8XIBDSn4knxBWMbWjrpZODPRCU8YqWZfGlT6tRwjFOynMKqBz6iI5xizk/FulSES0JCkizrY2q6Tialrl64ppLD0VysKcNVLkKFGjae8S40w5K14OB+WS9lKjkR/YgsrZsRnZzAAA') format('woff2'),
url('~@/static/fonts/iconfont.woff?t=1576335469449') format('woff'),
url('~@/static/fonts/iconfont.ttf?t=1576335469449') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('~@/static/fonts/iconfont.svg?t=1576335469449#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-shipin:before {
content: "\f0024";
}
.icon-tupian:before {
content: "\e650";
}
.icon-guanyuwomen:before {
content: "\e608";
}
.icon-ziyuan:before {
content: "\e60d";
}
- 我们在
index.vue
中写上如下结构
<view class="nav">
<view class="item">
<view class="iconfont icon-ziyuan"></view>
<text>超市首页</text>
</view>
<view class="item">
<view class="iconfont icon-tupian"></view>
<text>联系我们</text>
</view>
<view class="item">
<view class="iconfont icon-guanyuwomen"></view>
<text>社区图片</text>
</view>
<view class="item">
<view class="iconfont icon-shipin"></view>
<text>视频专区</text>
</view>
</view>
- 我们在
uni.scss
中定义一个主题颜色,如果使用的是less
我们可以新建一个全局文件定义变量
/* 颜色变量 */
$shop-color: #b50e03;
- 在
index.vue
添加CSS样式,这里使用的是flex
和百分比布局,并引用全局变量
.nav{
display: flex;
align-items: center;
.item{
width: 25%;
text-align: center;
view{
background: $shop-color;
line-height: 120rpx;
width: 120rpx;
height: 120rpx;
border-radius: 90px;
margin:10px auto;
}
text{
font-size: 15px;
}
}
.iconfont{
font-size: 25px;
color: #fff;
height: 50px;
}
.icon-tupian{
font-size: 20px;
}
}
当我们配置完后,就会出现以下画面
动态的页面导航
我们为四个导航按钮新建四个页面,上面有配置页面的步骤在搭建框架
/pages/goods/goods
/pages/pics/pics
/pages/contact/contact
/pages/videos/videos
我们需要为四个图标定义点击跳转事件,我们需要用到uni.navigateTo(OBJECT)
详情点击
优化导航结构
- 因为一个一个的绑定优点麻烦,我们对于导航的结构优化一下,在
index.vue
的data(){}
中,添加如下配置数据
navs: [
{
icons: "iconfont icon-ziyuan",
title: "超市首页",
path: "/pages/goods/goods"
},
{
icons: "iconfont icon-tupian",
title: "社区图片",
path: "/pages/pics/pics"
},
{
icons: "iconfont icon-guanyuwomen",
title: "联系我们",
path: "/pages/contact/contact"
},
{
icons: "iconfont icon-shipin",
title: "学习视频",
path: "/pages/videos/videos"
}
]
- 将上面的重复结构删掉,我们使用
v-for
遍历渲染结构,并定义点击跳转的方法
<view class="nav">
<view class="item" v-for="(item,index) in navs" :key="index" @click="navItemClick(item.path)">
<view :class="item.icons"></view>
<text>{{item.title}}</text>
</view>
</view>
- 我们在
methods
中定义点击的方法
navItemClick(url) {
uni.navigateTo({
url
})
}
- 之后我们就可以点击跳转了
4.4 首页推荐商品
单个的静态结构
- 我们根据效果图先写一个单个的静态结构
<!-- 推荐商品 -->
<view class="hot_goods">
<view class="tit">推荐商品</view>
<!-- 单个商品-->
<view class="goods_list">
<view class="goods_item">
<image></image>
<view class="price">
<text>1299</text>
<text>12990</text>
</view>
<view class="name">华为(HUAWEI)荣耀6Plus 16G双4G版</view>
</view>
</view>
</view>
- css样式
.hot_goods {
background: #eee;
.tit {
border-top: 2px solid #eee;
border-bottom: 2px solid #eee;
margin-top: 20px;
margin-bottom: 3px;
color: $shop-color;
height: 50px;
line-height: 50px;
text-align: center;
letter-spacing: 20px;
background: #fff;
}
.goods_list {
display: flex;
padding: 0 15rpx;
justify-content: space-between;
overflow: hidden;
flex-wrap: wrap;
.goods_item {
width: 355rpx;
margin-bottom: 15rpx;
background: #fff;
padding: 10px;
box-sizing: border-box;
image {
height: 150px;
width: auto;
mix-width: 160px;
margin: 10px auto;
}
.price {
font-size: 18px;
color: red;
padding: 8px 0;
text:nth-child(2) {
color: #ccc;
text-decoration: line-through;
margin-left: 10px;
font-size: 13px;
}
}
.name {
font-size: 14px;
}
}
}
}
商品组件
- 观看效果图,我们发现商品卡片是重复的结构,我们可以把它抽成一个组件
- 我们在根目录下新建
components/goods-list
文件夹,并新建goods-list.vue
文件
3. 我们把相应的代码复制到goods-list.vue
<template>
<view>
<!-- 一般用法 -->
<view class="goods_list">
<view class="goods_item">
<image></image>
<view class="price">
<text>1299</text>
<text>12990</text>
</view>
<view class="name">华为(HUAWEI)荣耀6Plus 16G双4G版</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
};
}
}
</script>
<style lang="less">
.goods_list {
display: flex;
padding: 0 15rpx;
justify-content: space-between;
overflow: hidden;
flex-wrap: wrap;
.goods_item {
width: 355rpx;
margin-bottom: 15rpx;
background: #fff;
padding: 10px;
box-sizing: border-box;
image {
width: 80%;
height: 150px;
display: block;
margin: auto;
}
.price {
font-size: 18px;
color: red;
padding: 8px 0;
text:nth-child(2) {
color: #ccc;
text-decoration: line-through;
margin-left: 10px;
font-size: 13px;
}
}
.name {
font-size: 14px;
}
}
}
</style>
- 在
index.vue
中引入并映射成组件
import GoodsList from '../../components/goods-list/goods-list.vue'
components:{
GoodsList
}
- 模板中
<view class="hot_goods">
<view class="tit">推荐商品</view>
<goods-list></goods-list>
</view>
- 效果图
获取动态的数据
- 在
index.vue
,methods
中定义方法获取数据
接口文档
methods
// 获取热门商品列表数据
async getHotGoods () {
const res = await this.$myRequest({
url: '/api/getgoods?pageindex=1'
})
this.goods = res.data.message
},
data
data(){
return {
goods: []
}
}
- 在声明周期函数中,发送请求
onLoad() {
this.getHotGoods()
},
将数据传递到子组件标签
- 传递
<goods-list :goods="goods"></goods-list>
- 子组件标签接收数据,并依据接口文档进行数据的渲染
goods-list
export default {
props:['goods'],
}
template
<view class="goods_list">
<view class="goods_item" v-for="item in goods" :key="item.id">
<image :src="item.img_url"></image>
<view class="price">
<text>¥{{item.sell_price}}</text>
<text>¥{{item.market_price}}</text>
</view>
<view class="name">{{item.title}}</view>
</view>
</view>
配置完之后的效果
5 单个商品的详情页
5.1 点击跳转
效果
我们新建一个页面为goods-detail
绑定点击监听
为每一个商品绑定点击监听
index.vue
<goods-list @goodsItemClick="goGoodsDetail" :goods="goods"></goods-list>
定义方法
goGoodsDetail (id) {
uni.navigateTo({
url: '/pages/goods-detail/goods-detail?id='+id
})
}
子组件触发方法
goods-list.vue
<view class="goods_item" v-for="item in goods" :key="item.id" @click="navigator(item.id)">
定义方法
methods: {
navigator (id) {
this.$emit('goodsItemClick',id)
}
}
5.2 获取详情页数据
在生命周期中得到,传来的数据
得到相应的数据,并在data中保存
data() {
return {
id: 0,
},
onLoad (options) {
this.id = options.id
}
获取展示轮播图数据
我们观察到展示的商品图片是一个轮播的形式,我们可以先写静态组件,这样也是比较科学,这里就不展示了,直接获取数据写结构
接口文档
根据接口文档写请求方法
data() {
return {
id: 0,
swipers: [],
}
},
methods: {
async getSwipers () {
const res = await this.$myRequest({
url: '/api/getthumimages/'+this.id
})
this.swipers = res.data.message
},
}
在生命周期函数中执行
onLoad (options) {
this.getSwipers()
},
页面结构
<view class="goods_detail">
<swiper indicator-dots>
<swiper-item v-for="(item,index) in swipers" :key="index">
<image :src="item.src"></image>
</swiper-item>
</swiper>
</view>
css
swiper{
height: 700rpx;
image{
width: 100%;
height: 100%;
}
}
当我们把上面写好之后,详情轮播图已经写好了。
获取商品信息
接口文档
根据接口文档写方法
data() {
return {
info: {},
},
async getDetailInfo () {
const res = await this.$myRequest({
url: '/api/goods/getinfo/'+this.id
})
this.info = res.data.message[0]
},
生命周期中调用方法
onLoad (options) {
this.id = options.id
this.getDetailInfo()
},
页面结构
<view class="box1">
<view class="price">
<text>¥{{info.sell_price}}</text>
<text>¥{{info.market_price}}</text>
</view>
<view class="goods_name">{{info.title}}</view>
</view>
<view class="line"></view>
<view class="box2">
<view>货号:{{info.goods_no}}</view>
<view>库存:{{info.stock_quantity}}</view>
</view>
<view class="line"></view>
样式
.box1 {
padding: 10px;
.price {
font-size: 35rpx;
color: $shop-color;
line-height: 80rpx;
text:nth-child(2){
color: #ccc;
font-size: 28rpx;
text-decoration: line-through;
margin-left: 20rpx;
}
}
.goods_name{
font-size: 32rpx;
line-height: 60rpx;
}
}
.box2 {
padding: 0 10px;
font-size: 32rpx;
line-height: 70rpx;
}
.line {
height: 10rpx;
width: 750rpx;
background: #eee;
}
效果图
获取商品介绍数据
接口文档
根据接口文档,写请求方法
data() {
return {
content: '',
},
async getDetailContent () {
const res = await this.$myRequest({
url: '/api/goods/getdesc/'+this.id
})
this.content = res.data.message[0].content
},
在生命周期中执行方法
onLoad (options) {
this.getDetailContent()
},
页面结构
注意到放回的数据是富文本,所以这里我们需要做特殊处理,使用<rich-text>
,详情点击
<view class="box3">
<view class="tit">详情介绍</view>
<view class="content">
<rich-text :nodes="content"></rich-text>
</view>
</view>
css
.box3 {
padding-bottom: 50px;
.tit{
font-size: 32rpx;
padding-left: 10px;
border-bottom: 1px solid #eee;
line-height: 70rpx;
}
.content {
padding: 10px;
font-size: 28rpx;
color: #333;
line-height: 50rpx;
}
}
当我们将上面的代码写完的时候,就会出现以下效果图
我们发现整个图片没有完全显示,我们观看以下打印的数据,发现每个图片上都有一个类属性gomeImgLoad
,我们给他一个宽度,一定要注意放在全局样式中App.vue
.gomeImgLoad{
width: 750rpx;
height: auto;
}
再次刷新,我们发现已经好了
5.3 UI组件的使用
寻找组件
- 打开uniapp的官网,点击组件,找到扩展组件,找到商品导航
2. 点开网页之后,我们导入组件,点击使用HBuilderX导入组件
3. 然后选择导入的项目就可以了
4. 我们将官网上面的代码,复制到我们的项目
引入组件
import uniGoodsNav from '@/components/uni-goods-nav/uni-goods-nav.vue'
export default {
components: {uniGoodsNav}
}
使用组件
<view class="goods_nav">
<uni-goods-nav :fill="true" :options="options" :buttonGroup="buttonGroup" @click="onClick" @buttonClick="buttonClick" />
</view>
export default {
data () {
return {
options: [{
icon: 'headphones',
text: '客服'
}, {
icon: 'shop',
text: '店铺',
info: 2,
infoBackgroundColor:'#007aff',
infoColor:"red"
}, {
icon: 'cart',
text: '购物车',
info: 2
}],
buttonGroup: [{
text: '加入购物车',
backgroundColor: '#ff0000',
color: '#fff'
},
{
text: '立即购买',
backgroundColor: '#ffa200',
color: '#fff'
}
]
}
},
methods: {
onClick (e) {
uni.showToast({
title: `点击${e.content.text}`,
icon: 'none'
})
},
buttonClick (e) {
console.log(e)
this.options[2].info++
}
}
}
- 我们可以选择将其固定在页面的底部,在
goods-detail.vue
中写上如下代码
.goods_nav {
position: fixed;
bottom:0;
width: 100%;
}
- 我们可以根据配置选择我们想要的功能,详情点击
6 超市首页
6.1 完成基础布局
这里的商品展示其实是和我们的首页的展示是差不多的,我们就可以利用首页的组件来进行作品的展示。
- 打开
pages/goods/goods.vue
文件,引入我们定义的goods-list.vue
文件,并映射成组件。
import goodsList from '../../components/goods-list/goods-list.vue'
components: {"goods-list":goodsList},
<view class="goods_list">
<goods-list ></goods-list>
</view>
6.2 触底加载
触底加载属于生命周期函数,详情点击
获取数据
获取数据。与首页不同的是,首页只获取了一页数据,而这里到达底部之后会再次获取数据
接口文档
根据接口文档定义方法
data() {
return {
pageindex: 1,
goods: [],
}
},
methods: {
// 获取商品列表数据
async getGoodsList() {
const res = await this.$myRequest({
url: '/api/getgoods?pageindex='+this.pageindex
})
this.goods = res.data.message
},
}
在生命周期中执行
onLoad () {
this.getGoodsList()
},
向组件中传递数据
<goods-list :goods="goods"></goods-list>
完成后,页面的效果如下
触底加载
- 触底加载需要的生命周期函数为
onReachBottom
,触底加载主要用于加载第二个页面的数据,所以写如下代码
onReachBottom() {
this.pageindex++
this.getGoodsList()
},
- 对于请求函数做修改
/* this.goods = res.data.message */
this.goods = [...this.goods,...res.data.message]
触底优化
当数据加载完毕的时候,提示数据加载完毕
<view class="goods_list">
<goods-list :goods="goods"></goods-list>
<view class="isOver" v-if="flag">-----我是有底线的-----</view>
</view>
flag
为标识是否触底
data() {
return {
flag: false
}
},
在onReachBottom
生命周期函数中加上这样一段代码
- 每次请求增加是个商品
if(this.goods.length<this.pageindex*10) return this.flag = true
修饰提示线
.goods_list {
background: #eee;;
}
.isOver {
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 28rpx;
}
6.3 下拉刷新
- 根据官方文档,我们要首先开启下拉刷新,打开
pages.json
,我们顺着可以改一下导航的文字
"style": {
"navigationBarTitleText": "商品列表",
"enablePullDownRefresh": true
}
- 下拉刷新函数
onPullDownRefresh() {
console.log('下拉刷新了')
this.pageindex = 1
this.goods = []
this.flag = false
this.getGoodsList()
}
- 需要注意的是我们要停止下拉刷新,但是我们要考虑什么时候停止下拉刷新,因为请求是一个异步函数,一般我们都应该是在请求数据之后停止下拉刷新,所以我们对于请求函数和下拉刷新函数优化一下
onPullDownRefresh() {
console.log('下拉刷新了')
this.pageindex = 1
this.goods = []
this.flag = false
setTimeout(()=>{
this.getGoodsList(()=>{
uni.stopPullDownRefresh()
})
},1000)
},
async getGoodsList(callBack) {
const res = await this.$myRequest({
url: '/api/getgoods?pageindex='+this.pageindex
})
this.goods = [...this.goods,...res.data.message]
callBack && callBack()
},
6.4 点击跳转到详情页
在我们写首页数据的时候,在goods-list.vue
组件中已经定义了一个函数goodsItemClick
,我们在此组件中再次写方法,当然你可以选择复制粘贴index.vue
<goods-list @goodsItemClick="goGoodsDetail" :goods="goods"></goods-list>
goGoodsDetail (id) {
uni.navigateTo({
url: '/pages/goods-detail/goods-detail?id='+id
})
}
7. 联系我们
7.1 静态页面结构
打开contact.vue
根据效果图搭建静态的页面结构
<view class="contact">
<image class="img" src="http://www.itcast.cn/2018czydz/images/gywmban.jpg"></image>
<view class="info">
<view @click="phone">联系电话:400-618-9090 ( 点击拨打 )</view>
<view>地址:xxxxx</view>
</view>
<map class="map" :longitude="longitude" :scale="scale" :latitude="latitude" :markers="markers"></map>
</view>
样式
.contact{
.img{
width: 750rpx;
height: 320rpx;
}
.info{
padding: 10rpx 20rpx;
font-size: 30rpx;
view{
line-height: 80rpx;
border-bottom: 1px solid #eee;
}
}
.map{
width: 750rpx;
height: 750rpx;
}
}
- 在这里我们使用了一个组件:
map
详情点击 - 并定义了一个点击的方法,用来点击拨打电话
- 注意我们需要给地图组件一个宽高
7.2 动态
export default {
data() {
return {
longitude: 120.363172,
latitude: 30.312212,
scale: 13,
markers:[
{
longitude: 120.363172,
latitude: 30.312212,
iconPath: '../../static/logo.png',
width: 30,
height: 30
}
]
}
},
methods: {
phone() {
uni.makePhoneCall({
phoneNumber: '400-618-9090'
})
}
}
}
这里我们只用了一个api,makePhoneCall
他是用来打电话的,详情点击
8. 社区图片
打开pics.vue
,观察效果图,我们发现他的左右两个部分,都属于滚动的部分,这就需要我们的组件来控制scroll-view
,详情点击
我们可以将静态的组件写好,再去写动态的组件,这里就不演示了,直接从后台获取数据
接口文档
- 获取左侧图片的分类
- 获取右侧的图片
写请求的方法
data() {
return {
cates: [],
active: 0,
secondData: []
}
},
methods: {
async getPicsCate () {
const res = await this.$myRequest({
url: '/api/getimgcategory'
})
this.cates = res.data.message
this.leftClickHandle(0,this.cates[0].id)
},
async leftClickHandle (index,id) {
this.active = index
// 获取右侧的数据
const res = await this.$myRequest({
url: '/api/getimages/'+id
})
this.secondData = res.data.message
},
}
active
标识当前点击的是哪一个leftClickHandle
点击左侧是获取右侧的数据,并修改active
cates
表示左侧的分类secondData
表示右侧的分类
在生命周期中执行请求
onLoad () {
this.getPicsCate()
}
写静态的结构
<view class="pics">
<scroll-view class="left" scroll-y>
<view
@click="leftClickHandle(index,item.id)"
:class="active===index?'active':''"
v-for="(item,index) in cates"
:key="item.id">
{{item.title}}
</view>
</scroll-view>
<scroll-view class="right" scroll-y>
<view class="item" v-for="item in secondData" :key="item.id">
<image @click="previewImg(item.img_url)" :src="item.img_url"></image>
<text>{{item.title}}</text>
</view>
<text v-if="secondData.length === 0">暂无数据</text>
</scroll-view>
</view>
- 这里使用了一个预览图片的api 详情点击
写样式
page{
height: 100%;
}
.pics{
height: 100%;
display: flex;
.left{
width: 200rpx;
height: 100%;
border-right:1px solid #eee;
view{
height: 60px;
line-height: 60px;
color: #333;
text-align: center;
font-size: 30rpx;
border-top:1px solid #eee;
}
.active{
background: $shop-color;
color: #fff;
}
}
.right{
height: 100%;
width: 520rpx;
margin: 10rpx auto;
.item{
image{
width: 520rpx;
height: 520rpx;
border-radius: 5px;
}
text{
font-size: 30rpx;
line-height: 60rpx;
}
}
}
}
写完之后的效果图
9. 咨讯
打开news.vue
9.1 封装基础组件
根据效果图写静态的页面
这里就不做展示了,直接请求数据,小伙伴们自己写吧,观察效果图,他是一列一列的重复数据,我们可以把它封装成一个组件news-list.vue
接口文档
根据接口文档写方法
news.vue
data() {
return {
newsList: []
}
},
methods:{
async getNews() {
const res = await this.$myRequest({
url: '/api/getnewslist'
})
this.newsList = res.data.message
},
},
在生命周期中执行方法
onLoad () {
this.getNews()
}
封装静态组件
news-list.vue
<view>
<view class="news_item" @click="navigator(item.id)" v-for="item in list" :key="item.id">
<image :src="item.img_url"></image>
<view class="right">
<view class="tit">
{{item.title}}
</view>
<view class="info">
<text>发表时间:{{item.add_time | formatDate}}</text>
<text>浏览:{{item.click}}</text>
</view>
</view>
</view>
</view>
- 注意这里用了一个局部过滤器
- 定义了一个方法,用于外部触发
export default {
props: ['list'],
filters: {
formatDate (date) {
const nDate = new Date(date)
const year = nDate.getFullYear()
const month = nDate.getMonth().toString().padStart(2,0)
const day = nDate.getDay().toString().padStart(2,0)
return year+'-'+month+'-'+day
}
},
methods:{
navigator (id) {
this.$emit('itemClick',id)
}
}
}
- 对于
padStart
的说明,详情点击
.news_item{
display: flex;
padding: 10rpx 20rpx;
border-bottom: 1px solid $shop-color;
image{
min-width: 200rpx;
max-width: 200rpx;
height: 150rpx;
}
.right{
margin-left: 15rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.tit{
font-size: 30rpx;
}
.info{
font-size: 24rpx;
text:nth-child(2){
margin-left: 30rpx;
}
}
}
}
将组件引入news.vue
,并传递数据
<template>
<view class="news">
<news-item :list="newsList"></news-item>
</view>
</template>
<script>
import newsItem from '../../components/news-item/news-item.vue'
components: {"news-item":newsItem},
</script>
9.2 跳转到详情页
新建页面
新建一个页面news-detail.vue
,根据效果图写静态的页面
接口文档
根据接口文档写方法
data() {
return {
id: 0,
detail: {}
}
},
methods: {
async getNewsDetail () {
const res = await this.$myRequest({
url: '/api/getnew/'+this.id
})
console.log(res)
this.detail = res.data.message[0]
}
},
在生命周期中执行方法
onLoad(options){
this.id = options.id
this.getNewsDetail()
}
- 因为是一个列表,点击列表的时候一定会传递一个标识,我们从声明周期中拿到标识
写详情页的静态结构
<view class="news_detail">
<text class="title">{{detail.title}}</text>
<view class="info">
<text>发表时间:{{detail.add_time | formatDate}}</text>
<text>浏览:{{detail.click}}</text>
</view>
<view class="content">
<rich-text :nodes="detail.content"></rich-text>
</view>
</view>
.news_detail{
font-size: 30rpx;
padding: 0 20rpx;
.title{
text-align: center;
width: 710rpx;
display: block;
margin: 20rpx 0;
}
.info{
display: flex;
justify-content: space-between;
}
}
定义全局过滤器
main.js
Vue.filter('formatDate',(date)=>{
const nDate = new Date(date)
const year = nDate.getFullYear()
const month = nDate.getMonth().toString().padStart(2,0)
const day = nDate.getDay().toString().padStart(2,0)
return year+'-'+month+'-'+day
})
在news.vue
中定义跳转的方法
<news-item @itemClick="goDetail" :list="newsList"></news-item>
goDetail (id) {
uni.navigateTo({
url: '/pages/news-detail/news-detail?id='+id
})
}
10 打包
10.1 小程序打包
10.2 H5打包
10.3 App打包
11 优化
以下内容在分支optimize上,运行命令 git clone -b optimize https://github.com/whynot-todo/uniapp_demo.git
轮播优化
- 主要优化了图片的展示方式以及指示点,导航的渐变底色
index.vue
image {
height: 370rpx;
width: 95%;
border-radius: 10rpx;
display: block;
margin: 5rpx auto ;
background-color: $shop-color;
}
view{
background-image: linear-gradient(to right, #fd0f02,#b50e03);
}
<swiper indicator-dots circular autoplay indicator-color='rgba(250,250,250,.5)'indicator-active-color='#b50e03'></swiper>
禁止滚动条
pages.json
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "商城首页",
"app-plus":{
"scrollIndicator":"none"
}
}
}
App.vue
::-webkit-scrollbar,scrollbar {
display: none !important;
width: 0px;
height: 0px;
}