[uni-app]小兔鲜-05登录+个人中心

登录

微信授权登录: 通过wx.login()获取code登录凭证, 通过特定类型按钮获取用户手机号, 实现授权登录

import type { LoginResult } from '@/types/member'
import { http } from '@/utils/http'

type LoginParams = {
    code: string
    encryptedData: string
    iv: string
}

// 微信登录
export const postLoginWxMin = (data: LoginParams) => {
    return http<LoginResult>({
        method: 'POST',
        url: '/login/wxMin',
        data,
    })
}

// 模拟微信登录
export const postLoginWxMinSimpleAPI = (phoneNumber: string) => {
    return http<LoginResult>({
        method: 'POST',
        url: '/login/wxMin/simple',
        data: {
            phoneNumber,
        },
    })
}
// 用户信息类型文件

/** 小程序登录 登录用户信息 */
export type LoginResult = {
   /** 用户ID */
    id: number
    /** 头像  */
    avatar: string
    /** 账户名  */
    account: string
    /** 昵称 */
    nickname?: string
    /** 手机号 */
    mobile: string
    /** 登录凭证 */
    token: string
}
<script setup lang="ts">
import { postLoginWxMin } from '@/services/login'
import { onLoad } from '@dcloudio/uni-app'

// 获取code
let code = ''
onLoad(async () => {
  const res = await wx.login()
  code = res.code
})

// 微信登录
const onGetPhoneNumber: UniHelper.ButtonOnGetphonenumber = async (ev) => {
  const encryptedData = ev.detail!.encryptedData!
  const iv = ev.detail!.iv!
  const res = await postLoginWxMin({
    encryptedData,
    iv,
    code,
  })
  console.log(res)
}

</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">
      <!-- 小程序端授权登录 -->
      <button class="button phone" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
        <text class="icon icon-phone"></text>
        手机号快捷登录
      </button>
       
      <view class="tips">登录/注册即视为你同意《服务条款》和《小兔鲜儿隐私协议》</view>
    </view>
  </view>
</template>
  1. 使用企业小程序appid, 并且把微信号添加到开发者列表中, 才能获取到用户手机号
  2. ! 非空断言排除的是前面的空值, 有需要的话可以使用多次

模拟登录: 获取手机号功能针对非个人开发者, 且认证完成的小程序开发

<sript setup lang="ts">
import { useMemberStore } from '@/stores'
import { postLoginWxMinSimpleAPI } from '@/services/login'

// 模拟微信登录
const onGetPhoneNumberSimple = async () => {
  const res = await postLoginWxMinSimpleAPI('15553266208')
  console.log(res)
}
</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">
      <!-- 小程序端授权登录 -->
      <button class="button phone">
        <text class="icon icon-phone"></text>
        手机号快捷登录
      </button>
       
      <view class="extra">
        <view class="caption">
          <text>其他登录方式</text>
        </view>
        <view class="options">
          <!-- 通用模拟登录 -->
          <button @tap="onGetPhoneNumberSimple">
            <text class="icon icon-phone">模拟快捷登录</text>
          </button>
        </view>
      </view>
      <view class="tips">登录/注册即视为你同意《服务条款》和《小兔鲜儿隐私协议》</view>
    </view>
  </view>
</template>

保存登录信息

<sript setup lang="ts">
import { useMemberStore } from '@/stores'
import { postLoginWxMinSimpleAPI } from '@/services/login'

// 模拟微信登录
const onGetPhoneNumberSimple = async () => {
  const res = await postLoginWxMinSimpleAPI('15553266208')
  // 保存用户信息
  const memberStore = useMemberStore()
  memberStore.setProfile(res.result)
  // 提示
  uni.showToast({
    icon: 'none',
    title: '登录成功',
  })
  // 跳转
  setTimeout(() => {
    uni.navigateBack()
  }, 500)
}
</script>
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: {
      storage: {
        getItem(key) {
          return uni.getStorageSync(key)
        },
        setItem(key, value) {
          uni.setStorageSync(key, value)
        },
      },
    },
  },
)

我的页面

创建我的页面, 渲染用户信息

"pages": [
		{
			"path": "pages/my/my",
			"style": {
				"navigationStyle": "custom", // 隐藏默认导航
				"navigationBarTextStyle": "white",
				"navigationBarTitleText": "我的"
			}
		}
]
<script setup lang="ts">
import { useMemberStore } from '@/stores'
import { ref } from 'vue'
import { useGuessList } from '@/composables/index'

