diff --git a/app/static/css/product_detail.css b/app/static/css/product_detail.css index c2d84fc..45796ed 100644 --- a/app/static/css/product_detail.css +++ b/app/static/css/product_detail.css @@ -5,6 +5,125 @@ } .product-card:hover { + +/* 规格选择样式 */ +.specs-section { + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 20px; + background-color: #f8f9fa; +} + +.spec-group { + margin-bottom: 15px; +} + +.spec-group:last-child { + margin-bottom: 0; +} + +.spec-group .form-label { + font-weight: 600; + margin-bottom: 10px; + color: #495057; +} + +.spec-options { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.spec-option { + min-width: 60px; + padding: 8px 16px; + border: 1px solid #dee2e6; + border-radius: 6px; + background-color: #fff; + color: #495057; + cursor: pointer; + transition: all 0.2s ease; + font-size: 14px; +} + +.spec-option:hover { + border-color: #007bff; + background-color: #e3f2fd; +} + +.spec-option.btn-primary { + background-color: #007bff; + border-color: #007bff; + color: white; +} + +.spec-option.btn-primary:hover { + background-color: #0056b3; + border-color: #0056b3; +} + +.spec-option:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: #e9ecef; +} + +/* 库存信息样式 */ +.stock-section { + padding: 15px; + background-color: #f8f9fa; + border-radius: 8px; + border: 1px solid #dee2e6; +} + +#stockCount { + font-weight: 600; + font-size: 1.1em; +} + +/* 数量选择样式 */ +.quantity-section .input-group { + max-width: 150px; +} + +.quantity-section .form-control { + text-align: center; + font-weight: 600; +} + +/* 操作按钮样式 */ +.action-buttons .btn { + padding: 12px 24px; + font-weight: 600; + border-radius: 8px; +} + +.action-buttons .btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* 服务承诺样式 */ +.service-promises { + background-color: #f8f9fa; + padding: 15px; + border-radius: 8px; + border: 1px solid #dee2e6; +} + +.service-promises h6 { + color: #495057; + margin-bottom: 10px; +} + +.service-promises li { + margin-bottom: 5px; + color: #6c757d; +} + +.service-promises .bi-check-circle { + margin-right: 8px; +} transform: translateY(-5px); box-shadow: 0 4px 15px rgba(0,0,0,0.1); } diff --git a/app/static/js/product_detail.js b/app/static/js/product_detail.js index be38f49..e5aa7f0 100644 --- a/app/static/js/product_detail.js +++ b/app/static/js/product_detail.js @@ -9,6 +9,23 @@ document.addEventListener('DOMContentLoaded', function() { if (inventoryData.length === 1) { currentSku = inventoryData[0]; updateStockInfo(); + } else if (inventoryData.length > 1) { + // 自动选择默认规格 + const defaultSku = inventoryData.find(sku => sku.is_default); + if (defaultSku && defaultSku.spec_combination) { + // 自动选择默认规格 + for (const [specName, specValue] of Object.entries(defaultSku.spec_combination)) { + selectedSpecs[specName] = specValue; + // 更新按钮状态 + const button = document.querySelector(`[data-spec-name="${specName}"][data-spec-value="${specValue}"]`); + if (button) { + button.classList.remove('btn-outline-secondary'); + button.classList.add('btn-primary'); + } + } + currentSku = defaultSku; + updateStockInfo(); + } } // 绑定规格选择事件 @@ -107,16 +124,43 @@ function selectSpec(button) { // 查找匹配的SKU function findMatchingSku() { + // 获取所有需要选择的规格类型 + const allSpecNames = new Set(); + inventoryData.forEach(sku => { + if (sku.spec_combination) { + Object.keys(sku.spec_combination).forEach(specName => { + allSpecNames.add(specName); + }); + } + }); + + // 检查是否选择了所有必需的规格 + const selectedSpecNames = Object.keys(selectedSpecs); + if (selectedSpecNames.length < allSpecNames.size) { + currentSku = null; + updateStockInfo(); + return; + } + + // 查找完全匹配的SKU for (let sku of inventoryData) { if (sku.spec_combination) { let isMatch = true; + + // 检查规格数量是否匹配 + if (Object.keys(sku.spec_combination).length !== selectedSpecNames.length) { + continue; + } + + // 检查每个规格是否匹配 for (let [specName, specValue] of Object.entries(selectedSpecs)) { if (sku.spec_combination[specName] !== specValue) { isMatch = false; break; } } - if (isMatch && Object.keys(selectedSpecs).length === Object.keys(sku.spec_combination).length) { + + if (isMatch) { currentSku = sku; updateStockInfo(); return; @@ -138,24 +182,38 @@ function updateStockInfo() { const quantityInput = document.getElementById('quantity'); if (currentSku) { + // 有选中的SKU stockElement.textContent = currentSku.stock; - stockElement.className = currentSku.stock > 0 ? 'text-success' : 'text-danger'; - priceElement.textContent = currentSku.final_price.toFixed(2); - if (currentSku.stock > 0) { + stockElement.className = 'text-success'; addToCartBtn.disabled = false; buyNowBtn.disabled = false; quantityInput.max = currentSku.stock; + quantityInput.value = Math.min(parseInt(quantityInput.value), currentSku.stock); } else { + stockElement.className = 'text-danger'; addToCartBtn.disabled = true; buyNowBtn.disabled = true; quantityInput.max = 0; + quantityInput.value = 1; } + + // 更新价格 + priceElement.textContent = currentSku.final_price.toFixed(2); } else if (inventoryData.length > 1) { + // 多规格商品但未完全选择 stockElement.textContent = '请选择规格'; stockElement.className = 'text-warning'; addToCartBtn.disabled = true; buyNowBtn.disabled = true; + quantityInput.max = 999; + } else { + // 无库存或其他错误 + stockElement.textContent = '暂无库存'; + stockElement.className = 'text-danger'; + addToCartBtn.disabled = true; + buyNowBtn.disabled = true; + quantityInput.max = 0; } } @@ -189,6 +247,15 @@ function loadCartCount() { }); } +// 更新购物车徽章 +function updateCartBadge(count) { + const badge = document.querySelector('.cart-badge'); + if (badge) { + badge.textContent = count; + badge.style.display = count > 0 ? 'inline' : 'none'; + } +} + // 加入购物车 function addToCart() { if (!currentSku) { @@ -207,6 +274,10 @@ function addToCart() { addToCartBtn.disabled = true; addToCartBtn.innerHTML = ' 添加中...'; + // 准备规格组合数据 + const specCombination = Object.keys(selectedSpecs).length > 0 ? + Object.entries(selectedSpecs).map(([key, value]) => `${key}:${value}`).join(', ') : ''; + // 提交到购物车 fetch('/cart/add', { method: 'POST', @@ -216,7 +287,7 @@ function addToCart() { body: JSON.stringify({ product_id: window.productId, sku_code: currentSku.sku_code, - spec_combination: Object.keys(selectedSpecs).length > 0 ? JSON.stringify(selectedSpecs) : '', + spec_combination: specCombination, quantity: quantity }) }) @@ -260,6 +331,15 @@ function buyNow() { return; } + // 禁用按钮,防止重复点击 + const buyNowBtn = document.getElementById('buyNowBtn'); + buyNowBtn.disabled = true; + buyNowBtn.innerHTML = ' 处理中...'; + + // 准备规格组合数据 + const specCombination = Object.keys(selectedSpecs).length > 0 ? + Object.entries(selectedSpecs).map(([key, value]) => `${key}:${value}`).join(', ') : ''; + // 先添加到购物车,然后跳转到结算页面 fetch('/cart/add', { method: 'POST', @@ -269,7 +349,7 @@ function buyNow() { body: JSON.stringify({ product_id: window.productId, sku_code: currentSku.sku_code, - spec_combination: Object.keys(selectedSpecs).length > 0 ? JSON.stringify(selectedSpecs) : '', + spec_combination: specCombination, quantity: quantity }) }) @@ -285,6 +365,11 @@ function buyNow() { .catch(error => { console.error('Error:', error); alert('购买失败,请稍后再试'); + }) + .finally(() => { + // 恢复按钮状态 + buyNowBtn.disabled = false; + buyNowBtn.innerHTML = ' 立即购买'; }); } @@ -304,8 +389,6 @@ function addToFavorites() { return; } - console.log('收藏商品ID:', productId); // 调试信息 - const favoriteBtn = document.querySelector('[onclick="addToFavorites()"]'); const isFavorited = favoriteBtn && favoriteBtn.innerHTML.includes('已收藏'); diff --git a/app/templates/product/detail.html b/app/templates/product/detail.html index afe22aa..129ca16 100644 --- a/app/templates/product/detail.html +++ b/app/templates/product/detail.html @@ -141,7 +141,11 @@ 库存: {% if inventory_list %} - {{ inventory_list[0].stock if inventory_list|length == 1 else '请选择规格' }} + {% if inventory_list|length == 1 %} + {{ inventory_list[0].stock }} + {% else %} + 请选择规格 + {% endif %} {% else %} 暂无库存 {% endif %} diff --git a/create_test_product_with_specs.py b/create_test_product_with_specs.py new file mode 100644 index 0000000..1811267 --- /dev/null +++ b/create_test_product_with_specs.py @@ -0,0 +1,114 @@ +""" +创建带规格的测试商品 +""" +from app.models.product import Product, Category, ProductInventory, SpecName, SpecValue +from config.database import db +import json + +def create_test_product(): + """创建测试商品""" + try: + # 创建或获取测试分类 + category = Category.query.filter_by(name='测试分类').first() + if not category: + category = Category( + name='测试分类', + parent_id=0, + level=1, + sort_order=1, + is_active=1 + ) + db.session.add(category) + db.session.flush() + + # 创建测试商品 + product = Product( + name='测试商品 - 多规格T恤', + category_id=category.id, + brand='测试品牌', + price=99.00, + original_price=129.00, + description='这是一件多规格测试商品,支持颜色和尺寸选择', + has_specs=1, + status=1, + weight=0.3 + ) + db.session.add(product) + db.session.flush() + + # 创建规格名称(如果不存在) + color_spec = SpecName.query.filter_by(name='颜色').first() + if not color_spec: + color_spec = SpecName(name='颜色', sort_order=1) + db.session.add(color_spec) + db.session.flush() + + size_spec = SpecName.query.filter_by(name='尺寸').first() + if not size_spec: + size_spec = SpecName(name='尺寸', sort_order=2) + db.session.add(size_spec) + db.session.flush() + + # 创建规格值(如果不存在) + colors = ['红色', '蓝色', '黑色'] + sizes = ['S', 'M', 'L', 'XL'] + + for color in colors: + color_value = SpecValue.query.filter_by(spec_name_id=color_spec.id, value=color).first() + if not color_value: + color_value = SpecValue(spec_name_id=color_spec.id, value=color) + db.session.add(color_value) + + for size in sizes: + size_value = SpecValue.query.filter_by(spec_name_id=size_spec.id, value=size).first() + if not size_value: + size_value = SpecValue(spec_name_id=size_spec.id, value=size) + db.session.add(size_value) + + db.session.flush() + + # 创建商品库存(多个SKU) + sku_data = [ + {'color': '红色', 'size': 'S', 'stock': 10, 'price_adj': 0, 'is_default': 1}, + {'color': '红色', 'size': 'M', 'stock': 15, 'price_adj': 0, 'is_default': 0}, + {'color': '红色', 'size': 'L', 'stock': 12, 'price_adj': 0, 'is_default': 0}, + {'color': '蓝色', 'size': 'S', 'stock': 8, 'price_adj': 5, 'is_default': 0}, + {'color': '蓝色', 'size': 'M', 'stock': 20, 'price_adj': 5, 'is_default': 0}, + {'color': '蓝色', 'size': 'L', 'stock': 18, 'price_adj': 5, 'is_default': 0}, + {'color': '黑色', 'size': 'M', 'stock': 25, 'price_adj': -5, 'is_default': 0}, + {'color': '黑色', 'size': 'L', 'stock': 22, 'price_adj': -5, 'is_default': 0}, + {'color': '黑色', 'size': 'XL', 'stock': 15, 'price_adj': 10, 'is_default': 0}, + ] + + for i, sku_info in enumerate(sku_data): + spec_combination = {'颜色': sku_info['color'], '尺寸': sku_info['size']} + sku_code = f"TST-{sku_info['color'][:1]}{sku_info['size']}-{product.id:03d}" + + inventory = ProductInventory( + product_id=product.id, + sku_code=sku_code, + spec_combination=spec_combination, + price_adjustment=sku_info['price_adj'], + stock=sku_info['stock'], + warning_stock=5, + is_default=sku_info['is_default'], + status=1 + ) + db.session.add(inventory) + + db.session.commit() + print(f"成功创建测试商品: {product.name} (ID: {product.id})") + print(f"创建了 {len(sku_data)} 个SKU") + return product.id + + except Exception as e: + db.session.rollback() + print(f"创建失败: {str(e)}") + return None + +if __name__ == '__main__': + from app import create_app + app = create_app() + + with app.app_context(): + create_test_product() diff --git a/project_code_export_20250704_033545.txt b/project_code_export_20250709_052912.txt similarity index 60% rename from project_code_export_20250704_033545.txt rename to project_code_export_20250709_052912.txt index aa0b436..81a7fea 100644 --- a/project_code_export_20250704_033545.txt +++ b/project_code_export_20250709_052912.txt @@ -2,20 +2,22 @@ 项目代码导出文件 ================================================================================ 项目名称: 基于Python的线上电商系统 -导出时间: 2025-07-04 03:35:45 +导出时间: 2025-07-09 05:29:12 项目路径: /Users/lishunqin/Desktop/Online_shopping_platform -文件总数: 83 +文件总数: 140 ================================================================================ 📁 文件目录: -------------------------------------------------- -README.md (0.0 KB) -app/__init__.py (2.5 KB) +README.md (7.4 KB) +app/__init__.py (1.3 KB) app/forms.py (5.2 KB) -app/models/__init__.py (0.8 KB) +app/models/__init__.py (0.9 KB) app/models/address.py (2.8 KB) app/models/admin.py (1.8 KB) +app/models/browse_history.py (3.8 KB) app/models/cart.py (4.5 KB) +app/models/favorite.py (3.4 KB) app/models/operation_log.py (1.8 KB) app/models/order.py (6.7 KB) app/models/payment.py (2.3 KB) @@ -23,40 +25,91 @@ app/models/product.py (9.4 KB) app/models/review.py (2.2 KB) app/models/user.py (1.7 KB) app/models/verification.py (1.8 KB) +app/static/css/address_form.css (1.4 KB) +app/static/css/addresses.css (1.3 KB) +app/static/css/admin_base.css (2.0 KB) +app/static/css/admin_categories.css (2.7 KB) +app/static/css/admin_dashboard.css (0.6 KB) +app/static/css/admin_login.css (1.4 KB) +app/static/css/admin_logs.css (5.0 KB) +app/static/css/admin_orders.css (3.7 KB) +app/static/css/admin_profile.css (4.3 KB) +app/static/css/admin_users.css (6.9 KB) +app/static/css/auth.css (1.2 KB) +app/static/css/base.css (0.6 KB) +app/static/css/cart.css (0.4 KB) +app/static/css/checkout.css (2.7 KB) +app/static/css/favorites.css (2.0 KB) +app/static/css/history.css (2.3 KB) +app/static/css/index.css (1.6 KB) +app/static/css/order_detail.css (2.1 KB) +app/static/css/orders.css (2.8 KB) +app/static/css/pay.css (2.1 KB) +app/static/css/product_detail.css (6.5 KB) +app/static/css/product_list.css (0.3 KB) +app/static/css/profile.css (5.4 KB) +app/static/css/register.css (0.5 KB) +app/static/css/review.css (14.2 KB) +app/static/js/address_form.js (7.2 KB) +app/static/js/addresses.js (2.0 KB) +app/static/js/admin_categories.js (9.0 KB) +app/static/js/admin_dashboard.js (2.3 KB) +app/static/js/admin_logs.js (11.3 KB) +app/static/js/admin_orders.js (6.7 KB) +app/static/js/admin_users.js (12.5 KB) +app/static/js/base.js (5.3 KB) +app/static/js/cart.js (6.1 KB) +app/static/js/checkout.js (4.7 KB) app/static/js/city_data.js (54.3 KB) -app/templates/admin/base.html (7.7 KB) -app/templates/admin/categories.html (26.3 KB) -app/templates/admin/dashboard.html (7.9 KB) -app/templates/admin/login.html (4.5 KB) -app/templates/admin/orders.html (0.0 KB) +app/static/js/favorites.js (6.6 KB) +app/static/js/history.js (8.0 KB) +app/static/js/order_detail.js (1.5 KB) +app/static/js/orders.js (3.4 KB) +app/static/js/pay.js (10.3 KB) +app/static/js/product_detail.js (13.2 KB) +app/static/js/product_list.js (0.2 KB) +app/static/js/profile.js (10.7 KB) +app/static/js/register.js (4.0 KB) +app/static/js/review.js (21.6 KB) +app/templates/admin/base.html (5.0 KB) +app/templates/admin/categories.html (17.6 KB) +app/templates/admin/dashboard.html (7.3 KB) +app/templates/admin/login.html (2.7 KB) +app/templates/admin/logs.html (11.0 KB) +app/templates/admin/order_detail.html (19.8 KB) +app/templates/admin/orders.html (17.4 KB) app/templates/admin/product_form.html (32.9 KB) app/templates/admin/products.html (17.6 KB) -app/templates/admin/profile.html (5.6 KB) -app/templates/admin/users.html (0.0 KB) -app/templates/base.html (11.4 KB) -app/templates/cart/index.html (16.2 KB) +app/templates/admin/profile.html (6.3 KB) +app/templates/admin/users.html (12.7 KB) +app/templates/base.html (5.8 KB) +app/templates/cart/index.html (9.9 KB) app/templates/common/footer.html (0.0 KB) app/templates/common/header.html (0.0 KB) app/templates/common/pagination.html (0.0 KB) -app/templates/index.html (9.5 KB) -app/templates/order/checkout.html (14.9 KB) -app/templates/order/detail.html (14.0 KB) -app/templates/order/pay.html (10.5 KB) -app/templates/product/detail.html (24.7 KB) -app/templates/product/list.html (13.6 KB) +app/templates/index.html (9.1 KB) +app/templates/order/checkout.html (12.9 KB) +app/templates/order/detail.html (12.6 KB) +app/templates/order/pay.html (7.9 KB) +app/templates/product/detail.html (18.1 KB) +app/templates/product/list.html (13.5 KB) +app/templates/review/my_reviews.html (9.0 KB) +app/templates/review/write.html (6.0 KB) app/templates/test_upload.html (14.5 KB) -app/templates/user/address_form.html (14.4 KB) -app/templates/user/addresses.html (7.4 KB) -app/templates/user/login.html (2.3 KB) -app/templates/user/orders.html (13.9 KB) -app/templates/user/profile.html (26.5 KB) -app/templates/user/register.html (9.4 KB) +app/templates/user/address_form.html (7.4 KB) +app/templates/user/addresses.html (6.4 KB) +app/templates/user/favorites.html (11.3 KB) +app/templates/user/history.html (12.3 KB) +app/templates/user/login.html (2.7 KB) +app/templates/user/orders.html (13.6 KB) +app/templates/user/profile.html (12.4 KB) +app/templates/user/register.html (5.4 KB) app/utils/__init__.py (0.0 KB) app/utils/auth.py (0.0 KB) app/utils/cos_client.py (7.7 KB) app/utils/cos_upload.py (0.0 KB) app/utils/database.py (1.0 KB) -app/utils/decorators.py (10.0 KB) +app/utils/decorators.py (3.1 KB) app/utils/email_service.py (2.4 KB) app/utils/file_upload.py (12.4 KB) app/utils/helpers.py (0.0 KB) @@ -64,13 +117,16 @@ app/utils/sms.py (0.0 KB) app/utils/wechat_pay.py (0.0 KB) app/views/__init__.py (0.0 KB) app/views/address.py (8.0 KB) -app/views/admin.py (7.7 KB) +app/views/admin.py (17.9 KB) app/views/auth.py (4.8 KB) app/views/cart.py (7.4 KB) +app/views/favorite.py (7.3 KB) +app/views/history.py (4.7 KB) app/views/main.py (6.4 KB) app/views/order.py (10.7 KB) -app/views/payment.py (6.6 KB) +app/views/payment.py (8.0 KB) app/views/product.py (23.2 KB) +app/views/review.py (8.9 KB) app/views/upload.py (5.3 KB) app/views/user.py (1.0 KB) check_avatar.py (0.6 KB) @@ -82,6 +138,7 @@ create_admin.py (5.2 KB) create_sample_categories.py (4.7 KB) create_sample_specs.py (2.0 KB) create_test_order.py (2.8 KB) +create_test_product_with_specs.py (4.3 KB) create_test_user.py (2.0 KB) docker/.dockerignore (0.0 KB) docker/Dockerfile (0.0 KB) @@ -97,114 +154,270 @@ test_email_detailed.py (8.3 KB) 🔸============================================================================== 📄 文件: README.md -📊 大小: 0 bytes (0.00 KB) -🕒 修改时间: 2025-07-03 02:46:14 +📊 大小: 7559 bytes (7.38 KB) +🕒 修改时间: 2025-07-04 19:06:52 🔸============================================================================== +
+ +# 🛍️ Python Flask 电商系统 🛍️ + +**一个功能完善、技术栈现代、可直接部署的线上电商平台毕业设计项目。** + +![Python](https://img.shields.io/badge/Python-3.8%2B-blue?style=for-the-badge&logo=python)![Flask](https://img.shields.io/badge/Flask-2.x-black?style=for-the-badge&logo=flask)![MySQL](https://img.shields.io/badge/MySQL-5.7%2B-orange?style=for-the-badge&logo=mysql)![Docker](https://img.shields.io/badge/Docker-Ready-blue?style=for-the-badge&logo=docker)![Tencent Cloud](https://img.shields.io/badge/Tencent_Cloud-COS_%26_CDN-red?style=for-the-badge&logo=tencent-cloud) + +
+ +--- + +## ✨ 项目简介 + +本项目是一个基于 **Python + Flask** 构建的全功能线上电商系统。它涵盖了从用户注册登录、商品浏览、购物车、下单支付到后台管理的全流程。项目架构清晰,代码结构规范,并集成了**腾讯云COS**、**CDN**等云服务,最终可通过 **Docker** 实现快速、标准化的部署。 + +## 🚀 技术栈概览 + +| 分类 | 技术 | 描述 | +| :--- | :--- | :--- | +| 🖥️ **后端** | `Python`, `Flask`, `SQLAlchemy` | 核心开发语言、Web框架与ORM | +| 🗃️ **数据库** | `MySQL` | 持久化数据存储 | +| 🎨 **前端** | `HTML`, `CSS`, `JavaScript`, `Bootstrap` | 页面构建、样式与交互逻辑 | +| ☁️ **云服务** | `腾讯云COS`, `腾讯云CDN`, `微信支付` | 对象存储、内容分发网络与支付服务 | +| 🐳 **部署** | `Docker`, `Nginx` | 容器化部署与反向代理 | +| ⚙️ **工具** | `Flask-Login`, `Flask-WTF`, `Flask-Mail` | 用户认证、表单处理、邮件服务 | + +## 🌟 系统核心功能 + +
+🛍️ 用户端功能 (点击展开) + +- **👤 用户中心** + - 手机/邮箱注册登录,支持短信/邮箱验证码 + - 微信授权登录(可选) + - 个人信息编辑(昵称、头像、性别等) + - 头像上传至腾讯云COS + - 收货地址管理(增删改查、设为默认) + - 我的收藏夹 & 浏览历史 + +- **🛒 购物流程** + - 多级商品分类导航 + - 商品列表(分页、排序、筛选、搜索) + - 商品详情页(轮播图、规格选择、用户评价) + - 购物车(添加、修改数量、删除、结算) + - 未登录用户购物车(`localStorage` 支持) + +- **💳 订单与支付** + - 创建订单,填写备注 + - **微信支付**(PC端扫码、移动端JSAPI) + - 订单状态跟踪(待支付、待发货、待收货、待评价...) + - 查看订单详情与物流信息 + - 取消订单、申请退款、确认收货 + +- **✍️ 评价系统** + - 对已完成订单的商品进行评价(评分、文字、图片) + - 匿名评价选项 + +
+ +
+🔧 管理后台功能 (点击展开) + +- **📊 数据看板 (Dashboard)** + - 销售额、订单量、用户增长等核心指标可视化 + +- **📦 商品管理** + - 商品分类的增删改查 + - 商品信息管理(上架/下架、编辑、库存、价格) + - 商品规格与属性管理 + +- **📋 订单管理** + - 按状态筛选和搜索订单 + - 查看订单详情 + - 执行发货操作(填写物流信息) + - 处理用户退款申请 + +- **👥 用户管理** + - 查看用户列表 + - 禁用/启用用户账户 + +- **⚙️ 系统设置** + - 网站基本信息配置 + - 支付接口与云存储配置 + +
+ +## 📂 项目结构 + +``` +Online_shopping_platform/ +├── app/ # 核心应用目录 +│ ├── models/ # 📦 数据模型 (ORM) +│ ├── views/ # 👁️ 视图函数 (Controllers) +│ ├── templates/ # 📄 HTML模板 +│ ├── static/ # 🎨 静态资源 (CSS, JS, Images) +│ ├── utils/ # 🛠️ 工具模块 (支付, COS, 邮件等) +│ ├── forms.py # 📝 表单定义 +│ └── __init__.py # 🚀 应用工厂函数 +├── config/ # ⚙️ 配置文件 +├── docker/ # 🐳 Docker 相关配置 +├── logs/ # 📜 日志文件 +├── tests/ # 🧪 测试用例 +├── requirements.txt # 📦 Python 依赖 +├── run.py # ▶️ 应用启动脚本 +└── README.md # 📖 你正在阅读的文件 +``` + +## 🗄️ 数据库设计 + +项目数据库设计遵循电商业务逻辑,结构清晰,关系明确。 + +
+查看核心数据表 (点击展开) + +| 表名 | 用途 | +| :--- | :--- | +| `users` | 存储用户信息 | +| `user_addresses` | 用户收货地址 | +| `products` | 商品基本信息 (SPU) | +| `product_inventory` | 商品库存单元 (SKU) | +| `categories` | 商品分类 | +| `cart` | 购物车 | +| `orders` | 订单主表 | +| `order_items` | 订单详情表 | +| `payments` | 支付记录 | +| `reviews` | 商品评价 | +| `admin_users` | 后台管理员 | + +
+ +> 完整的 `CREATE TABLE` SQL语句已在项目文件中提供,包含了详细的字段、索引和外键设计。 + +## 🛠️ 本地运行与部署 + +### 1. 环境准备 + +- 克隆项目到本地 + ```bash + git clone + cd Online_shopping_platform + ``` +- 创建并激活Python虚拟环境 + ```bash + python -m venv venv + source venv/bin/activate # Linux/Mac + # venv\Scripts\activate # Windows + ``` +- 安装项目依赖 + ```bash + pip install -r requirements.txt + ``` + +### 2. 配置 + +- 复制 `.env.example` 文件为 `.env` (如果提供)。 +- 根据提示修改 `config/` 目录下的配置文件,或设置环境变量,填入你的: + - **数据库连接信息** + - **Flask `SECRET_KEY`** + - **腾讯云 `COS` 和 `CDN` 配置** + - **微信支付商户信息** + - **邮件服务器配置** + +### 3. 初始化 + +- 初始化数据库表结构 + ```bash + # 在Flask应用上下文中执行 + flask db init # 如果使用Flask-Migrate + flask db migrate + flask db upgrade + # 或者使用自定义脚本 + python -c "from app import create_app, db; app = create_app(); app.app_context().push(); db.create_all()" + ``` +- 创建第一个管理员账户 + ```bash + python create_admin.py + ``` + +### 4. 启动! + +- 运行开发服务器 + ```bash + python run.py + ``` +- 🎉 恭喜!现在可以访问 `http://127.0.0.1:5000` 查看你的电商网站了。 + +### 5. Docker 部署 (推荐) + +- 确保已安装 Docker 和 Docker Compose。 +- 在项目根目录下执行: + ```bash + docker-compose up --build -d + ``` +- 服务将在后台启动,并通过 Nginx 代理对外提供服务。 + +## 💡 项目亮点 + +- **🛡️ 安全性**: 全面考虑了常见的Web安全问题,如 `SQL注入`、`XSS`、`CSRF`,并进行了有效防护。 +- **⚡ 性能优化**: 集成了 `CDN加速`、`图片懒加载`、`数据库查询优化` 等多种性能优化手段。 +- **📱 响应式设计**: 基于 Bootstrap 框架,完美适配PC和移动设备,提供一致的用户体验。 +- **🧩 模块化设计**: 代码结构清晰,功能高度解耦,易于维护和二次开发。 +- **☁️ 云原生支持**: 无缝集成云存储和CDN,为高并发和大数据量场景打下基础。 + +--- + +
+

本项目为毕业设计作品,旨在展示一个完整的Web应用开发流程。

+

作者:林金兴 | 指导老师:[指导老师姓名]

+
🔸============================================================================== 📄 文件: app/__init__.py -📊 大小: 2545 bytes (2.49 KB) -🕒 修改时间: 2025-07-04 02:28:26 +📊 大小: 1326 bytes (1.29 KB) +🕒 修改时间: 2025-07-09 02:21:01 🔸============================================================================== -""" -Flask应用工厂 -""" from flask import Flask -from flask_mail import Mail +from config.database import init_db from config.config import Config -from config.database import db -import re -# 初始化邮件服务 -mail = Mail() - - -def create_app(config_name='default'): +def create_app(config_name=None): app = Flask(__name__) - - # 加载配置 app.config.from_object(Config) - + # 初始化数据库 - db.init_app(app) - - # 初始化邮件服务 - mail.init_app(app) - - # 注册自定义过滤器 - register_filters(app) - + init_db(app) + # 注册蓝图 - register_blueprints(app) - - # 创建数据库表 - with app.app_context(): - try: - db.create_all() - print("✅ 数据库表创建/同步成功") - except Exception as e: - print(f"❌ 数据库表创建失败: {str(e)}") - - return app - - -def register_filters(app): - """注册自定义过滤器""" - - @app.template_filter('nl2br') - def nl2br_filter(text): - """将换行符转换为HTML
标签""" - if not text: - return '' - # 将换行符替换为
标签 - return text.replace('\n', '
') - - @app.template_filter('truncate_chars') - def truncate_chars_filter(text, length=50): - """截断字符串""" - if not text: - return '' - if len(text) <= length: - return text - return text[:length] + '...' - - -def register_blueprints(app): - """注册蓝图""" - from app.views.main import main_bp from app.views.auth import auth_bp + from app.views.main import main_bp from app.views.user import user_bp - from app.views.admin import admin_bp from app.views.product import product_bp from app.views.cart import cart_bp - from app.views.address import address_bp from app.views.order import order_bp from app.views.payment import payment_bp - - app.register_blueprint(main_bp) + from app.views.admin import admin_bp + from app.views.address import address_bp + from app.views.upload import upload_bp + from app.views.review import review_bp + from app.views.favorite import favorite_bp + from app.views.history import history_bp + app.register_blueprint(auth_bp) + app.register_blueprint(main_bp) app.register_blueprint(user_bp) - app.register_blueprint(admin_bp) app.register_blueprint(product_bp) app.register_blueprint(cart_bp) - app.register_blueprint(address_bp) app.register_blueprint(order_bp) app.register_blueprint(payment_bp) - - # 修复:正确注册upload蓝图并设置URL前缀 - try: - from app.views.upload import upload_bp - app.register_blueprint(upload_bp, url_prefix='/upload') # 添加URL前缀 - print("✅ 上传功能蓝图注册成功") - except ImportError as e: - print(f"⚠️ 上传功能暂时不可用: {str(e)}") - - print("✅ 商品管理蓝图注册成功") - print("✅ 购物车蓝图注册成功") + app.register_blueprint(admin_bp) + app.register_blueprint(address_bp) + app.register_blueprint(upload_bp) + app.register_blueprint(review_bp) + app.register_blueprint(favorite_bp) + app.register_blueprint(history_bp) + + return app 🔸============================================================================== @@ -345,8 +558,8 @@ class CheckoutForm(FlaskForm): 🔸============================================================================== 📄 文件: app/models/__init__.py -📊 大小: 822 bytes (0.80 KB) -🕒 修改时间: 2025-07-04 01:56:55 +📊 大小: 952 bytes (0.93 KB) +🕒 修改时间: 2025-07-09 02:20:50 🔸============================================================================== from app.models.user import User @@ -359,13 +572,15 @@ from app.models.address import UserAddress from app.models.order import Order, OrderItem, ShippingInfo from app.models.payment import Payment from app.models.review import Review +from app.models.favorite import UserFavorite +from app.models.browse_history import BrowseHistory __all__ = [ 'User', 'EmailVerification', 'AdminUser', 'OperationLog', 'Category', 'Product', 'ProductImage', 'SpecName', 'SpecValue', 'ProductInventory', 'InventoryLog', 'ProductSpecRelation', 'Cart', 'UserAddress', 'Order', 'OrderItem', 'ShippingInfo', - 'Payment', 'Review' + 'Payment', 'Review', 'UserFavorite', 'BrowseHistory' ] @@ -516,6 +731,125 @@ class AdminUser(db.Model): return f'' +🔸============================================================================== +📄 文件: app/models/browse_history.py +📊 大小: 3903 bytes (3.81 KB) +🕒 修改时间: 2025-07-09 02:19:42 +🔸============================================================================== + +""" +浏览历史模型 +""" +from datetime import datetime +from config.database import db +from app.models.product import Product +from app.models.user import User + + +class BrowseHistory(db.Model): + __tablename__ = 'browse_history' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + viewed_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # 关联关系 + user = db.relationship('User', backref='browse_history') + product = db.relationship('Product', backref='viewed_by') + + # 唯一约束 + __table_args__ = (db.UniqueConstraint('user_id', 'product_id', name='uk_user_product'),) + + def to_dict(self): + """转换为字典""" + return { + 'id': self.id, + 'user_id': self.user_id, + 'product_id': self.product_id, + 'viewed_at': self.viewed_at.isoformat() if self.viewed_at else None, + 'product': { + 'id': self.product.id, + 'name': self.product.name, + 'price': float(self.product.price), + 'main_image': self.product.main_image, + 'status': self.product.status, + 'sales_count': self.product.sales_count, + 'category': self.product.category.name if self.product.category else None + } if self.product else None + } + + @classmethod + def add_history(cls, user_id, product_id): + """添加浏览记录""" + # 检查商品是否存在 + product = Product.query.get(product_id) + if not product: + return False, "商品不存在" + + # 查找已有记录 + history = cls.query.filter_by(user_id=user_id, product_id=product_id).first() + + if history: + # 更新浏览时间 + history.viewed_at = datetime.utcnow() + else: + # 创建新记录 + history = cls(user_id=user_id, product_id=product_id) + db.session.add(history) + + try: + db.session.commit() + return True, "浏览记录添加成功" + except Exception as e: + db.session.rollback() + return False, f"添加浏览记录失败: {str(e)}" + + @classmethod + def get_user_history(cls, user_id, page=1, per_page=20): + """获取用户浏览历史""" + return cls.query.filter_by(user_id=user_id) \ + .join(Product) \ + .filter(Product.status == 1) \ + .order_by(cls.viewed_at.desc()) \ + .paginate(page=page, per_page=per_page, error_out=False) + + @classmethod + def get_user_history_count(cls, user_id): + """获取用户浏览历史数量""" + return cls.query.filter_by(user_id=user_id).count() + + @classmethod + def clear_user_history(cls, user_id): + """清空用户浏览历史""" + try: + cls.query.filter_by(user_id=user_id).delete() + db.session.commit() + return True, "浏览历史清空成功" + except Exception as e: + db.session.rollback() + return False, f"清空浏览历史失败: {str(e)}" + + @classmethod + def remove_history_item(cls, user_id, product_id): + """删除单个浏览记录""" + history = cls.query.filter_by(user_id=user_id, product_id=product_id).first() + if not history: + return False, "浏览记录不存在" + + db.session.delete(history) + + try: + db.session.commit() + return True, "浏览记录删除成功" + except Exception as e: + db.session.rollback() + return False, f"删除浏览记录失败: {str(e)}" + + def __repr__(self): + return f'' + + 🔸============================================================================== 📄 文件: app/models/cart.py 📊 大小: 4608 bytes (4.50 KB) @@ -660,6 +994,116 @@ class Cart(db.Model): return f'' +🔸============================================================================== +📄 文件: app/models/favorite.py +📊 大小: 3502 bytes (3.42 KB) +🕒 修改时间: 2025-07-09 02:19:33 +🔸============================================================================== + +""" +用户收藏模型 +""" +from datetime import datetime +from config.database import db +from app.models.product import Product +from app.models.user import User + + +class UserFavorite(db.Model): + __tablename__ = 'user_favorites' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + # 关联关系 + user = db.relationship('User', backref='favorites') + product = db.relationship('Product', backref='favorited_by') + + # 唯一约束 + __table_args__ = (db.UniqueConstraint('user_id', 'product_id', name='uk_user_product'),) + + def to_dict(self): + """转换为字典""" + return { + 'id': self.id, + 'user_id': self.user_id, + 'product_id': self.product_id, + 'created_at': self.created_at.isoformat() if self.created_at else None, + 'product': { + 'id': self.product.id, + 'name': self.product.name, + 'price': float(self.product.price), + 'main_image': self.product.main_image, + 'status': self.product.status, + 'sales_count': self.product.sales_count + } if self.product else None + } + + @classmethod + def is_favorited(cls, user_id, product_id): + """检查用户是否收藏了某商品""" + return cls.query.filter_by(user_id=user_id, product_id=product_id).first() is not None + + @classmethod + def add_favorite(cls, user_id, product_id): + """添加收藏""" + # 检查是否已存在 + existing = cls.query.filter_by(user_id=user_id, product_id=product_id).first() + if existing: + return False, "商品已在收藏夹中" + + # 检查商品是否存在 + product = Product.query.get(product_id) + if not product: + return False, "商品不存在" + + # 添加收藏 + favorite = cls(user_id=user_id, product_id=product_id) + db.session.add(favorite) + + try: + db.session.commit() + return True, "收藏成功" + except Exception as e: + db.session.rollback() + return False, f"收藏失败: {str(e)}" + + @classmethod + def remove_favorite(cls, user_id, product_id): + """取消收藏""" + favorite = cls.query.filter_by(user_id=user_id, product_id=product_id).first() + if not favorite: + return False, "商品未收藏" + + db.session.delete(favorite) + + try: + db.session.commit() + return True, "取消收藏成功" + except Exception as e: + db.session.rollback() + return False, f"取消收藏失败: {str(e)}" + + @classmethod + def get_user_favorites(cls, user_id, page=1, per_page=20): + """获取用户收藏列表""" + return cls.query.filter_by(user_id=user_id) \ + .join(Product) \ + .filter(Product.status == 1) \ + .order_by(cls.created_at.desc()) \ + .paginate(page=page, per_page=per_page, error_out=False) + + @classmethod + def get_user_favorites_count(cls, user_id): + """获取用户收藏数量""" + return cls.query.filter_by(user_id=user_id).count() + + def __repr__(self): + return f'' + + 🔸============================================================================== 📄 文件: app/models/operation_log.py 📊 大小: 1850 bytes (1.81 KB) @@ -1463,6 +1907,6352 @@ class EmailVerification(db.Model): return datetime.utcnow() > self.expired_at +🔸============================================================================== +📄 文件: app/static/css/address_form.css +📊 大小: 1445 bytes (1.41 KB) +🕒 修改时间: 2025-07-04 04:02:17 +🔸============================================================================== + +/* 地址表单页面样式 */ +.form-label { + font-weight: 500; + margin-bottom: 0.5rem; +} + +.form-label .text-danger { + font-size: 0.9em; +} + +.form-select:focus, +.form-control:focus { + border-color: #86b7fe; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} + +.text-danger { + font-size: 0.875em; + margin-top: 0.25rem; +} + +/* 调试信息样式 */ +.alert-info { + border-left: 4px solid #0dcaf0; + background-color: #cff4fc; + border-color: #b8daff; +} + +#debugInfo { + font-family: 'Courier New', monospace; + font-size: 0.9em; + margin-top: 0.5rem; +} + +/* 表单布局优化 */ +.row .col-md-4, +.row .col-md-6, +.row .col-md-8 { + margin-bottom: 0; +} + +.mb-3 { + margin-bottom: 1rem !important; +} + +/* 按钮组样式 */ +.d-flex.gap-2 { + gap: 0.5rem !important; +} + +.btn { + padding: 0.5rem 1rem; + font-weight: 500; +} + +/* 复选框样式 */ +.form-check { + padding-left: 1.5em; +} + +.form-check-input { + margin-top: 0.25em; +} + +.form-check-label { + font-weight: 500; + cursor: pointer; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .col-md-4, + .col-md-6, + .col-md-8 { + margin-bottom: 1rem; + } + + .d-flex.gap-2 { + flex-direction: column; + } + + .d-flex.gap-2 .btn { + width: 100%; + } +} + +/* 加载状态样式 */ +.form-select:disabled { + background-color: #e9ecef; + opacity: 0.65; +} + +.loading-text { + color: #6c757d; + font-style: italic; +} + + +🔸============================================================================== +📄 文件: app/static/css/addresses.css +📊 大小: 1295 bytes (1.26 KB) +🕒 修改时间: 2025-07-04 04:00:58 +🔸============================================================================== + +/* 地址管理页面样式 */ +.address-card { + transition: all 0.3s ease; + cursor: pointer; +} + +.address-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(0,0,0,0.1); +} + +.address-card.border-primary { + border-width: 2px !important; +} + +.address-card .card-body { + position: relative; +} + +.dropdown-toggle::after { + display: none; +} + +/* 空状态样式 */ +.empty-state { + padding: 3rem 0; +} + +.empty-state i { + opacity: 0.5; +} + +/* 地址卡片内容样式 */ +.address-card .card-title { + font-size: 1.1rem; + margin-bottom: 0.5rem; +} + +.address-card .badge { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; +} + +.address-card .text-muted { + font-size: 0.9rem; +} + +/* 下拉菜单样式 */ +.dropdown-menu { + min-width: 120px; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + border: 1px solid rgba(0,0,0,.125); +} + +.dropdown-item { + padding: 0.5rem 1rem; + font-size: 0.9rem; +} + +.dropdown-item:hover { + background-color: #f8f9fa; +} + +.dropdown-item.text-danger:hover { + background-color: #f8d7da; + color: #721c24 !important; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .address-card { + margin-bottom: 1rem; + } + + .col-md-6 { + padding-left: 0.75rem; + padding-right: 0.75rem; + } +} + + +🔸============================================================================== +📄 文件: app/static/css/admin_base.css +📊 大小: 2094 bytes (2.04 KB) +🕒 修改时间: 2025-07-04 14:51:53 +🔸============================================================================== + +:root { + --admin-primary: #0d6efd; + --admin-sidebar: #212529; + --admin-sidebar-hover: #495057; + --admin-bg: #f8f9fa; +} + +body { + background-color: var(--admin-bg); +} + +.admin-sidebar { + min-height: 100vh; + background-color: var(--admin-sidebar); + width: 250px; + position: fixed; + top: 0; + left: 0; + z-index: 1000; + padding-top: 20px; +} + +.admin-sidebar .nav-link { + color: #fff; + padding: 12px 20px; + border-radius: 0; + margin-bottom: 2px; +} + +.admin-sidebar .nav-link:hover, +.admin-sidebar .nav-link.active { + background-color: var(--admin-sidebar-hover); + color: #fff; +} + +.admin-sidebar .nav-link i { + margin-right: 10px; + width: 20px; +} + +.admin-main { + margin-left: 250px; + padding: 0; +} + +.admin-header { + background-color: #fff; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + padding: 15px 30px; + margin-bottom: 30px; +} + +.admin-content { + padding: 0 30px 30px; +} + +.stats-card { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 15px; + padding: 25px; + margin-bottom: 20px; + border: none; +} + +.stats-card.success { + background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); +} + +.stats-card.warning { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.stats-card.info { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); +} + +.admin-table { + background: white; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +.admin-table th { + background-color: #f8f9fa; + border: none; + font-weight: 600; + color: #495057; +} + +.admin-table td { + border: none; + vertical-align: middle; +} + +.admin-table tbody tr { + border-bottom: 1px solid #f8f9fa; +} + +.admin-table tbody tr:hover { + background-color: #f8f9fa; +} + +.sidebar-brand { + color: #fff; + font-size: 1.2rem; + font-weight: bold; + padding: 0 20px 30px; + border-bottom: 1px solid #495057; + margin-bottom: 20px; +} + +.sidebar-brand i { + margin-right: 10px; + color: var(--admin-primary); +} + + +🔸============================================================================== +📄 文件: app/static/css/admin_categories.css +📊 大小: 2763 bytes (2.70 KB) +🕒 修改时间: 2025-07-04 18:44:46 +🔸============================================================================== + +/* 分类管理页面样式 */ +.category-tree { + background: white; + border-radius: 10px; + padding: 20px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +.category-item { + border: 1px solid #e9ecef; + border-radius: 8px; + margin-bottom: 10px; + transition: all 0.3s ease; +} + +.category-item:hover { + border-color: #0d6efd; + box-shadow: 0 2px 8px rgba(13, 110, 253, 0.15); +} + +.category-header { + padding: 15px 20px; + background: #f8f9fa; + border-radius: 8px 8px 0 0; + display: flex; + align-items: center; + justify-content: space-between; +} + +.category-level-1 .category-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.category-level-2 .category-header { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + color: white; +} + +.category-level-3 .category-header { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + color: white; +} + +.category-info { + display: flex; + align-items: center; + gap: 15px; +} + +.category-icon { + width: 40px; + height: 40px; + border-radius: 8px; + object-fit: cover; + border: 2px solid rgba(255,255,255,0.3); +} + +.default-icon { + width: 40px; + height: 40px; + border-radius: 8px; + background: rgba(255,255,255,0.2); + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; +} + +.category-details h6 { + margin: 0; + font-weight: 600; +} + +.category-meta { + font-size: 12px; + opacity: 0.8; + margin-top: 2px; +} + +.category-actions { + display: flex; + gap: 8px; +} + +.children-categories { + padding: 0 20px 20px; + margin-left: 40px; + border-left: 2px dashed #dee2e6; +} + +.btn-icon { + width: 32px; + height: 32px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; +} + +.add-category-form { + background: #f8f9fa; + border-radius: 10px; + padding: 25px; + margin-bottom: 30px; + border: 2px dashed #dee2e6; +} + +.icon-upload-area { + width: 80px; + height: 80px; + border: 2px dashed #dee2e6; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + background: white; +} + +.icon-upload-area:hover { + border-color: #0d6efd; + background: #e3f2fd; +} + +.icon-upload-area img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 8px; +} + +.sort-handle { + cursor: move; + color: #6c757d; + margin-right: 10px; +} + +.sort-handle:hover { + color: #0d6efd; +} + +.empty-state { + text-align: center; + padding: 60px 20px; + color: #6c757d; +} + +.empty-state i { + font-size: 4rem; + margin-bottom: 20px; + opacity: 0.5; +} + + +🔸============================================================================== +📄 文件: app/static/css/admin_dashboard.css +📊 大小: 627 bytes (0.61 KB) +🕒 修改时间: 2025-07-04 14:51:53 +🔸============================================================================== + +/* Dashboard specific styles */ +.dashboard-stats { + margin-bottom: 30px; +} + +.chart-container { + position: relative; + height: 300px; +} + +.system-status-item { + display: flex; + justify-content: between; + align-items: center; + margin-bottom: 15px; + padding: 10px 0; + border-bottom: 1px solid #f0f0f0; +} + +.system-status-item:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.log-table-container { + margin-top: 30px; +} + +.empty-state { + text-align: center; + padding: 40px 20px; + color: #6c757d; +} + +.empty-state i { + font-size: 3rem; + margin-bottom: 15px; + opacity: 0.5; +} + + +🔸============================================================================== +📄 文件: app/static/css/admin_login.css +📊 大小: 1444 bytes (1.41 KB) +🕒 修改时间: 2025-07-04 14:51:53 +🔸============================================================================== + +body { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; +} + +.login-card { + background: white; + border-radius: 15px; + box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1); + padding: 40px; + min-width: 400px; +} + +.login-header { + text-align: center; + margin-bottom: 30px; +} + +.login-header h2 { + color: #333; + font-weight: 600; + margin-bottom: 10px; +} + +.login-header p { + color: #666; + margin-bottom: 0; +} + +.form-control { + padding: 12px 15px; + border-radius: 8px; + border: 1px solid #ddd; + font-size: 16px; +} + +.form-control:focus { + border-color: #667eea; + box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25); +} + +.btn-login { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; + padding: 12px; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + transition: all 0.3s ease; +} + +.btn-login:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); +} + +.input-group-text { + background-color: #f8f9fa; + border: 1px solid #ddd; + border-radius: 8px 0 0 8px; +} + +.form-control { + border-radius: 0 8px 8px 0; +} + +.form-control:first-child { + border-radius: 8px 0 0 8px; +} + +.back-link { + text-decoration: none; + color: #667eea; + font-size: 14px; +} + +.back-link:hover { + color: #764ba2; +} + + +🔸============================================================================== +📄 文件: app/static/css/admin_logs.css +📊 大小: 5113 bytes (4.99 KB) +🕒 修改时间: 2025-07-09 01:54:54 +🔸============================================================================== + +/* 操作日志页面样式 */ +.admin-logs { + padding: 0; +} + +/* 统计卡片样式 */ +.stats-card { + border: none; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + transition: transform 0.2s; +} + +.stats-card:hover { + transform: translateY(-2px); +} + +.stats-card .card-title { + font-size: 1.8rem; + font-weight: 600; + color: #333; + margin-bottom: 0.25rem; +} + +.stats-card .card-text { + color: #666; + font-size: 0.9rem; + margin-bottom: 0; +} + +.icon-wrapper { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + color: white; +} + +.icon-wrapper.primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.icon-wrapper.success { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); +} + +.icon-wrapper.warning { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.icon-wrapper.info { + background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); + color: #333; +} + +/* 表格样式 */ +.table th { + background-color: #f8f9fa; + border-bottom: 2px solid #dee2e6; + font-weight: 600; + color: #495057; + font-size: 0.9rem; +} + +.table td { + vertical-align: middle; + padding: 1rem 0.75rem; + font-size: 0.875rem; +} + +.table-hover tbody tr:hover { + background-color: #f8f9fa; +} + +/* 操作类型样式 */ +.operation-action { + display: inline-block; + padding: 0.25rem 0.5rem; + background-color: #e9ecef; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 500; + color: #495057; +} + +/* 资源类型样式 */ +.resource-type { + background-color: #d4edda; + color: #155724; + padding: 0.2rem 0.4rem; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 500; +} + +.resource-id { + color: #6c757d; + font-size: 0.8rem; + margin-left: 0.25rem; +} + +/* 用户代理样式 */ +.user-agent-wrapper { + max-width: 200px; +} + +.user-agent { + display: block; + font-size: 0.8rem; + color: #6c757d; + cursor: help; + line-height: 1.2; +} + +/* 徽章样式 */ +.badge { + font-size: 0.7rem; + font-weight: 500; + padding: 0.3em 0.6em; +} + +/* 时间显示样式 */ +.table td:first-child { + white-space: nowrap; + min-width: 110px; +} + +.table td:first-child small { + font-size: 0.75rem; +} + +/* 空状态样式 */ +.empty-state { + text-align: center; + padding: 3rem 1rem; + color: #6c757d; +} + +.empty-state i { + font-size: 3rem; + margin-bottom: 1rem; + color: #dee2e6; +} + +.empty-state div { + font-size: 1.1rem; + margin-bottom: 0.5rem; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .table-responsive { + font-size: 0.8rem; + } + + .table th, .table td { + padding: 0.75rem 0.5rem; + } + + .stats-card .card-title { + font-size: 1.5rem; + } + + .icon-wrapper { + width: 40px; + height: 40px; + font-size: 1.2rem; + } + + .user-agent-wrapper { + max-width: 150px; + } +} + +/* 筛选表单样式 */ +.card .form-label { + font-weight: 500; + color: #495057; +} + +.form-control:focus, .form-select:focus { + border-color: #667eea; + box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25); +} + +/* 分页样式 */ +.pagination .page-link { + color: #667eea; + border-color: #dee2e6; +} + +.pagination .page-link:hover { + color: #495057; + background-color: #f8f9fa; + border-color: #dee2e6; +} + +.pagination .page-item.active .page-link { + background-color: #667eea; + border-color: #667eea; +} + +/* 代码样式 */ +code { + background-color: #f8f9fa; + color: #e83e8c; + padding: 0.2rem 0.4rem; + border-radius: 4px; + font-size: 0.8rem; +} + +/* 表格滚动条样式 */ +.table-responsive::-webkit-scrollbar { + height: 8px; +} + +.table-responsive::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +.table-responsive::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} + +.table-responsive::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* 卡片头部样式 */ +.card-header h5 { + color: #333; + font-weight: 600; +} + +.card-header small { + font-weight: 400; +} + +/* 筛选区域样式 */ +.card-body form { + margin-bottom: 0; +} + +.card-body .btn { + height: 38px; + margin-top: 0.5rem; +} + +/* 日志详情样式 */ +.log-detail-btn { + font-size: 0.8rem; + padding: 0.2rem 0.4rem; + border-radius: 4px; +} + +/* 操作者信息样式 */ +.badge.bg-warning { + background-color: #ffc107 !important; + color: #212529 !important; +} + +.badge.bg-info { + background-color: #0dcaf0 !important; + color: #000 !important; +} + +/* 分页信息样式 */ +.card-footer { + padding: 1rem 1.5rem; + background-color: #f8f9fa !important; + border-top: 1px solid #dee2e6; +} + +/* 加载状态 */ +.loading { + text-align: center; + padding: 2rem; + color: #6c757d; +} + +.loading i { + font-size: 2rem; + margin-bottom: 1rem; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + + +🔸============================================================================== +📄 文件: app/static/css/admin_orders.css +📊 大小: 3820 bytes (3.73 KB) +🕒 修改时间: 2025-07-08 19:56:29 +🔸============================================================================== + +/* 订单管理样式 */ +.admin-orders { + padding: 0; +} + +/* 统计卡片 - 修复颜色问题,使用更高优先级 */ +.admin-orders .stats-card { + background: #ffffff !important; + color: #2c3e50 !important; + border: 1px solid #e9ecef !important; + border-radius: 0.5rem !important; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important; + transition: transform 0.2s, box-shadow 0.2s; + padding: 1.25rem !important; +} + +.admin-orders .stats-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important; +} + +.admin-orders .stats-card .card-body { + padding: 0 !important; + text-align: center; +} + +.stats-number { + font-size: 2.2rem; + font-weight: bold; + color: #2c3e50 !important; + line-height: 1.2; + margin-bottom: 0.25rem; +} + +.stats-label { + font-size: 0.9rem; + color: #6c757d !important; + font-weight: 500; +} + +/* 状态特定颜色 - 使用更明显的颜色对比 */ +.admin-orders .stats-card.pending-payment { + border-left: 4px solid #ffc107 !important; +} + +.admin-orders .stats-card.pending-payment .stats-number { + color: #f39c12 !important; +} + +.admin-orders .stats-card.pending-shipment { + border-left: 4px solid #17a2b8 !important; +} + +.admin-orders .stats-card.pending-shipment .stats-number { + color: #17a2b8 !important; +} + +.admin-orders .stats-card.shipped { + border-left: 4px solid #28a745 !important; +} + +.admin-orders .stats-card.shipped .stats-number { + color: #28a745 !important; +} + +.admin-orders .stats-card.completed { + border-left: 4px solid #6f42c1 !important; +} + +.admin-orders .stats-card.completed .stats-number { + color: #6f42c1 !important; +} + +.admin-orders .stats-card.cancelled { + border-left: 4px solid #dc3545 !important; +} + +.admin-orders .stats-card.cancelled .stats-number { + color: #dc3545 !important; +} + +/* 订单状态徽章 */ +.order-status-1 { + background-color: #ffc107; + color: #212529; +} + +.order-status-2 { + background-color: #17a2b8; + color: #fff; +} + +.order-status-3 { + background-color: #28a745; + color: #fff; +} + +.order-status-4 { + background-color: #fd7e14; + color: #fff; +} + +.order-status-5 { + background-color: #6f42c1; + color: #fff; +} + +.order-status-6 { + background-color: #dc3545; + color: #fff; +} + +.order-status-7 { + background-color: #e83e8c; + color: #fff; +} + +/* 表格样式 */ +.table th { + background-color: #f8f9fa; + font-weight: 600; + border-bottom: 2px solid #dee2e6; +} + +.table td { + vertical-align: middle; +} + +.table-hover tbody tr:hover { + background-color: #f8f9fa; +} + +/* 操作按钮组 */ +.btn-group .btn { + border-radius: 0.375rem; + margin-right: 0.25rem; +} + +.btn-group .btn:last-child { + margin-right: 0; +} + +/* 商品缩略图 */ +.product-thumb { + border-radius: 0.375rem; + border: 1px solid #dee2e6; +} + +/* 订单详情页面 */ +.admin-order-detail .card { + border: none; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.admin-order-detail .card-header { + background-color: #f8f9fa; + border-bottom: 1px solid #dee2e6; +} + +.admin-order-detail .table th { + background-color: transparent; + border-bottom: 1px solid #dee2e6; + font-weight: 600; +} + +.admin-order-detail .table td { + border-bottom: 1px solid #dee2e6; +} + +/* 模态框样式 */ +.modal-header { + background-color: #f8f9fa; + border-bottom: 1px solid #dee2e6; +} + +.modal-body .form-label { + font-weight: 600; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .admin-orders .stats-card { + margin-bottom: 1rem; + } + + .stats-number { + font-size: 1.8rem; + } + + .btn-group { + flex-direction: column; + gap: 0.25rem; + } + + .btn-group .btn { + margin-right: 0; + } + + .table-responsive { + font-size: 0.875rem; + } +} + + +🔸============================================================================== +📄 文件: app/static/css/admin_profile.css +📊 大小: 4383 bytes (4.28 KB) +🕒 修改时间: 2025-07-04 18:44:46 +🔸============================================================================== + +/* 管理员个人资料页面样式 */ +.profile-container { + padding: 20px 0; +} + +.profile-card { + border: none; + border-radius: 12px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + margin-bottom: 20px; +} + +.profile-card .card-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 12px 12px 0 0 !important; + padding: 15px 20px; +} + +.profile-card .card-header h5 { + margin: 0; + font-weight: 600; +} + +.profile-card .card-header i { + margin-right: 8px; +} + +.profile-card .card-body { + padding: 25px; +} + +.form-label { + font-weight: 600; + color: #495057; + margin-bottom: 8px; +} + +.form-control { + border-radius: 8px; + border: 1px solid #e0e6ed; + padding: 12px 15px; + transition: all 0.3s ease; +} + +.form-control:focus { + border-color: #667eea; + box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25); +} + +.form-control[readonly] { + background-color: #f8f9fa; + color: #6c757d; +} + +.form-text { + font-size: 12px; + color: #6c757d; + margin-top: 5px; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; + border-radius: 8px; + padding: 12px 24px; + font-weight: 600; + transition: all 0.3s ease; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.btn-warning { + background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); + border: none; + border-radius: 8px; + padding: 12px 24px; + font-weight: 600; + color: #8b4513; + transition: all 0.3s ease; +} + +.btn-warning:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(252, 182, 159, 0.4); + color: #8b4513; +} + +.btn i { + margin-right: 6px; +} + +/* 账号信息卡片 */ +.info-card { + background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); + border: none; + border-radius: 12px; + margin-bottom: 20px; +} + +.info-card .card-header { + background: rgba(255, 255, 255, 0.2); + border: none; + border-radius: 12px 12px 0 0 !important; +} + +.info-card .card-body { + background: rgba(255, 255, 255, 0.1); + border-radius: 0 0 12px 12px; +} + +.info-item { + margin-bottom: 15px; + padding: 10px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); +} + +.info-item:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.info-item strong { + color: #2c3e50; + font-weight: 600; +} + +.badge { + font-size: 12px; + padding: 6px 12px; + border-radius: 20px; + font-weight: 500; +} + +.badge.bg-success { + background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%) !important; + color: #2c3e50; +} + +.badge.bg-danger { + background: linear-gradient(135deg, #fc466b 0%, #3f5efb 100%) !important; + color: white; +} + +/* 密码修改卡片 */ +.password-card { + background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); + border: none; + border-radius: 12px; +} + +.password-card .card-header { + background: rgba(255, 255, 255, 0.2); + border: none; + border-radius: 12px 12px 0 0 !important; +} + +.password-card .card-body { + background: rgba(255, 255, 255, 0.1); + border-radius: 0 0 12px 12px; +} + +.password-card .form-control { + background: rgba(255, 255, 255, 0.8); + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.password-card .form-control:focus { + background: white; + border-color: #fcb69f; + box-shadow: 0 0 0 0.2rem rgba(252, 182, 159, 0.25); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .profile-container { + padding: 10px 0; + } + + .profile-card .card-body { + padding: 20px 15px; + } + + .row .col-md-6 { + margin-bottom: 15px; + } +} + +/* 动画效果 */ +.profile-card { + animation: fadeInUp 0.6s ease-out; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* 表单验证样式 */ +.form-control.is-invalid { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.form-control.is-valid { + border-color: #28a745; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.invalid-feedback { + display: block; + color: #dc3545; + font-size: 12px; + margin-top: 5px; +} + +.valid-feedback { + display: block; + color: #28a745; + font-size: 12px; + margin-top: 5px; +} + + +🔸============================================================================== +📄 文件: app/static/css/admin_users.css +📊 大小: 7104 bytes (6.94 KB) +🕒 修改时间: 2025-07-09 02:08:32 +🔸============================================================================== + +/* 用户管理页面样式 */ +.admin-users { + padding: 0; +} + +/* 统计卡片样式 */ +.stats-card { + border: none; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + transition: transform 0.2s; +} + +.stats-card:hover { + transform: translateY(-2px); +} + +.stats-card .card-title { + font-size: 1.8rem; + font-weight: 600; + color: #333; + margin-bottom: 0.25rem; +} + +.stats-card .card-text { + color: #666; + font-size: 0.9rem; + margin-bottom: 0; +} + +.icon-wrapper { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + color: white; +} + +.icon-wrapper.primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.icon-wrapper.success { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); +} + +.icon-wrapper.danger { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.icon-wrapper.info { + background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); + color: #333; +} + +/* 用户头像样式 - 表格中的头像 */ +.avatar-wrapper { + width: 48px !important; + height: 48px !important; + position: relative; + overflow: hidden !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; +} + +.user-avatar { + width: 48px !important; + height: 48px !important; + border-radius: 50% !important; + object-fit: cover !important; + border: 2px solid #f8f9fa !important; + display: block !important; + max-width: 48px !important; + max-height: 48px !important; + min-width: 48px !important; + min-height: 48px !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; +} + +.user-avatar-placeholder { + width: 48px !important; + height: 48px !important; + border-radius: 50% !important; + background: #e9ecef !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + font-size: 1.2rem !important; + color: #6c757d !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; +} + +/* 用户详情模态框中的头像容器 */ +.user-avatar-large-wrapper { + width: 80px !important; + height: 80px !important; + margin: 0 auto !important; + overflow: hidden !important; + border-radius: 50% !important; + position: relative !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; +} + +/* 用户详情模态框中的头像 */ +.avatar-large { + width: 80px !important; + height: 80px !important; + border-radius: 50% !important; + object-fit: cover !important; + border: 3px solid #f8f9fa !important; + display: block !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; +} + +.avatar-placeholder-large { + width: 80px !important; + height: 80px !important; + border-radius: 50% !important; + background: #e9ecef !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + font-size: 2rem !important; + color: #6c757d !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; +} + +/* 强制覆盖Bootstrap的所有可能的图片样式 */ +.user-detail img, +.table img, +.modal img { + max-width: none !important; + max-height: none !important; +} + +.user-detail img.avatar-large, +.modal img.avatar-large { + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; +} + +.table img.user-avatar { + width: 48px !important; + height: 48px !important; + max-width: 48px !important; + max-height: 48px !important; + min-width: 48px !important; + min-height: 48px !important; +} + +/* 表格样式 */ +.table th { + background-color: #f8f9fa; + border-bottom: 2px solid #dee2e6; + font-weight: 600; + color: #495057; +} + +.table td { + vertical-align: middle; + padding: 1rem 0.75rem; +} + +.table-hover tbody tr:hover { + background-color: #f8f9fa; +} + +/* 按钮组样式 */ +.btn-group .btn { + padding: 0.375rem 0.75rem; + font-size: 0.875rem; +} + +/* 空状态样式 */ +.empty-state { + text-align: center; + padding: 3rem 1rem; + color: #6c757d; +} + +.empty-state i { + font-size: 3rem; + margin-bottom: 1rem; + color: #dee2e6; +} + +.empty-state div { + font-size: 1.1rem; + margin-bottom: 0.5rem; +} + +/* 用户详情信息样式 */ +.user-detail { + padding: 1rem; +} + +.user-info-list { + margin-top: 1rem; +} + +.user-info-item { + display: flex; + align-items: center; + padding: 0.75rem 0; + border-bottom: 1px solid #f8f9fa; +} + +.user-info-item:last-child { + border-bottom: none; +} + +.user-info-label { + font-weight: 500; + color: #495057; + width: 120px; + flex-shrink: 0; +} + +.user-info-value { + color: #333; + flex: 1; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .table-responsive { + font-size: 0.875rem; + } + + .btn-group .btn { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + } + + .stats-card .card-title { + font-size: 1.5rem; + } + + .icon-wrapper { + width: 40px; + height: 40px; + font-size: 1.2rem; + } + + .user-avatar { + width: 40px !important; + height: 40px !important; + max-width: 40px !important; + max-height: 40px !important; + min-width: 40px !important; + min-height: 40px !important; + } + + .avatar-wrapper { + width: 40px !important; + height: 40px !important; + } + + .user-avatar-placeholder { + width: 40px !important; + height: 40px !important; + font-size: 1rem !important; + } +} + +/* 筛选表单样式 */ +.card .form-label { + font-weight: 500; + color: #495057; +} + +.form-control:focus, .form-select:focus { + border-color: #667eea; + box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25); +} + +/* 分页样式 */ +.pagination .page-link { + color: #667eea; + border-color: #dee2e6; +} + +.pagination .page-link:hover { + color: #495057; + background-color: #f8f9fa; + border-color: #dee2e6; +} + +.pagination .page-item.active .page-link { + background-color: #667eea; + border-color: #667eea; +} + +/* 用户详情模态框样式 */ +.modal-content { + border-radius: 12px; + border: none; + box-shadow: 0 10px 30px rgba(0,0,0,0.15); +} + +.modal-header { + background-color: #f8f9fa; + border-bottom: 1px solid #dee2e6; + border-radius: 12px 12px 0 0; +} + +.modal-title { + font-weight: 600; + color: #333; +} + +/* 徽章样式 */ +.badge { + font-size: 0.75rem; + font-weight: 500; + padding: 0.35em 0.65em; +} + +/* 加载状态 */ +.loading { + text-align: center; + padding: 2rem; + color: #6c757d; +} + +.loading i { + font-size: 2rem; + margin-bottom: 1rem; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + + +🔸============================================================================== +📄 文件: app/static/css/auth.css +📊 大小: 1183 bytes (1.16 KB) +🕒 修改时间: 2025-07-04 03:54:59 +🔸============================================================================== + +/* 认证页面样式 */ +.auth-container { + min-height: 60vh; + display: flex; + align-items: center; +} + +.auth-card { + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border: none; + border-radius: 10px; +} + +.auth-card .card-header { + background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); + color: white; + border-radius: 10px 10px 0 0; + border: none; +} + +.auth-card .card-header h4 { + margin: 0; + font-weight: 500; +} + +.auth-card .card-body { + padding: 2rem; +} + +/* 表单样式 */ +.form-control:focus { + border-color: #007bff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.btn-primary { + background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); + border: none; + padding: 0.75rem; + font-weight: 500; +} + +.btn-primary:hover { + background: linear-gradient(135deg, #0056b3 0%, #004085 100%); + transform: translateY(-1px); +} + +/* 链接样式 */ +.auth-link { + color: #007bff; + text-decoration: none; + font-weight: 500; +} + +.auth-link:hover { + color: #0056b3; + text-decoration: underline; +} + +/* 响应式设计 */ +@media (max-width: 576px) { + .auth-card .card-body { + padding: 1.5rem; + } +} + + +🔸============================================================================== +📄 文件: app/static/css/base.css +📊 大小: 658 bytes (0.64 KB) +🕒 修改时间: 2025-07-04 03:53:30 +🔸============================================================================== + +/* 基础样式 */ +.navbar-brand { + font-weight: bold; + color: #007bff !important; +} + +.footer { + background-color: #f8f9fa; + padding: 2rem 0; + margin-top: 3rem; +} + +.alert { + margin-bottom: 0; +} + +.search-form { + max-width: 300px; +} + +.cart-badge { + position: relative; + top: -2px; + font-size: 0.7rem; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .search-form { + max-width: 100%; + margin: 10px 0; + } +} + +/* 返回顶部按钮 */ +#backToTop { + display: none; + z-index: 1000; +} + +/* 成功提示框样式 */ +.success-toast { + top: 20px; + right: 20px; + z-index: 9999; + min-width: 300px; +} + + +🔸============================================================================== +📄 文件: app/static/css/cart.css +📊 大小: 379 bytes (0.37 KB) +🕒 修改时间: 2025-07-04 14:40:23 +🔸============================================================================== + +.cart-item { + transition: background-color 0.2s; +} + +.cart-item:hover { + background-color: #f8f9fa; +} + +.quantity-input { + width: 60px; +} + +.item-checkbox { + transform: scale(1.2); +} + +.position-sticky { + top: 20px !important; +} + +@media (max-width: 768px) { + .col-md-4 .position-sticky { + position: relative !important; + top: auto !important; + } +} + + +🔸============================================================================== +📄 文件: app/static/css/checkout.css +📊 大小: 2715 bytes (2.65 KB) +🕒 修改时间: 2025-07-08 17:14:27 +🔸============================================================================== + +/* 订单结算页面样式 */ +.checkout-section { + margin-bottom: 1.5rem; +} + +.address-card { + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid #e9ecef; +} + +.address-card:hover { + border-color: #007bff; + box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1); +} + +.address-card.selected { + border-color: #007bff; + background-color: #e7f3ff; +} + +.product-item { + padding: 1rem 0; + border-bottom: 1px solid #e9ecef; +} + +.product-item:last-child { + border-bottom: none; +} + +.order-summary { + background: #f8f9fa; + padding: 1rem; + border-radius: 0.5rem; +} + +.price-row { + display: flex; + justify-content: space-between; + margin-bottom: 0.5rem; +} + +.price-row.total-price { + font-size: 1.1rem; + font-weight: bold; + color: #dc3545; +} + +.form-check { + padding: 0.75rem; + border: 1px solid #e9ecef; + border-radius: 0.5rem; + margin-bottom: 0.5rem; + transition: all 0.3s ease; +} + +.form-check:hover { + border-color: #007bff; + background-color: #f8f9fa; +} + +.form-check-input:checked + .form-check-label { + color: #007bff; +} + +/* 支付方式特殊样式 */ +.form-check input[type="radio"][value="simulate"]:checked + label { + color: #ffc107; +} + +.form-check input[type="radio"][value="simulate"]:checked + label i { + color: #ffc107 !important; +} + +/* 模拟支付说明样式 */ +.alert-warning { + border-left: 4px solid #ffc107; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .checkout-section .col-md-4, + .checkout-section .col-md-3 { + margin-bottom: 1rem; + } + + .address-card { + margin-bottom: 1rem; + } + + .product-item .col-md-2, + .product-item .col-md-6 { + margin-bottom: 0.5rem; + } +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.alert { + animation: fadeIn 0.3s ease; +} + +/* 按钮样式 */ +.btn-lg { + padding: 0.75rem 1.5rem; + font-size: 1.1rem; +} + +/* 卡片头部样式 */ +.card-header h5 { + margin-bottom: 0; + color: #495057; +} + +.card-header i { + margin-right: 0.5rem; + color: #007bff; +} + +/* 表单标签样式 */ +.form-check-label { + cursor: pointer; + width: 100%; +} + +.form-check-label strong { + display: block; + margin-bottom: 0.25rem; +} + +.form-check-label small { + color: #6c757d; +} + +/* 商品图片样式 */ +.product-item img { + max-height: 80px; + object-fit: cover; +} + +/* 价格显示样式 */ +.fw-bold { + color: #dc3545; +} + +/* 面包屑导航样式 */ +.breadcrumb { + background: transparent; + padding: 0; +} + +.breadcrumb-item + .breadcrumb-item::before { + color: #6c757d; +} + + +🔸============================================================================== +📄 文件: app/static/css/favorites.css +📊 大小: 2094 bytes (2.04 KB) +🕒 修改时间: 2025-07-09 03:07:24 +🔸============================================================================== + +/* 收藏页面样式 */ +.favorite-item { + transition: all 0.3s ease; + border: 1px solid #e9ecef; +} + +.favorite-item:hover { + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + transform: translateY(-2px); +} + +.favorite-image { + width: 80px; + height: 80px; + object-fit: cover; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.favorite-image-placeholder { + width: 80px; + height: 80px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + color: #6c757d; + font-size: 2rem; +} + +.favorite-checkbox { + transform: scale(1.2); +} + +.empty-state { + min-height: 300px; + display: flex; + align-items: center; + justify-content: center; +} + +/* 图标按钮样式 */ +.icon-buttons .btn { + font-size: 1.1rem; + padding: 0.5rem; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + margin: 0 2px; + transition: all 0.2s ease; +} + +.icon-buttons .btn:hover { + transform: scale(1.1); +} + +.icon-buttons .btn-outline-primary:hover { + background-color: #007bff; + border-color: #007bff; + color: white; +} + +.icon-buttons .btn-outline-danger:hover { + background-color: #dc3545; + border-color: #dc3545; + color: white; +} + +.card-title a { + color: #212529; + font-size: 0.95rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.card-title a:hover { + color: #007bff; +} + +.favorite-item .card-body { + padding: 1rem; +} + +.badge { + font-size: 0.75rem; +} + +/* 工具提示样式 */ +.tooltip-inner { + font-size: 0.8rem; + padding: 0.25rem 0.5rem; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .favorite-image, .favorite-image-placeholder { + width: 60px; + height: 60px; + } + + .card-title a { + font-size: 0.9rem; + } + + .icon-buttons .btn { + font-size: 1rem; + width: 35px; + height: 35px; + } +} + + +🔸============================================================================== +📄 文件: app/static/css/history.css +📊 大小: 2369 bytes (2.31 KB) +🕒 修改时间: 2025-07-09 03:07:52 +🔸============================================================================== + +/* 浏览历史页面样式 */ +.history-item { + transition: all 0.3s ease; + border: 1px solid #e9ecef; +} + +.history-item:hover { + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + transform: translateY(-2px); +} + +.history-image { + width: 80px; + height: 80px; + object-fit: cover; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.history-image-placeholder { + width: 80px; + height: 80px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + color: #6c757d; + font-size: 2rem; +} + +.history-checkbox { + transform: scale(1.2); +} + +.empty-state { + min-height: 300px; + display: flex; + align-items: center; + justify-content: center; +} + +/* 卡片底部按钮区域样式 */ +.history-item .card-footer { + background-color: #f8f9fa; + border-top: 1px solid #e9ecef; + padding: 0.75rem; + margin-top: auto; +} + +.history-item .card-footer .btn-group { + display: flex; + gap: 0.25rem; +} + +.history-item .card-footer .btn { + font-size: 0.8rem; + padding: 0.375rem 0.75rem; + border-radius: 4px; + transition: all 0.2s ease; + flex: 1; +} + +.history-item .card-footer .btn:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.history-item .card-footer .btn-outline-primary:hover { + background-color: #007bff; + border-color: #007bff; + color: white; +} + +.history-item .card-footer .btn-outline-danger:hover { + background-color: #dc3545; + border-color: #dc3545; + color: white; +} + +.history-item .card-footer .btn-outline-secondary:hover { + background-color: #6c757d; + border-color: #6c757d; + color: white; +} + +.card-title a { + color: #212529; + font-size: 0.95rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.card-title a:hover { + color: #007bff; +} + +.history-item .card-body { + padding: 1rem; +} + +.badge { + font-size: 0.75rem; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .history-image, .history-image-placeholder { + width: 60px; + height: 60px; + } + + .card-title a { + font-size: 0.9rem; + } + + .history-item .card-footer .btn { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + } +} + + +🔸============================================================================== +📄 文件: app/static/css/index.css +📊 大小: 1679 bytes (1.64 KB) +🕒 修改时间: 2025-07-09 04:24:10 +🔸============================================================================== + +/* 首页样式 */ +.product-card { + transition: transform 0.2s; +} + +.product-card:hover { + transform: translateY(-5px); + box-shadow: 0 4px 15px rgba(0,0,0,0.1); +} + +.category-card { + transition: all 0.2s; +} + +.category-card:hover { + transform: translateY(-3px); + box-shadow: 0 4px 10px rgba(0,0,0,0.1); +} + +/* 欢迎横幅样式 */ +/* 欢迎横幅样式 */ +.jumbotron { + background: linear-gradient(135deg, #007bff 0%, #0056b3 100%) !important; + color: white !important; + border-radius: 0.5rem !important; +} + +.jumbotron h1 { + color: white !important; + font-weight: bold !important; +} + +.jumbotron p { + color: white !important; + opacity: 0.9; +} + +.jumbotron .btn-light { + background-color: white !important; + color: #007bff !important; + border: none !important; + font-weight: bold !important; +} + +.jumbotron .btn-light:hover { + background-color: #f8f9fa !important; + color: #0056b3 !important; +} + +/* 商品图片样式 */ +.product-image { + height: 200px; + object-fit: cover; +} + +.product-image-placeholder { + height: 200px; + background-color: #f8f9fa; + display: flex; + align-items: center; + justify-content: center; +} + +/* 价格样式 */ +.price-current { + color: #dc3545; + font-weight: bold; +} + +.price-original { + color: #6c757d; + text-decoration: line-through; + font-size: 0.875rem; +} + +/* 服务特色卡片 */ +.feature-card { + transition: transform 0.2s; +} + +.feature-card:hover { + transform: translateY(-3px); +} + +/* 用户专区卡片 */ +.user-zone-card { + transition: all 0.2s; +} + +.user-zone-card:hover { + transform: translateY(-2px); + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + + +🔸============================================================================== +📄 文件: app/static/css/order_detail.css +📊 大小: 2123 bytes (2.07 KB) +🕒 修改时间: 2025-07-08 16:54:22 +🔸============================================================================== + +/* 订单详情页面样式 */ + +/* 首先,重置所有可能影响的样式 */ +.order-detail-card .product-item img { + all: unset !important; +} + +/* 然后重新定义我们需要的样式 */ +.order-detail-card .product-item img { + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + object-fit: cover !important; + border-radius: 4px !important; + border: 1px solid #ddd !important; + display: block !important; + box-sizing: border-box !important; +} + +/* 订单状态时间线 */ +.order-status-timeline { + position: relative; + padding-left: 30px; +} + +.timeline-item { + position: relative; + padding-bottom: 20px; +} + +.timeline-item::before { + content: ''; + position: absolute; + left: -30px; + top: 0; + width: 12px; + height: 12px; + border-radius: 50%; + background-color: #dee2e6; +} + +.timeline-item.completed::before { + background-color: #28a745; +} + +.timeline-item.current::before { + background-color: #007bff; + box-shadow: 0 0 0 4px rgba(0,123,255,0.2); +} + +.timeline-item::after { + content: ''; + position: absolute; + left: -24px; + top: 12px; + width: 2px; + height: calc(100% - 12px); + background-color: #dee2e6; +} + +.timeline-item:last-child::after { + display: none; +} + +.timeline-item.completed::after { + background-color: #28a745; +} + +.order-detail-card { + margin-bottom: 20px; +} + +.product-item { + border-bottom: 1px solid #f0f0f0; + padding: 15px 0; +} + +.product-item:last-child { + border-bottom: none; +} + +.info-row { + display: flex; + justify-content: space-between; + margin-bottom: 8px; +} + +.total-amount { + color: #e74c3c; + font-weight: bold; + font-size: 1.2em; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .order-detail-card .product-item img { + width: 60px !important; + height: 60px !important; + max-width: 60px !important; + max-height: 60px !important; + min-width: 60px !important; + min-height: 60px !important; + } +} + + +🔸============================================================================== +📄 文件: app/static/css/orders.css +📊 大小: 2905 bytes (2.84 KB) +🕒 修改时间: 2025-07-04 04:02:57 +🔸============================================================================== + +/* 订单页面样式 */ +.order-card { + margin-bottom: 20px; + border: 1px solid #dee2e6; + border-radius: 8px; + transition: box-shadow 0.3s ease; +} + +.order-card:hover { + box-shadow: 0 4px 15px rgba(0,0,0,0.1); +} + +.order-header { + background-color: #f8f9fa; + padding: 15px; + border-bottom: 1px solid #dee2e6; + border-radius: 8px 8px 0 0; +} + +.order-item { + padding: 15px; + border-bottom: 1px solid #f0f0f0; +} + +.order-item:last-child { + border-bottom: none; +} + +.order-footer { + background-color: #f8f9fa; + padding: 15px; + border-top: 1px solid #dee2e6; + border-radius: 0 0 8px 8px; +} + +.status-badge { + font-size: 0.85em; + padding: 4px 8px; +} + +.order-amount { + color: #e74c3c; + font-weight: bold; + font-size: 1.1em; +} + +/* 强制限制商品图片尺寸 - 多重选择器确保优先级 */ +.product-image, +img.product-image, +.order-item .product-image, +.order-item img.product-image { + width: 80px !important; + height: 80px !important; + object-fit: cover !important; + border-radius: 4px !important; + display: block !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; +} + +/* 防止任何外部样式影响 */ +.col-md-2 .product-image, +.order-item .col-md-2 img { + width: 80px !important; + height: 80px !important; + object-fit: cover !important; + border-radius: 4px !important; +} + +/* 导航标签样式 */ +.nav-pills .nav-link { + border-radius: 20px; + padding: 0.5rem 1rem; + margin-right: 0.5rem; + transition: all 0.3s ease; +} + +.nav-pills .nav-link:hover { + background-color: #e9ecef; + color: #495057; +} + +.nav-pills .nav-link.active { + background-color: #007bff; + color: white; +} + +/* 空状态样式 */ +.empty-state { + padding: 3rem 0; +} + +.empty-state i { + opacity: 0.5; +} + +/* 分页样式 */ +.pagination { + margin-top: 2rem; +} + +.page-link { + color: #007bff; + border-color: #dee2e6; +} + +.page-link:hover { + color: #0056b3; + background-color: #e9ecef; + border-color: #dee2e6; +} + +.page-item.active .page-link { + background-color: #007bff; + border-color: #007bff; +} + +/* 按钮组样式 */ +.btn-group .btn { + margin-right: 0.5rem; +} + +.btn-group .btn:last-child { + margin-right: 0; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .order-header .row, + .order-footer .row { + flex-direction: column; + } + + .order-header .col-md-3, + .order-footer .col-md-6 { + margin-bottom: 0.5rem; + text-align: left !important; + } + + .order-item .row { + flex-direction: column; + text-align: center; + } + + .order-item .col-md-2, + .order-item .col-md-6 { + margin-bottom: 1rem; + } + + .nav-pills { + flex-wrap: wrap; + } + + .nav-pills .nav-link { + margin-bottom: 0.5rem; + font-size: 0.9rem; + } +} + + +🔸============================================================================== +📄 文件: app/static/css/pay.css +📊 大小: 2169 bytes (2.12 KB) +🕒 修改时间: 2025-07-08 17:11:04 +🔸============================================================================== + +/* 支付页面样式 */ +.pay-container { + max-width: 600px; + margin: 2rem auto; + padding: 0 1rem; +} + +.order-info { + background: #f8f9fa; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1.5rem; +} + +.payment-method { + border: 2px solid #e9ecef; + border-radius: 0.5rem; + padding: 1rem; + margin-bottom: 1rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.payment-method:hover { + border-color: #007bff; + background-color: #f8f9fa; +} + +.payment-method.selected { + border-color: #007bff; + background-color: #e7f3ff; +} + +.qr-code { + text-align: center; + padding: 2rem; + background: #fff; + border: 1px solid #dee2e6; + border-radius: 0.5rem; + margin: 1rem 0; +} + +.payment-status { + text-align: center; + padding: 3rem 1rem; +} + +.countdown { + font-weight: bold; + color: #dc3545; + font-size: 1.1rem; +} + +.simulate-panel { + background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); + border: 2px solid #ffc107 !important; +} + +.simulate-panel h6 { + margin-bottom: 0.5rem; +} + +.simulate-panel .btn { + min-width: 140px; +} + +.payment-tips { + background: #f8f9fa; + padding: 1rem; + border-radius: 0.5rem; + border-left: 4px solid #007bff; +} + +.payment-tips ul { + margin-bottom: 0; + padding-left: 1.2rem; +} + +.payment-tips li { + margin-bottom: 0.3rem; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .pay-container { + margin: 1rem auto; + padding: 0 0.5rem; + } + + .d-md-flex .btn { + margin-bottom: 0.5rem; + } + + .simulate-panel .btn { + min-width: auto; + width: 100%; + } +} + +/* 动画效果 */ +.payment-status i { + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } + 100% { + transform: scale(1); + } +} + +/* 按钮状态 */ +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* 支付方式图标 */ +.payment-method i { + min-width: 60px; +} + +/* 倒计时样式 */ +.countdown { + background: #fff; + padding: 0.2rem 0.5rem; + border-radius: 0.25rem; + border: 1px solid #dc3545; +} + + +🔸============================================================================== +📄 文件: app/static/css/product_detail.css +📊 大小: 6622 bytes (6.47 KB) +🕒 修改时间: 2025-07-09 05:23:08 +🔸============================================================================== + +/* 商品详情页样式 */ + +.product-card { + transition: transform 0.2s; +} + +.product-card:hover { + +/* 规格选择样式 */ +.specs-section { + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 20px; + background-color: #f8f9fa; +} + +.spec-group { + margin-bottom: 15px; +} + +.spec-group:last-child { + margin-bottom: 0; +} + +.spec-group .form-label { + font-weight: 600; + margin-bottom: 10px; + color: #495057; +} + +.spec-options { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.spec-option { + min-width: 60px; + padding: 8px 16px; + border: 1px solid #dee2e6; + border-radius: 6px; + background-color: #fff; + color: #495057; + cursor: pointer; + transition: all 0.2s ease; + font-size: 14px; +} + +.spec-option:hover { + border-color: #007bff; + background-color: #e3f2fd; +} + +.spec-option.btn-primary { + background-color: #007bff; + border-color: #007bff; + color: white; +} + +.spec-option.btn-primary:hover { + background-color: #0056b3; + border-color: #0056b3; +} + +.spec-option:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: #e9ecef; +} + +/* 库存信息样式 */ +.stock-section { + padding: 15px; + background-color: #f8f9fa; + border-radius: 8px; + border: 1px solid #dee2e6; +} + +#stockCount { + font-weight: 600; + font-size: 1.1em; +} + +/* 数量选择样式 */ +.quantity-section .input-group { + max-width: 150px; +} + +.quantity-section .form-control { + text-align: center; + font-weight: 600; +} + +/* 操作按钮样式 */ +.action-buttons .btn { + padding: 12px 24px; + font-weight: 600; + border-radius: 8px; +} + +.action-buttons .btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* 服务承诺样式 */ +.service-promises { + background-color: #f8f9fa; + padding: 15px; + border-radius: 8px; + border: 1px solid #dee2e6; +} + +.service-promises h6 { + color: #495057; + margin-bottom: 10px; +} + +.service-promises li { + margin-bottom: 5px; + color: #6c757d; +} + +.service-promises .bi-check-circle { + margin-right: 8px; +} + transform: translateY(-5px); + box-shadow: 0 4px 15px rgba(0,0,0,0.1); +} + +.spec-option { + border-radius: 4px; + transition: all 0.2s; +} + +.spec-option:hover { + transform: translateY(-1px); +} + +.thumbnail-image { + transition: all 0.2s; +} + +.thumbnail-image:hover { + transform: scale(1.05); + box-shadow: 0 2px 8px rgba(0,0,0,0.2); +} + +.price-section { + background: linear-gradient(135deg, #fff5f5 0%, #ffeee8 100%); + padding: 20px; + border-radius: 8px; + border: 1px solid #ffe6e6; +} + +.product-description { + line-height: 1.8; + white-space: pre-line; +} + +.service-promises li { + padding: 5px 0; +} + +/* 规格选择动效 */ +.spec-option.btn-primary { + background-color: #007bff; + border-color: #007bff; + color: white; + transform: scale(1.05); +} + +/* 按钮禁用状态样式 */ +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* 商品主图轮播样式修复 */ +.carousel-inner img { + /* 重置Bootstrap图片样式 */ + all: unset !important; + display: block !important; + width: 100% !important; + height: 400px !important; + object-fit: cover !important; + border-radius: 8px !important; +} + +/* 缩略图样式修复 */ +.thumbnail-image { + /* 重置Bootstrap图片样式 */ + all: unset !important; + display: block !important; + width: 100% !important; + height: 80px !important; + object-fit: cover !important; + cursor: pointer !important; + border-radius: 4px !important; + border: 2px solid #dee2e6 !important; + transition: all 0.2s ease !important; +} + +.thumbnail-image:hover { + transform: scale(1.05); + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + border-color: #007bff; +} + +/* 推荐商品图片样式修复 */ +.product-card .card-img-top { + /* 重置Bootstrap图片样式 */ + all: unset !important; + display: block !important; + width: 100% !important; + height: 200px !important; + object-fit: cover !important; + border-top-left-radius: 0.375rem !important; + border-top-right-radius: 0.375rem !important; +} + +/* 商品详情标签页内的图片样式 */ +.tab-content img { + /* 确保标签页内的图片不会过大 */ + max-width: 100% !important; + height: auto !important; + border-radius: 4px !important; + border: 1px solid #dee2e6 !important; +} + +/* 评价图片在商品详情页中的特殊样式 */ +.reviews-section img { + /* 重置评价图片样式 */ + all: unset !important; + display: inline-block !important; + max-width: 80px !important; + max-height: 80px !important; + width: auto !important; + height: auto !important; + object-fit: cover !important; + border-radius: 6px !important; + border: 1px solid #dee2e6 !important; + cursor: pointer !important; + transition: all 0.2s ease !important; + margin-right: 8px !important; + margin-bottom: 8px !important; +} + +.reviews-section img:hover { + transform: scale(1.05); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + border-color: #007bff; +} + +/* 用户头像图片样式 */ +.reviewer-avatar { + /* 重置用户头像样式 */ + all: unset !important; + display: block !important; + width: 40px !important; + height: 40px !important; + border-radius: 50% !important; + object-fit: cover !important; + border: 2px solid #e9ecef !important; +} + +/* 图片模态框样式 */ +.modal-body img { + /* 模态框中的图片样式 */ + all: unset !important; + display: block !important; + max-width: 100% !important; + max-height: 80vh !important; + width: auto !important; + height: auto !important; + margin: 0 auto !important; + border-radius: 8px !important; + box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; +} + +/* 响应式优化 */ +@media (max-width: 768px) { + .price-section { + text-align: center; + } + + .action-buttons .d-md-flex { + flex-direction: column; + } + + .action-buttons .btn { + margin-bottom: 10px; + } + + .carousel-inner img { + height: 300px !important; + } + + .thumbnail-image { + height: 60px !important; + } + + .reviews-section img { + max-width: 60px !important; + max-height: 60px !important; + } +} + +/* 无图片占位符样式 */ +.bg-light.d-flex { + background-color: #f8f9fa !important; + border: 2px dashed #dee2e6 !important; +} + +/* 确保所有图片都有基础的重置样式 */ +.product-detail img:not(.reviewer-avatar):not(.thumbnail-image):not(.card-img-top) { + max-width: 100% !important; + height: auto !important; + border-radius: 4px !important; +} + + +🔸============================================================================== +📄 文件: app/static/css/product_list.css +📊 大小: 259 bytes (0.25 KB) +🕒 修改时间: 2025-07-04 14:41:00 +🔸============================================================================== + +.product-card { + transition: transform 0.2s; +} + +.product-card:hover { + transform: translateY(-5px); + box-shadow: 0 4px 15px rgba(0,0,0,0.1); +} + +.btn-check:checked + .btn { + background-color: #007bff; + border-color: #007bff; + color: white; +} + + +🔸============================================================================== +📄 文件: app/static/css/profile.css +📊 大小: 5567 bytes (5.44 KB) +🕒 修改时间: 2025-07-04 03:59:34 +🔸============================================================================== + +/* 个人中心页面样式 */ + +/* 头像上传相关CSS */ +.avatar-upload { + position: relative !important; + display: inline-block !important; + width: 120px !important; + height: 120px !important; + overflow: hidden !important; +} + +/* 强制限制头像尺寸 - 多重选择器确保优先级 */ +.avatar-preview, +#avatarPreview, +.avatar-upload .avatar-preview, +.avatar-upload #avatarPreview { + width: 120px !important; + height: 120px !important; + border-radius: 50% !important; + border: 3px solid #ddd !important; + object-fit: cover !important; + cursor: pointer !important; + transition: all 0.3s ease !important; + display: block !important; + max-width: 120px !important; + max-height: 120px !important; + min-width: 120px !important; + min-height: 120px !important; +} + +.avatar-preview:hover, +#avatarPreview:hover { + border-color: #007bff !important; + transform: scale(1.05) !important; +} + +.avatar-placeholder { + width: 120px !important; + height: 120px !important; + border-radius: 50% !important; + border: 3px dashed #ddd !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + transition: all 0.3s ease !important; + background-color: #f8f9fa !important; +} + +.avatar-placeholder:hover { + border-color: #007bff !important; + background-color: #e3f2fd !important; +} + +.upload-overlay { + position: absolute !important; + top: 0 !important; + left: 0 !important; + width: 120px !important; + height: 120px !important; + border-radius: 50% !important; + background: rgba(0, 0, 0, 0.5) !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + opacity: 0 !important; + transition: opacity 0.3s ease !important; + cursor: pointer !important; +} + +.avatar-upload:hover .upload-overlay { + opacity: 1 !important; +} + +.upload-progress { + display: none !important; + margin-top: 10px !important; +} + +.upload-progress.show { + display: block !important; +} + +/* 图片预览模态框样式 */ +.image-preview-modal .modal-dialog { + max-width: 500px !important; + margin: 1.75rem auto !important; + display: flex !important; + align-items: center !important; + min-height: calc(100% - 3.5rem) !important; +} + +.image-preview-modal .modal-content { + max-height: 90vh !important; + display: flex !important; + flex-direction: column !important; +} + +.image-preview-modal .modal-body { + overflow-y: auto !important; +} + +.preview-container { + background: #f8f9fa !important; + border-radius: 12px !important; + padding: 30px !important; + text-align: center !important; +} + +.preview-image-wrapper { + position: relative !important; + display: block !important; + margin-bottom: 20px !important; + max-width: 100% !important; +} + +/* 强制限制预览图片大小 */ +.preview-image, +#previewImage, +.preview-image-wrapper .preview-image, +.preview-image-wrapper #previewImage { + max-width: 280px !important; + max-height: 280px !important; + width: auto !important; + height: auto !important; + object-fit: contain !important; + border-radius: 12px !important; + box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important; + border: 3px solid #fff !important; +} + +.preview-info { + background: #fff !important; + border-radius: 8px !important; + padding: 15px !important; + margin-top: 15px !important; + box-shadow: 0 2px 10px rgba(0,0,0,0.1) !important; +} + +.preview-stats { + display: flex !important; + justify-content: space-around !important; + margin-top: 10px !important; +} + +.stat-item { + text-align: center !important; +} + +.stat-value { + font-weight: bold !important; + color: #007bff !important; + font-size: 1.1em !important; +} + +.stat-label { + font-size: 0.85em !important; + color: #6c757d !important; + margin-top: 2px !important; +} + +/* 进度条样式 */ +.upload-progress .progress { + height: 8px !important; + margin-bottom: 5px !important; + border-radius: 4px !important; +} + +.upload-progress .progress-bar { + transition: width 0.3s ease !important; + border-radius: 4px !important; +} + +/* 模态框动画 */ +.modal.fade .modal-dialog { + transition: transform 0.3s ease-out !important; + transform: translate(0, -50px) !important; +} + +.modal.show .modal-dialog { + transform: none !important; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .image-preview-modal .modal-dialog { + max-width: 95% !important; + margin: 10px auto !important; + } + + .preview-image, + #previewImage { + max-width: 250px !important; + max-height: 250px !important; + } + + .preview-container { + padding: 20px !important; + } +} + +/* 大屏幕优化 */ +@media (min-width: 1200px) { + .preview-image, + #previewImage { + max-width: 300px !important; + max-height: 300px !important; + } +} + +/* 终极覆盖规则 - 确保所有情况下样式都生效 */ +img.avatar-preview, +img#avatarPreview { + width: 120px !important; + height: 120px !important; + border-radius: 50% !important; + object-fit: cover !important; + max-width: 120px !important; + max-height: 120px !important; + min-width: 120px !important; + min-height: 120px !important; +} + +/* 防止任何外部样式影响 */ +.col-md-4 .avatar-upload img, +.text-center .avatar-upload img { + width: 120px !important; + height: 120px !important; + border-radius: 50% !important; + object-fit: cover !important; +} + + +🔸============================================================================== +📄 文件: app/static/css/register.css +📊 大小: 526 bytes (0.51 KB) +🕒 修改时间: 2025-07-04 03:58:30 +🔸============================================================================== + +/* 注册页面样式 */ +.is-valid { + border-color: #28a745 !important; +} + +.is-invalid { + border-color: #dc3545 !important; +} + +.text-success { + color: #28a745 !important; +} + +.text-danger { + color: #dc3545 !important; +} + +/* 验证码按钮样式 */ +.btn.disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* 表单增强样式 */ +.form-text { + font-size: 0.875em; + margin-top: 0.25rem; +} + +.input-group .btn { + border-left: 0; +} + +.input-group .form-control:focus + .btn { + border-color: #86b7fe; +} + + +🔸============================================================================== +📄 文件: app/static/css/review.css +📊 大小: 14587 bytes (14.25 KB) +🕒 修改时间: 2025-07-08 19:29:05 +🔸============================================================================== + +/* 评价功能样式 */ + +/* 评价表单样式 */ +.product-info { + background-color: #f8f9fa; + padding: 15px; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.product-info img { + /* 图片重置样式 - 解决Bootstrap冲突 */ + all: unset !important; + display: block !important; + max-width: 100% !important; + max-height: 80px !important; + width: auto !important; + height: auto !important; + object-fit: cover !important; + border-radius: 4px !important; + border: 1px solid #dee2e6 !important; +} + +/* 星级评分样式 - 简化版本 */ +.rating-container { + display: flex; + align-items: center; + gap: 15px; +} + +.star-rating { + display: flex; + gap: 3px; +} + +.star { + font-size: 2.5rem; + cursor: pointer; + transition: all 0.2s ease; + user-select: none; + line-height: 1; + + /* 默认样式:灰色 */ + color: #ddd !important; +} + +.star:hover { + transform: scale(1.1); +} + +/* 填充状态:橙色 */ +.star.filled { + color: #ff6b35 !important; +} + +/* 评分文字样式 */ +.rating-text { + font-weight: 600; + color: #666; + font-size: 1.1rem; + padding: 10px 15px; + background-color: #f8f9fa; + border-radius: 8px; + border: 2px solid #e9ecef; + min-width: 120px; + text-align: center; + transition: all 0.2s ease; +} + +.rating-text.selected { + background-color: #ff6b35; + color: white; + border-color: #ff6b35; +} + +/* 图片上传样式 */ +.image-upload-container { + border: 2px dashed #ddd; + border-radius: 8px; + padding: 20px; + text-align: center; + transition: border-color 0.3s ease; +} + +.image-upload-container:hover { + border-color: #007bff; +} + +.upload-area { + cursor: pointer; + padding: 20px; +} + +.upload-area i { + font-size: 3rem; + color: #666; + display: block; + margin-bottom: 10px; +} + +/* 上传图片预览容器 - 强制控制布局 */ +.uploaded-images { + display: flex !important; + flex-wrap: wrap !important; + gap: 8px !important; + margin-top: 15px !important; + justify-content: flex-start !important; + align-items: flex-start !important; +} + +/* 上传图片预览尺寸 - 使用最强的样式规则 */ +.image-preview { + position: relative !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + border-radius: 8px !important; + overflow: hidden !important; + border: 2px solid #e9ecef !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; + display: inline-block !important; + box-sizing: border-box !important; +} + +/* 强制重置上传预览图片的所有样式 */ +.image-preview img { + all: unset !important; + display: block !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + object-fit: cover !important; + border-radius: 6px !important; + box-sizing: border-box !important; + position: relative !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + margin: 0 !important; + padding: 0 !important; + border: none !important; + outline: none !important; + background: none !important; +} + +/* 针对上传图片容器内的所有img标签 */ +.uploaded-images img { + all: unset !important; + display: block !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + object-fit: cover !important; + border-radius: 6px !important; + box-sizing: border-box !important; + position: relative !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; +} + +/* 上传图片容器的直接img子元素 */ +.uploaded-images > .image-preview > img { + all: unset !important; + display: block !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + object-fit: cover !important; + border-radius: 6px !important; +} + +.image-preview .remove-btn { + position: absolute !important; + top: 2px !important; + right: 2px !important; + background: rgba(255, 255, 255, 0.9) !important; + border: none !important; + border-radius: 50% !important; + width: 20px !important; + height: 20px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + font-size: 12px !important; + color: #dc3545 !important; + box-shadow: 0 1px 3px rgba(0,0,0,0.2) !important; + z-index: 10 !important; +} + +.image-preview .remove-btn:hover { + background: rgba(255, 255, 255, 1) !important; + transform: scale(1.1) !important; +} + +/* 评价列表样式 */ +.review-item { + border-bottom: 1px solid #e9ecef; + padding: 20px 0; + margin-bottom: 20px; +} + +.review-item:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.rating-display .stars { + color: #ff6b35 !important; + font-size: 1.2rem; + margin-right: 8px; +} + +.review-content { + line-height: 1.6; + margin: 10px 0; + word-wrap: break-word; +} + +.review-images { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.review-image-thumb { + /* 图片重置样式 - 解决Bootstrap冲突 */ + all: unset !important; + display: block !important; + width: 60px !important; + height: 60px !important; + object-fit: cover !important; + border-radius: 4px !important; + border: 1px solid #e9ecef !important; + cursor: pointer !important; + transition: transform 0.2s ease !important; +} + +.review-image-thumb:hover { + transform: scale(1.05); + box-shadow: 0 2px 8px rgba(0,0,0,0.15); +} + +.review-meta { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid #f8f9fa; +} + +/* 商品详情页评价标签页样式 */ +.reviews-section { + padding: 20px 0; +} + +.reviews-stats { + background: #f8f9fa; + padding: 20px; + border-radius: 8px; + margin-bottom: 20px; +} + +.rating-summary { + display: flex; + align-items: center; + gap: 20px; + margin-bottom: 15px; +} + +.overall-rating { + text-align: center; +} + +.overall-rating .score { + font-size: 3rem; + font-weight: bold; + color: #ff6b35; + line-height: 1; +} + +.overall-rating .stars { + color: #ff6b35 !important; + font-size: 1.5rem; +} + +.overall-rating .total { + color: #666; + margin-top: 5px; +} + +.rating-breakdown { + flex: 1; +} + +.rating-bar { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 8px; +} + +.rating-bar .label { + width: 40px; + font-size: 14px; +} + +.rating-bar .progress { + flex: 1; + height: 8px; +} + +.rating-bar .count { + width: 40px; + text-align: right; + font-size: 14px; + color: #666; +} + +.reviews-filter { + margin-bottom: 20px; +} + +.reviews-filter .btn { + margin-right: 10px; + margin-bottom: 10px; +} + +.review-list-item { + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 20px; + margin-bottom: 15px; + background: #fff; +} + +.reviewer-info { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 15px; +} + +/* 用户头像样式 - 重点修复区域 */ +.reviewer-avatar { + /* 头像图片重置样式 - 强制重置所有样式 */ + all: unset !important; + display: block !important; + width: 40px !important; + height: 40px !important; + max-width: 40px !important; + max-height: 40px !important; + min-width: 40px !important; + min-height: 40px !important; + border-radius: 50% !important; + object-fit: cover !important; + border: 2px solid #e9ecef !important; + box-sizing: border-box !important; + flex-shrink: 0 !important; + vertical-align: top !important; +} + +/* 针对商品详情页评价容器中的头像 */ +#reviewsContainer .reviewer-avatar { + /* 强制重置商品详情页评价容器中的头像 */ + all: unset !important; + display: block !important; + width: 40px !important; + height: 40px !important; + max-width: 40px !important; + max-height: 40px !important; + min-width: 40px !important; + min-height: 40px !important; + border-radius: 50% !important; + object-fit: cover !important; + border: 2px solid #e9ecef !important; + box-sizing: border-box !important; + flex-shrink: 0 !important; + vertical-align: top !important; +} + +/* 针对评价标签页中的头像 */ +#reviews .reviewer-avatar { + /* 评价标签页中的头像 */ + all: unset !important; + display: block !important; + width: 40px !important; + height: 40px !important; + max-width: 40px !important; + max-height: 40px !important; + min-width: 40px !important; + min-height: 40px !important; + border-radius: 50% !important; + object-fit: cover !important; + border: 2px solid #e9ecef !important; + box-sizing: border-box !important; + flex-shrink: 0 !important; + vertical-align: top !important; +} + +.reviewer-name { + font-weight: 500; +} + +.review-time { + color: #666; + font-size: 14px; +} + +.empty-state { + padding: 60px 20px; +} + +.empty-state i { + opacity: 0.3; +} + +/* 商品详情页评价图片展示 - 重点修复区域 */ +.product-review-images { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-top: 10px; +} + +.product-review-image { + /* 商品评价图片重置样式 - 强制重置所有样式 */ + all: unset !important; + display: inline-block !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + object-fit: cover !important; + border-radius: 6px !important; + border: 1px solid #dee2e6 !important; + cursor: pointer !important; + transition: all 0.2s ease !important; + box-sizing: border-box !important; + vertical-align: top !important; +} + +.product-review-image:hover { + transform: scale(1.05) !important; + box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; + border-color: #007bff !important; +} + +/* 特殊针对商品详情页面的评价容器 */ +#reviewsContainer img:not(.reviewer-avatar) { + /* 强制重置商品详情页评价容器中的所有图片(除了头像) */ + all: unset !important; + display: inline-block !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + object-fit: cover !important; + border-radius: 6px !important; + border: 1px solid #dee2e6 !important; + cursor: pointer !important; + transition: all 0.2s ease !important; + margin-right: 8px !important; + margin-bottom: 8px !important; + box-sizing: border-box !important; + vertical-align: top !important; +} + +#reviewsContainer img:not(.reviewer-avatar):hover { + transform: scale(1.05) !important; + box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; + border-color: #007bff !important; +} + +/* 评价标签页特殊处理 */ +#reviews img:not(.reviewer-avatar) { + /* 评价标签页中的图片(除了头像) */ + all: unset !important; + display: inline-block !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + object-fit: cover !important; + border-radius: 6px !important; + border: 1px solid #dee2e6 !important; + cursor: pointer !important; + transition: all 0.2s ease !important; + margin-right: 8px !important; + margin-bottom: 8px !important; + box-sizing: border-box !important; +} + +#reviews img:not(.reviewer-avatar):hover { + transform: scale(1.05) !important; + box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; + border-color: #007bff !important; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .star { + font-size: 2rem; + } + + .rating-summary { + flex-direction: column; + align-items: flex-start; + gap: 15px; + } + + .uploaded-images { + justify-content: flex-start !important; + } + + /* 移动端上传图片预览更小 */ + .image-preview { + width: 60px !important; + height: 60px !important; + max-width: 60px !important; + max-height: 60px !important; + min-width: 60px !important; + min-height: 60px !important; + } + + .image-preview img, + .uploaded-images img, + .uploaded-images > .image-preview > img { + width: 60px !important; + height: 60px !important; + max-width: 60px !important; + max-height: 60px !important; + min-width: 60px !important; + min-height: 60px !important; + } + + .image-preview .remove-btn { + width: 16px !important; + height: 16px !important; + font-size: 10px !important; + top: 1px !important; + right: 1px !important; + } + + .review-image-thumb { + width: 50px !important; + height: 50px !important; + } + + .product-review-image, + #reviewsContainer img:not(.reviewer-avatar), + #reviews img:not(.reviewer-avatar) { + width: 60px !important; + height: 60px !important; + max-width: 60px !important; + max-height: 60px !important; + min-width: 60px !important; + min-height: 60px !important; + } + + .reviewer-avatar, + #reviewsContainer .reviewer-avatar, + #reviews .reviewer-avatar { + width: 35px !important; + height: 35px !important; + max-width: 35px !important; + max-height: 35px !important; + min-width: 35px !important; + min-height: 35px !important; + } +} + +/* 加载状态 */ +.loading { + opacity: 0.6; + pointer-events: none; +} + +.loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 20px; + height: 20px; + border: 2px solid #f3f3f3; + border-top: 2px solid #007bff; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: translate(-50%, -50%) rotate(0deg); } + 100% { transform: translate(-50%, -50%) rotate(360deg); } +} + +/* 动画效果 */ +.review-item { + animation: fadeInUp 0.3s ease; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + + +🔸============================================================================== +📄 文件: app/static/js/address_form.js +📊 大小: 7374 bytes (7.20 KB) +🕒 修改时间: 2025-07-04 04:02:31 +🔸============================================================================== + +// 地址表单页面JavaScript功能 + +// 全局变量,避免重复初始化 +let addressFormInitialized = false; + +// 页面完全加载后初始化 +window.addEventListener('load', function() { + console.log('=== 地址表单初始化开始 ==='); + + // 显示调试信息 + document.getElementById('debugAlert').style.display = 'block'; + + // 检查数据是否加载 + if (typeof cityData === 'undefined') { + console.error('❌ cityData 未加载'); + document.getElementById('debugInfo').innerHTML = '❌ 地址数据加载失败'; + showAlert('地址数据加载失败,请刷新页面重试', 'error'); + return; + } + + console.log('✅ cityData 已加载,省份数量:', Object.keys(cityData).length); + document.getElementById('debugInfo').innerHTML = '✅ 地址数据已加载,省份数量: ' + Object.keys(cityData).length + ''; + + // 避免重复初始化 + if (addressFormInitialized) { + console.log('地址表单已初始化,跳过'); + return; + } + + addressFormInitialized = true; + + // 初始化省份列表 + initializeProvinces(); + + // 设置事件监听器 + setupEventListeners(); + + // 如果是编辑模式,设置初始值 + const savedProvince = document.getElementById('provinceValue').value; + const savedCity = document.getElementById('cityValue').value; + const savedDistrict = document.getElementById('districtValue').value; + + console.log('初始值:', {savedProvince, savedCity, savedDistrict}); + + if (savedProvince) { + setTimeout(() => { + setInitialValues(savedProvince, savedCity, savedDistrict); + }, 500); // 增加延迟确保DOM完全准备好 + } + + console.log('=== 地址表单初始化完成 ==='); +}); + +// 初始化省份列表 +function initializeProvinces() { + const provinceSelect = document.getElementById('province'); + + if (!provinceSelect) { + console.error('省份选择框未找到'); + return; + } + + console.log('开始初始化省份列表...'); + + // 清空并添加默认选项 + provinceSelect.innerHTML = ''; + + // 添加所有省份 + const provinces = Object.keys(cityData); + console.log('可用省份:', provinces); + + provinces.forEach(province => { + const option = document.createElement('option'); + option.value = province; + option.textContent = province; + provinceSelect.appendChild(option); + console.log('添加省份:', province); + }); + + console.log('省份列表初始化完成,总计:', provinces.length, '个'); +} + +// 设置事件监听器 +function setupEventListeners() { + console.log('设置事件监听器...'); + + // 省份变化事件 + const provinceSelect = document.getElementById('province'); + if (provinceSelect) { + provinceSelect.addEventListener('change', function() { + const province = this.value; + console.log('选择省份:', province); + updateCities(province); + clearDistricts(); + }); + } + + // 城市变化事件 + const citySelect = document.getElementById('city'); + if (citySelect) { + citySelect.addEventListener('change', function() { + const province = document.getElementById('province').value; + const city = this.value; + console.log('选择城市:', city, '省份:', province); + updateDistricts(province, city); + }); + } + + // 表单提交验证 + document.getElementById('addressForm').addEventListener('submit', function(e) { + const province = document.getElementById('province').value; + const city = document.getElementById('city').value; + const district = document.getElementById('district').value; + + console.log('表单验证:', {province, city, district}); + + if (!province) { + e.preventDefault(); + showAlert('请选择省份', 'warning'); + return false; + } + + if (!city) { + e.preventDefault(); + showAlert('请选择城市', 'warning'); + return false; + } + + if (!district) { + e.preventDefault(); + showAlert('请选择区县', 'warning'); + return false; + } + + return true; + }); + + console.log('事件监听器设置完成'); +} + +// 更新城市列表 +function updateCities(province) { + const citySelect = document.getElementById('city'); + citySelect.innerHTML = ''; + + console.log('更新城市列表,省份:', province); + + if (!province || !cityData[province]) { + console.log('省份为空或数据不存在'); + return; + } + + const cities = Object.keys(cityData[province]); + console.log('可用城市:', cities); + + cities.forEach(city => { + const option = document.createElement('option'); + option.value = city; + option.textContent = city; + citySelect.appendChild(option); + }); + + console.log('城市列表更新完成,总计:', cities.length, '个'); +} + +// 更新区县列表 +function updateDistricts(province, city) { + const districtSelect = document.getElementById('district'); + districtSelect.innerHTML = ''; + + console.log('更新区县列表,省份:', province, '城市:', city); + + if (!province || !city || !cityData[province] || !cityData[province][city]) { + console.log('省份或城市为空或数据不存在'); + return; + } + + const districts = cityData[province][city]; + console.log('可用区县:', districts); + + districts.forEach(district => { + const option = document.createElement('option'); + option.value = district; + option.textContent = district; + districtSelect.appendChild(option); + }); + + console.log('区县列表更新完成,总计:', districts.length, '个'); +} + +// 清空区县列表 +function clearDistricts() { + const districtSelect = document.getElementById('district'); + districtSelect.innerHTML = ''; + console.log('区县列表已清空'); +} + +// 设置初始值(编辑模式) +function setInitialValues(province, city, district) { + console.log('设置初始值:', {province, city, district}); + + const provinceSelect = document.getElementById('province'); + const citySelect = document.getElementById('city'); + const districtSelect = document.getElementById('district'); + + // 设置省份 + if (province && provinceSelect) { + provinceSelect.value = province; + console.log('省份设置为:', province); + updateCities(province); + + // 延迟设置城市 + setTimeout(() => { + if (city && citySelect) { + citySelect.value = city; + console.log('城市设置为:', city); + updateDistricts(province, city); + + // 延迟设置区县 + setTimeout(() => { + if (district && districtSelect) { + districtSelect.value = district; + console.log('区县设置为:', district); + } + }, 200); + } + }, 200); + } +} + + +🔸============================================================================== +📄 文件: app/static/js/addresses.js +📊 大小: 2037 bytes (1.99 KB) +🕒 修改时间: 2025-07-04 04:01:18 +🔸============================================================================== + +// 地址管理页面JavaScript功能 + +function setDefaultAddress(addressId) { + if (confirm('确定要设置为默认地址吗?')) { + fetch(`/address/set_default/${addressId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert(data.message, 'success'); + setTimeout(() => location.reload(), 1000); + } else { + showAlert(data.message, 'error'); + } + }) + .catch(error => { + showAlert('操作失败,请重试', 'error'); + }); + } +} + +function deleteAddress(addressId) { + if (confirm('确定要删除这个地址吗?删除后无法恢复。')) { + fetch(`/address/delete/${addressId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert(data.message, 'success'); + setTimeout(() => location.reload(), 1000); + } else { + showAlert(data.message, 'error'); + } + }) + .catch(error => { + showAlert('删除失败,请重试', 'error'); + }); + } +} + +// 页面加载完成后的处理 +document.addEventListener('DOMContentLoaded', function() { + // 为地址卡片添加点击效果 + const addressCards = document.querySelectorAll('.address-card'); + addressCards.forEach(card => { + card.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-2px)'; + this.style.boxShadow = '0 4px 15px rgba(0,0,0,0.1)'; + }); + + card.addEventListener('mouseleave', function() { + this.style.transform = 'translateY(0)'; + this.style.boxShadow = ''; + }); + }); +}); + + +🔸============================================================================== +📄 文件: app/static/js/admin_categories.js +📊 大小: 9228 bytes (9.01 KB) +🕒 修改时间: 2025-07-04 18:44:46 +🔸============================================================================== + +// 分类管理页面JavaScript + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', function() { + initIconUpload(); + initEditIconUpload(); + initFormSubmission(); +}); + +// 初始化图标上传 +function initIconUpload() { + const uploadArea = document.getElementById('iconUploadArea'); + const iconInput = document.getElementById('iconInput'); + const iconPreview = document.getElementById('iconPreview'); + + if (uploadArea && iconInput && iconPreview) { + uploadArea.addEventListener('click', () => iconInput.click()); + + iconInput.addEventListener('change', function(e) { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = function(e) { + iconPreview.src = e.target.result; + iconPreview.style.display = 'block'; + uploadArea.querySelector('i').style.display = 'none'; + }; + reader.readAsDataURL(file); + } + }); + } +} + +// 初始化编辑图标上传 +function initEditIconUpload() { + const uploadArea = document.getElementById('editIconUploadArea'); + const iconInput = document.getElementById('editIconInput'); + const iconPreview = document.getElementById('editIconPreview'); + + if (uploadArea && iconInput && iconPreview) { + uploadArea.addEventListener('click', () => iconInput.click()); + + iconInput.addEventListener('change', function(e) { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = function(e) { + iconPreview.src = e.target.result; + iconPreview.style.display = 'block'; + uploadArea.querySelector('i').style.display = 'none'; + }; + reader.readAsDataURL(file); + } + }); + } +} + +// 编辑分类 +function editCategory(categoryId) { + fetch(`/admin/products/categories/${categoryId}`) + .then(response => response.json()) + .then(data => { + if (data.success) { + const category = data.category; + document.getElementById('edit_category_id').value = category.id; + document.getElementById('edit_name').value = category.name; + document.getElementById('edit_parent_id').value = category.parent_id; + document.getElementById('edit_sort_order').value = category.sort_order; + document.getElementById('edit_is_active').value = category.is_active; + + // 设置图标预览 + const iconPreview = document.getElementById('editIconPreview'); + const uploadIcon = document.getElementById('editIconUploadArea').querySelector('i'); + + if (category.icon_url) { + iconPreview.src = category.icon_url; + iconPreview.style.display = 'block'; + uploadIcon.style.display = 'none'; + } else { + iconPreview.style.display = 'none'; + uploadIcon.style.display = 'block'; + } + + // 禁用当前分类及其子分类作为父分类选项 + const parentSelect = document.getElementById('edit_parent_id'); + Array.from(parentSelect.options).forEach(option => { + option.disabled = false; + if (option.value == categoryId) { + option.disabled = true; + } + }); + + new bootstrap.Modal(document.getElementById('editCategoryModal')).show(); + } else { + alert('获取分类信息失败'); + } + }) + .catch(error => { + console.error('Error:', error); + alert('获取分类信息失败: ' + error); + }); +} + +// 添加子分类 +function addSubCategory(parentId) { + const parentSelect = document.getElementById('parent_id'); + const nameInput = document.getElementById('name'); + + if (parentSelect) { + parentSelect.value = parentId; + } + + if (nameInput) { + nameInput.focus(); + } + + // 滚动到添加表单 + const addForm = document.querySelector('.add-category-form'); + if (addForm) { + addForm.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } +} + +// 删除分类 +function deleteCategory(categoryId) { + if (confirm('确定要删除这个分类吗?删除后无法恢复!')) { + fetch(`/admin/products/categories/${categoryId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + alert('分类删除成功'); + location.reload(); + } else { + alert('删除失败: ' + data.message); + } + }) + .catch(error => { + console.error('Error:', error); + alert('删除失败: ' + error); + }); + } +} + +// 切换分类展开/收起 +function toggleCategory(categoryId) { + const categoryItem = document.querySelector(`[data-id="${categoryId}"]`); + if (!categoryItem) return; + + const childrenDiv = categoryItem.querySelector('.children-categories'); + const toggleBtn = categoryItem.querySelector('.category-header .bi-chevron-down, .category-header .bi-chevron-up'); + + if (childrenDiv && toggleBtn) { + if (childrenDiv.style.display === 'none') { + childrenDiv.style.display = 'block'; + toggleBtn.className = 'bi bi-chevron-up'; + } else { + childrenDiv.style.display = 'none'; + toggleBtn.className = 'bi bi-chevron-down'; + } + } +} + +// 展开全部 +function expandAll() { + document.querySelectorAll('.children-categories').forEach(div => { + div.style.display = 'block'; + }); + document.querySelectorAll('.bi-chevron-down').forEach(icon => { + icon.className = 'bi bi-chevron-up'; + }); +} + +// 收起全部 +function collapseAll() { + document.querySelectorAll('.children-categories').forEach(div => { + div.style.display = 'none'; + }); + document.querySelectorAll('.bi-chevron-up').forEach(icon => { + icon.className = 'bi bi-chevron-down'; + }); +} + +// 初始化表单提交 +function initFormSubmission() { + const addForm = document.getElementById('addCategoryForm'); + if (addForm) { + addForm.addEventListener('submit', function(e) { + setTimeout(() => { + if (!document.querySelector('.alert-danger')) { + // 重置表单 + this.reset(); + const iconPreview = document.getElementById('iconPreview'); + const uploadIcon = document.getElementById('iconUploadArea').querySelector('i'); + + if (iconPreview) iconPreview.style.display = 'none'; + if (uploadIcon) uploadIcon.style.display = 'block'; + } + }, 100); + }); + } +} + +// 表单验证 +function validateCategoryForm(formId) { + const form = document.getElementById(formId); + if (!form) return false; + + const nameInput = form.querySelector('input[name="name"]'); + if (!nameInput || !nameInput.value.trim()) { + alert('请输入分类名称'); + if (nameInput) nameInput.focus(); + return false; + } + + return true; +} + +// 工具函数:显示加载状态 +function showLoading(element) { + if (element) { + element.disabled = true; + const originalText = element.innerHTML; + element.innerHTML = ' 处理中...'; + + setTimeout(() => { + element.disabled = false; + element.innerHTML = originalText; + }, 2000); + } +} + +// 工具函数:显示成功消息 +function showSuccess(message) { + const alertDiv = document.createElement('div'); + alertDiv.className = 'alert alert-success alert-dismissible fade show'; + alertDiv.innerHTML = ` + ${message} + + `; + + const container = document.querySelector('.container-fluid'); + if (container) { + container.insertBefore(alertDiv, container.firstChild); + + setTimeout(() => { + alertDiv.remove(); + }, 3000); + } +} + +// 工具函数:显示错误消息 +function showError(message) { + const alertDiv = document.createElement('div'); + alertDiv.className = 'alert alert-danger alert-dismissible fade show'; + alertDiv.innerHTML = ` + ${message} + + `; + + const container = document.querySelector('.container-fluid'); + if (container) { + container.insertBefore(alertDiv, container.firstChild); + + setTimeout(() => { + alertDiv.remove(); + }, 5000); + } +} + + +🔸============================================================================== +📄 文件: app/static/js/admin_dashboard.js +📊 大小: 2361 bytes (2.31 KB) +🕒 修改时间: 2025-07-04 14:51:53 +🔸============================================================================== + +// Dashboard JavaScript functionality +document.addEventListener('DOMContentLoaded', function() { + // Initialize user trend chart if canvas exists + const chartCanvas = document.getElementById('userTrendChart'); + if (chartCanvas) { + initUserTrendChart(); + } + + // Auto refresh dashboard data every 5 minutes + setInterval(function() { + refreshDashboardStats(); + }, 300000); // 5 minutes +}); + +function initUserTrendChart() { + const ctx = document.getElementById('userTrendChart').getContext('2d'); + + // Get data from template variables (these will be rendered by Jinja2) + const labels = window.userTrendLabels || []; + const data = window.userTrendData || []; + + const userTrendChart = new Chart(ctx, { + type: 'line', + data: { + labels: labels, + datasets: [{ + label: '注册用户数', + data: data, + borderColor: 'rgb(102, 126, 234)', + backgroundColor: 'rgba(102, 126, 234, 0.1)', + tension: 0.4, + fill: true + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false + } + }, + scales: { + y: { + beginAtZero: true, + ticks: { + stepSize: 1 + } + } + } + } + }); +} + +function refreshDashboardStats() { + // This function could be used to refresh dashboard statistics via AJAX + // For now, it's a placeholder for future implementation + console.log('Refreshing dashboard stats...'); +} + +// Utility function to format numbers +function formatNumber(num) { + if (num >= 1000000) { + return (num / 1000000).toFixed(1) + 'M'; + } else if (num >= 1000) { + return (num / 1000).toFixed(1) + 'K'; + } + return num.toString(); +} + +// Function to update stats cards (for future AJAX updates) +function updateStatsCard(cardSelector, value) { + const card = document.querySelector(cardSelector); + if (card) { + const valueElement = card.querySelector('h3'); + if (valueElement) { + valueElement.textContent = formatNumber(value); + } + } +} + + +🔸============================================================================== +📄 文件: app/static/js/admin_logs.js +📊 大小: 11576 bytes (11.30 KB) +🕒 修改时间: 2025-07-09 01:54:54 +🔸============================================================================== + +// 操作日志页面JavaScript +document.addEventListener('DOMContentLoaded', function() { + // 初始化 + initializeLogManagement(); +}); + +// 初始化日志管理功能 +function initializeLogManagement() { + // 添加事件监听器 + setupEventListeners(); + + // 初始化工具提示 + initializeTooltips(); + + // 初始化表格 + initializeTable(); +} + +// 设置事件监听器 +function setupEventListeners() { + // 搜索表单提交 + const searchForm = document.querySelector('form[method="GET"]'); + if (searchForm) { + searchForm.addEventListener('submit', function(e) { + // 可以在这里添加搜索前的验证 + }); + } + + // 用户类型筛选变更 + const userTypeSelect = document.getElementById('user_type'); + if (userTypeSelect) { + userTypeSelect.addEventListener('change', function() { + // 自动提交表单 + this.form.submit(); + }); + } + + // 操作类型输入框 + const actionInput = document.getElementById('action'); + if (actionInput) { + // 添加防抖搜索 + let searchTimer; + actionInput.addEventListener('input', function() { + clearTimeout(searchTimer); + searchTimer = setTimeout(() => { + // 可以实现实时搜索 + }, 500); + }); + } +} + +// 初始化工具提示 +function initializeTooltips() { + // 为用户代理字段添加工具提示 + const userAgentElements = document.querySelectorAll('.user-agent'); + userAgentElements.forEach(element => { + if (element.title) { + // 使用Bootstrap的tooltip + new bootstrap.Tooltip(element); + } + }); +} + +// 初始化表格 +function initializeTable() { + // 添加表格行点击事件 + const tableRows = document.querySelectorAll('.table tbody tr'); + tableRows.forEach(row => { + row.addEventListener('click', function(e) { + // 如果点击的是按钮,不触发行点击事件 + if (e.target.tagName === 'BUTTON' || e.target.closest('button')) { + return; + } + + // 高亮选中行 + tableRows.forEach(r => r.classList.remove('table-active')); + this.classList.add('table-active'); + }); + }); +} + +// 查看日志详情 +function viewLogDetail(logId) { + // 发送AJAX请求获取日志详情 + fetch(`/admin/logs/${logId}/detail`) + .then(response => response.json()) + .then(data => { + if (data.success) { + showLogDetailModal(data.log); + } else { + showError('获取日志详情失败: ' + data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showError('网络错误,请重试'); + }); +} + +// 显示日志详情模态框 +function showLogDetailModal(log) { + const modalHtml = ` + + `; + + // 移除现有的模态框 + const existingModal = document.getElementById('logDetailModal'); + if (existingModal) { + existingModal.remove(); + } + + // 添加新的模态框 + document.body.insertAdjacentHTML('beforeend', modalHtml); + + // 显示模态框 + const modal = new bootstrap.Modal(document.getElementById('logDetailModal')); + modal.show(); +} + +// 导出日志 +function exportLogs() { + // 获取当前筛选条件 + const userType = document.getElementById('user_type').value; + const action = document.getElementById('action').value; + + // 构建导出URL + const params = new URLSearchParams(); + if (userType) params.append('user_type', userType); + if (action) params.append('action', action); + + const exportUrl = `/admin/logs/export?${params.toString()}`; + + // 下载文件 + window.location.href = exportUrl; +} + +// 清理日志 +function clearLogs() { + if (!confirm('确定要清理历史日志吗?此操作不可逆!')) { + return; + } + + const daysToKeep = prompt('请输入要保留的天数(例如:30):', '30'); + if (!daysToKeep || isNaN(daysToKeep) || daysToKeep <= 0) { + showError('请输入有效的天数'); + return; + } + + // 显示加载状态 + showLoading(); + + // 发送AJAX请求 + fetch('/admin/logs/clear', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + days_to_keep: parseInt(daysToKeep) + }) + }) + .then(response => response.json()) + .then(data => { + hideLoading(); + if (data.success) { + showSuccess(data.message); + // 刷新页面 + setTimeout(() => { + location.reload(); + }, 1000); + } else { + showError(data.message); + } + }) + .catch(error => { + hideLoading(); + console.error('Error:', error); + showError('网络错误,请重试'); + }); +} + +// 搜索日志 +function searchLogs() { + const searchForm = document.querySelector('form[method="GET"]'); + if (searchForm) { + searchForm.submit(); + } +} + +// 重置搜索 +function resetSearch() { + window.location.href = '/admin/logs'; +} + +// 格式化日期时间 +function formatDateTime(dateTimeString) { + if (!dateTimeString) return '未知'; + + const date = new Date(dateTimeString); + return date.toLocaleString('zh-CN'); +} + +// 显示成功消息 +function showSuccess(message) { + showAlert(message, 'success'); +} + +// 显示错误消息 +function showError(message) { + showAlert(message, 'danger'); +} + +// 显示提示消息 +function showAlert(message, type) { + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${type} alert-dismissible fade show`; + alertDiv.innerHTML = ` + ${message} + + `; + + // 插入到页面顶部 + const container = document.querySelector('.admin-content'); + container.insertBefore(alertDiv, container.firstChild); + + // 3秒后自动关闭 + setTimeout(() => { + alertDiv.remove(); + }, 3000); +} + +// 显示加载状态 +function showLoading() { + const loadingDiv = document.createElement('div'); + loadingDiv.id = 'loading-overlay'; + loadingDiv.innerHTML = ` +
+ +
处理中...
+
+ `; + loadingDiv.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + color: white; + `; + + document.body.appendChild(loadingDiv); +} + +// 隐藏加载状态 +function hideLoading() { + const loadingDiv = document.getElementById('loading-overlay'); + if (loadingDiv) { + loadingDiv.remove(); + } +} + +// 表格排序功能 +function sortTable(column) { + // 实现表格排序功能 + console.log('Sort by:', column); +} + +// 批量操作功能(可选) +function bulkOperation() { + // 实现批量操作功能 + const selectedLogs = document.querySelectorAll('input[type="checkbox"]:checked'); + if (selectedLogs.length === 0) { + showError('请选择要操作的日志'); + return; + } + + // 实现批量操作逻辑 +} + + +🔸============================================================================== +📄 文件: app/static/js/admin_orders.js +📊 大小: 6855 bytes (6.69 KB) +🕒 修改时间: 2025-07-09 05:11:36 +🔸============================================================================== + +// 订单管理JavaScript功能 +document.addEventListener('DOMContentLoaded', function() { + // 初始化所有模态框 + const shipModal = new bootstrap.Modal(document.getElementById('shipModal')); + const refundModal = new bootstrap.Modal(document.getElementById('refundModal')); + const cancelModal = new bootstrap.Modal(document.getElementById('cancelModal')); + + // 当前操作的订单ID + let currentOrderId = null; + + // 显示发货模态框 + window.showShipModal = function(orderId, orderSn) { + currentOrderId = orderId; + document.getElementById('shipOrderSn').value = orderSn; + shipModal.show(); + }; + + // 显示退款模态框 + window.showRefundModal = function(orderId, orderSn) { + currentOrderId = orderId; + document.getElementById('refundOrderSn').value = orderSn; + refundModal.show(); + }; + + // 显示取消模态框 + window.showCancelModal = function(orderId, orderSn) { + currentOrderId = orderId; + document.getElementById('cancelOrderSn').value = orderSn; + cancelModal.show(); + }; + + // 处理发货表单提交 + document.getElementById('shipForm').addEventListener('submit', function(e) { + e.preventDefault(); + + if (!currentOrderId) { + showAlert('错误', '订单ID不能为空', 'danger'); + return; + } + + const formData = new FormData(this); + const submitBtn = this.querySelector('button[type="submit"]'); + + // 显示加载状态 + const originalText = submitBtn.innerHTML; + submitBtn.innerHTML = ' 处理中...'; + submitBtn.disabled = true; + + fetch(`/admin/orders/${currentOrderId}/ship`, { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert('成功', '发货成功!', 'success'); + shipModal.hide(); + setTimeout(() => { + location.reload(); + }, 1000); + } else { + showAlert('错误', data.message || '发货失败', 'danger'); + } + }) + .catch(error => { + console.error('Error:', error); + showAlert('错误', '网络请求失败', 'danger'); + }) + .finally(() => { + // 恢复按钮状态 + submitBtn.innerHTML = originalText; + submitBtn.disabled = false; + }); + }); + + // 处理退款表单提交 + document.getElementById('refundForm').addEventListener('submit', function(e) { + e.preventDefault(); + + if (!currentOrderId) { + showAlert('错误', '订单ID不能为空', 'danger'); + return; + } + + const formData = new FormData(this); + const submitBtn = this.querySelector('button[type="submit"]'); + + // 显示加载状态 + const originalText = submitBtn.innerHTML; + submitBtn.innerHTML = ' 处理中...'; + submitBtn.disabled = true; + + fetch(`/admin/orders/${currentOrderId}/refund`, { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert('成功', '退款成功!', 'success'); + refundModal.hide(); + setTimeout(() => { + location.reload(); + }, 1000); + } else { + showAlert('错误', data.message || '退款失败', 'danger'); + } + }) + .catch(error => { + console.error('Error:', error); + showAlert('错误', '网络请求失败', 'danger'); + }) + .finally(() => { + // 恢复按钮状态 + submitBtn.innerHTML = originalText; + submitBtn.disabled = false; + }); + }); + + // 处理取消表单提交 + document.getElementById('cancelForm').addEventListener('submit', function(e) { + e.preventDefault(); + + if (!currentOrderId) { + showAlert('错误', '订单ID不能为空', 'danger'); + return; + } + + const formData = new FormData(this); + const submitBtn = this.querySelector('button[type="submit"]'); + + // 显示加载状态 + const originalText = submitBtn.innerHTML; + submitBtn.innerHTML = ' 处理中...'; + submitBtn.disabled = true; + + fetch(`/admin/orders/${currentOrderId}/cancel`, { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert('成功', '订单取消成功!', 'success'); + cancelModal.hide(); + setTimeout(() => { + location.reload(); + }, 1000); + } else { + showAlert('错误', data.message || '取消失败', 'danger'); + } + }) + .catch(error => { + console.error('Error:', error); + showAlert('错误', '网络请求失败', 'danger'); + }) + .finally(() => { + // 恢复按钮状态 + submitBtn.innerHTML = originalText; + submitBtn.disabled = false; + }); + }); + + // 通用提示函数 + function showAlert(title, message, type) { + // 创建提示框 + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`; + alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; + alertDiv.innerHTML = ` + ${title} ${message} + + `; + + document.body.appendChild(alertDiv); + + // 3秒后自动关闭 + setTimeout(() => { + if (alertDiv.parentNode) { + alertDiv.parentNode.removeChild(alertDiv); + } + }, 3000); + } +}); + +// 旋转动画CSS(如果需要) +if (!document.querySelector('#admin-orders-style')) { + const style = document.createElement('style'); + style.id = 'admin-orders-style'; + style.textContent = ` + .spin { + animation: spin 1s linear infinite; + } + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + `; + document.head.appendChild(style); +} + + +🔸============================================================================== +📄 文件: app/static/js/admin_users.js +📊 大小: 12828 bytes (12.53 KB) +🕒 修改时间: 2025-07-09 02:08:32 +🔸============================================================================== + +// 用户管理页面JavaScript +document.addEventListener('DOMContentLoaded', function() { + // 初始化 + initializeUserManagement(); +}); + +// 初始化用户管理功能 +function initializeUserManagement() { + // 添加事件监听器 + setupEventListeners(); + + // 初始化头像显示 + initializeAvatars(); + + // 强制设置头像样式 + forceAvatarStyles(); +} + +// 设置事件监听器 +function setupEventListeners() { + // 搜索表单提交 + const searchForm = document.querySelector('form[method="GET"]'); + if (searchForm) { + searchForm.addEventListener('submit', function(e) { + // 可以在这里添加搜索前的验证 + }); + } + + // 状态筛选变更 + const statusSelect = document.getElementById('status'); + if (statusSelect) { + statusSelect.addEventListener('change', function() { + // 自动提交表单 + this.form.submit(); + }); + } +} + +// 初始化头像显示 +function initializeAvatars() { + const avatars = document.querySelectorAll('.user-avatar'); + avatars.forEach(avatar => { + avatar.onerror = function() { + // 如果头像加载失败,替换为默认头像 + this.style.display = 'none'; + const placeholder = this.parentElement.querySelector('.user-avatar-placeholder'); + if (placeholder) { + placeholder.style.display = 'flex'; + } + }; + }); +} + +// 强制设置头像样式,覆盖Bootstrap +function forceAvatarStyles() { + // 设置表格中的头像样式 + const tableAvatars = document.querySelectorAll('.table .user-avatar'); + tableAvatars.forEach(avatar => { + setAvatarStyles(avatar, '48px'); + }); +} + +// 设置单个头像样式 +function setAvatarStyles(avatar, size) { + if (!avatar) return; + + // 强制设置所有相关样式 + avatar.style.setProperty('width', size, 'important'); + avatar.style.setProperty('height', size, 'important'); + avatar.style.setProperty('max-width', size, 'important'); + avatar.style.setProperty('max-height', size, 'important'); + avatar.style.setProperty('min-width', size, 'important'); + avatar.style.setProperty('min-height', size, 'important'); + avatar.style.setProperty('border-radius', '50%', 'important'); + avatar.style.setProperty('object-fit', 'cover', 'important'); + avatar.style.setProperty('border', '2px solid #f8f9fa', 'important'); + avatar.style.setProperty('display', 'block', 'important'); + avatar.style.setProperty('flex-shrink', '0', 'important'); + avatar.style.setProperty('flex-grow', '0', 'important'); + + // 移除可能影响的Bootstrap类 + avatar.classList.remove('img-fluid', 'img-responsive', 'img-thumbnail'); + + // 设置父元素样式 + if (avatar.parentElement) { + avatar.parentElement.style.setProperty('width', size, 'important'); + avatar.parentElement.style.setProperty('height', size, 'important'); + avatar.parentElement.style.setProperty('overflow', 'hidden', 'important'); + avatar.parentElement.style.setProperty('flex-shrink', '0', 'important'); + avatar.parentElement.style.setProperty('flex-grow', '0', 'important'); + } +} + +// 查看用户详情 +function viewUser(userId) { + // 显示模态框 + const modal = new bootstrap.Modal(document.getElementById('userDetailModal')); + const content = document.getElementById('userDetailContent'); + + // 显示加载状态 + content.innerHTML = ` +
+ +
加载中...
+
+ `; + + modal.show(); + + // 发送AJAX请求获取用户详情 + fetch(`/admin/users/${userId}/detail`) + .then(response => response.json()) + .then(data => { + if (data.success) { + renderUserDetail(data.user); + // 立即强制设置头像样式 + setTimeout(() => { + forceModalAvatarStyles(); + }, 50); + // 再次确保样式正确应用 + setTimeout(() => { + forceModalAvatarStyles(); + }, 200); + } else { + showError('获取用户详情失败: ' + data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showError('网络错误,请重试'); + }); +} + +// 渲染用户详情 +function renderUserDetail(user) { + const content = document.getElementById('userDetailContent'); + + content.innerHTML = ` +
+
+
+
+ ${user.avatar_url ? + `用户头像` : + `
` + } +
+
${user.nickname || user.username}
+ + ${user.status === 1 ? '正常' : '禁用'} + +
+
+ +
+
+
+ `; +} + +// 强制设置模态框中的头像样式 +function forceModalAvatarStyles() { + const modalAvatar = document.getElementById('modalAvatar'); + if (modalAvatar) { + setAvatarStyles(modalAvatar, '80px'); + + // 设置容器样式 + const wrapper = document.querySelector('.user-avatar-large-wrapper'); + if (wrapper) { + wrapper.style.setProperty('width', '80px', 'important'); + wrapper.style.setProperty('height', '80px', 'important'); + wrapper.style.setProperty('margin', '0 auto', 'important'); + wrapper.style.setProperty('overflow', 'hidden', 'important'); + wrapper.style.setProperty('border-radius', '50%', 'important'); + wrapper.style.setProperty('position', 'relative', 'important'); + wrapper.style.setProperty('flex-shrink', '0', 'important'); + wrapper.style.setProperty('flex-grow', '0', 'important'); + } + } + + // 通用的模态框头像处理 + const modalAvatars = document.querySelectorAll('.modal .avatar-large'); + modalAvatars.forEach(avatar => { + setAvatarStyles(avatar, '80px'); + }); +} + +// 切换用户状态 +function toggleUserStatus(userId, currentStatus) { + const action = currentStatus === 1 ? '禁用' : '启用'; + const newStatus = currentStatus === 1 ? 0 : 1; + + if (!confirm(`确定要${action}此用户吗?`)) { + return; + } + + // 显示加载状态 + showLoading(); + + // 发送AJAX请求 + fetch(`/admin/users/${userId}/toggle-status`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + status: newStatus + }) + }) + .then(response => response.json()) + .then(data => { + hideLoading(); + if (data.success) { + showSuccess(data.message); + // 刷新页面 + setTimeout(() => { + location.reload(); + }, 1000); + } else { + showError(data.message); + } + }) + .catch(error => { + hideLoading(); + console.error('Error:', error); + showError('网络错误,请重试'); + }); +} + +// 获取性别文本 +function getGenderText(gender) { + switch (gender) { + case 1: return '男'; + case 2: return '女'; + default: return '未知'; + } +} + +// 格式化日期时间 +function formatDateTime(dateTimeString) { + if (!dateTimeString) return '未知'; + + const date = new Date(dateTimeString); + return date.toLocaleString('zh-CN'); +} + +// 显示成功消息 +function showSuccess(message) { + showAlert(message, 'success'); +} + +// 显示错误消息 +function showError(message) { + showAlert(message, 'danger'); +} + +// 显示提示消息 +function showAlert(message, type) { + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${type} alert-dismissible fade show`; + alertDiv.innerHTML = ` + ${message} + + `; + + // 插入到页面顶部 + const container = document.querySelector('.admin-content'); + container.insertBefore(alertDiv, container.firstChild); + + // 3秒后自动关闭 + setTimeout(() => { + alertDiv.remove(); + }, 3000); +} + +// 显示加载状态 +function showLoading() { + const loadingDiv = document.createElement('div'); + loadingDiv.id = 'loading-overlay'; + loadingDiv.innerHTML = ` +
+ +
处理中...
+
+ `; + loadingDiv.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + color: white; + `; + + document.body.appendChild(loadingDiv); +} + +// 隐藏加载状态 +function hideLoading() { + const loadingDiv = document.getElementById('loading-overlay'); + if (loadingDiv) { + loadingDiv.remove(); + } +} + +// 页面加载完成后强制设置头像样式 +window.addEventListener('load', function() { + forceAvatarStyles(); +}); + +// 定时检查并修复头像样式 +setInterval(function() { + // 检查并修复表格头像 + const tableAvatars = document.querySelectorAll('.table .user-avatar'); + tableAvatars.forEach(avatar => { + if (avatar.offsetWidth > 60 || avatar.offsetHeight > 60) { + setAvatarStyles(avatar, '48px'); + } + }); + + // 检查并修复模态框头像 + const modalAvatars = document.querySelectorAll('.modal .avatar-large'); + modalAvatars.forEach(avatar => { + if (avatar.offsetWidth > 100 || avatar.offsetHeight > 100) { + setAvatarStyles(avatar, '80px'); + } + }); +}, 500); + + +🔸============================================================================== +📄 文件: app/static/js/base.js +📊 大小: 5415 bytes (5.29 KB) +🕒 修改时间: 2025-07-08 17:18:51 +🔸============================================================================== + +// 基础JavaScript功能 + +// 返回顶部功能 +window.addEventListener('scroll', function() { + const backToTop = document.getElementById('backToTop'); + if (backToTop) { + if (window.pageYOffset > 300) { + backToTop.style.display = 'block'; + } else { + backToTop.style.display = 'none'; + } + } +}); + +function scrollToTop() { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); +} + +// 购物车数量更新 +function updateCartBadge(count) { + const badge = document.getElementById('cartBadge'); + if (badge) { + if (count > 0) { + badge.textContent = count; + badge.style.display = 'inline-block'; + } else { + badge.style.display = 'none'; + } + } +} + +// 通用提示函数 +function showAlert(message, type = 'info', duration = 3000) { + // 移除现有的提示框 + const existingAlerts = document.querySelectorAll('.custom-alert'); + existingAlerts.forEach(alert => alert.remove()); + + // 创建新的提示框 + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${getBootstrapAlertType(type)} alert-dismissible fade show custom-alert`; + alertDiv.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + z-index: 9999; + min-width: 300px; + max-width: 500px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + `; + + const icon = getAlertIcon(type); + alertDiv.innerHTML = ` +
+ +
${message}
+ +
+ `; + + document.body.appendChild(alertDiv); + + // 自动消失 + if (duration > 0) { + setTimeout(() => { + if (alertDiv.parentNode) { + alertDiv.classList.remove('show'); + setTimeout(() => { + if (alertDiv.parentNode) { + alertDiv.remove(); + } + }, 150); + } + }, duration); + } + + return alertDiv; +} + +// 获取Bootstrap警告类型 +function getBootstrapAlertType(type) { + const typeMap = { + 'success': 'success', + 'error': 'danger', + 'warning': 'warning', + 'info': 'info' + }; + return typeMap[type] || 'info'; +} + +// 获取警告图标 +function getAlertIcon(type) { + const iconMap = { + 'success': 'bi-check-circle-fill', + 'error': 'bi-exclamation-triangle-fill', + 'warning': 'bi-exclamation-triangle-fill', + 'info': 'bi-info-circle-fill' + }; + return iconMap[type] || 'bi-info-circle-fill'; +} + +// 确认对话框 +function showConfirm(message, callback) { + if (confirm(message)) { + if (typeof callback === 'function') { + callback(); + } + return true; + } + return false; +} + +// 页面加载完成后的初始化 +document.addEventListener('DOMContentLoaded', function() { + // 当前页面高亮 + const currentPath = window.location.pathname; + const navLinks = document.querySelectorAll('.navbar-nav .nav-link'); + + navLinks.forEach(link => { + if (link.getAttribute('href') === currentPath) { + link.classList.add('active'); + } + }); + + // 初始化购物车数量 + updateCartCount(); +}); + +// 通用AJAX错误处理 +function handleAjaxError(xhr, defaultMessage = '操作失败,请稍后再试') { + if (xhr.status === 401) { + showAlert('请先登录', 'warning'); + setTimeout(() => { + window.location.href = '/auth/login'; + }, 1500); + } else if (xhr.status === 403) { + showAlert('没有权限执行此操作', 'error'); + } else if (xhr.status === 404) { + showAlert('请求的资源不存在', 'error'); + } else if (xhr.status >= 500) { + showAlert('服务器错误,请稍后再试', 'error'); + } else { + showAlert(defaultMessage, 'error'); + } +} + +// 通用成功提示(保持向后兼容) +function showSuccessMessage(message) { + showAlert(message, 'success'); +} + +// 更新购物车数量 +function updateCartCount() { + fetch('/cart/count') + .then(response => response.json()) + .then(data => { + if (data.success) { + updateCartBadge(data.count); + } + }) + .catch(error => { + console.log('获取购物车数量失败:', error); + }); +} + +// 格式化价格 +function formatPrice(price) { + return '¥' + parseFloat(price).toFixed(2); +} + +// 格式化数字 +function formatNumber(num) { + return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); +} + +// 防抖函数 +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +// 节流函数 +function throttle(func, limit) { + let inThrottle; + return function() { + const args = arguments; + const context = this; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + } +} + + +🔸============================================================================== +📄 文件: app/static/js/cart.js +📊 大小: 6282 bytes (6.13 KB) +🕒 修改时间: 2025-07-04 14:40:34 +🔸============================================================================== + +let selectedItems = new Set(); + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', function() { + updateSelectAllState(); + updateTotalPrice(); +}); + +// 全选/取消全选 +document.getElementById('selectAll').addEventListener('change', function() { + const checkboxes = document.querySelectorAll('.item-checkbox:not(:disabled)'); + checkboxes.forEach(checkbox => { + checkbox.checked = this.checked; + if (this.checked) { + selectedItems.add(parseInt(checkbox.value)); + } else { + selectedItems.delete(parseInt(checkbox.value)); + } + }); + updateTotalPrice(); +}); + +// 单个商品选择 +document.querySelectorAll('.item-checkbox').forEach(checkbox => { + checkbox.addEventListener('change', function() { + const cartId = parseInt(this.value); + if (this.checked) { + selectedItems.add(cartId); + } else { + selectedItems.delete(cartId); + } + updateSelectAllState(); + updateTotalPrice(); + }); +}); + +// 更新全选状态 +function updateSelectAllState() { + const selectAllCheckbox = document.getElementById('selectAll'); + const availableCheckboxes = document.querySelectorAll('.item-checkbox:not(:disabled)'); + const checkedCheckboxes = document.querySelectorAll('.item-checkbox:not(:disabled):checked'); + + if (availableCheckboxes.length === 0) { + selectAllCheckbox.disabled = true; + selectAllCheckbox.checked = false; + } else { + selectAllCheckbox.disabled = false; + selectAllCheckbox.checked = availableCheckboxes.length === checkedCheckboxes.length; + } +} + +// 更新总价 +function updateTotalPrice() { + let totalPrice = 0; + let selectedCount = 0; + + selectedItems.forEach(cartId => { + const cartItem = document.querySelector(`[data-cart-id="${cartId}"]`); + if (cartItem) { + const itemTotal = parseFloat(cartItem.querySelector('.item-total').textContent); + const quantity = parseInt(cartItem.querySelector('.quantity-input').value); + totalPrice += itemTotal; + selectedCount += quantity; + } + }); + + document.getElementById('selectedCount').textContent = selectedCount; + document.getElementById('selectedTotal').textContent = totalPrice.toFixed(2); + document.getElementById('finalTotal').textContent = totalPrice.toFixed(2); + + // 更新结算按钮状态 + const checkoutBtn = document.getElementById('checkoutBtn'); + checkoutBtn.disabled = selectedItems.size === 0; +} + +// 修改数量 +function changeQuantity(cartId, delta) { + const input = document.querySelector(`[data-cart-id="${cartId}"]`); + const currentValue = parseInt(input.value); + const newValue = currentValue + delta; + + if (newValue >= 1 && newValue <= parseInt(input.max)) { + updateQuantity(cartId, newValue); + } +} + +// 更新数量 +function updateQuantity(cartId, quantity) { + if (quantity < 1) return; + + fetch('/cart/update', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + cart_id: cartId, + quantity: parseInt(quantity) + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + // 更新页面显示 + const cartItem = document.querySelector(`[data-cart-id="${cartId}"]`); + cartItem.querySelector('.quantity-input').value = quantity; + cartItem.querySelector('.item-total').textContent = data.item_total.toFixed(2); + + // 更新总价 + updateTotalPrice(); + + // 更新全局购物车数量 + updateCartBadge(data.cart_count); + + showSuccessMessage('数量更新成功'); + } else { + alert(data.message); + // 恢复原始值 + location.reload(); + } + }) + .catch(error => { + console.error('Error:', error); + alert('更新失败'); + location.reload(); + }); +} + +// 删除商品 +function removeItem(cartId) { + if (!confirm('确定要删除这件商品吗?')) { + return; + } + + fetch('/cart/remove', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + cart_id: cartId + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + // 从页面中移除商品 + const cartItem = document.querySelector(`[data-cart-id="${cartId}"]`); + cartItem.remove(); + + // 从选中列表中移除 + selectedItems.delete(cartId); + + // 更新显示 + updateSelectAllState(); + updateTotalPrice(); + updateCartBadge(data.cart_count); + + showSuccessMessage('商品已删除'); + + // 如果购物车为空,刷新页面 + if (data.cart_count === 0) { + setTimeout(() => { + location.reload(); + }, 1000); + } + } else { + alert(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + alert('删除失败'); + }); +} + +// 清空购物车 +function clearCart() { + if (!confirm('确定要清空购物车吗?')) { + return; + } + + fetch('/cart/clear', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage('购物车已清空'); + setTimeout(() => { + location.reload(); + }, 1000); + } else { + alert(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + alert('清空失败'); + }); +} + +// 去结算 +function checkout() { + if (selectedItems.size === 0) { + alert('请选择要购买的商品'); + return; + } + + const params = new URLSearchParams(); + selectedItems.forEach(cartId => { + params.append('items', cartId); + }); + + window.location.href = `/cart/checkout?${params.toString()}`; +} + + +🔸============================================================================== +📄 文件: app/static/js/checkout.js +📊 大小: 4771 bytes (4.66 KB) +🕒 修改时间: 2025-07-08 17:18:51 +🔸============================================================================== + +// 订单结算页面脚本 +let selectedAddressId = 0; +let subtotal = 0; + +// 初始化页面 +document.addEventListener('DOMContentLoaded', function() { + // 从页面获取初始数据 + const defaultAddress = document.querySelector('input[name="address_id"]:checked'); + if (defaultAddress) { + selectedAddressId = parseInt(defaultAddress.value); + } + + // 获取商品总价 + const subtotalElement = document.getElementById('subtotal'); + if (subtotalElement) { + subtotal = parseFloat(subtotalElement.textContent.replace('¥', '')); + } +}); + +// 选择地址 +function selectAddress(addressId) { + selectedAddressId = addressId; + + // 更新UI + document.querySelectorAll('.address-card').forEach(card => { + card.classList.remove('selected'); + }); + + const selectedCard = document.querySelector(`[data-address-id="${addressId}"]`); + if (selectedCard) { + selectedCard.classList.add('selected'); + } + + // 更新单选按钮 + const radioButton = document.querySelector(`input[value="${addressId}"]`); + if (radioButton) { + radioButton.checked = true; + } +} + +// 更新运费 +function updateShippingFee() { + const shippingMethodElement = document.querySelector('input[name="shipping_method"]:checked'); + if (!shippingMethodElement) return; + + const shippingMethod = shippingMethodElement.value; + let fee = 0; + + switch(shippingMethod) { + case 'express': + fee = 10; + break; + case 'same_day': + fee = 20; + break; + default: + fee = 0; + } + + const shippingFeeElement = document.getElementById('shippingFee'); + const totalAmountElement = document.getElementById('totalAmount'); + + if (shippingFeeElement) { + shippingFeeElement.textContent = `¥${fee.toFixed(2)}`; + } + + if (totalAmountElement) { + totalAmountElement.textContent = `¥${(subtotal + fee).toFixed(2)}`; + } +} + +// 提交订单 +function submitOrder() { + // 验证地址选择 + if (!selectedAddressId) { + showAlert('请选择收货地址', 'warning'); + return; + } + + // 获取表单数据 + const shippingMethodElement = document.querySelector('input[name="shipping_method"]:checked'); + const paymentMethodElement = document.querySelector('input[name="payment_method"]:checked'); + const remarkElement = document.getElementById('orderRemark'); + + if (!shippingMethodElement) { + showAlert('请选择配送方式', 'warning'); + return; + } + + if (!paymentMethodElement) { + showAlert('请选择支付方式', 'warning'); + return; + } + + const shippingMethod = shippingMethodElement.value; + const paymentMethod = paymentMethodElement.value; + const remark = remarkElement ? remarkElement.value : ''; + + // 获取选中的购物车商品ID + const urlParams = new URLSearchParams(window.location.search); + const selectedItems = urlParams.getAll('items'); + + if (selectedItems.length === 0) { + showAlert('没有选中的商品', 'error'); + return; + } + + const orderData = { + selected_items: selectedItems, + address_id: selectedAddressId, + shipping_method: shippingMethod, + payment_method: paymentMethod, + remark: remark + }; + + // 显示加载状态 + const submitBtn = document.querySelector('.btn-danger'); + if (!submitBtn) { + showAlert('提交按钮未找到', 'error'); + return; + } + + const originalText = submitBtn.innerHTML; + submitBtn.innerHTML = ' 提交中...'; + submitBtn.disabled = true; + + // 提交订单 + fetch('/order/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(orderData) + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (data.success) { + showAlert('订单创建成功!正在跳转到支付页面...', 'success'); + setTimeout(() => { + window.location.href = `/order/pay/${data.payment_sn}`; + }, 1500); + } else { + showAlert(data.message || '订单创建失败', 'error'); + // 恢复按钮状态 + submitBtn.innerHTML = originalText; + submitBtn.disabled = false; + } + }) + .catch(error => { + console.error('提交订单错误:', error); + showAlert('提交订单失败,请重试', 'error'); + // 恢复按钮状态 + submitBtn.innerHTML = originalText; + submitBtn.disabled = false; + }); +} + + 🔸============================================================================== 📄 文件: app/static/js/city_data.js 📊 大小: 55620 bytes (54.32 KB) @@ -1937,10 +8727,2639 @@ const cityData = { }; +🔸============================================================================== +📄 文件: app/static/js/favorites.js +📊 大小: 6804 bytes (6.64 KB) +🕒 修改时间: 2025-07-09 02:20:37 +🔸============================================================================== + +// 收藏页面JavaScript +let selectedItems = []; +let isSelectAll = false; + +document.addEventListener('DOMContentLoaded', function() { + // 初始化事件监听 + initEventListeners(); +}); + +function initEventListeners() { + // 复选框变化事件 + document.querySelectorAll('.favorite-checkbox').forEach(checkbox => { + checkbox.addEventListener('change', function() { + updateSelectedItems(); + }); + }); +} + +function updateSelectedItems() { + selectedItems = []; + document.querySelectorAll('.favorite-checkbox:checked').forEach(checkbox => { + selectedItems.push(parseInt(checkbox.value)); + }); + + // 更新按钮状态 + const batchBtn = document.querySelector('[onclick="batchRemove()"]'); + if (batchBtn) { + batchBtn.disabled = selectedItems.length === 0; + } +} + +function toggleSelectAll() { + isSelectAll = !isSelectAll; + const checkboxes = document.querySelectorAll('.favorite-checkbox'); + + checkboxes.forEach(checkbox => { + checkbox.checked = isSelectAll; + }); + + updateSelectedItems(); + + // 更新按钮文本 + const selectAllBtn = document.querySelector('[onclick="toggleSelectAll()"]'); + if (selectAllBtn) { + selectAllBtn.innerHTML = isSelectAll ? + ' 取消全选' : + ' 全选'; + } +} + +function removeFavorite(productId) { + if (confirm('确定要取消收藏这个商品吗?')) { + fetch('/favorite/remove', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: productId + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + // 移除商品卡片 + const itemElement = document.querySelector(`[data-product-id="${productId}"]`); + if (itemElement) { + itemElement.remove(); + } + // 更新收藏数量 + updateFavoriteCount(); + // 检查是否为空 + checkEmptyState(); + } else { + showErrorMessage(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showErrorMessage('操作失败,请稍后再试'); + }); + } +} + +function batchRemove() { + if (selectedItems.length === 0) { + showErrorMessage('请选择要删除的商品'); + return; + } + + const modal = new bootstrap.Modal(document.getElementById('confirmModal')); + document.getElementById('confirmMessage').textContent = + `确定要取消收藏这 ${selectedItems.length} 个商品吗?`; + + document.getElementById('confirmBtn').onclick = function() { + performBatchRemove(); + modal.hide(); + }; + + modal.show(); +} + +function performBatchRemove() { + fetch('/favorite/batch-remove', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_ids: selectedItems + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + // 移除选中的商品卡片 + selectedItems.forEach(productId => { + const itemElement = document.querySelector(`[data-product-id="${productId}"]`); + if (itemElement) { + itemElement.remove(); + } + }); + // 重置选择状态 + selectedItems = []; + isSelectAll = false; + updateSelectedItems(); + // 更新收藏数量 + updateFavoriteCount(); + // 检查是否为空 + checkEmptyState(); + } else { + showErrorMessage(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showErrorMessage('批量删除失败,请稍后再试'); + }); +} + +function addToCart(productId) { + // 调用购物车添加功能 + fetch('/cart/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: productId, + quantity: 1 + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + // 更新购物车数量 + if (typeof updateCartBadge === 'function') { + updateCartBadge(data.cart_count); + } + } else { + showErrorMessage(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showErrorMessage('加入购物车失败,请稍后再试'); + }); +} + +function updateFavoriteCount() { + fetch('/favorite/count') + .then(response => response.json()) + .then(data => { + if (data.success) { + // 更新收藏数量显示 + const badge = document.querySelector('.badge.bg-secondary'); + if (badge) { + badge.textContent = `共 ${data.favorite_count} 件商品`; + } + } + }) + .catch(error => { + console.error('Error:', error); + }); +} + +function checkEmptyState() { + const itemsContainer = document.querySelector('.row'); + const items = itemsContainer.querySelectorAll('.favorite-item'); + + if (items.length === 0) { + // 显示空状态 + itemsContainer.innerHTML = ` +
+
+
+ +

还没有收藏任何商品

+

去逛逛,收藏心仪的商品吧~

+ + 去首页逛逛 + +
+
+
+ `; + } +} + +function showSuccessMessage(message) { + // 使用现有的消息提示函数 + if (typeof showMessage === 'function') { + showMessage(message, 'success'); + } else { + alert(message); + } +} + +function showErrorMessage(message) { + // 使用现有的消息提示函数 + if (typeof showMessage === 'function') { + showMessage(message, 'error'); + } else { + alert(message); + } +} + + +🔸============================================================================== +📄 文件: app/static/js/history.js +📊 大小: 8170 bytes (7.98 KB) +🕒 修改时间: 2025-07-09 02:20:46 +🔸============================================================================== + +// 浏览历史页面JavaScript +let selectedItems = []; +let isSelectAll = false; + +document.addEventListener('DOMContentLoaded', function() { + // 初始化事件监听 + initEventListeners(); +}); + +function initEventListeners() { + // 复选框变化事件 + document.querySelectorAll('.history-checkbox').forEach(checkbox => { + checkbox.addEventListener('change', function() { + updateSelectedItems(); + }); + }); +} + +function updateSelectedItems() { + selectedItems = []; + document.querySelectorAll('.history-checkbox:checked').forEach(checkbox => { + selectedItems.push(parseInt(checkbox.value)); + }); + + // 更新按钮状态 + const batchBtn = document.querySelector('[onclick="batchRemove()"]'); + if (batchBtn) { + batchBtn.disabled = selectedItems.length === 0; + } +} + +function toggleSelectAll() { + isSelectAll = !isSelectAll; + const checkboxes = document.querySelectorAll('.history-checkbox'); + + checkboxes.forEach(checkbox => { + checkbox.checked = isSelectAll; + }); + + updateSelectedItems(); + + // 更新按钮文本 + const selectAllBtn = document.querySelector('[onclick="toggleSelectAll()"]'); + if (selectAllBtn) { + selectAllBtn.innerHTML = isSelectAll ? + ' 取消全选' : + ' 全选'; + } +} + +function removeHistory(productId) { + if (confirm('确定要删除这个浏览记录吗?')) { + fetch('/history/remove', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: productId + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + // 移除商品卡片 + const itemElement = document.querySelector(`[data-product-id="${productId}"]`); + if (itemElement) { + itemElement.remove(); + } + // 更新历史数量 + updateHistoryCount(); + // 检查是否为空 + checkEmptyState(); + } else { + showErrorMessage(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showErrorMessage('操作失败,请稍后再试'); + }); + } +} + +function batchRemove() { + if (selectedItems.length === 0) { + showErrorMessage('请选择要删除的商品'); + return; + } + + const modal = new bootstrap.Modal(document.getElementById('confirmModal')); + document.getElementById('confirmMessage').textContent = + `确定要删除这 ${selectedItems.length} 个浏览记录吗?`; + + document.getElementById('confirmBtn').onclick = function() { + performBatchRemove(); + modal.hide(); + }; + + modal.show(); +} + +function performBatchRemove() { + fetch('/history/batch-remove', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_ids: selectedItems + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + // 移除选中的商品卡片 + selectedItems.forEach(productId => { + const itemElement = document.querySelector(`[data-product-id="${productId}"]`); + if (itemElement) { + itemElement.remove(); + } + }); + // 重置选择状态 + selectedItems = []; + isSelectAll = false; + updateSelectedItems(); + // 更新历史数量 + updateHistoryCount(); + // 检查是否为空 + checkEmptyState(); + } else { + showErrorMessage(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showErrorMessage('批量删除失败,请稍后再试'); + }); +} + +function clearHistory() { + if (confirm('确定要清空所有浏览历史吗?此操作不可恢复。')) { + fetch('/history/clear', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + // 重新加载页面或显示空状态 + location.reload(); + } else { + showErrorMessage(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showErrorMessage('清空历史失败,请稍后再试'); + }); + } +} + +function addToCart(productId) { + // 调用购物车添加功能 + fetch('/cart/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: productId, + quantity: 1 + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + // 更新购物车数量 + if (typeof updateCartBadge === 'function') { + updateCartBadge(data.cart_count); + } + } else { + showErrorMessage(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showErrorMessage('加入购物车失败,请稍后再试'); + }); +} + +function addToFavorites(productId) { + fetch('/favorite/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: productId + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + } else { + showErrorMessage(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + showErrorMessage('收藏失败,请稍后再试'); + }); +} + +function updateHistoryCount() { + fetch('/history/count') + .then(response => response.json()) + .then(data => { + if (data.success) { + // 更新历史数量显示 + const badge = document.querySelector('.badge.bg-secondary'); + if (badge) { + badge.textContent = `共 ${data.history_count} 件商品`; + } + } + }) + .catch(error => { + console.error('Error:', error); + }); +} + +function checkEmptyState() { + const itemsContainer = document.querySelector('.row'); + const items = itemsContainer.querySelectorAll('.history-item'); + + if (items.length === 0) { + // 显示空状态 + itemsContainer.innerHTML = ` +
+
+
+ +

还没有浏览任何商品

+

去逛逛,看看有什么好商品~

+ + 去首页逛逛 + +
+
+
+ `; + } +} + +function showSuccessMessage(message) { + // 使用现有的消息提示函数 + if (typeof showMessage === 'function') { + showMessage(message, 'success'); + } else { + alert(message); + } +} + +function showErrorMessage(message) { + // 使用现有的消息提示函数 + if (typeof showMessage === 'function') { + showMessage(message, 'error'); + } else { + alert(message); + } +} + + +🔸============================================================================== +📄 文件: app/static/js/order_detail.js +📊 大小: 1505 bytes (1.47 KB) +🕒 修改时间: 2025-07-08 16:50:42 +🔸============================================================================== + +// 订单详情页面脚本 - 只处理业务逻辑,不处理样式 + +// 取消订单 +function cancelOrder(orderId) { + if (confirm('确定要取消这个订单吗?取消后无法恢复。')) { + fetch(`/order/cancel/${orderId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert(data.message, 'success'); + setTimeout(() => location.reload(), 1000); + } else { + showAlert(data.message, 'error'); + } + }) + .catch(error => { + showAlert('操作失败,请重试', 'error'); + }); + } +} + +// 确认收货 +function confirmReceipt(orderId) { + if (confirm('确定已收到商品吗?确认后订单将完成。')) { + fetch(`/order/confirm_receipt/${orderId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert(data.message, 'success'); + setTimeout(() => location.reload(), 1000); + } else { + showAlert(data.message, 'error'); + } + }) + .catch(error => { + showAlert('操作失败,请重试', 'error'); + }); + } +} + + +🔸============================================================================== +📄 文件: app/static/js/orders.js +📊 大小: 3465 bytes (3.38 KB) +🕒 修改时间: 2025-07-04 04:03:15 +🔸============================================================================== + +// 订单页面JavaScript功能 + +// 强制设置商品图片样式的函数 +function forceProductImageStyle(imgElement) { + if (!imgElement) return; + + // 强制设置所有样式属性 + imgElement.style.width = '80px'; + imgElement.style.height = '80px'; + imgElement.style.objectFit = 'cover'; + imgElement.style.borderRadius = '4px'; + imgElement.style.display = 'block'; + imgElement.style.maxWidth = '80px'; + imgElement.style.maxHeight = '80px'; + imgElement.style.minWidth = '80px'; + imgElement.style.minHeight = '80px'; + + // 设置属性避免被覆盖 + imgElement.setAttribute('width', '80'); + imgElement.setAttribute('height', '80'); +} + +// 页面加载完成后的处理 +document.addEventListener('DOMContentLoaded', function() { + // 强制设置所有商品图片样式 + const productImages = document.querySelectorAll('.product-image'); + productImages.forEach(function(img) { + forceProductImageStyle(img); + + // 图片加载完成后再次强制设置 + if (img.complete) { + forceProductImageStyle(img); + } else { + img.onload = function() { + forceProductImageStyle(img); + }; + } + }); + + // 为订单卡片添加悬停效果 + const orderCards = document.querySelectorAll('.order-card'); + orderCards.forEach(card => { + card.addEventListener('mouseenter', function() { + this.style.boxShadow = '0 4px 15px rgba(0,0,0,0.1)'; + }); + + card.addEventListener('mouseleave', function() { + this.style.boxShadow = ''; + }); + }); +}); + +// 额外的保险措施:定期检查并修正商品图片样式 +setInterval(function() { + const productImages = document.querySelectorAll('.product-image'); + productImages.forEach(function(img) { + // 检查图片是否超出预期尺寸 + const rect = img.getBoundingClientRect(); + if (rect.width > 85 || rect.height > 85) { + forceProductImageStyle(img); + } + }); +}, 1000); // 每秒检查一次 + +function cancelOrder(orderId) { + if (confirm('确定要取消这个订单吗?取消后无法恢复。')) { + fetch(`/order/cancel/${orderId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert(data.message, 'success'); + setTimeout(() => location.reload(), 1000); + } else { + showAlert(data.message, 'error'); + } + }) + .catch(error => { + showAlert('操作失败,请重试', 'error'); + }); + } +} + +function confirmReceipt(orderId) { + if (confirm('确定已收到商品吗?确认后订单将完成。')) { + fetch(`/order/confirm_receipt/${orderId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert(data.message, 'success'); + setTimeout(() => location.reload(), 1000); + } else { + showAlert(data.message, 'error'); + } + }) + .catch(error => { + showAlert('操作失败,请重试', 'error'); + }); + } +} + + +🔸============================================================================== +📄 文件: app/static/js/pay.js +📊 大小: 10562 bytes (10.31 KB) +🕒 修改时间: 2025-07-08 17:18:51 +🔸============================================================================== + +// 订单支付页面脚本 +let countdownTimer; +let statusCheckTimer; +let timeLeft = 15 * 60; // 15分钟 + +// 页面加载时开始倒计时 +document.addEventListener('DOMContentLoaded', function() { + startCountdown(); +}); + +// 页面卸载时清理定时器 +window.addEventListener('beforeunload', function() { + if (countdownTimer) clearInterval(countdownTimer); + if (statusCheckTimer) clearInterval(statusCheckTimer); +}); + +// 开始倒计时 +function startCountdown() { + const countdownElement = document.getElementById('countdown'); + if (!countdownElement) return; + + countdownTimer = setInterval(() => { + timeLeft--; + + const minutes = Math.floor(timeLeft / 60); + const seconds = timeLeft % 60; + + countdownElement.textContent = + `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + + if (timeLeft <= 0) { + clearInterval(countdownTimer); + showAlert('订单已过期,请重新下单', 'warning'); + setTimeout(() => { + window.location.href = '/order/list'; + }, 2000); + } + }, 1000); +} + +// 开始支付 +function startPayment() { + const paymentSn = document.querySelector('[data-payment-sn]')?.dataset.paymentSn; + const paymentMethod = document.querySelector('[data-payment-method]')?.dataset.paymentMethod; + + if (!paymentSn || !paymentMethod) { + showAlert('支付信息获取失败', 'error'); + return; + } + + // 如果是模拟支付,直接显示控制面板,不需要调用接口 + if (paymentMethod === 'simulate') { + showAlert('请使用下方控制面板完成模拟支付', 'info'); + return; + } + + fetch('/payment/process', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + payment_sn: paymentSn, + payment_method: paymentMethod + }) + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (data.success) { + if (data.payment_type === 'qrcode') { + showQRCode(data.qr_code_url); + startStatusCheck(); + } else if (data.payment_type === 'redirect') { + window.open(data.pay_url, '_blank'); + startStatusCheck(); + } else if (data.payment_type === 'simulate') { + showAlert('模拟支付已准备就绪', 'success'); + } + } else { + showAlert(data.message || '支付启动失败', 'error'); + } + }) + .catch(error => { + console.error('支付启动错误:', error); + showAlert('支付启动失败,请重试', 'error'); + }); +} + +// 显示二维码 +function showQRCode(qrUrl) { + const qrArea = document.getElementById('qrCodeArea'); + const qrImage = document.getElementById('qrCodeImage'); + + if (!qrArea || !qrImage) return; + + // 这里应该使用真实的二维码生成库,现在用文本模拟 + qrImage.innerHTML = ` +
+
+
+ 微信支付二维码 +
+
+ `; + + qrArea.style.display = 'block'; +} + +// 开始检查支付状态 +function startStatusCheck() { + statusCheckTimer = setInterval(() => { + checkPaymentStatus(); + }, 3000); // 每3秒检查一次 +} + +// 检查支付状态 +function checkPaymentStatus() { + const paymentSn = document.querySelector('[data-payment-sn]')?.dataset.paymentSn; + + if (!paymentSn) return; + + fetch(`/payment/check_status/${paymentSn}`) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (data.success) { + if (data.status === 2) { // 支付成功 + clearInterval(statusCheckTimer); + clearInterval(countdownTimer); + showPaymentSuccess(); + } else if (data.status === 3) { // 支付失败 + clearInterval(statusCheckTimer); + showPaymentFail(); + } + } + }) + .catch(error => { + console.error('状态检查失败:', error); + }); +} + +// 显示支付成功 +function showPaymentSuccess() { + const paymentArea = document.getElementById('paymentArea'); + const actionButtons = document.getElementById('actionButtons'); + const paymentStatus = document.getElementById('paymentStatus'); + const successStatus = document.getElementById('successStatus'); + + if (paymentArea) paymentArea.style.display = 'none'; + if (actionButtons) actionButtons.style.display = 'none'; + if (paymentStatus) paymentStatus.style.display = 'block'; + if (successStatus) successStatus.style.display = 'block'; + + showAlert('支付成功!正在跳转到订单详情...', 'success'); + + const orderId = document.querySelector('[data-order-id]')?.dataset.orderId; + setTimeout(() => { + if (orderId) { + window.location.href = `/order/detail/${orderId}`; + } else { + window.location.href = '/order/list'; + } + }, 2000); +} + +// 显示支付失败 +function showPaymentFail() { + const paymentArea = document.getElementById('paymentArea'); + const paymentStatus = document.getElementById('paymentStatus'); + const failStatus = document.getElementById('failStatus'); + + if (paymentArea) paymentArea.style.display = 'none'; + if (paymentStatus) paymentStatus.style.display = 'block'; + if (failStatus) failStatus.style.display = 'block'; + + showAlert('支付失败,请重新尝试', 'error'); + + // 显示重试按钮 + setTimeout(() => { + if (paymentArea) paymentArea.style.display = 'block'; + if (paymentStatus) paymentStatus.style.display = 'none'; + }, 3000); +} + +// 取消订单 +function cancelOrder() { + const orderId = document.querySelector('[data-order-id]')?.dataset.orderId; + + if (!orderId) { + showAlert('订单信息获取失败', 'error'); + return; + } + + showConfirm('确定要取消这个订单吗?', () => { + fetch(`/order/cancel/${orderId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (data.success) { + showAlert('订单已取消,正在跳转...', 'success'); + setTimeout(() => { + window.location.href = '/order/list'; + }, 1500); + } else { + showAlert(data.message || '取消订单失败', 'error'); + } + }) + .catch(error => { + console.error('取消订单错误:', error); + showAlert('取消失败,请重试', 'error'); + }); + }); +} + +// 模拟支付成功 +function simulatePaymentSuccess() { + const paymentSn = document.querySelector('[data-payment-sn]')?.dataset.paymentSn; + + if (!paymentSn) { + showAlert('支付信息获取失败', 'error'); + return; + } + + // 显示处理中状态 + const button = event.target; + const originalText = button.innerHTML; + button.innerHTML = ' 处理中...'; + button.disabled = true; + + fetch(`/payment/simulate_success/${paymentSn}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (data.success) { + showAlert('模拟支付成功!', 'success'); + setTimeout(() => { + showPaymentSuccess(); + }, 1000); + } else { + showAlert(data.message || '模拟支付失败', 'error'); + button.innerHTML = originalText; + button.disabled = false; + } + }) + .catch(error => { + console.error('模拟支付错误:', error); + showAlert('模拟支付失败', 'error'); + button.innerHTML = originalText; + button.disabled = false; + }); +} + +// 模拟支付失败 +function simulatePaymentFail() { + const paymentSn = document.querySelector('[data-payment-sn]')?.dataset.paymentSn; + + if (!paymentSn) { + showAlert('支付信息获取失败', 'error'); + return; + } + + showConfirm('确定要模拟支付失败吗?这将导致订单支付失败。', () => { + // 显示处理中状态 + const button = event.target; + const originalText = button.innerHTML; + button.innerHTML = ' 处理中...'; + button.disabled = true; + + fetch(`/payment/simulate_fail/${paymentSn}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (data.success) { + showAlert('模拟支付失败!', 'warning'); + setTimeout(() => { + showPaymentFail(); + }, 1000); + } else { + showAlert(data.message || '模拟操作失败', 'error'); + button.innerHTML = originalText; + button.disabled = false; + } + }) + .catch(error => { + console.error('模拟支付失败错误:', error); + showAlert('模拟操作失败', 'error'); + button.innerHTML = originalText; + button.disabled = false; + }); + }); +} + +// 兼容旧版本的模拟支付函数 +function simulatePayment() { + simulatePaymentSuccess(); +} + + +🔸============================================================================== +📄 文件: app/static/js/product_detail.js +📊 大小: 13520 bytes (13.20 KB) +🕒 修改时间: 2025-07-09 05:22:56 +🔸============================================================================== + +// 获取库存数据 +const inventoryData = JSON.parse(document.getElementById('inventoryData').textContent); +let selectedSpecs = {}; +let currentSku = null; + +// 初始化 +document.addEventListener('DOMContentLoaded', function() { + // 如果只有一个SKU,自动选择 + if (inventoryData.length === 1) { + currentSku = inventoryData[0]; + updateStockInfo(); + } else if (inventoryData.length > 1) { + // 自动选择默认规格 + const defaultSku = inventoryData.find(sku => sku.is_default); + if (defaultSku && defaultSku.spec_combination) { + // 自动选择默认规格 + for (const [specName, specValue] of Object.entries(defaultSku.spec_combination)) { + selectedSpecs[specName] = specValue; + // 更新按钮状态 + const button = document.querySelector(`[data-spec-name="${specName}"][data-spec-value="${specValue}"]`); + if (button) { + button.classList.remove('btn-outline-secondary'); + button.classList.add('btn-primary'); + } + } + currentSku = defaultSku; + updateStockInfo(); + } + } + + // 绑定规格选择事件 + document.querySelectorAll('.spec-option').forEach(button => { + button.addEventListener('click', function() { + selectSpec(this); + }); + }); + + // 初始化购物车数量显示 + if (typeof loadCartCount === 'function') { + loadCartCount(); + } + + // 添加浏览历史记录 + if (window.isLoggedIn && window.productId) { + addBrowseHistory(window.productId); + } + + // 检查收藏状态 + if (window.isLoggedIn && window.productId) { + checkFavoriteStatus(window.productId); + } +}); + +// 添加浏览历史记录 +function addBrowseHistory(productId) { + fetch('/history/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: productId + }) + }) + .then(response => response.json()) + .then(data => { + // 静默添加,不需要用户感知 + console.log('浏览历史记录已添加'); + }) + .catch(error => { + console.error('添加浏览历史失败:', error); + }); +} + +// 检查收藏状态 +function checkFavoriteStatus(productId) { + fetch(`/favorite/check/${productId}`) + .then(response => response.json()) + .then(data => { + if (data.success) { + updateFavoriteButton(data.is_favorited); + } + }) + .catch(error => { + console.error('检查收藏状态失败:', error); + }); +} + +// 更新收藏按钮状态 +function updateFavoriteButton(isFavorited) { + const favoriteBtn = document.querySelector('[onclick="addToFavorites()"]'); + if (favoriteBtn) { + if (isFavorited) { + favoriteBtn.innerHTML = ' 已收藏'; + favoriteBtn.className = 'btn btn-outline-danger'; + } else { + favoriteBtn.innerHTML = ' 收藏商品'; + favoriteBtn.className = 'btn btn-outline-secondary'; + } + } +} + +// 规格选择 +function selectSpec(button) { + const specName = button.getAttribute('data-spec-name'); + const specValue = button.getAttribute('data-spec-value'); + + // 清除同组其他选择 + document.querySelectorAll(`[data-spec-name="${specName}"]`).forEach(btn => { + btn.classList.remove('btn-primary'); + btn.classList.add('btn-outline-secondary'); + }); + + // 选中当前项 + button.classList.remove('btn-outline-secondary'); + button.classList.add('btn-primary'); + + // 更新选择状态 + selectedSpecs[specName] = specValue; + + // 查找匹配的SKU + findMatchingSku(); +} + +// 查找匹配的SKU +function findMatchingSku() { + // 获取所有需要选择的规格类型 + const allSpecNames = new Set(); + inventoryData.forEach(sku => { + if (sku.spec_combination) { + Object.keys(sku.spec_combination).forEach(specName => { + allSpecNames.add(specName); + }); + } + }); + + // 检查是否选择了所有必需的规格 + const selectedSpecNames = Object.keys(selectedSpecs); + if (selectedSpecNames.length < allSpecNames.size) { + currentSku = null; + updateStockInfo(); + return; + } + + // 查找完全匹配的SKU + for (let sku of inventoryData) { + if (sku.spec_combination) { + let isMatch = true; + + // 检查规格数量是否匹配 + if (Object.keys(sku.spec_combination).length !== selectedSpecNames.length) { + continue; + } + + // 检查每个规格是否匹配 + for (let [specName, specValue] of Object.entries(selectedSpecs)) { + if (sku.spec_combination[specName] !== specValue) { + isMatch = false; + break; + } + } + + if (isMatch) { + currentSku = sku; + updateStockInfo(); + return; + } + } + } + + // 未找到完全匹配的SKU + currentSku = null; + updateStockInfo(); +} + +// 更新库存信息 +function updateStockInfo() { + const stockElement = document.getElementById('stockCount'); + const priceElement = document.getElementById('currentPrice'); + const addToCartBtn = document.getElementById('addToCartBtn'); + const buyNowBtn = document.getElementById('buyNowBtn'); + const quantityInput = document.getElementById('quantity'); + + if (currentSku) { + // 有选中的SKU + stockElement.textContent = currentSku.stock; + if (currentSku.stock > 0) { + stockElement.className = 'text-success'; + addToCartBtn.disabled = false; + buyNowBtn.disabled = false; + quantityInput.max = currentSku.stock; + quantityInput.value = Math.min(parseInt(quantityInput.value), currentSku.stock); + } else { + stockElement.className = 'text-danger'; + addToCartBtn.disabled = true; + buyNowBtn.disabled = true; + quantityInput.max = 0; + quantityInput.value = 1; + } + + // 更新价格 + priceElement.textContent = currentSku.final_price.toFixed(2); + } else if (inventoryData.length > 1) { + // 多规格商品但未完全选择 + stockElement.textContent = '请选择规格'; + stockElement.className = 'text-warning'; + addToCartBtn.disabled = true; + buyNowBtn.disabled = true; + quantityInput.max = 999; + } else { + // 无库存或其他错误 + stockElement.textContent = '暂无库存'; + stockElement.className = 'text-danger'; + addToCartBtn.disabled = true; + buyNowBtn.disabled = true; + quantityInput.max = 0; + } +} + +// 数量变更 +function changeQuantity(delta) { + const quantityInput = document.getElementById('quantity'); + let quantity = parseInt(quantityInput.value) + delta; + + const min = parseInt(quantityInput.min) || 1; + const max = parseInt(quantityInput.max) || 999; + + quantity = Math.max(min, Math.min(max, quantity)); + quantityInput.value = quantity; +} + +// 轮播图跳转 +function goToSlide(index) { + const carousel = new bootstrap.Carousel(document.getElementById('productImageCarousel')); + carousel.to(index); +} + +// 加载购物车数量 +function loadCartCount() { + fetch('/cart/count') + .then(response => response.json()) + .then(data => { + updateCartBadge(data.cart_count); + }) + .catch(error => { + console.error('Error loading cart count:', error); + }); +} + +// 更新购物车徽章 +function updateCartBadge(count) { + const badge = document.querySelector('.cart-badge'); + if (badge) { + badge.textContent = count; + badge.style.display = count > 0 ? 'inline' : 'none'; + } +} + +// 加入购物车 +function addToCart() { + if (!currentSku) { + alert('请选择商品规格'); + return; + } + + const quantity = parseInt(document.getElementById('quantity').value); + if (quantity <= 0 || quantity > currentSku.stock) { + alert('请选择正确的购买数量'); + return; + } + + // 禁用按钮,防止重复点击 + const addToCartBtn = document.getElementById('addToCartBtn'); + addToCartBtn.disabled = true; + addToCartBtn.innerHTML = ' 添加中...'; + + // 准备规格组合数据 + const specCombination = Object.keys(selectedSpecs).length > 0 ? + Object.entries(selectedSpecs).map(([key, value]) => `${key}:${value}`).join(', ') : ''; + + // 提交到购物车 + fetch('/cart/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: window.productId, + sku_code: currentSku.sku_code, + spec_combination: specCombination, + quantity: quantity + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + updateCartBadge(data.cart_count); + + // 询问是否查看购物车 + setTimeout(() => { + if (confirm('商品已添加到购物车,是否查看购物车?')) { + window.location.href = '/cart/'; + } + }, 500); + } else { + alert(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + alert('加入购物车失败,请稍后再试'); + }) + .finally(() => { + // 恢复按钮状态 + addToCartBtn.disabled = false; + addToCartBtn.innerHTML = ' 加入购物车'; + }); +} + +// 立即购买 +function buyNow() { + if (!currentSku) { + alert('请选择商品规格'); + return; + } + + const quantity = parseInt(document.getElementById('quantity').value); + if (quantity <= 0 || quantity > currentSku.stock) { + alert('请选择正确的购买数量'); + return; + } + + // 禁用按钮,防止重复点击 + const buyNowBtn = document.getElementById('buyNowBtn'); + buyNowBtn.disabled = true; + buyNowBtn.innerHTML = ' 处理中...'; + + // 准备规格组合数据 + const specCombination = Object.keys(selectedSpecs).length > 0 ? + Object.entries(selectedSpecs).map(([key, value]) => `${key}:${value}`).join(', ') : ''; + + // 先添加到购物车,然后跳转到结算页面 + fetch('/cart/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: window.productId, + sku_code: currentSku.sku_code, + spec_combination: specCombination, + quantity: quantity + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + // 立即跳转到购物车结算 + window.location.href = '/cart/'; + } else { + alert(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + alert('购买失败,请稍后再试'); + }) + .finally(() => { + // 恢复按钮状态 + buyNowBtn.disabled = false; + buyNowBtn.innerHTML = ' 立即购买'; + }); +} + +// 收藏商品 +function addToFavorites() { + if (!window.isLoggedIn) { + if (confirm('请先登录后再收藏,是否前往登录?')) { + window.location.href = '/auth/login?next=' + encodeURIComponent(window.location.pathname); + } + return; + } + + // 确保获取到商品ID + const productId = window.productId || window.currentProductId; + if (!productId) { + alert('获取商品信息失败,请刷新页面重试'); + return; + } + + const favoriteBtn = document.querySelector('[onclick="addToFavorites()"]'); + const isFavorited = favoriteBtn && favoriteBtn.innerHTML.includes('已收藏'); + + // 临时禁用按钮 + if (favoriteBtn) { + favoriteBtn.disabled = true; + favoriteBtn.innerHTML = ' 处理中...'; + } + + fetch('/favorite/toggle', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + product_id: parseInt(productId) + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + updateFavoriteButton(data.is_favorited); + } else { + alert(data.message); + } + }) + .catch(error => { + console.error('Error:', error); + alert('操作失败,请稍后再试'); + }) + .finally(() => { + // 恢复按钮状态 + if (favoriteBtn) { + favoriteBtn.disabled = false; + } + }); +} + +// 显示成功消息 +function showSuccessMessage(message) { + // 这里可以使用Toast或其他方式显示消息 + if (typeof showToast === 'function') { + showToast(message, 'success'); + } else { + // 简单的成功提示 + alert(message); + } +} + + +🔸============================================================================== +📄 文件: app/static/js/product_list.js +📊 大小: 224 bytes (0.22 KB) +🕒 修改时间: 2025-07-04 14:41:10 +🔸============================================================================== + +function changeSort(sortType) { + const url = new URL(window.location); + url.searchParams.set('sort', sortType); + url.searchParams.set('page', '1'); // 重置到第一页 + window.location.href = url.toString(); +} + + +🔸============================================================================== +📄 文件: app/static/js/profile.js +📊 大小: 10990 bytes (10.73 KB) +🕒 修改时间: 2025-07-04 04:00:03 +🔸============================================================================== + +// 个人中心页面JavaScript功能 +let selectedFile = null; + +// 强制设置头像样式的函数 +function forceAvatarStyle(imgElement) { + if (!imgElement) return; + + // 强制设置所有样式属性 + imgElement.style.width = '120px'; + imgElement.style.height = '120px'; + imgElement.style.borderRadius = '50%'; + imgElement.style.border = '3px solid #ddd'; + imgElement.style.objectFit = 'cover'; + imgElement.style.cursor = 'pointer'; + imgElement.style.transition = 'all 0.3s ease'; + imgElement.style.display = 'block'; + imgElement.style.maxWidth = '120px'; + imgElement.style.maxHeight = '120px'; + imgElement.style.minWidth = '120px'; + imgElement.style.minHeight = '120px'; + + // 设置属性避免被覆盖 + imgElement.setAttribute('width', '120'); + imgElement.setAttribute('height', '120'); +} + +// 页面加载完成后的处理 +document.addEventListener('DOMContentLoaded', function() { + // 隐藏进度条 + const progressContainer = document.getElementById('uploadProgress'); + if (progressContainer) { + progressContainer.classList.remove('show'); + progressContainer.style.display = 'none'; + } + + // *** 关键:强制设置已存在的头像样式 *** + const existingAvatar = document.getElementById('avatarPreview'); + if (existingAvatar) { + forceAvatarStyle(existingAvatar); + + // 图片加载完成后再次强制设置 + if (existingAvatar.complete) { + forceAvatarStyle(existingAvatar); + } else { + existingAvatar.onload = function() { + forceAvatarStyle(existingAvatar); + }; + } + } + + // 添加拖拽上传支持 + initDragAndDrop(); +}); + +// 触发文件选择 +function triggerFileInput() { + document.getElementById('avatarInput').click(); +} + +// 处理文件选择 +function handleFileSelect(event) { + const file = event.target.files[0]; + if (!file) return; + + // 验证文件类型 + if (!file.type.match('image.*')) { + showAlert('请选择图片文件!', 'error'); + return; + } + + // 验证文件大小 (2MB) + if (file.size > 2 * 1024 * 1024) { + showAlert('图片大小不能超过 2MB!', 'error'); + return; + } + + selectedFile = file; + + // 预览图片 + const reader = new FileReader(); + reader.onload = function(e) { + const previewImage = document.getElementById('previewImage'); + previewImage.src = e.target.result; + + // 更新文件信息 + updateFileInfo(file); + + // 确保图片加载完成后再显示模态框 + previewImage.onload = function() { + // *** 强制设置预览图片样式 *** + previewImage.style.maxWidth = '280px'; + previewImage.style.maxHeight = '280px'; + previewImage.style.width = 'auto'; + previewImage.style.height = 'auto'; + previewImage.style.objectFit = 'contain'; + previewImage.style.borderRadius = '12px'; + previewImage.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15)'; + previewImage.style.border = '3px solid #fff'; + + // 更新图片尺寸信息 + document.getElementById('imageWidth').textContent = previewImage.naturalWidth; + document.getElementById('imageHeight').textContent = previewImage.naturalHeight; + + const modal = new bootstrap.Modal(document.getElementById('imagePreviewModal')); + modal.show(); + }; + }; + reader.readAsDataURL(file); +} + +// 更新文件信息 +function updateFileInfo(file) { + // 文件大小 + const sizeInMB = (file.size / 1024 / 1024).toFixed(2); + document.getElementById('imageSize').textContent = sizeInMB + ' MB'; + + // 文件类型 + const fileType = file.type.split('/')[1].toUpperCase(); + document.getElementById('imageType').textContent = fileType; +} + +// 确认上传 +function confirmUpload() { + if (!selectedFile) return; + + // 关闭模态框 + const modal = bootstrap.Modal.getInstance(document.getElementById('imagePreviewModal')); + modal.hide(); + + // 开始上传 + uploadAvatar(selectedFile); +} + +// 上传头像 +function uploadAvatar(file) { + const formData = new FormData(); + formData.append('avatar', file); + + // 显示上传进度 + const progressContainer = document.getElementById('uploadProgress'); + const progressBar = progressContainer.querySelector('.progress-bar'); + + progressContainer.style.display = 'block'; + progressContainer.classList.add('show'); + progressBar.style.width = '0%'; + progressBar.textContent = '0%'; + + // 创建XMLHttpRequest以支持进度显示 + const xhr = new XMLHttpRequest(); + + // 上传进度 + xhr.upload.addEventListener('progress', function(e) { + if (e.lengthComputable) { + const percentComplete = (e.loaded / e.total) * 100; + progressBar.style.width = percentComplete + '%'; + progressBar.textContent = Math.round(percentComplete) + '%'; + } + }); + + // 上传完成 + xhr.addEventListener('load', function() { + // 隐藏进度条 + progressContainer.style.display = 'none'; + progressContainer.classList.remove('show'); + + if (xhr.status === 200) { + try { + const response = JSON.parse(xhr.responseText); + if (response.success) { + // 更新头像显示 + updateAvatarDisplay(response.avatar_url); + showAlert('头像上传成功!', 'success'); + } else { + showAlert(response.message || '上传失败', 'error'); + } + } catch (e) { + showAlert('服务器响应错误', 'error'); + } + } else { + showAlert('上传失败,请重试', 'error'); + } + + // 清理文件输入 + document.getElementById('avatarInput').value = ''; + selectedFile = null; + }); + + // 上传错误 + xhr.addEventListener('error', function() { + progressContainer.style.display = 'none'; + progressContainer.classList.remove('show'); + showAlert('网络错误,请重试', 'error'); + + // 清理文件输入 + document.getElementById('avatarInput').value = ''; + selectedFile = null; + }); + + // 发送请求 + xhr.open('POST', '/upload/avatar'); + xhr.send(formData); +} + +// *** 关键:更新头像显示函数 *** +function updateAvatarDisplay(avatarUrl) { + const avatarPreview = document.getElementById('avatarPreview'); + const avatarPlaceholder = document.getElementById('avatarPlaceholder'); + + if (avatarPreview) { + // 更新现有头像 + avatarPreview.src = avatarUrl + '?t=' + new Date().getTime(); + + // *** 强制设置头像样式 *** + avatarPreview.onload = function() { + forceAvatarStyle(avatarPreview); + + // 延迟再次确保样式生效 + setTimeout(function() { + forceAvatarStyle(avatarPreview); + }, 100); + }; + + } else if (avatarPlaceholder) { + // 替换占位符为头像 + const avatarUpload = avatarPlaceholder.parentElement; + avatarPlaceholder.remove(); + + const img = document.createElement('img'); + img.src = avatarUrl + '?t=' + new Date().getTime(); + img.alt = '头像'; + img.className = 'avatar-preview'; + img.id = 'avatarPreview'; + + // *** 创建新头像时强制设置样式 *** + img.onload = function() { + forceAvatarStyle(img); + + // 延迟再次确保样式生效 + setTimeout(function() { + forceAvatarStyle(img); + }, 100); + }; + + avatarUpload.insertBefore(img, avatarUpload.firstChild); + } +} + +// 显示提示信息 +function showAlert(message, type = 'info') { + // 移除现有的提示框 + const existingAlerts = document.querySelectorAll('.alert.position-fixed'); + existingAlerts.forEach(alert => alert.remove()); + + // 创建提示框 + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show position-fixed`; + alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px; max-width: 400px;'; + + const icon = type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-triangle' : 'info-circle'; + + alertDiv.innerHTML = ` + + ${message} + + `; + + document.body.appendChild(alertDiv); + + // 3秒后自动消失 + setTimeout(() => { + if (alertDiv.parentElement) { + alertDiv.classList.remove('show'); + setTimeout(() => { + if (alertDiv.parentElement) { + alertDiv.remove(); + } + }, 150); + } + }, 3000); +} + +// 初始化拖拽上传功能 +function initDragAndDrop() { + const avatarUpload = document.querySelector('.avatar-upload'); + + if (avatarUpload) { + // 防止默认拖拽行为 + ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { + avatarUpload.addEventListener(eventName, preventDefaults, false); + }); + + function preventDefaults(e) { + e.preventDefault(); + e.stopPropagation(); + } + + // 拖拽进入和离开的视觉反馈 + ['dragenter', 'dragover'].forEach(eventName => { + avatarUpload.addEventListener(eventName, highlight, false); + }); + + ['dragleave', 'drop'].forEach(eventName => { + avatarUpload.addEventListener(eventName, unhighlight, false); + }); + + function highlight(e) { + avatarUpload.style.transform = 'scale(1.05)'; + avatarUpload.style.boxShadow = '0 0 20px rgba(0,123,255,0.5)'; + } + + function unhighlight(e) { + avatarUpload.style.transform = ''; + avatarUpload.style.boxShadow = ''; + } + + // 处理文件拖拽 + avatarUpload.addEventListener('drop', handleDrop, false); + + function handleDrop(e) { + const dt = e.dataTransfer; + const files = dt.files; + + if (files.length > 0) { + const file = files[0]; + // 模拟文件输入事件 + const event = { target: { files: [file] } }; + handleFileSelect(event); + } + } + } +} + +// 额外的保险措施:定期检查并修正头像样式 +setInterval(function() { + const avatar = document.getElementById('avatarPreview'); + if (avatar) { + // 检查头像是否超出预期尺寸 + const rect = avatar.getBoundingClientRect(); + if (rect.width > 125 || rect.height > 125) { + forceAvatarStyle(avatar); + } + } +}, 1000); // 每秒检查一次 + + +🔸============================================================================== +📄 文件: app/static/js/register.js +📊 大小: 4062 bytes (3.97 KB) +🕒 修改时间: 2025-07-04 03:58:52 +🔸============================================================================== + +// 注册页面JavaScript功能 +document.addEventListener('DOMContentLoaded', function() { + const sendBtn = document.getElementById('sendEmailCodeBtn'); + const emailInput = document.getElementById('emailInput'); + const btnText = document.getElementById('btnText'); + const passwordInput = document.getElementById('passwordInput'); + const confirmPasswordInput = document.getElementById('confirmPasswordInput'); + const passwordMatchMessage = document.getElementById('passwordMatchMessage'); + let countdown = 0; + let timer = null; + + // 密码确认实时验证 + function checkPasswordMatch() { + const password = passwordInput.value; + const confirmPassword = confirmPasswordInput.value; + + if (confirmPassword === '') { + passwordMatchMessage.textContent = ''; + passwordMatchMessage.className = 'form-text'; + return; + } + + if (password === confirmPassword) { + passwordMatchMessage.textContent = '✓ 密码匹配'; + passwordMatchMessage.className = 'form-text text-success'; + confirmPasswordInput.classList.remove('is-invalid'); + confirmPasswordInput.classList.add('is-valid'); + } else { + passwordMatchMessage.textContent = '✗ 密码不匹配'; + passwordMatchMessage.className = 'form-text text-danger'; + confirmPasswordInput.classList.remove('is-valid'); + confirmPasswordInput.classList.add('is-invalid'); + } + } + + // 监听密码输入 + passwordInput.addEventListener('input', checkPasswordMatch); + confirmPasswordInput.addEventListener('input', checkPasswordMatch); + + // 发送验证码 + sendBtn.addEventListener('click', function() { + const email = emailInput.value.trim(); + + // 简单的邮箱格式验证 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!email) { + alert('请输入邮箱地址'); + emailInput.focus(); + return; + } + + if (!emailRegex.test(email)) { + alert('请输入有效的邮箱地址'); + emailInput.focus(); + return; + } + + // 发送AJAX请求 + fetch('/auth/send_email_code', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': document.querySelector('input[name="csrf_token"]').value + }, + body: JSON.stringify({ + email: email, + type: 1 // 1表示注册 + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + alert('验证码已发送到您的邮箱,请查收!'); + startCountdown(); + } else { + alert(data.message || '发送失败,请重试'); + } + }) + .catch(error => { + console.error('Error:', error); + alert('发送失败,请检查网络连接'); + }); + }); + + // 倒计时功能 + function startCountdown() { + countdown = 60; + sendBtn.disabled = true; + sendBtn.classList.add('disabled'); + + timer = setInterval(function() { + btnText.textContent = `${countdown}秒后重发`; + countdown--; + + if (countdown < 0) { + clearInterval(timer); + sendBtn.disabled = false; + sendBtn.classList.remove('disabled'); + btnText.textContent = '发送验证码'; + } + }, 1000); + } + + // 表单提交前验证 + document.getElementById('registerForm').addEventListener('submit', function(e) { + const password = passwordInput.value; + const confirmPassword = confirmPasswordInput.value; + + if (password !== confirmPassword) { + e.preventDefault(); + alert('两次输入的密码不一致,请重新输入'); + confirmPasswordInput.focus(); + return false; + } + }); +}); + + +🔸============================================================================== +📄 文件: app/static/js/review.js +📊 大小: 22091 bytes (21.57 KB) +🕒 修改时间: 2025-07-08 19:31:57 +🔸============================================================================== + +// 评价功能 JavaScript + +document.addEventListener('DOMContentLoaded', function() { + initializeReviewForm(); + initializeImageUpload(); +}); + +// 初始化评价表单 +function initializeReviewForm() { + const starRating = document.getElementById('starRating'); + const ratingInput = document.getElementById('rating'); + const ratingText = document.getElementById('ratingText'); + const reviewForm = document.getElementById('reviewForm'); + + if (starRating) { + const stars = starRating.querySelectorAll('.star'); + const ratingTexts = { + 1: '很差', + 2: '较差', + 3: '一般', + 4: '满意', + 5: '非常满意' + }; + + let currentRating = 0; // 当前选中的评分 + + // 初始化:设置所有星星为空心 + stars.forEach(star => { + star.textContent = '☆'; // 空心星星 + }); + + // 星级点击事件 + stars.forEach((star, index) => { + star.addEventListener('click', function() { + const rating = index + 1; + setRating(rating); + }); + + // 鼠标悬停事件 + star.addEventListener('mouseenter', function() { + const rating = index + 1; + showHoverStars(rating); + + // 显示临时评分文字 + const tempText = ratingTexts[rating] || '请选择评分'; + ratingText.textContent = tempText; + ratingText.style.backgroundColor = '#ff6b35'; + ratingText.style.color = 'white'; + ratingText.style.borderColor = '#ff6b35'; + }); + }); + + // 鼠标离开星级评分区域 + starRating.addEventListener('mouseleave', function() { + showSelectedStars(currentRating); + + // 恢复原来的评分文字 + if (currentRating > 0) { + ratingText.textContent = ratingTexts[currentRating]; + ratingText.classList.add('selected'); + ratingText.style.backgroundColor = '#ff6b35'; + ratingText.style.color = 'white'; + ratingText.style.borderColor = '#ff6b35'; + } else { + ratingText.textContent = '请选择评分'; + ratingText.classList.remove('selected'); + ratingText.style.backgroundColor = '#f8f9fa'; + ratingText.style.color = '#666'; + ratingText.style.borderColor = '#e9ecef'; + } + }); + + // 设置评分 + function setRating(rating) { + currentRating = rating; + ratingInput.value = rating; + ratingText.textContent = ratingTexts[rating] || '请选择评分'; + ratingText.classList.add('selected'); + ratingText.style.backgroundColor = '#ff6b35'; + ratingText.style.color = 'white'; + ratingText.style.borderColor = '#ff6b35'; + showSelectedStars(rating); + } + + // 显示悬停状态的星星 + function showHoverStars(rating) { + stars.forEach((star, index) => { + star.classList.remove('filled'); + if (index < rating) { + star.textContent = '★'; // 实心星星 + star.classList.add('filled'); + } else { + star.textContent = '☆'; // 空心星星 + } + }); + } + + // 显示选中状态的星星 + function showSelectedStars(rating) { + stars.forEach((star, index) => { + star.classList.remove('filled'); + if (index < rating) { + star.textContent = '★'; // 实心星星 + star.classList.add('filled'); + } else { + star.textContent = '☆'; // 空心星星 + } + }); + } + } + + // 表单提交 + if (reviewForm) { + reviewForm.addEventListener('submit', function(e) { + e.preventDefault(); + submitReview(); + }); + } +} + +// 初始化图片上传 +function initializeImageUpload() { + const uploadArea = document.getElementById('uploadArea'); + const imageInput = document.getElementById('imageInput'); + const uploadedImages = document.getElementById('uploadedImages'); + + if (!uploadArea || !imageInput) return; + + let uploadedImageUrls = []; + + // 点击上传区域 + uploadArea.addEventListener('click', function() { + imageInput.click(); + }); + + // 拖拽上传 + uploadArea.addEventListener('dragover', function(e) { + e.preventDefault(); + this.style.borderColor = '#007bff'; + }); + + uploadArea.addEventListener('dragleave', function(e) { + e.preventDefault(); + this.style.borderColor = '#ddd'; + }); + + uploadArea.addEventListener('drop', function(e) { + e.preventDefault(); + this.style.borderColor = '#ddd'; + + const files = Array.from(e.dataTransfer.files); + handleFiles(files); + }); + + // 文件选择 + imageInput.addEventListener('change', function() { + const files = Array.from(this.files); + handleFiles(files); + }); + + // 处理文件上传 + function handleFiles(files) { + if (uploadedImageUrls.length + files.length > 5) { + showAlert('最多只能上传5张图片', 'warning'); + return; + } + + files.forEach(file => { + if (!file.type.startsWith('image/')) { + showAlert('只能上传图片文件', 'warning'); + return; + } + + if (file.size > 5 * 1024 * 1024) { + showAlert('图片大小不能超过5MB', 'warning'); + return; + } + + uploadImage(file); + }); + } + + // 上传图片到服务器 + function uploadImage(file) { + const formData = new FormData(); + formData.append('file', file); + + // 显示上传进度 + const previewElement = createImagePreview(URL.createObjectURL(file), true); + uploadedImages.appendChild(previewElement); + + fetch('/review/upload_image', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + // 更新预览元素 + const img = previewElement.querySelector('img'); + img.src = data.url; + // 强制设置图片样式 + forceImageStyles(img); + previewElement.classList.remove('uploading'); + previewElement.dataset.url = data.url; + uploadedImageUrls.push(data.url); + } else { + showAlert(data.message || '图片上传失败', 'error'); + previewElement.remove(); + } + }) + .catch(error => { + showAlert('图片上传失败', 'error'); + previewElement.remove(); + }); + } + + // 创建图片预览元素 + function createImagePreview(src, isUploading = false) { + const div = document.createElement('div'); + div.className = `image-preview ${isUploading ? 'uploading' : ''}`; + + // 强制设置容器样式 + div.style.cssText = ` + position: relative !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + border-radius: 8px !important; + overflow: hidden !important; + border: 2px solid #e9ecef !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; + display: inline-block !important; + box-sizing: border-box !important; + margin: 0 !important; + padding: 0 !important; + vertical-align: top !important; + `; + + const img = document.createElement('img'); + img.src = src; + img.alt = '评价图片'; + + // 强制设置图片样式 + forceImageStyles(img); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'remove-btn'; + removeBtn.innerHTML = '×'; + removeBtn.type = 'button'; + removeBtn.style.cssText = ` + position: absolute !important; + top: 2px !important; + right: 2px !important; + background: rgba(255, 255, 255, 0.9) !important; + border: none !important; + border-radius: 50% !important; + width: 20px !important; + height: 20px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + font-size: 12px !important; + color: #dc3545 !important; + box-shadow: 0 1px 3px rgba(0,0,0,0.2) !important; + z-index: 10 !important; + `; + + removeBtn.onclick = function() { + const url = div.dataset.url; + if (url) { + uploadedImageUrls = uploadedImageUrls.filter(u => u !== url); + } + div.remove(); + }; + + div.appendChild(img); + div.appendChild(removeBtn); + + return div; + } + + // 强制设置图片样式的函数 + function forceImageStyles(img) { + img.style.cssText = ` + display: block !important; + width: 80px !important; + height: 80px !important; + max-width: 80px !important; + max-height: 80px !important; + min-width: 80px !important; + min-height: 80px !important; + object-fit: cover !important; + border-radius: 6px !important; + box-sizing: border-box !important; + position: relative !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + margin: 0 !important; + padding: 0 !important; + border: none !important; + outline: none !important; + background: none !important; + vertical-align: top !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; + `; + + // 图片加载完成后再次强制设置样式 + img.onload = function() { + forceImageStyles(this); + }; + } + + // 获取上传的图片URL列表 + window.getUploadedImages = function() { + return uploadedImageUrls; + }; +} + +// 提交评价 +function submitReview() { + const submitBtn = document.getElementById('submitBtn'); + const orderId = document.getElementById('orderId').value; + const productId = document.getElementById('productId').value; + const rating = document.getElementById('rating').value; + const content = document.getElementById('content').value; + const isAnonymous = document.getElementById('isAnonymous').checked; + + // 验证 + if (!rating) { + showAlert('请选择评分', 'warning'); + return; + } + + // 禁用提交按钮 + submitBtn.disabled = true; + submitBtn.innerHTML = ' 提交中...'; + + const data = { + order_id: parseInt(orderId), + product_id: parseInt(productId), + rating: parseInt(rating), + content: content.trim(), + is_anonymous: isAnonymous, + images: window.getUploadedImages ? window.getUploadedImages() : [] + }; + + fetch('/review/submit', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert(data.message, 'success'); + setTimeout(() => { + window.location.href = `/order/detail/${orderId}`; + }, 1500); + } else { + showAlert(data.message, 'error'); + } + }) + .catch(error => { + showAlert('提交失败,请重试', 'error'); + }) + .finally(() => { + // 恢复提交按钮 + submitBtn.disabled = false; + submitBtn.innerHTML = ' 提交评价'; + }); +} + +// 加载商品评价列表(用于商品详情页) +function loadProductReviews(productId, page = 1, rating = null) { + const reviewsContainer = document.getElementById('reviewsContainer'); + if (!reviewsContainer) return; + + const params = new URLSearchParams({ + page: page + }); + + if (rating) { + params.append('rating', rating); + } + + reviewsContainer.innerHTML = '
加载中...
'; + + fetch(`/review/product/${productId}?${params}`) + .then(response => response.json()) + .then(data => { + if (data.success) { + renderReviews(data); + } else { + reviewsContainer.innerHTML = '
加载失败
'; + } + }) + .catch(error => { + reviewsContainer.innerHTML = '
加载失败
'; + }); +} + +// 渲染评价列表 +function renderReviews(data) { + const reviewsContainer = document.getElementById('reviewsContainer'); + if (!reviewsContainer) return; + + let html = ''; + + // 评价统计 + if (data.stats) { + html += renderReviewStats(data.stats); + } + + // 评价筛选 + html += renderReviewFilter(); + + // 评价列表 + if (data.reviews && data.reviews.length > 0) { + data.reviews.forEach(review => { + html += renderReviewItem(review); + }); + + // 分页 + if (data.pagination && data.pagination.pages > 1) { + html += renderPagination(data.pagination); + } + } else { + html += '
暂无评价
'; + } + + reviewsContainer.innerHTML = html; +} + +// 渲染评价统计 +function renderReviewStats(stats) { + const goodRate = stats.good_rate || 0; + const totalReviews = stats.total_reviews || 0; + + let html = ` +
+
+
+
${goodRate}%
+
好评率 (${totalReviews}条评价)
+
+
+ `; + + for (let i = 5; i >= 1; i--) { + const count = stats.rating_stats[i] || 0; + const percentage = totalReviews > 0 ? (count / totalReviews * 100) : 0; + + html += ` +
+ ${i}星 +
+
+
+ ${count} +
+ `; + } + + html += ` +
+
+
+ `; + + return html; +} + +// 渲染评价筛选 +function renderReviewFilter() { + return ` +
+ + + + +
+ `; +} + +// 渲染单个评价 - 修复图片和头像问题 +function renderReviewItem(review) { + let html = ` +
+
+ `; + + if (review.user_avatar) { + // 用户头像 - 添加内联样式强制约束尺寸 + html += `用户头像`; + } else { + html += `
+ +
`; + } + + html += ` +
+
${review.username}
+
${new Date(review.created_at).toLocaleDateString()}
+
+
+ +
+ ${review.rating_stars} + ${review.rating}分 +
+ `; + + if (review.content) { + html += `

${review.content}

`; + } + + if (review.images && review.images.length > 0) { + html += '
'; + review.images.forEach(imageUrl => { + // 评价图片 - 使用特殊的类名和内联样式确保图片尺寸正确 + html += `评价图片`; + }); + html += '
'; + } + + html += '
'; + + return html; +} + +// 渲染分页 +function renderPagination(pagination) { + if (pagination.pages <= 1) return ''; + + let html = ''; + + return html; +} + +// 筛选评价 +function filterReviews(rating) { + // 更新筛选按钮状态 + const filterButtons = document.querySelectorAll('.reviews-filter .btn'); + filterButtons.forEach(btn => btn.classList.remove('active')); + event.target.classList.add('active'); + + // 重新加载评价 + loadProductReviews(window.currentProductId, 1, rating); +} + +// 显示图片模态框 +function showImageModal(imageUrl) { + const modal = document.getElementById('imageModal'); + const modalImage = document.getElementById('modalImage'); + + if (modal && modalImage) { + modalImage.src = imageUrl; + new bootstrap.Modal(modal).show(); + } +} + +// 显示提示信息 +function showAlert(message, type = 'info') { + // 创建警告框 + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show position-fixed`; + alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; + alertDiv.innerHTML = ` + ${message} + + `; + + // 添加到页面 + document.body.appendChild(alertDiv); + + // 自动消失 + setTimeout(() => { + if (alertDiv.parentNode) { + alertDiv.remove(); + } + }, 5000); +} + +// 全局变量,用于存储当前商品ID +window.currentProductId = null; + + 🔸============================================================================== 📄 文件: app/templates/admin/base.html -📊 大小: 7904 bytes (7.72 KB) -🕒 修改时间: 2025-07-03 07:06:17 +📊 大小: 5154 bytes (5.03 KB) +🕒 修改时间: 2025-07-08 17:54:21 🔸============================================================================== @@ -1954,126 +11373,8 @@ const cityData = { - - + + {% block extra_css %}{% endblock %} @@ -2108,8 +11409,8 @@ const cityData = { - -
- + + @@ -4815,7 +14977,7 @@ document.addEventListener('DOMContentLoaded', function() { @@ -7137,7 +16549,11 @@ window.addEventListener('beforeunload', function() {
-

评价功能开发中...

+
+
+ 点击标签页加载评价 +
+
@@ -7194,351 +16610,61 @@ window.addEventListener('beforeunload', function() { {% endif %} + + + + {% endblock %} {% block scripts %} + + - - {% endblock %} 🔸============================================================================== 📄 文件: app/templates/product/list.html -📊 大小: 13955 bytes (13.63 KB) -🕒 修改时间: 2025-07-03 14:45:55 +📊 大小: 13860 bytes (13.54 KB) +🕒 修改时间: 2025-07-04 14:42:29 🔸============================================================================== {% extends "base.html" %} @@ -7549,6 +16675,10 @@ function addToFavorites() { 商品列表 - 太白购物商城 {% endblock %} +{% block head %} + +{% endblock %} + {% block content %}
@@ -7631,13 +16761,17 @@ function addToFavorites() { @@ -7811,31 +16945,341 @@ function addToFavorites() { {% endblock %} {% block scripts %} + +{% endblock %} + + +🔸============================================================================== +📄 文件: app/templates/review/my_reviews.html +📊 大小: 9242 bytes (9.03 KB) +🕒 修改时间: 2025-07-09 04:48:22 +🔸============================================================================== + +{% extends "base.html" %} +{% block title %}我的评价 - 太白购物商城{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+ + + + +
+
+
+
我的评价
+
+
+ {% if reviews.items %} + {% for review in reviews.items %} +
+
+
+ {{ review.product.name }} +
+
+
+ {{ review.product.name }} +
+ + +
+ {{ review.get_rating_stars() }} + {{ review.rating }}分 +
+ + + {% if review.content %} +

{{ review.content }}

+ {% endif %} + + + {% if review.get_images() %} +
+ {% for image_url in review.get_images() %} + 评价图片 + {% endfor %} +
+ {% endif %} + + +
+ + 评价时间:{{ review.created_at.strftime('%Y-%m-%d %H:%M') }} + {% if review.is_anonymous %} + | 匿名评价 + {% endif %} + +
+ +
+
+
+
+
+ {% endfor %} + + + {% if reviews.pages > 1 %} + + {% endif %} + + {% else %} +
+ +
暂无评价
+

您还没有发表过商品评价

+ + 去购物 + +
+ {% endif %} +
+
+
+
+ + + +{% endblock %} + +{% block scripts %} + +{% endblock %} - +{% extends "base.html" %} +{% block title %}评价商品 - 太白购物商城{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+ + +
+
+
+
+
评价商品
+
+
+ +
+
+
+ {{ order_item.product_name }} +
+
+
{{ order_item.product_name }}
+ {% if order_item.spec_combination %} +

{{ order_item.spec_combination }}

+ {% endif %} +

+ 单价:¥{{ "%.2f"|format(order_item.price) }} × {{ order_item.quantity }} +

+
+
+
+ + + + + + + +
+ +
+
+ + + + + +
+ 请选择评分 +
+ +
+ + +
+ + +
字数限制:500字以内
+
+ + +
+ +
+
+ +

点击或拖拽上传图片

+ 支持 JPG、PNG、GIF 格式,最大5MB +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+ 取消 + +
+ +
+
+
+
+
+{% endblock %} + +{% block scripts %} + {% endblock %} @@ -8227,13 +17671,17 @@ function changeSort(sortType) { 🔸============================================================================== 📄 文件: app/templates/user/address_form.html -📊 大小: 14730 bytes (14.38 KB) -🕒 修改时间: 2025-07-04 03:22:24 +📊 大小: 7575 bytes (7.40 KB) +🕒 修改时间: 2025-07-04 04:02:42 🔸============================================================================== {% extends "base.html" %} {% block title %}{% if action == 'add' %}添加地址{% else %}编辑地址{% endif %} - 太白购物商城{% endblock %} +{% block head %} + +{% endblock %} + {% block content %}
@@ -8375,248 +17823,23 @@ function changeSort(sortType) { {% block scripts %} - - + {% endblock %} 🔸============================================================================== 📄 文件: app/templates/user/addresses.html -📊 大小: 7551 bytes (7.37 KB) -🕒 修改时间: 2025-07-04 02:41:21 +📊 大小: 6585 bytes (6.43 KB) +🕒 修改时间: 2025-07-09 04:52:12 🔸============================================================================== {% extends "base.html" %} {% block title %}收货地址 - 太白购物商城{% endblock %} +{% block head %} + +{% endblock %} + {% block content %}
@@ -8635,10 +17858,13 @@ function setInitialValues(province, city, district) { 收货地址 - + + 我的评价 + + 我的收藏 - + 浏览历史
@@ -8711,7 +17937,7 @@ function setInitialValues(province, city, district) { {% endfor %}
{% else %} -
+
暂无收货地址

请添加您的收货地址,方便下单购物

@@ -8727,180 +17953,21 @@ function setInitialValues(province, city, district) { {% endblock %} {% block scripts %} - + {% endblock %} 🔸============================================================================== -📄 文件: app/templates/user/login.html -📊 大小: 2349 bytes (2.29 KB) -🕒 修改时间: 2025-07-03 03:01:24 +📄 文件: app/templates/user/favorites.html +📊 大小: 11617 bytes (11.34 KB) +🕒 修改时间: 2025-07-09 04:52:12 🔸============================================================================== {% extends "base.html" %} - -{% block title %}用户登录 - 太白购物商城{% endblock %} - -{% block content %} -
-
-
-
-

用户登录

-
-
-
- {{ form.hidden_tag() }} - -
- {{ form.username.label(class="form-label") }} - {{ form.username(class="form-control" + (" is-invalid" if form.username.errors else "")) }} - {% if form.username.errors %} -
- {% for error in form.username.errors %} - {{ error }} - {% endfor %} -
- {% endif %} -
- -
- {{ form.password.label(class="form-label") }} - {{ form.password(class="form-control" + (" is-invalid" if form.password.errors else "")) }} - {% if form.password.errors %} -
- {% for error in form.password.errors %} - {{ error }} - {% endfor %} -
- {% endif %} -
- -
- {{ form.remember_me(class="form-check-input") }} - {{ form.remember_me.label(class="form-check-label") }} -
- -
- {{ form.submit(class="btn btn-primary") }} -
-
- -
-
-

还没有账户? 立即注册

-
-
-
-
-
-{% endblock %} - - -🔸============================================================================== -📄 文件: app/templates/user/orders.html -📊 大小: 14280 bytes (13.95 KB) -🕒 修改时间: 2025-07-04 02:43:57 -🔸============================================================================== - -{% extends "base.html" %} -{% block title %}我的订单 - 太白购物商城{% endblock %} +{% block title %}我的收藏 - 太白购物商城{% endblock %} {% block head %} - + {% endblock %} {% block content %} @@ -8915,12 +17982,555 @@ function deleteAddress(addressId) { 基本信息 - + 我的订单 收货地址 + + 我的评价 + + + 我的收藏 + + + 浏览历史 + +
+
+
+ + +
+
+
+
我的收藏
+
+ 共 {{ total_count }} 件商品 + {% if total_count > 0 %} + + + {% endif %} +
+
+ +
+ {% if favorites.items %} +
+ {% for favorite in favorites.items %} +
+
+
+
+
+ +
+ + + +
+
+ + {{ favorite.product.name }} + +
+ +
+ + ¥{{ "%.2f"|format(favorite.product.price) }} + + + 销量 {{ favorite.product.sales_count }} + +
+ +
+ + {{ favorite.created_at.strftime('%Y-%m-%d') }} + + {% if favorite.product.status == 1 %} + 有货 + {% else %} + 下架 + {% endif %} +
+ +
+
+ {% if favorite.product.status == 1 %} + + {% endif %} + +
+
+
+
+
+
+
+ {% endfor %} +
+ + + {% if favorites.pages > 1 %} + + {% endif %} + + {% else %} + +
+
+ +

还没有收藏任何商品

+

去逛逛,收藏心仪的商品吧~

+ + 去首页逛逛 + +
+
+ {% endif %} +
+
+
+ + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} + + +🔸============================================================================== +📄 文件: app/templates/user/history.html +📊 大小: 12588 bytes (12.29 KB) +🕒 修改时间: 2025-07-09 04:52:12 +🔸============================================================================== + +{% extends "base.html" %} +{% block title %}浏览历史 - 太白购物商城{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+ + + + +
+
+
+
浏览历史
+
+ 共 {{ total_count }} 件商品 + {% if total_count > 0 %} + + + + {% endif %} +
+
+ +
+ {% if history.items %} +
+ {% for item in history.items %} +
+
+
+
+
+ +
+ + + +
+
+ + {{ item.product.name }} + +
+ +
+ + ¥{{ "%.2f"|format(item.product.price) }} + + + 销量 {{ item.product.sales_count }} + +
+ +
+ + {{ item.product.category.name if item.product.category else "未分类" }} + +
+ +
+ + {{ item.viewed_at.strftime('%Y-%m-%d %H:%M') }} + + {% if item.product.status == 1 %} + 有货 + {% else %} + 下架 + {% endif %} +
+ +
+
+ {% if item.product.status == 1 %} + + + {% endif %} + +
+
+
+
+
+
+
+ {% endfor %} +
+ + + {% if history.pages > 1 %} + + {% endif %} + + {% else %} + +
+
+ +

还没有浏览任何商品

+

去逛逛,看看有什么好商品~

+ + 去首页逛逛 + +
+
+ {% endif %} +
+
+
+
+ + + +{% endblock %} + +{% block scripts %} + +{% endblock %} + + +🔸============================================================================== +📄 文件: app/templates/user/login.html +📊 大小: 2715 bytes (2.65 KB) +🕒 修改时间: 2025-07-04 03:55:20 +🔸============================================================================== + +{% extends "base.html" %} + +{% block title %}用户登录 - 太白购物商城{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} +
+
+
+
+
+

用户登录

+
+
+
+ {{ form.hidden_tag() }} + +
+ {{ form.username.label(class="form-label") }} + {{ form.username(class="form-control" + (" is-invalid" if form.username.errors else "")) }} + {% if form.username.errors %} +
+ {% for error in form.username.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ +
+ {{ form.password.label(class="form-label") }} + {{ form.password(class="form-control" + (" is-invalid" if form.password.errors else "")) }} + {% if form.password.errors %} +
+ {% for error in form.password.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ +
+ {{ form.remember_me(class="form-check-input") }} + {{ form.remember_me.label(class="form-check-label") }} +
+ +
+ {{ form.submit(class="btn btn-primary") }} +
+
+ +
+
+

还没有账户? 立即注册

+
+
+
+
+
+
+{% endblock %} + + +🔸============================================================================== +📄 文件: app/templates/user/orders.html +📊 大小: 13975 bytes (13.65 KB) +🕒 修改时间: 2025-07-08 17:35:33 +🔸============================================================================== + +{% extends "base.html" %} +{% block title %}我的订单 - 太白购物商城{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+ +
+
+
+
个人中心
+
+
+ + 基本信息 + + + 我的订单 + + + 收货地址 + + + 我的评价 + 我的收藏 @@ -9031,7 +18641,7 @@ function deleteAddress(addressId) {