任务六 工单1
效果图:


1、在“我的管理”界面中,交互相关功能时,如“会员码”、“积分商城”、“历史订单”等按钮,需判断用户是否登录。通过store.js文件中引入...mapState(['member'])、...mapGetters(['isLogin'])两个计算参数,从而进行用户状态统计。
定义login()方法作为共享方法使用,完成用户登录功能,如下表1:

对已有页面进行登录逻辑跳转,代码如下:
<script>
import {mapState, mapGetters} from 'vuex'
export default {
data() {
return {
newIcon: 'https://s3.uuu.ovh/imgs/2024/12/05/6b3856fe6d711a0a.png'
}
},
computed: {
...mapState(['member']),
...mapGetters(['isLogin']),
},
methods: {
login() {//登录方法
uni.navigateTo({
url: '/pages/login/login'
})
},
memberCode() {//跳转会员码
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/mine/member-code'
})
},
integrals() {//跳转积分商城
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/integrals/integrals'
})
},
attendance() {//跳转积分签到
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/attendance/attendance'
})
},
orders() {//跳转历史订单
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/orders/orders'
})
},
addresses() {//跳转我的地址
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/address/address'
})
},
}
}
</script>
2、完善“我的管理”界面中用户的信息,通过member对象中的属性进行填充。部分代码如下:
<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>
<view class="user-grid" @tap="balance">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.balance : '***' }}
</view>
<view class="font-size-sm text-color-assist">余额</view>
</view>
<view class="user-grid" @tap="giftCards">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.giftBalance : '***' }}
</view>
<view class="font-size-sm text-color-assist">礼品卡</view>
</view>
用户成长值需通过currentValue(当前成长值)、needValue(需要多少成长值)两个属性进行计算(在计算属性中执行),具体算法如下:
growthValue() {//计算成长属性
if(!this.isLogin) return 0
const {currentValue, needValue} = this.member
return currentValue / (currentValue + needValue) * 100
}
方法定义完成后,通过页面执行,效果如图6.1.2:
<view class="font-size-sm text-color-assist">
当前成长值{{ isLogin ? member.currentValue : 0 }}/{{ isLogin ? member.currentValue + member.needValue : 0 }}
</view>

3、通过点击如图6.1.3处,进入“用户信息”界面。在mine文件夹下创建userinfo.vue页面,同时按照要求设置样式,样式代码如下:
{
"path" : "pages/mine/userinfo",
"style" :
{
"navigationBarTitleText": "用户信息",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}

“用户信息”界面创建完成后,完成相关跳转,方法如下:
userinfo() {//用户信息
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/mine/userinfo'
})
}
根据UI设计师设计效果完成“用户信息”界面。需要注意本页面引用listCell自定义组件完成布局,其次利用uni-app中picker选择器组件,从底部弹起的滚动选择器,效果如图6.1.4。picker支持五种选择器,通过mode来区分,分别是普通选择器,多列选择器,时间选择器,日期选择器,省市区选择器,默认是普通选择器。

