feat(prototype): add initial prototype for item database tool
This commit introduces the initial prototype for an item database and build list tool. It establishes the foundational structure and core features of the application. Key components included: - **Documentation:** Initial design, interaction, and project structure documents (`design.md`, `interaction.md`, `outline.md`). - **Core Pages:** - `index.html`: Item management with CRUD operations. - `export.html`: CSV export configuration with drag-and-drop sorting. - `history.html`: Price history visualization with ECharts. - **Logic:** `main.js` and page-specific scripts handle client-side logic, including data management with `localStorage`, UI interactions, and animations. - **Features:** Implements core functionalities such as item creation, editing, deletion, data backup/restore, and sample data loading.
This commit is contained in:
457
prototype/main.js
vendored
Normal file
457
prototype/main.js
vendored
Normal file
@@ -0,0 +1,457 @@
|
||||
// 物品数据库管理工具 - 主要JavaScript逻辑
|
||||
|
||||
class ItemDatabase {
|
||||
constructor() {
|
||||
this.items = this.loadItems();
|
||||
this.history = this.loadHistory();
|
||||
this.init();
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
init() {
|
||||
this.setupEventListeners();
|
||||
this.renderItems();
|
||||
this.updateStats();
|
||||
this.initAnimations();
|
||||
this.loadSampleData();
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
setupEventListeners() {
|
||||
// 表单提交
|
||||
document.getElementById('add-item-form').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.addItem();
|
||||
});
|
||||
|
||||
// 搜索和筛选
|
||||
document.getElementById('search-input').addEventListener('input', (e) => {
|
||||
this.filterItems();
|
||||
});
|
||||
|
||||
document.getElementById('filter-select').addEventListener('change', (e) => {
|
||||
this.filterItems();
|
||||
});
|
||||
|
||||
// 数据导入导出
|
||||
document.getElementById('export-data-btn').addEventListener('click', () => {
|
||||
this.exportData();
|
||||
});
|
||||
|
||||
document.getElementById('import-data-btn').addEventListener('click', () => {
|
||||
document.getElementById('import-file-input').click();
|
||||
});
|
||||
|
||||
document.getElementById('import-file-input').addEventListener('change', (e) => {
|
||||
this.importData(e.target.files[0]);
|
||||
});
|
||||
|
||||
// 编辑模态框
|
||||
document.getElementById('edit-form').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.updateItem();
|
||||
});
|
||||
|
||||
document.getElementById('cancel-edit').addEventListener('click', () => {
|
||||
this.closeEditModal();
|
||||
});
|
||||
|
||||
// 点击模态框外部关闭
|
||||
document.getElementById('edit-modal').addEventListener('click', (e) => {
|
||||
if (e.target.id === 'edit-modal') {
|
||||
this.closeEditModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载示例数据
|
||||
loadSampleData() {
|
||||
if (this.items.length === 0) {
|
||||
const sampleItems = [
|
||||
{
|
||||
id: this.generateId(),
|
||||
name: "明代青花瓷瓶",
|
||||
imageUrl: "https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=400&h=300&fit=crop",
|
||||
description: "明代宣德年间的青花瓷瓶,品相完好,具有极高的收藏价值",
|
||||
tags: ["古董", "瓷器", "明代"],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: this.generateId(),
|
||||
name: "清代玉雕摆件",
|
||||
imageUrl: "https://images.unsplash.com/photo-1605883705077-8d3d848f8a9d?w=400&h=300&fit=crop",
|
||||
description: "清代乾隆时期的玉雕摆件,雕工精细,玉质温润",
|
||||
tags: ["古董", "玉器", "清代"],
|
||||
createdAt: new Date(Date.now() - 86400000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 86400000).toISOString()
|
||||
},
|
||||
{
|
||||
id: this.generateId(),
|
||||
name: "宋代书法作品",
|
||||
imageUrl: "https://images.unsplash.com/photo-1581833971358-2c8b550f87b3?w=400&h=300&fit=crop",
|
||||
description: "宋代书法名家作品,纸本墨迹,保存完好",
|
||||
tags: ["艺术品", "书法", "宋代"],
|
||||
createdAt: new Date(Date.now() - 172800000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 172800000).toISOString()
|
||||
},
|
||||
{
|
||||
id: this.generateId(),
|
||||
name: "唐代金银器",
|
||||
imageUrl: "https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=400&h=300&fit=crop",
|
||||
description: "唐代金银器,工艺精湛,具有重要历史价值",
|
||||
tags: ["古董", "金银器", "唐代"],
|
||||
createdAt: new Date(Date.now() - 259200000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 259200000).toISOString()
|
||||
},
|
||||
{
|
||||
id: this.generateId(),
|
||||
name: "现代艺术画作",
|
||||
imageUrl: "https://images.unsplash.com/photo-1579783902614-a3fb3927b6a5?w=400&h=300&fit=crop",
|
||||
description: "当代知名艺术家作品,抽象风格,色彩丰富",
|
||||
tags: ["艺术品", "现代艺术", "画作"],
|
||||
createdAt: new Date(Date.now() - 345600000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 345600000).toISOString()
|
||||
},
|
||||
{
|
||||
id: this.generateId(),
|
||||
name: "古代青铜器",
|
||||
imageUrl: "https://images.unsplash.com/photo-1605883705077-8d3d848f8a9d?w=400&h=300&fit=crop",
|
||||
description: "商周时期青铜器,纹饰精美,保存完好",
|
||||
tags: ["古董", "青铜器", "商周"],
|
||||
createdAt: new Date(Date.now() - 432000000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 432000000).toISOString()
|
||||
}
|
||||
];
|
||||
|
||||
this.items = sampleItems;
|
||||
this.saveItems();
|
||||
this.renderItems();
|
||||
this.updateStats();
|
||||
}
|
||||
}
|
||||
|
||||
// 生成唯一ID
|
||||
generateId() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
}
|
||||
|
||||
// 添加物品
|
||||
addItem() {
|
||||
const name = document.getElementById('item-name').value.trim();
|
||||
const imageUrl = document.getElementById('item-image').value.trim();
|
||||
const description = document.getElementById('item-description').value.trim();
|
||||
const tags = document.getElementById('item-tags').value.trim();
|
||||
|
||||
if (!name || !imageUrl) {
|
||||
this.showNotification('请填写必填项', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const newItem = {
|
||||
id: this.generateId(),
|
||||
name,
|
||||
imageUrl,
|
||||
description,
|
||||
tags: tags ? tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
this.items.unshift(newItem);
|
||||
this.saveItems();
|
||||
this.renderItems();
|
||||
this.updateStats();
|
||||
|
||||
// 清空表单
|
||||
document.getElementById('add-item-form').reset();
|
||||
|
||||
this.showNotification('物品添加成功', 'success');
|
||||
}
|
||||
|
||||
// 编辑物品
|
||||
editItem(itemId) {
|
||||
const item = this.items.find(i => i.id === itemId);
|
||||
if (!item) return;
|
||||
|
||||
document.getElementById('edit-item-id').value = item.id;
|
||||
document.getElementById('edit-item-name').value = item.name;
|
||||
document.getElementById('edit-item-image').value = item.imageUrl;
|
||||
document.getElementById('edit-item-description').value = item.description || '';
|
||||
document.getElementById('edit-item-tags').value = item.tags ? item.tags.join(', ') : '';
|
||||
|
||||
document.getElementById('edit-modal').classList.remove('hidden');
|
||||
document.getElementById('edit-modal').classList.add('flex');
|
||||
}
|
||||
|
||||
// 更新物品
|
||||
updateItem() {
|
||||
const itemId = document.getElementById('edit-item-id').value;
|
||||
const item = this.items.find(i => i.id === itemId);
|
||||
if (!item) return;
|
||||
|
||||
item.name = document.getElementById('edit-item-name').value.trim();
|
||||
item.imageUrl = document.getElementById('edit-item-image').value.trim();
|
||||
item.description = document.getElementById('edit-item-description').value.trim();
|
||||
const tags = document.getElementById('edit-item-tags').value.trim();
|
||||
item.tags = tags ? tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [];
|
||||
item.updatedAt = new Date().toISOString();
|
||||
|
||||
this.saveItems();
|
||||
this.renderItems();
|
||||
this.closeEditModal();
|
||||
this.showNotification('物品更新成功', 'success');
|
||||
}
|
||||
|
||||
// 关闭编辑模态框
|
||||
closeEditModal() {
|
||||
document.getElementById('edit-modal').classList.add('hidden');
|
||||
document.getElementById('edit-modal').classList.remove('flex');
|
||||
}
|
||||
|
||||
// 删除物品
|
||||
deleteItem(itemId) {
|
||||
if (confirm('确定要删除这个物品吗?')) {
|
||||
this.items = this.items.filter(item => item.id !== itemId);
|
||||
this.saveItems();
|
||||
this.renderItems();
|
||||
this.updateStats();
|
||||
this.showNotification('物品删除成功', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染物品列表
|
||||
renderItems() {
|
||||
const grid = document.getElementById('items-grid');
|
||||
const emptyState = document.getElementById('empty-state');
|
||||
|
||||
if (this.items.length === 0) {
|
||||
grid.innerHTML = '';
|
||||
emptyState.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
emptyState.classList.add('hidden');
|
||||
|
||||
const filteredItems = this.getFilteredItems();
|
||||
|
||||
grid.innerHTML = filteredItems.map(item => `
|
||||
<div class="item-card card-hover fade-in" data-id="${item.id}">
|
||||
<div class="relative">
|
||||
<img src="${item.imageUrl}" alt="${item.name}" class="item-image"
|
||||
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik0xMDAgNzBDMTA4LjI4NCA3MCA5NS4wMjU4IDc0LjY4MjMgOTkuMjY5NSA4Mi45NTYyTDg1LjM0NDIgMTA4LjI2OEM4Mi43MjI3IDExMi45NTIgODQuNjgzMSAxMTguNzcgODkuMzY2OSAxMjEuMzg5TDk5IDIxMC4wMDFMMTA5LjYzMyAxMjEuMzg5QzExNC4zMTcgMTE4Ljc3IDExNi4yNzcgMTEyLjk1MiAxMTMuNjU1IDEwOC4yNjhMOTkuNzMwNSA4Mi45NTYyQzEwMy45NzQgNzQuNjgyMyA5MC43MTU5IDcwIDEwMCA3MFoiIGZpbGw9IiM5Q0EzQUYiLz4KPC9zdmc+'">
|
||||
<div class="absolute top-2 right-2 flex space-x-1">
|
||||
<button onclick="itemDB.editItem('${item.id}')"
|
||||
class="p-2 bg-white/80 hover:bg-white rounded-lg transition-colors">
|
||||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button onclick="itemDB.deleteItem('${item.id}')"
|
||||
class="p-2 bg-white/80 hover:bg-white rounded-lg transition-colors">
|
||||
<svg class="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h4 class="font-bold text-gray-800 mb-2 truncate">${item.name}</h4>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${item.description || '暂无描述'}</p>
|
||||
<div class="flex flex-wrap gap-1 mb-3">
|
||||
${item.tags ? item.tags.map(tag => `
|
||||
<span class="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">${tag}</span>
|
||||
`).join('') : ''}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
创建时间: ${new Date(item.createdAt).toLocaleDateString('zh-CN')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// 添加动画效果
|
||||
this.animateCards();
|
||||
}
|
||||
|
||||
// 获取筛选后的物品
|
||||
getFilteredItems() {
|
||||
const searchTerm = document.getElementById('search-input').value.toLowerCase();
|
||||
const filterTag = document.getElementById('filter-select').value;
|
||||
|
||||
return this.items.filter(item => {
|
||||
const matchesSearch = item.name.toLowerCase().includes(searchTerm) ||
|
||||
item.description.toLowerCase().includes(searchTerm);
|
||||
const matchesFilter = !filterTag || (item.tags && item.tags.includes(filterTag));
|
||||
return matchesSearch && matchesFilter;
|
||||
});
|
||||
}
|
||||
|
||||
// 筛选物品
|
||||
filterItems() {
|
||||
this.renderItems();
|
||||
}
|
||||
|
||||
// 更新统计信息
|
||||
updateStats() {
|
||||
const totalItems = this.items.length;
|
||||
const currentMonth = new Date().getMonth();
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const monthlyItems = this.items.filter(item => {
|
||||
const itemDate = new Date(item.createdAt);
|
||||
return itemDate.getMonth() === currentMonth && itemDate.getFullYear() === currentYear;
|
||||
}).length;
|
||||
|
||||
const historyRecords = this.history.length;
|
||||
const totalImages = this.items.filter(item => item.imageUrl).length;
|
||||
|
||||
document.getElementById('total-items').textContent = totalItems;
|
||||
document.getElementById('monthly-items').textContent = monthlyItems;
|
||||
document.getElementById('history-records').textContent = historyRecords;
|
||||
document.getElementById('total-images').textContent = totalImages;
|
||||
|
||||
// 添加数字动画
|
||||
this.animateNumbers();
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
exportData() {
|
||||
const data = {
|
||||
items: this.items,
|
||||
history: this.history,
|
||||
exportDate: new Date().toISOString()
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `items-backup-${new Date().toISOString().split('T')[0]}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
this.showNotification('数据导出成功', 'success');
|
||||
}
|
||||
|
||||
// 导入数据
|
||||
importData(file) {
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.target.result);
|
||||
if (data.items && Array.isArray(data.items)) {
|
||||
this.items = data.items;
|
||||
this.saveItems();
|
||||
}
|
||||
if (data.history && Array.isArray(data.history)) {
|
||||
this.history = data.history;
|
||||
this.saveHistory();
|
||||
}
|
||||
this.renderItems();
|
||||
this.updateStats();
|
||||
this.showNotification('数据导入成功', 'success');
|
||||
} catch (error) {
|
||||
this.showNotification('数据格式错误', 'error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
showNotification(message, type = 'info') {
|
||||
const notification = document.getElementById('notification');
|
||||
const messageEl = document.getElementById('notification-message');
|
||||
const iconEl = document.getElementById('notification-icon');
|
||||
|
||||
// 设置图标
|
||||
const icons = {
|
||||
success: '<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>',
|
||||
error: '<svg class="w-6 h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>',
|
||||
info: '<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>'
|
||||
};
|
||||
|
||||
iconEl.innerHTML = icons[type] || icons.info;
|
||||
messageEl.textContent = message;
|
||||
|
||||
notification.classList.add('show');
|
||||
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('show');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 初始化动画
|
||||
initAnimations() {
|
||||
// 页面加载动画
|
||||
const fadeElements = document.querySelectorAll('.fade-in');
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fadeElements.forEach(el => observer.observe(el));
|
||||
|
||||
// 标题动画
|
||||
anime({
|
||||
targets: '.hero-title',
|
||||
opacity: [0, 1],
|
||||
translateY: [30, 0],
|
||||
duration: 1000,
|
||||
easing: 'easeOutExpo',
|
||||
delay: 300
|
||||
});
|
||||
}
|
||||
|
||||
// 卡片动画
|
||||
animateCards() {
|
||||
anime({
|
||||
targets: '.item-card',
|
||||
opacity: [0, 1],
|
||||
translateY: [20, 0],
|
||||
duration: 600,
|
||||
easing: 'easeOutExpo',
|
||||
delay: anime.stagger(100)
|
||||
});
|
||||
}
|
||||
|
||||
// 数字动画
|
||||
animateNumbers() {
|
||||
anime({
|
||||
targets: '#total-items, #monthly-items, #history-records, #total-images',
|
||||
scale: [1.2, 1],
|
||||
duration: 300,
|
||||
easing: 'easeOutBack'
|
||||
});
|
||||
}
|
||||
|
||||
// 数据持久化
|
||||
saveItems() {
|
||||
localStorage.setItem('itemdb-items', JSON.stringify(this.items));
|
||||
}
|
||||
|
||||
loadItems() {
|
||||
const data = localStorage.getItem('itemdb-items');
|
||||
return data ? JSON.parse(data) : [];
|
||||
}
|
||||
|
||||
saveHistory() {
|
||||
localStorage.setItem('itemdb-history', JSON.stringify(this.history));
|
||||
}
|
||||
|
||||
loadHistory() {
|
||||
const data = localStorage.getItem('itemdb-history');
|
||||
return data ? JSON.parse(data) : [];
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
const itemDB = new ItemDatabase();
|
||||
Reference in New Issue
Block a user