Compare commits

...

2 Commits

Author SHA1 Message Date
mac
c26cbb3cf4 perf: 全项目批量优化-16处load()改为轻量刷新
- plan.js: createPlanFinance/deletePlanItem/planInlineUpdate
- finance.js: createFinance/deleteFinanceItem
- drawer.js: deleteDrawerItem/submitComment/deleteActivity
- products.js: createResource/submitProductDrawer
- projects.js: duplicateProject/submitTaskForm
- proposals.js: ensureStandardProposals/submitStandardComment/submitProposal
- plan_expense.js: savePlanExpense/deletePlanExpItem(上版本已修)
- 所有保存/删除/编辑后改为/api/<resource>/list+单模块render
- 仅保留app.js初始加载和switchTenant切换工作台的全量load()
- 版本号 v1.0.0.38
2026-07-09 16:20:21 +08:00
mac
296917456c perf: 计划费用保存/删除性能优化
- 后端新增/api/<resource>/list轻量级列表接口
- plan_expense.js保存/删除后改为轻量刷新,不再调load()
- 避免全量bootstrap+7模块渲染,只刷新费用列表
- 版本号 v1.0.0.37
2026-07-09 16:03:30 +08:00
9 changed files with 58 additions and 21 deletions

View File

@@ -666,6 +666,26 @@ def bootstrap():
# ---------- 通用 CRUD ----------
@bp.route("/api/<resource>/list", methods=["GET"])
@login_required
def list_resource(resource):
"""轻量级列表接口,只返回指定 tenant 的记录"""
if resource not in TABLES:
return jsonify({"error": "unknown resource"}), 404
table, _ = TABLES[resource]
tenant = request.args.get("tenant", session.get("tenants", [""])[0])
allowed = session.get("tenants", [])
allowed = [t if t != "总工作台" else "总览" for t in allowed]
allowed = [t if t != "学会·无界" else "学术·无界" for t in allowed]
if tenant not in allowed:
tenant = allowed[0] if allowed else ""
conn = db()
try:
result = rows(conn, f"SELECT * FROM {table} WHERE tenant=? ORDER BY id DESC", [tenant])
return jsonify(result)
finally:
conn.close()
@bp.route("/api/<resource>", methods=["POST"])
@login_required
def create_resource(resource):

View File

@@ -175,7 +175,9 @@ window.deleteDrawerItem = async (resource, id) => {
logActivity(resType, id, "删除了「" + name + "」");
}
closeDrawer();
await load();
var rk = resource === 'projectFinances' ? 'projectFinances' : resource === 'planFinances' ? 'planFinances' : null;
if (rk) { try { state.data[rk] = await api("/api/" + rk + "/list?tenant=" + encodeURIComponent(state.tenant)); } catch(e) {} }
if (resource === 'projectFinances') renderFinance(); else if (resource === 'planFinances') renderPlan();
} catch (error) {
toast("删除失败:" + error.message, "error");
}
@@ -216,21 +218,26 @@ window.submitComment = async (event, targetType, targetId, resource) => {
button.disabled = true;
button.textContent = "发送中…";
await api(`/api/followups/${targetType}/${targetId}`, { method: "POST", body: JSON.stringify({ data: { content } }) });
await load();
var ck = resource === 'projectFinances' ? 'projectFinances' : resource === 'planFinances' ? 'planFinances' : null;
if (ck) { try { state.data[ck] = await api("/api/" + ck + "/list?tenant=" + encodeURIComponent(state.tenant)); } catch(e) {} }
openDrawer(resource, targetId);
};
window.deleteActivity = async (id) => {
if (!confirm("确认删除这条动态?")) return;
await api(`/api/followups/${id}`, { method: "DELETE" });
await load();
var dk = resource === 'projectFinances' ? 'projectFinances' : resource === 'planFinances' ? 'planFinances' : null;
if (dk) { try { state.data[dk] = await api("/api/" + dk + "/list?tenant=" + encodeURIComponent(state.tenant)); } catch(e) {} }
if (resource === 'projectFinances') renderFinance(); else if (resource === 'planFinances') renderPlan();
};
window.deleteFollowup = async (event, followupId, resource, targetId) => {
event.stopPropagation();
if (!confirm("确认删除这条评论?")) return;
await api(`/api/followups/${followupId}`, { method: "DELETE" });
await load();
var fkey2 = resource === 'projectFinances' ? 'projectFinances' : resource === 'planFinances' ? 'planFinances' : null;
if (fkey2) { try { state.data[fkey2] = await api("/api/" + fkey2 + "/list?tenant=" + encodeURIComponent(state.tenant)); } catch(e) {} }
if (resource === 'projectFinances') renderFinance(); else if (resource === 'planFinances') renderPlan();
openDrawer(resource, targetId);
};

View File

