Files
dinner.tootaio.com/bidding/control.html
xiaomai 9394792242 feat(bidding): add initial auction control and display pages
This commit introduces a lightweight, browser-based auction system.

It consists of two main components that communicate in real-time using the `BroadcastChannel` API, enabling a serverless
front-end experience:

- `display.html`: The public-facing screen for attendees. It shows the current item, price with smooth animation, and
deal announcements. It automatically generates a unique session ID.
- `control.html`: The auctioneer's control panel. It uses the session ID from the display page to connect. It allows for
managing the auction flow, including loading items from a CSV, starting bids, updating prices, and finalizing sales.
2025-09-15 20:53:12 +08:00

203 lines
5.8 KiB
HTML

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Auction Control</title>
<style>
body {
font-family: sans-serif;
margin: 20px;
background: #f5f5f5;
}
h1 { margin-bottom: 10px; }
.section {
background: #fff;
padding: 15px;
margin-bottom: 15px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
input, select, button, textarea {
margin: 5px 0;
padding: 6px;
border-radius: 4px;
border: 1px solid #ccc;
}
button {
cursor: pointer;
background: #007bff;
color: #fff;
border: none;
}
button:hover {
background: #0056b3;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
border: 1px solid #ccc;
padding: 6px;
text-align: center;
}
th {
background: #eee;
}
.history {
max-height: 200px;
overflow-y: auto;
}
</style>
</head>
<body>
<h1>🎛 拍卖控制台</h1>
<div>当前频道 ID: <b id="displayId"></b></div>
<!-- 上传 CSV -->
<div class="section">
<h2>上传标品列表 (Items.csv)</h2>
<input type="file" id="csvUpload" accept=".csv">
<select id="itemSelect"></select>
</div>
<!-- 自定义输入 -->
<div class="section">
<h2>标品信息</h2>
<label>名称: <input type="text" id="itemName"></label><br>
<label>底价: <input type="number" id="itemBasePrice" value="0"></label><br>
<label>备注: <input type="text" id="itemRemark"></label><br>
<label>图片/影片链接: <input type="text" id="itemMedia"></label><br>
<button id="startAuction">开始竞拍</button>
</div>
<!-- 出价 -->
<div class="section">
<h2>竞价控制</h2>
<label>当前价格: <input type="number" id="currentPrice" value="0"></label><br>
<button id="updatePrice">更新价格</button>
<button id="dealItem">成交</button><br>
<label>得标者: <input type="text" id="winnerName" placeholder="留空则显示 兴 旺 发"></label>
</div>
<!-- 成交历史 -->
<div class="section">
<h2>成交历史</h2>
<div>总金额: <span id="totalAmount">0</span></div>
<div class="history">
<table id="historyTable">
<thead>
<tr>
<th>标品名称</th>
<th>成交价</th>
<th>得标者</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<script>
// 获取 displayId
const urlParams = new URLSearchParams(location.search);
const displayId = urlParams.get("display");
if (!displayId) {
alert("缺少 display 参数!请从 display.html 获取正确链接");
}
document.getElementById("displayId").textContent = displayId;
const channel = new BroadcastChannel("auction-" + displayId);
const itemSelect = document.getElementById("itemSelect");
const itemName = document.getElementById("itemName");
const itemBasePrice = document.getElementById("itemBasePrice");
const itemRemark = document.getElementById("itemRemark");
const itemMedia = document.getElementById("itemMedia");
const currentPrice = document.getElementById("currentPrice");
const winnerName = document.getElementById("winnerName");
const totalAmountEl = document.getElementById("totalAmount");
const historyTable = document.getElementById("historyTable").querySelector("tbody");
let totalAmount = 0;
let csvItems = [];
// 解析 CSV
document.getElementById("csvUpload").addEventListener("change", (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
const lines = ev.target.result.split(/\r?\n/).filter(l => l.trim());
csvItems = lines.map(l => {
const [name, basePrice, remark, media] = l.split(",");
return { name, basePrice: Number(basePrice), remark, media };
});
refreshItemSelect();
};
reader.readAsText(file);
});
function refreshItemSelect() {
itemSelect.innerHTML = "";
csvItems.forEach((it, idx) => {
const opt = document.createElement("option");
opt.value = idx;
opt.textContent = it.name;
itemSelect.appendChild(opt);
});
}
itemSelect.addEventListener("change", () => {
const idx = itemSelect.value;
if (csvItems[idx]) {
const it = csvItems[idx];
itemName.value = it.name;
itemBasePrice.value = it.basePrice;
itemRemark.value = it.remark;
itemMedia.value = it.media;
}
});
// 开始竞拍
document.getElementById("startAuction").addEventListener("click", () => {
const msg = {
type: "showItem",
name: itemName.value,
basePrice: Number(itemBasePrice.value),
remark: itemRemark.value,
media: itemMedia.value
};
channel.postMessage(msg);
currentPrice.value = itemBasePrice.value;
});
// 更新价格
document.getElementById("updatePrice").addEventListener("click", () => {
const msg = {
type: "updatePrice",
price: Number(currentPrice.value)
};
channel.postMessage(msg);
});
// 成交
document.getElementById("dealItem").addEventListener("click", () => {
const price = Number(currentPrice.value);
const winner = winnerName.value || "";
const msg = { type: "deal", price, winner };
channel.postMessage(msg);
// 保存历史
const tr = document.createElement("tr");
tr.innerHTML = `<td>${itemName.value}</td><td>${price}</td><td>${winner || "兴 旺 发"}</td>`;
historyTable.appendChild(tr);
totalAmount += price;
totalAmountEl.textContent = totalAmount;
});
</script>
</body>
</html>