220 lines
4.8 KiB
HTML
220 lines
4.8 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<title>ERP账单 - 方案4 Demo(diff提交 + 新增末尾)</title>
|
||
|
||
<style>
|
||
body { font-family: Arial; margin: 20px; }
|
||
table { border-collapse: collapse; width: 100%; margin-top: 10px; }
|
||
th, td { border: 1px solid #ddd; padding: 6px; text-align: left; }
|
||
th { background: #f5f5f5; }
|
||
input { width: 100%; box-sizing: border-box; }
|
||
button { margin: 5px; }
|
||
.toolbar { margin-bottom: 10px; }
|
||
.badge { padding: 2px 6px; background: orange; color: white; border-radius: 4px; }
|
||
</style>
|
||
|
||
</head>
|
||
|
||
<body>
|
||
|
||
<h2>账单明细(方案4:分页 + diff提交)</h2>
|
||
|
||
<div class="toolbar">
|
||
<button onclick="prevPage()">上一页</button>
|
||
<button onclick="nextPage()">下一页</button>
|
||
<button onclick="addRow()">新增费用</button>
|
||
<button onclick="submit()">提交(只提交diff)</button>
|
||
<span id="pageInfo"></span>
|
||
</div>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>费用名称</th>
|
||
<th>金额</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbody"></tbody>
|
||
</table>
|
||
|
||
<h3>Diff 输出:</h3>
|
||
<pre id="output"></pre>
|
||
|
||
<script>
|
||
// ===============================
|
||
// 模拟后端数据(200条)
|
||
// ===============================
|
||
let serverData = Array.from({length: 200}, (_, i) => ({
|
||
id: i + 1,
|
||
name: "费用-" + (i + 1),
|
||
amount: Math.floor(Math.random() * 1000)
|
||
}));
|
||
|
||
// ===============================
|
||
// 分页参数
|
||
// ===============================
|
||
let page = 1;
|
||
let pageSize = 10;
|
||
|
||
// ===============================
|
||
// diff结构(核心)
|
||
// ===============================
|
||
const state = {
|
||
originalMap: new Map(),
|
||
updated: new Map(),
|
||
created: [],
|
||
deleted: new Set()
|
||
};
|
||
|
||
// ===============================
|
||
// 模拟分页接口
|
||
// ===============================
|
||
function fetchPage(page, size) {
|
||
const start = (page - 1) * size;
|
||
const end = start + size;
|
||
return serverData.slice(start, end);
|
||
}
|
||
|
||
// ===============================
|
||
// 渲染
|
||
// ===============================
|
||
function render() {
|
||
const data = fetchPage(page, pageSize);
|
||
|
||
const tbody = document.getElementById("tbody");
|
||
tbody.innerHTML = "";
|
||
|
||
state.originalMap.clear();
|
||
|
||
data.forEach(row => {
|
||
state.originalMap.set(row.id, JSON.parse(JSON.stringify(row)));
|
||
|
||
if (state.deleted.has(row.id)) return;
|
||
|
||
const tr = document.createElement("tr");
|
||
|
||
tr.innerHTML = `
|
||
<td>${row.id}</td>
|
||
<td><input value="${row.name}" onchange="edit(${row.id}, 'name', this.value)"></td>
|
||
<td><input value="${row.amount}" onchange="edit(${row.id}, 'amount', this.value)"></td>
|
||
<td>
|
||
<button onclick="del(${row.id})">删除</button>
|
||
${state.updated.has(row.id) ? '<span class="badge">已修改</span>' : ''}
|
||
</td>
|
||
`;
|
||
|
||
tbody.appendChild(tr);
|
||
});
|
||
|
||
document.getElementById("pageInfo").innerText =
|
||
`第 ${page} 页 / 共 ${Math.ceil(serverData.length / pageSize)} 页`;
|
||
}
|
||
|
||
// ===============================
|
||
// 编辑(diff核心)
|
||
// ===============================
|
||
function edit(id, field, value) {
|
||
const original = state.originalMap.get(id);
|
||
if (!original) return;
|
||
|
||
let current = state.updated.get(id);
|
||
if (!current) {
|
||
current = {...original};
|
||
}
|
||
|
||
current[field] = field === "amount" ? Number(value) : value;
|
||
state.updated.set(id, current);
|
||
}
|
||
|
||
// ===============================
|
||
// 删除
|
||
// ===============================
|
||
function del(id) {
|
||
state.deleted.add(id);
|
||
state.updated.delete(id);
|
||
render();
|
||
}
|
||
|
||
// ===============================
|
||
// 新增(关键修复:追加到末尾 + 跳最后页)
|
||
// ===============================
|
||
function addRow() {
|
||
const newId = Date.now();
|
||
|
||
const row = {
|
||
id: newId,
|
||
name: "新费用",
|
||
amount: 0
|
||
};
|
||
|
||
// ✅ 追加到末尾(ERP标准:append-only)
|
||
serverData.push(row);
|
||
|
||
// 记录create diff
|
||
state.created.push(row);
|
||
|
||
// ✅ 自动跳转到最后一页
|
||
page = Math.ceil(serverData.length / pageSize);
|
||
|
||
render();
|
||
}
|
||
|
||
// ===============================
|
||
// 分页
|
||
// ===============================
|
||
function nextPage() {
|
||
if (page * pageSize < serverData.length) {
|
||
page++;
|
||
render();
|
||
}
|
||
}
|
||
|
||
function prevPage() {
|
||
if (page > 1) {
|
||
page--;
|
||
render();
|
||
}
|
||
}
|
||
|
||
// ===============================
|
||
// 提交 diff(核心)
|
||
// ===============================
|
||
function submit() {
|
||
|
||
const patch = {
|
||
create: state.created,
|
||
update: [],
|
||
delete: Array.from(state.deleted)
|
||
};
|
||
|
||
// 计算update diff
|
||
state.updated.forEach((newVal, id) => {
|
||
const oldVal = state.originalMap.get(id);
|
||
if (!oldVal) return;
|
||
|
||
if (
|
||
oldVal.name !== newVal.name ||
|
||
oldVal.amount !== newVal.amount
|
||
) {
|
||
patch.update.push(newVal);
|
||
}
|
||
});
|
||
|
||
document.getElementById("output").innerText =
|
||
JSON.stringify(patch, null, 2);
|
||
|
||
console.log("提交patch:", patch);
|
||
|
||
alert("已生成diff,请查看下方JSON或控制台");
|
||
}
|
||
|
||
// 初始渲染
|
||
render();
|
||
</script>
|
||
|
||
</body>
|
||
</html> |