@@ -908,7 +908,8 @@ window.createFinance = async (event) => {
document.querySelector("#pf-id-input").value = "";
document.querySelector("#financeModalTitle").textContent = "新增项目财务";
closeFinanceModal();
await load();
state.data.projectFinances = await api("/api/projectFinances/list?tenant=" + encodeURIComponent(state.tenant));
renderFinance();
} catch (error) {
toast("保存失败:" + error.message, "error");
}
@@ -923,7 +924,8 @@ window.deleteFinanceItem = async () => {
try {
await api(`/api/projectFinances/${pfId}`, { method: "DELETE" });
closeFinanceModal();
await load();
state.data.projectFinances = await api("/api/projectFinances/list?tenant=" + encodeURIComponent(state.tenant));
renderFinance();
toast("已删除", "success");
} catch (error) {
toast("删除失败:" + error.message, "error");

View File

@@ -945,7 +945,8 @@ window.createPlanFinance = async (event) => {
document.querySelector("#plan_pf-id-input").value = "";
document.querySelector("#planModalTitle").textContent = "新增项目财务";
closePlanModal();
await load();
state.data.planFinances = await api("/api/planFinances/list?tenant=" + encodeURIComponent(state.tenant));
renderPlan();
} catch (error) {
toast("刷新失败:" + error.message, "error");
}
@@ -960,7 +961,8 @@ window.deletePlanItem = async () => {
try {
await api(`/api/planFinances/${pfId}`, { method: "DELETE" });
closePlanModal();
await load();
state.data.planFinances = await api("/api/planFinances/list?tenant=" + encodeURIComponent(state.tenant));
renderPlan();
toast("已删除", "success");
} catch (error) {
toast("删除失败:" + error.message, "error");
@@ -1285,7 +1287,8 @@ function planInlineSelect(name, pfId, field, currentVal, options) {
window.planInlineUpdate = async (pfId, field, value) => {
try {
await api("/api/planFinances/" + pfId, { method: "PUT", body: JSON.stringify({ data: { [field]: value } }) });
await load();
state.data.planFinances = await api("/api/planFinances/list?tenant=" + encodeURIComponent(state.tenant));
renderPlan();
toast("已更新", "success");
} catch (error) {
toast("更新失败:" + error.message, "error");

View File

@@ -199,8 +199,10 @@ window.savePlanExpense = async function(event) {
return;
}
closePlanExpModal();
await load();
renderPlanExpense();
try {
state.data.planExpense = await api("/api/planExpense/list?tenant=" + encodeURIComponent(state.tenant));
renderPlanExpense();
} catch (e) { /* non-critical */ }
};
window.deletePlanExpItem = async function() {
@@ -213,6 +215,8 @@ window.deletePlanExpItem = async function() {
return;
}
closePlanExpModal();
await load();
renderPlanExpense();
try {
state.data.planExpense = await api("/api/planExpense/list?tenant=" + encodeURIComponent(state.tenant));
renderPlanExpense();
} catch (e) { /* non-critical */ }
};

View File

@@ -19,7 +19,7 @@ async function createResource(event, resource) {
const name = data.project_name || data.target_customer || data.customer_or_project_name || data.product_name || "";
if (result.id && name) logActivity(resType, result.id, "创建了" + name);
form.reset();
await load();
try { state.data.products = await api("/api/products/list?tenant=" + encodeURIComponent(state.tenant)); renderProducts(); } catch(e) {}
} catch (error) {
toast("创建失败:" + error.message, "error");
}
@@ -141,7 +141,7 @@ window.submitProductDrawer = async (event) => {
form.reset();
closeProductDrawer();
if (result.id) logActivity("product", result.id, "创建了产品版本「" + data.product_name + " " + data.version + "」");
await load();
try { state.data.products = await api("/api/products/list?tenant=" + encodeURIComponent(state.tenant)); renderProducts(); } catch(e) {}
} catch (error) {
toast("创建失败:" + error.message, "error");
}

View File

@@ -226,7 +226,7 @@ window.duplicateProject = async () => {
});
}
toast("已创建副本", "success");
await load();
try { state.data.operations = await api("/api/operations/list?tenant=" + encodeURIComponent(state.tenant)); renderProjects(); } catch(e) {}
} catch (error) {
toast("创建副本失败:" + error.message, "error");
}
@@ -463,7 +463,7 @@ window.submitTaskForm = async (event, projectId) => {
const result = await api("/api/tasks", { method: "POST", body: JSON.stringify({ data }) });
if (result.id && data.task) logActivity("task", result.id, "创建了任务「" + data.task + "」");
closeTaskDrawer(projectId);
await load();
try { state.data.tasks = await api("/api/tasks/list?tenant=" + encodeURIComponent(state.tenant)); } catch(e) {}
}
} catch (error) {
toast("保存失败:" + error.message, "error");

View File

@@ -32,7 +32,8 @@ async function ensureStandardProposals() {
});
} catch (e) { /* ignore */ }
}
await load();
state.data.proposals = await api("/api/proposals/list?tenant=" + encodeURIComponent(state.tenant));
renderProposals();
}
window.switchProposalTab = (tab) => {
@@ -183,7 +184,7 @@ window.submitStandardComment = async (event, targetId) => {
button.disabled = true;
button.textContent = "发送中…";
await api(`/api/followups/proposal/${targetId}`, { method: "POST", body: JSON.stringify({ data: { content } }) });
await load();
try { state.data.proposals = await api("/api/proposals/list?tenant=" + encodeURIComponent(state.tenant)); renderProposals(); } catch(e) {}
openStandardProposalDrawer(targetId);
};
@@ -207,7 +208,7 @@ window.submitProposal = async (event) => {
if (result.id && data.customer_or_project_name) logActivity("proposal", result.id, "创建了方案「" + data.customer_or_project_name + "」");
form.reset();
closeProposalModal();
await load();
try { state.data.proposals = await api("/api/proposals/list?tenant=" + encodeURIComponent(state.tenant)); renderProposals(); } catch(e) {}
} catch (error) {
toast("保存失败:" + error.message, "error");
}

View File

@@ -76,7 +76,7 @@
<header class="topbar border-b border-slate-200 bg-white px-8 py-5">
<div class="flex items-center gap-3 w-full">
<div class="flex-1">
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.0.0.36</span></p>
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.0.0.38</span></p>
<div class="flex items-center gap-4 mt-1">
<h1 class="text-2xl font-semibold" id="workspaceTitle">科普 OPC 工作台</h1>
<div class="hidden sm:flex items-center gap-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 rounded-full px-4 py-1.5">