利用以下方法获取this.member用户信息,在本界面完成相关属性布局,具体完整代码扫描二维码进行学习查看。
onLoad() {
this.member = this.$store.state.member
}
5、通过与“我的管理”界面下方“更多服务”区域交互,跳转至“更多服务”功能区域。在pages文件夹下创建“更多服务”界面services.vue,并在pages.json中进行样式调试,样式如下:
{
"path": "pages/services/services",
"style": {
"navigationBarTitleText": "更多服务",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}
通过交互跳转至“更多服务”界面后,利用image及view完成页面重复相关布局(效果如图6.1.6),部分代码如下。
...
...
<view class="cell" @tap="helpCenter">
<view class="content">
<image src="/static/images/services/bzzx.png" class="icon"></image>
<view>帮助中心</view>
</view>
<image src="/static/images/navigator-1.png" class="navigator"></image>
</view>
...
...
按照上述方法完成“帮助中心”界面研发,效果如图6.1.7。


6、至此,本工单的相关功能开发工作结束。团队成员应运用SourceTree工具执行版本控制的提交操作,以便为本次工单的开发代码建立历史版本的记录。
mine.vue完整源码:
<template>
<view class="container">
<!-- 上方布局 -->
<view class="position-relative">
<image src="https://s3.uuu.ovh/imgs/2025/04/06/0311c7822ce0e8aa.png" class="bg"></image>
<button type="default" size="mini" class="hym-btn" @tap="memberCode">
<image src="/static/images/mine/hym.png"></image>
<text>会员码</text>
</button>
</view>
<view style="padding: 0 30rpx;">
<!-- 用户信息 -->
<view class="d-flex flex-column bg-white user-box">
<!-- 头像 -->
<view class="d-flex align-items-center">
<view class="avatar">
<image :src="isLogin ? member.avatar : '/static/images/mine/default.png'"></image>
<!-- 用户等级标识 -->
<view class="badge" v-if="isLogin">
<image src="/static/images/mine/level.png"></image>
<view>{{ member.memberLevel }}</view>
</view>
</view>
<view class="d-flex flex-column flex-fill overflow-hidden" style="margin-top: 20rpx;">
<!-- 昵称 -->
<view v-if="isLogin"
class="font-size-lg font-weight-bold d-flex justify-content-start align-items-center"
@tap="userinfo">
<view class="text-truncate">{{ member.nickname }}</view>
<view class="iconfont iconarrow-right line-height-100"></view>
</view>
<view v-else class="font-size-lg font-weight-bold" @tap="login">请点击授权登录</view>
<view class="font-size-sm text-color-assist">
当前成长值{{ isLogin ? member.currentValue : 0 }}/{{ isLogin ? member.currentValue + member.needValue : 0 }}
</view>
<view class="w-100">
<!-- 成长值进度条(未计算) -->
<progress v-if="!isLogin" activeColor="#ADB838" height="8rpx" percent="0"
border-radius="8rpx" />
<progress v-else activeColor="#ADB838" height="8rpx" percent="82" border-radius="8rpx" />
</view>
</view>
<view
class="level-benefit d-flex flex-shrink-0 align-items-center justify-content-end text-color-white bg-warning font-size-sm">
<view>会员权益</view>
<view class="iconfont iconarrow-right line-height-100"></view>
</view>
</view>
<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>
<view class="user-grid" @tap="balance">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.balance : '***' }}
</view>
<view class="font-size-sm text-color-assist">余额</view>
</view>
<view class="user-grid" @tap="giftCards">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.giftBalance : '***' }}
</view>
<view class="font-size-sm text-color-assist">礼品卡</view>
</view>
</view>
</view>
<!-- 用户盒子 -->
<view class="level-benefit-box" v-if="!isLogin">
<view class="d-flex align-items-center justify-content-between font-size-base">
<view class="text-color-base">新用户加入会员,享会员权益</view>
<view class="text-color-primary" @tap="login">立即加入</view>
</view>
<view class="row">
<view class="grid">
<image src="/static/images/mine/rhyl.png"></image>
<view>入会有礼</view>
</view>
<view class="grid">
<image src="/static/images/mine/jfdh.png"></image>
<view>积分兑换</view>
</view>
<view class="grid">
<image src="/static/images/mine/sjtq.png"></image>
<view>升级特权</view>
</view>
<view class="grid">
<image src="/static/images/mine/srtq.png"></image>
<view>生日特权</view>
</view>
<view class="grid">
<image src="/static/images/mine/nxbz.png"></image>
<view>云鲤宝藏</view>
</view>
</view>
</view>
<!-- 中间banner图片 -->
<image src="/static/images/mine/banner.png" class="banner" mode="widthFix"></image>
</view>
<!-- 下方功能列表 -->
<view class="service-box">
<view class="font-size-lg text-color-base font-weight-bold" style="margin-bottom: 20rpx;">我的服务</view>
<view class="row">
<view class="grid" @tap="attendance">
<view class="image">
<image src="/static/images/mine/jfqd.png"></image>
</view>
<view>积分签到</view>
</view>
<view class="grid">
<view class="image">
<image src="/static/images/mine/stxy.png"></image>
</view>
<view>送她心愿</view>
<image :src="newIcon" class="new-badage"></image>
</view>
<view class="grid">
<view class="image">
<image src="/static/images/mine/nxsc.png"></image>
</view>
<view>云鲤商城</view>
</view>
<view class="grid">
<view class="image">
<image src="/static/images/mine/lxkf.png"></image>
</view>
<view>联系客服</view>
</view>
<view class="grid" @tap="orders">
<view class="image">
<image src="/static/images/mine/wddd.png"></image>
</view>
<view>我的订单</view>
</view>
<view class="grid" @tap="userinfo">
<view class="image">
<image src="/static/images/mine/wdzl.png"></image>
</view>
<view>我的资料</view>
</view>
<view class="grid" @tap="addresses">
<view class="image">
<image src="/static/images/mine/shdz.png"></image>
</view>
<view>收货地址</view>
</view>
<view class="grid" @tap="services">
<view class="image">
<image src="/static/images/mine/gdfw.png"></image>
</view>
<view>更多服务</view>
</view>
</view>
</view>
<view class="d-flex just-content-center align-items-center text-color-assist"
style="padding: 30rpx 0; font-size: 22rpx;">
会员卡适用于云鲤奶茶和云鲤酒屋指定范围
</view>
</view>
</template>
<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'
})
},
memberCode() { //会员码
if (!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/mine/member-code'
})
},
integrals() { //积分商城
if (!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/integrals/integrals'
})
},
attendance() { //积分签到
if (!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/attendance/attendance'
})
},
orders() { //历史订单
if (!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/orders/orders'
})
},
addresses() { //我的地址
if (!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/address/address'
})
},
userinfo() {//用户信息
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/mine/userinfo'
})
},
services() {//更多服务
uni.navigateTo({
url: '/pages/services/services'
})
},
}
}
</script>
<style lang="scss" scoped>
page {
height: auto;
min-height: 100%;
}
.bg {
width: 100%;
height: calc(410 / 594 * 750rpx);
}
.hym-btn {
position: absolute;
top: 40rpx;
right: 40rpx;
color: $color-primary;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50rem;
font-size: $font-size-sm;
box-shadow: 0 0 20rpx rgba(66, 66, 66, 0.1);
&::after {
border: 0;
}
image {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
}
}
.user-box {
position: relative;
border-radius: 8rpx;
margin-bottom: 30rpx;
margin-top: -115rpx;
box-shadow: $box-shadow;
}
.avatar {
position: relative;
margin-top: -35rpx;
margin-left: 35rpx;
margin-right: 35rpx;
width: 160rpx;
height: 160rpx;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #FFFFFF;
box-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.2);
image {
width: 140rpx;
height: 140rpx;
border-radius: 100%;
}
.badge {
position: absolute;
right: -10rpx;
bottom: -10rpx;
background-color: #FFFFFF;
border-radius: 50rem;
display: flex;
align-items: center;
justify-content: center;
color: $color-warning;
font-size: 24rpx;
padding: 8rpx 16rpx;
box-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.2);
image {
width: 30rpx;
height: 30rpx;
}
}
}
.level-benefit {
margin-left: 35rpx;
padding: 10rpx 0 10rpx 30rpx;
border-radius: 50rem 0 0 50rem;
}
.user-grid {
width: 25%;
padding: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.value {
margin-bottom: 20rpx;
}
}
.banner {
width: 100%;
border-radius: 8rpx;
margin-bottom: 30rpx;
}
.service-box {
width: 100%;
background-color: #FFFFFF;
padding: 32rpx 30rpx 10rpx;
box-shadow: $box-shadow;
.row {
display: flex;
flex-wrap: wrap;
color: $text-color-assist;
font-size: $font-size-sm;
padding-bottom: -40rpx;
.grid {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 40rpx;
width: 25%;
position: relative;
.image {
image {
width: 80rpx;
height: 80rpx;
margin-bottom: 20rpx;
}
}
.new-badage {
width: 40rpx;
height: 40rpx;
position: absolute;
top: 0;
right: 30rpx;
}
}
}
}
.level-benefit-box {
border-radius: 8rpx;
margin-bottom: 30rpx;
box-shadow: 0 10rpx 8rpx rgba($color: #878889, $alpha: 0.1);
width: 100%;
display: flex;
padding: 30rpx;
flex-direction: column;
background-color: #FFFFFF;
.row {
display: flex;
padding: 30rpx 0 20rpx;
justify-content: space-around;
align-items: center;
.grid {
width: 20%;
display: flex;
flex-direction: column;
font-size: $font-size-sm;
color: $text-color-assist;
align-items: center;
image {
width: 80rpx;
height: 80rpx;
margin-bottom: 10rpx;
}
}
}
}
</style>
userinfo.vue完整代码:
<template>
<!-- 用户信息 -->
<view class="container d-flex flex-column">
<view class="flex-fill form">
<list-cell :hover="false">
<view class="form-input w-100 d-flex align-items-center">
<view class="label">昵称</view>
<view class="input flex-fill">
<input type="text" placeholder="请填写昵称" placeholder-class="text-color-assist font-size-base"
v-model="member.nickname">
</view>
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input w-100 d-flex align-items-center">
<view class="label">手机号码</view>
<view class="input flex-fill">
<input type="text" v-model="member.mobilePhone">
</view>
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input w-100 d-flex align-items-center">
<view class="label">性别</view>
<view class="input flex-fill">
<view class="radio-group">
<view class="radio" :class="{'checked': member.gender == '1'}" style="margin-right: 10rpx;"
@tap="member.gender=1">先生</view>
<view class="radio" :class="{'checked': member.gender == '2'}" @tap="member.gender=2">女士
</view>
</view>
</view>
</view>
</list-cell>
<list-cell :hover="false" :arrow="!member.birthday">
<view class="form-input w-100 d-flex align-items-center">
<view class="label">生日</view>
<view class="input flex-fill">
<picker mode="date" :value="date" :start="startDate" :end="endDate" v-if="!member.birthday"
@change="handleDateChange">
生日当天有惊喜
</picker>
<input type="text" v-else :value="member.birthday" disabled>
</view>
</view>
</list-cell>
<list-cell :hover="false" last>
<view class="form-input w-100 d-flex align-items-center">
<view class="label">入会时间</view>
<view class="input flex-fill">
<input type="text" v-model="member.openingCardDate" disabled>
</view>
</view>
</list-cell>
</view>
<view class="btn-box d-flex align-items-center just-content-center">
<button type="primary" class="save-btn" @tap="save">保存</button>
</view>
</view>
</template>
<script>
import listCell from '@/components/list-cell/list-cell'
export default {
components: {
listCell
},
data() {
const currentDate = this.getDate({
format: true
})
return {
member: {},
date: currentDate
}
},
computed: {
startDate() {
return this.getDate('start');
},
endDate() {
return this.getDate('end');
}
},
onLoad() {
this.member = this.$store.state.member
},
methods: {
getDate(type) {
const date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
if (type === 'start') {
year = year - 60;
} else if (type === 'end') {
year = year + 2;
}
month = month > 9 ? month : '0' + month;;
day = day > 9 ? day : '0' + day;
return `${year}-${month}-${day}`;
},
handleDateChange(e) {
this.member.birthday = e.target.value
},
save() {
const member = Object.assign(this.$store.state.member, this.member)
this.$store.commit('SET_MEMBER', member)
uni.navigateBack()
}
}
}
</script>
<style lang="scss" scoped>
page {
height: 100%;
}
.container {
padding: 20rpx 30rpx;
}
.form {
border-radius: 8rpx;
}
.form-input {
.label {
width: 160rpx;
font-size: $font-size-base;
color: $text-color-base;
}
.input {}
.radio-group {
display: flex;
justify-content: flex-start;
.radio {
padding: 10rpx 30rpx;
border-radius: 6rpx;
border: 2rpx solid $text-color-assist;
color: $text-color-assist;
font-size: $font-size-base;
&.checked {
background-color: $color-primary;
color: $text-color-white;
border: 2rpx solid $color-primary;
}
}
}
}
.btn-box {
height: calc((100vh - 40rpx) / 2);
}
.save-btn {
width: 90%;
border-radius: 50rem !important;
font-size: $font-size-lg;
}
</style>
services.vue完整代码:
<template>
<!-- 更多服务 -->
<view class="container">
<view class="cell" @tap="helpCenter">
<view class="content">
<image src="/static/images/services/bzzx.png" class="icon"></image>
<view>帮助中心</view>
</view>
<image src="/static/images/navigator-1.png" class="navigator"></image>
</view>
<view class="cell">
<view class="content">
<image src="/static/images/services/hyxy.png" class="icon"></image>
<view>关于云鲤</view>
</view>
<image src="/static/images/navigator-1.png" class="navigator"></image>
</view>
<view class="cell">
<view class="content">
<image src="/static/images/services/wdzl.png" class="icon"></image>
<view>云鲤礼物</view>
</view>
<image src="/static/images/navigator-1.png" class="navigator"></image>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
helpCenter() {
uni.navigateTo({
url: '/pages/services/help-center'
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #FFFFFF;
}
.cell {
padding: 30rpx;
display: flex;
align-items: center;
position: relative;
.content {
flex: 1;
display: flex;
align-items: center;
font-size: $font-size-base;
color: $text-color-base;
.icon {
width: 70rpx;
height: 70rpx;
margin-right: 10rpx;
}
}
.navigator {
width: 50rpx;
height: 50rpx;
position: relative;
margin-right: -10rpx;
flex-shrink: 0;
}
&:after {
content: '';
position: absolute;
border-bottom: 2rpx solid #d9d9d9;
-webkit-transform: scaleY(0.8);
transform: scaleY(0.8);
bottom: 0;
right: 30rpx;
left: 30rpx;
}
}
</style>
help-center.vue完整代码
<template>
<!-- 帮助中心 -->
<view class="container">
<view class="cell">
<view class="content">
<image src="/static/images/services/djsm.png" class="icon"></image>
<view>等级说明</view>
</view>
<image src="/static/images/navigator-1.png" class="navigator"></image>
</view>
<view class="cell">
<view class="content">
<image src="/static/images/services/hyxy.png" class="icon"></image>
<view>会员协议</view>
</view>
<image src="/static/images/navigator-1.png" class="navigator"></image>
</view>
<view class="cell">
<view class="content">
<image src="/static/images/services/ystk.png" class="icon"></image>
<view>隐私条款</view>
</view>
<image src="/static/images/navigator-1.png" class="navigator"></image>
</view>
<view class="cell">
<view class="content">
<image src="/static/images/services/cjwt.png" class="icon"></image>
<view>常见问题</view>
</view>
<image src="/static/images/navigator-1.png" class="navigator"></image>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #FFFFFF;
}
.cell {
padding: 30rpx;
display: flex;
align-items: center;
position: relative;
.content {
flex: 1;
display: flex;
align-items: center;
font-size: $font-size-base;
color: $text-color-base;
.icon {
width: 70rpx;
height: 70rpx;
margin-right: 10rpx;
}
}
.navigator {
width: 50rpx;
height: 50rpx;
position: relative;
margin-right: -10rpx;
flex-shrink: 0;
}
&:after {
content: '';
position: absolute;
border-bottom: 2rpx solid #d9d9d9;
-webkit-transform: scaleY(0.8);
transform: scaleY(0.8);
bottom: 0;
right: 30rpx;
left: 30rpx;
}
}
</style>