黑馬商城實(shí)戰(zhàn)項(xiàng)目
項(xiàng)目搭建
利用HBuilder X創(chuàng)建基本項(xiàng)目結(jié)構(gòu)
運(yùn)行項(xiàng)目
-
整理基本項(xiàng)目結(jié)構(gòu)郭膛,并修改窗口外觀
"globalStyle": { "navigationBarTextStyle": "white", "navigationBarTitleText": "黑馬商城", "navigationBarBackgroundColor": "#1989fa", "backgroundColor": "#F8F8F8" }
配置tabbar
創(chuàng)建tabbar對(duì)應(yīng)的四個(gè)頁(yè)面和圖標(biāo)準(zhǔn)備好
-
將頁(yè)面路徑配置到pages.json中的pages數(shù)組中
"pages": [ //pages數(shù)組中第一項(xiàng)表示應(yīng)用啟動(dòng)頁(yè)掀序,參考:https://uniapp.dcloud.io/collocation/pages { "path": "pages/index/index" }, { "path": "pages/member/member" }, { "path": "pages/cart/cart" }, { "path": "pages/search/search" } ]
-
配置tabbar
{ "tabBar": { "list": [ { "pagePath":"pages/index/index", "text":"首頁(yè)", "iconPath":"static/icon/home.png", "selectedIconPath":"static/icon/home-active.png" }, { "pagePath":"pages/member/member", "text":"會(huì)員", "iconPath":"static/icon/member.png", "selectedIconPath":"static/icon/member-active.png" }, { "pagePath":"pages/cart/cart", "text":"購(gòu)物車(chē)", "iconPath":"static/icon/cart.png", "selectedIconPath":"static/icon/cart-active.png" }, { "pagePath":"pages/search/search", "text":"搜索", "iconPath":"static/icon/search.png", "selectedIconPath":"static/icon/search-active.png" } ] } }
獲取輪播圖數(shù)據(jù)
-
封裝uni.request請(qǐng)求锤窑,并掛在到全局
-
創(chuàng)建util》api.js
// 封裝get請(qǐng)求 const baseUrl = "http://localhost:8082" export const myRequest = (options)=>{ return new Promise((resolve,reject)=>{ uni.request({ method: options.method, data: options.data, url: baseUrl+options.url, success(res) { if(res.data.status !== 0) { return uni.showToast({ title: '獲取數(shù)據(jù)失敗' }) } resolve(res) }, fail(err) { uni.showToast({ title: '獲取數(shù)據(jù)失敗' }) reject(err) } }) }) }
-
在main.js中導(dǎo)入并掛載到全局
import { myRequest } from './util/api.js' Vue.prototype.$myRequest = myReques
-
-
獲取輪播圖的數(shù)據(jù)
-
定義獲取輪播圖的方法
methods: { async getSwipers () { const res = await this.$myRequest({ method: 'GET', url: '/api/getlunbo' }) this.swipers = res.data.message } }
-
在onLoad中調(diào)用該方法
this.getSwipers()
-
實(shí)現(xiàn)輪播圖的結(jié)構(gòu)和數(shù)據(jù)渲染
-
定義輪播圖的基本結(jié)構(gòu)
<swiper class="swiper" indicator-dots :autoplay="true" :interval="2000" circular> <swiper-item v-for="item in swipers" :key="item.id"> <image :src="item.img"></image> </swiper-item> </swiper>
-
樣式影锈,在工具中安裝scss
<style lang="scss"> .home{ swiper{ height: 380rpx; image{ width: 750rpx; height: 380rpx; } } } </style>
實(shí)現(xiàn)菜單導(dǎo)航結(jié)構(gòu)
引入字體圖標(biāo)初始化樣式
<style>
@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";
}
</style>
完成菜單導(dǎo)航基本結(jié)構(gòu)
<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>聯(lián)系我們</text>
</view>
<view class="item">
<view class="iconfont icon-guanyuwomen"></view>
<text>社區(qū)圖片</text>
</view>
<view class="item">
<view class="iconfont icon-shipin"></view>
<text>視頻專(zhuān)區(qū)</text>
</view>
</view>
菜單導(dǎo)航樣式
.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;
}
}
實(shí)現(xiàn)推薦商品列表
定義基本結(jié)構(gòu)
<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>
美化樣式
.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;
}
}
}
}
獲取數(shù)據(jù)
-
定義獲取數(shù)據(jù)的方法
// 獲取推薦商品 async getGoods () { const res = await this.$myRequest({ url: '/api/getgoods?pageindex=1' }) this.goods = res.data.message }
-
在onLoad生命周期中調(diào)用該方法
this.getGoods()
渲染數(shù)據(jù)
-
通過(guò)v-for渲染數(shù)據(jù)
<view class="hot_goods"> <view class="tit">推薦商品</view> <!-- 一般用法 --> <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> </view>
完成黑馬超市頁(yè)面
改造導(dǎo)航菜單
-
定義數(shù)據(jù)
navs: [ { icons: "iconfont icon-ziyuan", title: "黑馬超市", path: "/pages/goods/list" }, { icons: "iconfont icon-tupian", title: "社區(qū)圖片", path: "/pages/pics/pics" }, { icons: "iconfont icon-guanyuwomen", title: "聯(lián)系我們", path: "/pages/contact/contact" }, { icons: "iconfont icon-shipin", title: "學(xué)習(xí)視頻", path: "/pages/videos/videos" } ]
-
渲染數(shù)據(jù)
<view class="nav"> <view class="item" v-for="(item,index) in navs" :key="index"> <view :class="item.icons"></view> <text>{{item.title}}</text> </view> </view>
-
給導(dǎo)航菜單注冊(cè)點(diǎn)擊事件
<view class="goods_item" v-for="item in goods" :key="item.id">
-
定義跳轉(zhuǎn)的方法
goNavigator (url) { uni.navigateTo({ url }) }
創(chuàng)建黑馬超市頁(yè)面
- 創(chuàng)建頁(yè)面,goods>list.vue
- 將頁(yè)面路勁配置到pages文件中万搔,修改標(biāo)題
封裝商品列表組件
-
在components下面創(chuàng)建goods>list.vue
<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> </template> <script> export default { props:{ goods:Array } } </script> <style lang="scss"> .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: 150px; display: block; 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; } } } </style>
-
在首頁(yè)引入該組件
import goodsList from "../../components/goods-list/index.vue" components: { "goods-list":goodsList }
-
使用組件并將數(shù)據(jù)傳遞到組件內(nèi)部
<goods-list :goods="goods"></goods-list>
渲染商品列表
-
定義獲取商品列表數(shù)據(jù)的方法并調(diào)用
<script> export default { data () { return { goods: [] } }, methods: { async getGoods () { const res = await this.$myRequest({ url: '/api/getgoods?pageindex=1' }) this.goods = res.data.message }, }, onLoad () { this.getGoods() } } </script>
-
引入商品組件并使用
<template> <view class="goods_list"> <goods-list :goods="goods"></goods-list> </view> </template> <script> import goodsList from "../../components/goods-list/index.vue" export default { components: { "goods-list": goodsList } } </script>
實(shí)現(xiàn)上拉加載更多
-
通過(guò)onReachBottom來(lái)監(jiān)聽(tīng)觸底
onReachBottom () { this.pageindex++ this.getGoods() }
-
修改給goods賦值
this.goods = [...this.goods,...res.data.message]
-
動(dòng)態(tài)顯示底線
-
通過(guò)onReachBottom監(jiān)聽(tīng)是否還有更多
if(this.pageindex*10>this.goods.length) return this.flag = true
-
通過(guò)v-if控制底線
<view class="over_line" v-if="flag">----------我是有底線的----------</view>
-
實(shí)現(xiàn)下拉刷新
-
通過(guò)onPullDownRefresh進(jìn)行下拉刷新的操作
onPullDownRefresh() { this.goods = [] this.pageindex = 1 this.flag = false setTimeout(()=>{ this.getGoods(()=>{ uni.stopPullDownRefresh() }) },1000) }
關(guān)于我們
實(shí)現(xiàn)社區(qū)圖片
實(shí)現(xiàn)資訊列表
實(shí)現(xiàn)列表項(xiàng)的結(jié)構(gòu)和樣式
結(jié)構(gòu)
<view class="news_item">
<image src="../../static/logo.png"></image>
<view class="content">
<view class="tit">1季度多家房企利潤(rùn)跌幅超50% 去庫(kù)存促銷(xiāo)戰(zhàn)打響</view>
<view class="info">
<text>發(fā)表時(shí)間:2019-12-23</text>
<text>瀏覽:1次</text>
</view>
</view>
</view>
樣式
.news{
.news_item{
display: flex;
padding: 5px 10px;
border-bottom: 3px solid $shop-color;
image{
width: 300rpx;
height: 150rpx;
}
.content {
padding: 5px 10px;
position: relative;
.tit{
font-size: 30rpx;
}
.info{
font-size: 26rpx;
position: absolute;
left: 10px;
bottom: 5px;
text:nth-child(2){
margin-left: 20px;
}
}
}
}
}
封裝為組件
創(chuàng)建news-item.vue
<template>
<view>
<view class="news_item" v-for="item in data" :key="item.id">
<image :src="item.img_url"></image>
<view class="content">
<view class="tit">{{item.title}}</view>
<view class="info">
<text>發(fā)表時(shí)間:{{item.add_time | formatDate}}</text>
<text>瀏覽:{{item.click+123}}次</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: ['data'],
filters:{
formatDate(data){
const date = new Date(data)
console.log(date)
const day = date.getMonth().toString().padStart(2,'0')+'-'+date.getDay().toString().padStart(2,'0')
return date.getFullYear()+'-'+day
}
}
}
</script>
<style lang="scss">
.news_item{
display: flex;
padding: 5px 10px;
border-bottom: 1rpx solid $shop-color;
image{
max-width: 200rpx;
min-width: 200rpx;
height: 150rpx;
}
.content {
padding: 5px 10px;
position: relative;
.tit{
font-size: 30rpx;
}
.info{
font-size: 26rpx;
position: absolute;
left: 10px;
bottom: 5px;
text:nth-child(2){
margin-left: 20px;
}
}
}
}
</style>
在新聞頁(yè)面導(dǎo)入并使用
<template>
<view class="news">
<new-item :data="newsList"></new-item>
</view>
</template>
<script>
import newItem from '../../components/new-item/new-item.vue'
export default {
data() {
return {
newsList: []
}
},
methods: {
async getNewsList () {
const res = await this.$myRequest({
url:'/api/getnewslist'
})
this.newsList = res.data.message
}
},
onLoad () {
this.getNewsList()
},
components: {
"new-item":newItem
}
}
</script>
<style lang="scss">
</style>
點(diǎn)擊列表進(jìn)入詳情
-
點(diǎn)擊子組件列表項(xiàng)通過(guò)this.$emit觸發(fā)父組件的方法
navigatorTo (item) { this.$emit('clickItem',item) }
-
父組件通過(guò)注冊(cè)clickItem事件及事件函數(shù)實(shí)現(xiàn)跳轉(zhuǎn)操作
<template> <view class="news"> <new-item :data="newsList" @clickItem="goDetail"></new-item> </view> </template> <script> export default { methods: { goDetail (data) { console.log(data) uni.navigateTo({ url: '/pages/news-detail/news-detail' }) } } } </script>
新建/pages/news-detail/news-detail頁(yè)面
實(shí)現(xiàn)資訊詳情
實(shí)現(xiàn)基本結(jié)構(gòu)
<template>
<view class="news_detail">
<view class="news_title">
標(biāo)題
</view>
<view class="info">
<text>發(fā)表時(shí)間:</text>
<text>瀏覽:</text>
</view>
<view class="content">
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style lang="scss">
.news_detail {
padding: 15rpx;
.news_title{
text-align: center;
font-size: 32rpx;
}
.info{
font-size: 28rpx;
display: flex;
justify-content: space-between;
}
}
</style>
獲取詳情的數(shù)據(jù)
methods: {
async getNewsDetail(id){
const res = await this.$myRequest({
url: '/api/getnew/'+id
})
console.log(res)
this.newsDetail = res.data.message[0]
}
},
onLoad (options){
this.getNewsDetail(options.id)
}
實(shí)現(xiàn)詳情的渲染
<view class="news_title">
{{newsDetail.title}}
</view>
<view class="info">
<text>發(fā)表時(shí)間:{{newsDetail.add_time | formatDate}}</text>
<text>瀏覽:{{newsDetail.click}}</text>
</view>
<view class="content">
<rich-text :nodes="newsDetail.content"></rich-text>
</view>
實(shí)現(xiàn)商品詳情頁(yè)
商品列表注冊(cè)點(diǎn)擊事件
<template>
<view class="goods_list">
<view @click="itemClick(item)" class="goods_item" v-for="item in goods" :key="item.id">
</view>
</view>
</template>
<script>
export default {
props:{
goods:Array
},
methods: {
itemClick(item) {
this.$emit('itemClick',item)
}
}
}
</script>
父組件綁定自定義事件進(jìn)行跳轉(zhuǎn)
<goods-list @itemClick="godetail" :goods="goods"></goods-list>
godetail (item) {
uni.navigateTo({
url: '/pages/goods-detail/goods-detail?id='+item.id
})
}
創(chuàng)建商品詳情頁(yè)
獲取詳情輪播圖數(shù)據(jù)
methods: {
async getDetail(){
const res = await this.$myRequest({
url:'/api/getthumimages/'+this.id
})
console.log(res)
}
},
onLoad(options){
this.id = options.id
this.getDetail()
}
渲染輪播圖
<swiper indicator-dots>
<swiper-item v-for="item in swipers" :key="item.src">
<image :src="item.src"></image>
</swiper-item>
</swiper>
<style lang="scss">
.goods_detail{
swiper{
height: 700rpx;
image{
width: 100%;
height: 100%;
}
}
}
</style>
獲取商品信息
methods: {
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()
}