任务6-3操作步骤:会员储值功能

任务六 工单3

效果图


6.3.1 会员储值界面效果图.png

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”交互操作,可以进入“会员储值”界面,其使用方法如下:


6.3.2 会员储值交互处.png
    balance() {//进入会员充值界面
        if(!this.isLogin) {
            this.login()
            return
        }
        uni.navigateTo({
            url: '/pages/balance/balance'
        })
    }

2、按照UI设计效果图进行页面布局研发时,将页面分为两个区域,实现多维度信息展示,如图6.3.3。


6.3.3 页面切分区域.png

       在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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容