// 获取屏幕边界到安全区域距离
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 memberStore = useMemberStore()
</script>

<template>
  <scroll-view class="viewport" scroll-y enable-back-to-top @scrolltolower="onScorlltolower">
    <!-- 个人资料 -->
    <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.account }}
          </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>
  </scroll-view>
</template>

猜你喜欢分页加载

<script setup lang="ts">
import { useGuessList } from '@/composables/index'

// 调用组合式函数,实现猜你喜欢分页加载
const { guessRef, onScorlltolower } = useGuessList()
</script>

<template>
  <scroll-view class="viewport" scroll-y enable-back-to-top @scrolltolower="onScorlltolower">
    <!-- 个人资料 -->
    <view class="profile" :style="{ paddingTop: safeAreaInsets!.top + 'px' }">
       ... ...
    </view>
    <!-- 猜你喜欢 -->
    <view class="guess">
      <XtxGuess ref="guessRef" />
    </view>
  </scroll-view>
</template>
import type { XtxGuessInstance } from '@/types/component'
import { ref } from 'vue'

/**
 * 猜你喜欢组合式函数
 */
export const useGuessList = () => {
    // 猜你喜欢的组件实例
    const guessRef = ref<XtxGuessInstance>()

    // 滚动触底事件
    const onScorlltolower = () => {
        guessRef.value.getMore()
    }

    // 返回ref 和 事件处理函数
    return {
        guessRef,
        onScorlltolower,
    }
}
  1. 组合式函数是vue3组合式API支持的语法, 进一步封装vue组件中的函数
  2. 组合式函数一般以use开头进行命名

效果展示

设置页面

新建分包页面, 配置分包预下载

分包: 将小程序的核心页面和次要页面进行分割,打包成多个小程序包,减少小程序的加载时间 ,提高用户体验

预下载: 在进入小程序的指定页面时,由框架自动提前下载分包资源. 提升进入分包页面的启动速度

<template>
  <view class="viewport">
    <!-- 列表1 -->
    <view class="list" v-if="true">
      <navigator url="/pagesMember/address/address" hover-class="none" class="item arrow">
        我的收货地址
      </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">退出登录</view>
    </view>
  </view>
</template>
  1. 存放: 主包页面放在pages中管理, 分包单独创建文件夹管理
  2. 新建: 编辑器都有快捷新建分包页面的功能

配置分包预下载规则

{
  // 主包页面配置
	"pages": [
     ...
	],
	// 分包页面配置
	"subPackages": [
		{
			// 子包的根目录
			"root": "pagesMember",
			// 页面路径和窗口表现
			"pages": [
				{
					"path": "settings/settings",
					"style": {
						"navigationBarTitleText": "设置"
					}
				},
			]
		},
	],
	// 分包预下载规则
	"preloadRule": {
		// 进入页面时预下载的分包
		"pages/my/my": {
			// 网络
			"network": "all",
			// 预下载的分包
			"packages": [
				"pagesMember"
			]
		}
	}
}

退出登录

<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="memberStore.profile">
      <navigator url="/pagesMember/address/address" hover-class="none" class="item arrow">
        我的收货地址
      </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-if="memberStore.profile">退出登录</view>
    </view>
  </view>
</template>

实现效果

个人信息

准备分包页面, 初始化渲染

<script setup lang="ts">
import { getMemberProfileAPI } from '@/services/profile'
import type { ProfileDetail } from '@/types/member'
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'

// 获取屏幕边界到安全区域距离
const { safeAreaInsets } = uni.getSystemInfoSync()

// 获取个人信息
const profile = ref({} as ProfileDetail)
const getMemberProfileData = async () => {
  const res = await getMemberProfileAPI()
  profile.value = res.result
}

onLoad(() => {
  getMemberProfileData()
})
</script>

