uni-app(优医咨询)项目实战 - 第 4 天
学习目标:
- 掌握登录权限验证的实现方法
- 能够动态设置导航栏标题
- 能够动态设置 tabBar 角标文字
- 知道验证身份证号的正则表达式
- 掌握 uni-swipe-action 侧滑组件的使用方法
一、权限验证
此处的权限验证是指服务端接口验证码 token 是否存在或有效,这就需要我们在调用接口时将 token 以自定义头信息的方式发送给服务端接口,如果 token 不存在或者 token 过期了,则接口会返回状态码的值为 401。
关于权限验证的逻辑我们做如下的处理:
- 配置请求拦截器,读取 Pinia 中记录的 token 数据
- 检测接口返回的状态码是否为 401,如果是则跳转到登录页面
- 在登录成功后跳转回原来的页面
我们按上述的步骤分别来实现:
1.1 配置拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
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 = {} 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
1 2 3 4 5 6 7
| <!-- pages/my/index.vue --> <script setup> // 测试的代码,将来会被删除 import { http } from '@/utils/http.js' // 调用接口 http.get('/patient/myUser') </script>
|
测试两种情况:一是登录成功后,另一种是未登录时,观察是否存在请求头 Authorization
1.2 检测状态码
调用接口后服务端检测到没有传递 token
或者 token
失效时,状态码会返回 401
(后端人员与前端人员约定好的,也可以是其它的数值),在响应拦截器读取状态码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
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() 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 状态管理实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { ref } from 'vue' import { defineStore } from 'pinia'
export const useUserStore = defineStore( 'user', () => { const token = ref('') const redirectURL = ref('/pages/index/index') const openType = ref('switchTab') return { token, redirectURL,openType } }, { persist: { paths: ['token', 'redirectURL', 'openType'], }, } )
|
小程序提供了多种路由路转的 API,如 uni.switchTab
、uni.redirectTo
、uni.navigateTo
等,大家应该还记得 tabBar 的页面跳转时只能使用 uni.switchTab
,因此登录成功后进行跳转时需要判断页面地址是否为 tabBar 页面,如果是则用 uni.switchTab
跳转,否则用 uni.redirectTo
跳转。
小程序规定 tabBar 页面最多只能有 5 个,因此我们可以事先将 tabBar 中定义好的页面路径定义在一个数组件,然后根据数组方法 includes
来判断是否为 tabBar 的页面路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
import Request from 'luch-request' import { useUserStore } from '@/stores/user.js'
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 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
,然后跳转加这个页面(路由)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <!-- 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
组件来使用的,还要注意的是 这个页面使用的是自定义导航栏。
1 2 3 4 5 6 7 8 9 10 11 12
| { "pages": [ { "path": "pages/my/index", "style": { "navigationBarTitleText": " 我的 ", "enablePullDownRefresh": false, "navigationStyle": "custom" } } ] }
|
2.1.1 Scroll-page
在手机屏幕中常常要处理不同类型的屏幕,比如异形屏(浏海屏)需要处理好安全区域内容的展示,为此我们来专封装一个组件,在该组件统一进行处理,要求该组件满足:
- 页面可以滚动
- 适配安全区域
- 自定义底部 tabBar 边框线
- 支持下拉刷新和上拉加载
首先按照 easycom
规范新建组件 scroll-page
- 使用内置组件
scroll-view
保证页面可以滚动,并且 scroll-view
的高度为视口的高度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!-- /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>
|
- 适配安全区域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <!-- /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 边框线的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <!-- /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
实现下拉刷新交互
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <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
组件规范,该组件要求满足:
- 自定义标题
- 自定义样式
- 右侧是否显示箭头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <!-- /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 布局模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
| <!-- 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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
| .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 个人信息
在用户处于登录状态时调用接口获取户的头像、昵称等个人信息,我们分成两个步骤来实现:
- 封装调用接口的方法,接口文档 地址在这里
1 2 3 4 5 6 7 8 9 10 11 12
|
import { http } from '@/utils/http'
export const userInfoApi = () => { return http.get('/patient/myUser') }
|
- 调用方法获取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <!-- 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>
|
- 渲染个人信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <!-- 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 清空即可,然后再跳转到登录页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <!-- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| { "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 布局模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <!-- 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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| .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-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 个步骤来对数据进行验证:
- 获取表单数据,根据接口需要来定义数据名称并获取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| <!-- 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]$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <!-- 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
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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) { if (data.idCard.slice(16, 17) % 2 !== value) { callback(' 选择的性别与身份号中性别不一致 ') } return true }, }, ], }, }
|
- 调用验证方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <!-- 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 提交数据
- 根据接口文档封装接口调用的方法,文档的地址 在这里。
1 2 3 4 5 6 7 8 9 10 11
|
import { http } from '@/utils/http'
export const addPatientApi = (data) => { return http.post('/patient/add', data) }
|
- 调用接口提交数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <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 患者列表
在【我的】页面中找到【家庭档案】,给它添加链接地址跳转到患者列表页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <!-- 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 布局模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| <!-- 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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| .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);
.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 获取数据
- 根据接口文档的要求封装接口调用的方法,接口文档 请看这里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
import { http } from '@/utils/http'
export const addPatientApi = (data) => { return http.post('/patient/add', data) }
export const patientListApi = (data) => { return http.get('/patient/mylist') }
|
- 在页面调用接口获取数据并渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| <!-- 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
组件,该组件能够监听到用的点击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <!-- 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 删除数据
- 根据接口文档封装调用接口的方法,接口文档 地址在这里。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
import { http } from '@/utils/http'
export const removePatientApi = (id) => { return http.delete(`/patient/del/${id}`) }
|
- 在点击事件回调中调用接口删除患者数据,在删除数据的时候要注意,调用接口是要删除服务器的患者数据,但是本地在 Vue 中也保存了一份患者数据,Vue 中保存的数据也可同步删除,根据索引值实来删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <!-- 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 查询患者信息
- 根据接口文档获封装接口调用的方法来获取患者信息,接口文档 地址在这里。
1 2 3 4 5 6 7 8 9 10 11
| import { http } from '@/utils/http'
export const patientDetailApi = (id) => { return http.get(`/patient/info/${id}`) }
|
3.5.2 获取地址上参数,根据 ID 参数查询患者信息
在 uni-app 中获取页面地址参数有两种方法,一种是在 onLoad
生命周期中,另一种是使用 defineProps
,接下来分别演示两种用法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!-- 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
来获取地址参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <!-- 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 更新患者信息
在原有患者信息基础之上进行修改,修改完毕合再次调用接口实现数据的更新,接口文档 的在址在这里。
- 封装接口调用的方法
1 2 3 4 5 6 7 8 9 10 11
| import { http } from '@/utils/http'
export const updatePatientApi = (data) => { return http.put(`/patient/update`, data) }
|
- 调用更新患者信息的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <!-- 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 来区别到底是添加患者还是编辑患者。