🆕 Add scan history feature with UI and localStorage functionality
This commit is contained in:
@@ -233,6 +233,127 @@
|
||||
.loading.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.history-section {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 2px solid #f0f4ff;
|
||||
}
|
||||
|
||||
.history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.history-title {
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.clear-history-btn {
|
||||
padding: 8px 16px;
|
||||
background: #ffe6e6;
|
||||
color: #d63031;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.clear-history-btn:hover {
|
||||
background: #ffd6d6;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 12px;
|
||||
background: #f8f9ff;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.history-item:hover {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.history-text {
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.history-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.history-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.delete-btn, .copy-history-btn {
|
||||
padding: 6px 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #ffe6e6;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
color: #d63031;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background: #d63031;
|
||||
color: white;
|
||||
border-color: #d63031;
|
||||
}
|
||||
|
||||
.copy-history-btn {
|
||||
color: #667eea;
|
||||
border-color: #e8eaff;
|
||||
}
|
||||
|
||||
.copy-history-btn:hover {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.copy-history-btn.copied {
|
||||
background: #00b894;
|
||||
color: white;
|
||||
border-color: #00b894;
|
||||
}
|
||||
|
||||
.empty-history {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -258,6 +379,14 @@
|
||||
<div class="result-text" id="resultText"></div>
|
||||
<button class="copy-btn" id="copyBtn">📋 Copy</button>
|
||||
</div>
|
||||
|
||||
<div class="history-section" id="historySection">
|
||||
<div class="history-header">
|
||||
<div class="history-title">📜 Scan History</div>
|
||||
<button class="clear-history-btn" id="clearHistoryBtn">🗑️ Clear All</button>
|
||||
</div>
|
||||
<div class="history-list" id="historyList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="camera-modal" id="cameraModal">
|
||||
@@ -280,9 +409,106 @@
|
||||
const video = document.getElementById('video');
|
||||
const cameraStatus = document.getElementById('cameraStatus');
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
const historyList = document.getElementById('historyList');
|
||||
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
|
||||
const HISTORY_KEY = 'qr_scan_history';
|
||||
let stream = null;
|
||||
let scanInterval = null;
|
||||
|
||||
function loadHistory() {
|
||||
const stored = localStorage.getItem(HISTORY_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
}
|
||||
|
||||
function saveHistory(history) {
|
||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
|
||||
}
|
||||
|
||||
function addToHistory(text) {
|
||||
const history = loadHistory();
|
||||
const existingIndex = history.findIndex(item => item.text === text);
|
||||
if (existingIndex !== -1) {
|
||||
history.splice(existingIndex, 1);
|
||||
}
|
||||
history.unshift({ text, timestamp: Date.now() });
|
||||
if (history.length > 50) {
|
||||
history.pop();
|
||||
}
|
||||
saveHistory(history);
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
function deleteFromHistory(index) {
|
||||
const history = loadHistory();
|
||||
history.splice(index, 1);
|
||||
saveHistory(history);
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
localStorage.removeItem(HISTORY_KEY);
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
function renderHistory() {
|
||||
const history = loadHistory();
|
||||
if (history.length === 0) {
|
||||
historyList.innerHTML = '<div class="empty-history">No scan history yet</div>';
|
||||
return;
|
||||
}
|
||||
historyList.innerHTML = history.map((item, index) => {
|
||||
const date = new Date(item.timestamp);
|
||||
const timeStr = date.toLocaleString();
|
||||
const displayText = item.text.length > 100 ? item.text.substring(0, 100) + '...' : item.text;
|
||||
return `
|
||||
<div class="history-item">
|
||||
<div class="history-text">
|
||||
<div>${escapeHtml(displayText)}</div>
|
||||
<div class="history-time">${timeStr}</div>
|
||||
</div>
|
||||
<div class="history-actions">
|
||||
<button class="copy-history-btn" data-index="${index}">📋 Copy</button>
|
||||
<button class="delete-btn" data-index="${index}">🗑️ Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
historyList.querySelectorAll('.delete-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const index = parseInt(e.target.dataset.index);
|
||||
deleteFromHistory(index);
|
||||
});
|
||||
});
|
||||
historyList.querySelectorAll('.copy-history-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const index = parseInt(e.target.dataset.index);
|
||||
const text = history[index].text;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
e.target.textContent = '✓ Copied!';
|
||||
e.target.classList.add('copied');
|
||||
setTimeout(() => {
|
||||
e.target.textContent = '📋 Copy';
|
||||
e.target.classList.remove('copied');
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
clearHistoryBtn.addEventListener('click', () => {
|
||||
if (confirm('Are you sure you want to clear all scan history?')) {
|
||||
clearHistory();
|
||||
}
|
||||
});
|
||||
|
||||
renderHistory();
|
||||
|
||||
dropZone.addEventListener('click', () => fileInput.click());
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
@@ -371,6 +597,7 @@
|
||||
function showResult(text) {
|
||||
resultText.textContent = text;
|
||||
result.classList.add('visible');
|
||||
addToHistory(text);
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
|
||||
Reference in New Issue
Block a user