适老化适配:Uniapp 陪诊小程序功能优化与用户体验设计
随着社会老龄化趋势加剧,陪诊小程序成为连接老年患者与医疗服务的重要桥梁。然而,传统的移动应用设计往往忽略了老年用户的特殊需求。本文旨在探讨如何通过Uniapp框架,从功能设计和用户体验两大维度,打造一个真正"适老"的陪诊小程序。
一、 核心设计原则
在编写代码之前,我们首先要确立适老化设计的核心原则:
- 界面清晰、简洁:信息密度低,重点突出,避免不必要的装饰。
- 文字放大、可读性强:默认使用更大字号,确保高对比度。
- 操作简单、容错性强:点击区域足够大,操作流程线性,提供明确的反馈和撤销机制。
- 语音辅助与引导:关键功能配合语音提示,支持语音输入。
二、 全局样式与配置:构建适老界面基石
首先,我们在 App.vue 或全局样式中建立统一的视觉规范。
scss
css
/* 全局适老样式表 (styles/elderly.scss) */
/* 1. 定义放大比例,便于整体调整 */
:root {
--elderly-scale: 1.2; /* 整体放大系数 */
--font-size-base: 18px; /* 基准字体,远大于默认的14px */
--color-primary: #1890ff; /* 主色,清晰醒目 */
--color-background: #f5f5f5; /* 背景色,减少视觉疲劳 */
--color-text: #333; /* 主要文字色,高对比度 */
--color-text-secondary: #666;
--border-radius: 12px; /* 较大的圆角,感觉更柔和 */
}
/* 2. 全局字体与背景 */
page {
font-size: var(--font-size-base);
line-height: 1.6; /* 更大的行高,提升可读性 */
color: var(--color-text);
background-color: var(--color-background);
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, sans-serif; /* 系统默认字体,识别度高 */
}
/* 3. 超大点击区域类 */
.elderly-tap-area {
padding: 20rpx 30rpx; /* 使用rpx适配不同屏幕 */
min-height: 100rpx; /* 确保高度足够 */
display: flex;
align-items: center;
}
/* 4. 主要按钮样式 */
.elderly-btn-primary {
background-color: var(--color-primary);
color: #fff;
border: none;
border-radius: var(--border-radius);
font-size: 20px; /* 按钮字体更大 */
font-weight: 500;
@extend .elderly-tap-area; /* 继承大点击区域 */
justify-content: center;
margin: 20rpx 0;
&:active {
opacity: 0.8;
transform: scale(0.99); /* 提供按压反馈 */
}
}
/* 5. 输入框样式 */
.elderly-input {
font-size: 18px;
background: #fff;
border: 2rpx solid #e0e0e0;
border-radius: var(--border-radius);
padding: 25rpx 30rpx;
margin: 20rpx 0;
&:focus {
border-color: var(--color-primary);
}
}
/* 6. 卡片样式 */
.elderly-card {
background: #fff;
border-radius: var(--border-radius);
padding: 30rpx;
margin: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
在 main.js 中引入全局样式。
javascript
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './styles/elderly.scss' // 引入全局适老样式
const app = createApp(App)
app.mount('#app')
三、 首页功能优化:一键核心,信息清晰
首页是用户的第一印象,必须做到核心功能一目了然。
vue
xml
<!-- pages/index/index.vue -->
<template>
<view class="elderly-home">
<!-- 顶部欢迎栏,显示用户信息 -->
<view class="welcome-bar elderly-card">
<text class="welcome-text">您好,{{ userName }}!</text>
<text class="sub-text">今天感觉怎么样?</text>
</view>
<!-- 核心功能网格,大图标大文字 -->
<view class="func-grid">
<view
class="func-item"
v-for="item in mainFunctions"
:key="item.id"
@tap="navigateTo(item.path)"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
>
<image class="func-icon" :src="item.icon" mode="aspectFit"></image>
<text class="func-text">{{ item.name }}</text>
</view>
</view>
<!-- 语音助手悬浮按钮 -->
<view
class="voice-assistant"
@tap="toggleVoiceAssistant"
@longpress="startVoiceInput"
>
<image src="/static/icon/voice.png" mode="aspectFit"></image>
</view>
<!-- 语音助手面板 -->
<view class="voice-panel" v-if="showVoicePanel">
<text class="voice-tip">{{ voiceTip }}</text>
<view class="voice-animation"></view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
// 响应式数据
const userName = ref('张爷爷')
const showVoicePanel = ref(false)
const voiceTip = ref('请说出您的需求...')
const mainFunctions = ref([
{ id: 1, name: '一键陪诊', icon: '/static/icon/companion.png', path: '/pages/companion/quick' },
{ id: 2, name: '我的订单', icon: '/static/icon/order.png', path: '/pages/order/list' },
{ id: 3, name: '用药提醒', icon: '/static/icon/medication.png', path: '/pages/medication/reminder' },
{ id: 4, name: '医院导航', icon: '/static/icon/navigation.png', path: '/pages/hospital/map' },
{ id: 5, name: '健康档案', icon: '/static/icon/health.png', path: '/pages/health/record' },
{ id: 6, name: '紧急联系', icon: '/static/icon/emergency.png', path: '/pages/emergency/contact' }
])
// 方法定义
const navigateTo = (path) => {
uni.navigateTo({
url: path,
fail: (err) => {
console.error('导航失败:', err)
uni.showToast({ title: '功能即将上线', icon: 'none' })
}
})
}
const toggleVoiceAssistant = () => {
showVoicePanel.value = !showVoicePanel.value
if (showVoicePanel.value) {
// 播放引导语音
playVoiceGuide()
}
}
const startVoiceInput = () => {
// 开始语音识别
uni.startSpeechRecognize({
lang: 'zh_CN',
success: (res) => {
handleVoiceCommand(res.result)
},
fail: (err) => {
uni.showToast({ title: '请再说一遍', icon: 'none' })
}
})
}
const playVoiceGuide = () => {
// 使用uni.createInnerAudioContext()播放引导语音
const audio = uni.createInnerAudioContext()
audio.src = '/static/voice/home-guide.mp3'
audio.play()
}
const handleVoiceCommand = (text) => {
// 处理语音识别结果
if (text.includes('陪诊')) {
navigateTo('/pages/companion/quick')
} else if (text.includes('订单')) {
navigateTo('/pages/order/list')
}
// ... 其他命令处理
showVoicePanel.value = false
}
// 触觉反馈
const onTouchStart = (e) => {
// 可以在这里添加轻微震动反馈 (如果需要)
// uni.vibrateShort()
}
const onTouchEnd = (e) => {
// 结束反馈
}
</script>
<style scoped lang="scss">
.elderly-home {
padding: 20rpx;
min-height: 100vh;
}
.welcome-bar {
margin-bottom: 40rpx;
.welcome-text {
font-size: 24px;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.sub-text {
font-size: 18px;
color: var(--color-text-secondary);
}
}
.func-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 25rpx;
}
.func-item {
@extend .elderly-card;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 20rpx;
.func-icon {
width: 100rpx;
height: 100rpx;
margin-bottom: 20rpx;
}
.func-text {
font-size: 16px;
text-align: center;
font-weight: 500;
}
}
.voice-assistant {
position: fixed;
right: 40rpx;
bottom: 120rpx;
width: 120rpx;
height: 120rpx;
background: var(--color-primary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 30rpx rgba(24, 144, 255, 0.4);
image {
width: 50rpx;
height: 50rpx;
}
}
.voice-panel {
position: fixed;
bottom: 260rpx;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
padding: 40rpx;
border-radius: var(--border-radius);
.voice-tip {
color: #fff;
font-size: 18px;
text-align: center;
display: block;
margin-bottom: 30rpx;
}
.voice-animation {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: var(--color-primary);
margin: 0 auto;
animation: pulse 1.5s infinite;
}
}
@keyframes pulse {
0% { transform: scale(0.8); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
100% { transform: scale(0.8); opacity: 1; }
}
</style>
四、 一键陪诊流程优化:极简操作,全程引导
陪诊是核心功能,流程必须极其简化。
vue
xml
<!-- pages/companion/quick.vue -->
<template>
<view class="quick-companion">
<view class="elderly-card">
<text class="title">一键呼叫陪诊师</text>
<!-- 医院选择 - 大按钮选择器 -->
<view class="section">
<text class="section-title">选择医院</text>
<scroll-view class="hospital-scroll" scroll-x>
<view
v-for="hospital in nearbyHospitals"
:key="hospital.id"
class="hospital-item"
:class="{ active: selectedHospital === hospital.id }"
@tap="selectHospital(hospital.id)"
>
<text>{{ hospital.name }}</text>
</view>
</scroll-view>
</view>
<!-- 时间选择 - 大日期卡片 -->
<view class="section">
<text class="section-title">预约时间</text>
<view class="date-grid">
<view
v-for="date in availableDates"
:key="date.value"
class="date-card"
:class="{ active: selectedDate === date.value }"
@tap="selectDate(date.value)"
>
<text class="date-week">{{ date.week }}</text>
<text class="date-day">{{ date.day }}</text>
</view>
</view>
</view>
<!-- 服务类型 -->
<view class="section">
<text class="section-title">服务类型</text>
<view class="service-type">
<view
v-for="service in serviceTypes"
:key="service.id"
class="service-card"
:class="{ active: selectedService === service.id }"
@tap="selectService(service.id)"
>
<image :src="service.icon" class="service-icon"></image>
<text class="service-name">{{ service.name }}</text>
<text class="service-desc">{{ service.desc }}</text>
</view>
</view>
</view>
<!-- 一键呼叫按钮 -->
<view
class="call-btn elderly-btn-primary"
@tap="callCompanion"
:class="{ disabled: !isFormValid }"
>
<text class="btn-text">一键呼叫陪诊师</text>
<text class="btn-desc">立即为您匹配专业陪诊人员</text>
</view>
</view>
<!-- 操作指引浮层 -->
<view class="guide-overlay" v-if="showGuide">
<view class="guide-content">
<text class="guide-title">操作指引</text>
<text class="guide-step">1. 选择您要去的医院</text>
<text class="guide-step">2. 选择方便的日期</text>
<text class="guide-step">3. 选择需要的服务类型</text>
<text class="guide-step">4. 点击下方大按钮呼叫</text>
<view class="elderly-btn-primary" @tap="showGuide = false">
<text>我知道了</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
// 响应式数据
const selectedHospital = ref(null)
const selectedDate = ref(null)
const selectedService = ref(null)
const showGuide = ref(false)
const nearbyHospitals = ref([
{ id: 1, name: '市第一人民医院' },
{ id: 2, name: '市中心医院' },
{ id: 3, name: '省人民医院' },
{ id: 4, name: '中医医院' }
])
const availableDates = ref([
{ value: 'today', week: '今天', day: '12月5日' },
{ value: 'tomorrow', week: '明天', day: '12月6日' },
{ value: 'dayAfter', week: '后天', day: '12月7日' }
])
const serviceTypes = ref([
{
id: 1,
name: '全程陪诊',
desc: '挂号取药全包办',
icon: '/static/icon/full-service.png'
},
{
id: 2,
name: '半程陪诊',
desc: '协助就诊指导',
icon: '/static/icon/half-service.png'
},
{
id: 3,
name: '代办取药',
desc: '代取药品送到家',
icon: '/static/icon/medicine-pickup.png'
}
])
// 计算属性:表单是否有效
const isFormValid = computed(() => {
return selectedHospital.value && selectedDate.value && selectedService.value
})
// 方法定义
const selectHospital = (id) => {
selectedHospital.value = id
// 提供触觉反馈
uni.vibrateShort()
}
const selectDate = (date) => {
selectedDate.value = date
uni.vibrateShort()
}
const selectService = (serviceId) => {
selectedService.value = serviceId
uni.vibrateShort()
}
const callCompanion = () => {
if (!isFormValid.value) {
uni.showToast({
title: '请先完成所有选择',
icon: 'none',
duration: 2000
})
return
}
// 显示呼叫中状态
uni.showLoading({
title: '正在匹配陪诊师...',
mask: true
})
// 模拟API调用
setTimeout(() => {
uni.hideLoading()
uni.showModal({
title: '呼叫成功',
content: '已为您匹配专业陪诊师,稍后将与您电话联系确认详情。',
confirmText: '好的',
showCancel: false,
success: () => {
uni.navigateBack()
}
})
}, 2000)
}
onMounted(() => {
// 首次进入显示指引
const hasSeenGuide = uni.getStorageSync('hasSeenCompanionGuide')
if (!hasSeenGuide) {
showGuide.value = true
uni.setStorageSync('hasSeenCompanionGuide', true)
}
})
</script>
<style scoped lang="scss">
.quick-companion {
padding: 20rpx;
}
.title {
font-size: 22px;
font-weight: bold;
display: block;
text-align: center;
margin-bottom: 40rpx;
}
.section {
margin-bottom: 50rpx;
.section-title {
font-size: 18px;
font-weight: 600;
display: block;
margin-bottom: 25rpx;
color: var(--color-text);
}
}
.hospital-scroll {
white-space: nowrap;
.hospital-item {
display: inline-block;
padding: 25rpx 40rpx;
margin-right: 20rpx;
background: #f8f8f8;
border-radius: var(--border-radius);
border: 2rpx solid transparent;
&.active {
background: var(--color-primary);
color: #fff;
border-color: var(--color-primary);
}
text {
font-size: 16px;
}
}
}
.date-grid {
display: flex;
gap: 20rpx;
.date-card {
flex: 1;
padding: 30rpx 20rpx;
background: #f8f8f8;
border-radius: var(--border-radius);
border: 2rpx solid transparent;
text-align: center;
&.active {
background: var(--color-primary);
color: #fff;
border-color: var(--color-primary);
}
.date-week {
font-size: 16px;
display: block;
margin-bottom: 10rpx;
}
.date-day {
font-size: 14px;
display: block;
}
}
}
.service-type {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
.service-card {
padding: 30rpx 20rpx;
background: #f8f8f8;
border-radius: var(--border-radius);
border: 2rpx solid transparent;
text-align: center;
&.active {
background: var(--color-primary);
color: #fff;
border-color: var(--color-primary);
}
.service-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 15rpx;
}
.service-name {
font-size: 16px;
font-weight: 500;
display: block;
margin-bottom: 8rpx;
}
.service-desc {
font-size: 12px;
display: block;
opacity: 0.8;
}
}
}
.call-btn {
margin-top: 60rpx;
&.disabled {
opacity: 0.6;
}
.btn-text {
font-size: 20px;
font-weight: bold;
display: block;
margin-bottom: 8rpx;
}
.btn-desc {
font-size: 14px;
display: block;
opacity: 0.9;
}
}
.guide-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
.guide-content {
background: #fff;
margin: 40rpx;
padding: 50rpx 40rpx;
border-radius: var(--border-radius);
text-align: center;
.guide-title {
font-size: 22px;
font-weight: bold;
display: block;
margin-bottom: 40rpx;
}
.guide-step {
font-size: 18px;
display: block;
margin-bottom: 25rpx;
text-align: left;
padding-left: 40rpx;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 20rpx;
height: 20rpx;
background: var(--color-primary);
border-radius: 50%;
}
}
}
}
</style>
五、 全局辅助功能增强
在 main.js 或单独的工具文件中添加辅助功能。
javascript
php
// utils/elderlyUtils.js
/**
* 语音朗读文本 (TTS)
*/
export const speakText = (text) => {
// 在App端可以使用plus.speech
// 在微信小程序端可以使用wx.createInnerAudioContext播放预录语音或调用云函数实现TTS
if (uni.getSystemInfoSync().platform === 'app') {
const speech = uni.requireNativePlugin('DC-Speech')
speech.speak({
text: text,
rate: 0.5, // 较慢的语速
pitch: 1.0
})
} else {
uni.showToast({
title: text,
icon: 'none',
duration: 3000
})
}
}
/**
* 简化分享功能
*/
export const simpleShare = (content) => {
uni.share({
provider: 'weixin',
scene: 'WXSceneSession',
type: 0,
title: '陪诊服务',
summary: content,
success: function (res) {
uni.showToast({ title: '分享成功' })
},
fail: function (err) {
uni.showToast({ title: '分享失败', icon: 'none' })
}
})
}
/**
* 紧急联系人呼叫
*/
export const callEmergencyContact = () => {
uni.showActionSheet({
itemList: ['呼叫儿子', '呼叫女儿', '呼叫社区'],
success: function (res) {
const contacts = ['13800138000', '13900139000', '13600136000']
uni.makePhoneCall({
phoneNumber: contacts[res.tapIndex]
})
}
})
}
六、 总结
通过上述代码实现,我们构建了一个真正适合老年人使用的陪诊小程序:
- 视觉优化:大字体、高对比度、大点击区域
- 交互简化:线性流程、明确反馈、语音辅助
- 功能聚焦:核心功能前置,减少认知负担
- 容错设计:完善的指引和错误提示
这些设计模式和代码实践不仅适用于陪诊小程序,也可以为其他面向老年用户的移动应用开发提供有价值的参考。适老化设计本质上是一种人文关怀的技术体现,通过细节的打磨,让科技真正服务于所有人群。