<template>
  <view class="viewport">
    <!-- 导航栏 -->
    <view class="navbar" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
      <navigator open-type="navigateBack" class="back icon-left" hover-class="none"></navigator>
      <view class="title">个人信息</view>
    </view>
    <!-- 头像 -->
    <view class="avatar">
      <view class="avatar-content" @tap="onAvatarChange">
        <image class="image" :src="profile?.avatar" mode="aspectFill" />
        <text class="text">点击修改头像</text>
      </view>
    </view>
    <!-- 表单 -->
    <view class="form">
      <!-- 表单内容 -->
      <view class="form-content">
        <view class="form-item">
          <text class="label">账号</text>
          <text class="account">{{ profile?.account }}</text>
        </view>
        <view class="form-item">
          <text class="label">昵称</text>
          <input class="input" type="text" placeholder="请填写昵称" v-model="profile!.nickname" />
        </view>
        <view class="form-item">
          <text class="label">性别</text>
          <radio-group @change="onGenderChange">
            <label class="radio">
              <radio value="男" color="#27ba9b" :checked="profile?.gender === '男'" />
              男
            </label>
            <label class="radio">
              <radio value="女" color="#27ba9b" :checked="profile?.gender === '女'" />
              女
            </label>
          </radio-group>
        </view>
        <view class="form-item">
          <text class="label">生日</text>
          <picker
            class="picker"
            mode="date"
            start="1900-01-01"
            :end="new Date()"
            @change="onBirthdayChange"
            :value="profile?.birthday"
          >
            <view v-if="profile?.birthday">{{ profile?.birthday }}</view>
            <view class="placeholder" v-else>请选择日期</view>
          </picker>
        </view>
        <view class="form-item">
          <text class="label">城市</text>
          <picker
            class="picker"
            mode="region"
            :value="profile?.fullLocation?.split(' ')"
            @change="onFullLocationChange"
          >
            <view v-if="profile?.fullLocation">{{ profile?.fullLocation }}</view>
            <view class="placeholder" v-else>请选择城市</view>
          </picker>
        </view>
        <view class="form-item">
          <text class="label">职业</text>
          <input class="input" type="text" placeholder="请填写职业" v-model="profile.profession" />
        </view>
      </view>
      <!-- 提交按钮 -->
      <button class="form-button" @tap="onSubmit">保 存</button>
    </view>
  </view>
</template>
import type { ProfileDetail, ProfileParams } from '@/types/member'
import { http } from '@/utils/http'

/**
 * 获取个人信息
 */
export const getMemberProfileAPI = () => {
    return http<ProfileDetail>({
        method: 'GET',
        url: '/member/profile',
    })
}
// 用户信息类型文件

/** 封装通用信息 */
type BaseProfile = {
    /** 用户ID */
    id: number
    /** 头像  */
    avatar: string
    /** 账户名  */
    account: string
    /** 昵称 */
    nickname?: string
}

/** 小程序登录 登录用户信息 */
export type LoginResult = BaseProfile & {
    /** 手机号 */
    mobile: string
    /** 登录凭证 */
    token: string
}

/** 个人信息 用户详情信息 */
export type ProfileDetail = BaseProfile & {
    /** 性别 */
    gender?: Gender
    /** 生日 */
    birthday?: string
    /** 省市区 */
    fullLocation?: string
    /** 职业 */
    profession?: string
}

/** 性别 */
export type Gender = '女' | '男'
  1. 使用联合类型 & 进一步封装类型声明

头像上传

<script setup lang="ts">
import { getMemberProfileAPI, putMemberProfileAPI } from '@/services/profile'
import type { ProfileDetail, Gender } from '@/types/member'
import { ref } from 'vue'
import { useMemberStore } from '@/stores'

// 点击头像
const memberStore = useMemberStore()
const onAvatarChange = () => {
  // 调用拍照/选择图片
  // #ifdef H5 || APP-PLUS
  // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替
  uni.chooseImage({
    count: 1,
    success: (res) => {
      // 文件路径
      const tempFilePaths = res.tempFilePaths
      // 上传
      uploadFile(tempFilePaths[0])
    },
  })
  // #endif

  // #ifdef MP-WEIXIN
  // uni.chooseMedia 仅支持微信小程序端
  uni.chooseMedia({
    // 文件个数
    count: 1,
    // 文件类型
    mediaType: ['image'],
    success: (res) => {
      // 本地路径
      const { tempFilePath } = res.tempFiles[0]
      // 上传
      uploadFile(tempFilePath)
    },
  })
  // #endif
}

// 文件上传-兼容小程序端、H5端、App端
const uploadFile = (file: string) => {
  // 文件上传
  uni.uploadFile({
    url: '/member/profile/avatar',
    name: 'file',
    filePath: file,
    success: (res) => {
      if (res.statusCode === 200) {
        const avatar = JSON.parse(res.data).result.avatar
        // 个人信息页数据更新
        profile.value!.avatar = avatar
        // Store头像更新
        memberStore.profile!.avatar = avatar
        uni.showToast({ icon: 'success', title: '更新成功' })
      } else {
        uni.showToast({ icon: 'error', title: '出现错误' })
      }
    },
  })
}
</script>

