Compare commits

...

1 Commits

2 changed files with 48 additions and 9 deletions

View File

@@ -176,7 +176,7 @@ window.switchTab = switchTab;
function renderProjects() { function renderProjects() {
const items = state.data.operations; const items = state.data.operations;
const rows = items.map((x) => [ const rows = items.map((x) => [
`<strong>${x.project_name}</strong><p class="text-xs text-slate-500">${x.project_version}</p>`, `<strong>${x.project_name}</strong>`,
text(x.customer_need || x.notes), text(x.customer_need || x.notes),
badge(x.current_stage || x.project_status), badge(x.current_stage || x.project_status),
x.expected_contract_amount ? money(x.expected_contract_amount) : "—", x.expected_contract_amount ? money(x.expected_contract_amount) : "—",
@@ -186,7 +186,6 @@ function renderProjects() {
document.querySelector("#projects").innerHTML = `<div class="grid gap-4"> document.querySelector("#projects").innerHTML = `<div class="grid gap-4">
${card(formHtml([ ${card(formHtml([
{ label: "项目名称", input: `<input name="project_name" required>` }, { label: "项目名称", input: `<input name="project_name" required>` },
{ label: "项目版本", input: `<input name="project_version" value="v1.0">` },
{ label: "当前阶段", input: `<select name="current_stage"><option>项目准备</option><option></option><option></option><option></option></select>` }, { label: "当前阶段", input: `<select name="current_stage"><option>项目准备</option><option></option><option></option><option></option></select>` },
{ label: "项目金额", input: `<input name="expected_contract_amount" type="number" step="0.01" placeholder="万元">` }, { label: "项目金额", input: `<input name="expected_contract_amount" type="number" step="0.01" placeholder="万元">` },
{ label: "负责人", input: `<input name="owner">` }, { label: "负责人", input: `<input name="owner">` },
@@ -199,11 +198,12 @@ function showTaskModal(projectId) {
const project = state.data.operations.find((x) => x.id === projectId); const project = state.data.operations.find((x) => x.id === projectId);
const tasks = (state.data.tasks || []).filter((t) => t.project_id === projectId); const tasks = (state.data.tasks || []).filter((t) => t.project_id === projectId);
const phases = ["项目准备", "项目执行", "项目验收", "验收完毕"]; const phases = ["项目准备", "项目执行", "项目验收", "验收完毕"];
document.querySelector("#taskModal").innerHTML = `<div class="task-overlay" onclick="closeTaskModal()"><div class="task-panel" onclick="event.stopPropagation()"><div class="flex items-center justify-between border-b border-slate-200 px-6 py-4"><h2 class="text-lg font-bold">${project.project_name} · 任务清单</h2><button class="btn btn-ghost btn-sm" onclick="closeTaskModal()">关闭</button></div><div class="grid gap-4 p-6">${phases.map((phase) => { document.querySelector("#taskModal").innerHTML = `<div class="task-overlay" onclick="closeTaskModal()"><div class="task-panel" onclick="event.stopPropagation()"><div class="task-header"><h2 class="task-title">${project.project_name} · 任务清单</h2><button class="task-close" onclick="closeTaskModal()"><i data-lucide="x"></i></button></div><div class="task-body">${phases.map((phase) => {
const pt = tasks.filter((t) => t.phase === phase); const pt = tasks.filter((t) => t.phase === phase);
return `<div class="rounded-lg border border-slate-200"><div class="rounded-t-lg bg-slate-50 px-4 py-2.5 font-semibold text-slate-700 text-sm">${phase}${pt.length ? ` <span class="text-slate-400 font-normal">(${pt.filter(t=>t.task).length}/${pt.length})</span>` : ""}</div><div class="p-3 grid gap-2">${pt.length ? pt.map((t) => `<div class="flex items-start gap-3 rounded-md bg-white px-3 py-2 text-sm"><input type="checkbox" class="mt-0.5" ${t.task ? "" : "checked disabled"}><div class="flex-1"><p class="text-slate-800">${t.milestone ? `<strong>${t.milestone}</strong>` : ""}${t.task}</p>${t.owner ? `<p class="text-xs text-slate-400 mt-0.5">👤 ${t.owner}${t.due_date ? " · 📅 " + t.due_date : ""}</p>` : ""}</div></div>`).join("") : `<p class="text-sm text-slate-400 py-2 text-center">暂无任务</p>`}</div></div>`; return `<div class="task-group"><div class="task-group-hd"><span class="task-group-icon"><i data-lucide="layers"></i></span><span class="task-group-label">${phase}</span><span class="task-group-n">${pt.length}</span></div><div class="task-group-list">${pt.length ? pt.map((t) => `<div class="task-row" data-id="${t.id}"><span class="task-dot"><i data-lucide="${t.status === 'done' ? 'check-circle' : 'circle'}"></i></span><span class="task-name">${t.task}</span></div>`).join("") : `<div class="task-none">暂无任务</div>`}</div><button class="task-group-add" onclick="event.stopPropagation()">+ 添加任务</button></div>`;
}).join("")}</div></div></div>`; }).join("")}</div></div></div>`;
document.querySelector("#taskModal").classList.add("active"); document.querySelector("#taskModal").classList.add("active");
if (window.lucide) window.lucide.createIcons();
} }
window.closeTaskModal = () => document.querySelector("#taskModal").classList.remove("active"); window.closeTaskModal = () => document.querySelector("#taskModal").classList.remove("active");

View File

@@ -490,15 +490,54 @@ td {
margin: 4px 0; margin: 4px 0;
} }
/* Task Modal */ /* Task Modal — Plane style */
.task-modal { display: none; } .task-modal { display: none; }
.task-modal.active { display: block; } .task-modal.active { display: block; }
.task-overlay { .task-overlay {
position: fixed; inset: 0; background: rgba(15,23,42,0.4); z-index: 200; position: fixed; inset: 0; background: rgba(10,12,16,0.5); z-index: 200;
display: flex; align-items: flex-start; justify-content: center; display: flex; align-items: flex-start; justify-content: center;
padding-top: 60px; overflow-y: auto; padding-top: 48px; overflow-y: auto;
} }
.task-panel { .task-panel {
background: white; border-radius: 12px; width: 800px; max-width: 90vw; background: #18191c; border-radius: 12px; width: 600px; max-width: 92vw;
box-shadow: 0 20px 60px rgba(0,0,0,0.15); margin-bottom: 60px; box-shadow: 0 24px 80px rgba(0,0,0,0.4); margin-bottom: 48px;
} }
.task-header {
display: flex; align-items: center; justify-content: space-between;
padding: 16px 20px; border-bottom: 1px solid #2a2d34;
}
.task-title { color: #e4e5e7; font-size: 15px; font-weight: 600; }
.task-close {
color: #6b6d75; background: none; border: none; cursor: pointer;
padding: 4px; border-radius: 6px; display: flex;
}
.task-close:hover { color: #e4e5e7; background: #2a2d34; }
.task-body { padding: 20px; display: flex; flex-direction: column; gap: 16px; }
.task-group {
background: #1e2025; border-radius: 8px;
border: 1px solid #2a2d34; overflow: hidden;
}
.task-group-hd {
display: flex; align-items: center; gap: 8px;
padding: 10px 14px;
}
.task-group-icon { color: #6b6d75; display: flex; }
.task-group-label { color: #aeafb4; font-size: 13px; font-weight: 600; }
.task-group-n {
background: #2a2d34; color: #6b6d75; font-size: 11px;
padding: 1px 7px; border-radius: 10px;
}
.task-group-list { display: flex; flex-direction: column; }
.task-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 14px; border-top: 1px solid #24272d;
}
.task-dot { display: flex; color: #4b4d54; }
.task-name { color: #c5c6ca; font-size: 13px; }
.task-none { color: #4b4d54; font-size: 13px; padding: 12px 14px; text-align: center; border-top: 1px solid #24272d; }
.task-group-add {
display: block; width: 100%; padding: 10px; text-align: center;
color: #6b6d75; font-size: 13px; background: none; border: none;
border-top: 1px solid #24272d; cursor: pointer;
}
.task-group-add:hover { color: #e4e5e7; background: #24272d; }