【Hello AI 科技致善】用 SOLO 打造心智障碍群体职场任务分解助手,让每一步工作都清晰可见

【Hello AI 科技致善】用 SOLO 打造心智障碍群体职场任务分解助手,让每一步工作都清晰可见

一、摘要

面向心智障碍群体(孤独症谱系障碍、智力发育迟缓等),在职场场景中他们常因理解复杂工作流程困难而难以独立完成任务。我用 TRAE SOLO 开发了一款「职场任务分解助手」Web 应用,将复杂工作拆解为可视化步骤卡片,配合语音播报、进度追踪和情绪反馈功能,帮助心智障碍群体更独立、自信地完成工作任务。

二、真实场景与需求

目标人群

心智障碍群体,包含孤独症谱系障碍(自闭症)、智力发育迟缓、唐氏综合征等。他们具备一定的劳动能力,但在理解复杂指令、执行多步骤任务方面存在困难。

痛点描述

在实际职场场景中(如仓库整理、清洁工作、简单装配等),心智障碍群体面临以下具体困难:

  1. 理解困难:口头交代的"把仓库整理一下"对他们来说太抽象,不知道从哪一步开始
  2. 记忆负担:多步骤任务容易遗忘中间环节,做到一半不知道下一步该做什么
  3. 情绪波动:遇到不确定的情况容易焦虑,但可能无法清晰表达"我需要帮助"
  4. 反馈缺失:完成一步后缺乏正向激励,容易失去动力

现有做法

目前主要依赖就业辅导员一对一陪同指导,但存在以下问题:

  • 人力成本高:一个辅导员通常只能同时看顾 2-3 人
  • 资源紧缺:专业就业辅导员数量远远不够
  • 难以持续:辅导员不在场时,心智障碍员工就无法独立工作

三、作品介绍

「职场任务分解助手」是一个纯前端 Web 应用,核心功能包括:

功能 说明
:clipboard: 任务模板 预设 6 种常见岗位任务(仓库整理、清洁工作、简单装配、产品包装、物品分类、绿化养护)
:joker: 步骤卡片 每个任务拆解为 6-8 个清晰步骤,大字体+图标展示
:speaker_high_volume: 语音播报 每个步骤支持语音朗读,语速放慢,适合听觉学习者
:white_check_mark: 进度追踪 点击完成打卡,进度条实时更新
:blush: 情绪反馈 开心/一般/需要帮助三个按钮,选择后获得语音鼓励
:sos_button: 紧急求助 一键求助按钮,带脉冲动画提示
:bar_chart: 今日统计 完成任务数、步骤数、获得星星数等正向激励

四、用 SOLO 实现的过程

第一步:需求分析与任务拆解

我向 SOLO 描述了核心需求:

“创建一个面向心智障碍群体的职场任务分解助手 Web 应用。要求:界面极简、大字体、高对比度;将复杂工作拆解为可视化步骤卡片;支持语音播报(Web Speech API);包含情绪反馈和紧急求助功能;预设多种岗位任务模板。”

第二步:SOLO 协助开发

SOLO 帮我完成了以下工作:

  1. 界面设计 — 绿色+橙色温暖配色,大圆角卡片,高对比度文字
  2. 任务模板系统 — 6 种岗位模板,每个包含 6-8 个详细步骤
  3. 语音播报功能 — 基于 Web Speech API,语速 0.85、音调 1.1,友好自然
  4. 进度追踪系统 — 双重进度条(总体+当前任务),完成打卡动画
  5. 情绪反馈模块 — 三个大按钮,选择后自动语音鼓励
  6. 紧急求助 — 红色脉冲动画按钮,全屏求助弹窗
  7. 数据持久化 — localStorage 保存今日进度和统计

第三步:无障碍优化

  • 所有按钮最小 64px 高度,适合触摸操作
  • 使用 Noto Sans SC 中文字体,清晰易读
  • 完成任务时有庆祝动画,提供正向激励
  • 紧急求助按钮始终可见,带醒目脉冲动画

五、成果展示

界面展示

首页:今日进度 + 情绪反馈 + 任务选择 + 紧急求助
任务页:步骤卡片列表 + 语音播报 + 进度条 + 返回按钮