<template>
  <view class="viewport">
    ...
    <!-- 头像 -->
    <view class="avatar">
      <view class="avatar-content" @tap="onAvatarChange">
        <image class="image" :src="profile?.avatar" mode="aspectFill" />
        <text class="text">点击修改头像</text>
      </view>
    </view>
  ...
    </view>
  </view>
</template>

修改用户昵称

import type { ProfileDetail, ProfileParams } from '@/types/member'
import { http } from '@/utils/http'

/**
 * 修改个人信息
 */
export const putMemberProfileAPI = (data: ProfileParams) => {
    return http<ProfileDetail>({
        method: 'PUT',
        url: '/member/profile',
        data,
    })
}
// 用户信息类型文件

/** 封装通用信息 */
type BaseProfile = {
    /** 用户ID */
    id: number
    /** 头像  */
    avatar: string
    /** 账户名  */
    account: string
    /** 昵称 */
    nickname?: string
}

/** 个人信息 用户详情信息 */
export type ProfileDetail = BaseProfile & {
    /** 性别 */
    gender?: Gender
    /** 生日 */
    birthday?: string
    /** 省市区 */
    fullLocation?: string
    /** 职业 */
    profession?: string
}

/** 性别 */
export type Gender = '女' | '男'

/** 个人信息 修改请求体参数 */
export type ProfileParams = Pick<
    ProfileDetail,
    'nickname' | 'gender' | 'birthday' | 'profession'
> & {
    /** 省份编码 */
    provinceCode?: string
    /** 城市编码 */
    cityCode?: string
    /** 区/县编码 */
    countyCode?: string
}
  1. Pick<目标类型, 选取的属性>泛型工具方法是TS提供的, 用于提取已有类型中的参数, 组合成新的类型
<script setup lang="ts">
import { putMemberProfileAPI } from '@/services/profile'
import type { ProfileDetail } from '@/types/member'
import { ref } from 'vue'

// 获取个人信息
const profile = ref({} as ProfileDetail)

// 表单提交
const onSubmit = async () => {
  const res = await putMemberProfileAPI({
    nickname: profile.value.nickname,
  })
  // 更新stroe的昵称
  memberStore.profile!.nickname = res.result.nickname
  uni.showToast({
    icon: 'success',
    titlelist: '修改成功',
  })
  // 返回上一页
  setTimeout(() => {
    uni.navigateBack()
  }, 400)
}
</script>

<template>
  <view class="viewport">
    <!-- 表单 -->
    <view class="form">
      <!-- 表单内容 -->
      <view class="form-content">
        <view class="form-item">
          <text class="label">账号</text>
          <text class="account">{{ profile?.account }}</text>
        </view>
        <view class="form-item">
          <text class="label">昵称</text>
          <input class="input" type="text" placeholder="请填写昵称" v-model="profile!.nickname" />
        </view>
      </view>
      <!-- 提交按钮 -->
      <button class="form-button" @tap="onSubmit">保 存</button>
    </view>
  </view>
</template>
  1. 在TS中对数据的类型限制非常严格, 一个对象中的属性是不能随便添加的, 要和类型声明保持一致
  2. 仅做展示的对象数据, 一般只限制类型, 不给对象的定义初始值, 这样比较方便
  3. 对于需要双向绑定的数据, 就要使用 as类型断言, 告诉TS这个对象以后是什么样子, 这个对象才能顺利的进行数据双向绑定, 也就相当于给对象进行了初始值的定义

修改性别信息

<script setup lang="ts">
import { putMemberProfileAPI } from '@/services/profile'
import type { ProfileDetail, Gender } from '@/types/member'
import { ref } from 'vue'

// 获取个人信息
const profile = ref({} as ProfileDetail)

// 男女选项变化
const onGenderChange: UniHelper.RadioGroupOnChange = (ev) => {
  profile.value.gender = ev.detail.value as Gender
}

// 表单提交
const onSubmit = async () => {
  const res = await putMemberProfileAPI({
    gender: profile.value.gender,
  })
  // 更新stroe的昵称
  memberStore.profile!.nickname = res.result.nickname
  uni.showToast({
    icon: 'success',
    titlelist: '修改成功',
  })
  setTimeout(() => {
    uni.navigateBack()
  }, 400)
}
</script>

