32 用户模块布局

用户模块布局

    • 1 布局容器-组件路由{#layout-routes}
    • 2 布局容器-底部tab栏{#layout-tab}
    • 3 布局容器-访问权限控制{#layout-auth}
    • 4布局容器-页面标题{#layout-title}
    • 5 个人中心-用户信息类型{#user-types}
    • 6 个人中心-头部展示{#user-head-render}
    • 7个人中心-快捷工具{#user-tools}
    • 8 个人中心-退出登录{#user-logout}

1 布局容器-组件路由{#layout-routes}

目标:首页,健康百科,消息通知,我的,布局容器等静态结构搭建

在这里插入图片描述
说明:练习模板组件(02-其它资源),重点学习在线医疗业务功能开发,基本结构都使用vant组件库搭建。

  • 基础组件

Layout/index.vue == 公共布局 ==

Home/index.vue-首页

Article/index.vue-健康百科

Notify/index.vue-消息通知

User/index.vue-我的

  • 其它业务组件

Consult/ConsultFast.vue-极速问诊

Consult/ConsultIllness.vue-选择问诊类型

Consult/ConsultDep.vue-选择问诊科室=>选择问诊患者

Consult/ConsultPay.vue-问诊支付

Room/index.vue-医生问诊室

Medicine/OrderPay.vue药品支付

Medicine/OrderPayResult.vue支付结果

Medicine/OrderDetail.vue订单详情

Medicine/OrderExpress.vue物流详情

  • ts类型声明文件和api函数

user.d.ts 用户类型

consult.d.ts 极速问诊类型

room.d.ts 问诊室类型

medicine.d.ts药品订单类型

路由配置

  routes: [
    
    {
      path: '/',
      component: () => import('@/views/Layout/index.vue'),
      redirect: '/home',
      children: [
        { path: '/home', component: () => import('@/views/home/index.vue') },
        { path: '/article', component: () => import('@/views/article/index.vue') },
        { path: '/notify', component: () => import('@/views/notify/index.vue') },
        { path: '/user', component: () => import('@/views/user/index.vue') }
      ]
    }
  ]

2 布局容器-底部tab栏{#layout-tab}

实现:底部tab的切换

步骤:

  • 使用 tab-bar 实现路由切换功能,给tabbar添加route属性
  • tab-bar 加上自定义图标,根据active判断是否选中
  • 根据需要自定义tabbar字体大小

代码:

Layout/index.vue

  • 路由切换功能
    <van-tabbar route>
      <van-tabbar-item to="/home">首页</van-tabbar-item>
      <van-tabbar-item to="/article">健康百科</van-tabbar-item>
      <van-tabbar-item to="/notify">消息中心</van-tabbar-item>
      <van-tabbar-item to="/user">我的</van-tabbar-item>
    </van-tabbar>
  • 自定义图标
<script setup lang="ts"></script>

<template>
  <div class="layout-page">
    <router-view></router-view>
    <van-tabbar route>
      <van-tabbar-item to="/home">
        首页
        <template #icon="{ active }">
          <cp-icon :name="`home-index-${active ? 'active' : 'default'}`" />
        </template>
      </van-tabbar-item>
      <van-tabbar-item to="/article">
        健康百科
        <template #icon="{ active }">
          <cp-icon :name="`home-article-${active ? 'active' : 'default'}`" />
        </template>
      </van-tabbar-item>
      <van-tabbar-item to="/notify">
        消息中心
        <template #icon="{ active }">
          <cp-icon :name="`home-notice-${active ? 'active' : 'default'}`" />
        </template>
      </van-tabbar-item>
      <van-tabbar-item to="/user">
        我的
        <template #icon="{ active }">
          <cp-icon :name="`home-mine-${active ? 'active' : 'default'}`" />
        </template>
      </van-tabbar-item>
    </van-tabbar>
  </div>
</template>

<style lang="scss" scoped>
.layout-page {
  ::v-deep() {
    .van-tabbar-item {
      &__icon {
        font-size: 21px;
      }
      &__text {
        font-size: 11px;
      }
    }
  }
}
</style>

3 布局容器-访问权限控制{#layout-auth}

实现:需要登录的页面,需要判断是否有token

  • vue-router 导航守卫文档

    • return '/login' 跳转指定地址
    • 不返回,或者 return true 就是放行;return false取消导航
    • 可以不是 next 函数了
  • 访问权限控制 router/index.ts

import { useUserStore } from '@/stores'

// 访问权限控制
router.beforeEach((to, from) => {
  // 用户仓库
  const store = useUserStore()
  // 不需要登录的页面,白名单
  const wihteList = ['/login']
  // 如果没有登录且不在白名单内,去登录
  if (!store.user?.token && !wihteList.includes(to.path)) return '/login'
  // 否则不做任何处理
})

提问:

  • 如果 /register 也不需要登录,写哪里?
    • const wihteList = ['/login', 'register']

4布局容器-页面标题{#layout-title}

实现:切换页面切换标题,扩展 vue-router 的类型

步骤:

  1. 给路由添加meta元信息
  2. 前置守卫获取元信息设置网页title

router/index.ts

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/login', component: () => import('@/views/Login/index.vue'), meta: { title: '登录' } },
    {
      path: '/',
      component: () => import('@/views/Layout/index.vue'),
      redirect: '/home',
      children: [
        {
          path: '/home',
          component: () => import('@/views/Home/index.vue'),
          meta: { title: '首页' }
        },
        {
          path: '/article',
          component: () => import('@/views/Article/index.vue'),
          meta: { title: '健康百科' }
        },
        {
          path: '/notify',
          component: () => import('@/views/Notify/index.vue'),
          meta: { title: '消息通知' }
        },
        {
          path: '/user',
          component: () => import('@/views/User/index.vue'),
          meta: { title: '个人中心' }
        }
      ]
    }
  ]
})
  • 切换路由设置标题

router/index.ts

// 访问权限控制
router.beforeEach((to) => {
  // 处理标题
+  document.title = `优医问诊-${to.meta.title || ''}`
  // 用户仓库
  const store = useUserStore()
  • 扩展元信息类型 types/vue-router.d.ts
import 'vue-router'

declare module 'vue-router' {
  // 扩展 元信息类型
  interface RouteMeta {
    // 标题
    title?: string
  }
}

5 个人中心-用户信息类型{#user-types}

掌握:Pick 与 Omit 从现有类型中得到可复用类型

场景:

  • User 对象类型,现在需要用户api接口 UserInfo 类型,字段多一些
  • 使用 交叉类型 可以复用 User 类型,但是不需要 token 属性

Pick 与 Omit TS内置类型

  • Pick 可以从一个对象类型中 取出某些属性
type Person = {
  name: string
  age: number
}
type PickPerson = Pick<Person, 'age'>
// PickPerson === { age: string }
  • Omit 可以从一个对象类型中 排出某些属性
type Person = {
  name: string
  age: number
}
type OmitPerson = Omit<Person, 'age'>
// OmitPerson === { name: string }

落地代码:

types/user.d.ts

// 1. 登录响应数据
export interface User {
  /**
   * 用户名
   */
  account?: string
  /**
   * 头像
   */
  avatar?: string
  /**
   * 用户id
   */
  id?: string
  /**
   * 脱敏手机号,带星号的手机号
   */
  mobile?: string
  /**
   * refreshToken
   */
  refreshToken?: string
  /**
   * token
   */
  token: string
}

// 短信验证码类型
export type CodeType = 'login' | 'register'

// == add 个人信息 ==
type OmitUser = Omit<User, 'token'>
export type UserInfo = OmitUser & {
  likeNumber: number
  collectionNumber: number
  score: number
  couponNumber: number
  orderInfo: {
    paidNumber: number
    receivedNumber: number
    shippedNumber: number
    finishedNumber: number
  }
}

小结:

  • Pick 作用?
    • 从类型对象中取出指定的属性类型
  • Omit 作用?
    • 从类型对象中排出指定的属性类型,得到剩余的属性类型

6 个人中心-头部展示{#user-head-render}

实现:头部个人信息展示与订单卡片布局

步骤:

  • 熟悉基础结构
  • 定义API函数
  • 获取数据进行渲染

需求分析❓:

  1. 定义api接口返回数据类型

  2. 封装后台api函数

  3. 获取用户信息数据渲染显示

  4. 退出登录

代码:

1)定义API函数

api/user.ts

import type { ..., UserInfo } from '@/types/user'

// ... 省略 ...
// 获取个人信息
export const getUserInfo = () => request.get<any, UserInfo>('/patient/myUser')

2)获取数据进行渲染

user/index.vue

import { getUserInfo } from '@/api/user'
import type { UserInfo } from '@/types/user'
import { onMounted, ref } from 'vue'

const user = ref<UserInfo>()
onMounted(async () => {
  const res = await getUserInfo()
  user.value = res
})
+<div class="user-page" v-if="user">
    <div class="user-page-head">
      <div class="top">
+        <van-image round fit="cover" :src="user.avatar" />
        <div class="name">
+          <p>{{ user.account }}</p>
          <p><van-icon name="edit" /></p>
        </div>
      </div>
      <van-row>
        <van-col span="6">
+          <p>{{ user.collectionNumber }}</p>
          <p>收藏</p>
        </van-col>
        <van-col span="6">
+          <p>{{ user.likeNumber }}</p>
          <p>关注</p>
        </van-col>
        <van-col span="6">
+          <p>{{ user.score }}</p>
          <p>积分</p>
        </van-col>
        <van-col span="6">
+          <p>{{ user.couponNumber }}</p>
          <p>优惠券</p>
        </van-col>
      </van-row>
    </div>
    <div class="user-page-order">
      <div class="head">
        <h3>药品订单</h3>
        <router-link to="/order">全部订单 <van-icon name="arrow" /></router-link>
      </div>
      <van-row>
        <van-col span="6">
+          <van-badge :content="user.orderInfo.paidNumber || ''">
            <cp-icon name="user-paid" />
+          </van-badge>
          <p>待付款</p>
        </van-col>
        <van-col span="6">
+          <van-badge :content="user.orderInfo.shippedNumber || ''">
            <cp-icon name="user-shipped" />
+          </van-badge>
          <p>待发货</p>
        </van-col>
        <van-col span="6">
+          <van-badge :content="user.orderInfo.receivedNumber || ''">
            <cp-icon name="user-received" />
+          </van-badge>
          <p>待收货</p>
        </van-col>
        <van-col span="6">
+          <van-badge :content="user.orderInfo.finishedNumber || ''">
            <cp-icon name="user-finished" />
+          </van-badge>
          <p>已完成</p>
        </van-col>
      </van-row>
    </div>
  </div>

7个人中心-快捷工具{#user-tools}

实现:快捷工具栏目渲染

步骤:

  • 准备初始化数据
  • 遍历工具栏

代码:

user/index.vue

1)准备初始化数据

const tools = [
  { label: '我的问诊', path: '/user/consult' },
  { label: '我的处方', path: '/' },
  { label: '家庭档案', path: '/user/patient' },
  { label: '地址管理', path: '/user/address' },
  { label: '我的评价', path: '/' },
  { label: '官方客服', path: '/' },
  { label: '设置', path: '/' }
]

2)遍历工具栏

    <div class="user-page-group">
      <h3>快捷工具</h3>
      <van-cell
        :title="item.label"
        is-link
        :to="item.path"
        :border="false"
        v-for="(item, i) in tools"
        :key="i"
      >
        <template #icon><cp-icon :name="`user-tool-0${i + 1}`" /></template>
      </van-cell>
    </div>

8 个人中心-退出登录{#user-logout}

实现:退出功能

步骤:

  • 实现退出
    • 确认框
    • 清除token
    • 跳转登录

代码:

user/index.vue

1)实现退出

<a class="logout" @click="logout" href="javascript:;">退出登录</a>
import { showConfirmDialog } from 'vant'
import { useUserStore } from '@/stores/index'

// ... 省略 ...
const store = useUserStore()
const router = useRouter()
const logout = async () => {
  await showConfirmDialog({
    title: '提示',
    message: '亲,确定退出问诊吗?'
  })
  store.delUser()
  router.push('/login')
}

说明❓:Dialog需要单独引入样式

main.ts

...
+ import 'vant/es/dialog/style';
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

THE ORDER

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值