技术实现

  • 纯前端单页应用(HTML + CSS + JavaScript)
  • Tailwind CSS(CDN)
  • Web Speech API(语音播报)
  • localStorage(数据持久化)
  • 零后端依赖,浏览器直接打开即可使用
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  <title>心智障碍群体职场任务分解助手</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            primary: '#2E7D32',
            'primary-light': '#4CAF50',
            'primary-dark': '#1B5E20',
            accent: '#FF8F00',
            'accent-light': '#FFB300',
            'accent-dark': '#E65100',
            warm: '#FFF8E1',
            'warm-dark': '#FFECB3',
            danger: '#D32F2F',
            'danger-light': '#EF5350',
            success: '#2E7D32',
          }
        }
      }
    }
  </script>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&display=swap');

    * {
      -webkit-tap-highlight-color: transparent;
      box-sizing: border-box;
    }

    body {
      font-family: 'Noto Sans SC', sans-serif;
      background: linear-gradient(135deg, #E8F5E9 0%, #FFF8E1 50%, #FFF3E0 100%);
      min-height: 100vh;
      overflow-x: hidden;
    }

    /* 大按钮触摸样式 */
    .btn-touch {
      min-height: 64px;
      min-width: 64px;
      border-radius: 20px;
      transition: all 0.2s ease;
      cursor: pointer;
      user-select: none;
      -webkit-user-select: none;
    }

    .btn-touch:active {
      transform: scale(0.95);
    }

    /* 步骤卡片 */
    .step-card {
      transition: all 0.3s ease;
      cursor: pointer;
      user-select: none;
      -webkit-user-select: none;
    }

    .step-card:active {
      transform: scale(0.97);
    }

    .step-card.completed {
      opacity: 0.7;
    }

    .step-card.completed .step-check {
      background: #4CAF50;
      border-color: #4CAF50;
    }

    /* 进度条动画 */
    .progress-fill {
      transition: width 0.5s ease;
    }

    /* 模板卡片 */
    .template-card {
      transition: all 0.2s ease;
      cursor: pointer;
      user-select: none;
      -webkit-user-select: none;
    }

    .template-card:active {
      transform: scale(0.97);
    }

    /* 情绪按钮 */
    .emotion-btn {
      transition: all 0.2s ease;
      cursor: pointer;
    }

    .emotion-btn:active {
      transform: scale(0.9);
    }

    .emotion-btn.selected {
      ring: 4px;
      transform: scale(1.1);
    }

    /* 紧急按钮脉冲动画 */
    @keyframes pulse-danger {
      0%, 100% { box-shadow: 0 0 0 0 rgba(211, 47, 47, 0.4); }
      50% { box-shadow: 0 0 0 15px rgba(211, 47, 47, 0); }
    }

    .pulse-danger {
      animation: pulse-danger 2s infinite;
    }

    /* 完成庆祝动画 */
    @keyframes celebrate {
      0% { transform: scale(1); }
      25% { transform: scale(1.1) rotate(-3deg); }
      50% { transform: scale(1.2) rotate(3deg); }
      75% { transform: scale(1.1) rotate(-3deg); }
      100% { transform: scale(1); }
    }

    .celebrate {
      animation: celebrate 0.6s ease;
    }

    /* 页面切换动画 */
    .page {
      display: none;
      animation: fadeIn 0.3s ease;
    }

    .page.active {
      display: block;
    }

    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(10px); }
      to { opacity: 1; transform: translateY(0); }
    }

    /* Toast 通知 */
    .toast {
      animation: slideIn 0.3s ease, slideOut 0.3s ease 2.7s;
    }

    @keyframes slideIn {
      from { transform: translateY(-100%); opacity: 0; }
      to { transform: translateY(0); opacity: 1; }
    }

    @keyframes slideOut {
      from { transform: translateY(0); opacity: 1; }
      to { transform: translateY(-100%); opacity: 0; }
    }

    /* 滚动条美化 */
    ::-webkit-scrollbar {
      width: 8px;
    }

    ::-webkit-scrollbar-track {
      background: #E8F5E9;
      border-radius: 4px;
    }

    ::-webkit-scrollbar-thumb {
      background: #81C784;
      border-radius: 4px;
    }

    /* 星星动画 */
    @keyframes twinkle {
      0%, 100% { opacity: 1; transform: scale(1); }
      50% { opacity: 0.5; transform: scale(0.8); }
    }

    .star {
      animation: twinkle 1.5s ease infinite;
    }

    /* 紧急弹窗遮罩 */
    .modal-overlay {
      background: rgba(0, 0, 0, 0.6);
      backdrop-filter: blur(4px);
    }
  </style>