<template>
  <view class="viewport">
  ... ...
    <!-- 表单 -->
    <view class="form">
      <!-- 表单内容 -->
      <view class="form-content">
        <view class="form-item">
          <text class="label">性别</text>
          <radio-group @change="onGenderChange">
            <label class="radio">
              <radio value="男" color="#27ba9b" :checked="profile?.gender === '男'" />
              男
            </label>
            <label class="radio">
              <radio value="女" color="#27ba9b" :checked="profile?.gender === '女'" />
              女
            </label>
          </radio-group>
        </view>
  
      </view>
      <!-- 提交按钮 -->
      <button class="form-button" @tap="onSubmit">保 存</button>
    </view>
  </view>
</template>
  1. ev.detail.value 拿到的是string类型数据
  2. profile.value.gender 限制的数据类型是 gender
  3. 如果直接赋值TS会爆红, 所以要通过类型断言告诉TS, ev拿到的就是gender类型

修改生日信息

<script setup lang="ts">
import { putMemberProfileAPI } from '@/services/profile'
import type { ProfileDetail, Gender } from '@/types/member'
import { ref } from 'vue'

// 获取个人信息
const profile = ref({} as ProfileDetail)

// 修改生日
const onBirthdayChange: UniHelper.DatePickerOnChange = (ev) => {
  profile.value.birthday = ev.detail.value
}

// 表单提交
const onSubmit = async () => {
  const res = await putMemberProfileAPI({
    birthday: profile.value.birthday,
  })
  // 更新stroe的昵称
  memberStore.profile!.nickname = res.result.nickname
  uni.showToast({
    icon: 'success',
    titlelist: '修改成功',
  })
  setTimeout(() => {
    uni.navigateBack()
  }, 400)
}
</script>

<template>
  <view class="viewport">
    ... ...
    <!-- 表单 -->
    <view class="form">
      <!-- 表单内容 -->
      <view class="form-content">
        <view class="form-item">
          <text class="label">生日</text>
          <picker
            class="picker"
            mode="date"
            start="1900-01-01"
            :end="new Date()"
            @change="onBirthdayChange"
            :value="profile?.birthday"
          >
            <view v-if="profile?.birthday">{{ profile?.birthday }}</view>
            <view class="placeholder" v-else>请选择日期</view>
          </picker>
        </view>
      </view>
      <!-- 提交按钮 -->
      <button class="form-button" @tap="onSubmit">保 存</button>
    </view>
  </view>
</template>

修改用户所在城市

<script setup lang="ts">
import { putMemberProfileAPI } from '@/services/profile'
import type { ProfileDetail } from '@/types/member'
import { ref } from 'vue'

// 获取个人信息
const profile = ref({} as ProfileDetail)

// 修改城市
let fullLocationCode: [string, string, string] = ['', '', '']
const onFullLocationChange: UniHelper.RegionPickerOnChange = (ev) => {
  // 修改前端界面
  profile.value.fullLocation = ev.detail.value.join(' ')

  // 提交后端更新
  fullLocationCode = ev.detail.code!
}

// 表单提交
const onSubmit = async () => {
  const res = await putMemberProfileAPI({
    provinceCode: fullLocationCode[0],
    cityCode: fullLocationCode[1],
    countyCode: fullLocationCode[2],
  })
  // 更新stroe的昵称
  memberStore.profile!.nickname = res.result.nickname
  uni.showToast({
    icon: 'success',
    titlelist: '修改成功',
  })
  setTimeout(() => {
    uni.navigateBack()
  }, 400)
}
</script>

<template>
  <view class="viewport">
    ... 
    <!-- 表单 -->
    <view class="form">
      <!-- 表单内容 -->
      <view class="form-content">
        ...
        <view class="form-item">
          <text class="label">城市</text>
          <picker
            class="picker"
            mode="region"
            :value="profile?.fullLocation?.split(' ')"
            @change="onFullLocationChange"
          >
            <view v-if="profile?.fullLocation">{{ profile?.fullLocation }}</view>
            <view class="placeholder" v-else>请选择城市</view>
          </picker>
        </view>
      </view>
      <!-- 提交按钮 -->
      <button class="form-button" @tap="onSubmit">保 存</button>
    </view>
  </view>
</template>
  1. 在TS中, 长度和类型都已经确定的数组称为元组

实现效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值