下面是一套 Dash 常用交互组件(Interactive Components),专为 Web 钱包、支付页面、DApp 设计,基于 HTML + JavaScript + DashJS,支持 实时交互、用户反馈、动画、错误处理,提升用户体验。
核心技术栈
<script src="https://cdn.jsdelivr.net/npm/dash@4.0.0/dist/dash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
1. 余额实时刷新组件(带加载动画)
<div class="card shadow-sm">
<div class="card-body text-center">
<div class="spinner-border text-primary" role="status" id="balance-spinner">
<span class="visually-hidden">加载中...</span>
</div>
<h5 class="mt-3">钱包余额</h5>
<h2 id="balance-value" class="text-primary">0.00</h2>
<small class="text-muted" id="balance-address"></small>
<button class="btn btn-sm btn-outline-primary mt-2" onclick="refreshBalance()">刷新</button>
</div>
</div>
<script>
let balanceInterval;
async function refreshBalance() {
const spinner = document.getElementById('balance-spinner');
const value = document.getElementById('balance-value');
const addr = document.getElementById('balance-address');
spinner.style.display = 'block';
value.textContent = '加载中...';
try {
const account = await getAccount();
const balance = account.getTotalBalance() / 1e8;
const address = account.getUnusedAddress().address;
value.textContent = `${balance.toFixed(6)} DASH`;
addr.textContent = address;
} catch (e) {
value.textContent = '错误';
addr.textContent = e.message;
} finally {
spinner.style.display = 'none';
}
}
// 自动每30秒刷新
balanceInterval = setInterval(refreshBalance, 30000);
refreshBalance();
</script>
2. 扫码支付按钮(相机 + 解析地址)
<button class="btn dash-btn w-100" onclick="scanQR()">
扫码支付
</button>
<script>
async function scanQR() {
if (!('mediaDevices' in navigator)) {
Swal.fire('错误', '您的浏览器不支持摄像头', 'error');
return;
}
const html5QrCode = new Html5Qrcode("qr-reader");
const video = document.createElement('div');
video.id = 'qr-reader';
document.body.appendChild(video);
Swal.fire({
title: '扫码支付',
html: video,
showConfirmButton: false,
width: '400px'
});
html5QrCode.start(
{ facingMode: "environment" },
{ fps: 10, qrbox: 250 },
(text) => {
if (text.startsWith('y') || text.startsWith('x') || text.endsWith('.dash')) {
document.getElementById('to').value = text;
Swal.close();
html5QrCode.stop();
}
},
() => {}
).catch(err => Swal.fire('错误', err, 'error'));
}
</script>
<!-- 引入 QR 扫描库 -->
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
3. 发送 DASH 交互表单(带确认 + 进度)
<form id="sendDashForm" class="card p-4">
<div class="mb-3">
<label>收款人</label>
<div class="input-group">
<input type="text" class="form-control" id="to" placeholder="地址或 alice.dash" required />
<button type="button" class="btn btn-outline-secondary" onclick="scanQR()">扫一扫</button>
</div>
</div>
<div class="mb-3">
<label>金额 (DASH)</label>
<input type="number" step="0.00000001" class="form-control" id="amount" required />
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="instantsend" checked />
<label class="form-check-label">InstantSend(1秒到账)</label>
</div>
<button type="submit" class="btn dash-btn w-100" id="sendBtn">
<span id="sendText">发送</span>
<span id="sendSpinner" class="spinner-border spinner-border-sm ms-2" style="display:none"></span>
</button>
<div id="sendResult" class="mt-3"></div>
</form>
<script>
document.getElementById('sendDashForm').onsubmit = async (e) => {
e.preventDefault();
const btn = document.getElementById('sendBtn');
const text = document.getElementById('sendText');
const spinner = document.getElementById('sendSpinner');
const result = document.getElementById('sendResult');
btn.disabled = true;
text.style.display = 'none';
spinner.style.display = 'inline-block';
const to = document.getElementById('to').value.trim();
const amount = parseFloat(document.getElementById('amount').value);
const instant = document.getElementById('instantsend').checked;
try {
const account = await getAccount();
const tx = account.createTransaction({
recipient: to,
satoshis: Math.floor(amount * 1e8),
instantSend: instant
});
Swal.fire({
title: '确认发送?',
text: `发送 ${amount} DASH 给 ${to.slice(0,10)}...`,
icon: 'question',
showCancelButton: true
}).then(async (res) => {
if (res.isConfirmed) {
const txid = await account.broadcastTransaction(tx);
result.innerHTML = `
<div class="alert alert-success p-2">
发送成功!<br>
<a href="https://testnet-insight.dashevo.org/insight/tx/${txid}" target="_blank">
查看交易 ${txid.slice(0,16)}...
</a>
</div>`;
refreshBalance();
}
});
} catch (err) {
result.innerHTML = `<div class="alert alert-danger p-2">${err.message}</div>`;
} finally {
btn.disabled = false;
text.style.display = 'inline';
spinner.style.display = 'none';
}
};
</script>
4. 交易状态实时监听(WebSocket)
<div class="card">
<div class="card-body">
<h6>交易通知</h6>
<div id="txNotifications"></div>
</div>
</div>
<script>
let ws;
async function startTxListener() {
const account = await getAccount();
const address = account.getUnusedAddress().address;
ws = new WebSocket('wss://testnet-insight.dashevo.org/insight-api');
ws.onopen = () => {
ws.send(JSON.stringify({ "op": "addr_sub", "addr": address }));
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.op === 'utx') {
const value = data.x.vout[0].value / 1e8;
const txid = data.x.hash;
const div = document.createElement('div');
div.className = 'alert alert-info py-1 px-2 mb-1';
div.innerHTML = `新收入: +${value} DASH <a href="https://testnet-insight.dashevo.org/insight/tx/${txid}" target="_blank">查看</a>`;
document.getElementById('txNotifications').prepend(div);
refreshBalance();
}
};
}
startTxListener();
</script>
5. DashPay 联系人浮窗(悬浮按钮)
<button class="btn btn-floating dash-btn position-fixed bottom-0 end-0 m-3 rounded-circle shadow-lg"
style="width:60px;height:60px;font-size:24px" onclick="openContacts()">
联系人
</button>
<div class="modal fade" id="contactsModal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header"><h5>我的联系人</h5></div>
<div class="modal-body" id="contactsList">加载中...</div>
</div>
</div>
</div>
<script>
async function openContacts() {
const list = document.getElementById('contactsList');
list.innerHTML = '<div class="text-center"><div class="spinner-border"></div></div>';
try {
const platform = client.platform;
const contacts = await platform.names.search('', 'dashpay');
list.innerHTML = '';
contacts.forEach(c => {
const item = document.createElement('div');
item.className = 'd-flex justify-content-between align-items-center p-2 border-bottom';
item.innerHTML = `
<span>${c.label}</span>
<button class="btn btn-sm btn-outline-primary" onclick="selectContact('${c.data.to}')">发送</button>
`;
list.appendChild(item);
});
} catch {
list.innerHTML = '<small class="text-muted">暂无联系人</small>';
}
new bootstrap.Modal(document.getElementById('contactsModal')).show();
}
function selectContact(addr) {
document.getElementById('to').value = addr;
bootstrap.Modal.getInstance(document.getElementById('contactsModal')).hide();
}
</script>
6. 一键复制 + 成功动画
<div class="input-group">
<input type="text" class="form-control" id="copyInput" readonly />
<button class="btn btn-outline-secondary" onclick="copyAndAnimate()">
复制
</button>
</div>
<script>
function copyAndAnimate() {
const input = document.getElementById('copyInput');
input.select();
document.execCommand('copy');
const btn = input.nextElementSibling;
const original = btn.innerHTML;
btn.innerHTML = '已复制!';
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = original;
btn.classList.remove('btn-success');
}, 2000);
}
</script>
7. 完整交互页面模板(复制即用)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>Dash 交互钱包</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script src="https://cdn.jsdelivr.net/npm/dash@4.0.0/dist/dash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.dash-btn { background: #008DE4; color: white; }
.dash-btn:hover { background: #006bb3; }
.btn-floating { z-index: 1000; }
</style>
</head>
<body class="bg-light">
<div class="container py-4">
<!-- 余额 -->
<div class="card shadow-sm mb-3">
<div class="card-body text-center">
<div class="spinner-border text-primary" id="balance-spinner" style="width:2rem;height:2rem"></div>
<h5 class="mt-3">余额</h5>
<h2 id="balance-value" class="text-primary">0.00</h2>
<small class="text-muted" id="balance-address"></small>
<button class="btn btn-sm btn-outline-primary mt-2" onclick="refreshBalance()">刷新</button>
</div>
</div>
<!-- 接收 -->
<div class="text-center mb-4">
<h5>接收 DASH</h5>
<div id="qrcode" class="d-inline-block border p-2 bg-white"></div>
<p><code id="receive-address" class="small"></code></p>
<button class="btn btn-sm btn-outline-secondary" onclick="copyAndAnimate()">复制地址</button>
</div>
<!-- 发送 -->
<form id="sendDashForm" class="card p-4 mb-4">
<h5>发送</h5>
<div class="input-group mb-3">
<input type="text" class="form-control" id="to" placeholder="地址或用户名" required />
<button type="button" class="btn btn-outline-secondary" onclick="scanQR()">扫一扫</button>
</div>
<input type="number" step="0.00000001" class="form-control mb-3" id="amount" placeholder="金额" required />
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="instantsend" checked />
<label>InstantSend</label>
</div>
<button type="submit" class="btn dash-btn w-100" id="sendBtn">
<span id="sendText">发送</span>
<span id="sendSpinner" class="spinner-border spinner-border-sm ms-2" style="display:none"></span>
</button>
<div id="sendResult"></div>
</form>
<!-- 通知 -->
<div class="card">
<div class="card-body">
<h6>通知</h6>
<div id="txNotifications" class="small"></div>
</div>
</div>
<!-- 联系人浮窗 -->
<button class="btn btn-floating dash-btn position-fixed bottom-0 end-0 m-3 rounded-circle shadow-lg"
style="width:60px;height:60px;font-size:24px" onclick="openContacts()">
联系人
</button>
</div>
<script>
// 替换为你的测试网助记词
const MNEMONIC = 'abandon ... art';
const client = new Dash.Client({ network: 'testnet', wallet: { mnemonic: MNEMONIC } });
async function getAccount() {
return await client.wallet.getAccount();
}
// 初始化
async function init() {
const account = await getAccount();
const addr = account.getUnusedAddress().address;
document.getElementById('receive-address').textContent = addr;
document.getElementById('copyInput') && (document.getElementById('copyInput').value = addr);
new QRCode(document.getElementById("qrcode"), { text: addr, width: 160, height: 160, colorDark: "#008DE4" });
refreshBalance();
startTxListener();
}
init();
</script>
<!-- 包含上面所有脚本:refreshBalance, scanQR, sendDashForm, startTxListener, openContacts, copyAndAnimate -->
</body>
</html>
交互组件汇总
| 组件 | 交互方式 | 用户体验 |
|---|---|---|
| 余额刷新 | 按钮 + 自动轮询 | 实时更新 + 加载动画 |
| 扫码支付 | 相机 + 实时解析 | 一扫即填 |
| 发送确认 | SweetAlert2 弹窗 | 二次确认防误操作 |
| 交易通知 | WebSocket 推送 | 实时到账提醒 |
| 联系人浮窗 | 悬浮按钮 + 模态框 | 快速选择 |
| 复制反馈 | 按钮变色 + 文字 | 明确反馈 |
立即测试:保存为 dash-interactive.html,用浏览器打开,替换 MNEMONIC 即可体验完整交互!
需要我生成 React / Vue 组件版、移动端 PWA、或集成到 Shopify 吗? 随时告诉我!