v1.0.7 — 连修三 bug:表单事件丢失 + Chart 堆积 + 字段重命名
This commit is contained in:
@@ -1,5 +1,16 @@
|
|||||||
# OPC Manager Version Log
|
# OPC Manager Version Log
|
||||||
|
|
||||||
|
## v1.0.7 — 2026-06-04
|
||||||
|
- 修复新增表单 async 后 `event.currentTarget` 丢失导致页面不刷新(影响所有新增按钮)
|
||||||
|
- `createResource` 改用预存 form 引用 + try/catch 错误提示
|
||||||
|
|
||||||
|
## v1.0.6 — 2026-06-04
|
||||||
|
- 修复财务 Tab Chart 无限堆积:renderChartOn 缺少旧 chart 销毁 + state 跟踪
|
||||||
|
- 财务图表容器加固定高度(300px),避免 resize 循环
|
||||||
|
|
||||||
|
## v1.0.5 — 2026-06-04
|
||||||
|
- "销售管理" Tab 改为"业务机会","目标客户"字段统一改为"业务机会"
|
||||||
|
|
||||||
## v1.0.4 — 2026-06-04
|
## v1.0.4 — 2026-06-04
|
||||||
- CDN 全量本地化:Tailwind / Chart.js / Squire / Lucide 下载到 `static/vendor/`,不再依赖外部 CDN
|
- CDN 全量本地化:Tailwind / Chart.js / Squire / Lucide 下载到 `static/vendor/`,不再依赖外部 CDN
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const state = {
|
|||||||
data: null,
|
data: null,
|
||||||
opFilter: "all",
|
opFilter: "all",
|
||||||
chart: null,
|
chart: null,
|
||||||
|
chart2: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const money = (value) => `${Number(value || 0).toLocaleString("zh-CN")} 万`;
|
const money = (value) => `${Number(value || 0).toLocaleString("zh-CN")} 万`;
|
||||||
@@ -143,10 +144,15 @@ function formHtml(fields, button) {
|
|||||||
|
|
||||||
async function createResource(event, resource) {
|
async function createResource(event, resource) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const data = Object.fromEntries(new FormData(event.currentTarget).entries());
|
const form = event.currentTarget;
|
||||||
await api(`/api/${resource}`, { method: "POST", body: JSON.stringify({ data }) });
|
const data = Object.fromEntries(new FormData(form).entries());
|
||||||
event.currentTarget.reset();
|
try {
|
||||||
await load();
|
await api(`/api/${resource}`, { method: "POST", body: JSON.stringify({ data }) });
|
||||||
|
form.reset();
|
||||||
|
await load();
|
||||||
|
} catch (error) {
|
||||||
|
alert("创建失败:" + error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.createSales = (event) => createResource(event, "sales");
|
window.createSales = (event) => createResource(event, "sales");
|
||||||
@@ -161,11 +167,11 @@ function renderSales() {
|
|||||||
const salesClicks = state.data.sales.map((x) => ({ resource: "sales", id: x.id }));
|
const salesClicks = state.data.sales.map((x) => ({ resource: "sales", id: x.id }));
|
||||||
document.querySelector("#sales").innerHTML = `<div class="grid gap-4">
|
document.querySelector("#sales").innerHTML = `<div class="grid gap-4">
|
||||||
${card(formHtml([
|
${card(formHtml([
|
||||||
{ label: "目标客户", input: `<input name="target_customer" required placeholder="客户名称">` },
|
{ label: "业务机会", input: `<input name="target_customer" required placeholder="客户名称">` },
|
||||||
{ label: "优先级", input: `<select name="priority"><option>P0</option><option selected>P1</option><option>P2</option><option>P3</option></select>` },
|
{ label: "优先级", input: `<select name="priority"><option>P0</option><option selected>P1</option><option>P2</option><option>P3</option></select>` },
|
||||||
{ label: "状态", input: `<select name="status"><option>待跟进</option><option>跟进中</option><option>方案中</option><option>商务谈判</option><option>已签约</option><option>暂缓</option><option>已丢单</option></select>` },
|
{ label: "状态", input: `<select name="status"><option>待跟进</option><option>跟进中</option><option>方案中</option><option>商务谈判</option><option>已签约</option><option>暂缓</option><option>已丢单</option></select>` },
|
||||||
], { handler: "createSales", text: `<i data-lucide="plus"></i>新增客户` }), "p-4")}
|
], { handler: "createSales", text: `<i data-lucide="plus"></i>新增业务机会` }), "p-4")}
|
||||||
${renderTable(["目标客户", "优先级", "状态", "最新跟进记录"], rows, salesClicks)}
|
${renderTable(["业务机会", "优先级", "状态", "最新跟进记录"], rows, salesClicks)}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +251,7 @@ function renderProducts() {
|
|||||||
function renderFinance() {
|
function renderFinance() {
|
||||||
const rows = state.data.finance.map((x) => [x.month, badge(x.record_type === "revenue" ? "收入" : "成本/费用"), x.category, money(x.amount), x.occurred_date, text(x.notes)]);
|
const rows = state.data.finance.map((x) => [x.month, badge(x.record_type === "revenue" ? "收入" : "成本/费用"), x.category, money(x.amount), x.occurred_date, text(x.notes)]);
|
||||||
document.querySelector("#finance").innerHTML = `<div class="grid gap-4">
|
document.querySelector("#finance").innerHTML = `<div class="grid gap-4">
|
||||||
${card(`<h2 class="mb-4 text-lg font-bold">收入、毛利、成本/费用、净利月度曲线</h2><canvas id="financeChart2" height="105"></canvas>`, "p-5")}
|
${card(`<h2 class="mb-4 text-lg font-bold">收入、毛利、成本/费用、净利月度曲线</h2><div style="position:relative;height:300px"><canvas id="financeChart2"></canvas></div>`, "p-5")}
|
||||||
${card(formHtml([
|
${card(formHtml([
|
||||||
{ label: "月份", input: `<input name="month" required placeholder="YYYY-MM" pattern="\\d{4}-\\d{2}">` },
|
{ label: "月份", input: `<input name="month" required placeholder="YYYY-MM" pattern="\\d{4}-\\d{2}">` },
|
||||||
{ label: "类型", input: `<select name="record_type"><option value="revenue">收入</option><option value="cost_expense">成本/费用</option></select>` },
|
{ label: "类型", input: `<select name="record_type"><option value="revenue">收入</option><option value="cost_expense">成本/费用</option></select>` },
|
||||||
@@ -261,7 +267,8 @@ function renderFinance() {
|
|||||||
function renderChartOn(id, data) {
|
function renderChartOn(id, data) {
|
||||||
const canvas = document.querySelector(`#${id}`);
|
const canvas = document.querySelector(`#${id}`);
|
||||||
if (!canvas || !window.Chart) return;
|
if (!canvas || !window.Chart) return;
|
||||||
new Chart(canvas, {
|
if (state.chart2) state.chart2.destroy();
|
||||||
|
state.chart2 = new Chart(canvas, {
|
||||||
type: "line",
|
type: "line",
|
||||||
data: {
|
data: {
|
||||||
labels: data.map((x) => x.month),
|
labels: data.map((x) => x.month),
|
||||||
@@ -292,9 +299,9 @@ function openDrawer(resource, id) {
|
|||||||
const item = list.find((x) => x.id === id);
|
const item = list.find((x) => x.id === id);
|
||||||
const drawer = document.querySelector("#drawer");
|
const drawer = document.querySelector("#drawer");
|
||||||
const fields = resource === "sales"
|
const fields = resource === "sales"
|
||||||
? [["target_customer","目标客户"],["priority","优先级"],["status","状态"]]
|
? [["target_customer","业务机会"],["priority","优先级"],["status","状态"]]
|
||||||
: resource === "operations"
|
: resource === "operations"
|
||||||
? [["project_name","项目名称"],["project_version","项目版本"],["project_status","项目状态"],["current_stage","当前阶段"],["target_customer","目标客户"],["customer_need","客户需求"],["expected_contract_amount","预计签约金额"],["expected_sign_date","预计签约时间"],["sign_probability","签约概率"],["sop_stage","SOP 阶段"],["execution_progress","执行进度"],["current_deliverable","当前交付物"],["risks","风险与阻塞"],["next_action","下一步动作"]]
|
? [["project_name","项目名称"],["project_version","项目版本"],["project_status","项目状态"],["current_stage","当前阶段"],["target_customer","业务机会"],["customer_need","客户需求"],["expected_contract_amount","预计签约金额"],["expected_sign_date","预计签约时间"],["sign_probability","签约概率"],["sop_stage","SOP 阶段"],["execution_progress","执行进度"],["current_deliverable","当前交付物"],["risks","风险与阻塞"],["next_action","下一步动作"]]
|
||||||
: resource === "proposals"
|
: resource === "proposals"
|
||||||
? [["customer_or_project_name","客户/项目"],["version","版本号"],["description","版本说明"],["created_date","创建日期"],["status","状态"]]
|
? [["customer_or_project_name","客户/项目"],["version","版本号"],["description","版本说明"],["created_date","创建日期"],["status","状态"]]
|
||||||
: [["product_name","产品名称"],["version","版本号"],["version_goal","版本目标"],["feature_list","核心功能清单"],["launch_date","上线日期"],["status","当前状态"],["notes","备注"]];
|
: [["product_name","产品名称"],["version","版本号"],["version_goal","版本目标"],["feature_list","核心功能清单"],["launch_date","上线日期"],["status","当前状态"],["notes","备注"]];
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<nav class="tabs border-b border-slate-200 bg-white px-8" id="tabs">
|
<nav class="tabs border-b border-slate-200 bg-white px-8" id="tabs">
|
||||||
<button class="active" data-tab="home"><i data-lucide="home"></i>首页</button>
|
<button class="active" data-tab="home"><i data-lucide="home"></i>首页</button>
|
||||||
<button data-tab="sales"><i data-lucide="briefcase-business"></i>销售管理</button>
|
<button data-tab="sales"><i data-lucide="briefcase-business"></i>业务机会</button>
|
||||||
<button data-tab="proposals"><i data-lucide="file-text"></i>业务方案</button>
|
<button data-tab="proposals"><i data-lucide="file-text"></i>业务方案</button>
|
||||||
<button data-tab="operations"><i data-lucide="activity"></i>运营管理</button>
|
<button data-tab="operations"><i data-lucide="activity"></i>运营管理</button>
|
||||||
<button data-tab="products"><i data-lucide="package"></i>产品研发</button>
|
<button data-tab="products"><i data-lucide="package"></i>产品研发</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user