</head>
<body class="pb-8">

  <!-- Toast 通知容器 -->
  <div id="toast-container" class="fixed top-4 left-1/2 -translate-x-1/2 z-50 flex flex-col gap-2"></div>

  <!-- ==================== 首页 ==================== -->
  <div id="page-home" class="page active">
    <!-- 顶部导航 -->
    <header class="bg-primary text-white px-4 py-5 shadow-lg sticky top-0 z-40">
      <div class="max-w-2xl mx-auto flex items-center justify-between">
        <div class="flex items-center gap-3">
          <span class="text-4xl">📋</span>
          <div>
            <h1 class="text-2xl font-bold leading-tight">我的任务助手</h1>
            <p class="text-base opacity-90" id="today-date"></p>
          </div>
        </div>
        <button onclick="showPage('stats')" class="btn-touch bg-white/20 hover:bg-white/30 px-4 py-2 rounded-2xl text-lg font-medium flex items-center gap-2">
          <span>📊</span>
          <span>统计</span>
        </button>
      </div>
    </header>

    <main class="max-w-2xl mx-auto px-4 py-6">
      <!-- 今日进度概览 -->
      <section class="bg-white rounded-3xl shadow-md p-6 mb-6">
        <div class="flex items-center justify-between mb-4">
          <h2 class="text-2xl font-bold text-gray-800">今日进度</h2>
          <span class="text-3xl" id="home-progress-emoji">💪</span>
        </div>
        <div class="w-full bg-gray-200 rounded-full h-8 mb-3 overflow-hidden">
          <div id="home-progress-bar" class="progress-fill bg-gradient-to-r from-primary to-primary-light h-8 rounded-full flex items-center justify-center text-white font-bold text-lg" style="width: 0%">
            <span id="home-progress-text">0%</span>
          </div>
        </div>
        <p class="text-xl text-gray-600" id="home-progress-desc">今天还没有开始任务哦,选一个开始吧!</p>
      </section>

      <!-- 情绪反馈 -->
      <section class="bg-white rounded-3xl shadow-md p-6 mb-6">
        <h2 class="text-2xl font-bold text-gray-800 mb-4">你现在感觉怎么样?</h2>
        <div class="flex justify-around gap-3">
          <button onclick="selectEmotion('happy')" class="emotion-btn flex flex-col items-center gap-2 p-4 rounded-2xl flex-1 bg-green-50 hover:bg-green-100 border-3 border-transparent" id="emotion-happy" data-emotion="happy">
            <span class="text-5xl">😊</span>
            <span class="text-xl font-bold text-green-700">开心</span>
          </button>
          <button onclick="selectEmotion('okay')" class="emotion-btn flex flex-col items-center gap-2 p-4 rounded-2xl flex-1 bg-yellow-50 hover:bg-yellow-100 border-3 border-transparent" id="emotion-okay" data-emotion="okay">
            <span class="text-5xl">😐</span>
            <span class="text-xl font-bold text-yellow-700">一般</span>
          </button>
          <button onclick="selectEmotion('help')" class="emotion-btn flex flex-col items-center gap-2 p-4 rounded-2xl flex-1 bg-orange-50 hover:bg-orange-100 border-3 border-transparent" id="emotion-help" data-emotion="help">
            <span class="text-5xl">😟</span>
            <span class="text-xl font-bold text-orange-700">需要帮助</span>
          </button>
        </div>
        <p id="emotion-response" class="text-center text-xl text-gray-500 mt-4 hidden"></p>
      </section>

      <!-- 任务模板选择 -->
      <section>
        <h2 class="text-2xl font-bold text-gray-800 mb-4 px-1">选择今天的任务</h2>
        <div class="grid grid-cols-1 sm:grid-cols-2 gap-4" id="template-list">
          <!-- 由 JS 动态生成 -->
        </div>
      </section>

      <!-- 紧急求助按钮 -->
      <section class="mt-8">
        <button onclick="showEmergencyModal()" class="btn-touch pulse-danger w-full bg-danger hover:bg-danger-light text-white py-6 rounded-3xl text-2xl font-bold shadow-lg flex items-center justify-center gap-4">
          <span class="text-4xl">🆘</span>
          <span>紧急求助</span>
        </button>
      </section>
    </main>
  </div>

  <!-- ==================== 任务执行页 ==================== -->
  <div id="page-task" class="page">
    <!-- 顶部导航 -->
    <header class="bg-primary text-white px-4 py-5 shadow-lg sticky top-0 z-40">
      <div class="max-w-2xl mx-auto">
        <div class="flex items-center justify-between mb-3">
          <button onclick="confirmGoHome()" class="btn-touch bg-white/20 hover:bg-white/30 px-4 py-2 rounded-2xl text-lg font-medium flex items-center gap-2">
            <span>←</span>
            <span>返回</span>
          </button>
          <h1 class="text-xl font-bold text-center flex-1 px-2" id="task-title">任务名称</h1>
          <button onclick="speakAllSteps()" class="btn-touch bg-white/20 hover:bg-white/30 px-4 py-2 rounded-2xl text-lg font-medium flex items-center gap-2">
            <span>🔊</span>
            <span>播报</span>
          </button>
        </div>
        <!-- 进度条 -->
        <div class="w-full bg-white/30 rounded-full h-6 overflow-hidden">
          <div id="task-progress-bar" class="progress-fill bg-accent h-6 rounded-full flex items-center justify-center text-white font-bold text-base" style="width: 0%">
            <span id="task-progress-text">0 / 0</span>
          </div>
        </div>
      </div>
    </header>

    <main class="max-w-2xl mx-auto px-4 py-6">
      <!-- 当前步骤提示 -->
      <div id="current-step-hint" class="bg-accent/10 border-3 border-accent rounded-3xl p-5 mb-6 text-center">
        <p class="text-xl text-accent-dark font-bold" id="current-step-label">请从第 1 步开始</p>
      </div>

      <!-- 步骤卡片列表 -->
      <div class="flex flex-col gap-4" id="steps-container">
        <!-- 由 JS 动态生成 -->
      </div>

      <!-- 全部完成区域 -->
      <div id="completion-area" class="hidden mt-8 text-center">
        <div class="bg-white rounded-3xl shadow-lg p-8">
          <div class="text-7xl mb-4 celebrate">🎉</div>
          <h2 class="text-3xl font-bold text-primary mb-3">太棒了!任务完成!</h2>
          <p class="text-xl text-gray-600 mb-6">你今天做得非常好!</p>
          <div class="flex gap-4 justify-center flex-wrap">
            <button onclick="showPage('home')" class="btn-touch bg-primary hover:bg-primary-dark text-white px-8 py-4 rounded-2xl text-xl font-bold shadow-md">
              返回首页
            </button>
            <button onclick="speakText('太棒了!你完成了所有任务!你是最优秀的!')" class="btn-touch bg-accent hover:bg-accent-dark text-white px-8 py-4 rounded-2xl text-xl font-bold shadow-md flex items-center gap-2">
              <span>🔊</span>
              <span>听表扬</span>
            </button>
          </div>
        </div>
      </div>

      <!-- 底部紧急求助 -->
      <div class="mt-8">
        <button onclick="showEmergencyModal()" class="btn-touch pulse-danger w-full bg-danger hover:bg-danger-light text-white py-5 rounded-3xl text-xl font-bold shadow-lg flex items-center justify-center gap-3">
          <span class="text-3xl">🆘</span>
          <span>紧急求助</span>
        </button>
      </div>
    </main>
  </div>

  <!-- ==================== 统计页 ==================== -->
  <div id="page-stats" class="page">
    <header class="bg-primary text-white px-4 py-5 shadow-lg sticky top-0 z-40">
      <div class="max-w-2xl mx-auto flex items-center justify-between">
        <button onclick="showPage('home')" class="btn-touch bg-white/20 hover:bg-white/30 px-4 py-2 rounded-2xl text-lg font-medium flex items-center gap-2">
          <span>←</span>
          <span>返回</span>
        </button>
        <h1 class="text-2xl font-bold">今日统计</h1>
        <div class="w-20"></div>
      </div>
    </header>

    <main class="max-w-2xl mx-auto px-4 py-6">
      <!-- 统计卡片 -->
      <div class="grid grid-cols-2 gap-4 mb-6">
        <div class="bg-white rounded-3xl shadow-md p-6 text-center">
          <span class="text-5xl block mb-2">✅</span>
          <p class="text-4xl font-black text-primary" id="stat-completed">0</p>
          <p class="text-xl text-gray-600 mt-1">已完成任务</p>
        </div>
        <div class="bg-white rounded-3xl shadow-md p-6 text-center">
          <span class="text-5xl block mb-2">📝</span>
          <p class="text-4xl font-black text-accent" id="stat-steps">0</p>
          <p class="text-xl text-gray-600 mt-1">完成步骤数</p>
        </div>
        <div class="bg-white rounded-3xl shadow-md p-6 text-center">
          <span class="text-5xl block mb-2">⭐</span>
          <p class="text-4xl font-black text-yellow-500" id="stat-stars">0</p>
          <p class="text-xl text-gray-600 mt-1">获得星星</p>
        </div>
        <div class="bg-white rounded-3xl shadow-md p-6 text-center">
          <span class="text-5xl block mb-2">😊</span>
          <p class="text-4xl font-black text-green-500" id="stat-emotion">-</p>
          <p class="text-xl text-gray-600 mt-1">今日心情</p>
        </div>
      </div>

      <!-- 完成记录 -->
      <div class="bg-white rounded-3xl shadow-md p-6">
        <h2 class="text-2xl font-bold text-gray-800 mb-4">完成记录</h2>
        <div id="stat-records" class="flex flex-col gap-3">
          <p class="text-xl text-gray-400 text-center py-4">今天还没有完成记录</p>
        </div>
      </div>

      <!-- 重置按钮 -->
      <div class="mt-6 text-center">
        <button onclick="resetTodayStats()" class="btn-touch bg-gray-200 hover:bg-gray-300 text-gray-600 px-6 py-3 rounded-2xl text-lg font-medium">
          重置今日数据
        </button>
      </div>
    </main>
  </div>

  <!-- ==================== 紧急求助弹窗 ==================== -->
  <div id="emergency-modal" class="fixed inset-0 z-50 hidden items-center justify-center modal-overlay">
    <div class="bg-white rounded-3xl shadow-2xl p-8 max-w-md mx-4 text-center">
      <div class="text-7xl mb-4">🆘</div>
      <h2 class="text-3xl font-bold text-danger mb-3">需要帮助!</h2>
      <p class="text-xl text-gray-600 mb-6">不要着急,请找身边的工作人员帮忙。</p>
      <div class="bg-red-50 rounded-2xl p-5 mb-6">
        <p class="text-2xl font-bold text-danger mb-2">请大声说:</p>
        <p class="text-3xl font-black text-danger">"请帮帮我!"</p>
      </div>
      <div class="flex flex-col gap-3">
        <button onclick="speakText('请帮帮我!我需要帮助!')" class="btn-touch bg-danger hover:bg-danger-light text-white py-4 rounded-2xl text-xl font-bold shadow-md flex items-center justify-center gap-2">
          <span>🔊</span>
          <span>帮我喊出来</span>
        </button>
        <button onclick="hideEmergencyModal()" class="btn-touch bg-gray-200 hover:bg-gray-300 text-gray-600 py-4 rounded-2xl text-xl font-medium">
          我已经找到帮助了
        </button>
      </div>
    </div>
  </div>

  <!-- ==================== 确认返回弹窗 ==================== -->
  <div id="confirm-modal" class="fixed inset-0 z-50 hidden items-center justify-center modal-overlay">
    <div class="bg-white rounded-3xl shadow-2xl p-8 max-w-md mx-4 text-center">
      <div class="text-6xl mb-4">🤔</div>
      <h2 class="text-2xl font-bold text-gray-800 mb-3">确定要返回吗?</h2>
      <p class="text-xl text-gray-600 mb-6">你的进度已经保存了,下次可以继续。</p>
      <div class="flex gap-3">
        <button onclick="hideConfirmModal()" class="btn-touch flex-1 bg-gray-200 hover:bg-gray-300 text-gray-600 py-4 rounded-2xl text-xl font-medium">
          继续任务
        </button>
        <button onclick="hideConfirmModal(); showPage('home')" class="btn-touch flex-1 bg-primary hover:bg-primary-dark text-white py-4 rounded-2xl text-xl font-bold">
          确定返回
        </button>
      </div>
    </div>
  </div>

  <script>
    // ==================== 任务模板数据 ====================
    const taskTemplates = [
      {
        id: 'warehouse',
        name: '仓库整理',
        icon: '📦',
        color: 'from-blue-500 to-blue-600',
        bgColor: 'bg-blue-50',
        borderColor: 'border-blue-200',
        description: '整理仓库物品,保持整齐',
        steps: [
          { icon: '👀', title: '检查任务单', desc: '看看今天需要整理哪些物品', voice: '第一步,检查任务单,看看今天需要整理哪些物品。' },
          { icon: '🧤', title: '戴上手套', desc: '保护双手,注意安全', voice: '第二步,戴上手套,保护双手,注意安全。' },
          { icon: '🔍', title: '找到物品位置', desc: '根据任务单找到物品在哪里', voice: '第三步,找到物品位置,根据任务单找到物品在哪里。' },
          { icon: '📦', title: '整理物品', desc: '把物品摆放整齐,标签朝外', voice: '第四步,整理物品,把物品摆放整齐,标签朝外。' },
          { icon: '🏷️', title: '检查标签', desc: '确认每个物品的标签都正确', voice: '第五步,检查标签,确认每个物品的标签都正确。' },
          { icon: '🧹', title: '打扫卫生', desc: '把整理区域打扫干净', voice: '第六步,打扫卫生,把整理区域打扫干净。' },
          { icon: '✅', title: '报告完成', desc: '告诉主管任务已经完成', voice: '第七步,报告完成,告诉主管任务已经完成。' },
        ]
      },
      {
        id: 'cleaning',
        name: '清洁工作',
        icon: '🧹',
        color: 'from-green-500 to-green-600',
        bgColor: 'bg-green-50',
        borderColor: 'border-green-200',
        description: '打扫卫生,保持环境整洁',
        steps: [
          { icon: '🧤', title: '准备工具', desc: '拿好扫把、拖把和清洁布', voice: '第一步,准备工具,拿好扫把、拖把和清洁布。' },
          { icon: '🗑️', title: '清理垃圾', desc: '把垃圾倒进垃圾桶', voice: '第二步,清理垃圾,把垃圾倒进垃圾桶。' },
          { icon: '🧹', title: '扫地', desc: '从里到外把地面扫干净', voice: '第三步,扫地,从里到外把地面扫干净。' },
          { icon: '🪣', title: '拖地', desc: '用清水把地面拖干净', voice: '第四步,拖地,用清水把地面拖干净。' },
          { icon: '🪟', title: '擦桌子', desc: '用湿布把桌面擦干净', voice: '第五步,擦桌子,用湿布把桌面擦干净。' },
          { icon: '🪑', title: '摆好椅子', desc: '把椅子摆放整齐', voice: '第六步,摆好椅子,把椅子摆放整齐。' },
          { icon: '✅', title: '检查一遍', desc: '看看还有没有没打扫的地方', voice: '第七步,检查一遍,看看还有没有没打扫的地方。' },
          { icon: '🙌', title: '报告完成', desc: '告诉主管清洁工作完成了', voice: '第八步,报告完成,告诉主管清洁工作完成了。' },
        ]
      },
      {
        id: 'assembly',
        name: '简单装配',
        icon: '🔧',
        color: 'from-orange-500 to-orange-600',
        bgColor: 'bg-orange-50',
        borderColor: 'border-orange-200',
        description: '简单的产品装配工作',
        steps: [
          { icon: '👀', title: '看说明书', desc: '仔细看装配步骤说明', voice: '第一步,看说明书,仔细看装配步骤说明。' },
          { icon: '📦', title: '清点零件', desc: '确认所有零件都齐全', voice: '第二步,清点零件,确认所有零件都齐全。' },
          { icon: '🔧', title: '第一步装配', desc: '按照说明装好第一个部分', voice: '第三步,按照说明装好第一个部分。' },
          { icon: '🔩', title: '第二步装配', desc: '把第二个部分装上去', voice: '第四步,把第二个部分装上去。' },
          { icon: '✨', title: '检查质量', desc: '看看装得对不对、牢不牢', voice: '第五步,检查质量,看看装得对不对、牢不牢。' },
          { icon: '📦', title: '包装好', desc: '把成品包装好', voice: '第六步,把成品包装好。' },
          { icon: '✅', title: '放到指定位置', desc: '把成品放到指定的位置', voice: '第七步,把成品放到指定的位置。' },
        ]
      },
      {
        id: 'packaging',
        name: '产品包装',
        icon: '🎁',
        color: 'from-purple-500 to-purple-600',
        bgColor: 'bg-purple-50',
        borderColor: 'border-purple-200',
        description: '给产品打包和贴标签',
        steps: [
          { icon: '👀', title: '看包装要求', desc: '看看今天要怎么包装', voice: '第一步,看包装要求,看看今天要怎么包装。' },
          { icon: '📦', title: '准备材料', desc: '拿好包装盒、胶带和标签', voice: '第二步,准备材料,拿好包装盒、胶带和标签。' },
          { icon: '🎁', title: '装进盒子', desc: '把产品小心放进包装盒', voice: '第三步,把产品小心放进包装盒。' },
          { icon: '📎', title: '封好盒子', desc: '用胶带把盒子封好', voice: '第四步,用胶带把盒子封好。' },
          { icon: '🏷️', title: '贴标签', desc: '把标签贴在正确位置', voice: '第五步,把标签贴在正确位置。' },
          { icon: '🔍', title: '检查包装', desc: '看看包装好不好', voice: '第六步,检查包装,看看包装好不好。' },
          { icon: '📋', title: '登记记录', desc: '在记录本上记下来', voice: '第七步,在记录本上记下来。' },
        ]
      },
      {
        id: 'sorting',
        name: '物品分类',
        icon: '📊',
        color: 'from-teal-500 to-teal-600',
        bgColor: 'bg-teal-50',
        borderColor: 'border-teal-200',
        description: '把不同物品分门别类',
        steps: [
          { icon: '👀', title: '了解分类标准', desc: '看看物品要怎么分类', voice: '第一步,了解分类标准,看看物品要怎么分类。' },
          { icon: '📦', title: '准备分类区域', desc: '把不同类别的区域标好', voice: '第二步,准备分类区域,把不同类别的区域标好。' },
          { icon: '🔍', title: '拿起一个物品', desc: '看看它属于哪一类', voice: '第三步,拿起一个物品,看看它属于哪一类。' },
          { icon: '🎯', title: '放到正确位置', desc: '把物品放到对应的区域', voice: '第四步,把物品放到对应的区域。' },
          { icon: '🔄', title: '继续分类', desc: '重复以上步骤直到分完', voice: '第五步,继续分类,重复以上步骤直到分完。' },
          { icon: '✅', title: '检查一遍', desc: '确认每个物品都分对了', voice: '第六步,检查一遍,确认每个物品都分对了。' },
        ]
      },
      {
        id: 'garden',
        name: '绿化养护',
        icon: '🌿',
        color: 'from-emerald-500 to-emerald-600',
        bgColor: 'bg-emerald-50',
        borderColor: 'border-emerald-200',
        description: '照顾花草植物',
        steps: [
          { icon: '👀', title: '看看植物状态', desc: '观察植物长得怎么样', voice: '第一步,看看植物状态,观察植物长得怎么样。' },
          { icon: '🚿', title: '浇水', desc: '给需要水的植物浇水', voice: '第二步,给需要水的植物浇水。' },
          { icon: '✂️', title: '修剪枝叶', desc: '把枯黄的叶子剪掉', voice: '第三步,修剪枝叶,把枯黄的叶子剪掉。' },
          { icon: '🌱', title: '除草', desc: '把杂草拔掉', voice: '第四步,把杂草拔掉。' },
          { icon: '🧹', title: '清理现场', desc: '把剪下来的叶子扫干净', voice: '第五步,清理现场,把剪下来的叶子扫干净。' },
          { icon: '✅', title: '记录工作', desc: '在记录本上写好今天做了什么', voice: '第六步,记录工作,在记录本上写好今天做了什么。' },
        ]
      }
    ];

    // ==================== 应用状态 ====================
    let currentTask = null;
    let completedSteps = new Set();
    let todayStats = loadTodayStats();

    // ==================== 初始化 ====================
    function init() {
      setTodayDate();
      renderTemplateList();
      updateHomeProgress();
      updateStatsPage();
    }

    // 设置今日日期
    function setTodayDate() {
      const now = new Date();
      const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' };
      document.getElementById('today-date').textContent = now.toLocaleDateString('zh-CN', options);
    }

    // ==================== 本地存储 ====================
    function getTodayKey() {
      const now = new Date();
      return `task-helper-${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`;
    }

    function loadTodayStats() {
      try {
        const data = localStorage.getItem(getTodayKey());
        if (data) return JSON.parse(data);
      } catch (e) {}
      return {
        completedTasks: [],
        completedStepsCount: 0,
        stars: 0,
        emotion: null,
        taskProgress: {}
      };
    }

    function saveTodayStats() {
      try {
        localStorage.setItem(getTodayKey(), JSON.stringify(todayStats));
      } catch (e) {}
    }

    // ==================== 页面切换 ====================
    function showPage(pageName) {
      document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
      const page = document.getElementById(`page-${pageName}`);
      if (page) {
        page.classList.add('active');
        window.scrollTo(0, 0);
      }

      if (pageName === 'home') {
        updateHomeProgress();
      }
      if (pageName === 'stats') {
        updateStatsPage();
      }
    }

    // ==================== 模板列表渲染 ====================
    function renderTemplateList() {
      const container = document.getElementById('template-list');
      container.innerHTML = taskTemplates.map(t => `
        <div class="template-card bg-white rounded-3xl shadow-md overflow-hidden border-2 ${t.borderColor}" onclick="startTask('${t.id}')">
          <div class="bg-gradient-to-r ${t.color} p-5 text-white">
            <div class="flex items-center gap-3">
              <span class="text-5xl">${t.icon}</span>
              <div>
                <h3 class="text-2xl font-bold">${t.name}</h3>
                <p class="text-base opacity-90">${t.steps.length} 个步骤</p>
              </div>
            </div>
          </div>
          <div class="p-4">
            <p class="text-lg text-gray-600">${t.description}</p>
            <div class="mt-3 flex items-center justify-between">
              <div class="flex items-center gap-1">
                ${getTaskProgressDots(t.id)}
              </div>
              <span class="text-base font-medium text-primary">${getTaskProgressText(t.id)}</span>
            </div>
          </div>
        </div>
      `).join('');
    }

    function getTaskProgressDots(taskId) {
      const template = taskTemplates.find(t => t.id === taskId);
      if (!template) return '';
      const progress = todayStats.taskProgress[taskId] || [];
      return template.steps.map((_, i) => {
        const done = progress.includes(i);
        return `<div class="w-3 h-3 rounded-full ${done ? 'bg-primary' : 'bg-gray-200'}"></div>`;
      }).join('');
    }

    function getTaskProgressText(taskId) {
      const template = taskTemplates.find(t => t.id === taskId);
      if (!template) return '';
      const progress = todayStats.taskProgress[taskId] || [];
      return `${progress.length} / ${template.steps.length}`;
    }

    // ==================== 任务执行 ====================
    function startTask(taskId) {
      const template = taskTemplates.find(t => t.id === taskId);
      if (!template) return;

      currentTask = template;
      completedSteps = new Set(todayStats.taskProgress[taskId] || []);

      document.getElementById('task-title').textContent = template.icon + ' ' + template.name;
      renderSteps();
      updateTaskProgress();
      showPage('task');

      // 语音播报任务开始
      speakText(`开始任务:${template.name}。一共有${template.steps.length}个步骤。加油!`);
    }

    function renderSteps() {
      if (!currentTask) return;

      const container = document.getElementById('steps-container');
      container.innerHTML = currentTask.steps.map((step, index) => {
        const isCompleted = completedSteps.has(index);
        const isNext = !isCompleted && (index === 0 || completedSteps.has(index - 1));
        return `
          <div class="step-card ${isCompleted ? 'completed' : ''} ${isNext ? 'ring-4 ring-accent' : ''} bg-white rounded-3xl shadow-md p-6 border-2 ${isCompleted ? 'border-primary' : isNext ? 'border-accent' : 'border-gray-200'}" onclick="toggleStep(${index})" id="step-${index}">
            <div class="flex items-start gap-4">
              <!-- 完成标记 -->
              <div class="step-check w-14 h-14 min-w-[56px] rounded-2xl ${isCompleted ? 'bg-primary' : 'bg-gray-100'} flex items-center justify-center text-2xl border-3 ${isCompleted ? 'border-primary' : 'border-gray-300'}">
                ${isCompleted ? '✓' : (index + 1)}
              </div>
              <!-- 步骤内容 -->
              <div class="flex-1">
                <div class="flex items-center gap-3 mb-2">
                  <span class="text-4xl">${step.icon}</span>
                  <h3 class="text-2xl font-bold ${isCompleted ? 'text-gray-400 line-through' : 'text-gray-800'}">${step.title}</h3>
                </div>
                <p class="text-xl ${isCompleted ? 'text-gray-400' : 'text-gray-600'} mb-3">${step.desc}</p>
                <button onclick="event.stopPropagation(); speakStep(${index})" class="btn-touch bg-primary/10 hover:bg-primary/20 text-primary px-4 py-2 rounded-xl text-lg font-medium inline-flex items-center gap-2">
                  <span>🔊</span>
                  <span>听语音</span>
                </button>
              </div>
            </div>
          </div>
        `;
      }).join('');

      // 更新当前步骤提示
      updateCurrentStepHint();

      // 显示/隐藏完成区域
      const completionArea = document.getElementById('completion-area');
      if (completedSteps.size === currentTask.steps.length) {
        completionArea.classList.remove('hidden');
        document.getElementById('current-step-hint').classList.add('hidden');
      } else {
        completionArea.classList.add('hidden');
        document.getElementById('current-step-hint').classList.remove('hidden');
      }
    }

    function toggleStep(index) {
      if (!currentTask) return;

      if (completedSteps.has(index)) {
        completedSteps.delete(index);
      } else {
        completedSteps.add(index);

        // 语音播报步骤完成
        const step = currentTask.steps[index];
        speakText(`${step.title},完成了!${completedSteps.size < currentTask.steps.length ? '继续加油!' : '太棒了!所有步骤都完成了!'}`);

        // 添加完成动画
        const card = document.getElementById(`step-${index}`);
        if (card) {
          card.classList.add('celebrate');
          setTimeout(() => card.classList.remove('celebrate'), 600);
        }
      }

      // 保存进度
      todayStats.taskProgress[currentTask.id] = Array.from(completedSteps).sort();
      saveTodayStats();

      // 检查是否全部完成
      if (completedSteps.size === currentTask.steps.length && !todayStats.completedTasks.includes(currentTask.id)) {
        todayStats.completedTasks.push(currentTask.id);
        todayStats.stars += currentTask.steps.length;
        saveTodayStats();
        showToast('🎉 任务完成!获得 ' + currentTask.steps.length + ' 颗星星!', 'success');
      }

      renderSteps();
      updateTaskProgress();
    }

    function updateCurrentStepHint() {
      if (!currentTask) return;
      const label = document.getElementById('current-step-label');

      if (completedSteps.size === currentTask.steps.length) {
        label.textContent = '所有步骤都完成了!你真棒!';
        return;
      }

      // 找到下一个未完成的步骤
      let nextIndex = 0;
      for (let i = 0; i < currentTask.steps.length; i++) {
        if (!completedSteps.has(i)) {
          nextIndex = i;
          break;
        }
      }

      const nextStep = currentTask.steps[nextIndex];
      label.textContent = `下一步:${nextStep.icon} ${nextStep.title}`;
    }

    function updateTaskProgress() {
      if (!currentTask) return;
      const total = currentTask.steps.length;
      const done = completedSteps.size;
      const percent = Math.round((done / total) * 100);

      document.getElementById('task-progress-bar').style.width = percent + '%';
      document.getElementById('task-progress-text').textContent = `${done} / ${total}`;
    }

    // ==================== 语音播报 ====================
    function speakText(text) {
      if (!('speechSynthesis' in window)) {
        showToast('您的浏览器不支持语音播报', 'warning');
        return;
      }

      // 停止之前的播报
      window.speechSynthesis.cancel();

      const utterance = new SpeechSynthesisUtterance(text);
      utterance.lang = 'zh-CN';
      utterance.rate = 0.85;
      utterance.pitch = 1.1;
      utterance.volume = 1;

      // 尝试使用中文语音
      const voices = window.speechSynthesis.getVoices();
      const zhVoice = voices.find(v => v.lang.startsWith('zh'));
      if (zhVoice) {
        utterance.voice = zhVoice;
      }

      window.speechSynthesis.speak(utterance);
    }

    function speakStep(index) {
      if (!currentTask) return;
      const step = currentTask.steps[index];
      speakText(`第${index + 1}步,${step.voice}`);
    }

    function speakAllSteps() {
      if (!currentTask) return;
      let text = `${currentTask.name},一共有${currentTask.steps.length}个步骤。`;
      currentTask.steps.forEach((step, i) => {
        text += `第${i + 1}步,${step.title},${step.desc}。`;
      });
      speakText(text);
    }

    // 确保语音列表加载
    if ('speechSynthesis' in window) {
      window.speechSynthesis.onvoiceschanged = () => {
        window.speechSynthesis.getVoices();
      };
    }

    // ==================== 情绪反馈 ====================
    function selectEmotion(emotion) {
      todayStats.emotion = emotion;
      saveTodayStats();

      // 更新按钮样式
      document.querySelectorAll('.emotion-btn').forEach(btn => {
        btn.classList.remove('ring-4', 'ring-primary', 'scale-110');
      });
      const selectedBtn = document.querySelector(`[data-emotion="${emotion}"]`);
      if (selectedBtn) {
        selectedBtn.classList.add('ring-4', 'ring-primary', 'scale-110');
      }

      // 显示回复
      const responseEl = document.getElementById('emotion-response');
      responseEl.classList.remove('hidden');

      const responses = {
        happy: '😊 很高兴你今天心情不错!继续保持!',
        okay: '🤗 没关系,慢慢来,你可以的!',
        help: '💪 别担心,有困难可以随时找工作人员帮忙!'
      };

      responseEl.textContent = responses[emotion];
      speakText(responses[emotion].replace(/[^\u4e00-\u9fa5a-zA-Z0-9,。!、]/g, ''));
    }

    // ==================== 首页进度更新 ====================
    function updateHomeProgress() {
      const totalTemplates = taskTemplates.length;
      const completedTasks = todayStats.completedTasks.length;
      const percent = Math.round((completedTasks / totalTemplates) * 100);

      document.getElementById('home-progress-bar').style.width = percent + '%';
      document.getElementById('home-progress-text').textContent = percent + '%';

      const descEl = document.getElementById('home-progress-desc');
      const emojiEl = document.getElementById('home-progress-emoji');

      if (completedTasks === 0) {
        descEl.textContent = '今天还没有开始任务哦,选一个开始吧!';
        emojiEl.textContent = '💪';
      } else if (completedTasks < totalTemplates) {
        descEl.textContent = `已完成 ${completedTasks} 个任务,继续加油!`;
        emojiEl.textContent = '🌟';
      } else {
        descEl.textContent = '太棒了!今天的任务全部完成了!';
        emojiEl.textContent = '🏆';
      }

      // 刷新模板列表的进度显示
      renderTemplateList();
    }

    // ==================== 统计页更新 ====================
    function updateStatsPage() {
      document.getElementById('stat-completed').textContent = todayStats.completedTasks.length;
      document.getElementById('stat-steps').textContent = todayStats.completedStepsCount || countTotalSteps();
      document.getElementById('stat-stars').textContent = todayStats.stars;

      const emotionMap = { happy: '😊', okay: '😐', help: '😟' };
      document.getElementById('stat-emotion').textContent = todayStats.emotion ? emotionMap[todayStats.emotion] : '-';

      // 完成记录
      const recordsEl = document.getElementById('stat-records');
      if (todayStats.completedTasks.length === 0) {
        recordsEl.innerHTML = '<p class="text-xl text-gray-400 text-center py-4">今天还没有完成记录</p>';
      } else {
        recordsEl.innerHTML = todayStats.completedTasks.map(taskId => {
          const template = taskTemplates.find(t => t.id === taskId);
          if (!template) return '';
          const progress = todayStats.taskProgress[taskId] || [];
          return `
            <div class="flex items-center gap-3 bg-green-50 rounded-2xl p-4">
              <span class="text-3xl">${template.icon}</span>
              <div class="flex-1">
                <p class="text-lg font-bold text-gray-800">${template.name}</p>
                <p class="text-base text-gray-500">${progress.length} / ${template.steps.length} 步骤</p>
              </div>
              <span class="text-3xl">✅</span>
            </div>
          `;
        }).join('');
      }
    }

    function countTotalSteps() {
      let total = 0;
      todayStats.completedTasks.forEach(taskId => {
        const progress = todayStats.taskProgress[taskId] || [];
        total += progress.length;
      });
      return total;
    }

    // ==================== 弹窗 ====================
    function showEmergencyModal() {
      const modal = document.getElementById('emergency-modal');
      modal.classList.remove('hidden');
      modal.classList.add('flex');
      speakText('需要帮助!请找身边的工作人员帮忙!');
    }

    function hideEmergencyModal() {
      const modal = document.getElementById('emergency-modal');
      modal.classList.add('hidden');
      modal.classList.remove('flex');
      window.speechSynthesis.cancel();
    }

    function confirmGoHome() {
      const modal = document.getElementById('confirm-modal');
      modal.classList.remove('hidden');
      modal.classList.add('flex');
    }

    function hideConfirmModal() {
      const modal = document.getElementById('confirm-modal');
      modal.classList.add('hidden');
      modal.classList.remove('flex');
    }

    // ==================== Toast 通知 ====================
    function showToast(message, type = 'info') {
      const container = document.getElementById('toast-container');
      const toast = document.createElement('div');

      const bgColors = {
        success: 'bg-primary',
        warning: 'bg-accent',
        error: 'bg-danger',
        info: 'bg-gray-700'
      };

      toast.className = `toast ${bgColors[type] || bgColors.info} text-white px-6 py-4 rounded-2xl shadow-lg text-xl font-medium text-center max-w-sm`;
      toast.textContent = message;
      container.appendChild(toast);

      setTimeout(() => {
        toast.remove();
      }, 3000);
    }

    // ==================== 重置统计 ====================
    function resetTodayStats() {
      if (confirm('确定要重置今天的数据吗?')) {
        todayStats = {
          completedTasks: [],
          completedStepsCount: 0,
          stars: 0,
          emotion: null,
          taskProgress: {}
        };
        saveTodayStats();
        updateStatsPage();
        showToast('数据已重置', 'info');
      }
    }

    // ==================== 启动应用 ====================
    document.addEventListener('DOMContentLoaded', init);
  </script>
