编辑页面

修改后将从当前时间重新计算过期时间
${categoryOptions('major')} ${categoryOptions('minor')} ${categoryOptions('detail')}
`; } if (type === 'qcRaw') { title.textContent = '检测员 · 新增原料检测'; body.innerHTML = `
`; } if (type === 'qcFinished') { const readyProjects = data.projects.filter(hasQualifiedFormula); title.textContent = '检测员 · 新增成品检测'; body.innerHTML = `
成品检测原则:项目开发表里先出现合格实验,配方状态变为“可送样”后,再进入成品检测。
`; } if (type === 'projectTrack') { const stage = currentProjectStage(project); title.textContent = `${project.name} · 新增跟踪动态`; body.innerHTML = `
`; } if (type === 'importExcel') { const tab = projectTabs.find(item => item.id === state.projectTab); const headers = defaultImportHeaders(state.projectTab); state.importDraft = { headers, rows: sampleImportRows(headers), fileName: '模拟导入数据', realRows: false }; title.textContent = `${project.name} · 导入 Excel`; body.innerHTML = `
系统会先读取 Excel 表头,再允许修改表头名称。客户以前的表格字段不统一,也可以先导入,再按公司习惯改名。
识别到的表头,可直接修改 ${tab.title}
${renderHeaderEditor(headers)}
`; renderImportPreview(); } if (type === 'technicalPlan') { const flow = ensureDevelopFlow(project); title.textContent = `${project.name} · 新增技术方案`; body.innerHTML = `
`; } if (type === 'taskAssign') { const flow = ensureDevelopFlow(project); const scheme = flow.schemes.find(item => item.no === state.selectedSchemeNo) || flow.schemes[0]; const leaders = data.people.filter(person => roleName(person) === '项目组长'); const technicians = data.people.filter(person => roleName(person) === '技术员'); const labs = data.people.filter(person => roleName(person) === '实验员'); title.textContent = `${project.name} · 调整任务分配`; body.innerHTML = scheme ? `
` : '
当前项目还没有技术方案,请先由技术主管新增技术方案。
'; } if (type === 'formulaRecord') { const flow = ensureDevelopFlow(project); title.textContent = `${project.name} · 新增配方记录`; body.innerHTML = `
`; } if (['raw', 'finished', 'develop', 'sample', 'summary'].includes(type)) { const flow = ensureDevelopFlow(project); const config = { raw: ['新增原料记录', `
`], finished: ['新增成品记录', `
成品记录应来自开发表里已经合格的实验配方,未合格的项目需要先继续实验。
`], sample: ['新增发样记录', `
样品寄送最适合由项目组长登记:检测员先确认成品检测和出库状态,项目组长负责寄送信息、快递成本和客户反馈跟进。
`], develop: ['新增实验记录', `
实验保存后会自动回写配方状态:合格变为“可送样”,不合格变为“需调整”,检测中保持等待结论。
`], summary: ['新增总结记录', `
`] }[type]; title.textContent = `${project.name} · ${config[0]}`; body.innerHTML = config[1]; } $('#modalForm').reset(); $('#modal').classList.add('open'); const dateInput = body.querySelector('input[type="date"]'); if (dateInput) dateInput.valueAsDate = new Date(); } function closeModal() { $('#modal').classList.remove('open'); state.modal = null; } function saveModal(event) { event.preventDefault(); const form = new FormData(event.currentTarget); const value = Object.fromEntries(form.entries()); if (state.modal === 'person') { const role = roleById(value.roleId) || data.roles[0]; const person = { id: `u${Date.now()}`, name: value.name, roleId: role.id, role: role.name, dept: value.dept, phone: value.phone || '-', status: '在职', scope: role.name === '总经理' ? '全部数据' : '本人参与项目', permissions: role.permissions.slice() }; data.people.push(person); state.selectedPersonId = person.id; addLog('新增人员', person.name, `${role.name} / ${person.dept}`); toast('人员已新增,并按角色生成默认权限'); } if (state.modal === 'role') { const rolePermissions = form.getAll('rolePermission'); const role = { id: `role-${Date.now()}`, name: value.name, desc: value.desc || '客户自定义角色', permissions: rolePermissions.length ? rolePermissions : ['projectView'] }; data.roles.push(role); addLog('新增角色', role.name, `权限 ${role.permissions.length} 项`); toast('自定义角色已新增,可分配给人员'); } if (state.modal === 'project') { const project = { id: `p${Date.now()}`, no: `XM-2026-${String(data.projects.length + 1).padStart(3, '0')}`, name: value.name, customer: value.customer, categoryMajor: value.categoryMajor, categoryMinor: value.categoryMinor, categoryDetail: value.categoryDetail, category: `${value.categoryMajor} / ${value.categoryMinor} / ${value.categoryDetail}`, owner: value.owner, status: '研发中', target: value.target, raw: [], finished: [], develop: [], summary: [] }; data.projects.unshift(project); state.selectedProjectId = project.id; state.categoryFilter = { level: 'detail', major: project.categoryMajor, minor: project.categoryMinor, detail: project.categoryDetail }; addProjectTrack(project, '项目已创建,等待技术主管评估和建立维度标准', '确认项目维度、标准和负责人', value.owner, '需求接收'); addLog('新增项目', project.name, `负责人:${project.owner}`); toast('项目已新增,四张表已自动建立'); } if (state.modal === 'importExcel') { const project = selectedProject(); const fields = collectImportFields(); if (!fields.length) { toast('请至少保留一个表头'); return; } const fieldNames = fields.map(field => field.name); const uniqueHeaders = fieldNames.map((header, index) => { const sameBefore = fieldNames.slice(0, index).filter(item => item === header).length; return sameBefore ? `${header}_${sameBefore + 1}` : header; }); const sourceRows = state.importDraft?.realRows ? (state.importDraft.rows || []) : state.importDraft?.rows?.length ? state.importDraft.rows : sampleImportRows(fields.map(field => field.original)); const rows = sourceRows.map(sourceRow => { const row = {}; fields.forEach((field, index) => { row[uniqueHeaders[index]] = sourceRow[field.original] ?? sourceRow[field.name] ?? ''; }); return row; }); const fileName = document.querySelector('input[name="excelFile"]')?.files?.[0]?.name || state.importDraft?.fileName || '在线Excel粘贴数据'; ensureImports(project)[state.projectTab].push({ fileName, headers: uniqueHeaders, hiddenHeaders: fields.map((field, index) => field.hidden ? uniqueHeaders[index] : null).filter(Boolean), rows }); const tab = projectTabs.find(item => item.id === state.projectTab); addProjectTrack(project, `导入 ${tab.title}:${fileName},共 ${rows.length} 行数据`, '核对导入数据并补充缺失字段', currentUser().name, currentProjectStage(project)); addLog('导入Excel', project.name, `${tab.title} / ${fileName} / ${uniqueHeaders.length} 个自定义表头 / ${rows.length} 行数据`); toast('Excel 数据已导入,表头和字段显示已按自定义配置生成'); } if (state.modal === 'projectTrack') { const project = selectedProject(); addProjectTrack(project, value.content, value.next, value.owner, value.stage, value.risk || null); project.stage = value.stage; addLog('新增项目跟踪', project.name, `${value.stage} / ${value.owner}`); toast('项目跟踪动态已新增,刘经理看板卡点同步更新'); } if (state.modal === 'technicalPlan') { const project = selectedProject(); const flow = ensureDevelopFlow(project); flow.schemes.push({ no: value.no, name: value.name, source: value.source, leader: value.leader, technician: value.technician, experimenter: value.experimenter || '李实验员', status: '待执行', target: value.target }); refreshDevelopFlowStatus(flow); addProjectTrack(project, `新增技术方案 ${value.no}:${value.name}`, `由 ${value.leader} 统筹,${value.technician} 推进配方,${value.experimenter || '李实验员'} 执行实验`, value.source, '项目开发'); addLog('新增技术方案', project.name, `${value.no} / ${value.name}`); toast('技术方案已新增,并分配到组长、技术员和实验员'); } if (state.modal === 'taskAssign') { const project = selectedProject(); const flow = ensureDevelopFlow(project); const scheme = flow.schemes.find(item => item.no === value.schemeNo); if (!scheme) { toast('当前项目还没有可分配的技术方案'); return; } scheme.leader = value.leader; scheme.technician = value.technician; scheme.experimenter = value.experimenter; refreshDevelopFlowStatus(flow); addProjectTrack(project, `调整方案 ${scheme.no} 分配`, `${value.technician} 负责配方,${value.experimenter} 负责实验;${value.note}`, currentUser().name, '项目开发'); addLog('调整任务分配', project.name, `${scheme.no} / 组长:${value.leader} / 技术员:${value.technician} / 实验员:${value.experimenter}`, currentUser().name); toast('任务分配已更新,项目跟踪已同步留痕'); } if (state.modal === 'formulaRecord') { const project = selectedProject(); const flow = ensureDevelopFlow(project); if (!flow.schemes.length) { toast('请先新增技术方案,再新增配方记录'); return; } flow.formulas.push({ version: value.version, schemeNo: value.schemeNo, designer: value.designer, purpose: value.purpose, composition: value.composition, status: '待实验' }); refreshDevelopFlowStatus(flow); addProjectTrack(project, `新增配方 ${value.version},关联方案 ${value.schemeNo}`, '按该配方安排实验并记录维度结果', value.designer, '项目开发'); addLog('新增配方记录', project.name, `${value.version} / 关联方案 ${value.schemeNo}`); toast('配方记录已新增,可用于后续实验'); } if (state.modal === 'develop') { const project = selectedProject(); const flow = ensureDevelopFlow(project); if (!value.formulaVersion || !flow.formulas.some(item => item.version === value.formulaVersion)) { toast('请先新增配方记录,再填写实验记录'); return; } const formula = flow.formulas.find(item => item.version === value.formulaVersion); const experiment = { no: value.no, formulaVersion: value.formulaVersion, schemeNo: formula?.schemeNo || '-', experimenter: value.experimenter, recordBy: value.recordBy, date: value.date, dimensions: value.dimensions, result: value.result, conclusion: value.reason }; flow.experiments.push(experiment); refreshDevelopFlowStatus(flow); project.develop.push({ version: value.formulaVersion, date: value.date, owner: value.experimenter, dimensions: value.dimensions, result: value.result, reason: value.reason }); addProjectTrack(project, `新增实验 ${value.no},结果:${value.result}`, value.result === '合格' ? '安排检测和发样判断' : '保留失败原因并继续调整配方', value.experimenter, value.result === '合格' ? '检测中' : '实验中', value.result === '不合格' ? value.reason : null); addLog('新增实验记录', project.name, `${value.no} / 使用配方 ${value.formulaVersion}`); toast(value.result === '合格' ? '实验记录已新增,关联配方已变为可送样' : '实验记录已新增,并关联到配方版本'); } if (state.modal === 'sample') { const project = selectedProject(); if (!hasQualifiedFormula(project)) { toast('请先在开发表中形成合格实验,再新增发样记录'); return; } data.sampleShipments.push({ id: value.id, projectId: project.id, sample: value.sample, owner: value.owner, date: value.date, weight: Number(value.weight || 0), materialCost: Number(value.materialCost || 0), packageCost: Number(value.packageCost || 0), expressCost: Number(value.expressCost || 0), result: value.result }); addProjectTrack(project, `新增发样 ${value.sample},样品成本 ${money(Number(value.materialCost || 0) + Number(value.packageCost || 0) + Number(value.expressCost || 0))}`, '跟进客户反馈并判断是否复调或总结', value.owner, '发样中'); addLog('新增发样记录', project.name, `${value.sample} / 成本 ${money(Number(value.materialCost || 0) + Number(value.packageCost || 0) + Number(value.expressCost || 0))}`); toast('发样记录已新增,首页样品成本已联动'); } if (state.modal === 'qcRaw') { const project = projectById(value.projectId) || selectedProject(); project.raw.push({ material: value.material, supplier: value.supplier, batch: value.batch, date: value.date, item: value.item, standard: value.standard, value: value.value, result: value.result, inbound: value.inbound, note: value.note || '-' }); state.selectedProjectId = project.id; addProjectTrack(project, `新增原料检测:${value.material} / ${value.result}`, value.result === '合格' ? '允许入库后继续推进实验或生产验证' : '处理异常批次并安排复检', '王检', '检测中', value.result === '不合格' ? value.note || '原料检测不合格' : null); addLog('新增原料检测', project.name, `${value.material} / ${value.batch} / ${value.result}`, '王检'); toast('原料检测已新增,项目原料表和刘经理看板已联动'); } if (state.modal === 'qcFinished') { const project = projectById(value.projectId) || selectedProject(); if (!hasQualifiedFormula(project)) { toast('请先在开发表中形成合格实验,再新增成品检测'); return; } project.finished.push({ sample: value.sample, date: value.date, item: value.item, standard: value.standard, value: value.value, result: value.result, canShip: value.canShip, feedback: value.feedback || '-' }); state.selectedProjectId = project.id; addProjectTrack(project, `新增成品检测:${value.sample} / ${value.result}`, value.result === '合格' ? '确认是否发样并等待客户反馈' : '记录异常原因并回到配方调整', '王检', value.result === '合格' ? '发样中' : '检测中', value.result === '不合格' ? value.feedback || '成品检测不合格' : null); addLog('新增成品检测', project.name, `${value.sample} / ${value.result}`, '王检'); toast('成品检测已新增,项目成品表和刘经理看板已联动'); } if (['raw', 'finished', 'summary'].includes(state.modal)) { const project = selectedProject(); if (state.modal === 'finished' && !hasQualifiedFormula(project)) { toast('请先在开发表中形成合格实验,再新增成品记录'); return; } project[state.modal].push(value); if (state.modal === 'summary') addProjectTrack(project, '新增项目总结,项目经验已沉淀', '归档总结并复用到同类项目', value.owner, '总结完成'); if (state.modal === 'raw') addProjectTrack(project, `新增原料记录:${value.material || '原料'} / ${value.result || '-'}`, '根据原料检测结论判断是否入库', currentUser().name, '技术评估'); if (state.modal === 'finished') addProjectTrack(project, `新增成品记录:${value.sample || '样品'} / ${value.result || '-'}`, '判断是否可发样或出库', currentUser().name, '检测中'); addLog('新增项目记录', project.name, projectTabs.find(t => t.id === state.modal).title); toast('项目记录已新增'); } normalizeData(); state.aiAnalysisKey = null; persistData(); closeModal(); render(); } document.addEventListener('click', (event) => { const pageBtn = event.target.closest('[data-page]'); if (pageBtn) setPage(pageBtn.dataset.page); const person = event.target.closest('[data-select-person]'); if (person) { state.selectedPersonId = person.dataset.selectPerson; render(); } const project = event.target.closest('[data-select-project]'); if (project) { state.selectedProjectId = project.dataset.selectProject; state.aiAnalysisKey = null; render(); } const categoryFilter = event.target.closest('[data-category-level]'); if (categoryFilter) { const level = categoryFilter.dataset.categoryLevel; state.categoryFilter = level === 'all' ? { level: 'all' } : { level, major: categoryFilter.dataset.major || '', minor: categoryFilter.dataset.minor || '', detail: categoryFilter.dataset.detail || '' }; render(); return; } const openProject = event.target.closest('[data-open-project]'); if (openProject) { state.page = 'projects'; state.selectedProjectId = openProject.dataset.openProject; state.aiAnalysisKey = null; render(); } const projectModal = event.target.closest('[data-project-modal]'); if (projectModal) { const projectId = projectModal.dataset.projectId; if (projectId) state.selectedProjectId = projectId; if (projectModal.dataset.projectTabTarget) { state.projectTab = projectModal.dataset.projectTabTarget; state.aiAnalysisKey = null; } openModal(projectModal.dataset.projectModal); return; } const assignScheme = event.target.closest('[data-assign-scheme]'); if (assignScheme) { if (assignScheme.dataset.projectId) state.selectedProjectId = assignScheme.dataset.projectId; state.selectedSchemeNo = assignScheme.dataset.assignScheme; openModal('taskAssign'); return; } const tab = event.target.closest('[data-project-tab]'); if (tab) { state.projectTab = tab.dataset.projectTab; state.aiAnalysisKey = null; render(); } const runAi = event.target.closest('[data-run-ai-analysis]'); if (runAi) { state.aiAnalysisKey = runAi.dataset.runAiAnalysis; toast('AI分析已生成'); render(); return; } const open = event.target.closest('[data-open-modal]'); if (open) openModal(open.dataset.openModal); const addRow = event.target.closest('[data-add-row]'); if (addRow) openModal(addRow.dataset.addRow); const editImport = event.target.closest('[data-edit-import]'); if (editImport) { const project = projectById(editImport.dataset.importProject) || selectedProject(); startImportEdit(project, editImport.dataset.importType, Number(editImport.dataset.editImport)); render(); return; } if (event.target.closest('[data-cancel-import-edit]')) { state.importEdit = null; toast('已取消编辑'); render(); return; } if (event.target.closest('[data-save-import-edit]')) { if (!state.importEdit) return; const project = projectById(state.importEdit.projectId) || selectedProject(); const imported = ensureImports(project)[state.importEdit.type] || []; const draft = readImportEditorDraft(); draft.rows = (draft.rows || []).filter(row => Object.values(row).some(value => String(value || '').trim())); imported[state.importEdit.batchIndex] = draft; addProjectTrack(project, `编辑导入表格:${draft.fileName || 'Excel导入数据'}`, '核对字段和数据是否符合客户Excel习惯', currentUser().name, currentProjectStage(project)); addLog('编辑导入表格', project.name, `${projectTabs.find(tab => tab.id === state.importEdit.type)?.title || state.importEdit.type} / ${draft.headers.length} 列 / ${draft.rows.length} 行`); state.importEdit = null; state.aiAnalysisKey = null; persistData(); toast('导入表格已保存'); render(); return; } if (event.target.closest('[data-add-import-column]')) { if (!state.importEdit) return; const draft = readImportEditorDraft(); const nextHeaders = uniqueHeaders([...draft.headers, `自定义字段${draft.headers.length + 1}`]); const nextName = nextHeaders[nextHeaders.length - 1]; draft.headers.push(nextName); draft.rows.forEach(row => { row[nextName] = ''; }); state.importEdit.batch = draft; render(); return; } if (event.target.closest('[data-add-import-row]')) { if (!state.importEdit) return; const draft = readImportEditorDraft(); const row = {}; draft.headers.forEach(header => { row[header] = ''; }); draft.rows.push(row); state.importEdit.batch = draft; render(); return; } const deleteImportColumn = event.target.closest('[data-delete-import-column]'); if (deleteImportColumn) { if (!state.importEdit) return; const draft = readImportEditorDraft(); const colIndex = Number(deleteImportColumn.dataset.deleteImportColumn); if (draft.headers.length <= 1) { toast('至少保留一列表头'); return; } const header = draft.headers[colIndex]; const hasData = draft.rows.some(row => String(row[header] || '').trim()); if (hasData) { toast('本列已有数据,不能删除;请先清空本列数据或选择隐藏'); return; } draft.headers.splice(colIndex, 1); draft.hiddenHeaders = (draft.hiddenHeaders || []).filter(item => item !== header); draft.rows = draft.rows.map(row => { const next = {}; draft.headers.forEach(item => { next[item] = row[item] || ''; }); return next; }); state.importEdit.batch = draft; render(); return; } const removeImport = event.target.closest('[data-remove-import]'); if (removeImport) { const project = projectById(removeImport.dataset.importProject) || selectedProject(); const type = removeImport.dataset.importType; const index = Number(removeImport.dataset.removeImport); const imported = ensureImports(project)[type] || []; const removed = imported.splice(index, 1)[0]; addProjectTrack(project, `删除导入数据:${removed?.fileName || 'Excel导入数据'}`, '重新核对并导入正确数据', currentUser().name, currentProjectStage(project)); addLog('删除导入数据', project.name, `${projectTabs.find(tab => tab.id === type)?.title || type} / ${removed?.fileName || '-'}`); state.aiAnalysisKey = null; persistData(); toast('已删除本次导入数据'); render(); return; } const addImportHeader = event.target.closest('[data-add-import-header]'); if (addImportHeader) { const fields = collectImportFields(); const nextHeader = `自定义字段${fields.length + 1}`; $('#importHeaders')?.insertAdjacentHTML('beforeend', renderHeaderEditor([nextHeader])); const count = $('#importHeaderCount'); if (count) count.value = `${fields.length + 1} 个表头`; renderImportPreview(); return; } const removeHeader = event.target.closest('[data-remove-header]'); if (removeHeader) { const item = removeHeader.closest('[data-header-item]'); const input = item?.querySelector('input[name="importHeader"]'); const originalHeader = input?.dataset.originalHeader || ''; const currentHeader = input?.value.trim() || originalHeader; if (importColumnHasData(originalHeader, currentHeader)) { toast('本列已有数据,不能删除;如不想展示可勾选隐藏字段'); return; } item?.remove(); const fields = collectImportFields(); const count = $('#importHeaderCount'); if (count) count.value = `${fields.length} 个表头`; renderImportPreview(); return; } if (event.target.closest('[data-close-modal]')) closeModal(); const applyRole = event.target.closest('[data-apply-role]'); if (applyRole) { const selected = data.people.find(p => p.id === state.selectedPersonId); const role = roleById(applyRole.dataset.applyRole); if (!selected || !role) return; if (roleName(selected) === '总经理') { toast('总经理默认拥有全部权限'); return; } selected.roleId = role.id; selected.role = role.name; selected.permissions = role.permissions.slice(); selected.scope ||= role.name === '总经理' ? '全部数据' : '本人参与项目'; addLog('应用角色', selected.name, `${role.name} / 权限 ${role.permissions.length} 项`); normalizeData(); persistData(); toast('角色已应用给当前人员'); render(); } if (event.target.closest('[data-save-permissions]')) { const selected = data.people.find(p => p.id === state.selectedPersonId); if (!selected || roleName(selected) === '总经理') { toast('总经理默认拥有全部权限'); return; } const selectedRoleId = document.querySelector('[data-role-select]')?.value; const selectedRole = roleById(selectedRoleId); if (selectedRole) { selected.roleId = selectedRole.id; selected.role = selectedRole.name; } selected.scope = document.querySelector('[data-scope-select]')?.value || selected.scope || '本人参与项目'; selected.permissions = Array.from(document.querySelectorAll('#view .perm input:checked')).map(input => input.value); addLog('调整人员权限', selected.name, `${roleName(selected)} / ${selected.scope} / 当前权限:${selected.permissions.map(key => permissionLabels[key]).join('、') || '无'}`); normalizeData(); persistData(); toast('权限已保存,操作记录已同步'); render(); } }); document.addEventListener('input', (event) => { if (event.target.matches('input[name="importHeader"], input[name="hiddenHeader"]')) { renderImportPreview(); return; } if (event.target.matches('[data-paste-excel]')) { const parsed = parseImportText(event.target.value); if (parsed.headers.length) { updateHeaderEditor(parsed.headers, parsed.rows, '在线Excel粘贴数据', true); toast(`已识别 ${parsed.headers.length} 个表头、${parsed.rows.length} 行数据`); } } }); document.addEventListener('change', async (event) => { if (event.target.matches('[data-viewer-select]')) { state.currentUserId = event.target.value; const viewer = currentUser(); const roleDefaultPage = defaultPageForRole(viewer); const firstPage = visibleNavItems()[0]; state.page = visibleNavItems().some(item => item.id === roleDefaultPage) ? roleDefaultPage : firstPage?.id || 'projects'; addLog('切换演示角色', viewer.name, `${roleName(viewer)} / ${viewer.scope || viewer.dept}`, viewer.name); toast(`已切换到:${viewer.name} / ${roleName(viewer)}`); render(); return; } if (event.target.matches('[data-role-select]')) { const selected = data.people.find(p => p.id === state.selectedPersonId); const role = roleById(event.target.value); if (!selected || !role || roleName(selected) === '总经理') return; selected.roleId = role.id; selected.role = role.name; selected.permissions = role.permissions.slice(); selected.scope ||= '本人参与项目'; addLog('切换人员角色', selected.name, `${role.name} / 权限 ${role.permissions.length} 项`); normalizeData(); persistData(); toast('已按角色模板刷新权限'); render(); return; } if (!event.target.matches('input[name="excelFile"]')) return; const file = event.target.files?.[0]; if (!file) return; const fallbackHeaders = defaultImportHeaders(state.projectTab); const lowerName = file.name.toLowerCase(); if (lowerName.endsWith('.xlsx')) { try { const tab = projectTabs.find(item => item.id === state.projectTab); const parsed = await parseXlsxFile(file, tab?.title || ''); updateHeaderEditor(parsed.headers.length ? parsed.headers : fallbackHeaders, parsed.rows, `${file.name} / ${parsed.sheetName}`, true); toast(`已读取工作表“${parsed.sheetName}”:${parsed.headers.length} 个表头、${parsed.rows.length} 行数据`); } catch (error) { console.warn('Excel 解析失败', error); updateHeaderEditor(fallbackHeaders, [], file.name, true); toast(error.message || 'Excel 解析失败,请复制表格区域后粘贴导入'); } return; } if (!lowerName.endsWith('.csv') && !lowerName.endsWith('.txt')) { updateHeaderEditor(fallbackHeaders, [], file.name, true); toast('当前 demo 支持 .xlsx/.csv/.txt;老版 .xls 请另存为 .xlsx 后导入'); return; } const reader = new FileReader(); reader.onload = () => { const parsed = parseImportText(reader.result); updateHeaderEditor(parsed.headers.length ? parsed.headers : fallbackHeaders, parsed.rows.length ? parsed.rows : sampleImportRows(fallbackHeaders), file.name, parsed.rows.length > 0); toast(`已读取 ${parsed.headers.length || fallbackHeaders.length} 个表头、${parsed.rows.length || 2} 行数据`); }; reader.readAsText(file, 'utf-8'); }); $('#modalForm').addEventListener('submit', saveModal); $('#modal').addEventListener('click', (event) => { if (event.target.id === 'modal') closeModal(); }); hydrateSavedData(); normalizeData(); persistData(); render();
取消
访问码: egp4miky