<!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>小鼠体重语音录入工具 - 数字鼠号版</title><script src="https://unpkg.com/xlsx/dist/xlsx.full.min.js"></script><style>:root {--primary-color: #4a90e2;--success-color: #4caf50;--warning-color: #ff9800;--danger-color: #f44336;--light-gray: #f5f5f5;--border-color: #ddd;}* {box-sizing: border-box;margin: 0;padding: 0;}body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;line-height: 1.6;color: #333;background-color: #f9f9f9;padding: 20px;}.container {max-width: 1200px;margin: 0 auto;background: white;border-radius: 10px;box-shadow: 0 3px 15px rgba(0, 0, 0, 0.1);overflow: hidden;}header {background: linear-gradient(135deg, var(--primary-color), #2a75c9);color: white;padding: 20px;text-align: center;}h1 {margin-bottom: 10px;font-size: 28px;}.description {font-size: 16px;opacity: 0.9;}.main-content {display: flex;flex-direction: column;gap: 20px;padding: 20px;}.controls-panel {display: grid;grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: 15px;background-color: var(--light-gray);padding: 20px;border-radius: 8px;}.control-group {display: flex;flex-direction: column;gap: 10px;}.control-group h3 {margin-bottom: 5px;color: var(--primary-color);font-size: 16px;}.btn {padding: 10px 15px;border: none;border-radius: 5px;cursor: pointer;font-weight: 500;transition: all 0.2s;display: flex;align-items: center;gap: 8px;}.btn-primary {background-color: var(--primary-color);color: white;}.btn-success {background-color: var(--success-color);color: white;}.btn-warning {background-color: var(--warning-color);color: white;}.btn-danger {background-color: var(--danger-color);color: white;}.btn:hover {opacity: 0.9;transform: translateY(-2px);}.table-container {overflow-x: auto;margin-bottom: 20px;border: 1px solid var(--border-color);border-radius: 8px;}table {width: 100%;border-collapse: collapse;min-width: 800px;}th, td {border: 1px solid var(--border-color);padding: 12px;text-align: center;position: relative;}th {background-color: #f0f5ff;font-weight: 600;position: sticky;top: 0;}.group-header {background-color: #e6f0ff;font-weight: bold;text-align: left;}.mouse-cell {background-color: #f9f9f9;font-weight: 500;}.date-header {cursor: pointer;transition: all 0.2s;}.date-header.selected {background-color: var(--success-color);color: white;box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);}.date-header:hover {background-color: #bbdefb;}.editable:focus {outline: 2px solid var(--primary-color);background-color: #f0f7ff;}.selected {box-shadow: 0 0 0 2px var(--primary-color);}.voice-controls {display: flex;flex-direction: column;gap: 15px;background-color: #f0f7ff;padding: 20px;border-radius: 8px;border-left: 4px solid var(--primary-color);}.voice-btn {padding: 15px;font-size: 18px;border: none;border-radius: 50px;cursor: pointer;background: linear-gradient(135deg, var(--success-color), #3d8b40);color: white;font-weight: bold;display: flex;justify-content: center;align-items: center;gap: 10px;transition: all 0.3s;box-shadow: 0 4px 10px rgba(76, 175, 80, 0.3);}.voice-btn.listening {background: linear-gradient(135deg, var(--danger-color), #d32f2f);animation: pulse 1.5s infinite;box-shadow: 0 4px 10px rgba(244, 67, 54, 0.3);}@keyframes pulse {0% { transform: scale(1); }50% { transform: scale(1.05); }100% { transform: scale(1); }}.status {padding: 15px;border-radius: 8px;text-align: center;font-weight: 500;min-height: 20px;display: flex;justify-content: center;align-items: center;gap: 10px;}.status.info {background-color: #e3f2fd;color: #1565c0;}.status.success {background-color: #e8f5e9;color: #2e7d32;}.status.warning {background-color: #fff8e1;color: #f57c00;}.status.error {background-color: #ffebee;color: #d32f2f;}.status.listening {background-color: #fff8e1;color: #ff8f00;}.manual-input {display: grid;grid-template-columns: 1fr 2fr 1fr;gap: 10px;align-items: center;}.manual-input input, .manual-input select {padding: 12px;border: 1px solid var(--border-color);border-radius: 5px;font-size: 16px;}.instructions {background-color: #fff3e0;border-left: 4px solid var(--warning-color);padding: 15px;border-radius: 8px;margin-top: 20px;}.instructions h3 {color: var(--warning-color);margin-bottom: 10px;}.instructions ul {list-style-type: none;padding-left: 10px;}.instructions li {margin-bottom: 8px;position: relative;padding-left: 25px;}.instructions li:before {content: "•";color: var(--warning-color);font-weight: bold;position: absolute;left: 0;top: 0;font-size: 18px;}footer {text-align: center;padding: 15px;color: #777;font-size: 14px;border-top: 1px solid var(--border-color);}@media (max-width: 768px) {.controls-panel {grid-template-columns: 1fr;}.manual-input {grid-template-columns: 1fr;}}</style> </head> <body><div class="container"><header><h1>小鼠体重语音录入工具 - 数字鼠号版</h1><p class="description">使用纯数字作为鼠号,语音指令格式:鼠号 体重(如"01号 25.3克")</p></header><div class="main-content"><div class="controls-panel"><div class="control-group"><h3>行操作</h3><button id="addRowBtn" class="btn btn-primary">新增鼠号行</button><button id="addGroupRowBtn" class="btn btn-primary">新增组行</button></div><div class="control-group"><h3>列操作</h3><button id="addDateColumnBtn" class="btn btn-primary">新增日期列</button><button id="removeDateColumnBtn" class="btn btn-danger">删除日期列</button></div><div class="control-group"><h3>数据操作</h3><button id="saveDataBtn" class="btn btn-success">保存数据</button><button id="loadDataBtn" class="btn btn-primary">加载数据</button></div><div class="control-group"><h3>导入导出</h3><button id="excelImportBtn" class="btn btn-success">导入Excel</button><button id="excelExportBtn" class="btn btn-primary">导出Excel</button></div></div><div class="table-container"><table id="weightTable"><thead><tr><th>组别</th><th>鼠号</th><!-- 日期列将通过JavaScript动态添加 --></tr></thead><tbody><!-- 数据行将通过JavaScript动态添加 --></tbody></table></div><div class="voice-controls"><div class="selected-date-info"><h3>当前选中日期: <span id="selectedDateDisplay">未选择</span></h3></div><button id="voiceBtn" class="voice-btn"><span>开始语音录入 (说"鼠号 体重")</span></button><div id="status" class="status info">请先选择一个日期列,然后说出鼠号和体重(如"1 25.3")</div><div class="manual-input"><select id="mouseSelect"><option value="">选择鼠号</option></select><input type="number" id="manualInput" step="0.1" placeholder="输入体重"><button id="saveManualBtn" class="btn btn-success">手动保存</button></div></div><div class="instructions"><h3>使用说明</h3><ul><li><strong>日期选择</strong>: 点击表格中的日期列标题选中日期(绿色高亮显示)</li><li><strong>语音录入</strong>: 点击"开始语音录入"按钮,说出鼠号和体重(如"01号 25.3克")</li><li><strong>手动录入</strong>: 使用下拉菜单选择鼠号,输入体重后点击"手动保存"</li><li><strong>添加行列</strong>: 使用上方控制面板添加新的组、鼠号行或日期列</li><li><strong>数据保存</strong>: 数据会自动保存到浏览器本地存储</li></ul></div></div><footer><p>© 2025 小鼠体重管理系统 | 技术支持: lab@example.com</p></footer></div><script>document.addEventListener('DOMContentLoaded', function() {// 全局变量const table = document.getElementById('weightTable');const tbody = table.querySelector('tbody');const voiceBtn = document.getElementById('voiceBtn');const statusDiv = document.getElementById('status');const mouseSelect = document.getElementById('mouseSelect');const manualInput = document.getElementById('manualInput');const saveManualBtn = document.getElementById('saveManualBtn');const selectedDateDisplay = document.getElementById('selectedDateDisplay');let recognition = null;let selectedDateColumn = null;let selectedCell = null;let finalTranscript = '';let isRecognizing = false;// 初始化表格initializeTable();// 设置事件监听器setupEventListeners();// 初始化语音识别// if(!recognition){// initializeSpeechRecognition();// }// 加载保存的数据loadSavedData();// 初始化表格函数function initializeTable() {// 添加示例数据addGroup('组一');addMouse('组一', '1');addMouse('组一', '2');addGroup('组二');addMouse('组二', '3');addMouse('组二', '4');// 添加日期列addDateColumn('20250901');addDateColumn('20250902');addDateColumn('20250903');// 更新选择器updateMouseSelector();}// 设置事件监听器function setupEventListeners() {// 行操作按钮document.getElementById('addRowBtn').addEventListener('click', addMouseRow);document.getElementById('addGroupRowBtn').addEventListener('click', addGroupRow);// 列操作按钮document.getElementById('addDateColumnBtn').addEventListener('click', addDateColumnPrompt);document.getElementById('removeDateColumnBtn').addEventListener('click', removeSelectedDateColumn);// 数据操作按钮document.getElementById('saveDataBtn').addEventListener('click', saveData);document.getElementById('loadDataBtn').addEventListener('click', loadSavedData);document.getElementById('excelExportBtn').addEventListener('click', exportToExcel);document.getElementById('excelImportBtn').addEventListener('click', function() {// 创建文件输入元素const fileInput = document.createElement('input');fileInput.type = 'file';fileInput.accept = '.xlsx, .xls';fileInput.onchange = function(e) {const file = e.target.files[0];if (file) {importExcelFile(file);}};// 触发文件选择对话框fileInput.click();});// 手动保存按钮saveManualBtn.addEventListener('click', saveManualData);// 鼠号选择器事件mouseSelect.addEventListener('change', function() {if (this.value && selectedDateColumn) {focusCellForMouse(this.value);}});}function startRecognition() {initializeSpeechRecognition(); // 确保实例已初始化if (!isRecognizing) {isRecognizing = true;recognition.start();console.log("语音识别已启动...");}}function stopRecognition() {if (isRecognizing) {isRecognizing = false;recognition.stop();console.log("语音识别已停止.");}}// window.onload = initializeSpeechRecognition;// 初始化语音识别function initializeSpeechRecognition() {if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;if(!recognition){recognition = new SpeechRecognition();recognition.continuous = true;recognition.interimResults = true;recognition.lang = 'zh-CN';recognition.onstart = function() {voiceBtn.classList.add('listening');statusDiv.textContent = '正在聆听...请说"鼠号 体重"(如"01号 25.3克")';statusDiv.className = 'status listening';let finalTranscript = '';};recognition.onresult = function(event) {let interimTranscript = finalTranscript;for (let i = event.resultIndex; i < event.results.length; i++) {if (event.results[i].isFinal) {finalTranscript += event.results[i][0].transcript + ' ';} else {interimTranscript += event.results[i][0].transcript;}}console.log('finalTranscript: ' + finalTranscript); const transcript = event.results[event.results.length-1][0].transcript.trim();processVoiceCommand(transcript);};recognition.onerror = function(event) {statusDiv.textContent = '识别错误: ' + event.error;statusDiv.className = 'status error';voiceBtn.classList.remove('listening');};recognition.onend = function() {voiceBtn.classList.remove('listening');sleep(2000);recognition.onstart();console.log('isRecognizing: ' + isRecognizing);recognition.start();};}} else {voiceBtn.disabled = true;statusDiv.textContent = '您的浏览器不支持语音识别功能';statusDiv.className = 'status error';}}function sleep(ms) {return new Promise(resolve => setTimeout(resolve, ms));}voiceBtn.addEventListener('click', function() {if (!selectedDateColumn) {statusDiv.textContent = '请先选择一个日期列';statusDiv.className = 'status error';return;}// if (recognition) {try {// recognition.start();startRecognition();} catch (error) {statusDiv.textContent = '无法启动语音识别: ' + error.message;statusDiv.className = 'status error';}// }});// 处理语音指令function processVoiceCommand(command) {if (!selectedDateColumn) {statusDiv.textContent = '请先选择一个日期列';statusDiv.className = 'status error';return;}console.log(command);command = convertChineseNumbers(command);command = command.replace("号", '').replace("克", '');console.log(command);// 使用正则表达式匹配数字鼠号和体重const pattern = /(\d+)\s+(\d+(?:\.\d+)?)/;const match = command.match(pattern);if (match && match.length >= 3) {const mouse = match[1];const weight = parseFloat(match[2]);if (!isNaN(weight)) {const success = focusCellForMouse(mouse);if (success && selectedCell) {selectedCell.textContent = weight.toFixed(1);statusDiv.textContent = `成功录入: ${mouse}号 ${weight.toFixed(1)}g`;statusDiv.className = 'status success';saveData();} else {statusDiv.textContent = `未找到鼠号: ${mouse}`;statusDiv.className = 'status warning';}} else {statusDiv.textContent = '未能识别到有效体重数值';statusDiv.className = 'status error';}} else {statusDiv.textContent = '指令格式不正确,请说"鼠号 体重"(如"01号 25.3克")';statusDiv.className = 'status error';}}function convertChineseNumbers(input) {// 中文数字映射const chineseNumbers = {'零': 0, '〇': 0, '一': 1, '二': 2, '三': 3, '四': 4,'五': 5, '六': 6, '七': 7, '八': 8, '九': 9,'壹': 1, '贰': 2, '叁': 3, '肆': 4, '伍': 5,'陆': 6, '柒': 7, '捌': 8, '玖': 9, '貮': 2, '两': 2};// 中文单位映射const chineseUnits = {'十': 10, '拾': 10,'百': 100, '佰': 100,'千': 1000, '仟': 1000,'万': 10000, '萬': 10000,'亿': 100000000, '億': 100000000};let result = '';let numberBuffer = '';let tempNumber = 0;let unitStack = 0;let lastWasNumber = false;for (let i = 0; i < input.length; i++) {const char = input[i];// 如果是阿拉伯数字或小数点,直接添加到缓冲区if (/[0-9.]/.test(char)) {if (!lastWasNumber && numberBuffer !== '') {result += processChineseNumber(numberBuffer) + ' ';numberBuffer = '';}numberBuffer += char;lastWasNumber = true;continue;}// 如果是中文数字if (char in chineseNumbers) {if (!lastWasNumber && numberBuffer !== '') {result += processChineseNumber(numberBuffer) + ' ';numberBuffer = '';}numberBuffer += char;lastWasNumber = true;continue;}// 如果是中文单位if (char in chineseUnits) {numberBuffer += char;lastWasNumber = true;continue;}// 如果不是数字字符,处理缓冲区中的数字if (numberBuffer !== '') {result += processChineseNumber(numberBuffer) + ' ';numberBuffer = '';lastWasNumber = false;}// 添加非数字字符到结果result += char;lastWasNumber = false;}// 处理最后可能剩余的数字if (numberBuffer !== '') {result += processChineseNumber(numberBuffer);}return result.trim();// 处理中文数字字符串的函数function processChineseNumber(numStr) {// 如果已经是阿拉伯数字,直接返回if (/^[0-9.]+$/.test(numStr)) {return numStr;}let total = 0;let current = 0;let decimalMode = false;let decimalPlaces = 0;for (let i = 0; i < numStr.length; i++) {const char = numStr[i];// 处理小数点if (char === '.') {decimalMode = true;total += current;current = 0;continue;}// 如果是中文数字if (char in chineseNumbers) {if (decimalMode) {decimalPlaces++;current = current * 10 + chineseNumbers[char];} else {current = chineseNumbers[char];}continue;}// 如果是中文单位if (char in chineseUnits) {const unit = chineseUnits[char];// 处理"十"开头的特殊情况,如"十一"(11)if (unit === 10 && current === 0) {current = 1;}if (unit >= 10000) {total += current;total *= unit;current = 0;} else {current *= unit;total += current;current = 0;}}}total += current;// 处理小数部分if (decimalMode && decimalPlaces > 0) {total = total.toFixed(decimalPlaces);}return total.toString();}}// 使用示例// console.log(convertChineseNumbers("四十三号二十一克")); // 输出: "43号 21克"// console.log(convertChineseNumbers("零八号二十2.0")); // 输出: "08号 202.0"// console.log(convertChineseNumbers("一百二十三")); // 输出: "123"// console.log(convertChineseNumbers("三千五百六十八")); // 输出: "3568"// console.log(convertChineseNumbers("十二点三四")); // 输出: "12.34"// 选择日期列function selectDateColumn(headerCell) {if (selectedDateColumn) {selectedDateColumn.classList.remove('selected');}selectedDateColumn = headerCell;headerCell.classList.add('selected');const date = headerCell.textContent;selectedDateDisplay.textContent = date;statusDiv.textContent = `已选择日期: ${date}`;statusDiv.className = 'status info';}// 选择单元格function selectCell(cell) {if (selectedCell) {selectedCell.classList.remove('selected');}selectedCell = cell;cell.classList.add('selected');}// 根据鼠号定位单元格到当前选中日期列function focusCellForMouse(mouse) {if (!selectedDateColumn) {statusDiv.textContent = '请先选择一个日期列';statusDiv.className = 'status error';return false;}const dateColumnIndex = selectedDateColumn.cellIndex;const rows = tbody.rows;for (let i = 0; i < rows.length; i++) {const mouseCell = rows[i].cells[1];if (mouseCell && mouseCell.textContent === mouse) {selectCell(rows[i].cells[dateColumnIndex]);return true;}}return false;}// 添加组行function addGroupRow() {const groupName = prompt('请输入组名:', '新组');if (groupName) {addGroup(groupName);saveData();updateMouseSelector();}}// 添加鼠号行function addMouseRow() {const groups = Array.from(table.querySelectorAll('td.group-header')).map(cell => cell.textContent);if (groups.length === 0) {alert('请先添加组!');return;}const group = prompt(`请输入组名(可选组: ${groups.join(', ')}):`, groups[0]);if (!group) return;const mouseId = prompt('请输入鼠号:', (tbody.rows.length + 1).toString());if (mouseId) {// 验证鼠号是否为有效数字if (!/^\d+$/.test(mouseId)) {alert('鼠号必须为数字!');return;}addMouse(group, mouseId);saveData();updateMouseSelector();}}// 添加组function addGroup(groupName) {const row = tbody.insertRow();const cell = row.insertCell();cell.textContent = groupName;cell.classList.add('group-header');cell.colSpan = table.rows[0].cells.length;cell.contentEditable = true;cell.addEventListener('blur', function() {saveData();updateMouseSelector();});}// 添加鼠号function addMouse(groupName, mouseId) {// 找到组行let groupRowIndex = -1;const rows = tbody.rows;for (let i = 0; i < rows.length; i++) {if (rows[i].cells[0].textContent === groupName && rows[i].cells[0].classList.contains('group-header')) {groupRowIndex = i;break;}}if (groupRowIndex === -1) {alert(`未找到组: ${groupName}`);return;}// 在组行后插入新行const row = tbody.insertRow(groupRowIndex + 1);const groupCell = row.insertCell();groupCell.textContent = groupName;groupCell.style.visibility = 'hidden';const mouseCell = row.insertCell();mouseCell.textContent = mouseId;mouseCell.classList.add('mouse-cell');mouseCell.contentEditable = true;// 为日期列添加空单元格const headerCells = table.rows[0].cells;for (let i = 2; i < headerCells.length; i++) {const dataCell = row.insertCell();dataCell.contentEditable = true;}// 添加编辑事件mouseCell.addEventListener('blur', function() {// 验证鼠号是否为有效数字if (!/^\d+$/.test(this.textContent)) {alert('鼠号必须为数字!');this.textContent = mouseId; // 恢复原值return;}saveData();updateMouseSelector();});}// 添加日期列提示function addDateColumnPrompt() {const dateStr = prompt('请输入日期(格式: YYYYMMDD):', new Date().toISOString().slice(0, 10).replace(/-/g, ''));if (dateStr) {addDateColumn(dateStr);saveData();}}// 添加日期列function addDateColumn(dateStr) {// 添加表头const headerRow = table.rows[0];const headerCell = headerRow.insertCell();headerCell.textContent = dateStr;headerCell.classList.add('date-header');// 为日期列添加点击事件headerCell.addEventListener('click', function() {selectDateColumn(this);});// 为每行添加单元格const rows = tbody.rows;for (let i = 0; i < rows.length; i++) {// 跳过组行(组行只有一个单元格,colspan为总列数)if (rows[i].cells.length === 1 && rows[i].cells[0].colSpan > 1) {// 更新组行的colspanrows[i].cells[0].colSpan = headerRow.cells.length;continue;}const cell = rows[i].insertCell();cell.contentEditable = true;}}// 删除选中的日期列function removeSelectedDateColumn() {if (!selectedDateColumn) {alert('请先选择一个日期列');return;}const colIndex = selectedDateColumn.cellIndex;if (colIndex < 2) {alert('不能删除组或鼠号列!');return;}if (confirm('确定要删除这一列吗?')) {// 删除表头单元格const headerRow = table.rows[0];headerRow.deleteCell(colIndex);// 删除数据行中的单元格const rows = tbody.rows;for (let i = 0; i < rows.length; i++) {// 跳过组行(组行只有一个单元格,colspan为总列数)if (rows[i].cells.length === 1 && rows[i].cells[0].colSpan > 1) {// 更新组行的colspanrows[i].cells[0].colSpan = headerRow.cells.length;continue;}rows[i].deleteCell(colIndex);}// 重置选中状态selectedDateColumn = null;selectedDateDisplay.textContent = '未选择';statusDiv.textContent = '日期列已删除,请选择另一个日期列';statusDiv.className = 'status info';saveData();}}// 手动保存数据function saveManualData() {const mouse = mouseSelect.value;const weight = parseFloat(manualInput.value);if (!mouse || isNaN(weight)) {alert('请选择鼠号并输入有效的体重值!');return;}if (!selectedDateColumn) {alert('请先选择一个日期列!');return;}const success = focusCellForMouse(mouse);if (success && selectedCell) {selectedCell.textContent = weight.toFixed(1);statusDiv.textContent = `手动录入: ${mouse}号 ${weight.toFixed(1)}g`;statusDiv.className = 'status success';manualInput.value = '';saveData();}}// 更新鼠号选择器function updateMouseSelector() {mouseSelect.innerHTML = '<option value="">选择鼠号</option>';const rows = tbody.rows;for (let i = 0; i < rows.length; i++) {const mouseCell = rows[i].cells[1];if (mouseCell && mouseCell.classList.contains('mouse-cell')) {const option = document.createElement('option');option.value = mouseCell.textContent;option.textContent = mouseCell.textContent + '号';mouseSelect.appendChild(option);}}}function exportToExcel() {const savedData = localStorage.getItem('mouseWeightData');if(!saveData){retrun;}data = JSON.parse(savedData);// 创建工作簿const wb = XLSX.utils.book_new();// 创建数据数组const dataArray = [];// 添加表头const headerRow = ['组别', '鼠号', ...data.headers];dataArray.push(headerRow);// 处理每个组的数据let rowIndex = 1; // 从第二行开始(表头是第一行)const merges = []; // 存储合并单元格信息for (const groupName in data.groups) {// 添加组别行(合并单元格)const groupRow = [groupName];// 其余单元格留空,用于合并for (let i = 0; i < headerRow.length - 1; i++) {groupRow.push('');}dataArray.push(groupRow);// 记录组别行的起始位置const groupStartRow = rowIndex;// 添加该组下的所有鼠号数据const mice = data.groups[groupName];for (const mouseId in mice) {const mouseData = mice[mouseId];const mouseRow = ['', mouseId]; // 组别列为空(已合并)// 添加每个日期的数据for (const date of data.headers) {mouseRow.push(mouseData[date] || '');}dataArray.push(mouseRow);rowIndex++;}// 添加合并区域(组别列合并)merges.push({s: { r: groupStartRow, c: 0 }, // 起始行和列e: { r: rowIndex - 1, c: 0 } // 结束行和列});rowIndex++;}// 创建工作表const ws = XLSX.utils.aoa_to_sheet(dataArray);// 设置合并单元格ws['!merges'] = merges;// 添加样式 - 突出显示特定日期列const highlightColIndex = data.headers.indexOf(data.selectedDate) + 2; // +2 因为前面有组别和鼠号列if (highlightColIndex >= 0) {// 遍历所有行,为特定列添加样式for (let r = 0; r < dataArray.length; r++) {const cellRef = XLSX.utils.encode_cell({ r: r, c: highlightColIndex });if (!ws[cellRef]) ws[cellRef] = {};if (!ws[cellRef].s) ws[cellRef].s = {};// 设置背景色为浅绿色ws[cellRef].s = {fill: {patternType: "solid",fgColor: { rgb: "FFE0F0E0" }}};}}// 将工作表添加到工作簿XLSX.utils.book_append_sheet(wb, ws, "实验数据");// 导出Excel文件XLSX.writeFile(wb, "实验数据导出.xlsx");alert("Excel文件已生成并开始下载!");}// 保存数据到localStoragefunction saveData() {const data = {headers: [],groups: {},selectedDate: selectedDateColumn ? selectedDateColumn.textContent : null};// 获取列标题(从第三列开始)const headerRow = table.rows[0];for (let i = 2; i < headerRow.cells.length; i++) {data.headers.push(headerRow.cells[i].textContent);}// 获取组和鼠数据let currentGroup = '';const rows = tbody.rows;for (let i = 0; i < rows.length; i++) {const cells = rows[i].cells;// 检查是否是组行if (cells.length === 1 || cells[0].colSpan > 1) {currentGroup = cells[0].textContent;data.groups[currentGroup] = {};continue;}// 处理鼠数据行const mouseId = cells[1].textContent;data.groups[currentGroup][mouseId] = {};// 获取体重数据for (let j = 2; j < cells.length; j++) {const date = headerRow.cells[j].textContent;data.groups[currentGroup][mouseId][date] = cells[j].textContent;}}// console.log(JSON.stringify(data));localStorage.setItem('mouseWeightData', JSON.stringify(data));// 3秒后恢复提示setTimeout(() => {if (!selectedDateColumn) {statusDiv.textContent = '请先选择一个日期列,然后说出鼠号和体重';statusDiv.className = 'status info';}}, 3000);}// Excel导入功能实现(修复版)function importExcelFile(file) {const reader = new FileReader();reader.onload = function(e) {try {const data = new Uint8Array(e.target.result);const workbook = XLSX.read(data, { type: 'array' });// 获取第一个工作表const worksheet = workbook.Sheets[workbook.SheetNames[0]];// 将工作表转换为JSON对象,确保返回二维数组const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });// 处理导入的数据processImportedData(jsonData);} catch (error) {console.error('导入Excel文件时出错:', error);alert('导入失败,请检查文件格式是否正确');}};reader.onerror = function() {alert('读取文件时发生错误');};reader.readAsArrayBuffer(file);}// 处理导入的Excel数据(修复版)function processImportedData(data) {if (!data || data.length < 2) {alert('导入的数据格式不正确');return;}// 确保data[0]是数组if (!Array.isArray(data[0])) {// 如果不是数组,尝试转换const convertedData = [];for (let i = 0; i < data.length; i++) {if (typeof data[i] === 'object' && data[i] !== null) {// 如果是对象,转换为数组convertedData.push(Object.values(data[i]));} else {// 其他情况直接使用convertedData.push(data[i]);}}data = convertedData;}// 提取表头(日期列)const headers = Array.isArray(data[0]) ? data[0].slice(2) : [];// 初始化groups对象const groups = {};let currentGroup = '';// 从第二行开始处理数据for (let i = 1; i < data.length; i++) {const row = data[i];if (!row || (Array.isArray(row) && row.length < 2)) continue;// 检查是否是组别行(组名列有值,鼠号列为空)if (Array.isArray(row) && row[0] && !row[1]) {currentGroup = row[0];groups[currentGroup] = {};continue;}// 处理鼠号数据行if (currentGroup && Array.isArray(row) && (row[0] === '' || row[0] === null || row[0] === undefined) && row[1]) {const mouseId = row[1].toString(); // 确保鼠号是字符串groups[currentGroup][mouseId] = {};// 填充日期数据for (let j = 0; j < headers.length; j++) {const date = headers[j];const value = (row[j + 2] || '').toString(); // 数据从第三列开始groups[currentGroup][mouseId][date] = value;}}}// 构建完整的数据结构const importedData = {headers: headers,groups: groups,selectedDate: headers.length > 0 ? headers[0] : '' // 默认选择第一个日期};console.log(importedData);// 使用导入的数据更新应用状态localStorage.setItem('mouseWeightData', JSON.stringify(importedData));loadSavedData();alert('Excel文件导入成功!');}// 从localStorage加载数据function loadSavedData() {const savedData = localStorage.getItem('mouseWeightData');if (savedData) {const data = JSON.parse(savedData);// 清空现有表格(保留表头前两列)while (tbody.firstChild) {tbody.removeChild(tbody.firstChild);}// 清空日期列(保留前两列)const headerRow = table.rows[0];while (headerRow.cells.length > 2) {headerRow.deleteCell(2);}// 添加日期列data.headers.forEach(date => {addDateColumn(date);});// 添加组和鼠数据for (const [groupName, mice] of Object.entries(data.groups)) {addGroup(groupName);for (const [mouseId, weights] of Object.entries(mice)) {addMouse(groupName, mouseId);// 填充体重数据const rows = tbody.rows;for (let i = 0; i < rows.length; i++) {const mouseCell = rows[i].cells[1];if (mouseCell && mouseCell.textContent === mouseId) {for (const [date, weight] of Object.entries(weights)) {const headerCells = headerRow.cells;for (let j = 2; j < headerCells.length; j++) {if (headerCells[j].textContent === date) {rows[i].cells[j].textContent = weight;break;}}}break;}}}}// 恢复选中的日期if (data.selectedDate) {const headerCells = headerRow.cells;for (let i = 2; i < headerCells.length; i++) {if (headerCells[i].textContent === data.selectedDate) {selectDateColumn(headerCells[i]);break;}}}updateMouseSelector();statusDiv.textContent = '数据已加载';statusDiv.className = 'status success';} else {statusDiv.textContent = '没有找到保存的数据,使用示例数据';statusDiv.className = 'status info';}}});</script> </body> </html>
鼠你爱称重
相关文章:
鼠你爱称重
<!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>小鼠体重语音录入工具 - 数字鼠号版</t…...
详细介绍:用户争夺与智能管理:定制开发开源AI智能名片S2B2C商城小程序的战略价值与实践路径
详细介绍:用户争夺与智能管理:定制开发开源AI智能名片S2B2C商城小程序的战略价值与实践路径pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&…...
PlorarD(WEB中等)
到底给不给flag呢先看代码 get和post里面必须只有一个发送了flag 如果两个都发送了会是true然后运行exit直接结束代码 再下一个是发送的flag不能是===flag 不然也是一样 之后就是一个循环遍历,把post传的参数当作一个变量名然后参数值当作变量值 输入一个flag=a看一下所以这里…...
神经网络稀疏化设计构架方式和原理深度解析
神经网络稀疏化设计构架方式和原理深度解析pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !impo…...
天下拍拍卖系统:二方系统也能扩展三方平台功能
过去很多年,大多数拍卖公司为了快速开展线上拍卖会,普遍选择入驻阿里拍卖、京东拍卖、公拍网等三方平台——功能齐全、流量大、上线快。但随着业务深入,企业逐渐发现三方平台存在一些限制,想要私有化搭建一套属于拍卖公司自己的拍卖系统,但同时可能也想保留一些三方平台的…...
express使用redis
我用的pnpm pnpm add express redisconst express = require(express); const redis = require(redis); var app = express() var port = 3000 // 创建 Redis 客户端实例 const redisClient = redis.createClient({url: redis://172.17.0.185:6379 ,password: b7371d927aec647d…...
day07 课程
day07 课程课程:https://www.bilibili.com/video/BV1o4411M71o?spm_id_from=333.788.videopod.episodes&p=148 7.1 字典的应用场景7.2 创建字典的语法7.3 字典常用操作之新增7.4 字典常用操作之删除7.5 字典常用操作之修改———————————————————————…...
111
111111111...
排序实现java - 教程
排序实现java - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14p…...
.net core 发布到 iis 步骤
1. 打开服务器管理器,管理,添加角色和功能,把 IIS 相关的全勾上。 2. 安装.net core 环境,需要 ASP.NET Core 运行时的 Hosting Bundle 版本,其他版本没用。 3. 安装 webdeploy, 服务器防火墙打开8172端口。 4. 在 IIS 上创建站点, 配置的文件夹权限需要添加 everyone 的…...
kylin SP2安装mysql8.4.5
环境:OS:kylin SP2mysql:8.4.5 glibc2.17,建议安装glibc.2.28版本 查看系统glibc版本[root@localhost soft]# ldd --version ldd (GNU libc) 2.28 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There i…...
微信社群机器人接口
微信个人号开发API/文档/教程 大家一般需求点无非是以下几个需求: 1.开发个人微信营销系统 2.开发自定义的微信机器人, 3.开发微信智能聊天客服系统 4.定制行业内的群数据分析功能需求很简单,业务代码贼好撸,但是如何和微信交互呢,如何取到微信数据调用相关聊天接口呢,具体…...
C++的枚举类
语法:enum class 枚举类名 [: 底层类型] {枚举值1,枚举值2,... };一般形式(当然我们一般默认成员都显转int,因此底层类型一般不写) C++的枚举类: 在C++中,enum class是一种类型安全的枚举类型,它比传统的enum类型提供了更好的作用域控制和类型安全性。使用enum class可以…...
Revit二次开发 钢筋生成API(一)
1、自由钢筋生成API创建不受约束的自由形式钢筋。以后不能将约束添加到此钢筋。public static Rebar CreateFreeForm(Document doc,RebarBarType barType,Element host,IList<CurveLoop> curves,out RebarFreeFormValidationResult error )通过此方法,可以创建一个或者多…...
方法
什么是方法 方法是程序中最小的执行单位 实际开发中:重复的代码,具有独立功能的代码可以抽取到方法当中 实际开发中方法的好处:可以提高代码的复用性 提高代码的可维护性 最简单的方法定义和调用 方法的格式:把一些代码打包在一起,用到时候就调用 方法定义:把一些代码打包在…...
详细介绍:PHP基础-语法初步(第七天)
详细介绍:PHP基础-语法初步(第七天)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !importan…...
如何通过Python SDK 删除 Collection
本文介绍如何通过Python SDK删除一个已创建的Collection。 重要 删除Collection后,该Collection所有数据将删除且不可恢复,请谨慎操作。 前提条件已创建Cluster:创建Cluster已获得API-KEY:API-KEY管理已安装最新版SDK:安装DashVector SDK接口定义 Python示例: Client.del…...
maven项目连接DM数据库和基本sql使用
maven项目连接DM数据库和基本sql使用直接引入Maven依赖<!-- DM数据库JDBC驱动 --> <dependency><groupId>com.dameng</groupId><artifactId>DmJdbcDriver18</artifactId><version>8.1.3.140</version> </dependency>dem…...
【中国计算机学会CCF主办】第六届人工智能、大数据与算法国际学术会议(CAIBDA 2026)
第六届人工智能、大数据与算法国际学术会议(CAIBDA 2026) 2026 6th International Conference on Artificial Intelligence, Big Data and Algorithms (CAIBDA 2026)重要信息 大会时间:2026年6月12-14日 大会地点:天津(线上同步进行) 大会官网:www.caibda.org *为报名…...
图片 - voasem
常用工具binwalkforemostwinhex010filestegsolvezstegF5StegdetectSteghideoutguessexiftoolstegseek解题思路 一.未知文件类型 当文件没有后缀名或者有后缀名却无法打开时,我们需要去识别图片类型 1.可以用file命令进行识别2.通过以下应用查看文件头类型---->然后判断出文…...
图片大全 - voasem
常用工具binwalkforemostwinhex010filestegsolvezstegF5StegdetectSteghideoutguessexiftoolstegseek解题思路 一.未知文件类型 当文件没有后缀名或者有后缀名却无法打开时,我们需要去识别图片类型 1.可以用file命令进行识别2.通过以下应用查看文件头类型---->然后判断出文…...
面试时让你设计一个“朋友圈点赞”功能测试,如何回答才出彩?
希望这篇文章能够帮助你在面试中脱颖而出,不仅拿到心仪的offer,更展现出你作为优秀测试工程师的潜质和能力。祝你面试成功!朋友圈点赞,一个看似简单的功能,背后却涉及复杂的技术逻辑和用户体验考量。当面试官抛出这个问题时,他真正想考察的不是你能想到多少测试点,而是你…...
企训宝教育培训微信小程序系统
1. 概述总结 企训宝教育培训小程序系统包含微信小程序和抖音小程序相关的源码及定制开发服务。其交付方式为微擎系统交付,微擎系统是一款基于 PHP 开发的开源应用生态系统,主要用于快速搭建微信公众号、小程序等应用,同时支持 Web 系统开发与部署,该程序源码未加密,为官方…...
Inventor Professional 2026.1.1 产品设计与工程制图
描述 Autodesk Inventor提供了专业级机械设计、文档编制和产品仿真工具。参数化建模、直接建模、自由形状建模和基于规则的设计功能的强大组合。用于钣金、结构件设计、三维布管、电缆和线束、演示、渲染、仿真、机床设计等的集成工具。值得信赖的 DWG™ 兼容性,强大的基于模型…...
叮当计步微信小程序系统
1. 概述总结 叮当计步小程序系统是基于微擎系统交付的应用,微擎系统是一款基于 PHP 开发的开源应用生态系统,主要用于快速搭建微信公众号、小程序等应用,同时支持 Web 系统开发与部署。该计步系统历经数月研发,投入 20 多万研发费用,注重数据可靠性、系统扩展性和高并发支…...
fetch-event-source踩坑sse(getReader)后续 IOS全量返回问题
这两天在做智能聊天,遇到了和这个博主相同的问题,我按这个改了,https://blog.csdn.net/a598829181/article/details/135913704,但是也停留在IOS会全量返回。 后来试了fetch 模拟,失败,增加各种IOS兼容,web-streams-polyfill,失败。试了event-source-polyfill可以,但是会…...
P12508 「ROI 2025 Day2」程序员的日常
在天数 \(k\) 固定时,定义 \(p_i\) 为第 \(i\) 个连续段的起点。那么一个贪心是在保证第 \(i\) 段的 \(\max=a_{p_i}\) 时尽量最小化 \(a_{p_{i+1}}\)。于是有 \(p_{i+1}=\arg\min\limits\left\{a_j\mid p_i+1\le j\le \min(r_{p_i},n-k+i+1)\right\}\)。注意最后一个位置可能…...
手机上有哪些比较好用的待办事项提醒工具 - 指南
手机上有哪些比较好用的待办事项提醒工具 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace …...
Redis源码学习 -- 数据类型编码 -- List - -蓝蜗牛
1. 什么是List? List是Redis的数据类型之一,给用户提供一个双向链表的功能。核心优势是头尾操作O(1)。 2. List的编码模式 List的编码模式有两种:LISTPACK和QUICKLIST。(下文用全大写表示编码名称,首字母大写表示数据结构) Quicklist本身就是节点为Listpack的链表,所…...
乌班图无法登录桌面,只能终端登录用户。且有网拉不了包(DNS问题)
尝试startx解决dns问题 $ sudo vi /etc/resolv.conf 新增nameserver 127.0.1.1 #这里用的是阿里云的DNS服务器 nameserver 223.5.5.5 nameserver 223.6.6.6一定要更新一下 $ sudo apt-get update重新安装桌面$ sudo apt-get install xorg $ sudo apt-get install ubuntu-desk…...
事半功倍是蠢蛋53 tornado接口报错
新写的接口无法访问也不404,log也没有任何输出。 二分找出初始化的时候报错...
完整教程:云手机的技术架构可分为哪些
完整教程:云手机的技术架构可分为哪些pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !importan…...
AI提示词遇见精密算法:TimeGuessr如何用数学魔法打造文化游戏新体验
在人工智能与历史文化的美妙交融中,一套精密的评分算法正在重新定义游戏公平性与挑战性当我们谈论AI生成的文化游戏时,很多人首先想到的是华丽的视觉效果和智能的内容生成。然而,真正让TimeGuessr(https://timeguessr.online/)脱颖而出的,是其背后那套**精密而公平的评分算…...
Arkime:大规模开源网络分析与数据包捕获系统
Arkime是一个开源的大规模网络数据包捕获与分析系统,支持PB级流量处理,提供完整的PCAP存储、索引和搜索功能,帮助安全团队进行网络取证和威胁检测。Arkime:大规模开源网络分析与数据包捕获系统 项目描述 Arkime(前身为Moloch)是一个大规模、开源的网络数据包捕获和分析系…...
kylin SP2安装mysql 8.0.41
环境:OS:kylin SP2mysql:8.0.41 glibc2.17查看系统glibc版本[root@localhost soft]# ldd --version ldd (GNU libc) 2.28 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even…...
SAP采购订单数据获取
最近要配合公司AI做一个采购订单信息获取。 1、根据条件获取采购订单基本信息。 2、得到最早交易记录和最晚交易记录。 3、得出平均含税单价。 4、得出总交易条数。 AI的模型输入因为是不确定的,可能单个问,多个问,各种问,目前定义了采购订单、供应商、物料、日期等维度,这…...
get和post如何理解
基础概念: get主要是获取资源,post主要提交资源 传输链路上区别: get 在URL上携带参数,公开透明,不安全,幂等性(同一个请求多次执行,结果和只执行一次是一样的,不会产生额外的副作用,不会改变服务器的状态) 传递参数数量比较少(一般是2048个字符,但是具体还要看浏…...
me and my girlfriend WP复盘
一台非常简单的靶机复盘 vulnhub官网注释 Description Back to the Top Description: This VM tells us that there are a couple of lovers namely Alice and Bob, where the couple was originally very romantic, but since Alice worked at a private company, "C…...
顺序表
#include<iostream> #include<cstdlib> #define Maxsize 100 using namespace std; typedef struct//存储元素 {int data[Maxsize];int length; }Sqlist; //建立顺序表 typedef int CYDGOOD; //初始化线性表 void InitList(Sqlist *& L) {L = new Sqlist;L …...
能源管理的数字神经:MyEMS如何重塑能效认知
在工业设备的低沉轰鸣中,在写字楼宇的明暗交替间,能源如血液般在现代社会中流动。如何读懂这些流动的韵律,如何与这些无形的能量对话,成为当代能效管理的核心命题。MyEMS作为一套开源的能源管理系统,正在为各类组织构建这样的数字感知能力,让能源管理从模糊的经验艺术走向…...
开源・数据・能效:MyEMS 如何成为能源管理革新的核心引擎
在现代建筑的钢构骨架内,在工厂设备的运转节拍中,能量以电流、热能、冷量的形式不停流动。这些无形的流动蕴含着效率的秘密与优化的钥匙,而读懂这种语言需要特殊的解码能力。MyEMS作为一套开源能源管理系统,正扮演着这样的解码者角色——它将混沌的能源数据转化为清晰的行动…...
mysql回表,为什么你的查询总是慢半拍?
各位数据库爱好者们,不知道你们是否遇到过这样的场景:明明建了索引,查询速度却还是不理想?今天我们就来深入探讨这个让无数开发者头疼的问题——MySQL回表机制。理解了这个概念,你将能够轻松诊断并优化那些看似诡异的慢查询。 回表到底是什么? 简单来说,回表就是MySQL在…...
HMCL 3.6.17 Minecraft我的世界启动器
描述 HMCL是一个跨平台的Minecraft启动器,支持模组管理,游戏定制,自动安装(Forge、Fabric、Quilt、LiteLoader和OptiFine),Modpack创建,UI定制等。HMCL具有惊人的跨平台功能。 它不仅可以在不同的操作系统上运行,例如Windows,Linux和macOS, 但也支持多种CPU架构,如x…...
用自带的Nginx为gitlab做白名单
修改 /etc/gitlab/gitlab.rb文件vim /etc/gitlab/gitlab.rb如下这种写法不建议用 nginx[custom_gitlab_server_config] = "location ~* (.*) {deny 192.168.1.10;allow 192.168.1.0/24;deny all;proxy_cache off;proxy_pass http://gitlab-workhorse;root html;index …...
XHR/Fetch请求介绍与安全测试
XHR/Fetch请求介绍与安全测试目录XHR/Fetch是什么?所引发的安全问题XHR/Fetch是什么? XHR/Fetch 都是浏览器与服务器进行数据通信(即 API 调用)的两种主要技术。 简单来说,它们都是用来实现 AJAX(Asynchronous JavaScript and XML)理念的技术,即在不重新加载整个页面的…...
能流新智:MyEMS与开源时代的能源感知
在机器轰鸣的工厂、灯火通明的写字楼、冷热交替的数据中心,能量的流动从未停止。而真正理解这些流动,并与之对话,需要一种新的语言和感知能力。MyEMS,作为一个开源能源管理系统,正是这种感知能力的构建者——它让不可见的能源变得可见,让无序的消耗变得可解,最终让能源管…...
普科科技罗氏线圈应用指南:精准掌控电流测量的艺术
普科罗氏线圈以无磁饱和、宽频带、灵活轻便优势,提供高效精准电流测量解决方案。在电力测量、新能源及工业驱动领域,安全、精准地测量电流,尤其是高频、大电流信号,是一项核心需求。传统电流互感器(CT)易饱和、体积大、安装不便的局限性日益凸显。普科科技(PUKY-Tec…...
go mod基础
新建项目 并且新建 mod 管理 mkdir go_study cd go_study && go mod init studygo mod tid 下载依赖以及移除未使用的依赖require github.com/gin-gonic/gin v1.9.0...
go 变量作用域
1...
Oracle笔记:测试update语句关联表扫描的次数
我们的文章会在微信公众号IT民工的龙马人生和博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢! 由于博客中有大量代码,通过页面浏览效果更佳。Oracle笔记:测试update语句关联表扫描的次数下面是测试一下update语句…...