任务四 工单3
1、团队小组成员对购物车管理功能完成后,顾客需支付其商品金额。在page文件夹下新建pay.vue“支付信息”界面,并在pages.json中设置其相应的样式,代码如下:
{
"path" : "pages/pay/pay",
"style" :
{
"navigationBarTitleText": "支付信息",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}
通过点击menu.vue“点餐”界面中的“去结算”区域,调用@tap="toPay"方法完成页面跳转至pay.vue页面。跳转前得判断用户是否登录,若已登录,则将购物车列表cart数组通过setStorageSync()方法存入本地数据,用于“支付信息”界面核算金额。
toPay() {//支付
if (!this.isLogin) {
uni.navigateTo({//跳转登录页面
url: '/pages/login/login'
})
return
}
uni.showLoading({
title: '加载中'
})
uni.setStorageSync('cart', JSON.parse(JSON.stringify(this.cart)))//将购物车数据存入本地数据
uni.navigateTo({//跳转支付页面
url: '/pages/pay/pay'
})
uni.hideLoading()
}
2、在pay.vue页面的onLoad()生命周期中获取本地购物车数据,用于核算金额。从vuex中引入mapState, mapMutations,并在computed计算属性中通过...mapState(['orderType','address','store'])获取商品取餐类型、顾客配送地址、店铺信息。
通过判断'orderType'商品取餐类型决定界面样式,若orderType为takein,效果如图4.3.4,若为takeout,样式如4.3.5。
中间商品列表list-cell为购物车中选购商品,通过v-for循环对cart购物车遍历进行展示。total()、amount()计算属性为总计金额、实付金额,通过
this.cart.reduce()核算,相关代码如下:
computed: {
...mapState(['orderType','address','store']),//获取取餐类型、配送地址、店铺信息
total() {//总计金额
return this.cart.reduce((acc, cur) => acc + cur.number * cur.price, 0)
},
amount() {//实付金额
return this.cart.reduce((acc, cur) => acc + cur.number * cur.price, 0)
}
},
3、当顾客选择“外卖”配送时,可通过点击第一栏地址信息跳转“我的地址”界面进行切换用户配送地址,选择新的地址后,回退到“支付信息”页面。由于在“首页”界面点击外卖选择完地址后,是跳转到“点餐”界面。因此,需要在chooseAddress()方法中跳转至“我的地址”界面传递一个参数scene=pay进行区分,跳转路劲为url: '/pages/address/address?scene=pay'。
在address.vue的onLoad()方法中获取到scene参数,进行判断。代码如下:
data() {
return {
...
...
scene: 'menu', //判断哪个页面过来
}
},
async onLoad({scene}) {
this.scene = scene || 'menu'
},
...
...
methods: {
...mapMutations(['SET_ADDRESS','SET_ADDRESSES', 'SET_ORDER_TYPE']),
chooseAddress(address) {
this.SET_ADDRESS(address) //添加派送的地址
this.SET_ORDER_TYPE('takeout') //设置状态,自取还是外卖
if(this.scene == 'menu') {//如果是从地址点过来的选择地址,则跳转menu,选择商品
uni.switchTab({
url: '/pages/menu/menu'
})
} else if(this.scene == 'pay') {//如果是支付信息页面跳过来的,重新选择地址后直接返回支付信息页面
uni.navigateTo({
url: '/pages/pay/pay'
})
}
}
4、通过与列表中的云鲤券、备注交互跳转至功能所在处,如图4.4.6为交互处。重要相关代码如下:
goToPackages() {//跳转云鲤券界面
uni.navigateTo({
url: '/pages/packages/packages'
})
},
goToRemark() {//跳转备注页面
uni.navigateTo({
url: '/pages/remark/remark?remark=' + this.form.remark
})
},
通过goToRemark()方法跳转至“备注”界面。在page文件夹下新建remark.vue“备注”界面,并在pages.json中设置其相应的样式,代码如下:
{
"path": "pages/remark/remark",
"style": {
"navigationBarTitleText": "备注",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}
“备注”界面中需注意,quickInputs为快速备注信息,点击某项信息后通过handleQuickInput(item)方法拼接到this.remark中。若备注中信息超过50字则字体显示为红色,效果如图4.3.7。
备注信息填写完毕后,通过点击下方“完成”按钮返回上一界面,返回时带入包含备注信息的参数this.remark,此块重要相关代码如下:
methods: {
handleQuickInput(item) {
this.remark = this.remark.concat(" ", item)
},
submit() {
uni.navigateTo({//返回支付页面
url: "/pages/pay/pay?remark=" + this.remark
})
}
}
回到“支付信息”界面后,在备注栏中将显示备注信息,效果如图4.3.8。
5、当顾客所有信息填写完毕后,将对所有商品进行付款操作。若顾客选择的为“外卖”类型,则弹出模态框进行用户地址确认(效果如图4.3.9),确认后清除购物车中商品并跳转至“取餐信息”界面。
通过@tap="submit"进行付款交互,定义布尔参数ensureAddressModalVisible判断是否提示地址模态框,代码如下:
submit() { //提交付款
if (this.orderType == 'takeout') { //如果为外卖,提醒地址
this.ensureAddressModalVisible = true
} else { //直接走支付
this.pay()
}
}
由于后台开发工程师正在对项目进行研发,前端小组成员使用import orders from '@/api/orders'模拟订单数据。支付后,将订单数据通过SET_ORDER()方法存入,同时调用uni.removeStorageSync('cart')方法将购物车中商品清除,跳转至“取餐信息”界面(take-foods.vue)。
pay() {
uni.showLoading({
title: '加载中'
})
//测试订单
let order = this.orderType == 'takein' ? orders[0] : orders[1]
order = Object.assign(order, {
status: 1
})
this.SET_ORDER(order)
uni.removeStorageSync('cart')
uni.reLaunch({
url: '/pages/take-foods/take-foods'
})
uni.hideLoading()
}
6、至此,支付结算界面相关功能开发工作已经完成。团队成员应使用SourceTree工具进行版本控制提交,以便为本次工单的开发代码创建历史版本记录。
menu.vue 跳转代码
...
...
...
<!-- 购物车栏 begin -->
<view class="cart-box" v-if="cart.length > 0">
<view class="mark">
<image src="/static/images/menu/cart.png" class="cart-img" @tap="openCartPopup"></image>
<view class="tag">{{ getCartGoodsNumber }}</view>
</view>
<view class="price">¥{{ getCartGoodsPrice }}</view>
<button type="primary" class="pay-btn" @tap="toPay" :disabled="disabledPay">
{{ disabledPay ? `差${spread}元起送` : '去结算' }}
</button>
</view>
<!-- 购物车栏 end -->
...
...
toPay() {//支付
if (!this.isLogin) {
uni.navigateTo({
url: '/pages/login/login'
})
return
}
uni.showLoading({
title: '加载中'
})
uni.setStorageSync('cart', JSON.parse(JSON.stringify(this.cart)))//将购物车数据存入本地数据
uni.navigateTo({
url: '/pages/pay/pay'
})
uni.hideLoading()
}
...
...
pay.vue 完整代码
<template>
<!-- 支付页面 -->
<view class="container position-relative">
<view style="padding-bottom: 130rpx;">
<!-- 相关信息(自取、外卖) begin -->
<view class="section-1">
<!--第一栏 用户自取 店名-->
<template v-if="orderType == 'takein'">
<list-cell class="location">
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="store-name flex-fill">
{{ store.name }}
</view>
<image src="/static/images/navigator-1.png" class="arrow"></image>
</view>
</list-cell>
</template>
<!-- 外卖 选择地址栏 样式 -->
<template v-else>
<list-cell @click="chooseAddress">
<view class="w-100 d-flex flex-column">
<view class="d-flex align-items-center justify-content-between mb-10">
<view class="font-size-extra-lg text-color-base">{{ address.street }}</view>
<image src="/static/images/navigator-1.png" class="arrow"></image>
</view>
<view class="d-flex text-color-assist font-size-sm align-items-center">
<view class="mr-10">{{ address.accept_name }}</view>
<view class="mr-10">{{ !address.sex ? '先生' : '女士' }}</view>
<view>{{ address.mobile }}</view>
</view>
</view>
</list-cell>
</template>
<!-- 第二栏 用户自取 样式 -->
<template v-if="orderType == 'takein'">
<list-cell arrow class="meal-time">
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="title">取餐时间</view>
<view class="time">立即用餐</view>
</view>
</list-cell>
<list-cell class="contact" last :hover="false">
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="title" style="flex: 1 0 auto !important;">联系电话</view>
<view class="time">
<input class="text-right" placeholder="请输入手机号码" value="18666600000" />
</view>
<view class="contact-tip font-size-sm" style="flex: 1 0 auto !important;">自动填写</view>
</view>
</list-cell>
</template>
<!-- 外卖 样式 -->
<template v-else>
<list-cell>
<view class="w-100 d-flex flex-column">
<view class="d-flex align-items-center font-size-base text-color-base">
<view class="flex-fill">预计送达时间</view>
<view class="mr-10">15:18</view>
<image src="/static/images/navigator-1.png" class="arrow"></image>
</view>
<view class="font-size-base text-color-primary">
特殊时期减少接触,请修改下方订单备注
</view>
</view>
</list-cell>
</template>
</view>
<!-- 相关信息(自取、外卖) end -->
<!-- 购物车列表 begin -->
<view class="section-2">
<view class="cart d-flex flex-column">
<list-cell last v-for="(item, index) in cart" :key="index">
<view class="w-100 d-flex flex-column">
<view class="d-flex align-items-center mb-10">
<view class="name-and-props overflow-hidden">
<view class="text-color-base font-size-lg">
{{ item.name }}
</view>
</view>
<view
class="d-flex flex-fill justify-content-between align-items-center text-color-base font-size-lg">
<view>x{{ item.number }}</view>
<view>¥{{ item.price }}</view>
</view>
</view>
<view class="text-truncate font-size-base text-color-assist">
{{ item.props_text }}
</view>
</view>
</list-cell>
<template v-if="orderType == 'takeout'">
<list-cell last v-if="store.packing_fee > 0">
<view class="w-100 d-flex font-size-base align-items-center justify-content-between">
<view>包装费</view>
<view>¥{{ parseFloat(store.packing_fee) }}</view>
</view>
</list-cell>
<list-cell last v-if="store.delivery_cost > 0">
<view class="w-100 d-flex font-size-base align-items-center justify-content-between">
<view>配送费</view>
<view>¥{{ parseFloat(store.delivery_cost) }}</view>
</view>
</list-cell>
</template>
</view>
<list-cell arrow @click="goToPackages">
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="text-color-base">云鲤券</view>
<view class="text-color-primary">超值购买优惠券大礼包</view>
</view>
</list-cell>
<list-cell arrow>
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="text-color-base">礼品卡</view>
<view class="text-color-primary">请选择</view>
</view>
</list-cell>
<list-cell last>
<view class="flex-fill d-flex justify-content-end align-items-center">
<view>总计¥{{ total }},实付</view>
<view class="font-size-extra-lg font-weight-bold">¥{{ amount }}</view>
</view>
</list-cell>
</view>
<!-- 购物车列表 end -->
<view class="d-flex align-items-center justify-content-start font-size-sm text-color-warning"
style="padding: 20rpx 0;">
<view class="iconfont iconhelp line-height-100"></view>
<view>优惠券不与满赠、满减活动共享</view>
</view>
<!-- 支付方式 begin -->
<view class="payment">
<list-cell last :hover="false">
<text>支付方式</text>
</list-cell>
<list-cell>
<view class="d-flex align-items-center justify-content-between w-100 disabled">
<view class="iconfont iconbalance line-height-100 payment-icon"></view>
<view class="flex-fill">余额支付(余额¥0)</view>
<view class="font-size-sm">余额不足</view>
<view class="iconfont iconradio-button-off line-height-100 checkbox"></view>
</view>
</list-cell>
<list-cell last>
<view class="d-flex align-items-center justify-content-between w-100">
<view class="iconfont iconwxpay line-height-100 payment-icon" style="color: #7EB73A;"></view>
<view class="flex-fill">微信支付</view>
<view class="iconfont iconradio-button-on line-height-100 checkbox checked"></view>
</view>
</list-cell>
</view>
<!-- 支付方式 end -->
<!-- 备注 begin -->
<list-cell arrow last @click="goToRemark">
<view class="d-flex flex-fill align-items-center justify-content-between overflow-hidden">
<view class="flex-shrink-0 mr-20">备注</view>
<view class="text-color-primary flex-fill text-truncate text-right">{{ form.remark || '点击填写备注' }}
</view>
</view>
</list-cell>
<!-- 备注 end -->
</view>
<!-- 付款栏 begin -->
<view
class="w-100 pay-box position-fixed fixed-bottom d-flex align-items-center justify-content-between bg-white">
<view class="font-size-sm" style="margin-left: 20rpx;">合计:</view>
<view class="font-size-lg flex-fill">¥{{ amount }}</view>
<view class="bg-primary h-100 d-flex align-items-center just-content-center text-color-white font-size-base"
style="padding: 0 60rpx;" @tap="submit">
付款
</view>
</view>
<!-- 付款栏 end -->
<!-- 地址确认模态框 -->
<modal :show="ensureAddressModalVisible" custom :mask-closable="false" width="90%">
<view class="modal-content">
<view class="d-flex justify-content-end">
<image src="/static/images/pay/close.png" style="width: 40rpx; height: 40rpx;"
@tap="ensureAddressModalVisible=false"></image>
</view>
<view class="d-flex just-content-center align-items-center" style="margin-bottom: 40px;">
<view class="font-size-extra-lg text-color-base">请再次确认下单地址</view>
</view>
<view
class="d-flex font-size-base text-color-base font-weight-bold align-items-center justify-content-between mb-20">
<view>{{ address.accept_name }} {{ address.sex ? '女士' : '先生' }}</view>
<view>{{ address.mobile }}</view>
</view>
<view class="d-flex font-size-sm text-color-assist align-items-center justify-content-between mb-40">
<view>{{ address.street + address.door_number }}</view>
<button type="primary" size="mini" plain class="change-address-btn"
@click="chooseAddress">修改地址</button>
</view>
<button type="primary" class="pay_btn" @tap="pay">确认并付款</button>
</view>
</modal>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from 'vuex'
import orders from '@/api/orders'
export default {
data() {
return {
cart: [],
form: {
remark: ''
},
ensureAddressModalVisible: false //地址模态框确认栏
};
},
onLoad(option) {
const {
remark
} = option
this.cart = uni.getStorageSync('cart') //获取本地购物车数据
remark && this.$set(this.form, 'remark', remark) //确保新增的属性能够实时更新视图
},
methods: {
...mapMutations(['SET_ORDER']),
chooseAddress() { //跳转选择地址
uni.navigateTo({
url: '/pages/address/address?scene=pay'
})
},
goToPackages() { //跳转云鲤券界面
uni.navigateTo({
url: '/pages/packages/packages'
})
},
goToRemark() { //跳转备注页面
uni.navigateTo({
url: '/pages/remark/remark?remark=' + this.form.remark
})
},
submit() { //提交付款
if (this.orderType == 'takeout') { //如果为外卖,提醒地址
this.ensureAddressModalVisible = true
} else { //直接走支付
this.pay()
}
},
pay() {
uni.showLoading({
title: '加载中'
})
//测试订单
let order = this.orderType == 'takein' ? orders[0] : orders[1]
order = Object.assign(order, {
status: 1
})
this.SET_ORDER(order)
uni.removeStorageSync('cart')
uni.reLaunch({
url: '/pages/take-foods/take-foods'
})
uni.hideLoading()
}
},
computed: {
...mapState(['orderType', 'address', 'store']),
total() { //总计金额
return this.cart.reduce((acc, cur) => acc + cur.number * cur.price, 0)
},
amount() { //实付金额
return this.cart.reduce((acc, cur) => acc + cur.number * cur.price, 0)
}
},
}
</script>
<style lang="scss">
.container {
padding: 30rpx;
}
.arrow {
width: 50rpx;
height: 50rpx;
position: relative;
margin-right: -10rpx;
}
.location {
.store-name {
font-size: $font-size-lg;
}
.iconfont {
font-size: 50rpx;
line-height: 100%;
color: $color-primary;
}
}
.section-1 {
margin-bottom: 30rpx;
.contact {
.contact-tip {
margin-left: 10rpx;
border: 2rpx solid $color-primary;
padding: 6rpx 10rpx;
color: $color-primary;
}
}
}
.section-2 {
.name-and-props {
width: 65%;
}
}
.payment {
margin-bottom: 30rpx;
.disabled {
color: $text-color-grey;
}
.payment-icon {
font-size: 44rpx;
margin-right: 10rpx;
}
.checkbox {
font-size: 36rpx;
margin-left: 10rpx;
}
.checked {
color: $color-primary;
}
}
.pay-box {
box-shadow: 0 0 20rpx rgba(0, 0, 0, .1);
height: 100rpx;
}
.modal-content {
.change-address-btn {
line-height: 2;
padding: 0 1em;
}
.pay_btn {
width: 100%;
border-radius: 50rem !important;
line-height: 3;
}
}
</style>
remark.vue 完整代码
<template>
<!-- 备注页面 -->
<view class="container w-100 h-100 overflow-hidden">
<view class="textarea">
<textarea placeholder-class="text-color-assist font-size-base" v-model="remark"
class="bg-white w-100 border-box font-size-base remark"
:class="{'text-color-danger': remarkLength > 50, 'text-color-assist' : remarkLength <=50}"
placeholder="请填写备注信息" focus/>
<view class="tips" :class="{'text-color-danger': remarkLength > 50, 'text-color-assist' : remarkLength <=50}">
{{ remarkLength }}/50
</view>
</view>
<view class="d-flex font-size-base text-color-assist" style="margin: 40rpx 0;">
快捷输入
</view>
<view class="quick-inputs d-flex flex-wrap justify-content-start">
<view class="quick-input" v-for="(item, index) in quickInputs" :key="index" @tap="handleQuickInput(item)">
{{ item }}
</view>
</view>
<view class="d-flex just-content-center align-items-center" style="margin-top: 60rpx;">
<button type="primary" class="submit-btn font-size-base" @tap="submit">完成</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
remark: '',
quickInputs: [
'请放门把手上', '请放门口', '请放前台桌上', '如地址封闭管理,请电话与我联系'
]
};
},
onLoad({remark}) {
this.remark = remark
},
computed: {//计算属性
remarkLength() {
return this.remark.length
},
isDanger() {
return this.remark.length > 50
}
},
methods: {
handleQuickInput(item) {
this.remark = this.remark.concat(" ", item)
},
submit() {
uni.navigateTo({//返回支付页面
url: "/pages/pay/pay?remark=" + this.remark
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 30rpx 40rpx;
.textarea {
position: relative;
.remark {
border-radius: 8rpx;
padding: 30rpx 40rpx;
height: 320rpx;
color: $font-size-base;
}
.tips {
position: absolute;
bottom: 30rpx;
right: 40rpx;
}
}
.quick-inputs {
padding-right: 20rpx;
.quick-input {
background-color: #FFFFFF;
border: 2rpx solid $color-primary;
color: $color-primary;
font-size: $font-size-base;
padding: 16rpx 26rpx;
margin-right: 20rpx;
margin-bottom: 20rpx;
}
}
.submit-btn {
width: 90%;
height: 80rpx;
border-radius: 40rpx;
line-height: 80rpx;
}
}
</style>