let wallet;
let priceChart;
// 初始化页面
document.addEventListener('DOMContentLoaded', () => {
// 检查是否已有钱包连接
checkWalletConnection();
// 初始化价格图表
initPriceChart();
// 定期刷新数据
setInterval(() => {
if (wallet) {
fetchProposals();
updatePriceChart();
}
}, 30000); // 每30秒刷新一次
});
/**
* 连接钱包
*/
async function connectWallet() {
try {
// 检查浏览器是否安装了Solana钱包扩展
if (window.solana && window.solana.isPhantom) {
console.log('Phantom钱包已检测到');
// 请求连接钱包
wallet = window.solana;
const response = await wallet.connect({ onlyIfTrusted: false });
// 显示连接成功信息
showWalletConnected(response.publicKey.toString());
// 加载数据
fetchProposals();
updatePriceChart();
// 监听钱包断开连接事件
wallet.on('disconnect', handleDisconnect);
// 监听账户变化事件
wallet.on('accountChanged', handleAccountChange);
} else {
alert('请安装Phantom钱包扩展以使用此DApp');
window.open('https://phantom.app/', '_blank');
}
} catch (error) {
console.error('连接钱包失败:', error);
alert('连接钱包失败: ' + error.message);
}
}
/**
* 处理钱包断开连接
*/
function handleDisconnect() {
console.log('钱包已断开连接');
wallet = null;
showWalletDisconnected();
}
/**
* 处理账户变化
*/
function handleAccountChange(publicKey) {
if (publicKey) {
console.log('账户已切换:', publicKey.toString());
showWalletConnected(publicKey.toString());
} else {
handleDisconnect();
}
}
/**
* 检查钱包连接状态
*/
async function checkWalletConnection() {
if (window.solana && window.solana.isPhantom) {
const response = await window.solana.connect({ onlyIfTrusted: true });
if (response.publicKey) {
wallet = window.solana;
showWalletConnected(response.publicKey.toString());
return true;
}
}
showWalletDisconnected();
return false;
}
/**
* 显示钱包已连接状态
*/
function showWalletConnected(publicKey) {
// 显示钱包地址(部分隐藏)
const shortAddress = publicKey.slice(0, 6) + '...' + publicKey.slice(-4);
document.getElementById('walletStatus').innerHTML = `
${shortAddress}
`;
// 显示功能内容
document.getElementById('walletConnectedContent').classList.remove('hidden');
document.getElementById('walletNotConnectedContent').classList.add('hidden');
}
/**
* 显示钱包未连接状态
*/
function showWalletDisconnected() {
document.getElementById('walletStatus').innerHTML = `
连接钱包
`;
// 隐藏功能内容
document.getElementById('walletConnectedContent').classList.add('hidden');
document.getElementById('walletNotConnectedContent').classList.remove('hidden');
}
/**
* 断开钱包连接
*/
async function disconnectWallet() {
if (wallet) {
await wallet.disconnect();
handleDisconnect();
}
}
/**
* 获取兑换报价
*/
async function getQuote() {
try {
if (!wallet) {
alert('请先连接钱包');
return;
}
const inputMint = document.getElementById('inputMint').value;
const outputMint = document.getElementById('outputMint').value;
const amount = parseFloat(document.getElementById('amount').value);
if (!amount || amount <= 0) {
alert('请输入有效的兑换数量');
return;
}
// 显示加载状态
document.getElementById('quoteResult').innerHTML = `
正在获取报价...
`;
document.getElementById('quoteResult').classList.remove('hidden');
// 转换为最小单位(假设6位小数)
const amountInSmallestUnit = Math.floor(amount * 1e6);
// 调用后端API获取报价
const response = await fetch(`/api/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amountInSmallestUnit}`);
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
// 显示报价结果
const inputSymbol = document.getElementById('inputMint').options[document.getElementById('inputMint').selectedIndex].text;
const outputSymbol = document.getElementById('outputMint').options[document.getElementById('outputMint').selectedIndex].text;
const outputAmount = data.outAmount / 1e6;
document.getElementById('quoteResult').innerHTML = `
报价信息:
${amount} ${inputSymbol} 可兑换约 ${outputAmount.toFixed(6)} ${outputSymbol}
滑点容忍度:0.5%
预计手续费:~${(data.fee / 1e9).toFixed(6)} SOL
`;
// 保存报价信息供后续兑换使用
localStorage.setItem('lastQuote', JSON.stringify(data));
} catch (error) {
console.error('获取报价失败:', error);
document.getElementById('quoteResult').innerHTML = `
获取报价失败: ${error.message}
`;
document.getElementById('quoteResult').classList.remove('hidden');
}
}
/**
* 执行兑换
*/
async function swap() {
try {
if (!wallet) {
alert('请先连接钱包');
return;
}
// 获取之前保存的报价信息
const lastQuote = localStorage.getItem('lastQuote');
if (!lastQuote) {
alert('请先获取报价');
return;
}
const quoteData = JSON.parse(lastQuote);
// 显示加载状态
alert('正在准备兑换交易,请在钱包中确认...');
// 调用后端API构建兑换交易
const response = await fetch('/api/swap', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
route: quoteData.route,
userPublicKey: wallet.publicKey.toString()
})
});
const swapData = await response.json();
if (swapData.error) {
throw new Error(swapData.error);
}
// 发送交易到钱包签名
const transaction = swapData.swapTransaction;
const encodedTransaction = transaction;
const decodedTransaction = new Uint8Array(atob(encodedTransaction).split('').map(c => c.charCodeAt(0)));
// 发送交易
const signature = await wallet.signAndSendTransaction(decodedTransaction);
// 显示交易结果
alert(`兑换交易已发送,交易ID: ${signature}`);
console.log('交易签名:', signature);
// 可以在这里添加交易确认轮询
} catch (error) {
console.error('兑换失败:', error);
alert('兑换失败: ' + error.message);
}
}
/**
* 绑定身份标签
*/
async function bindIdentity() {
try {
if (!wallet) {
alert('请先连接钱包');
return;
}
const tag = document.getElementById('tag').value.trim();
if (!tag) {
alert('请输入语义标签');
return;
}
// 调用后端API绑定身份
const response = await fetch('/api/identity', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
wallet: wallet.publicKey.toString(),
semanticTag: tag
})
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
// 显示结果
document.getElementById('identityResult').innerHTML = `
身份绑定成功!您的标签:${tag}
`;
document.getElementById('identityResult').classList.remove('hidden');
} catch (error) {
console.error('绑定身份失败:', error);
document.getElementById('identityResult').innerHTML = `
绑定身份失败: ${error.message}
`;
document.getElementById('identityResult').classList.remove('hidden');
}
}
/**
* 创建提案
*/
async function createProposal() {
try {
if (!wallet) {
alert('请先连接钱包');
return;
}
const title = document.getElementById('proposalTitle').value.trim();
const desc = document.getElementById('proposalDesc').value.trim();
if (!title) {
alert('请输入提案标题');
return;
}
if (!desc) {
alert('请输入提案描述');
return;
}
// 调用后端API创建提案
const response = await fetch('/api/proposal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title,
description: desc,
creatorWallet: wallet.publicKey.toString()
})
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
// 显示成功信息
alert(`提案创建成功!提案ID: ${data.proposalId}`);
// 清空输入框
document.getElementById('proposalTitle').value = '';
document.getElementById('proposalDesc').value = '';
// 刷新提案列表
fetchProposals();
} catch (error) {
console.error('创建提案失败:', error);
alert('创建提案失败: ' + error.message);
}
}
/**
* 获取提案列表
*/
async function fetchProposals() {
try {
const response = await fetch('/api/proposals');
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
const proposalsList = document.getElementById('proposalsList');
if (data.proposals && data.proposals.length > 0) {
proposalsList.innerHTML = '';
// 按创建时间排序(最新的在前)
const sortedProposals = data.proposals.sort((a, b) => b.createdAt - a.createdAt);
sortedProposals.forEach(proposal => {
const date = new Date(proposal.createdAt);
const formattedDate = date.toLocaleString();
// 简化显示的钱包地址
const shortWallet = proposal.creatorWallet.slice(0, 6) + '...' + proposal.creatorWallet.slice(-4);
const proposalElement = document.createElement('div');
proposalElement.className = 'p-4 border border-gray-200 rounded-lg hover:border-primary/30 transition-colors';
proposalElement.innerHTML = `
${proposal.title}
${formattedDate}
${proposal.description}
创建者: ${shortWallet}
投票 / 查看结果
`;
proposalsList.appendChild(proposalElement);
});
} else {
proposalsList.innerHTML = '暂无提案,创建第一个提案吧!
';
}
} catch (error) {
console.error('获取提案列表失败:', error);
}
}
/**
* 显示投票面板
*/
async function showVotingPanel(proposalId) {
try {
const panelId = `votingPanel-${proposalId}`;
const panel = document.getElementById(panelId);
// 切换面板显示状态
if (!panel.classList.contains('hidden')) {
panel.classList.add('hidden');
return;
}
// 显示加载状态
panel.innerHTML = `
加载投票信息...
`;
panel.classList.remove('hidden');
// 获取投票结果
const response = await fetch(`/api/proposal/${proposalId}/votes`);
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
// 计算支持率
const supportRate = data.totalVotes > 0
? Math.round((data.supportCount / data.totalVotes) * 100)
: 0;
// 更新面板内容
panel.innerHTML = `
支持: ${data.supportCount}
反对: ${data.opposeCount}
总票数: ${data.totalVotes}
支持率: ${supportRate}%
支持
反对
`;
} catch (error) {
console.error('获取投票信息失败:', error);
document.getElementById(`votingPanel-${proposalId}`).innerHTML = `
加载投票信息失败: ${error.message}
`;
}
}
/**
* 对提案进行投票
*/
async function voteOnProposal(proposalId, support) {
try {
if (!wallet) {
alert('请先连接钱包');
return;
}
// 调用后端API提交投票
const response = await fetch('/api/vote', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
proposalId: proposalId,
voterWallet: wallet.publicKey.toString(),
support: support
})
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
// 显示成功信息
alert('投票成功!');
// 刷新投票面板
showVotingPanel(proposalId);
} catch (error) {
console.error('投票失败:', error);
alert('投票失败: ' + error.message);
}
}
/**
* 初始化价格图表
*/
function initPriceChart() {
const ctx = document.getElementById('priceChart').getContext('2d');
// 生成模拟数据
const now = new Date();
const labels = [];
const eacoData = [];
const solData = [];
for (let i = 23; i >= 0; i--) {
const time = new Date(now - i * 3600000); // 过去24小时,每小时一个数据点
labels.push(time.getHours() + ':00');
// 生成模拟价格数据
const baseEaco = 0.5 + Math.random() * 0.3;
const baseSol = 20 + Math.random() * 5;
eacoData.push(baseEaco.toFixed(3));
solData.push(baseSol.toFixed(2));
}
// 创建图表
priceChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'EACO/USDC',
data: eacoData,
borderColor: '#165DFF',
backgroundColor: 'rgba(22, 93, 255, 0.1)',
tension: 0.4,
fill: true
},
{
label: 'SOL/USDC',
data: solData,
borderColor: '#9945FF',
backgroundColor: 'rgba(153, 69, 255, 0.1)',
tension: 0.4,
fill: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: false
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
}
});
}
/**
* 更新价格图表数据
*/
function updatePriceChart() {
if (!priceChart) return;
// 模拟新数据
const lastEaco = parseFloat(priceChart.data.datasets[0].data[priceChart.data.datasets[0].data.length - 1]);
const lastSol = parseFloat(priceChart.data.datasets[1].data[priceChart.data.datasets[1].data.length - 1]);
// 小幅波动
const newEaco = (lastEaco + (Math.random() - 0.5) * 0.05).toFixed(3);
const newSol = (lastSol + (Math.random() - 0.5) * 0.5).toFixed(2);
// 更新数据
priceChart.data.datasets[0].data.shift();
priceChart.data.datasets[0].data.push(newEaco);
priceChart.data.datasets[1].data.shift();
priceChart.data.datasets[1].data.push(newSol);
// 更新时间标签
const now = new Date();
priceChart.data.labels.shift();
priceChart.data.labels.push(now.getHours() + ':00');
priceChart.update();
}