diff --git a/app/__init__.py b/app/__init__.py index 1a8e226..cc1dc88 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,95 +1,41 @@ -""" -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 diff --git a/app/models/__init__.py b/app/models/__init__.py index c26a7ad..f6b0c75 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -8,11 +8,13 @@ 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' ] diff --git a/app/models/browse_history.py b/app/models/browse_history.py new file mode 100644 index 0000000..ca4e37b --- /dev/null +++ b/app/models/browse_history.py @@ -0,0 +1,111 @@ +""" +浏览历史模型 +""" +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'' diff --git a/app/models/favorite.py b/app/models/favorite.py new file mode 100644 index 0000000..71b0e33 --- /dev/null +++ b/app/models/favorite.py @@ -0,0 +1,102 @@ +""" +用户收藏模型 +""" +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'' diff --git a/app/static/css/admin_logs.css b/app/static/css/admin_logs.css new file mode 100644 index 0000000..cd790e7 --- /dev/null +++ b/app/static/css/admin_logs.css @@ -0,0 +1,295 @@ +/* 操作日志页面样式 */ +.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); } +} diff --git a/app/static/css/admin_orders.css b/app/static/css/admin_orders.css new file mode 100644 index 0000000..d75d38d --- /dev/null +++ b/app/static/css/admin_orders.css @@ -0,0 +1,202 @@ +/* 订单管理样式 */ +.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; + } +} diff --git a/app/static/css/admin_users.css b/app/static/css/admin_users.css new file mode 100644 index 0000000..e81a922 --- /dev/null +++ b/app/static/css/admin_users.css @@ -0,0 +1,351 @@ +/* 用户管理页面样式 */ +.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); } +} diff --git a/app/static/css/checkout.css b/app/static/css/checkout.css index 2179dd1..aefe9d2 100644 --- a/app/static/css/checkout.css +++ b/app/static/css/checkout.css @@ -1,26 +1,27 @@ /* 订单结算页面样式 */ .checkout-section { - margin-bottom: 20px; + margin-bottom: 1.5rem; } .address-card { cursor: pointer; transition: all 0.3s ease; + border: 2px solid #e9ecef; } .address-card:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); + border-color: #007bff; + box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1); } .address-card.selected { border-color: #007bff; - background-color: #f8f9ff; + background-color: #e7f3ff; } .product-item { - border-bottom: 1px solid #eee; - padding: 15px 0; + padding: 1rem 0; + border-bottom: 1px solid #e9ecef; } .product-item:last-child { @@ -28,19 +29,136 @@ } .order-summary { - background-color: #f8f9fa; - border-radius: 8px; - padding: 20px; + background: #f8f9fa; + padding: 1rem; + border-radius: 0.5rem; } .price-row { display: flex; justify-content: space-between; - margin-bottom: 10px; + margin-bottom: 0.5rem; } -.total-price { - font-size: 1.2em; +.price-row.total-price { + font-size: 1.1rem; font-weight: bold; - color: #e74c3c; + 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; } diff --git a/app/static/css/favorites.css b/app/static/css/favorites.css new file mode 100644 index 0000000..a6c0b2b --- /dev/null +++ b/app/static/css/favorites.css @@ -0,0 +1,118 @@ +/* 收藏页面样式 */ +.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; + } +} diff --git a/app/static/css/history.css b/app/static/css/history.css new file mode 100644 index 0000000..f48d3ce --- /dev/null +++ b/app/static/css/history.css @@ -0,0 +1,125 @@ +/* 浏览历史页面样式 */ +.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; + } +} diff --git a/app/static/css/index.css b/app/static/css/index.css index 78a4ea9..86a431d 100644 --- a/app/static/css/index.css +++ b/app/static/css/index.css @@ -17,9 +17,34 @@ box-shadow: 0 4px 10px rgba(0,0,0,0.1); } +/* 欢迎横幅样式 */ /* 欢迎横幅样式 */ .jumbotron { - background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); + 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; } /* 商品图片样式 */ diff --git a/app/static/css/order_detail.css b/app/static/css/order_detail.css index 04c8642..e0422c8 100644 --- a/app/static/css/order_detail.css +++ b/app/static/css/order_detail.css @@ -1,4 +1,26 @@ /* 订单详情页面样式 */ + +/* 首先,重置所有可能影响的样式 */ +.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; @@ -60,13 +82,6 @@ border-bottom: none; } -.product-image { - width: 80px; - height: 80px; - object-fit: cover; - border-radius: 4px; -} - .info-row { display: flex; justify-content: space-between; @@ -78,3 +93,15 @@ 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; + } +} diff --git a/app/static/css/pay.css b/app/static/css/pay.css index 654bbef..5b49bdf 100644 --- a/app/static/css/pay.css +++ b/app/static/css/pay.css @@ -1,49 +1,134 @@ -/* 订单支付页面样式 */ +/* 支付页面样式 */ .pay-container { max-width: 600px; - margin: 50px auto; + margin: 2rem auto; + padding: 0 1rem; } .order-info { - background-color: #f8f9fa; - border-radius: 8px; - padding: 20px; - margin-bottom: 20px; + background: #f8f9fa; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1.5rem; } .payment-method { - border: 2px solid #dee2e6; - border-radius: 8px; - padding: 20px; - margin-bottom: 15px; + 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: #f8f9ff; + background-color: #f8f9fa; } .payment-method.selected { border-color: #007bff; - background-color: #f8f9ff; + background-color: #e7f3ff; } .qr-code { text-align: center; - padding: 30px; - background-color: #f8f9fa; - border-radius: 8px; -} - -.countdown { - font-size: 1.2em; - color: #e74c3c; - font-weight: bold; + padding: 2rem; + background: #fff; + border: 1px solid #dee2e6; + border-radius: 0.5rem; + margin: 1rem 0; } .payment-status { text-align: center; - padding: 20px; + 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; } diff --git a/app/static/css/product_detail.css b/app/static/css/product_detail.css index 39dee84..c2d84fc 100644 --- a/app/static/css/product_detail.css +++ b/app/static/css/product_detail.css @@ -1,3 +1,5 @@ +/* 商品详情页样式 */ + .product-card { transition: transform 0.2s; } @@ -55,6 +57,108 @@ 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 { @@ -68,4 +172,30 @@ .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; } diff --git a/app/static/css/review.css b/app/static/css/review.css new file mode 100644 index 0000000..dd47132 --- /dev/null +++ b/app/static/css/review.css @@ -0,0 +1,626 @@ +/* 评价功能样式 */ + +/* 评价表单样式 */ +.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); + } +} diff --git a/app/static/js/admin_logs.js b/app/static/js/admin_logs.js new file mode 100644 index 0000000..7707e58 --- /dev/null +++ b/app/static/js/admin_logs.js @@ -0,0 +1,350 @@ +// 操作日志页面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; + } + + // 实现批量操作逻辑 +} diff --git a/app/static/js/admin_orders.js b/app/static/js/admin_orders.js new file mode 100644 index 0000000..ba0b3ec --- /dev/null +++ b/app/static/js/admin_orders.js @@ -0,0 +1,201 @@ +// 订单管理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); +} diff --git a/app/static/js/admin_users.js b/app/static/js/admin_users.js new file mode 100644 index 0000000..bd38ed9 --- /dev/null +++ b/app/static/js/admin_users.js @@ -0,0 +1,370 @@ +// 用户管理页面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); diff --git a/app/static/js/base.js b/app/static/js/base.js index 09e1372..0512632 100644 --- a/app/static/js/base.js +++ b/app/static/js/base.js @@ -3,10 +3,12 @@ // 返回顶部功能 window.addEventListener('scroll', function() { const backToTop = document.getElementById('backToTop'); - if (window.pageYOffset > 300) { - backToTop.style.display = 'block'; - } else { - backToTop.style.display = 'none'; + if (backToTop) { + if (window.pageYOffset > 300) { + backToTop.style.display = 'block'; + } else { + backToTop.style.display = 'none'; + } } }); @@ -20,14 +22,96 @@ function scrollToTop() { // 购物车数量更新 function updateCartBadge(count) { const badge = document.getElementById('cartBadge'); - if (count > 0) { - badge.textContent = count; - badge.style.display = 'inline-block'; - } else { - badge.style.display = 'none'; + 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() { // 当前页面高亮 @@ -41,36 +125,79 @@ document.addEventListener('DOMContentLoaded', function() { }); // 初始化购物车数量 - // TODO: 实现购物车数量获取 + updateCartCount(); }); // 通用AJAX错误处理 -function handleAjaxError(xhr) { +function handleAjaxError(xhr, defaultMessage = '操作失败,请稍后再试') { if (xhr.status === 401) { - alert('请先登录'); - window.location.href = '/auth/login'; + showAlert('请先登录', 'warning'); + setTimeout(() => { + window.location.href = '/auth/login'; + }, 1500); } else if (xhr.status === 403) { - alert('没有权限执行此操作'); + showAlert('没有权限执行此操作', 'error'); + } else if (xhr.status === 404) { + showAlert('请求的资源不存在', 'error'); + } else if (xhr.status >= 500) { + showAlert('服务器错误,请稍后再试', 'error'); } else { - alert('操作失败,请稍后再试'); + showAlert(defaultMessage, 'error'); } } -// 通用成功提示 +// 通用成功提示(保持向后兼容) function showSuccessMessage(message) { - // 创建临时提示框 - const alertDiv = document.createElement('div'); - alertDiv.className = 'alert alert-success alert-dismissible fade show position-fixed success-toast'; - alertDiv.innerHTML = ` - ${message} - - `; - document.body.appendChild(alertDiv); - - // 3秒后自动消失 - setTimeout(() => { - if (alertDiv.parentNode) { - alertDiv.parentNode.removeChild(alertDiv); - } - }, 3000); + 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); + } + } } diff --git a/app/static/js/checkout.js b/app/static/js/checkout.js index e91d97e..da4e686 100644 --- a/app/static/js/checkout.js +++ b/app/static/js/checkout.js @@ -26,15 +26,24 @@ function selectAddress(addressId) { card.classList.remove('selected'); }); - document.querySelector(`[data-address-id="${addressId}"]`).classList.add('selected'); + const selectedCard = document.querySelector(`[data-address-id="${addressId}"]`); + if (selectedCard) { + selectedCard.classList.add('selected'); + } // 更新单选按钮 - document.querySelector(`input[value="${addressId}"]`).checked = true; + const radioButton = document.querySelector(`input[value="${addressId}"]`); + if (radioButton) { + radioButton.checked = true; + } } // 更新运费 function updateShippingFee() { - const shippingMethod = document.querySelector('input[name="shipping_method"]:checked').value; + const shippingMethodElement = document.querySelector('input[name="shipping_method"]:checked'); + if (!shippingMethodElement) return; + + const shippingMethod = shippingMethodElement.value; let fee = 0; switch(shippingMethod) { @@ -48,20 +57,44 @@ function updateShippingFee() { fee = 0; } - document.getElementById('shippingFee').textContent = `¥${fee.toFixed(2)}`; - document.getElementById('totalAmount').textContent = `¥${(subtotal + fee).toFixed(2)}`; + 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 shippingMethod = document.querySelector('input[name="shipping_method"]:checked').value; - const paymentMethod = document.querySelector('input[name="payment_method"]:checked').value; - const remark = document.getElementById('orderRemark').value; + // 获取表单数据 + 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); @@ -82,10 +115,16 @@ function submitOrder() { // 显示加载状态 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: { @@ -93,7 +132,12 @@ function submitOrder() { }, body: JSON.stringify(orderData) }) - .then(response => response.json()) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) .then(data => { if (data.success) { showAlert('订单创建成功!正在跳转到支付页面...', 'success'); @@ -101,13 +145,16 @@ function submitOrder() { window.location.href = `/order/pay/${data.payment_sn}`; }, 1500); } else { - showAlert(data.message, 'error'); + showAlert(data.message || '订单创建失败', 'error'); + // 恢复按钮状态 submitBtn.innerHTML = originalText; submitBtn.disabled = false; } }) .catch(error => { + console.error('提交订单错误:', error); showAlert('提交订单失败,请重试', 'error'); + // 恢复按钮状态 submitBtn.innerHTML = originalText; submitBtn.disabled = false; }); diff --git a/app/static/js/favorites.js b/app/static/js/favorites.js new file mode 100644 index 0000000..d47e4ea --- /dev/null +++ b/app/static/js/favorites.js @@ -0,0 +1,229 @@ +// 收藏页面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); + } +} diff --git a/app/static/js/history.js b/app/static/js/history.js new file mode 100644 index 0000000..de071ba --- /dev/null +++ b/app/static/js/history.js @@ -0,0 +1,278 @@ +// 浏览历史页面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); + } +} diff --git a/app/static/js/order_detail.js b/app/static/js/order_detail.js index a4e161b..39d4d15 100644 --- a/app/static/js/order_detail.js +++ b/app/static/js/order_detail.js @@ -1,4 +1,4 @@ -// 订单详情页面脚本 +// 订单详情页面脚本 - 只处理业务逻辑,不处理样式 // 取消订单 function cancelOrder(orderId) { diff --git a/app/static/js/pay.js b/app/static/js/pay.js index 97cc7cb..a0f8741 100644 --- a/app/static/js/pay.js +++ b/app/static/js/pay.js @@ -16,13 +16,16 @@ window.addEventListener('beforeunload', function() { // 开始倒计时 function startCountdown() { + const countdownElement = document.getElementById('countdown'); + if (!countdownElement) return; + countdownTimer = setInterval(() => { timeLeft--; const minutes = Math.floor(timeLeft / 60); const seconds = timeLeft % 60; - document.getElementById('countdown').textContent = + countdownElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; if (timeLeft <= 0) { @@ -45,6 +48,12 @@ function startPayment() { return; } + // 如果是模拟支付,直接显示控制面板,不需要调用接口 + if (paymentMethod === 'simulate') { + showAlert('请使用下方控制面板完成模拟支付', 'info'); + return; + } + fetch('/payment/process', { method: 'POST', headers: { @@ -55,7 +64,12 @@ function startPayment() { payment_method: paymentMethod }) }) - .then(response => response.json()) + .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') { @@ -64,12 +78,15 @@ function startPayment() { } 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'); + showAlert(data.message || '支付启动失败', 'error'); } }) .catch(error => { + console.error('支付启动错误:', error); showAlert('支付启动失败,请重试', 'error'); }); } @@ -79,6 +96,8 @@ function showQRCode(qrUrl) { const qrArea = document.getElementById('qrCodeArea'); const qrImage = document.getElementById('qrCodeImage'); + if (!qrArea || !qrImage) return; + // 这里应该使用真实的二维码生成库,现在用文本模拟 qrImage.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 => response.json()) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) .then(data => { if (data.success) { - showAlert('模拟支付成功', 'success'); + showAlert('模拟支付失败!', 'warning'); setTimeout(() => { - showPaymentSuccess(); + showPaymentFail(); }, 1000); } else { - showAlert(data.message, 'error'); + showAlert(data.message || '模拟操作失败', 'error'); + button.innerHTML = originalText; + button.disabled = false; } }) .catch(error => { - showAlert('模拟支付失败', 'error'); + console.error('模拟支付失败错误:', error); + showAlert('模拟操作失败', 'error'); + button.innerHTML = originalText; + button.disabled = false; }); - } + }); +} + +// 兼容旧版本的模拟支付函数 +function simulatePayment() { + simulatePaymentSuccess(); } diff --git a/app/static/js/product_detail.js b/app/static/js/product_detail.js index a94a72e..be38f49 100644 --- a/app/static/js/product_detail.js +++ b/app/static/js/product_detail.js @@ -22,8 +22,67 @@ document.addEventListener('DOMContentLoaded', function() { 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'); @@ -231,6 +290,68 @@ function buyNow() { // 收藏商品 function addToFavorites() { - // TODO: 实现收藏功能 - alert('收藏功能开发中...'); + 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; + } + + console.log('收藏商品ID:', productId); // 调试信息 + + 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); + } } diff --git a/app/static/js/review.js b/app/static/js/review.js new file mode 100644 index 0000000..9182326 --- /dev/null +++ b/app/static/js/review.js @@ -0,0 +1,646 @@ +// 评价功能 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; diff --git a/app/templates/admin/base.html b/app/templates/admin/base.html index 2e75765..51e055b 100644 --- a/app/templates/admin/base.html +++ b/app/templates/admin/base.html @@ -45,8 +45,8 @@ - -
- + + @@ -54,7 +47,7 @@
- - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
+ +
+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} {% for category, message in messages %} {% endfor %} -
- {% endif %} - {% endwith %} + {% endif %} + {% endwith %} - -
{% block content %}{% endblock %}
-