| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>CSGO 商品比价系统</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
-
- body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
- background: #f5f5f5;
- padding: 20px;
- }
-
- .container {
- max-width: 1400px;
- margin: 0 auto;
- background: white;
- border-radius: 12px;
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
- overflow: hidden;
- }
-
- .header {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- padding: 20px 30px;
- }
-
- .header h1 {
- font-size: 24px;
- margin-bottom: 10px;
- }
-
- .controls {
- padding: 20px 30px;
- background: #f8f9fa;
- border-bottom: 1px solid #e9ecef;
- display: flex;
- gap: 15px;
- flex-wrap: wrap;
- align-items: flex-end;
- }
-
- .control-group {
- flex: 1;
- min-width: 150px;
- }
-
- .control-group label {
- display: block;
- font-size: 12px;
- color: #6c757d;
- margin-bottom: 5px;
- }
-
- .control-group input,
- .control-group select {
- width: 100%;
- padding: 8px 12px;
- border: 1px solid #dee2e6;
- border-radius: 6px;
- font-size: 14px;
- }
-
- .exchange-group {
- display: flex;
- gap: 10px;
- }
-
- .exchange-group input {
- flex: 1;
- }
-
- .exchange-group button {
- padding: 8px 15px;
- background: #28a745;
- color: white;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- }
-
- .exchange-group button:hover {
- background: #218838;
- }
-
- .stats {
- padding: 15px 30px;
- background: #e9ecef;
- font-size: 14px;
- color: #495057;
- display: flex;
- gap: 20px;
- flex-wrap: wrap;
- }
-
- .stats span {
- font-weight: bold;
- color: #667eea;
- }
-
- table {
- width: 100%;
- border-collapse: collapse;
- }
-
- th, td {
- padding: 12px 15px;
- text-align: left;
- border-bottom: 1px solid #e9ecef;
- }
-
- th {
- background: #f8f9fa;
- font-weight: 600;
- color: #495057;
- cursor: pointer;
- user-select: none;
- }
-
- th:hover {
- background: #e9ecef;
- }
-
- th.active {
- color: #667eea;
- }
-
- tr:hover {
- background: #f8f9fa;
- }
-
- .price {
- font-weight: 600;
- color: #28a745;
- }
-
- .price-missing {
- color: #dc3545;
- font-size: 12px;
- }
-
- .item-name {
- font-weight: 500;
- max-width: 300px;
- word-break: break-word;
- }
-
- .item-type {
- font-size: 12px;
- color: #6c757d;
- }
-
- .rarity {
- display: inline-block;
- padding: 2px 8px;
- border-radius: 4px;
- font-size: 12px;
- background: #e9ecef;
- }
-
- .pagination {
- padding: 20px 30px;
- display: flex;
- justify-content: center;
- gap: 10px;
- }
-
- .pagination button {
- padding: 8px 15px;
- border: 1px solid #dee2e6;
- background: white;
- border-radius: 6px;
- cursor: pointer;
- }
-
- .pagination button:hover:not(:disabled) {
- background: #667eea;
- color: white;
- border-color: #667eea;
- }
-
- .pagination button:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
-
- .pagination .active {
- background: #667eea;
- color: white;
- border-color: #667eea;
- }
-
- .loading {
- text-align: center;
- padding: 50px;
- color: #6c757d;
- }
-
- .search-box {
- flex: 1;
- }
-
- .icon-img {
- width: 40px;
- height: 40px;
- object-fit: cover;
- border-radius: 4px;
- }
-
- @media (max-width: 768px) {
- .controls {
- flex-direction: column;
- }
-
- th, td {
- padding: 8px 10px;
- font-size: 12px;
- }
- }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>🎮 CSGO 商品比价系统</h1>
- <p>对比 Buff、Steam、CSFloat、DMarket 平台价格</p>
- </div>
-
- <div class="controls">
- <div class="control-group search-box">
- <label>🔍 搜索商品 (market_hash_name)</label>
- <input type="text" id="searchInput" placeholder="输入商品名称或哈希名称...">
- </div>
-
- <div class="control-group">
- <label>📊 排序方式</label>
- <select id="sortSelect">
- <option value="updated_at">最新更新</option>
- <option value="buff_price">Buff 价格</option>
- <option value="steam_price">Steam 价格</option>
- <option value="csfloat_price">CSFloat 价格</option>
- <option value="dmarket_price">DMarket 价格</option>
- </select>
- </div>
-
- <div class="control-group">
- <label>⬆️⬇️ 排序顺序</label>
- <select id="orderSelect">
- <option value="desc">降序</option>
- <option value="asc">升序</option>
- </select>
- </div>
-
- <div class="control-group exchange-group">
- <label>💱 美元汇率 (USD → CNY)</label>
- <input type="number" id="exchangeRate" step="0.01" value="7.2">
- <button id="setRateBtn">设置</button>
- </div>
- </div>
-
- <div class="stats" id="stats">
- <div>加载中...</div>
- </div>
-
- <div style="overflow-x: auto;">
- <table id="productTable">
- <thead>
- <tr>
- <th>图标</th>
- <th>商品名称 / 类型</th>
- <th>稀有度</th>
- <th>Buff 价格 (CNY)</th>
- <th>Steam 价格 (CNY)</th>
- <th>CSFloat 价格 (CNY)</th>
- <th>DMarket 价格 (CNY)</th>
- <th>更新时间</th>
- </tr>
- </thead>
- <tbody id="tableBody">
- <tr><td colspan="8" class="loading">加载中...</td></tr>
- </tbody>
- </table>
- </div>
-
- <div class="pagination" id="pagination"></div>
- </div>
-
- <script>
- let currentPage = 1;
- let totalPages = 1;
- let currentRate = 7.2;
-
- function loadProducts() {
- const search = document.getElementById('searchInput').value;
- const sort = document.getElementById('sortSelect').value;
- const order = document.getElementById('orderSelect').value;
-
- fetch(`/api/products?page=${currentPage}&search=${encodeURIComponent(search)}&sort=${sort}&order=${order}`)
- .then(res => res.json())
- .then(data => {
- renderTable(data.products);
- renderPagination(data);
- currentRate = data.exchange_rate;
- document.getElementById('exchangeRate').value = currentRate;
- })
- .catch(err => console.error('加载失败:', err));
- }
-
- function renderTable(products) {
- const tbody = document.getElementById('tableBody');
-
- if (!products || products.length === 0) {
- tbody.innerHTML = '<tr><td colspan="8" class="loading">暂无数据</td></tr>';
- return;
- }
-
- tbody.innerHTML = products.map(p => `
- <tr>
- <td>${p.icon_url ? `<img src="${p.icon_url}" class="icon-img" onerror="this.style.display='none'">` : '-'}</td>
- <td class="item-name">
- ${escapeHtml(p.name)}
- <div class="item-type">${p.type || '未知类型'}</div>
- <div style="font-size: 11px; color: #999;">${escapeHtml(p.market_hash_name)}</div>
- </td>
- <td><span class="rarity">${p.rarity || '普通'}</span></td>
- <td class="price">${formatPrice(p.buff_price_cny)}</td>
- <td class="price">${formatPrice(p.steam_price_cny)}</td>
- <td class="price">${formatPrice(p.csfloat_price_cny)}</td>
- <td class="price">${formatPrice(p.dmarket_price_cny)}</td>
- <td style="font-size: 12px;">${formatDate(p.updated_at)}</td>
- </tr>
- `).join('');
- }
-
- function formatPrice(price) {
- if (!price || price === 0) return '<span class="price-missing">无数据</span>';
- return `¥${parseFloat(price).toFixed(2)}`;
- }
-
- function formatDate(dateStr) {
- if (!dateStr) return '-';
- const date = new Date(dateStr);
- return date.toLocaleString('zh-CN');
- }
-
- function renderPagination(data) {
- totalPages = data.total_pages;
- const paginationDiv = document.getElementById('pagination');
-
- if (totalPages <= 1) {
- paginationDiv.innerHTML = '';
- return;
- }
-
- let html = '';
- html += `<button ${currentPage === 1 ? 'disabled' : ''} onclick="goPage(1)">首页</button>`;
- html += `<button ${currentPage === 1 ? 'disabled' : ''} onclick="goPage(${currentPage - 1})">上一页</button>`;
-
- let start = Math.max(1, currentPage - 2);
- let end = Math.min(totalPages, currentPage + 2);
-
- for (let i = start; i <= end; i++) {
- html += `<button class="${i === currentPage ? 'active' : ''}" onclick="goPage(${i})">${i}</button>`;
- }
-
- html += `<button ${currentPage === totalPages ? 'disabled' : ''} onclick="goPage(${currentPage + 1})">下一页</button>`;
- html += `<button ${currentPage === totalPages ? 'disabled' : ''} onclick="goPage(${totalPages})">末页</button>`;
-
- paginationDiv.innerHTML = html;
- }
-
- function goPage(page) {
- currentPage = page;
- loadProducts();
- }
-
- function loadStatistics() {
- fetch('/api/statistics')
- .then(res => res.json())
- .then(data => {
- document.getElementById('stats').innerHTML = `
- <div>📦 商品总数: <span>${data.total_products}</span></div>
- <div>💰 Buff 有价: <span>${data.buff_count || 0}</span></div>
- <div>💰 Steam 有价: <span>${data.steam_count || 0}</span></div>
- <div>💰 CSFloat 有价: <span>${data.csfloat_count || 0}</span></div>
- <div>💰 DMarket 有价: <span>${data.dmarket_count || 0}</span></div>
- `;
- });
- }
-
- function setExchangeRate() {
- const rate = parseFloat(document.getElementById('exchangeRate').value);
- if (isNaN(rate) || rate <= 0) {
- alert('请输入有效的汇率');
- return;
- }
-
- fetch('/api/set_exchange_rate', {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({rate: rate})
- })
- .then(res => res.json())
- .then(data => {
- if (data.success) {
- currentRate = data.rate;
- loadProducts();
- }
- });
- }
-
- function escapeHtml(str) {
- if (!str) return '';
- return str.replace(/[&<>]/g, function(m) {
- if (m === '&') return '&';
- if (m === '<') return '<';
- if (m === '>') return '>';
- return m;
- });
- }
-
- // 事件监听
- document.getElementById('searchInput').addEventListener('input', () => {
- currentPage = 1;
- loadProducts();
- });
- document.getElementById('sortSelect').addEventListener('change', () => {
- currentPage = 1;
- loadProducts();
- });
- document.getElementById('orderSelect').addEventListener('change', () => {
- currentPage = 1;
- loadProducts();
- });
- document.getElementById('setRateBtn').addEventListener('click', setExchangeRate);
-
- // 初始加载
- loadProducts();
- loadStatistics();
-
- // 每5分钟刷新统计
- setInterval(loadStatistics, 300000);
- </script>
- </body>
- </html>
|