用户模块布局
- 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
的类型
步骤:
- 给路由添加
meta
元信息 - 前置守卫获取元信息设置网页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函数
- 获取数据进行渲染
需求分析❓:
-
定义api接口返回数据类型
-
封装后台api函数
-
获取用户信息数据渲染显示
-
退出登录
代码:
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';