今日学习内容
- 登录模块
- 会员中心模块
登录模块
1、定义类型
export type loginResult = {
accout: string
avatar: string
id: string
mobile: string
nickname: string
token: string
}
2、封装接口
import type { loginResult } from "@/types/member"
import { http } from "@/utils/http"
export type loginParams = {
code: string
encryptedData: string
iv: string
}
/**
* 小程序快捷登录
* @returns
*/
export const loginWx = (data: loginParams) => {
return http<loginResult>({
method: 'POST',
url: '/login/wxMin',
data
})
}
/**
* 小程序登录_内测版
* @param phoneNumber 模拟手机号码
*/
export const postLoginWxMinSimpleAPI = (phoneNumber: string) => {
return http<loginResult>({
method: 'POST',
url: '/login/wxMin/simple',
data: {
phoneNumber,
},
})
}
3、组件代码
// src/pages/login/login.vue
<script setup lang="ts">
import { loginWx, postLoginWxMinSimpleAPI } from '@/services/login';
import { useMemberStore } from '@/stores';
import { onLoad } from '@dcloudio/uni-app';
let code = ''
onLoad(async () => {
//获取登录凭证
const res = await uni.login()
code = res.code
})
const memberStore = useMemberStore()
//获取用户的手机号码
const onGetphonenumber: UniHelper.ButtonOnGetphonenumber = (ev) => {
const encryptedData = ev.detail!.encryptedData!
const iv = ev.detail!.iv!
const res = loginWx({
code,
encryptedData,
iv
})
}
const simpleLogin = async () => {
const res = await postLoginWxMinSimpleAPI('17359619003')
memberStore.setProfile(res.result)
uni.showToast({ icon: 'success', title: '登录成功' })
setTimeout(() => {
//跳转我的
uni.switchTab({ url: '/pages/my/my' })
}, 500)
}
</script>
<template>
<view class="viewport">
<view class="logo">
<image src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/images/logo_icon.png"></image>
</view>
<view class="login">
<!-- 网页端表单登录 -->
<!-- <input class="input" type="text" placeholder="请输入用户名/手机号码" /> -->
<!-- <input class="input" type="text" password placeholder="请输入密码" /> -->
<!-- <button class="button phone">登录</button> -->
<!-- 小程序端授权登录 -->
<button class="button phone" open-type="getPhoneNumber" @getphonenumber="onGetphonenumber">
<text class="icon icon-phone"></text>
手机号快捷登录
</button>
<view class="extra">
<view class="caption">
<text>其他登录方式</text>
</view>
<view class="options">
<!-- 通用模拟登录 -->
<button @tap="simpleLogin()">
<text class="icon icon-phone">模拟快捷登录</text>
</button>
</view>
</view>
<view class="tips">登录/注册即视为你同意《服务条款》和《小兔鲜儿隐私协议》</view>
</view>
</view>
</template>
<style lang="scss">
page {
height: 100%;
}
.viewport {
display: flex;
flex-direction: column;
height: 100%;
padding: 20rpx 40rpx;
}
.logo {
flex: 1;
text-align: center;
image {
width: 220rpx;
height: 220rpx;
margin-top: 15vh;
}
}
.login {
display: flex;
flex-direction: column;
height: 60vh;
padding: 40rpx 20rpx 20rpx;
.input {
width: 100%;
height: 80rpx;
font-size: 28rpx;
border-radius: 72rpx;
border: 1px solid #ddd;
padding-left: 30rpx;
margin-bottom: 20rpx;
}
.button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 80rpx;
font-size: 28rpx;
border-radius: 72rpx;
color: #fff;
.icon {
font-size: 40rpx;
margin-right: 6rpx;
}
}
.phone {
background-color: #28bb9c;
}
.wechat {
background-color: #06c05f;
}
.extra {
flex: 1;
padding: 70rpx 70rpx 0;
.caption {
width: 440rpx;
line-height: 1;
border-top: 1rpx solid #ddd;
font-size: 26rpx;
color: #999;
position: relative;
text {
transform: translate(-40%);
background-color: #fff;
position: absolute;
top: -12rpx;
left: 50%;
}
}
.options {
display: flex;
justify-content: center;
align-items: center;
margin-top: 70rpx;
button {
padding: 0;
background-color: transparent;
}
}
.icon {
font-size: 24rpx;
color: #444;
display: flex;
flex-direction: column;
align-items: center;
&::before {
display: flex;
align-items: center;
justify-content: center;
width: 80rpx;
height: 80rpx;
margin-bottom: 6rpx;
font-size: 40rpx;
border: 1rpx solid #444;
border-radius: 50%;
}
}
.icon-weixin::before {
border-color: #06c05f;
color: #06c05f;
}
}
}
.tips {
position: absolute;
bottom: 80rpx;
left: 20rpx;
right: 20rpx;
font-size: 22rpx;
color: #999;
text-align: center;
}
</style>
4、处理会员信息持久化
import type { loginResult } from '@/types/member'
import { defineStore } from 'pinia'
import { ref } from 'vue'
// 定义 Store
export const useMemberStore = defineStore(
'member',
() => {
// 会员信息
const profile = ref<loginResult>()
// 保存会员信息,登录时使用
const setProfile = (val: loginResult) => {
profile.value = val
}
// 清理会员信息,退出时使用
const clearProfile = () => {
profile.value = undefined
}
// 记得 return
return {
profile,
setProfile,
clearProfile,
}
},
// TODO: 持久化
{
//网页端的写法
// persist: true,
persist: {
storage: {
getItem(key) {
return uni.getStorageSync(key)
},
setItem(key, value) {
uni.setStorageSync(key, value)
},
},
},
},
)
会员中心模块
1、封装猜你喜欢组合式函数
// src/composables/index.ts
import type { XtxGuessInstance } from '@/types/component'
import { ref } from 'vue'
/**
* 猜你喜欢组合式函数
*/
export const useGuessList = () => {
// 获取猜你喜欢组件实例
const guessRef = ref<XtxGuessInstance>()
// 滚动触底事件
const onScrolltolower = () => {
guessRef.value?.getMore()
}
// 返回 ref 和事件处理函数
return { guessRef, onScrolltolower }
}
2、组件代码
// src/pages/my/my.vue
<script setup lang="ts">
import { useGuessList } from '@/composables';
import { useMemberStore } from '@/stores';
// 获取屏幕边界到安全区域距离
const { safeAreaInsets } = uni.getSystemInfoSync()
// 订单选项
const orderTypes = [
{ type: 1, text: '待付款', icon: 'icon-currency' },
{ type: 2, text: '待发货', icon: 'icon-gift' },
{ type: 3, text: '待收货', icon: 'icon-check' },
{ type: 4, text: '待评价', icon: 'icon-comment' },
]
//组件
const { guessRef, onScrolltolower } = useGuessList()
//会员信息
const memberStore = useMemberStore()
</script>
<template>
<scroll-view class="viewport" scroll-y enable-back-to-top @scrolltolower="onScrolltolower">
<!-- 个人资料 -->
<view class="profile" :style="{ paddingTop: safeAreaInsets!.top + 'px' }">
<!-- 情况1:已登录 -->
<view class="overview" v-if="memberStore.profile">
<navigator url="/pagesMember/profile/profile" hover-class="none">
<image class="avatar" mode="aspectFill" :src="memberStore.profile.avatar"></image>
</navigator>
<view class="meta">
<view class="nickname"> {{ memberStore.profile.nickname || memberStore.profile.accout }} </view>
<navigator class="extra" url="/pagesMember/profile/profile" hover-class="none">
<text class="update">更新头像昵称</text>
</navigator>
</view>
</view>
<!-- 情况2:未登录 -->
<view class="overview" v-else>
<navigator url="/pages/login/login" hover-class="none">
<image class="avatar gray" mode="aspectFill"
src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-06/db628d42-88a7-46e7-abb8-659448c33081.png">
</image>
</navigator>
<view class="meta">
<navigator url="/pages/login/login" hover-class="none" class="nickname">
未登录
</navigator>
<view class="extra">
<text class="tips">点击登录账号</text>
</view>
</view>
</view>
<navigator class="settings" url="/pagesMember/settings/settings" hover-class="none">
设置
</navigator>
</view>
<!-- 我的订单 -->
<view class="orders">
<view class="title">
我的订单
<navigator class="navigator" url="/pagesOrder/list/list?type=0" hover-class="none">
查看全部订单<text class="icon-right"></text>
</navigator>
</view>
<view class="section">
<!-- 订单 -->
<navigator v-for="item in orderTypes" :key="item.type" :class="item.icon"
:url="`/pagesOrder/list/list?type=${item.type}`" class="navigator" hover-class="none">
{{ item.text }}
</navigator>
<!-- 客服 -->
<button class="contact icon-handset" open-type="contact">售后</button>
</view>
</view>
<!-- 猜你喜欢 -->
<view class="guess">
<XtxGuess ref="guessRef" />
</view>
</scroll-view>
</template>
<style lang="scss">
page {
height: 100%;
overflow: hidden;
background-color: #f7f7f8;
}
.viewport {
height: 100%;
background-repeat: no-repeat;
background-image: url(https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/images/center_bg.png);
background-size: 100% auto;
}
/* 用户信息 */
.profile {
margin-top: 20rpx;
position: relative;
.overview {
display: flex;
height: 120rpx;
padding: 0 36rpx;
color: #fff;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background-color: #eee;
}
.gray {
filter: grayscale(100%);
}
.meta {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
line-height: 30rpx;
padding: 16rpx 0;
margin-left: 20rpx;
}
.nickname {
max-width: 350rpx;
margin-bottom: 16rpx;
font-size: 30rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.extra {
display: flex;
font-size: 20rpx;
}
.tips {
font-size: 22rpx;
}
.update {
padding: 3rpx 10rpx 1rpx;
color: rgba(255, 255, 255, 0.8);
border: 1rpx solid rgba(255, 255, 255, 0.8);
margin-right: 10rpx;
border-radius: 30rpx;
}
.settings {
position: absolute;
bottom: 0;
right: 40rpx;
font-size: 30rpx;
color: #fff;
}
}
/* 我的订单 */
.orders {
position: relative;
z-index: 99;
padding: 30rpx;
margin: 50rpx 20rpx 0;
background-color: #fff;
border-radius: 10rpx;
box-shadow: 0 4rpx 6rpx rgba(240, 240, 240, 0.6);
.title {
height: 40rpx;
line-height: 40rpx;
font-size: 28rpx;
color: #1e1e1e;
.navigator {
font-size: 24rpx;
color: #939393;
float: right;
}
}
.section {
width: 100%;
display: flex;
justify-content: space-between;
padding: 40rpx 20rpx 10rpx;
.navigator,
.contact {
text-align: center;
font-size: 24rpx;
color: #333;
&::before {
display: block;
font-size: 60rpx;
color: #ff9545;
}
}
.contact {
padding: 0;
margin: 0;
border: 0;
background-color: transparent;
line-height: inherit;
}
}
}
/* 猜你喜欢 */
.guess {
background-color: #f7f7f8;
margin-top: 20rpx;
}
</style>
设置(分包)
<script setup lang="ts">
import { useMemberStore } from '@/stores';
const memberStore = useMemberStore()
// 退出登录
const onLogout = () => {
// 模态弹窗
uni.showModal({
content: '是否退出登录?',
success: (res) => {
if (res.confirm) {
// 清理用户信息
memberStore.clearProfile()
// 返回上一页
uni.navigateBack()
}
},
})
}
</script>
<template>
<view class="viewport">
<!-- 列表1 -->
<view class="list" v-if="true">
<navigator url="/pagesMember/address/address" hover-class="none" class="item arrow" v-show="memberStore.profile">
我的收货地址
</navigator>
</view>
<!-- 列表2 -->
<view class="list">
<button hover-class="none" class="item arrow" open-type="openSetting">授权管理</button>
<button hover-class="none" class="item arrow" open-type="feedback">问题反馈</button>
<button hover-class="none" class="item arrow" open-type="contact">联系我们</button>
</view>
<!-- 列表3 -->
<view class="list">
<navigator hover-class="none" class="item arrow" url=" ">关于小兔鲜儿</navigator>
</view>
<!-- 操作按钮 -->
<view class="action">
<view class="button" @tap="onLogout" v-show="memberStore.profile">退出登录</view>
</view>
</view>
</template>
<style lang="scss">
page {
background-color: #f4f4f4;
}
.viewport {
padding: 20rpx;
}
/* 列表 */
.list {
padding: 0 20rpx;
background-color: #fff;
margin-bottom: 20rpx;
border-radius: 10rpx;
.item {
line-height: 90rpx;
padding-left: 10rpx;
font-size: 30rpx;
color: #333;
border-top: 1rpx solid #ddd;
position: relative;
text-align: left;
border-radius: 0;
background-color: #fff;
&::after {
width: auto;
height: auto;
left: auto;
border: none;
}
&:first-child {
border: none;
}
&::after {
right: 5rpx;
}
}
.arrow::after {
content: '\e6c2';
position: absolute;
top: 50%;
color: #ccc;
font-family: 'erabbit' !important;
font-size: 32rpx;
transform: translateY(-50%);
}
}
/* 操作按钮 */
.action {
text-align: center;
line-height: 90rpx;
margin-top: 40rpx;
font-size: 32rpx;
color: #333;
.button {
background-color: #fff;
margin-bottom: 20rpx;
border-radius: 10rpx;
}
}
</style>
3、page.json的配置
,
"subPackages": [
{
// 子包的根目录
"root": "pagesMember",
// 页面路径和窗口表现
"pages": [
{
"path": "settings/settings",
"style": {
"navigationBarTitleText": "设置"
}
}
]
}
],
// 分包预下载规则
"preloadRule": {
"pages/my/my": {
"network": "all",
"packages": [
"pagesMember"
]
}
}