uniapp陪诊小程序

适老化适配:Uniapp 陪诊小程序功能优化与用户体验设计

随着社会老龄化趋势加剧,陪诊小程序成为连接老年患者与医疗服务的重要桥梁。然而,传统的移动应用设计往往忽略了老年用户的特殊需求。本文旨在探讨如何通过Uniapp框架,从功能设计和用户体验两大维度,打造一个真正"适老"的陪诊小程序。

一、 核心设计原则

在编写代码之前,我们首先要确立适老化设计的核心原则:

  1. 界面清晰、简洁:信息密度低,重点突出,避免不必要的装饰。
  2. 文字放大、可读性强:默认使用更大字号,确保高对比度。
  3. 操作简单、容错性强:点击区域足够大,操作流程线性,提供明确的反馈和撤销机制。
  4. 语音辅助与引导:关键功能配合语音提示,支持语音输入。

二、 全局样式与配置:构建适老界面基石

首先,我们在 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]
      })
    }
  })
}

六、 总结

通过上述代码实现,我们构建了一个真正适合老年人使用的陪诊小程序:

  1. 视觉优化:大字体、高对比度、大点击区域
  2. 交互简化:线性流程、明确反馈、语音辅助
  3. 功能聚焦:核心功能前置,减少认知负担
  4. 容错设计:完善的指引和错误提示

这些设计模式和代码实践不仅适用于陪诊小程序,也可以为其他面向老年用户的移动应用开发提供有价值的参考。适老化设计本质上是一种人文关怀的技术体现,通过细节的打磨,让科技真正服务于所有人群。

相关推荐
Terio_my7 小时前
微信小程序-智慧社区项目开发完整技术文档(上)
微信小程序·小程序
從南走到北9 小时前
JAVA国际版任务悬赏发布接单系统源码支持IOS+Android+H5
android·java·ios·微信·微信小程序·小程序
汤姆yu11 小时前
基于微信小程序的博物馆文创系统
微信小程序·小程序·博物馆
云起SAAS1 天前
ai公司起名取名抖音快手微信小程序看广告流量主开源
微信小程序·小程序·ai编程·看广告变现轻·ai公司起名取名
黑马源码库miui520861 天前
JAVA购物返利商品比价系统源码支持微信小程序
微信·微信小程序·小程序·1024程序员节
学会煎墙1 天前
使用uniapp——实现微信小程序的拖拽排序(vue3+ts)
微信小程序·uni-app·vue·ts
赣州云智科技的技术铺子1 天前
【一步步开发AI运动APP】十三、如何进行运动开始前的站位预检,提升用户体验
微信小程序·小程序·云开发·智能小程序
TiAmo zhang2 天前
微信小程序开发案例 | 简易登录小程序
微信小程序·小程序·1024程序员节
黑马源码库miui520862 天前
场馆预定系统小程序
微信·微信小程序·小程序·1024程序员节