消费 $83,用Claude 实现临床护理系统记录单(所见即所得版)
又到周末了,手又痒痒了,又想用AI做点东西。。。
上个月做过➡️体温单了,那就做一个所见即所得的护理记录单吧,把参照图丢给 CHATGPT,提示词如下:
要求用 react 实现一款移动护理系统里用到的护理记录单功能,需要你帮我以前梳理一下思路。如图所示,单击日期和时间,会自动赋值当前的。病情观察及护理措施,点击后,弹窗,textarea 点确定后,根据长度回显到记录单里。签名那里单击弹窗输入工号密码,正确后渲染上签名图片。
一页写满后,可以新建一页记录单。自己可以创建不同类型的护理记录单,配置不同的表格项。
老规矩,先用ChatGPT打头阵!
🏗 功能拆解
-
表格基础结构
-
表格按“记录单页”划分,一页多行,每行对应一条护理记录。
-
每条记录包含:
- 日期(单击自动填充当前日期)
- 时间(单击自动填充当前时间)
- 体温、脉搏、呼吸、血压、SpO₂ 等数值字段(输入框)
- 病情观察及护理措施(textarea 弹窗输入,回显到表格)
- 签名(弹窗输入工号+密码,校验后渲染签名图片)
-
-
交互逻辑
- 点击“日期/时间” → 自动赋值
new Date()
- 点击“病情观察及护理措施” → 弹窗
Modal
+textarea
- 点击“签名” → 弹窗
Modal
输入工号/密码 → 校验成功 → 显示签名图片 - 当前页满了(比如 20 行),可以点击“新建一页” → 新增一张护理记录单
- 点击“日期/时间” → 自动赋值
-
记录单类型管理
- 护理记录单类型可以自定义
- 不同类型对应不同表格列配置(类似于动态表单配置)
- 配置方式:后端提供 JSON 配置,前端动态渲染列和输入控件
🔑 技术实现思路
-
数据结构设计
const recordPage = {id: 'page-1',type: 'general', // 记录单类型rows: [{date: '2023-07-24',time: '08:05',temperature: 36.1,pulse: 72,respiration: 21,bp: '115/68',spo2: 98,note: '病情观察...',signature: '/uploads/sign/123.png'}] };
-
表格组件
RecordTable
:渲染当前页RecordRow
:渲染单行,可点击单元格编辑- 通过
columns
配置项动态渲染
const columns = [{ key: 'date', label: '日期', type: 'autoDate' },{ key: 'time', label: '时间', type: 'autoTime' },{ key: 'temperature', label: '体温', type: 'input' },{ key: 'pulse', label: '脉搏', type: 'input' },{ key: 'respiration', label: '呼吸', type: 'input' },{ key: 'bp', label: '血压', type: 'input' },{ key: 'spo2', label: 'SpO2', type: 'input' },{ key: 'note', label: '病情观察及护理措施', type: 'modalTextarea' },{ key: 'signature', label: '签名', type: 'modalSignature' } ];
-
弹窗交互
-
病情观察及护理措施
- 点击 → 弹出
Modal + textarea
- 点确定 → 保存内容 → 超过长度自动换行/截断
- 点击 → 弹出
-
签名
- 点击 → 弹出
工号+密码输入框
- 校验接口 → 返回签名图片 URL → 渲染到表格中
- 点击 → 弹出
-
-
分页逻辑
- 每页固定 20 行(可配置)
- 填满后,点击“新建一页” → 追加一个
recordPage
- 多页之间可切换(tabs 或翻页器)
-
记录单类型
后端返回不同配置:
{"general": [{ "key": "date", "label": "日期", "type": "autoDate" },{ "key": "time", "label": "时间", "type": "autoTime" },{ "key": "temperature", "label": "体温", "type": "input" },...],"icu": [{ "key": "date", "label": "日期", "type": "autoDate" },{ "key": "consciousness", "label": "意识", "type": "select", "options": ["清醒", "嗜睡", "昏迷"] },...] }
前端根据配置动态渲染不同的表格结构。
示例代码(React 16.8+)
import React, { useState } from "react";
import { Modal } from "antd"; // 这里用 antd 的 Modal,你也可以换成 antd-mobileconst FIXED_ROWS = 20;const initialRow = () => ({date: "",time: "",temperature: "",pulse: "",respiration: "",bp: "",spo2: "",note: "",signature: "",
});function RecordPage({ pageIndex, rows, onUpdateRow }) {const [noteModal, setNoteModal] = useState({ visible: false, rowIndex: null, value: "" });const [signModal, setSignModal] = useState({ visible: false, rowIndex: null, job: "", password: "" });const handleDateClick = (rowIndex) => {const now = new Date();onUpdateRow(rowIndex, {date: now.toLocaleDateString(),time: now.toLocaleTimeString().slice(0, 5),});};return (<div className="record-page"><h3>护理记录单 - 第 {pageIndex + 1} 页</h3><table border="1" cellPadding="4" style={{ width: "100%", borderCollapse: "collapse" }}><thead><tr><th>日期</th><th>时间</th><th>体温</th><th>脉搏</th><th>呼吸</th><th>血压</th><th>SpO₂</th><th>病情观察及护理措施</th><th>签名</th></tr></thead><tbody>{rows.map((row, idx) => (<tr key={idx}><td onClick={() => handleDateClick(idx)}>{row.date || "点击生成"}</td><td onClick={() => handleDateClick(idx)}>{row.time || "点击生成"}</td><td><inputvalue={row.temperature}onChange={(e) => onUpdateRow(idx, { temperature: e.target.value })}/></td><td><inputvalue={row.pulse}onChange={(e) => onUpdateRow(idx, { pulse: e.target.value })}/></td><td><inputvalue={row.respiration}onChange={(e) => onUpdateRow(idx, { respiration: e.target.value })}/></td><td><inputvalue={row.bp}onChange={(e) => onUpdateRow(idx, { bp: e.target.value })}/></td><td><inputvalue={row.spo2}onChange={(e) => onUpdateRow(idx, { spo2: e.target.value })}/></td><tdstyle={{ cursor: "pointer" }}onClick={() => setNoteModal({ visible: true, rowIndex: idx, value: row.note })}>{row.note || "点击填写"}</td><tdstyle={{ cursor: "pointer" }}onClick={() => setSignModal({ visible: true, rowIndex: idx, job: "", password: "" })}>{row.signature ? (<img src={row.signature} alt="签名" style={{ height: 24 }} />) : ("点击签名")}</td></tr>))}</tbody></table>{/* 病情观察 Modal */}<Modalvisible={noteModal.visible}title="填写病情观察及护理措施"onCancel={() => setNoteModal({ visible: false, rowIndex: null, value: "" })}onOk={() => {onUpdateRow(noteModal.rowIndex, { note: noteModal.value });setNoteModal({ visible: false, rowIndex: null, value: "" });}}><textareastyle={{ width: "100%", minHeight: 120 }}value={noteModal.value}onChange={(e) => setNoteModal({ ...noteModal, value: e.target.value })}/></Modal>{/* 签名 Modal */}<Modalvisible={signModal.visible}title="护士签名"onCancel={() => setSignModal({ visible: false, rowIndex: null, job: "", password: "" })}onOk={() => {// 模拟校验,这里写死一个账号密码if (signModal.job === "123" && signModal.password === "123456") {onUpdateRow(signModal.rowIndex, { signature: "/signatures/demo-sign.png" });setSignModal({ visible: false, rowIndex: null, job: "", password: "" });} else {alert("工号或密码错误!");}}}><div>工号:{" "}<inputvalue={signModal.job}onChange={(e) => setSignModal({ ...signModal, job: e.target.value })}/></div><div style={{ marginTop: 8 }}>密码:{" "}<inputtype="password"value={signModal.password}onChange={(e) => setSignModal({ ...signModal, password: e.target.value })}/></div></Modal></div>);
}export default function NursingRecord() {const [pages, setPages] = useState([{ id: 1, rows: Array.from({ length: FIXED_ROWS }, () => initialRow()) },]);const updateRow = (pageIndex, rowIndex, newData) => {setPages((prev) =>prev.map((page, pIdx) =>pIdx === pageIndex? {...page,rows: page.rows.map((r, rIdx) => (rIdx === rowIndex ? { ...r, ...newData } : r)),}: page));};const addPage = () => {setPages((prev) => [...prev, { id: prev.length + 1, rows: Array.from({ length: FIXED_ROWS }, () => initialRow()) }]);};return (<div>{pages.map((page, idx) => (<RecordPagekey={page.id}pageIndex={idx}rows={page.rows}onUpdateRow={(rowIdx, data) => updateRow(idx, rowIdx, data)}/>))}<button onClick={addPage}>新建一页</button></div>);
}
我需要的不是一个文件,而是一个demo
1️⃣ 初始化项目
在命令行执行:
npx create-react-app nursing-record-demo
cd nursing-record-demo
npm install antd
npm start
这样你就有一个干净的 React 环境。
注:这里要注意你的 react 版本和 antd 版本兼容情况。
2️⃣ 替换 src/App.js
把我之前写的代码整体放进 src/App.js
里(删掉默认的内容)。
完整版本如下(可直接运行):
import React, { useState } from "react";
import { Modal } from "antd";
import "antd/dist/antd.css";const FIXED_ROWS = 20;const initialRow = () => ({date: "",time: "",temperature: "",pulse: "",respiration: "",bp: "",spo2: "",note: "",signature: "",
});function RecordPage({ pageIndex, rows, onUpdateRow }) {const [noteModal, setNoteModal] = useState({ visible: false, rowIndex: null, value: "" });const [signModal, setSignModal] = useState({ visible: false, rowIndex: null, job: "", password: "" });const handleDateClick = (rowIndex) => {const now = new Date();onUpdateRow(rowIndex, {date: now.toLocaleDateString(),time: now.toLocaleTimeString().slice(0, 5),});};return (<div className="record-page"><h3>护理记录单 - 第 {pageIndex + 1} 页</h3><table border="1" cellPadding="4" style={{ width: "100%", borderCollapse: "collapse" }}><thead><tr><th>日期</th><th>时间</th><th>体温</th><th>脉搏</th><th>呼吸</th><th>血压</th><th>SpO₂</th><th>病情观察及护理措施</th><th>签名</th></tr></thead><tbody>{rows.map((row, idx) => (<tr key={idx}><td onClick={() => handleDateClick(idx)}>{row.date || "点击生成"}</td><td onClick={() => handleDateClick(idx)}>{row.time || "点击生成"}</td><td><inputvalue={row.temperature}onChange={(e) => onUpdateRow(idx, { temperature: e.target.value })}/></td><td><inputvalue={row.pulse}onChange={(e) => onUpdateRow(idx, { pulse: e.target.value })}/></td><td><inputvalue={row.respiration}onChange={(e) => onUpdateRow(idx, { respiration: e.target.value })}/></td><td><inputvalue={row.bp}onChange={(e) => onUpdateRow(idx, { bp: e.target.value })}/></td><td><inputvalue={row.spo2}onChange={(e) => onUpdateRow(idx, { spo2: e.target.value })}/></td><tdstyle={{ cursor: "pointer" }}onClick={() => setNoteModal({ visible: true, rowIndex: idx, value: row.note })}>{row.note || "点击填写"}</td><tdstyle={{ cursor: "pointer" }}onClick={() => setSignModal({ visible: true, rowIndex: idx, job: "", password: "" })}>{row.signature ? (<img src={row.signature} alt="签名" style={{ height: 24 }} />) : ("点击签名")}</td></tr>))}</tbody></table>{/* 病情观察 Modal */}<Modalopen={noteModal.visible}title="填写病情观察及护理措施"onCancel={() => setNoteModal({ visible: false, rowIndex: null, value: "" })}onOk={() => {onUpdateRow(noteModal.rowIndex, { note: noteModal.value });setNoteModal({ visible: false, rowIndex: null, value: "" });}}><textareastyle={{ width: "100%", minHeight: 120 }}value={noteModal.value}onChange={(e) => setNoteModal({ ...noteModal, value: e.target.value })}/></Modal>{/* 签名 Modal */}<Modalopen={signModal.visible}title="护士签名"onCancel={() => setSignModal({ visible: false, rowIndex: null, job: "", password: "" })}onOk={() => {if (signModal.job === "123" && signModal.password === "123456") {onUpdateRow(signModal.rowIndex, { signature: "https://via.placeholder.com/80x24?text=签名" });setSignModal({ visible: false, rowIndex: null, job: "", password: "" });} else {alert("工号或密码错误!");}}}><div>工号:{" "}<inputvalue={signModal.job}onChange={(e) => setSignModal({ ...signModal, job: e.target.value })}/></div><div style={{ marginTop: 8 }}>密码:{" "}<inputtype="password"value={signModal.password}onChange={(e) => setSignModal({ ...signModal, password: e.target.value })}/></div></Modal></div>);
}export default function NursingRecord() {const [pages, setPages] = useState([{ id: 1, rows: Array.from({ length: FIXED_ROWS }, () => initialRow()) },]);const updateRow = (pageIndex, rowIndex, newData) => {setPages((prev) =>prev.map((page, pIdx) =>pIdx === pageIndex? {...page,rows: page.rows.map((r, rIdx) => (rIdx === rowIndex ? { ...r, ...newData } : r)),}: page));};return (<div style={{ padding: 20 }}>{pages.map((page, idx) => (<RecordPagekey={page.id}pageIndex={idx}rows={page.rows}onUpdateRow={(rowIdx, data) => updateRow(idx, rowIdx, data)}/>))}</div>);
}
3️⃣ 运行
执行:
npm start
浏览器打开 http://localhost:3000,就能看到护理记录单 demo:
- 点击 日期/时间 自动生成当前值
- 点击 病情观察 弹窗 textarea
- 点击 签名 弹窗工号/密码(输入
123 / 123456
成功显示签名图片)
做到这,发现 CHATGPT 又到了限额了,而系统根本没完成。。。
想起自己在➡️ anyrouter 里还有签到攒的几百刀(由于网络经常不稳,一直也没怎么用)。今天一试发现网络就竟然正常,响应速度很快,这么好的机会可不能浪费!😃
跑起来后,无论怎么改,都感觉很乱。Claude感觉就弄不明白了。
正想着最近没怎么用Claude,怎么退步成这样了?最终还是我发现,它竟然愚蠢的没加载css文件……
- 有了样式,立马不一样了。接着就是一系列的细节优化。。。
看成果!
- 日期/时间 自动取当前时间
- 意识支持选择
- 病情观察自动根据长度计算分行,当前页显示不开,自动创建新页
- 支持按行删除和按页删除
- 输入工号密码签名,支持签名取消
优化空间还有不少(网络报错了,今天没法继续了)
- 目前支持tab键来移动单元格,可以加上方向键左右控制
- 每页都应该带一个表头
- 病情观察首行开头空两格
- 打印目前还没有调完
AI编程,一定注意要做好版本管理 !!
其实做完似乎也没啥实际使用场景,好像就是单纯为了消费,哈哈!
- 看看消费情况统计(花掉就是赚到)
其实 $83.83,真是不便宜,不过是签到送的就不心疼了!😛
P.S.无聊,又整理了一版生词……
Accomplishing、Actualizing、Animating、Beboppin、Billowing、Blanching、Brewing、Caramelizing、Cerebrating、Coalescing、Cogitating、Combobulating、Concocting、Contemplating、Crunching、Deciphering、Deliberating、Determining、Discombobulating、Divining、Drizzling、Effecting、Enchanting、Finagling、Flibbertigibbeting、Forging、Gallivanting、Germinating、Harmonizing、Hatching、Herding、Hustling、Ideating、Incubating、Jiving、Julienning、Levitating、Lollygagging、Orbiting、Percolating、Philosophising、Photosynthesizing、Pondering、Marinating、Moseying、Mulling、Mustering、Nesting、Noodling、Recombobulating、Ruminating、Schlepping、Seasoning、Shimmying、Shucking、Slithering、Spining、Stewing、Swooping、Syncopating、Synthesizing、Thaumaturging、Tinkering、Tomfoolering、Transmuting、Unfurling、Vibing、Wandering、Whatchamacalliting、Wibbling、Wizarding……
附
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
This is a React-based nursing record management system (nursing-record-demo
) built with Create React App. The application provides an interactive interface for healthcare professionals to create, edit, and manage nursing records with support for multiple templates and electronic signatures.
Common Development Commands
- Start development server:
npm start
(opens http://localhost:3000) - Run tests:
npm test
(runs in interactive watch mode) - Build for production:
npm run build
- Linting: Uses built-in ESLint configuration from
react-scripts
Architecture & Core Components
Main Application Structure
- App.js: Main application component containing the core nursing record table interface, pagination, and modal management
- Templates System (
src/config/templates.js
): Configurable templates for different nursing specialties (General, ICU, Surgery, Pediatric) - Component Library (
src/components/
):PatientHeader.js
: Static patient information display componentProfessionalTable.js
: Main table component for nursing records with row rendering and interaction handlingSignatureAuth.js
: Electronic and password-based signature authentication with canvas drawing supportEnhancedNoteModal.js
: Enhanced modal for editing patient observationsInputComponents.js
: Specialized input components for different data types (vitals, intake/output, drainage, etc.)
Data Management
- Storage (
src/utils/storage.js
): LocalStorage-based persistence with auto-save functionality- Records data, user settings, patient information
NursingRecordStorage
class for data operationsAutoSaveManager
class for automatic data persistence with configurable intervals
- Helpers (
src/utils/helpers.js
): Utility functions for text processing, date/time formatting, and validationTextUtils
: Chinese/English mixed text wrapping with character width calculationDateTimeUtils
: Date/time formatting and manipulationValidationUtils
: Form validation for job numbers, passwords, and vital signs
Key Data Structures
- displayRows: Array representing the visual table rows (null for empty rows, objects for record data with
{recordId, lineText, isFirst}
) - recordsRef.current: Map of record IDs to complete record data including vitals, observations, and signatures
- Patient Information: Static display data for patient demographics and case information
- Page Management: Fixed 17-row pages with intelligent pagination and cross-page content handling
User Interface Features
- Dynamic Row Management: Click empty rows to create records, intelligent text wrapping for observations across page boundaries
- Multi-page Support: Each page contains exactly 17 rows with alternating background colors and page-specific controls
- Fixed Navigation Bar: Sticky top toolbar with page jump functionality (1-20, 21-40, etc.) and print controls
- Smart Content Distribution: Observations automatically span multiple rows, with signatures appearing on the last line of each record
- Auto-fill DateTime: Click date/time cells to populate with current timestamp
- Electronic Signatures: Support for both electronic signature drawing and password-based authentication
- Consciousness Selection: Dropdown selection for consciousness levels (清醒, 嗜睡, 昏睡, 昏迷, etc.)
Template System
Templates define column layouts for different nursing specialties:
- GENERAL: Basic nursing with vitals and observations
- ICU: Intensive care with intake/output tracking
- SURGERY: Surgical nursing with drainage and wound monitoring
- PEDIATRIC: Pediatric-specific fields like feeding and excretion
Each template specifies:
- Column configuration (type, width, fields)
- Input validation rules
- Character limits for text wrapping
- Specialized input components
Data Persistence
All data is stored in browser localStorage with these keys:
nursing_records
: Main record data and display statecurrent_template
: Active template selectionuser_settings
: User preferences and auto-save settingscustom_templates
: User-defined templates
Auto-save functionality operates on a 30-second interval by default, automatically persisting changes without user intervention.
Key Constants
ROWS_PER_PAGE
: 20 rows per pageMAX_CHARS_PER_LINE
: 22 characters per line for text wrapping (template-configurable)
Development Notes
- React 16.13.1 with functional components and hooks
- Ant Design 4.x for UI components
- No external state management - uses React state and refs
- Chinese language interface with mixed Chinese/English character width handling
- Testing setup with React Testing Library and Jest