任务六 工单3
效果图
1、在顾客进行支付时,为了简化支付流程并提升用户体验,在会员服务系统中新增了“会员储值”功能模块。开发团队小组决定在UniApp的page目录下新建一个名为balance.vue的新文件,该文件将负责构建会员储值界面,并与支付功能实现无缝对接。
为了确保界面设计的一致性和美观性,在项目启动前必须在pages.json文件中添加相应的样式配置,以支持balance.vue组件的视觉展示效果。
系统将在下次登录时为用户展示最新的会员储值状态,包括当前余额、已充值金额以及剩余可用额度等信息。最终的界面效果将与图示6.3.1所示一致,确保用户体验流畅且符合预期。
以下是具体样式实现代码示例:
{
"path" : "pages/balance/balance",
"style" : {
"navigationBarTitleText": "会员储值",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}
通过图6.3.2所示的“balance”交互操作,可以进入“会员储值”界面,其使用方法如下:
balance() {//进入会员充值界面
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/balance/balance'
})
}
2、按照UI设计效果图进行页面布局研发时,将页面分为两个区域,实现多维度信息展示,如图6.3.3。
在onLoad()生命函数中通过调用await this.$api('rechargeCards')获得充值的标准数据amounts,为100、200、300、400元等通过v-for循环遍历amounts数据将其展示,需注意利用动态class完成选择与否。具体代码如下:
在UniApp的初始化阶段(onLoad()),通过调用内置API函数rechargeCards获取标准充值金额数据集,并利用v-for循环动态遍历此数据集。为了实现简洁、高效的展示逻辑,本方案采用动态class技术对各个金额值进行条件性显示:当检测到点击了特定金额时,将对应金额字段的背景颜色设置为绿色以吸引注意力;反之,则保持正常显示状态。具体实现如下:
<view class="d-flex flex-wrap justify-content-between amounts">
<view class="d-flex align-items-center just-content-center amount"
:class="{'bg-primary text-color-white': item.selected, 'bg-base text-color-base': !item.selected}"
v-for="(item, index) in amounts" :key="index" @tap="handleSelected(index)">
<view class="font-size-extra-lg font-weight-bold">{{ parseInt(item.value) }}</view>
<view class="font-size-sm font-weight-light">元</view>
</view>
</view>
在UniApp的组件初始化阶段(onLoad()),通过动态绑定和状态更新实现选择金额的功能。在前端响应区域组件中,首先调用内置API函数获取充值金额数据集,并将该数据集以amounts形式呈现给用户。
当顾客点击某个金额值时,系统会触发@tap事件,由处理函数handleSelected(index)接收。在handleSelected函数中,动态地将selected数组中的所有元素初始化为false,然后通过索引index定位到被选中的金额项,并将其selected属性设置为true。
同时,指令动态绑定selected属性的变化,触发对应类名的更新:当某个amount的selected属性为true时,会将class设为 selected;反之则维持default。具体方法如下:
methods: {
handleSelected(index) {
this.amounts.forEach(item => this.$set(item, 'selected', false))
this.$set(this.amounts[index], 'selected', true)
}
}
3、通过计算属性(computed)实现对充值金额选择的逻辑判断及相应信息展示。具体而言,在前端响应区域组件中,系统首先获取并绑定所有可选的充值金额数据集(amounts),以及每个金额项的selected状态信息。
通过将这两个信息结合为一个对象,利用computed属性计算出是否需要显示使用说明:当且仅当选中某个金额项时,才会显示其对应的使用说明;否则则隐藏该内容。
<template v-if="rechargeCard">
<view class="font-size-lg text-color-base font-weight-bold" style="margin-bottom: 20rpx;">使用说明</view>
<view class="pre-line font-size-sm text-color-assist">
{{ rechargeCard.desc }}
</view>
</template>
...
...
...
computed: {
rechargeCard() {
return this.amounts.find(item => item.selected) || ''
}
},
...
...
其他布局将遵循UI设计规范,并基于设计图实现。
4、至此,本工单的功能开发工作已完成。为确保代码的完整性和可追溯性,建议团队成员使用源代码管理工具(如SourceTree),通过版本控制提交操作记录本次开发成果,并为后续代码维护建立完整的变更历史记录。
balance.vue完整代码:
<template>
<view class="container bg-white w-100 h-100">
<view style="padding-bottom: 220rpx;">
<view class="balance-info d-flex justify-content-between">
<view class="flex-fill d-flex flex-column align-items-between justify-content-between">
<view class="font-size-sm text-color-base">账户余额(元)</view>
<view class="font-size-extra-lg text-color-base font-weight-bold">0</view>
<view class="font-size-sm text-color-primary">交易记录</view>
</view>
<image src="/static/images/balance.png" mode="widthFix"></image>
</view>
<view class="font-size-lg text-color-base font-weight-bold" style="margin-bottom: 20rpx;">储值金额</view>
<view class="d-flex flex-wrap justify-content-between amounts">
<view class="d-flex align-items-center just-content-center amount"
:class="{'bg-primary text-color-white': item.selected, 'bg-base text-color-base': !item.selected}"
v-for="(item, index) in amounts" :key="index" @tap="handleSelected(index)">
<view class="font-size-extra-lg font-weight-bold">{{ parseInt(item.value) }}</view>
<view class="font-size-sm font-weight-light">元</view>
</view>
</view>
<template v-if="rechargeCard">
<view class="font-size-lg text-color-base font-weight-bold" style="margin-bottom: 20rpx;">使用说明</view>
<view class="pre-line font-size-sm text-color-assist">
{{ rechargeCard.desc }}
</view>
</template>
<!-- bottom box begin -->
<view class="bottom-box position-fixed fixed-bottom w-100 d-flex flex-column bg-white">
<view class="protocol font-size-sm d-flex justify-content-start align-items-center">
<view class="iconfont line-height-100" @tap="agree = !agree"
:class="{'iconradio-button-on text-color-primary': agree, 'iconradio-button-off text-color-base': !agree}">
</view>
<view class="text-color-base">我已阅读并同意</view>
<view class="text-color-primary">《储值协议》</view>
</view>
<button type="primary" class="b">购买</button>
</view>
<!-- bottom box end -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
agree: false,
amounts: []
}
},
async onLoad() {
this.amounts = await this.$api('rechargeCards') //获取数据
},
computed: {
rechargeCard() { //判断是否有使用说明
return this.amounts.find(item => item.selected) || ''
}
},
methods: {
handleSelected(index) { //选择金额时标识属性
this.amounts.forEach(item => this.$set(item, 'selected', false))
this.$set(this.amounts[index], 'selected', true)
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 40rpx;
}
.balance-info {
padding: 50rpx 10rpx;
view {
height: calc(366 / 638 * 340);
padding: 10rpx 0;
}
image {
width: 340rpx;
}
}
.amounts {
margin-bottom: 40rpx;
.amount {
width: calc(96% / 3);
padding: 50rpx 0;
margin-bottom: 20rpx;
border-radius: 8rpx;
}
}
.bottom-box {
padding: 0 40rpx;
box-shadow: 0 0 20rpx rgba(6, 6, 6, 0.1);
.protocol {
padding: 36rpx 0;
height: 100rpx;
view {
margin-right: 10rpx;
}
}
button {
height: 70rpx;
line-height: 70rpx;
border-radius: 50rem !important;
margin-bottom: 30rpx;
}
}
</style>
mine.vue 部分跳转代码
...
...
...
<view class="w-100 d-flex align-items-center just-content-center">
<!-- 通过三目运算符判断 -->
<view class="user-grid" @tap="coupons">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.couponNum : '***' }}
</view>
<view class="font-size-sm text-color-assist">云鲤券</view>
</view>
<view class="user-grid" @tap="integrals">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.pointNum : '***' }}
</view>
<view class="font-size-sm text-color-assist">积分商城</view>
</view>
...
...
...
<script>
import {
mapState,
mapGetters
} from 'vuex'
export default {
data() {
return {
// isLogin: false,//false为未登录,true为登录
newIcon: 'https://s3.uuu.ovh/imgs/2024/12/05/6b3856fe6d711a0a.png'
}
},
computed: {
...mapState(['member']),
...mapGetters(['isLogin']),
growthValue() { //计算成长属性
if (!this.isLogin) return 0
const {
currentValue,
needValue
} = this.member
return currentValue / (currentValue + needValue) * 100
}
},
methods: {
login() { //登录
uni.navigateTo({
url: '/pages/login/login'
})
},
...
...
...
userinfo() {//用户信息
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/mine/userinfo'
})
},
services() {//更多服务
uni.navigateTo({
url: '/pages/services/services'
})
},
coupons() {//我的卡券
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/coupons/coupons'
})
},
balance() { //进入会员充值界面
if (!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/balance/balance'
})
},
}
}
</script>