This commit is contained in:
oneao committed 2026-05-06 21:38:45 +08:00
1 parent 742ca27f2e
commit 95fd2aeced
1 file changed
+187 -85
+187 -85
View File
@@ -1,117 +1,219 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>金额输入 Demo</title>
<title>ERP账单 - 方案4 Demo(diff提交 + 新增末尾)</title>
<style>
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI";
background: #f5f6f7;
}
.container {
max-width: 420px;
margin: 0 auto;
background: #fff;
min-height: 100vh;
padding: 20px;
}
.amount-wrapper {
text-align: center;
padding: 30px 0;
cursor: pointer;
}
.amount-label {
font-size: 14px;
color: #999;
margin-bottom: 6px;
}
.amount {
font-size: 42px;
font-weight: 600;
color: #111;
letter-spacing: -1px;
}
/* 点击反馈 */
.amount-wrapper:active {
opacity: 0.6;
}
/* 隐藏输入 */
.hidden-input {
position: absolute;
opacity: 0;
pointer-events: none;
}
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>
<div class="container">
<h2>账单明细(方案4:分页 + diff提交)</h2>
<!-- 金额 -->
<div class="amount-wrapper" onclick="focusInput()">
<div class="amount-label">金额</div>
<div class="amount" id="amount">¥ 0.00</div>
<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>
<!-- 隐藏输入 -->
<input
id="input"
class="hidden-input"
type="text"
inputmode="decimal"
oninput="handleInput(this.value)"
/>
<table>
<thead>
<tr>
<th>ID</th>
<th>费用名称</th>
<th>金额</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<h3>Diff 输出:</h3>
<pre id="output"></pre>
<script>
let value = "0";
// ===============================
// 模拟后端数据(200条)
// ===============================
let serverData = Array.from({length: 200}, (_, i) => ({
id: i + 1,
name: "费用-" + (i + 1),
amount: Math.floor(Math.random() * 1000)
}));
function focusInput() {
document.getElementById("input").focus();
// ===============================
// 分页参数
// ===============================
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 handleInput(text) {
// 只保留数字和小数点
let v = text.replace(/[^\d.]/g, "");
// ===============================
// 渲染
// ===============================
function render() {
const data = fetchPage(page, pageSize);
// 只允许一个 .
const parts = v.split(".");
if (parts.length > 2) {
v = parts[0] + "." + parts[1];
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)} 页`;
}
// 限制两位小数
if (v.includes(".")) {
let [int, dec] = v.split(".");
v = int + "." + dec.slice(0, 2);
// ===============================
// 编辑(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};
}
// 去前导0
if (v.startsWith("0") && v[1] !== ".") {
v = v.replace(/^0+/, "");
current[field] = field === "amount" ? Number(value) : value;
state.updated.set(id, current);
}
if (v === "") v = "0";
value = v;
updateUI();
// ===============================
// 删除
// ===============================
function del(id) {
state.deleted.add(id);
state.updated.delete(id);
render();
}
function updateUI() {
document.getElementById("amount").innerText = "¥ " + value;
document.getElementById("input").value = value;
// ===============================
// 新增(关键修复:追加到末尾 + 跳最后页)
// ===============================
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>