【Hello AI 科技致善】用 SOLO 打造心智障碍群体职场任务分解助手,让每一步工作都清晰可见
一、摘要
面向心智障碍群体(孤独症谱系障碍、智力发育迟缓等),在职场场景中他们常因理解复杂工作流程困难而难以独立完成任务。我用 TRAE SOLO 开发了一款「职场任务分解助手」Web 应用,将复杂工作拆解为可视化步骤卡片,配合语音播报、进度追踪和情绪反馈功能,帮助心智障碍群体更独立、自信地完成工作任务。
二、真实场景与需求
目标人群
心智障碍群体,包含孤独症谱系障碍(自闭症)、智力发育迟缓、唐氏综合征等。他们具备一定的劳动能力,但在理解复杂指令、执行多步骤任务方面存在困难。
痛点描述
在实际职场场景中(如仓库整理、清洁工作、简单装配等),心智障碍群体面临以下具体困难:
- 理解困难:口头交代的"把仓库整理一下"对他们来说太抽象,不知道从哪一步开始
- 记忆负担:多步骤任务容易遗忘中间环节,做到一半不知道下一步该做什么
- 情绪波动:遇到不确定的情况容易焦虑,但可能无法清晰表达"我需要帮助"
- 反馈缺失:完成一步后缺乏正向激励,容易失去动力
现有做法
目前主要依赖就业辅导员一对一陪同指导,但存在以下问题:
- 人力成本高:一个辅导员通常只能同时看顾 2-3 人
- 资源紧缺:专业就业辅导员数量远远不够
- 难以持续:辅导员不在场时,心智障碍员工就无法独立工作
三、作品介绍
「职场任务分解助手」是一个纯前端 Web 应用,核心功能包括:
| 功能 | 说明 |
|---|---|
| 预设 6 种常见岗位任务(仓库整理、清洁工作、简单装配、产品包装、物品分类、绿化养护) | |
| 每个任务拆解为 6-8 个清晰步骤,大字体+图标展示 | |
| 每个步骤支持语音朗读,语速放慢,适合听觉学习者 | |
| 点击完成打卡,进度条实时更新 | |
| 开心/一般/需要帮助三个按钮,选择后获得语音鼓励 | |
| 一键求助按钮,带脉冲动画提示 | |
| 完成任务数、步骤数、获得星星数等正向激励 |
四、用 SOLO 实现的过程
第一步:需求分析与任务拆解
我向 SOLO 描述了核心需求:
“创建一个面向心智障碍群体的职场任务分解助手 Web 应用。要求:界面极简、大字体、高对比度;将复杂工作拆解为可视化步骤卡片;支持语音播报(Web Speech API);包含情绪反馈和紧急求助功能;预设多种岗位任务模板。”
第二步:SOLO 协助开发
SOLO 帮我完成了以下工作:
- 界面设计 — 绿色+橙色温暖配色,大圆角卡片,高对比度文字
- 任务模板系统 — 6 种岗位模板,每个包含 6-8 个详细步骤
- 语音播报功能 — 基于 Web Speech API,语速 0.85、音调 1.1,友好自然
- 进度追踪系统 — 双重进度条(总体+当前任务),完成打卡动画
- 情绪反馈模块 — 三个大按钮,选择后自动语音鼓励
- 紧急求助 — 红色脉冲动画按钮,全屏求助弹窗
- 数据持久化 — 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 步 | 准备工具 → 检查植物 → 浇水施肥 → 修剪枝叶 → 清理现场 → 报告完成 |
六、验证方式与下一步
模拟验证
所有 6 种任务模板均可正常加载和操作
语音播报功能在 Chrome/Edge 中正常工作
步骤打卡、进度追踪功能正常
情绪反馈和紧急求助功能正常
数据持久化正常(刷新页面后进度保留)
响应式设计,手机和电脑均可使用
下一步计划
- 真实用户测试:联系特殊教育机构或残疾人就业服务中心,邀请目标用户试用并收集反馈
- 自定义任务:支持辅导员/家长自定义任务模板,适配更多岗位
- 图片辅助:为每个步骤添加配图,进一步降低理解门槛
- 多语言支持:增加英文版本,扩大适用范围
- PWA 离线支持:添加 Service Worker,支持无网络环境使用
技术向善,让 AI 成为每个人的助手。项目完全开源,欢迎特殊教育机构和公益组织联系合作。

