未设置项目
//========================================================== // 视图: 项目管理(多项目/快照/导入导出) //========================================================== function renderProjects() { const all = loadAll(); const projects = Object.values(all.projects || {}).sort((a, b) => b.updatedAt - a.updatedAt); const currentId = getCurrentProjectId(); return ` ${viewHeader('项目管理', '新建/切换/删除项目,快照备份,JSON导入导出。数据仅保存在当前浏览器的 localStorage,强烈建议定期导出备份。', [{name:'首页',route:'dashboard'},{name:'项目管理'}])}
项目列表
${projects.map(p => { const totalT = (p.tasks?.completed?.length) ? p.tasks.completed.length : 0; return ` `; }).join('')}
项目名 创建时间 最近更新 进度 SKU数 操作
${escapeHtml(p.name)}${p.id === currentId ? ' 当前' : ''} ${formatDate(p.createdAt)} ${formatDate(p.updatedAt)} ${totalT} 任务 ${(p.skus || []).length} ${p.id !== currentId ? `` : ''}
快照(当前项目)
快照用于记录某个时刻的完整状态,将来可恢复。建议在重大节点(完成第一阶段任务、首单成交等)保存快照。
${(STATE.snapshots || []).length === 0 ? '
尚无快照
' : ` ${STATE.snapshots.slice().reverse().map(sn => ` `).join('')}
名称时间操作
${escapeHtml(sn.name)} ${formatDate(sn.timestamp)}
`}
测算结果历史
${(STATE.calcResults || []).length === 0 ? '
尚无保存的测算结果
' : ` ${STATE.calcResults.slice().reverse().map(r => ` `).join('')}
SKU出厂价Temu价美亚价时间
${escapeHtml(r.name)} ¥${r.costRMB} $${r.temuPrice} $${r.amzPrice} ${formatDate(r.timestamp)}
`}
⚠ 数据安全提示
所有数据保存在当前浏览器的 localStorage(本地存储)中。如果你清理浏览器缓存、换浏览器、换电脑,数据会丢失。请每周至少导出一次 JSON 作为备份
`; } function newProject() { showModal(`
`); setTimeout(() => document.getElementById('np_name').focus(), 50); } function doNewProject() { const name = document.getElementById('np_name').value.trim() || '新建项目'; const p = emptyProject(name); const all = loadAll(); all.projects[p.id] = p; saveAll(all); setCurrentProjectId(p.id); STATE = p; closeModal(); nav('dashboard'); updateProjLabel(); } function switchProject(pid) { const all = loadAll(); if (all.projects[pid]) { setCurrentProjectId(pid); STATE = all.projects[pid]; updateProjLabel(); render(); } } function renameProject(pid) { const all = loadAll(); const p = all.projects[pid]; if (!p) return; showModal(`
`); } function doRename(pid) { const name = document.getElementById('rn_name').value.trim(); if (!name) return; const all = loadAll(); all.projects[pid].name = name; all.projects[pid].updatedAt = Date.now(); saveAll(all); if (pid === STATE.id) { STATE.name = name; STATE.updatedAt = Date.now(); } closeModal(); render(); } function deleteProject(pid) { const all = loadAll(); const count = Object.keys(all.projects).length; if (count <= 1) { alert('至少保留一个项目'); return; } confirm2('确认删除此项目?此操作不可恢复,数据将永久丢失。', `doDeleteProject('${pid}')`); } function doDeleteProject(pid) { const all = loadAll(); delete all.projects[pid]; saveAll(all); if (pid === STATE.id) { // 切换到其他项目 const any = Object.keys(all.projects)[0]; setCurrentProjectId(any); STATE = all.projects[any]; updateProjLabel(); } render(); } function createSnapshot() { showModal(`
`); setTimeout(() => document.getElementById('sn_name').focus(), 50); } function doCreateSnapshot() { const name = document.getElementById('sn_name').value.trim() || ('快照 ' + formatDate(Date.now())); // 深拷贝当前状态(排除snapshots自身) const payload = JSON.parse(JSON.stringify(STATE)); delete payload.snapshots; const sn = { id: newId(), name, timestamp: Date.now(), payload }; if (!STATE.snapshots) STATE.snapshots = []; STATE.snapshots.push(sn); persist(); closeModal(); render(); } function restoreSnapshot(snId) { confirm2('恢复快照会覆盖当前所有数据(除快照本身)。建议先创建新快照保存当前状态。确认恢复?', `doRestore('${snId}')`); } function doRestore(snId) { const sn = STATE.snapshots.find(x => x.id === snId); if (!sn) return; const keepSnapshots = STATE.snapshots; const keepId = STATE.id; const keepName = STATE.name; Object.assign(STATE, JSON.parse(JSON.stringify(sn.payload))); STATE.snapshots = keepSnapshots; STATE.id = keepId; STATE.name = keepName; persist(); render(); } function deleteSnapshot(snId) { STATE.snapshots = STATE.snapshots.filter(x => x.id !== snId); persist(); render(); } //========================================================== // 导入导出 //========================================================== function exportJSON() { const data = JSON.stringify(STATE, null, 2); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `chuhai_${STATE.name.replace(/[^\w\u4e00-\u9fa5]/g,'_')}_${formatDate(Date.now()).replace(/[: ]/g,'_')}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function exportAllJSON() { const all = loadAll(); const data = JSON.stringify(all, null, 2); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `chuhai_all_projects_${formatDate(Date.now()).replace(/[: ]/g,'_')}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function importJSON(evt) { const file = evt.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const data = JSON.parse(e.target.result); if (data.projects) { // 全项目 JSON const all = loadAll(); let added = 0; Object.entries(data.projects).forEach(([id, p]) => { p.id = newId(); p.name = p.name + ' (导入)'; all.projects[p.id] = p; added++; }); saveAll(all); alert(`成功导入 ${added} 个项目`); } else if (data.id && data.setup) { // 单项目 JSON const all = loadAll(); data.id = newId(); data.name = data.name + ' (导入)'; all.projects[data.id] = data; saveAll(all); setCurrentProjectId(data.id); STATE = data; alert('项目已导入并切换'); } else { alert('文件格式不识别'); } render(); } catch (err) { alert('导入失败: ' + err.message); } }; reader.readAsText(file); evt.target.value = ''; // 重置 } //========================================================== // 视图: 任务详情(占位,暂整合在任务列表里) //========================================================== function renderTaskDetail(params) { return renderTasks(); } //========================================================== // 启动 //========================================================== loadCurrentProject(); updateProjLabel(); render(); // 提示:如果是首次打开,在首页显示引导 if (!STATE.setup.primaryPath && (!STATE.tasks.completed || STATE.tasks.completed.length === 0)) { setTimeout(() => { if (!location.hash || location.hash === '#/dashboard') { // 首次打开,引导去设置 } }, 100); }