uni-app(优医咨询)项目实战 - 第4天
学习目标:
- 掌握登录权限验证的实现方法
- 能够动态设置导航栏标题
- 能够动态设置tabBar角标文字
- 知道验证身份证号的正则表达式
- 掌握uni-swipe-action侧滑组件的使用方法
一、权限验证
此处的权限验证是指服务端接口验证码 token 是否存在或有效,这就需要我们在调用接口时将 token 以自定义头信息的方式发送给服务端接口,如果 token 不存在或者 token 过期了,则接口会返回状态码的值为 401。
关于权限验证的逻辑我们做如下的处理:
- 配置请求拦截器,读取 Pinia 中记录的 token 数据
- 检测接口返回的状态码是否为 401,如果是则跳转到登录页面
- 在登录成功后跳转回原来的页面
我们按上述的步骤分别来实现:
1.1 配置拦截器
代码语言:javascript复制// utils/http.js
// 导入模块
import Request from 'luch-request'
import { useUserStore } from '@/stores/user.js'
// 接口白名单
const whiteList = ['/code', '/login', '/login/password']
// 实例化网络请求
const http = new Request({
// 接口基地址
baseURL: 'https://consult-api.itheima.net/',
custom: {
loading: true,
},
})
// 请求拦截器
http.interceptors.request.use(
function (config) {
// 显示加载状态提示
if (config.custom.loading) {
uni.showLoading({ title: '正在加载...', mask: true })
}
// 用户相关的数据
const userStore = useUserStore()
// 全局默认的头信息(方便以后扩展)
const defaultHeader = {}
// 判断是否存在 token 并且不在接口白单当中
if (userStore.token && !whiteList.includes(config.url)) {
defaultHeader.Authorization = 'Bearer ' userStore.token
}
// 合并全局头信息和局部头信息(局部优先级高全局)
config.header = {
...defaultHeader,
...config.header,
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 响应拦截器
http.interceptors.response.use(
// ...
)
// 导出配置好的模网络模块
export { http }
注意事项:在组件之外调用 useXXXStore
时,为确保 pinia 实例被激活,最简单的方法就是将 useStore()
的调用放在 pinia 安 装后才会执行的函数中。
在【我的】页面中调用一个接口测试发起请求时,有没有自定义头信息 Authorization
<!-- pages/my/index.vue -->
<script setup>
// 测试的代码,将来会被删除
import { http } from '@/utils/http.js'
// 调用接口
http.get('/patient/myUser')
</script>
测试两种情况:一是登录成功后,另一种是未登录时,观察是否存在请求头 Authorization
1.2 检测状态码
调用接口后服务端检测到没有传递 token
或者 token
失效时,状态码会返回 401
(后端人员与前端人员约定好的,也可以是其它的数值),在响应拦截器读取状态码。
// utils/http.js
// 导入模块
import Request from 'luch-request'
import { useUserStore } from '@/stores/user.js'
// 实例化网络请求
const http = new Request({
// 接口基地址
baseURL: 'https://consult-api.itheima.net/',
custom: {
loading: true,
},
})
// 请求拦截器
http.interceptors.request.use(
// ...
)
// 响应拦截器
http.interceptors.response.use(
function ({ statusCode, data, config }) {
// 隐藏加载状态提示
uni.hideLoading()
// 解构出响应主体
return data
},
function (error) {
// 隐藏加载状态提示
uni.hideLoading()
// 后端约定 token 过期(失效)时,状态码值为 401
if (error.statusCode === 401) reLogin()
return Promise.reject(error)
}
)
// 引导用户重新登录
function reLogin() {
// 跳转到登录页面
uni.redirectTo({
url: `/pages/login/index`,
})
}
// 导出配置好的模网络模块
export { http }
在此还有一点优化的空间,就是在请求前判断是否有 token ,如果没有的则不发起请求。
1.3 重定向页面
在用户登录成功后需要跳回登录前的页面,要实现这个逻辑就要求在跳转到登录页之前读取到这个页面的路径(路由),然后在登录成功后再跳转回这个页面,分成两个步骤来实现:
- 获取并记录跳转登录前的页面地址
在登录页面获取到登录前的页面地址,通常有两种方式实现:一是通过 URL 参数传递,另一种是通过 Pinia 状态管理,但由于小程序中借助地址传参时存在局限性,因此我们只能选择用 Pinia 状态管理实现。
代码语言:javascript复制// stores/user.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore(
'user',
() => {
// 记录用户登录状态
const token = ref('')
// 记录登录成功后要路转的地址(默认值为首页)
const redirectURL = ref('/pages/index/index')
// 跳转地址时采用的 API 名称
const openType = ref('switchTab')
return { token, redirectURL,openType }
},
{
persist: {
// redirectURL 和 openType 也要持久化存储
paths: ['token', 'redirectURL', 'openType'],
},
}
)
小程序提供了多种路由路转的 API,如 uni.switchTab
、uni.redirectTo
、uni.navigateTo
等,大家应该还记得 tabBar 的页面跳转时只能使用 uni.switchTab
,因此登录成功后进行跳转时需要判断页面地址是否为 tabBar 页面,如果是则用 uni.switchTab
跳转,否则用 uni.redirectTo
跳转。
小程序规定 tabBar 页面最多只能有 5 个,因此我们可以事先将 tabBar 中定义好的页面路径定义在一个数组件,然后根据数组方法 includes
来判断是否为 tabBar 的页面路径。
// utils/http.js
// 导入模块
import Request from 'luch-request'
import { useUserStore } from '@/stores/user.js'
// tabBar页面路径
const tabBarList = [
'pages/index/index',
'pages/wiki/index',
'pages/notify/index',
'pages/my/index',
]
// 省略前面小节的代码...
// 引导用户重新登录
function reLogin() {
// 动态读取当前页面的路径
const pageStack = getCurrentPages()
const currentPage = pageStack[pageStack.length - 1]
// 完整的路由(包含地址中的参数)
const redirectURL = currentPage.$page.fullPath
// 是否为 tabBar 中定义的路径
const openType = tabBarList.includes(currentPage.route) ? 'switchTab' : 'redirectTo'
// 用户相关数据
const userStore = useUserStore()
// 将来再跳转回这个页面
userStore.redirectURL = redirectURL
// 页面(路由)跳转方式
userStore.openType = openType
// 跳转到登录页面
uni.redirectTo({ url: `/pages/login/index` })
}
// 导出配置好的模网络模块
export { http }
注意事项:在小程序中 /pages/login/index?name=xiaoming?a=1
这种格式的页面地址(地址中出现两个 ?)在跳转时会自动的将参数过滤掉,变成 /pages/login/index?name=xiaoming
,这个特点大家要记住。
- 登录成功后,跳回到登录前的页面
接下来在登录成功后读取 redirectURL
和 openType
,然后跳转加这个页面(路由)
<!-- pages/login/components/mobile.vue -->
<script setup>
import { ref } from 'vue'
import { loginByMobileApi, verifyCodeApi } from '@/services/user'
import { useUserStore } from '@/stores/user'
// 用户相关的数据
const userStore = useUserStore()
// 省略前面小节代码...
// 提交表单数据
async function onFormSubmit() {
// 判断是否勾选协议
if (!isAgree.value) return uni.utils.toast('请先同意协议!')
// 调用 uniForms 组件验证数据的方法
try {
// 验证通过后会返回表单的数据
const formData = await formRef.value.validate()
// 提交表单数据
const { code, data, message } = await loginByMobileApi(formData)
// 检测接口是否调用成功
if (code !== 10000) return uni.utils.toast(message)
// 持久化存储 token
userStore.token = data.token
// 跳转到登录前的页面
uni[userStore.openType]({
url: userStore.redirectURL,
})
} catch (error) {
console.log(error)
}
}
// 省略前面小节代码...
</script>
<template>
...
</template>
二、我的
我的,即个人中心页面,这个页面中包含了用户的基本信息、数据统计和一些功能模块入口。
2.1 页面布局
在该页面当中使用到了多色图标,并且是配合 uni-list
组件来使用的,还要注意的是这个页面使用的是自定义导航栏。
{
"pages": [
{
"path": "pages/my/index",
"style": {
"navigationBarTitleText": "我的",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}
]
}
2.1.1 scroll-page
在手机屏幕中常常要处理不同类型的屏幕,比如异形屏(浏海屏)需要处理好安全区域内容的展示,为此我们来专封装一个组件,在该组件统一进行处理,要求该组件满足:
- 页面可以滚动
- 适配安全区域
- 自定义底部 tabBar 边框线
- 支持下拉刷新和上拉加载
首先按照 easycom
规范新建组件 scroll-page
- 使用内置组件
scroll-view
保证页面可以滚动,并且scroll-view
的高度为视口的高度
<!-- /components/scroll-page.vue -->
<script setup>
// 读取页面视口的高度
const { windowHeight } = uni.getSystemInfoSync()
</script>
<template>
<scroll-view
:style="{ height: windowHeight 'px'}"
scroll-y
>
<view></view>
</scroll-view>
</template>
<style lang="scss"></style>
- 适配安全区域
<!-- /components/scroll-page.vue -->
<script setup>
// 读取页面视口的高度
const { windowHeight } = uni.getSystemInfoSync()
</script>
<template>
<scroll-view :style="{ height: windowHeight 'px' }" scroll-y>
<view class="scroll-page-content">
<slot></slot>
</view>
</scroll-view>
</template>
<style lang="scss">
.scroll-page-content {
padding-bottom: env(safe-area-inset-bottom);
}
</style>
- 自定义底部 tabBar 边框线
小程序中底部 tabBar 的边框线只能定义黑色或白色,在开发中非常不实用,我们来给 scroll-page
添加底部边框线的方式来模拟实现 tabBar 边框线的效果。
<!-- /components/scroll-page.vue -->
<script setup>
// 读取页面视口的高度
const { windowHeight } = uni.getSystemInfoSync()
// 自定义组件属性
const scrollPageProps = defineProps({
borderStyle: {
type: [String, Boolean],
default: false,
},
})
</script>
<template>
<scroll-view
:style="{
height: windowHeight 'px',
boxSizing: 'border-box',
borderBottom: scrollPageProps.borderStyle,
}"
scroll-y
>
<view class="scroll-page-content">
<slot></slot>
</view>
</scroll-view>
</template>
<style lang="scss">
.scroll-page-content {
padding-bottom: env(safe-area-inset-bottom);
}
</style>
- 基于内置组件
scroll-view
实现下拉刷新交互
<script setup>
// 读取页面视口的高度
const { windowHeight } = uni.getSystemInfoSync()
// 自定义组件属性
const scrollPageProps = defineProps({
borderStyle: {
type: [String, Boolean],
default: false,
},
refresherEnabled: {
type: Boolean,
default: false,
},
refresherTriggered: {
type: Boolean,
default: false,
},
})
// 自定义事件
defineEmits(['refresherrefresh', 'scrolltolower'])
</script>
<template>
<scroll-view
:style="{
height: windowHeight 'px',
boxSizing: 'border-box',
borderBottom: scrollPageProps.borderStyle,
}"
scroll-y
:refresherEnabled="scrollPageProps.refresherEnabled"
:refresherTriggered="scrollPageProps.refresherTriggered"
@refresherrefresh="$emit('refresherrefresh', $event)"
@scrolltolower="$emit('scrolltolower', $event)"
>
<view class="scroll-page-content">
<slot></slot>
</view>
</scroll-view>
</template>
<style lang="scss">
.scroll-page-content {
padding-bottom: env(safe-area-inset-bottom);
}
</style>
自定义组件 scroll-page
本质上就是对内置组件 scroll-view
进行的二次封装。
2.1.2 custom-section
为了保证统一的页面风格,我们需要封装一个自定义组件 custom-section
通过这个组件来统一布局页面中的不同版块,该组件定义成全局组件并符合 easycom
组件规范,该组件要求满足:
- 自定义标题
- 自定义样式
- 右侧是否显示箭头
<!-- /components/custom-section/custom-section.vue -->
<script setup>
const sectionProps = defineProps({
title: {
type: String,
default: '',
},
showArrow: {
type: Boolean,
default: false,
},
customStyle: {
type: Object,
default: {},
},
})
</script>
<template>
<view class="custom-section" :style="{ ...sectionProps.customStyle }">
<view class="custom-section-header">
<view class="section-header-title">{{ sectionProps.title }}</view>
<view class="section-header-right">
<slot name="right" />
<uni-icons
v-if="sectionProps.showArrow"
color="#c3c3c5"
size="16"
type="forward"
/>
</view>
</view>
<slot />
</view>
</template>
<style lang="scss">
.custom-section {
padding: 40rpx 30rpx 30rpx;
margin-bottom: 20rpx;
background-color: #fff;
border-radius: 20rpx;
}
.custom-section-header {
display: flex;
justify-content: space-between;
line-height: 1;
margin-bottom: 20rpx;
}
.section-header-title {
font-size: 32rpx;
color: #333;
}
.section-header-right {
display: flex;
align-items: center;
font-size: 26rpx;
color: #c3c3c5;
}
</style>
2.1.2 布局模板
代码语言:javascript复制<!-- pages/my/index.vue -->
<script setup></script>
<template>
<scroll-page background-color="#F6F7F9">
<view class="my-page">
<!-- 用户资料(头像&昵称) -->
<view class="user-profile">
<image
class="user-avatar"
src="/static/uploads/doctor-avatar.jpg"
></image>
<view class="user-info">
<text class="nickname">用户907456</text>
<text class="iconfont icon-edit"></text>
</view>
</view>
<!-- 用户数据 -->
<view class="user-data">
<navigator hover-class="none" url=" ">
<text class="data-number">150</text>
<text class="data-label">收藏</text>
</navigator>
<navigator hover-class="none" url=" ">
<text class="data-number">23</text>
<text class="data-label">关注</text>
</navigator>
<navigator hover-class="none" url=" ">
<text class="data-number">230</text>
<text class="data-label">积分</text>
</navigator>
<navigator hover-class="none" url=" ">
<text class="data-number">3</text>
<text class="data-label">优惠券</text>
</navigator>
</view>
<!-- 问诊医生 -->
<custom-section :custom-style="{ paddingBottom: '20rpx' }" title="问诊中">
<swiper
class="uni-swiper"
indicator-active-color="#2CB5A5"
indicator-color="#EAF8F6"
indicator-dots
>
<swiper-item>
<view class="doctor-brief">
<image
class="doctor-avatar"
src="/static/uploads/doctor-avatar.jpg"
/>
<view class="doctor-info">
<view class="meta">
<text class="name">王医生</text>
<text class="title">内分泌科 | 主任医师</text>
</view>
<view class="meta">
<text class="tag">三甲</text>
<text class="hospital">积水潭医院</text>
</view>
</view>
<navigator class="doctor-contcat" hover-class="none" url=" ">
进入咨询
</navigator>
</view>
</swiper-item>
<swiper-item>
<view class="doctor-brief">
<image
class="doctor-avatar"
src="/static/uploads/doctor-avatar.jpg"
/>
<view class="doctor-info">
<view class="meta">
<text class="name">王医生</text>
<text class="title">内分泌科 | 主任医师</text>
</view>
<view class="meta">
<text class="tag">三甲</text>
<text class="hospital">积水潭医院</text>
</view>
</view>
<navigator class="doctor-contcat" hover-class="none" url=" ">
进入咨询
</navigator>
</view>
</swiper-item>
</swiper>
</custom-section>
<!-- 药品订单 -->
<custom-section show-arrow title="药品订单">
<template #right>
<navigator hover-class="none" url=" ">
全部订单
</navigator>
</template>
<view class="drug-order">
<navigator hover-class="none" url=" ">
<uni-badge :text="0" :offset="[3, 3]" absolute="rightTop">
<image
src="/static/images/order-status-1.png"
class="status-icon"
/>
</uni-badge>
<text class="status-label">待付款</text>
</navigator>
<navigator hover-class="none" url=" ">
<uni-badge text="2" :offset="[3, 3]" absolute="rightTop">
<image
src="/static/images/order-status-2.png"
class="status-icon"
/>
</uni-badge>
<text class="status-label">待付款</text>
</navigator>
<navigator hover-class="none" url=" ">
<uni-badge :text="0" :offset="[3, 3]" absolute="rightTop">
<image
src="/static/images/order-status-3.png"
class="status-icon"
/>
</uni-badge>
<text class="status-label">待付款</text>
</navigator>
<navigator hover-class="none" url=" ">
<uni-badge :text="0" :offset="[3, 3]" absolute="rightTop">
<image
src="/static/images/order-status-4.png"
class="status-icon"
/>
</uni-badge>
<text class="status-label">待付款</text>
</navigator>
</view>
</custom-section>
<!-- 快捷工具 -->
<custom-section title="快捷工具">
<uni-list :border="false">
<uni-list-item
:border="false"
title="我的问诊"
show-arrow
show-extra-icon
:extra-icon="{
customPrefix: 'icon-symbol',
type: 'icon-symbol-tool-01',
}"
/>
<uni-list-item
:border="false"
title="我的处方"
show-arrow
show-extra-icon
:extra-icon="{
customPrefix: 'icon-symbol',
type: 'icon-symbol-tool-02',
}"
/>
<uni-list-item
:border="false"
title="家庭档案"
show-arrow
show-extra-icon
:extra-icon="{
customPrefix: 'icon-symbol',
type: 'icon-symbol-tool-03',
}"
/>
<uni-list-item
:border="false"
title="地址管理"
show-arrow
show-extra-icon
:extra-icon="{
customPrefix: 'icon-symbol',
type: 'icon-symbol-tool-04',
}"
/>
<uni-list-item
:border="false"
title="我的评价"
show-arrow
show-extra-icon
:extra-icon="{
customPrefix: 'icon-symbol',
type: 'icon-symbol-tool-05',
}"
/>
<uni-list-item
:border="false"
title="官方客服"
show-arrow
show-extra-icon
:extra-icon="{
customPrefix: 'icon-symbol',
type: 'icon-symbol-tool-06',
}"
/>
<uni-list-item
:border="false"
title="设置"
show-arrow
show-extra-icon
:extra-icon="{
customPrefix: 'icon-symbol',
type: 'icon-symbol-tool-07',
}"
/>
</uni-list>
</custom-section>
<!-- 退出登录 -->
<view class="logout-button">退出登录</view>
</view>
</scroll-page>
</template>
<style lang="scss">
@import './index.scss';
</style>
代码语言:javascript复制// pages/my/index.scss
.my-page {
min-height: 500rpx;
padding: 150rpx 30rpx 10rpx;
background-image: linear-gradient(
180deg,
rgba(44, 181, 165, 0.46) 0,
rgba(44, 181, 165, 0) 500rpx
);
}
.user-profile {
display: flex;
height: 140rpx;
}
.user-avatar {
width: 140rpx;
height: 140rpx;
border-radius: 50%;
}
.user-info {
display: flex;
flex-direction: column;
justify-content: space-between;
line-height: 1;
padding: 30rpx 0;
margin-left: 24rpx;
.nickname {
font-size: 36rpx;
font-weight: 500;
color: #333;
}
.icon-edit {
color: #16c2a3;
padding-top: 20rpx;
font-size: 32rpx;
}
}
.user-data {
display: flex;
justify-content: space-around;
height: 100rpx;
text-align: center;
line-height: 1;
margin: 50rpx 0 30rpx;
.data-number {
display: block;
margin-bottom: 10rpx;
font-size: 48rpx;
color: #333;
}
.data-label {
display: block;
font-size: 24rpx;
color: #979797;
}
}
.doctor-brief {
display: flex;
align-items: center;
height: 160rpx;
.doctor-avatar {
width: 100rpx;
height: 100rpx;
margin-left: 10rpx;
border-radius: 50%;
}
.doctor-info {
height: 100rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-left: 12rpx;
flex: 1;
}
.name {
font-size: 36rpx;
color: #3c3e42;
margin-right: 10rpx;
}
.title {
font-size: 24rpx;
color: #6f6f6f;
}
.tag {
line-height: 1;
padding: 2rpx 16rpx;
font-size: 22rpx;
color: #fff;
border-radius: 6rpx;
background-color: #677fff;
}
.hospital {
font-size: 26rpx;
color: #3c3e42;
margin-left: 10rpx;
}
.doctor-contcat {
line-height: 1;
padding: 16rpx 24rpx;
border-radius: 100rpx;
font-size: 24rpx;
color: #2cb5a5;
background-color: rgba(44, 181, 165, 0.1);
}
}
.uni-swiper {
height: 200rpx;
}
.drug-order {
display: flex;
justify-content: space-between;
text-align: center;
padding: 30rpx 20rpx 10rpx;
.status-icon {
width: 54rpx;
height: 54rpx;
}
.status-label {
display: block;
font-size: 24rpx;
margin-top: 10rpx;
color: #3c3e42;
}
}
:deep(.uni-list-item__content-title) {
font-size: 30rpx !important;
color: #3c3e42 !important;
}
:deep(.uni-list-item__container) {
padding: 20rpx 0 !important;
}
:deep(.uni-icon-wrapper) {
padding-right: 0 !important;
color: #c3c3c5 !important;
}
:deep(.uni-icons) {
display: block !important;
}
.logout-button {
height: 88rpx;
text-align: center;
line-height: 88rpx;
margin: 40rpx 0 30rpx;
border-radius: 20rpx;
font-size: 32rpx;
color: #3c3e42;
background-color: #fff;
}
2.2 个人信息
在用户处于登录状态时调用接口获取户的头像、昵称等个人信息,我们分成两个步骤来实现:
- 封装调用接口的方法,接口文档地址在这里
// services/user.js
// 导入封装好的网络请求模块
import { http } from '@/utils/http'
// 省略前面小节的代码...
/**
* 获取用户信息
*/
export const userInfoApi = () => {
return http.get('/patient/myUser')
}
- 调用方法获取数据
<!-- pages/my/index.vue -->
<script setup>
import { ref } from 'vue'
import { userInfoApi } from '@/services/user'
// 用户信息
const userInfo = ref({})
// 获取用户信息
async function getUserInfo() {
// 调用接口获取用户信息
const { code, data, message } = await userInfoApi()
// 检测接口是否调用成功
if (code !== 10000) return uni.utils.toast(message)
// 渲染用户数据
userInfo.value = data
}
// 获取个人信息
getUserInfo()
</script>
- 渲染个人信息
<!-- pages/my/index.vue -->
<template>
<scroll-page background-color="#F6F7F9">
<view class="my-page">
<!-- 用户资料(头像&昵称) -->
<view class="user-profile">
<image class="user-avatar" :src="userInfo.avatar"></image>
<view class="user-info">
<text class="nickname">{{ userInfo.account }}</text>
<text class="iconfont icon-edit"></text>
</view>
</view>
<!-- 用户数据 -->
<view class="user-data">
<navigator hover-class="none" url=" ">
<text class="data-number">{{ userInfo.collectionNumber }}</text>
<text class="data-label">收藏</text>
</navigator>
<navigator hover-class="none" url=" ">
<text class="data-number">{{ userInfo.likeNumber }}</text>
<text class="data-label">关注</text>
</navigator>
<navigator hover-class="none" url=" ">
<text class="data-number">{{ userInfo.score }}</text>
<text class="data-label">积分</text>
</navigator>
<navigator hover-class="none" url=" ">
<text class="data-number">{{ userInfo.couponNumber }}</text>
<text class="data-label">优惠券</text>
</navigator>
</view>
<!-- 此处省略前面小节代码... -->
<!-- 退出登录 -->
<view class="logout-button">退出登录</view>
</view>
</scroll-page>
</template>
2.3 退出登录
退出登录仅需将本地登录状态,即 token 清空即可,然后再跳转到登录页面。
代码语言:javascript复制<!-- pages/my/index.vue -->
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { userInfoApi } from '@/services/user'
import { useUserStore } from '@/stores/user'
// 用户相关的数据
const userStore = useUserStore()
// 省略前面小节的代码...
// 退出登录
function onLogoutClick() {
// 清除登录状态
userStore.token = ''
// 重置 Pinia 的数据
userStore.openType = 'switchTab'
userStore.redirectURL = '/pages/index/index'
// 跳转到登录页
uni.reLaunch({ url: '/pages/login/index' })
}
// 省略前面小节的代码...
</script>
<template>
<scroll-page background-color="#F6F7F9">
<view class="my-page">
<!-- 此处省略前面小节的代码... -->
<!-- 退出登录 -->
<view @click="onLogoutClick" class="logout-button">退出登录</view>
</view>
</scroll-page>
</template>
<style lang="scss">
@import './index.scss';
</style>
在上述代码中要注意,不仅清除了用户的登录状态 token
,同时还将 redirectURL
和 openType
重置为默认值,目的是重新登录后能够跳转到首页面。
还有就是在跳转页面时使用了 uni.reLaunch
,目的是清除所有的页面历史,不允许再有返回的操作。
三、家庭档案
家庭档案就是要填写并保存患者信息,有添加患者、删除患者、编辑患者和患者列表4部分功能构成。
3.1 创建分包
创建分包来包含家庭档案相关的页面,分包目录为 subpkg_archive
{
"pages": [],
"globalStyle": {},
"tabBar": {},
"subPackages": [
{
"root": "subpkg_archive",
"pages": [
{
"path": "form/index",
"style": {
"navigationBarTitleText": "添加患者"
}
},
{
"path": "list/index",
"style": {
"navigationBarTitleText": "患者列表"
}
}
]
}
],
"uniIdRouter": {}
}
注意事项:先按上述的分包配置创建页面,再去 pages.json
中添加配置。
3.2 添加患者
填写患者信息包括姓名、身份证号、性别等,以表单的方式填写。
3.2.1 布局模板
代码语言:javascript复制<!-- subpkg_archive/form/index.vue -->
<script setup>
import { ref } from 'vue'
const isDefault = ref([0])
</script>
<template>
<scroll-page>
<view class="archive-page">
<uni-forms border label-width="220rpx" ref="form">
<uni-forms-item label="患者姓名" name="name">
<uni-easyinput
placeholder-style="color: #C3C3C5; font-size: 32rpx"
:styles="{ color: '#121826' }"
:input-border="false"
:clearable="false"
placeholder="请填写真实姓名"
/>
</uni-forms-item>
<uni-forms-item label="患者身份证号" name="name">
<uni-easyinput
placeholder-style="color: #C3C3C5; font-size: 32rpx"
:styles="{ color: '#121826' }"
:input-border="false"
:clearable="false"
placeholder="请填写身份证号"
/>
</uni-forms-item>
<uni-forms-item label="患者性别" name="name">
<uni-data-checkbox
selectedColor="#16C2A3"
:localdata="[
{ text: '男', value: 1 },
{ text: '女', value: 0 },
]"
/>
</uni-forms-item>
<uni-forms-item label="默认就诊人" name="name">
<view class="uni-switch">
<switch checked color="#20c6b2" style="transform: scale(0.7)" />
</view>
</uni-forms-item>
<button class="uni-button">保存</button>
</uni-forms>
</view>
</scroll-page>
</template>
<style lang="scss">
@import './index.scss';
</style>
代码语言:javascript复制// subpkg_archive/form/index.scss
.archive-page {
padding: 30rpx;
:deep(.uni-forms-item) {
padding-top: 30rpx !important;
padding-bottom: 20rpx !important;
}
:deep(.uni-forms-item__label) {
font-size: 32rpx;
color: #3c3e42;
}
:deep(.uni-forms-item--border) {
border-top: none;
border-bottom: 1rpx solid #ededed;
}
:deep(.uni-easyinput__content-input) {
height: 36px;
}
:deep(.checklist-text) {
font-size: 32rpx !important;
margin-left: 20rpx !important;
}
// :deep(.uni-forms-item__error) {
// position: absolute !important;
// left: -220rpx !important;
// right: 0 !important;
// top: auto !important;
// padding-top: 10rpx !important;
// margin-top: 20rpx;
// border-top: 2rpx solid #eb5757;
// color: #eb5757;
// font-size: 24rpx;
// transition: none;
// }
:deep(.uni-data-checklist) {
display: flex;
height: 100%;
padding-left: 10px;
}
:deep(.radio__inner) {
transform: scale(1.25);
}
:deep(.checkbox__inner) {
transform: scale(1.25);
}
}
.uni-switch {
display: flex;
align-items: center;
height: 100%;
}
.uni-button {
margin-top: 60rpx;
}
HBuilder X 使用小技巧:当在编辑器中正在编辑某个页面时,点击【重新运行】,会自动在浏览器中打开这个页面,例如正在编辑的页面是 subpkg_archive/form/index.vue
,点击【重新运行】会自动在浏览器中打这个页面。
3.2.2 表单数据验证
在填写好表单数据后还需要验证表单数据的合法,数据合法后再提交给后端接口,分3个步骤来对数据进行验证:
- 获取表单数据,根据接口需要来定义数据名称并获取数据
<!-- subpkg_archive/form/index.vue -->
<script setup>
import { ref } from 'vue'
// 表单数据
const formData = ref({
name: '',
idCard: '',
gender: 1,
defaultFlag: 1,
})
// 是否为默认就诊人
function onSwitchChange(ev) {
// 是否设置为默认就诊患人
formData.value.defaultFlag = ev.detail.value ? 1 : 0
}
</script>
<template>
<scroll-page>
<view class="archive-page">
<uni-forms border label-width="220rpx" :model="formData" ref="form">
<uni-forms-item label="患者姓名" name="name">
<uni-easyinput
v-model="formData.name"
placeholder-style="color: #C3C3C5; font-size: 32rpx"
:styles="{ color: '#121826' }"
:input-border="false"
:clearable="false"
placeholder="请填写真实姓名"
/>
</uni-forms-item>
<uni-forms-item label="患者身份证号" name="idCard">
<uni-easyinput
v-model="formData.idCard"
placeholder-style="color: #C3C3C5; font-size: 32rpx"
:styles="{ color: '#121826' }"
:input-border="false"
:clearable="false"
placeholder="请填写身份证号"
/>
</uni-forms-item>
<uni-forms-item label="患者性别" name="gender">
<uni-data-checkbox
v-model="formData.gender"
selectedColor="#16C2A3"
:localdata="[
{ text: '男', value: 1 },
{ text: '女', value: 0 },
]"
/>
</uni-forms-item>
<uni-forms-item label="默认就诊人">
<view class="uni-switch">
<switch
@change="onSwitchChange"
:checked="formData.defaultFlag === 1"
color="#20c6b2"
style="transform: scale(0.7)"
/>
</view>
</uni-forms-item>
<button class="uni-button">保存</button>
</uni-forms>
</view>
</scroll-page>
</template>
在获取表单数据时,用户名、身份证号、性别都是通过 v-model
来获取的,而默认就诊人则是通过监听 change
事件来获取的,并且接口接收的数据为 0
和 1
而不是 true
和 false
。
注意事项:
uni-froms
组件要添加:model
属性uni-forms-item
组件要添加name
属性
- 定义数据验证规则
为不同的表单数据定义不同的验证规:
- 验证中文姓名正则
^[u4e00-u9fa5]{2,5}$
- 验证身份证
^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$
<!-- subpkg_archive/form/index.vue -->
<script setup>
import { ref } from 'vue'
// 表单数据
const formData = ref({
name: '',
idCard: '',
gender: 1,
defaultFlag: 1,
})
// 表单验证规则
const formRules = {
name: {
rules: [
{ required: true, errorMessage: '请填写患者姓名' },
{
pattern: '^[u4e00-u9fa5]{2,5}$',
errorMessage: '患者姓名为2-5位中文',
},
],
},
idCard: {
rules: [
{ required: true, errorMessage: '请输入身份证号' },
{
pattern:
'^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$',
errorMessage: '身份证号格式不正确',
},
],
},
gender: {
rules: [
{ required: true, errorMessage: '请勾选患者姓名' },
],
},
}
// 是否为默认就诊人
function onSwitchChange(ev) {
// 是否设置为默认就诊患人
formData.value.defaultFlag = ev.detail.value ? 1 : 0
}
</script>
<template>
<scroll-page>
<view class="archive-page">
<uni-forms
border
label-width="220rpx"
:model="formData"
:rules="formRules"
ref="form"
>
<!-- 省略前面小节代码... -->
</uni-forms>
</view>
</scroll-page>
</template>
关于性别的验证还有补充,我们先把下一小节学习完再回来介绍。
我们都知道根据身份证号是可以区别性别的,当用户勾选的性别与身份证号性别不符时,要以身份证号中的性别为准,这就要求判断身份证号中性别与勾选的性别是否相同。
实现的关键步骤:
- 身份证号中第17位用来标识性别,偶数为女,奇数为男。
validateFunction
自定义数据校验的逻辑,返回值为true
表示验证通过,验证不通过时调用callback
方法。
// 表单验证规则
const formRules = {
name: {
rules: [
{ required: true, errorMessage: '请填写患者姓名' },
{
pattern: '^[u4e00-u9fa5]{2,5}$',
errorMessage: '患者姓名为2-5位中文',
},
],
},
idCard: {
rules: [
{ required: true, errorMessage: '请输入身份证号' },
{
pattern:
'^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$',
errorMessage: '身份证号格式不正确',
},
],
},
gender: {
rules: [
{ required: true, errorMessage: '请勾选患者性别' },
{
validateFunction(rule, value, data, callback) {
// 检测身份证号第17位是否为偶数
if (data.idCard.slice(16, 17) % 2 !== value) {
callback('选择的性别与身份号中性别不一致')
}
// 验证通过时返回 true
return true
},
},
],
},
}
- 调用验证方法
<!-- subpkg_archive/form/index.vue -->
<script setup>
import { ref } from 'vue'
// 表单组件 ref
const formRef = ref()
// 表单数据
const formData = ref({
name: '',
idCard: '',
gender: 1,
defaultFlag: 0,
})
// 省略前面小节的代码...
// 提交表单数据
async function onFormSubmit() {
try {
// 根据验证规则验证数据
await formRef.value.validate()
} catch(error) {
console.log(error)
}
}
// 是否为默认就诊人
function onSwitchChange(ev) {
// 是否设置为默认就诊患人
formData.value.defaultFlag = ev.detail.value ? 1 : 0
}
</script>
<template>
<scroll-page>
<view class="archive-page">
<uni-forms
border
label-width="220rpx"
:model="formData"
:rules="formRules"
ref="formRef"
>
<!-- 省略前面小节代码... -->
<button @click="onFormSubmit" class="uni-button">保存</button>
</uni-forms>
</view>
</scroll-page>
</template>
测试用身份证号数据:
- 110101198307212600
- 110101196107145504
- 11010119890512132X
- 110101196501023433
- 110101197806108758
- 110101198702171378
- 110101198203195893
- 如有雷同纯属巧合,可删除。
3.2.3 提交数据
- 根据接口文档封装接口调用的方法,文档的地址在这里。
// services/patinet.js
// 导入封装好的网络请求模块
import { http } from '@/utils/http'
/**
* 添加患者(家庭档案)
*/
export const addPatientApi = (data) => {
return http.post('/patient/add', data)
}
- 调用接口提交数据
<script setup>
import { ref } from 'vue'
import { addPatientApi } from '@/services/patient'
// 省略前面部分代码...
// 提交表单数据
async function onFormSubmit() {
try {
// 根据验证规则验证数据
const formData = await formRef.value.validate()
// 添加患者
addPatient()
} catch (error) {
console.log(error)
}
}
// 省略前面小节的代码...
// 添加患者信息
async function addPatient() {
// 添加患者接口
const { code, message } = await addPatientApi(formData.value)
// 检测接口是否调用成功
if (code !== 10000) return uni.utils.toast(message)
// 跳转到患者列表页面
uni.navigateBack()
}
</script>
在添加患者成功后的逻辑是到患者列表中进行查看,而在正常的添加患者逻辑中,添加患者的页面是从患者列表跳转过来的,因此我们调用 uni.navigateBack
返加上一页就可以了。
3.3 患者列表
在【我的】页面中找到【家庭档案】,给它添加链接地址跳转到患者列表页面。
代码语言:javascript复制<!-- pages/my/index.vue -->
<script setup>
// 省略前面小节代码...
</script>
<template>
<scroll-page background-color="#F6F7F9">
<view class="my-page">
<!-- 省略前面小节的代码... -->
<!-- 快捷工具 -->
<custom-section title="快捷工具">
<uni-list :border="false">
...
<uni-list-item
:border="false"
title="家庭档案"
show-arrow
show-extra-icon
to="/subpkg_archive/list/index"
:extra-icon="{
customPrefix: 'icon-symbol',
type: 'icon-symbol-tool-03',
}"
/>
...
</uni-list>
</custom-section>
<!-- 退出登录 -->
<view @click="onLogoutClick" class="logout-button">退出登录</view>
</view>
</scroll-page>
</template>
3.3.1 布局模板
代码语言:javascript复制<!-- subpkg_archive/list/index.vue -->
<script setup>
import { ref } from 'vue'
const swipeOptions = ref([
{
text: '删除',
style: {
backgroundColor: '#dd524d',
},
},
])
</script>
<template>
<scroll-page>
<view class="archive-page">
<view class="archive-tips">最多可添加6人</view>
<uni-swipe-action>
<uni-swipe-action-item :right-options="swipeOptions">
<view class="archive-card active">
<view class="archive-info">
<text class="name">李富贵</text>
<text class="id-card">321***********6164</text>
<text class="default">默认</text>
</view>
<view class="archive-info">
<text class="gender">男</text>
<text class="age">32岁</text>
</view>
<navigator
hover-class="none"
class="edit-link"
url="/subpkg_archive/form/index"
>
<uni-icons
type="icon-edit"
size="20"
color="#16C2A3"
custom-prefix="iconfont"
/>
</navigator>
</view>
</uni-swipe-action-item>
<uni-swipe-action-item :right-options="swipeOptions">
<view class="archive-card">
<view class="archive-info">
<text class="name">李富贵</text>
<text class="id-card">321***********6164</text>
</view>
<view class="archive-info">
<text class="gender">男</text>
<text class="age">32岁</text>
</view>
<navigator
hover-class="none"
class="edit-link"
url="/subpkg_archive/form/index"
>
<uni-icons
type="icon-edit"
size="20"
color="#16C2A3"
custom-prefix="iconfont"
/>
</navigator>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
<!-- 添加按钮 -->
<view v-if="true" class="archive-card">
<navigator
class="add-link"
hover-class="none"
url="/subpkg_archive/form/index"
>
<uni-icons color="#16C2A3" size="24" type="plusempty" />
<text class="label">添加患者</text>
</navigator>
</view>
</view>
</scroll-page>
</template>
<style lang="scss">
@import './index.scss';
</style>
代码语言:javascript复制// subpkg_archive/list/index.scss
.archive-page {
padding: 30rpx;
}
.archive-tips {
line-height: 1;
padding-left: 10rpx;
margin: 30rpx 0;
font-size: 26rpx;
color: #6f6f6f;
}
.archive-card {
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
height: 180rpx;
padding: 30rpx;
margin-bottom: 30rpx;
border-radius: 10rpx;
box-sizing: border-box;
border: 1rpx solid transparent;
background-color: #f6f6f6;
&.active {
background-color: rgba(44, 181, 165, 0.1);
// border: 1rpx solid #16c2a3;
.default {
display: block;
}
}
.archive-info {
display: flex;
align-items: center;
color: #6f6f6f;
font-size: 28rpx;
margin-bottom: 10rpx;
}
.name {
margin-right: 30rpx;
color: #121826;
font-size: 32rpx;
font-weight: 500;
}
.id-card {
color: #121826;
}
.gender {
margin-right: 30rpx;
}
.default {
display: none;
height: 36rpx;
line-height: 36rpx;
text-align: center;
padding: 0 12rpx;
margin-left: 30rpx;
border-radius: 4rpx;
color: #fff;
font-size: 24rpx;
background-color: #16c2a3;
}
}
.edit-link {
position: absolute;
top: 50%;
right: 30rpx;
transform: translateY(-50%);
}
.add-link {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.label {
margin-top: 10rpx;
font-size: 28rpx;
color: #16c2a3;
}
}
:deep(.uni-swipe_button-group) {
bottom: 30rpx;
}
3.3.2 获取数据
- 根据接口文档的要求封装接口调用的方法,接口文档请看这里。
// services/patinent.js
// 导入封装好的网络请求模块
import { http } from '@/utils/http'
/**
* 添加患者(家庭档案)
*/
export const addPatientApi = (data) => {
return http.post('/patient/add', data)
}
/**
* 获取患者(家庭档案)列表
*/
export const patientListApi = (data) => {
return http.get('/patient/mylist')
}
- 在页面调用接口获取数据并渲染
<!-- subpkg_archive/list/index.uve -->
<script setup>
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { patientListApi } from '@/services/patient'
// 是否显示页面内容
const pageShow = ref(false)
// 患者列表
const patinetList = ref([])
// 侧滑按钮配置
const swipeOptions = ref([
{
text: '删除',
style: {
backgroundColor: '#dd524d',
},
},
])
// 生命周期(页面显示)
onShow(() => {
getPatientList()
})
// 家庭档案(患者)列表
async function getPatientList() {
// 患者列表接口
const { code, data } = await patientListApi()
// 检测接口是否调用成功
if (code !== 10000) return uni.utils.showToast('列表获取失败,稍后重试!')
// 渲染接口数据
patinetList.value = data
// 展示页面内容
pageShow.value = true
}
</script>
<template>
<scroll-page>
<view class="archive-page" v-if="pageShow">
<view class="archive-tips">最多可添加6人</view>
<uni-swipe-action>
<uni-swipe-action-item
v-for="(patient, index) in patinetList"
:key="patient.id"
:right-options="swipeOptions"
>
<view
:class="{ active: patient.defaultFlag === 1 }"
class="archive-card"
>
<view class="archive-info">
<text class="name">{{ patient.name }}</text>
<text class="id-card">
{{ patient.idCard.replace(/^(.{6}). (.{4})$/, '$1********$2') }}
</text>
<text v-if="patient.defaultFlag === 1" class="default">默认</text>
</view>
<view class="archive-info">
<text class="gender">{{ patient.genderValue }}</text>
<text class="age">{{ patient.age }}岁</text>
</view>
<navigator
hover-class="none"
class="edit-link"
:url="`/subpkg_archive/form/index?id=${patient.id}`"
>
<uni-icons
type="icon-edit"
size="20"
color="#16C2A3"
custom-prefix="iconfont"
/>
</navigator>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
<!-- 添加按钮 -->
<view v-if="patinetList.length < 6" class="archive-card">
<navigator
class="add-link"
hover-class="none"
url="/subpkg_archive/form/index"
>
<uni-icons color="#16C2A3" size="24" type="plusempty" />
<text class="label">添加患者</text>
</navigator>
</view>
</view>
</scroll-page>
</template>
在渲染数据时要注意:
pageShow
避免页面的抖动,数据未请求结束时显示空白内容- 身份证号脱敏正则
/^(.{6}). (.{4})$/
- 最多只能添加 6 名患者,超出6个后隐藏添加按钮
- 跳转到编辑患者页面时地址中要拼接患者的 ID
- 数据获取在是
onShow
生命周期获取,组件式函数onShow
由@dcloudio/uni-app
提供
3.4 删除患者
用户在患者列表上向左滑动就能展示删除的按钮,点击这个按钮调用接口删除数据。
3.4.1 监听点击
此用用到了 uni-swipe-action-item
组件,该组件能够监听到用的点击事件
<!-- subpkg_archive/list/index.vue -->
<script setup>
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { patientListApi } from '@/services/patient'
// 省略前面小节的代码...
// 侧滑按钮配置
const swipeOptions = ref([
{
text: '删除',
style: {
backgroundColor: '#dd524d',
},
},
])
// 省略前面小节的代码...
// 滑动操作点击
async function onSwipeActionClick(id, index) {
// 传递数据的 id 值和索引值
}
// 省略前面小节的代码...
</script>
<template>
<scroll-page>
<view class="archive-page" v-if="pageShow">
<view class="archive-tips">最多可添加6人</view>
<uni-swipe-action>
<uni-swipe-action-item
v-for="(patient, index) in patinetList"
:key="patient.id"
:right-options="swipeOptions"
@click="onSwipeActionClick(patient.id, index)"
>
...
</uni-swipe-action-item>
</uni-swipe-action>
<!-- 省略前面小节代码... -->
</view>
</scroll-page>
</template>
在点击事件的回调函数里接收了待删除数据的 ID 和索引,这两个参数在后面小节当中会用到。
3.4.2 删除数据
- 根据接口文档封装调用接口的方法,接口文档地址在这里。
// services/patient.js
// 导入封装好的网络请求模块
import { http } from '@/utils/http'
// 省略前面小节的代码...
/**
* 删除患者(家庭档案)
*/
export const removePatientApi = (id) => {
return http.delete(`/patient/del/${id}`)
}
- 在点击事件回调中调用接口删除患者数据,在删除数据的时候要注意,调用接口是要删除服务器的患者数据,但是本地在 Vue 中也保存了一份患者数据,Vue 中保存的数据也可同步删除,根据索引值实来删除。
<!-- subpkg_archive/list/index.vue -->
<script setup>
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { patientListApi, removePatientApi } from '@/services/patient'
// 省略前面小节的代码...
// 侧滑按钮配置
const swipeOptions = ref([
{
text: '删除',
style: {
backgroundColor: '#dd524d',
},
},
])
// 省略前面小节的代码...
// 滑动操作点击
async function onSwipeActionClick(id, index) {
// 调用删除患者接口
const { code, message } = await removePatientApi(id)
// 检测接口是否调用成功
if (code !== 10000) return uni.utils.toast(message)
// Vue 实例中的数据也要同步删除
patinetList.value.splice(index, 1)
}
// 省略前面小节的代码...
</script>
<template>
<scroll-page>
<view class="archive-page" v-if="pageShow">
<view class="archive-tips">最多可添加6人</view>
<uni-swipe-action>
<uni-swipe-action-item
v-for="(patient, index) in patinetList"
:key="patient.id"
:right-options="swipeOptions"
@click="onSwipeActionClick(patient.id, index)"
>
...
</uni-swipe-action-item>
</uni-swipe-action>
<!-- 省略前面小节代码... -->
</view>
</scroll-page>
</template>
3.5 编辑患者
编辑患者与添加患者共有了相同的页面,区别在于编患者时需要在地址中包含患者的 ID,并且获取这个 ID 将患者原信息查询出来,在此基础之上进行修改(编辑)。
3.5.1 查询患者信息
- 根据接口文档获封装接口调用的方法来获取患者信息,接口文档地址在这里。
// services/patinet.js
import { http } from '@/utils/http'
// 省略前面小节的代码...
/**
* 患者详情(家庭档案)
*/
export const patientDetailApi = (id) => {
return http.get(`/patient/info/${id}`)
}
3.5.2 获取地址上参数,根据 ID 参数查询患者信息
在 uni-app 中获取页面地址参数有两种方法,一种是在 onLoad
生命周期中,另一种是使用 defineProps
,接下来分别演示两种用法:
<!-- subpkg_archive/form/index.vue -->
<script setup>
import { onLoad } from '@dcloudio/uni-app'
// 生命周期(页面加载完成)
onLoad((query) => {
console.log(query.id)
})
// 使用 defineProps 接收地址参数
const props = defineProps({ id: String })
console.log(props.id)
</script>
注意组件式函数 onLoad
由 @dcloudio/uni-app
提供,本小节使用 defineProps
来获取地址参数
<!-- subpkg_archive/form/index.vue -->
<script setup>
import { ref } from 'vue'
import { addPatientApi, patientDetailApi } from '@/services/patient'
// 省略前面小节的代码...
// 使用 defineProps 接收地址参数
const props = defineProps({ id: String })
// 省略前面小节的代码...
// 获取患者详情信息
async function getPatientDetail() {
// 是否存在患者 ID
if (!props.id) return
// 有ID说明当前处于编辑状态,修改页面标题
uni.setNavigationBarTitle({ title: '编辑患者' })
// 患者详情接口
const {
code,
data: { genderValue, age, ...rest },
} = await patientDetailApi(props.id)
// 渲染患者信息
formData.value = rest
}
// 查询患者信息
getPatientDetail()
</script>
在上述的代码中要注意:
- 务必要判断地址中是否包含有 ID,有 ID 的情况下才会出查询数据
uni.setNavigationBarTitle
API 动态修改导航栏的标题- 过滤掉多余的数据
age
和genderValue
,年龄是根据身份证号计算的,genderValue
不需要回显
3.5.2 更新患者信息
在原有患者信息基础之上进行修改,修改完毕合再次调用接口实现数据的更新,接口文档的在址在这里。
- 封装接口调用的方法
// services/patient.js
import { http } from '@/utils/http'
// 省略前面小节的代码...
/**
* 编辑(更新)患者(家庭档案)
*/
export const updatePatientApi = (data) => {
return http.put(`/patient/update`, data)
}
- 调用更新患者信息的接口
<!-- subpkg_archive/form/index.vue -->
<script setup>
import { ref } from 'vue'
import {
addPatientApi,
patientDetailApi,
updatePatientApi,
} from '@/services/patient'
// 省略前面小节的代码...
// 使用 defineProps 接收地址参数
const props = defineProps({ id: String })
// 提交表单数据
async function onFormSubmit() {
try {
// 根据验证规则验证数据
const formData = await formRef.value.validate()
// 添加患者或更新患者
/****************重要*****************/
props.id ? updatePatient() : addPatient()
/****************重要*****************/
} catch (error) {
console.log(error)
}
}
// 省略前面小节的代码...
// 添加患者信息
async function addPatient() {
// 添加患者接口
const { code, message } = await addPatientApi(formData.value)
// 检测接口是否调用成功
if (code !== 10000) return uni.utils.toast(message)
// 跳转到患者列表页面
uni.navigateBack()
}
// 编辑(更新)患者信息
async function updatePatient() {
// 更新患者信息接口
const { code, message } = await updatePatientApi(formData.value)
// 检测接口是否调用成功
if (code !== 10000) return uni.utils.toast(message)
// 跳转到患者列表页面
uni.navigateBack()
}
// 省略前面小节的代码...
</script>
在用户点击提交按钮时根据是否存在患者 ID 来区别到底是添加患者还是编辑患者。