源代码续:
{% extends 'base.html' %}
{% block title %}购买商品 | {{ super() }}{% endblock %}
{% block styles %}
{{ super() }}
<style>
.item-summary {
display: flex;
margin-bottom: 20px;
padding: 15px;
border: 1px solid #dee2e6;
border-radius: 8px;
background-color: #f8f9fa;
}
.item-image {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 4px;
margin-right: 15px;
}
.item-details {
flex-grow: 1;
}
.item-price {
font-size: 1.5rem;
font-weight: bold;
color: #ff4136;
}
.order-section {
margin-top: 30px;
}
.order-total {
font-size: 1.2rem;
font-weight: bold;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #dee2e6;
}
</style>
{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h1 class="mb-4">确认订单</h1>
<!-- 商品摘要 -->
<div class="item-summary">
<img src="{{ item.get_main_image_url() }}" class="item-image" alt="{{ item.title }}">
<div class="item-details">
<h5>{{ item.title }}</h5>
<p class="text-muted mb-1">卖家:{{ item.seller.username }}</p>
<p class="text-muted mb-1">
状况:{{ {'brand_new': '全新', 'like_new': '几乎全新', 'slightly_used': '轻微使用痕迹', 'used': '使用过', 'heavily_used': '重度使用'}[item.condition] }}
</p>
<p class="item-price mb-0">¥{{ "%.2f"|format(item.price) }}</p>
</div>
</div>
<!-- 订单表单 -->
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">订单信息</h4>
</div>
<div class="card-body">
<form method="post">
{{ form.hidden_tag() }}
<div class="mb-3">
<label class="form-label">交易地点</label>
<p class="form-control-plaintext">{{ item.location }}</p>
</div>
<div class="mb-3">
{{ form.message.label(class="form-label") }}
{{ form.message(class="form-control", rows=3) }}
{% if form.message.errors %}
<div class="invalid-feedback d-block">
{% for error in form.message.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">可以给卖家留言,如:具体交易时间、地点等</div>
</div>
<div class="order-total d-flex justify-content-between">
<span>订单总价:</span>
<span class="item-price">¥{{ "%.2f"|format(item.price) }}</span>
</div>
<div class="alert alert-info mt-3">
<h5 class="alert-heading"><i class="bi bi-info-circle"></i> 交易提示</h5>
<ul class="mb-0">
<li>请在校园内公共场所进行交易,确保安全</li>
<li>交易前请仔细检查商品</li>
<li>确认收到商品并满意后再确认完成交易</li>
<li>如有问题请及时联系卖家或平台客服</li>
</ul>
</div>
<div class="d-grid gap-2 mt-4">
{{ form.submit(class="btn btn-primary btn-lg") }}
<a href="{{ url_for('item.detail', item_id=item.id) }}" class="btn btn-outline-secondary">取消</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
app\templates\item\category.html
{% extends 'base.html' %}
{% block title %}{{ category.name }} | {{ super() }}{% endblock %}
{% block styles %}
{{ super() }}
<style>
.category-header {
background-color: #f8f9fa;
padding: 30px 0;
margin-bottom: 30px;
border-radius: 8px;
}
.category-icon {
font-size: 2.5rem;
margin-bottom: 15px;
color: #0d6efd;
}
.item-card {
transition: transform 0.3s, box-shadow 0.3s;
height: 100%;
}
.item-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.item-image {
height: 200px;
object-fit: cover;
}
.item-price {
font-size: 1.2rem;
font-weight: bold;
color: #ff4136;
}
.item-original-price {
text-decoration: line-through;
color: #adb5bd;
font-size: 0.9rem;
}
.discount-badge {
position: absolute;
top: 10px;
right: 10px;
background-color: #ff4136;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
}
.new-badge {
position: absolute;
top: 10px;
left: 10px;
background-color: #28a745;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
}
.pagination {
justify-content: center;
margin-top: 2rem;
}
.no-results {
text-align: center;
padding: 3rem 0;
}
.condition-badge {
font-size: 0.8rem;
margin-right: 5px;
}
.sort-options {
margin-bottom: 20px;
}
</style>
{% endblock %}
{% block content %}
<div class="container my-5">
<!-- 分类头部 -->
<div class="category-header text-center">
<div class="category-icon">
<i class="bi {{ category.icon or 'bi-tag' }}"></i>
</div>
<h1 class="mb-2">{{ category.name }}</h1>
<p class="text-muted">{{ category.description or '浏览该分类下的所有商品' }}</p>
</div>
<!-- 排序选项 -->
<div class="d-flex justify-content-between align-items-center sort-options">
<p class="mb-0">共 <strong>{{ pagination.total }}</strong> 个商品</p>
<div class="btn-group">
<a href="{{ url_for('item.category', category_id=category.id, sort='newest') }}" class="btn btn-outline-secondary {{ 'active' if request.args.get('sort') == 'newest' or not request.args.get('sort') }}">最新发布</a>
<a href="{{ url_for('item.category', category_id=category.id, sort='price_asc') }}" class="btn btn-outline-secondary {{ 'active' if request.args.get('sort') == 'price_asc' }}">价格从低到高</a>
<a href="{{ url_for('item.category', category_id=category.id, sort='price_desc') }}" class="btn btn-outline-secondary {{ 'active' if request.args.get('sort') == 'price_desc' }}">价格从高到低</a>
<a href="{{ url_for('item.category', category_id=category.id, sort='popular') }}" class="btn btn-outline-secondary {{ 'active' if request.args.get('sort') == 'popular' }}">最受欢迎</a>
</div>
</div>
<!-- 商品列表 -->
{% if items %}
<div class="row">
{% for item in items %}
<div class="col-md-3 mb-4">
<div class="card item-card">
<div class="position-relative">
{% if item.discount_percentage > 0 %}
<span class="discount-badge">{{ item.discount_percentage }}% OFF</span>
{% endif %}
{% if item.is_new %}
<span class="new-badge">新上架</span>
{% endif %}
<img src="{{ item.get_main_image_url() }}" class="card-img-top item-image" alt="{{ item.title }}">
</div>
<div class="card-body">
<h5 class="card-title text-truncate">{{ item.title }}</h5>
<div class="mb-2">
<span class="badge bg-secondary condition-badge">
{{ {'brand_new': '全新', 'like_new': '几乎全新', 'slightly_used': '轻微使用痕迹', 'used': '使用过', 'heavily_used': '重度使用'}[item.condition] }}
</span>
<small class="text-muted">{{ item.location }}</small>
</div>
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="item-price">¥{{ "%.2f"|format(item.price) }}</span>
{% if item.original_price and item.original_price > item.price %}
<span class="item-original-price">¥{{ "%.2f"|format(item.original_price) }}</span>
{% endif %}
</div>
<small class="text-muted">{{ item.created_at.strftime('%m-%d') }}</small>
</div>
</div>
<div class="card-footer bg-white border-top-0">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="bi bi-eye"></i> {{ item.views }}
</small>
<small class="text-muted">
{{ item.seller.username }}
</small>
</div>
</div>
<a href="{{ url_for('item.detail', item_id=item.id) }}" class="stretched-link"></a>
</div>
</div>
{% endfor %}
</div>
<!-- 分页 -->
{% if pagination.pages > 1 %}
<nav aria-label="Page navigation">
<ul class="pagination">
{% if pagination.has_prev %}
<li class="page-item">
<a class="page-link" href="{{ url_for('item.category', category_id=category.id, page=pagination.prev_num, sort=request.args.get('sort', 'newest')) }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
{% for page in pagination.iter_pages(left_edge=2, left_current=2, right_current=3, right_edge=2) %}
{% if page %}
{% if page == pagination.page %}
<li class="page-item active">
<a class="page-link" href="#">{{ page }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="{{ url_for('item.category', category_id=category.id, page=page, sort=request.args.get('sort', 'newest')) }}">{{ page }}</a>
</li>
{% endif %}
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">...</a>
</li>
{% endif %}
{% endfor %}
{% if pagination.has_next %}
<li class="page-item">
<a class="page-link" href="{{ url_for('item.category', category_id=category.id, page=pagination.next_num, sort=request.args.get('sort', 'newest')) }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="no-results">
<img src="{{ url_for('static', filename='images/no_results.svg') }}" alt="没有找到商品" style="max-width: 200px; margin-bottom: 20px;">
<h3>该分类下暂无商品</h3>
<p class="text-muted">成为第一个在该分类下发布商品的人吧!</p>
<a href="{{ url_for('item.new_item') }}" class="btn btn-primary mt-3">发布商品</a>
</div>
{% endif %}
</div>
{% endblock %}
app\templates\item\detail.html
{% extends 'base.html' %}
{% block title %}{{ item.title }} | {{ super() }}{% endblock %}
{% block styles %}
{{ super() }}
<style>
.item-image {
width: 100%;
height: 400px;
object-fit: cover;
border-radius: 8px;
}
.thumbnail {
width: 80px;
height: 80px;
object-fit: cover;
cursor: pointer;
border: 2px solid transparent;
border-radius: 4px;
margin-right: 10px;
}
.thumbnail.active {
border-color: #0d6efd;
}
.price-tag {
font-size: 1.8rem;
color: #ff4136;
font-weight: bold;
}
.original-price {
text-decoration: line-through;
color: #adb5bd;
font-size: 1.2rem;
}
.discount-badge {
background-color: #ff4136;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.9rem;
margin-left: 10px;
}
.seller-info {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.seller-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
object-fit: cover;
margin-right: 15px;
}
.item-stats {
display: flex;
gap: 15px;
color: #6c757d;
margin-bottom: 20px;
}
.similar-item {
transition: transform 0.3s;
}
.similar-item:hover {
transform: translateY(-5px);
}
</style>
{% endblock %}
{% block content %}
<div class="container my-5">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('main.index') }}">首页</a></li>
<li class="breadcrumb-item"><a href="{{ url_for('item.category', category_id=item.category.id) }}">{{ item.category.name }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ item.title }}</li>
</ol>
</nav>
<div class="row">
<!-- 商品图片 -->
<div class="col-md-6 mb-4">
<div class="position-relative">
{% if item.is_sold %}
<div class="position-absolute top-50 start-50 translate-middle bg-dark bg-opacity-50 text-white p-3 rounded" style="z-index: 10; font-size: 2rem;">
已售出
</div>
{% elif not item.is_active %}
<div class="position-absolute top-50 start-50 translate-middle bg-dark bg-opacity-50 text-white p-3 rounded" style="z-index: 10; font-size: 2rem;">
已下架
</div>
{% endif %}
{% if item.image_list %}
<img id="main-image" src="{{ url_for('static', filename='uploads/items/' + item.image_list[0]) }}" class="item-image" alt="{{ item.title }}">
{% else %}
<img src="{{ url_for('static', filename='images/default_item.jpg') }}" class="item-image" alt="{{ item.title }}">
{% endif %}
</div>
{% if item.image_list and item.image_list|length > 1 %}
<div class="d-flex mt-3 overflow-auto">
{% for image in item.image_list %}
<img src="{{ url_for('static', filename='uploads/items/' + image) }}"
class="thumbnail {% if loop.first %}active{% endif %}"
alt="缩略图 {{ loop.index }}"
onclick="changeMainImage('{{ url_for('static', filename='uploads/items/' + image) }}', this)">
{% endfor %}
</div>
{% endif %}
</div>
<!-- 商品信息 -->
<div class="col-md-6">
<h1 class="mb-3">{{ item.title }}</h1>
<div class="item-stats">
<span><i class="bi bi-eye"></i> {{ item.views }} 浏览</span>
<span><i class="bi bi-calendar3"></i> {{ item.created_at.strftime('%Y-%m-%d') }}</span>
<span><i class="bi bi-geo-alt"></i> {{ item.location }}</span>
</div>
<div class="mb-4">
<span class="price-tag">¥{{ "%.2f"|format(item.price) }}</span>
{% if item.original_price and item.original_price > item.price %}
<span class="original-price ms-2">¥{{ "%.2f"|format(item.original_price) }}</span>
<span class="discount-badge">{{ item.discount_percentage }}% OFF</span>
{% endif %}
</div>
<div class="mb-4">
<span class="badge bg-secondary">{{ {'brand_new': '全新', 'like_new': '几乎全新', 'slightly_used': '轻微使用痕迹', 'used': '使用过', 'heavily_used': '重度使用'}[item.condition] }}</span>
{% if item.is_new %}
<span class="badge bg-success ms-2">新上架</span>
{% endif %}
</div>
<div class="seller-info">
<img src="{{ url_for('static', filename='uploads/avatars/' + item.seller.avatar) if item.seller.avatar else url_for('static', filename='images/default_avatar.jpg') }}"
alt="{{ item.seller.username }}"
class="seller-avatar">
<div>
<h5 class="mb-0">{{ item.seller.username }}</h5>
<small class="text-muted">{{ item.seller.dormitory or '未填写宿舍信息' }}</small>
</div>
</div>
<div class="d-grid gap-2 mb-4">
{% if current_user.is_authenticated and not item.is_sold and item.is_active and current_user.id != item.seller_id %}
<a href="{{ url_for('item.buy', item_id=item.id) }}" class="btn btn-primary btn-lg">立即购买</a>
{% endif %}
{% if current_user.is_authenticated %}
<button id="favorite-btn" class="btn btn-outline-danger" onclick="toggleFavorite()">
<i class="bi {% if is_favorite %}bi-heart-fill{% else %}bi-heart{% endif %}"></i>
{% if is_favorite %}已收藏{% else %}收藏{% endif %}
</button>
{% endif %}
{% if current_user.is_authenticated and current_user.id == item.seller_id %}
<div class="btn-group">
<a href="{{ url_for('item.edit_item', item_id=item.id) }}" class="btn btn-outline-secondary">编辑商品</a>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li>
<form action="{{ url_for('item.toggle_status', item_id=item.id) }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="dropdown-item">
{% if item.is_active %}下架商品{% else %}上架商品{% endif %}
</button>
</form>
</li>
<li>
<button type="button" class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
删除商品
</button>
</li>
</ul>
</div>
{% endif %}
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">商品描述</h5>
</div>
<div class="card-body">
<p class="card-text" style="white-space: pre-line;">{{ item.description }}</p>
</div>
</div>
</div>
</div>
<!-- 相似商品 -->
{% if similar_items %}
<div class="mt-5">
<h3 class="mb-4">相似商品</h3>
<div class="row">
{% for similar_item in similar_items %}
<div class="col-md-3 mb-4">
<div class="card h-100 similar-item">
<img src="{{ similar_item.get_main_image_url() }}" class="card-img-top" alt="{{ similar_item.title }}" style="height: 200px; object-fit: cover;">
<div class="card-body">
<h5 class="card-title text-truncate">{{ similar_item.title }}</h5>
<p class="card-text text-danger fw-bold">¥{{ "%.2f"|format(similar_item.price) }}</p>
<a href="{{ url_for('item.detail', item_id=similar_item.id) }}" class="stretched-link"></a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- 卖家其他商品 -->
{% if seller_other_items %}
<div class="mt-5">
<h3 class="mb-4">卖家其他商品</h3>
<div class="row">
{% for other_item in seller_other_items %}
<div class="col-md-3 mb-4">
<div class="card h-100 similar-item">
<img src="{{ other_item.get_main_image_url() }}" class="card-img-top" alt="{{ other_item.title }}" style="height: 200px; object-fit: cover;">
<div class="card-body">
<h5 class="card-title text-truncate">{{ other_item.title }}</h5>
<p class="card-text text-danger fw-bold">¥{{ "%.2f"|format(other_item.price) }}</p>
<a href="{{ url_for('item.detail', item_id=other_item.id) }}" class="stretched-link"></a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
<!-- 删除确认模态框 -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">确认删除</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
您确定要删除这个商品吗?此操作不可撤销。
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<form action="{{ url_for('item.delete_item', item_id=item.id) }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-danger">确认删除</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
function changeMainImage(src, thumbnail) {
document.getElementById('main-image').src = src;
// 更新缩略图选中状态
document.querySelectorAll('.thumbnail').forEach(thumb => {
thumb.classList.remove('active');
});
thumbnail.classList.add('active');
}
function toggleFavorite() {
fetch('{{ url_for("item.toggle_favorite", item_id=item.id) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': '{{ csrf_token() }}'
}
})
.then(response => response.json())
.then(data => {
const favoriteBtn = document.getElementById('favorite-btn');
const icon = favoriteBtn.querySelector('i');
if (data.is_favorite) {
icon.classList.remove('bi-heart');
icon.classList.add('bi-heart-fill');
favoriteBtn.innerHTML = '<i class="bi bi-heart-fill"></i> 已收藏';
} else {
icon.classList.remove('bi-heart-fill');
icon.classList.add('bi-heart');
favoriteBtn.innerHTML = '<i class="bi bi-heart"></i> 收藏';
}
// 显示提示消息
const toast = new bootstrap.Toast(document.createElement('div'));
const toastContainer = document.createElement('div');
toastContainer.className = 'toast-container position-fixed bottom-0 end-0 p-3';
toastContainer.innerHTML = `
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">提示</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
${data.message}
</div>
</div>
`;
document.body.appendChild(toastContainer);
const toastEl = toastContainer.querySelector('.toast');
const toast = new bootstrap.Toast(toastEl);
toast.show();
// 自动移除toast
setTimeout(() => {
toastContainer.remove();
}, 3000);
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
{% endblock %}
app\templates\item\edit.html
{% extends 'base.html' %}
{% block title %}编辑商品 | {{ super() }}{% endblock %}
{% block styles %}
{{ super() }}
<style>
.image-preview-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.image-preview {
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 4px;
border: 1px solid #dee2e6;
}
.existing-image-container {
position: relative;
width: 150px;
margin-bottom: 10px;
}
.existing-image {
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 4px;
border: 1px solid #dee2e6;
}
.delete-image-btn {
position: absolute;
top: 5px;
right: 5px;
background-color: rgba(255, 0, 0, 0.7);
color: white;
border: none;
border-radius: 50%;
width: 25px;
height: 25px;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.form-label.required::after {
content: " *";
color: red;
}
.price-input {
position: relative;
}
.price-input::before {
content: "¥";
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
z-index: 10;
}
.price-input input {
padding-left: 25px;
}
</style>
{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">编辑商品</h3>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.title.label(class="form-label required") }}
{{ form.title(class="form-control") }}
{% if form.title.errors %}
<div class="invalid-feedback d-block">
{% for error in form.title.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.category.label(class="form-label required") }}
{{ form.category(class="form-select") }}
{% if form.category.errors %}
<div class="invalid-feedback d-block">
{% for error in form.category.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
{{ form.condition.label(class="form-label required") }}
{{ form.condition(class="form-select") }}
{% if form.condition.errors %}
<div class="invalid-feedback d-block">
{% for error in form.condition.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.price.label(class="form-label required") }}
<div class="price-input">
{{ form.price(class="form-control") }}
</div>
{% if form.price.errors %}
<div class="invalid-feedback d-block">
{% for error in form.price.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
{{ form.original_price.label(class="form-label") }}
<div class="price-input">
{{ form.original_price(class="form-control") }}
</div>
{% if form.original_price.errors %}
<div class="invalid-feedback d-block">
{% for error in form.original_price.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">如果有原价,可以显示折扣信息</div>
</div>
</div>
<div class="mb-3">
{{ form.location.label(class="form-label required") }}
{{ form.location(class="form-control") }}
{% if form.location.errors %}
<div class="invalid-feedback d-block">
{% for error in form.location.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">填写校内交易地点,如:一食堂门口、图书馆等</div>
</div>
<div class="mb-3">
{{ form.description.label(class="form-label required") }}
{{ form.description(class="form-control", rows=5) }}
{% if form.description.errors %}
<div class="invalid-feedback d-block">
{% for error in form.description.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">详细描述商品的情况,如品牌、型号、使用时长、瑕疵等</div>
</div>
<!-- 现有图片展示 -->
{% if item.image_list %}
<div class="mb-3">
<label class="form-label">现有图片</label>
<div class="d-flex flex-wrap gap-3">
{% for image in item.image_list %}
<div class="existing-image-container" id="image-container-{{ loop.index }}">
<img src="{{ url_for('static', filename='uploads/items/' + image) }}" class="existing-image" alt="商品图片 {{ loop.index }}">
<button type="button" class="delete-image-btn" onclick="deleteImage('{{ image }}', {{ loop.index }})">
<i class="bi bi-x"></i>
</button>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<div class="mb-3">
{{ form.images.label(class="form-label") }}
{{ form.images(class="form-control", id="imageInput", onchange="previewImages(event)") }}
{% if form.images.errors %}
<div class="invalid-feedback d-block">
{% for error in form.images.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">上传新图片,最多总共5张图片</div>
<div id="imagePreviewContainer" class="image-preview-container"></div>
</div>
<div class="d-grid gap-2 mt-4">
{{ form.submit(class="btn btn-primary btn-lg", value="保存修改") }}
<a href="{{ url_for('item.detail', item_id=item.id) }}" class="btn btn-outline-secondary">取消</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
function previewImages(event) {
const container = document.getElementById('imagePreviewContainer');
container.innerHTML = '';
const files = event.target.files;
const maxFiles = 5;
const existingImageCount = {{ item.image_list|length }};
const remainingSlots = maxFiles - existingImageCount;
const previewCount = Math.min(files.length, remainingSlots);
for (let i = 0; i < previewCount; i++) {
const file = files[i];
if (!file.type.match('image.*')) {
continue;
}
const reader = new FileReader();
reader.onload = function(e) {
const img = document.createElement('img');
img.classList.add('image-preview');
img.src = e.target.result;
container.appendChild(img);
}
reader.readAsDataURL(file);
}
if (files.length > remainingSlots) {
const message = document.createElement('div');
message.classList.add('alert', 'alert-warning', 'mt-2');
message.textContent = `您选择了 ${files.length} 张图片,但只能上传前 ${remainingSlots} 张(总共最多5张)。`;
container.appendChild(message);
}
}
function deleteImage(filename, index) {
if (confirm('确定要删除这张图片吗?')) {
fetch(`{{ url_for('item.api_delete_image', item_id=item.id, filename='') }}${filename}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 移除图片容器
document.getElementById(`image-container-${index}`).remove();
// 显示成功消息
const toast = new bootstrap.Toast(document.createElement('div'));
const toastContainer = document.createElement('div');
toastContainer.className = 'toast-container position-fixed bottom-0 end-0 p-3';
toastContainer.innerHTML = `
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">提示</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
图片已成功删除
</div>
</div>
`;
document.body.appendChild(toastContainer);
const toastEl = toastContainer.querySelector('.toast');
const toast = new bootstrap.Toast(toastEl);
toast.show();
// 自动移除toast
setTimeout(() => {
toastContainer.remove();
}, 3000);
} else {
alert('删除图片失败:' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('删除图片时发生错误');
});
}
}
</script>
{% endblock %}
app\templates\item\new.html
{% extends 'base.html' %}
{% block title %}发布商品 | {{ super() }}{% endblock %}
{% block styles %}
{{ super() }}
<style>
.image-preview-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.image-preview {
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 4px;
border: 1px solid #dee2e6;
}
.form-label.required::after {
content: " *";
color: red;
}
.price-input {
position: relative;
}
.price-input::before {
content: "¥";
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
z-index: 10;
}
.price-input input {
padding-left: 25px;
}
</style>
{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">发布商品</h3>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.title.label(class="form-label required") }}
{{ form.title(class="form-control") }}
{% if form.title.errors %}
<div class="invalid-feedback d-block">
{% for error in form.title.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.category.label(class="form-label required") }}
{{ form.category(class="form-select") }}
{% if form.category.errors %}
<div class="invalid-feedback d-block">
{% for error in form.category.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
{{ form.condition.label(class="form-label required") }}
{{ form.condition(class="form-select") }}
{% if form.condition.errors %}
<div class="invalid-feedback d-block">
{% for error in form.condition.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.price.label(class="form-label required") }}
<div class="price-input">
{{ form.price(class="form-control") }}
</div>
{% if form.price.errors %}
<div class="invalid-feedback d-block">
{% for error in form.price.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
{{ form.original_price.label(class="form-label") }}
<div class="price-input">
{{ form.original_price(class="form-control") }}
</div>
{% if form.original_price.errors %}
<div class="invalid-feedback d-block">
{% for error in form.original_price.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">如果有原价,可以显示折扣信息</div>
</div>
</div>
<div class="mb-3">
{{ form.location.label(class="form-label required") }}
{{ form.location(class="form-control") }}
{% if form.location.errors %}
<div class="invalid-feedback d-block">
{% for error in form.location.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">填写校内交易地点,如:一食堂门口、图书馆等</div>
</div>
<div class="mb-3">
{{ form.description.label(class="form-label required") }}
{{ form.description(class="form-control", rows=5) }}
{% if form.description.errors %}
<div class="invalid-feedback d-block">
{% for error in form.description.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">详细描述商品的情况,如品牌、型号、使用时长、瑕疵等</div>
</div>
<div class="mb-3">
{{ form.images.label(class="form-label") }}
{{ form.images(class="form-control", id="imageInput", onchange="previewImages(event)") }}
{% if form.images.errors %}
<div class="invalid-feedback d-block">
{% for error in form.images.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="form-text">最多上传5张图片,第一张将作为主图</div>
<div id="imagePreviewContainer" class="image-preview-container"></div>
</div>
<div class="d-grid gap-2 mt-4">
{{ form.submit(class="btn btn-primary btn-lg") }}
<a href="{{ url_for('main.index') }}" class="btn btn-outline-secondary">取消</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
function previewImages(event) {
const container = document.getElementById('imagePreviewContainer');
container.innerHTML = '';
const files = event.target.files;
const maxFiles = 5;
const previewCount = Math.min(files.length, maxFiles);
for (let i = 0; i < previewCount; i++) {
const file = files[i];
if (!file.type.match('image.*')) {
continue;
}
const reader = new FileReader();
reader.onload = function(e) {
const img = document.createElement('img');
img.classList.add('image-preview');
img.src = e.target.result;
container.appendChild(img);
}
reader.readAsDataURL(file);
}
if (files.length > maxFiles) {
const message = document.createElement('div');
message.classList.add('alert', 'alert-warning', 'mt-2');
message.textContent = `您选择了 ${files.length} 张图片,但只能上传前 ${maxFiles} 张。`;
container.appendChild(message);
}
}
</script>
{% endblock %}
app\templates\item\search.html
{% extends 'base.html' %}
{% block title %}搜索商品 | {{ super() }}{% endblock %}
{% block styles %}
{{ super() }}
<style>
.filter-card {
position: sticky;
top: 20px;
}
.item-card {
transition: transform 0.3s, box-shadow 0.3s;
height: 100%;
}
.item-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.item-image {
height: 200px;
object-fit: cover;
}
.item-price {
font-size: 1.2rem;
font-weight: bold;
color: #ff4136;
}
.item-original-price {
text-decoration: line-through;
color: #adb5bd;
font-size: 0.9rem;
}
.discount-badge {
position: absolute;
top: 10px;
right: 10px;
background-color: #ff4136;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
}
.new-badge {
position: absolute;
top: 10px;
left: 10px;
background-color: #28a745;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
}
.pagination {
justify-content: center;
margin-top: 2rem;
}
.no-results {
text-align: center;
padding: 3rem 0;
}
.condition-badge {
font-size: 0.8rem;
margin-right: 5px;
}
</style>
{% endblock %}
{% block content %}
<div class="container my-5">
<h1 class="mb-4">搜索商品</h1>
<div class="row">
<!-- 筛选栏 -->
<div class="col-md-3 mb-4">
<div class="card filter-card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">筛选条件</h5>
</div>
<div class="card-body">
<form method="get" action="{{ url_for('item.search') }}">
<div class="mb-3">
{{ form.keyword.label(class="form-label") }}
{{ form.keyword(class="form-control") }}
</div>
<div class="mb-3">
{{ form.category.label(class="form-label") }}
{{ form.category(class="form-select") }}
</div>
<div class="mb-3">
<label class="form-label">价格范围</label>
<div class="row">
<div class="col-6">
{{ form.min_price(class="form-control", placeholder="最低价") }}
</div>
<div class="col-6">
{{ form.max_price(class="form-control", placeholder="最高价") }}
</div>
</div>
</div>
<div class="mb-3">
{{ form.condition.label(class="form-label") }}
{{ form.condition(class="form-select") }}
</div>
<div class="mb-3">
{{ form.sort.label(class="form-label") }}
{{ form.sort(class="form-select") }}
</div>
<div class="d-grid">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
</div>
</div>
<!-- 商品列表 -->
<div class="col-md-9">
{% if items %}
<div class="d-flex justify-content-between align-items-center mb-4">
<p class="mb-0">共找到 <strong>{{ pagination.total }}</strong> 个商品</p>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
排序方式
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item {% if form.sort.data == 'newest' %}active{% endif %}" href="{{ url_for('item.search', **request.args|dict_update(sort='newest')) }}">最新发布</a></li>
<li><a class="dropdown-item {% if form.sort.data == 'price_asc' %}active{% endif %}" href="{{ url_for('item.search', **request.args|dict_update(sort='price_asc')) }}">价格从低到高</a></li>
<li><a class="dropdown-item {% if form.sort.data == 'price_desc' %}active{% endif %}" href="{{ url_for('item.search', **request.args|dict_update(sort='price_desc')) }}">价格从高到低</a></li>
<li><a class="dropdown-item {% if form.sort.data == 'popular' %}active{% endif %}" href="{{ url_for('item.search', **request.args|dict_update(sort='popular')) }}">最受欢迎</a></li>
</ul>
</div>
</div>
<div class="row">
{% for item in items %}
<div class="col-md-4 mb-4">
<div class="card item-card">
<div class="position-relative">
{% if item.discount_percentage > 0 %}
<span class="discount-badge">{{ item.discount_percentage }}% OFF</span>
{% endif %}
{% if item.is_new %}
<span class="new-badge">新上架</span>
{% endif %}
<img src="{{ item.get_main_image_url() }}" class="card-img-top item-image" alt="{{ item.title }}">
</div>
<div class="card-body">
<h5 class="card-title text-truncate">{{ item.title }}</h5>
<div class="mb-2">
<span class="badge bg-secondary condition-badge">
{{ {'brand_new': '全新', 'like_new': '几乎全新', 'slightly_used': '轻微使用痕迹', 'used': '使用过', 'heavily_used': '重度使用'}[item.condition] }}
</span>
<small class="text-muted">{{ item.location }}</small>
</div>
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="item-price">¥{{ "%.2f"|format(item.price) }}</span>
{% if item.original_price and item.original_price > item.price %}
<span class="item-original-price">¥{{ "%.2f"|format(item.original_price) }}</span>
{% endif %}
</div>
<small class="text-muted">{{ item.created_at.strftime('%m-%d') }}</small>
</div>
</div>
<div class="card-footer bg-white border-top-0">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="bi bi-eye"></i> {{ item.views }}
</small>
<small class="text-muted">
{{ item.seller.username }}
</small>
</div>
</div>
<a href="{{ url_for('item.detail', item_id=item.id) }}" class="stretched-link"></a>
</div>
</div>
{% endfor %}
</div>
<!-- 分页 -->
{% if pagination.pages > 1 %}
<nav aria-label="Page navigation">
<ul class="pagination">
{% if pagination.has_prev %}
<li class="page-item">
<a class="page-link" href="{{ url_for('item.search', **request.args|dict_update(page=pagination.prev_num)) }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
{% for page in pagination.iter_pages(left_edge=2, left_current=2, right_current=3, right_edge=2) %}
{% if page %}
{% if page == pagination.page %}
<li class="page-item active">
<a class="page-link" href="#">{{ page }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="{{ url_for('item.search', **request.args|dict_update(page=page)) }}">{{ page }}</a>
</li>
{% endif %}
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">...</a>
</li>
{% endif %}
{% endfor %}
{% if pagination.has_next %}
<li class="page-item">
<a class="page-link" href="{{ url_for('item.search', **request.args|dict_update(page=pagination.next_num)) }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="no-results">
<img src="{{ url_for('static', filename='images/no_results.svg') }}" alt="没有找到商品" style="max-width: 200px; margin-bottom: 20px;">
<h3>没有找到符合条件的商品</h3>
<p class="text-muted">尝试修改筛选条件或者搜索其他关键词</p>
<a href="{{ url_for('main.index') }}" class="btn btn-primary mt-3">返回首页</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
// 辅助函数,用于更新URL参数
if (!window.Jinja2) {
window.Jinja2 = {};
}
window.Jinja2.dict_update = function(dict, key, value) {
dict[key] = value;
return dict;
};
</script>
{% endblock %}
app\templates\main\about.html
{% extends "base.html" %}
{% block title %}关于我们 - {{ super() }}{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card shadow mb-4">
<div class="card-header bg-primary text-white">
<h4 class="mb-0"><i class="fas fa-info-circle me-2"></i>关于我们</h4>
</div>
<div class="card-body">
<div class="text-center mb-4">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="校园二手物品交易平台" class="img-fluid" style="max-width: 200px;">
<h2 class="mt-3">校园二手物品交易平台</h2>
<p class="lead text-muted">让闲置物品流动起来,创造更多价值</p>
</div>
<div class="row mb-4">
<div class="col-md-6">
<h4><i class="fas fa-bullseye text-primary me-2"></i>我们的使命</h4>
<p>校园二手物品交易平台致力于为校园内的学生提供一个安全、便捷、高效的二手物品交易环境。我们希望通过这个平台,让闲置物品得到更好的利用,减少资源浪费,同时也帮助学生节省开支。</p>
</div>
<div class="col-md-6">
<h4><i class="fas fa-eye text-primary me-2"></i>我们的愿景</h4>
<p>我们希望成为校园内最受欢迎的二手物品交易平台,打造一个绿色、环保、互助的校园生态系统。通过我们的平台,让每一件物品都能找到它的新主人,让每一位学生都能享受到便捷的二手交易体验。</p>
</div>
</div>
<div class="mb-4">
<h4><i class="fas fa-history text-primary me-2"></i>平台历史</h4>
<p>校园二手物品交易平台创建于2023年,由一群热爱创新的大学生共同开发。我们注意到校园内二手物品交易需求旺盛,但缺乏一个专门的平台,于是决定自主开发这个平台,为校园内的同学们提供服务。</p>
<p>经过不断的改进和完善,我们的平台功能日益丰富,用户体验不断提升。目前,我们已经拥有了数千名注册用户,每天有数百件物品在平台上交易。</p>
</div>
<div class="mb-4">
<h4><i class="fas fa-shield-alt text-primary me-2"></i>平台特色</h4>
<div class="row mt-3">
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5><i class="fas fa-user-check text-success me-2"></i>校内实名认证</h5>
<p>所有用户必须通过学生身份认证,确保交易双方都是校内学生,提高交易安全性。</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5><i class="fas fa-map-marker-alt text-danger me-2"></i>校内面对面交易</h5>
<p>买卖双方在校内面对面交易,方便快捷,避免了物流环节可能带来的问题。</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5><i class="fas fa-star text-warning me-2"></i>信用评价系统</h5>
<p>完善的信用评价系统,让用户可以了解交易对象的信用状况,选择更可靠的交易伙伴。</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5><i class="fas fa-comments text-info me-2"></i>即时通讯功能</h5>
<p>内置即时通讯功能,方便买卖双方沟通交流,协商交易细节。</p>
</div>
</div>
</div>
</div>
</div>
<div class="mb-4">
<h4><i class="fas fa-users text-primary me-2"></i>团队成员</h4>
<div class="row mt-3">
<div class="col-md-4 text-center mb-4">
<img src="{{ url_for('static', filename='images/team/member1.jpg') }}" alt="团队成员1" class="rounded-circle img-fluid mb-3" style="width: 150px; height: 150px; object-fit: cover;">
<h5>张三</h5>
<p class="text-muted">项目负责人</p>
</div>
<div class="col-md-4 text-center mb-4">
<img src="{{ url_for('static', filename='images/team/member2.jpg') }}" alt="团队成员2" class="rounded-circle img-fluid mb-3" style="width: 150px; height: 150px; object-fit: cover;">
<h5>李四</h5>
<p class="text-muted">前端开发</p>
</div>
<div class="col-md-4 text-center mb-4">
<img src="{{ url_for('static', filename='images/team/member3.jpg') }}" alt="团队成员3" class="rounded-circle img-fluid mb-3" style="width: 150px; height: 150px; object-fit: cover;">
<h5>王五</h5>
<p class="text-muted">后端开发</p>
</div>
</div>
</div>
<div>
<h4><i class="fas fa-envelope text-primary me-2"></i>联系我们</h4>
<p>如果您有任何问题、建议或合作意向,欢迎通过以下方式联系我们:</p>
<ul class="list-unstyled">
<li><i class="fas fa-envelope-open me-2"></i>电子邮箱:contact@campus-trading.com</li>
<li><i class="fas fa-phone me-2"></i>联系电话:123-4567-8901</li>
<li><i class="fas fa-map-marker-alt me-2"></i>办公地址:大学生创新创业中心 B栋305室</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
app\templates\main\help.html
{% extends "base.html" %}
{% block title %}帮助中心 - {{ super() }}{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card shadow mb-4">
<div class="card-header bg-primary text-white">
<h4 class="mb-0"><i class="fas fa-question-circle me-2"></i>帮助中心</h4>
</div>
<div class="card-body">
<div class="accordion" id="helpAccordion">
<!-- 常见问题 -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
<i class="fas fa-question-circle me-2"></i>常见问题
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#helpAccordion">
<div class="accordion-body">
<div class="mb-4">
<h5>1. 如何注册账号?</h5>
<p>点击网站右上角的"注册"按钮,填写相关信息并提交,然后按照邮箱中的验证链接完成注册。</p>
</div>
<div class="mb-4">
<h5>2. 如何发布商品?</h5>
<p>登录后,点击"发布商品"按钮,填写商品信息,上传图片,设置价格后提交即可。</p>
</div>
<div class="mb-4">
<h5>3. 如何联系卖家?</h5>
<p>在商品详情页面,点击"联系卖家"按钮,可以发送私信与卖家沟通。</p>
</div>
<div class="mb-4">
<h5>4. 如何完成交易?</h5>
<p>买卖双方协商好交易细节后,可以选择线下面对面交易,确认无误后,买家在平台上确认交易完成。</p>
</div>
<div>
<h5>5. 忘记密码怎么办?</h5>
<p>在登录页面点击"忘记密码",输入注册邮箱,按照邮件中的指引重置密码。</p>
</div>
</div>
</div>
</div>
<!-- 用户指南 -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingTwo">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
<i class="fas fa-book me-2"></i>用户指南
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#helpAccordion">
<div class="accordion-body">
<div class="mb-4">
<h5>新用户注册</h5>
<ol>
<li>点击网站右上角的"注册"按钮</li>
<li>填写用户名、邮箱、学号和密码</li>
<li>阅读并同意用户协议</li>
<li>点击"注册"按钮提交</li>
<li>前往邮箱查收验证邮件</li>
<li>点击邮件中的验证链接完成注册</li>
</ol>
</div>
<div class="mb-4">
<h5>发布商品</h5>
<ol>
<li>登录账号</li>
<li>点击"发布商品"按钮</li>
<li>选择商品分类</li>
<li>填写商品标题、描述、价格等信息</li>
<li>上传商品图片(最多5张)</li>
<li>设置交易地点和时间偏好</li>
<li>点击"发布"按钮提交</li>
</ol>
</div>
<div>
<h5>购买商品</h5>
<ol>
<li>浏览或搜索商品</li>
<li>查看商品详情</li>
<li>点击"联系卖家"与卖家沟通</li>
<li>协商交易细节(时间、地点等)</li>
<li>线下面对面交易</li>
<li>确认收货后,在平台上点击"确认交易完成"</li>
<li>对卖家进行评价</li>
</ol>
</div>
</div>
</div>
</div>
<!-- 安全交易提示 -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingThree">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
<i class="fas fa-shield-alt me-2"></i>安全交易提示
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#helpAccordion">
<div class="accordion-body">
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>请注意:平台不会要求您提供银行卡密码、验证码等敏感信息,请警惕诈骗!
</div>
<div class="mb-4">
<h5>交易前</h5>
<ul>
<li>查看卖家信用评价和历史交易记录</li>
<li>仔细核对商品信息,必要时要求卖家提供更多图片或视频</li>
<li>与卖家充分沟通,明确商品状况、价格和交易方式</li>
<li>不要轻信过低的价格,谨防虚假信息</li>
</ul>
</div>
<div class="mb-4">
<h5>交易中</h5>
<ul>
<li>选择安全的公共场所进行交易,如学校食堂、图书馆等</li>
<li>尽量白天交易,避免夜间或偏僻地点</li>
<li>交易前仔细检查商品,确认无误后再付款</li>
<li>保留交易凭证和聊天记录</li>
</ul>
</div>
<div>
<h5>交易后</h5>
<ul>
<li>及时在平台上确认交易完成</li>
<li>对卖家进行客观、公正的评价</li>
<li>如遇问题,及时联系平台客服处理</li>
</ul>
</div>
</div>
</div>
</div>
<!-- 联系客服 -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingFour">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
<i class="fas fa-headset me-2"></i>联系客服
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour" data-bs-parent="#helpAccordion">
<div class="accordion-body">
<p>如果您在使用过程中遇到任何问题,或者有任何建议和反馈,欢迎通过以下方式联系我们的客服团队:</p>
<div class="row mt-4">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body text-center">
<i class="fas fa-envelope fa-3x text-primary mb-3"></i>
<h5>电子邮件</h5>
<p>support@campus-trading.com</p>
<p class="text-muted small">工作日回复时间:24小时内</p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body text-center">
<i class="fas fa-comment-dots fa-3x text-success mb-3"></i>
<h5>在线客服</h5>
<p>工作时间:9:00-21:00</p>
<a href="#" class="btn btn-primary">立即咨询</a>
</div>
</div>
</div>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle me-2"></i>提示:联系客服时,请提供您的用户名和问题详情,以便我们更快地为您解决问题。
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
app\templates\main\index.html
{% extends "base.html" %}
{% block title %}首页 - {{ super() }}{% endblock %}
{% block content %}
<!-- 轮播图 -->
<div id="homeCarousel" class="carousel slide mb-4 shadow rounded" data-bs-ride="carousel">
<div class="carousel-indicators">
<button type="button" data-bs-target="#homeCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
<button type="button" data-bs-target="#homeCarousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
<button type="button" data-bs-target="#homeCarousel" data-bs-slide-to="2" aria-label="Slide 3"></button>
</div>
<div class="carousel-inner rounded">
<div class="carousel-item active">
<img src="{{ url_for('static', filename='images/banner1.jpg') }}" class="d-block w-100" alt="校园二手物品交易平台">
<div class="carousel-caption d-none d-md-block">
<h2>校园二手物品交易平台</h2>
<p>让闲置物品流动起来,创造更多价值</p>
<a href="#" class="btn btn-primary">立即浏览</a>
</div>
</div>
<div class="carousel-item">
<img src="{{ url_for('static', filename='images/banner2.jpg') }}" class="d-block w-100" alt="安全可靠">
<div class="carousel-caption d-none d-md-block">
<h2>安全可靠的交易环境</h2>
<p>校内实名认证,安全有保障</p>
<a href="#" class="btn btn-primary">了解更多</a>
</div>
</div>
<div class="carousel-item">
<img src="{{ url_for('static', filename='images/banner3.jpg') }}" class="d-block w-100" alt="便捷交易">
<div class="carousel-caption d-none d-md-block">
<h2>便捷的校园交易</h2>
<p>线上沟通,线下交易,方便快捷</p>
<a href="#" class="btn btn-primary">立即注册</a>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#homeCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#homeCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
<!-- 搜索栏 -->
<div class="card shadow mb-4">
<div class="card-body">
<form class="row g-3" action="{{ url_for('main.search') }}" method="get">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text bg-primary text-white"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" name="q" placeholder="搜索商品名称、描述或卖家..." aria-label="搜索商品">
</div>
</div>
<div class="col-md-3">
<select class="form-select" name="category">
<option value="">所有分类</option>
<option value="books">教材书籍</option>
<option value="electronics">电子产品</option>
<option value="clothing">服装鞋帽</option>
<option value="daily">日常用品</option>
<option value="sports">体育用品</option>
<option value="others">其他物品</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary w-100">搜索</button>
</div>
</form>
</div>
</div>
<!-- 分类导航 -->
<div class="row mb-4">
<div class="col-12">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-th-large me-2"></i>商品分类</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-4 col-md-2 mb-3">
<a href="#" class="text-decoration-none category-link">
<div class="category-icon mb-2">
<i class="fas fa-book fa-2x text-primary"></i>
</div>
<div>教材书籍</div>
</a>
</div>
<div class="col-4 col-md-2 mb-3">
<a href="#" class="text-decoration-none category-link">
<div class="category-icon mb-2">
<i class="fas fa-laptop fa-2x text-success"></i>
</div>
<div>电子产品</div>
</a>
</div>
<div class="col-4 col-md-2 mb-3">
<a href="#" class="text-decoration-none category-link">
<div class="category-icon mb-2">
<i class="fas fa-tshirt fa-2x text-danger"></i>
</div>
<div>服装鞋帽</div>
</a>
</div>
<div class="col-4 col-md-2 mb-3">
<a href="#" class="text-decoration-none category-link">
<div class="category-icon mb-2">
<i class="fas fa-home fa-2x text-info"></i>
</div>
<div>日常用品</div>
</a>
</div>
<div class="col-4 col-md-2 mb-3">
<a href="#" class="text-decoration-none category-link">
<div class="category-icon mb-2">
<i class="fas fa-futbol fa-2x text-warning"></i>
</div>
<div>体育用品</div>
</a>
</div>
<div class="col-4 col-md-2 mb-3">
<a href="#" class="text-decoration-none category-link">
<div class="category-icon mb-2">
<i class="fas fa-ellipsis-h fa-2x text-secondary"></i>
</div>
<div>其他物品</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 最新上架 -->
<div class="row mb-4">
<div class="col-12">
<div class="card shadow">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="fas fa-fire me-2"></i>最新上架</h5>
<a href="#" class="btn btn-sm btn-light">查看更多</a>
</div>
<div class="card-body">
<div class="row">
{% if latest_items %}
{% for item in latest_items %}
<div class="col-md-6 col-lg-3 mb-4">
<div class="card h-100 item-card">
<div class="position-relative">
<img src="{{ item.get_main_image_url() }}" class="card-img-top item-thumbnail" alt="{{ item.title }}">
{% if item.is_new %}
<span class="position-absolute top-0 start-0 badge bg-success m-2">新品</span>
{% endif %}
<button class="btn btn-sm btn-outline-danger position-absolute top-0 end-0 m-2 favorite-btn" data-item-id="{{ item.id }}">
<i class="far fa-heart"></i>
</button>
</div>
<div class="card-body">
<h5 class="card-title text-truncate">{{ item.title }}</h5>
<p class="card-text text-danger fw-bold">¥{{ item.price }}</p>
<p class="card-text small text-muted">
<i class="fas fa-user me-1"></i>{{ item.seller.username }}
<i class="fas fa-eye ms-2 me-1"></i>{{ item.views }}
</p>
</div>
<div class="card-footer bg-white">
<a href="{{ url_for('item.detail', item_id=item.id) }}" class="btn btn-primary w-100">查看详情</a>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="col-12 text-center py-5">
<i class="fas fa-box-open fa-4x text-muted mb-3"></i>
<h5>暂无商品</h5>
<p>目前还没有商品上架,请稍后再来查看</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- 热门商品 -->
<div class="row mb-4">
<div class="col-12">
<div class="card shadow">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="fas fa-star me-2"></i>热门商品</h5>
<a href="#" class="btn btn-sm btn-light">查看更多</a>
</div>
<div class="card-body">
<div class="row">
{% if popular_items %}
{% for item in popular_items %}
<div class="col-md-6 col-lg-3 mb-4">
<div class="card h-100 item-card">
<div class="position-relative">
<img src="{{ item.get_main_image_url() }}" class="card-img-top item-thumbnail" alt="{{ item.title }}">
<span class="position-absolute top-0 start-0 badge bg-danger m-2">热门</span>
<button class="btn btn-sm btn-outline-danger position-absolute top-0 end-0 m-2 favorite-btn" data-item-id="{{ item.id }}">
<i class="far fa-heart"></i>
</button>
</div>
<div class="card-body">
<h5 class="card-title text-truncate">{{ item.title }}</h5>
<p class="card-text text-danger fw-bold">¥{{ item.price }}</p>
<p class="card-text small text-muted">
<i class="fas fa-user me-1"></i>{{ item.seller.username }}
<i class="fas fa-eye ms-2 me-1"></i>{{ item.views }}
</p>
</div>
<div class="card-footer bg-white">
<a href="{{ url_for('item.detail', item_id=item.id) }}" class="btn btn-primary w-100">查看详情</a>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="col-12 text-center py-5">
<i class="fas fa-box-open fa-4x text-muted mb-3"></i>
<h5>暂无热门商品</h5>
<p>目前还没有热门商品,请稍后再来查看</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- 平台优势 -->
<div class="row mb-4">
<div class="col-12">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-thumbs-up me-2"></i>平台优势</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-md-3 mb-4">
<div class="p-3">
<i class="fas fa-shield-alt fa-3x text-primary mb-3"></i>
<h4>安全可靠</h4>
<p class="text-muted">校内实名认证,交易更有保障</p>
</div>
</div>
<div class="col-md-3 mb-4">
<div class="p-3">
<i class="fas fa-hand-holding-usd fa-3x text-success mb-3"></i>
<h4>价格实惠</h4>
<p class="text-muted">二手商品,物美价廉</p>
</div>
</div>
<div class="col-md-3 mb-4">
<div class="p-3">
<i class="fas fa-map-marker-alt fa-3x text-danger mb-3"></i>
<h4>就近交易</h4>
<p class="text-muted">校内面对面交易,方便快捷</p>
</div>
</div>
<div class="col-md-3 mb-4">
<div class="p-3">
<i class="fas fa-recycle fa-3x text-info mb-3"></i>
<h4>环保节约</h4>
<p class="text-muted">物尽其用,减少浪费</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// 收藏按钮点击事件
const favoriteBtns = document.querySelectorAll('.favorite-btn');
favoriteBtns.forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
const itemId = this.getAttribute('data-item-id');
// 检查是否已登录
{% if current_user.is_authenticated %}
// 发送收藏请求
fetch(`/api/items/${itemId}/favorite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 切换图标
const icon = this.querySelector('i');
if (data.is_favorite) {
icon.classList.remove('far');
icon.classList.add('fas');
this.classList.add('btn-danger');
this.classList.remove('btn-outline-danger');
} else {
icon.classList.remove('fas');
icon.classList.add('far');
this.classList.remove('btn-danger');
this.classList.add('btn-outline-danger');
}
}
});
{% else %}
// 未登录,跳转到登录页面
window.location.href = "{{ url_for('auth.login', next=request.path) }}";
{% endif %}
});
});
});
</script>
{% endblock %}
app\templates\order\detail.html