From 223d4859d1d20cba21036c4ff93c310480be04f7 Mon Sep 17 00:00:00 2001 From: houhuan Date: Sun, 11 Jan 2026 10:58:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20=E6=B7=B1=E5=BA=A6?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20UI=20=E4=B8=BA=E7=8E=BB=E7=92=83=E6=8B=9F?= =?UTF-8?q?=E6=80=81=E9=A3=8E=E6=A0=BC=EF=BC=8C=E4=BC=98=E5=8C=96=E5=A4=A7?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=87=8F=E4=B8=8B=E7=9A=84=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E6=80=A7=E8=83=BD=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=8F=B0=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/css/style.css | 164 +++++++++++++++++++++++++++++++------------ static/js/main.js | 138 +++++++++++++++++++++++++++--------- templates/index.html | 9 ++- 3 files changed, 226 insertions(+), 85 deletions(-) diff --git a/static/css/style.css b/static/css/style.css index 407b6f1..e304f05 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,21 +1,45 @@ -/* Modern Color Palette & Reset */ +/* Modern Color Palette & Reset (UI/UX Pro Max: SaaS Analytics + Glassmorphism) */ :root { - --primary-color: #6366f1; - /* Indigo */ - --secondary-color: #8b5cf6; - /* Violet */ - --accent-color: #ec4899; - /* Pink */ - --bg-color: #f8fafc; - --text-primary: #1e293b; - --text-secondary: #64748b; - --card-bg: rgba(255, 255, 255, 0.9); - --glass-border: 1px solid rgba(255, 255, 255, 0.4); + /* Primary: Trust & Intelligence (Deep Blue/Indigo) */ + --primary-color: #4F46E5; + /* Indigo-600 */ + --primary-light: #818CF8; + + /* Secondary: Vibrant Accent (Purple/Pink for graphs/highlights) */ + --secondary-color: #D946EF; + /* Fuchsia-500 */ + + /* Backgrounds */ + --bg-color: #F8FAFC; + /* Slate-50 */ + --bg-gradient-1: #E0E7FF; + /* Indigo-100 */ + --bg-gradient-2: #FCE7F3; + /* Pink-100 */ + + /* Glassmorphism Variables */ + --glass-bg: rgba(255, 255, 255, 0.75); + --glass-border: 1px solid rgba(255, 255, 255, 0.6); + --glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07); + --blur-strength: 16px; + + /* Typography */ + --font-heading: 'Poppins', sans-serif; + --font-body: 'Open Sans', sans-serif; + + /* Text Colors */ + --text-primary: #1E293B; + /* Slate-800 */ + --text-secondary: #64748B; + /* Slate-500 */ + --text-tertiary: #94A3B8; + + /* Spacing & Radius */ + --radius-lg: 24px; + --radius-md: 16px; + --radius-sm: 8px; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --radius-lg: 16px; - --radius-md: 12px; } * { @@ -26,56 +50,74 @@ } body { - font-family: 'Inter', system-ui, -apple-system, sans-serif; + font-family: var(--font-body); background: var(--bg-color); + /* Dynamic Mesh Gradient Background */ background-image: - radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.15) 0px, transparent 50%), - radial-gradient(at 100% 100%, rgba(236, 72, 153, 0.15) 0px, transparent 50%); + radial-gradient(at 0% 0%, var(--bg-gradient-1) 0px, transparent 50%), + radial-gradient(at 100% 0%, var(--bg-gradient-2) 0px, transparent 50%), + radial-gradient(at 100% 100%, #EDF2F7 0px, transparent 50%); background-attachment: fixed; color: var(--text-primary); line-height: 1.5; min-height: 100vh; + font-size: 14px; + /* Slightly smaller base for compactness */ } /* Glassmorphism Utilities */ .glass-card { - background: var(--card-bg); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + background: var(--glass-bg); + backdrop-filter: blur(var(--blur-strength)); + -webkit-backdrop-filter: blur(var(--blur-strength)); border: var(--glass-border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-sm); + border-radius: var(--radius-md); + box-shadow: var(--glass-shadow); + transition: transform 0.2s ease, box-shadow 0.2s ease; } -/* Layout */ +.glass-card:hover { + box-shadow: 0 12px 40px 0 rgba(31, 38, 135, 0.12); +} + +/* ... Layout ... */ .app-container { - max-width: 1200px; + max-width: 1000px; + /* More compact container */ margin: 0 auto; - padding: 20px; + padding: 24px 16px; } /* Header */ .main-header { text-align: center; - padding: 40px 0 30px; + padding: 32px 0 24px; } .logo { display: inline-flex; align-items: center; gap: 12px; - color: var(--primary-color); margin-bottom: 8px; } .logo i { font-size: 28px; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; } .logo h1 { - font-size: 28px; - font-weight: 800; - letter-spacing: -0.5px; + font-family: var(--font-heading); + font-size: 26px; + font-weight: 700; + letter-spacing: -0.02em; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; } .subtitle { @@ -123,22 +165,34 @@ body { font-weight: 600; font-size: 14px; border-radius: 50px; - padding: 10px 20px; - transition: all 0.2s; + padding: 10px 24px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: inline-flex; align-items: center; gap: 8px; + position: relative; + overflow: hidden; +} + +.btn:active { + transform: scale(0.96); } .btn-lg { - padding: 12px 28px; - font-size: 15px; + padding: 14px 32px; + font-size: 16px; } .btn-primary { background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; - box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); + box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.btn-primary:hover { + box-shadow: 0 8px 25px rgba(79, 70, 229, 0.5); + transform: translateY(-2px); } .btn-primary:active { @@ -216,14 +270,23 @@ body { } .stat-card { - background: white; - padding: 20px; + background: rgba(255, 255, 255, 0.65); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + padding: 24px; border-radius: var(--radius-md); box-shadow: var(--shadow-sm); display: flex; align-items: center; - gap: 16px; - border: 1px solid #f1f5f9; + gap: 20px; + border: 1px solid rgba(255, 255, 255, 0.5); + transition: transform 0.2s; +} + +.stat-card:hover { + transform: translateY(-2px); + background: rgba(255, 255, 255, 0.85); + box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05); } .icon-circle { @@ -343,18 +406,29 @@ body { /* Daily List */ .day-card { - margin-bottom: 16px; - border: 1px solid rgba(255, 255, 255, 0.5); + margin-bottom: 20px; + border: var(--glass-border); + border-radius: var(--radius-md); overflow: hidden; + background: rgba(255, 255, 255, 0.4); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02); + transition: all 0.3s ease; +} + +.day-card:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05); } .day-header { - padding: 16px 20px; + padding: 16px 24px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; - background: rgba(255, 255, 255, 0.4); + background: rgba(255, 255, 255, 0.3); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .day-info { diff --git a/static/js/main.js b/static/js/main.js index d14721e..ca64f72 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -153,25 +153,45 @@ function displayData(data) { function renderStats(data) { let totalQty = 0; let totalAmt = 0; - const days = data.daily_summary.length; - const uniqueProducts = new Set(); // Simplified unique product count logic based on daily summary data structure might differ, here we approximate or iterate raw if needed. - // Correction: Use raw_data for unique products count if available, or iterate all dailies. + + // 1. Calculate Unique Days (based on YYYY-MM-DD) + const uniqueDates = new Set(); + + // 2. Track Unique Products + const uniqueProducts = new Set(); data.daily_summary.forEach(day => { - if (day.summary_info) { + // Extract YYYY-MM-DD from timestamp "YYYY-MM-DD HH:MM:SS" + if (day.date && day.date.length >= 10) { + uniqueDates.add(day.date.substring(0, 10)); + } + + // Sum up totals + // Priority: Use the sum of products (day.total_...) if available. + // If products are missing/zero but header info (summary_info) exists, use that. + if (day.products && day.products.length > 0) { + // Use calculated sum from products + totalQty += day.total_quantity; + totalAmt += day.total_amount; + } else if (day.summary_info) { + // Fallback to header info if no products parsed totalQty += day.summary_info.total_quantity; totalAmt += day.summary_info.total_amount; } else { totalQty += day.total_quantity; totalAmt += day.total_amount; } - day.products.forEach(p => uniqueProducts.add(p.product)); + + // Collect unique products + if (day.products) { + day.products.forEach(p => uniqueProducts.add(p.product)); + } }); // Count Animation animateValue(elements.totalAmount, totalAmt, '¥'); animateValue(elements.totalQuantity, totalQty, ''); - animateValue(elements.totalDays, days, ''); + animateValue(elements.totalDays, uniqueDates.size, ''); // Use unique dates count animateValue(elements.totalProducts, uniqueProducts.size, ''); } @@ -205,8 +225,19 @@ function animateValue(obj, end, prefix = '') { window.requestAnimationFrame(step); } +// Pagination State +const ITEMS_PER_PAGE = 20; +let displayedItems = ITEMS_PER_PAGE; + function renderDailyList(data) { - elements.dailyData.innerHTML = data.daily_summary.map(day => { + const listContainer = elements.dailyData; + const totalItems = data.daily_summary.length; + + // Slice data based on current limit + const visibleData = data.daily_summary.slice(0, displayedItems); + + // Render list + const listHTML = visibleData.map(day => { const totalAmt = day.summary_info ? day.summary_info.total_amount : day.total_amount; const totalQty = day.summary_info ? day.summary_info.total_quantity : day.total_quantity; @@ -236,6 +267,29 @@ function renderDailyList(data) { `; }).join(''); + + // Append "Show More" button if needed + let buttonHTML = ''; + if (displayedItems < totalItems) { + buttonHTML = ` +
+ +
+ `; + } + + listContainer.innerHTML = listHTML + buttonHTML; +} + +function loadMoreItems() { + displayedItems += ITEMS_PER_PAGE; + if (state.filteredData) { + renderDailyList(state.filteredData); + } else if (state.allData) { + renderDailyList(state.allData); // Fallback if no filter active + } } function toggleDayDetails(header) { @@ -246,41 +300,55 @@ function toggleDayDetails(header) { function applyFilters() { if (!state.allData) return; - let filtered = JSON.parse(JSON.stringify(state.allData)); + // Reset pagination on filter change + displayedItems = ITEMS_PER_PAGE; - // Search Filter - if (state.searchTerm) { - filtered.daily_summary = filtered.daily_summary.map(day => { - const matchedProducts = day.products.filter(p => p.product.toLowerCase().includes(state.searchTerm)); - if (matchedProducts.length > 0) { - const tQty = matchedProducts.reduce((a, b) => a + b.quantity, 0); - const tAmt = matchedProducts.reduce((a, b) => a + b.amount, 0); - return { ...day, products: matchedProducts, total_quantity: tQty, total_amount: tAmt, summary_info: null }; - } - return null; - }).filter(d => d); - } + const searchTerm = (state.searchTerm || "").trim(); + const currentFilter = state.currentFilter; - // Amount Filter - if (state.currentFilter !== 'all') { - filtered.daily_summary = filtered.daily_summary.filter(day => { - const amt = day.summary_info ? day.summary_info.total_amount : day.total_amount; - if (state.currentFilter === 'high') return amt >= 1000; - if (state.currentFilter === 'medium') return amt >= 100 && amt < 1000; - if (state.currentFilter === 'low') return amt < 100; - return true; - }); - } + // Filter without cloning for massive performance boost + const filteredItems = state.allData.daily_summary.map(day => { + // 1. Search filter + let matchedProducts = day.products; + if (searchTerm) { + matchedProducts = day.products.filter(p => + p.product.toLowerCase().includes(searchTerm) + ); + } - state.filteredData = filtered; - renderStats(filtered); - renderDailyList(filtered); + if (matchedProducts.length === 0) return null; + + // 2. Amount filter + const amt = day.summary_info ? day.summary_info.total_amount : day.total_amount; + let amountMatch = true; + + if (currentFilter === 'high') amountMatch = (amt >= 1000); + else if (currentFilter === 'medium') amountMatch = (amt >= 100 && amt < 1000); + else if (currentFilter === 'low') amountMatch = (amt < 100); + + if (amountMatch) { + return { + ...day, + products: matchedProducts, + total_quantity: searchTerm ? matchedProducts.reduce((a, b) => a + b.quantity, 0) : day.total_quantity, + total_amount: searchTerm ? matchedProducts.reduce((a, b) => a + b.amount, 0) : day.total_amount, + summary_info: searchTerm ? null : day.summary_info + }; + } + return null; + }).filter(d => d); + + state.filteredData = { daily_summary: filteredItems }; + renderStats(state.filteredData); + renderDailyList(state.filteredData); } -function filterByAmount(type) { +function filterByAmount(type, btnElement) { state.currentFilter = type; document.querySelectorAll('.filter-chip').forEach(btn => btn.classList.remove('active')); - event.target.classList.add('active'); + if (btnElement) { + btnElement.classList.add('active'); + } applyFilters(); } diff --git a/templates/index.html b/templates/index.html index 9a44f91..f486b47 100644 --- a/templates/index.html +++ b/templates/index.html @@ -7,7 +7,6 @@ 销售数据分析器 - @@ -95,10 +94,10 @@
- - - - + + + +