</body>
</html>

预设岗位模板

岗位 步骤数 示例步骤
仓库整理 7 步 检查任务单 → 戴上手套 → 找到物品位置 → 整理物品 → 检查标签 → 打扫卫生 → 报告完成
清洁工作 8 步 准备工具 → 清理桌面 → 擦拭窗户 → 清洁地面 → 整理垃圾 → 检查结果 → 收好工具 → 报告完成
简单装配 7 步 检查材料 → 阅读说明 → 第一步装配 → 检查质量 → 第二步装配 → 最终检查 → 报告完成
产品包装 7 步 检查产品 → 准备包装 → 放入产品 → 添加填充 → 封好包装 → 贴上标签 → 报告完成
物品分类 6 步 查看分类标准 → 拿起物品 → 判断类别 → 放入对应区域 → 检查结果 → 报告完成
绿化养护 6 步 准备工具 → 检查植物 → 浇水施肥 → 修剪枝叶 → 清理现场 → 报告完成

六、验证方式与下一步

模拟验证

  • :white_check_mark: 所有 6 种任务模板均可正常加载和操作
  • :white_check_mark: 语音播报功能在 Chrome/Edge 中正常工作
  • :white_check_mark: 步骤打卡、进度追踪功能正常
  • :white_check_mark: 情绪反馈和紧急求助功能正常
  • :white_check_mark: 数据持久化正常(刷新页面后进度保留)
  • :white_check_mark: 响应式设计,手机和电脑均可使用

下一步计划

  1. 真实用户测试:联系特殊教育机构或残疾人就业服务中心,邀请目标用户试用并收集反馈
  2. 自定义任务:支持辅导员/家长自定义任务模板,适配更多岗位
  3. 图片辅助:为每个步骤添加配图,进一步降低理解门槛
  4. 多语言支持:增加英文版本,扩大适用范围
  5. PWA 离线支持:添加 Service Worker,支持无网络环境使用

:light_bulb: 技术向善,让 AI 成为每个人的助手。项目完全开源,欢迎特殊教育机构和公益组织联系合作。