mirror of
https://github.com/zhtyyx/ioe.git
synced 2026-06-03 21:02:59 +08:00
feat: fix add product error
This commit is contained in:
parent
a738ca3aa3
commit
baf81883e0
@ -32,18 +32,44 @@
|
||||
<div class="card-body">
|
||||
<div class="row g-3 mb-4 align-items-center">
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="bi bi-search"></i></span>
|
||||
<input type="text" class="form-control" id="searchInput" placeholder="搜索商品名称或条码...">
|
||||
</div>
|
||||
<form method="get" action="{% url 'product_list' %}" class="mb-0">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="bi bi-search"></i></span>
|
||||
<input type="text" class="form-control" id="searchInput" name="search"
|
||||
placeholder="搜索商品名称或条码..." value="{{ search_query }}">
|
||||
<input type="hidden" name="sort" value="{{ sort_by }}">
|
||||
<input type="hidden" name="category" value="{{ selected_category }}">
|
||||
<input type="hidden" name="status" value="{{ selected_status }}">
|
||||
<button type="submit" class="btn btn-primary">搜索</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-md-end">
|
||||
<div class="btn-group" id="categoryFilter">
|
||||
<button type="button" class="btn btn-outline-primary active" data-category="">全部</button>
|
||||
{% for category in categories %}
|
||||
<button type="button" class="btn btn-outline-primary" data-category="{{ category.id }}">{{ category.name }}</button>
|
||||
{% endfor %}
|
||||
<div class="btn-group me-2">
|
||||
<a href="{% url 'product_list' %}?sort=updated{% if search_query %}&search={{ search_query }}{% endif %}{% if selected_category %}&category={{ selected_category }}{% endif %}{% if selected_status %}&status={{ selected_status }}{% endif %}"
|
||||
class="btn btn-outline-secondary {% if sort_by == 'updated' %}active{% endif %}">
|
||||
<i class="bi bi-clock-history"></i> 最近更新
|
||||
</a>
|
||||
<a href="{% url 'product_list' %}?sort=name{% if search_query %}&search={{ search_query }}{% endif %}{% if selected_category %}&category={{ selected_category }}{% endif %}{% if selected_status %}&status={{ selected_status }}{% endif %}"
|
||||
class="btn btn-outline-secondary {% if sort_by == 'name' %}active{% endif %}">
|
||||
<i class="bi bi-sort-alpha-down"></i> 名称
|
||||
</a>
|
||||
<a href="{% url 'product_list' %}?sort=created{% if search_query %}&search={{ search_query }}{% endif %}{% if selected_category %}&category={{ selected_category }}{% endif %}{% if selected_status %}&status={{ selected_status }}{% endif %}"
|
||||
class="btn btn-outline-secondary {% if sort_by == 'created' %}active{% endif %}">
|
||||
<i class="bi bi-calendar-plus"></i> 创建时间
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group ms-2" style="min-width: 150px;">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="bi bi-tag"></i></span>
|
||||
<select id="categoryDropdown" class="form-select" aria-label="分类筛选">
|
||||
<option value="" {% if not selected_category %}selected{% endif %}>全部分类</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}" {% if selected_category == category.id|stringformat:'s' %}selected{% endif %}>{{ category.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -128,6 +154,56 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 添加分页控件 -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<div class="d-flex justify-content-center mt-4">
|
||||
<nav aria-label="商品列表分页">
|
||||
<ul class="pagination">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}{% if selected_category %}&category={{ selected_category }}{% endif %}{% if sort_by %}&sort={{ sort_by }}{% endif %}{% if selected_status %}&status={{ selected_status }}{% endif %}" aria-label="上一页">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" aria-label="上一页">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for i in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == i %}
|
||||
<li class="page-item active">
|
||||
<a class="page-link" href="#">{{ i }}</a>
|
||||
</li>
|
||||
{% elif i > page_obj.number|add:"-4" and i < page_obj.number|add:"4" %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ i }}{% if search_query %}&search={{ search_query }}{% endif %}{% if selected_category %}&category={{ selected_category }}{% endif %}{% if sort_by %}&sort={{ sort_by }}{% endif %}{% if selected_status %}&status={{ selected_status }}{% endif %}">{{ i }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}{% if selected_category %}&category={{ selected_category }}{% endif %}{% if sort_by %}&sort={{ sort_by }}{% endif %}{% if selected_status %}&status={{ selected_status }}{% endif %}" aria-label="下一页">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" aria-label="下一页">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -135,49 +211,46 @@
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 搜索功能
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
searchInput.addEventListener('input', filterProducts);
|
||||
// 删除前端搜索过滤逻辑,由后端处理搜索和筛选
|
||||
|
||||
// 分类筛选功能
|
||||
const categoryButtons = document.querySelectorAll('#categoryFilter button');
|
||||
categoryButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
// 移除所有按钮的active类
|
||||
categoryButtons.forEach(btn => btn.classList.remove('active'));
|
||||
// 给当前点击的按钮添加active类
|
||||
this.classList.add('active');
|
||||
|
||||
filterProducts();
|
||||
// 添加分页导航
|
||||
document.querySelectorAll('.page-link').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (this.getAttribute('href') === '#') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function filterProducts() {
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
const activeCategory = document.querySelector('#categoryFilter button.active').dataset.category;
|
||||
|
||||
const rows = document.querySelectorAll('tbody tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
// 忽略空行
|
||||
if (!row.querySelector('td:first-child')) return;
|
||||
// 分类下拉列表变化处理
|
||||
const categoryDropdown = document.getElementById('categoryDropdown');
|
||||
if (categoryDropdown) {
|
||||
categoryDropdown.addEventListener('change', function() {
|
||||
// 构建URL
|
||||
let url = '{% url 'product_list' %}?';
|
||||
|
||||
const productName = row.querySelector('h6')?.textContent.toLowerCase() || '';
|
||||
const barcode = row.querySelector('small')?.textContent.toLowerCase() || '';
|
||||
const rowCategory = row.dataset.category;
|
||||
|
||||
// 检查搜索条件
|
||||
const matchesSearch = productName.includes(searchTerm) || barcode.includes(searchTerm);
|
||||
|
||||
// 检查分类条件
|
||||
const matchesCategory = !activeCategory || rowCategory === activeCategory;
|
||||
|
||||
// 同时满足搜索和分类条件才显示
|
||||
if (matchesSearch && matchesCategory) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
// 添加分类参数 (如果有选择分类)
|
||||
if (this.value) {
|
||||
url += 'category=' + this.value;
|
||||
}
|
||||
|
||||
// 添加排序参数 (如果存在)
|
||||
{% if sort_by %}
|
||||
url += (url.endsWith('?') ? '' : '&') + 'sort={{ sort_by }}';
|
||||
{% endif %}
|
||||
|
||||
// 添加搜索参数 (如果存在)
|
||||
{% if search_query %}
|
||||
url += (url.endsWith('?') ? '' : '&') + 'search={{ search_query }}';
|
||||
{% endif %}
|
||||
|
||||
// 添加状态参数 (如果存在)
|
||||
{% if selected_status %}
|
||||
url += (url.endsWith('?') ? '' : '&') + 'status={{ selected_status }}';
|
||||
{% endif %}
|
||||
|
||||
// 导航到构建的URL
|
||||
window.location.href = url;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -90,10 +90,13 @@ def product_list(request):
|
||||
search_query = request.GET.get('search', '')
|
||||
category_id = request.GET.get('category', '')
|
||||
status = request.GET.get('status', 'active') # 默认显示活跃商品
|
||||
sort_by = request.GET.get('sort', 'name')
|
||||
sort_by = request.GET.get('sort', 'updated') # 修改默认排序为更新时间
|
||||
|
||||
print(f"DEBUG: 列表筛选参数 - 搜索: {search_query}, 分类: {category_id}, 状态: {status}, 排序: {sort_by}")
|
||||
|
||||
# 基本查询集
|
||||
products = Product.objects.select_related('category').all()
|
||||
print(f"DEBUG: 初始查询集数量: {products.count()}")
|
||||
|
||||
# 应用筛选
|
||||
if search_query:
|
||||
@ -109,6 +112,7 @@ def product_list(request):
|
||||
# 状态筛选
|
||||
if status == 'active':
|
||||
products = products.filter(is_active=True)
|
||||
print(f"DEBUG: 应用活跃状态筛选后的数量: {products.count()}")
|
||||
elif status == 'inactive':
|
||||
products = products.filter(is_active=False)
|
||||
|
||||
@ -121,6 +125,10 @@ def product_list(request):
|
||||
products = products.order_by('category__name', 'name')
|
||||
elif sort_by == 'created':
|
||||
products = products.order_by('-created_at')
|
||||
elif sort_by == 'updated': # 添加按更新时间排序
|
||||
products = products.order_by('-updated_at')
|
||||
else: # 默认按更新时间降序
|
||||
products = products.order_by('-updated_at')
|
||||
|
||||
# 分页
|
||||
paginator = Paginator(products, 15) # 每页15个商品
|
||||
@ -134,6 +142,8 @@ def product_list(request):
|
||||
total_products = Product.objects.count()
|
||||
active_products = Product.objects.filter(is_active=True).count()
|
||||
|
||||
print(f"DEBUG: 总商品数: {total_products}, 活跃商品数: {active_products}, 当前页面商品数: {len(page_obj)}")
|
||||
|
||||
context = {
|
||||
'page_obj': page_obj,
|
||||
'categories': categories,
|
||||
@ -188,40 +198,47 @@ def product_create(request):
|
||||
form = ProductForm(request.POST)
|
||||
image_formset = ProductImageFormSet(request.POST, request.FILES, prefix='images')
|
||||
|
||||
if form.is_valid() and image_formset.is_valid():
|
||||
# 修改验证逻辑,只检查表单是否有效,不强制检查图片表单集
|
||||
if form.is_valid():
|
||||
# 保存商品数据
|
||||
product = form.save(commit=False)
|
||||
product.created_by = request.user
|
||||
product.is_active = True # 确保商品默认为活跃状态
|
||||
product.save()
|
||||
|
||||
# 保存商品图片
|
||||
for image_form in image_formset:
|
||||
if image_form.cleaned_data and not image_form.cleaned_data.get('DELETE'):
|
||||
image = image_form.save(commit=False)
|
||||
image.product = product
|
||||
|
||||
# 处理图片文件
|
||||
if image.image:
|
||||
# 生成缩略图
|
||||
thumbnail = generate_thumbnail(image.image, (300, 300))
|
||||
# 只有当图片表单集有效时才处理图片
|
||||
if image_formset.is_valid():
|
||||
# 保存商品图片
|
||||
for image_form in image_formset:
|
||||
if image_form.cleaned_data and not image_form.cleaned_data.get('DELETE'):
|
||||
image = image_form.save(commit=False)
|
||||
image.product = product
|
||||
|
||||
# 保存缩略图
|
||||
thumb_name = f'thumb_{uuid.uuid4()}.jpg'
|
||||
thumb_path = f'products/thumbnails/{thumb_name}'
|
||||
thumb_file = io.BytesIO()
|
||||
thumbnail.save(thumb_file, format='JPEG')
|
||||
# 处理图片文件
|
||||
if image.image:
|
||||
# 生成缩略图
|
||||
thumbnail = generate_thumbnail(image.image, (300, 300))
|
||||
|
||||
# 保存缩略图
|
||||
thumb_name = f'thumb_{uuid.uuid4()}.jpg'
|
||||
thumb_path = f'products/thumbnails/{thumb_name}'
|
||||
thumb_file = io.BytesIO()
|
||||
thumbnail.save(thumb_file, format='JPEG')
|
||||
|
||||
# 设置缩略图路径
|
||||
image.thumbnail = thumb_path
|
||||
|
||||
# 设置缩略图路径
|
||||
image.thumbnail = thumb_path
|
||||
|
||||
image.save()
|
||||
image.save()
|
||||
|
||||
# 创建初始库存记录
|
||||
warning_level = 10 # 设置一个默认的预警值
|
||||
if 'warning_level' in form.cleaned_data and form.cleaned_data['warning_level'] is not None:
|
||||
warning_level = form.cleaned_data['warning_level']
|
||||
|
||||
Inventory.objects.create(
|
||||
product=product,
|
||||
quantity=0,
|
||||
warning_level=form.cleaned_data.get('warning_level', 5)
|
||||
warning_level=warning_level
|
||||
)
|
||||
|
||||
messages.success(request, f'商品 {product.name} 创建成功')
|
||||
@ -230,7 +247,8 @@ def product_create(request):
|
||||
if 'next' in request.POST and request.POST['next'] == 'bulk':
|
||||
return redirect('product_bulk_create')
|
||||
|
||||
return redirect('product_detail', pk=product.id)
|
||||
# 修改重定向,解决模板不存在的问题
|
||||
return redirect('product_list')
|
||||
else:
|
||||
form = ProductForm()
|
||||
image_formset = ProductImageFormSet(prefix='images')
|
||||
@ -263,53 +281,61 @@ def product_update(request, pk):
|
||||
form = ProductForm(request.POST, instance=product)
|
||||
image_formset = ProductImageFormSet(request.POST, request.FILES, prefix='images', instance=product)
|
||||
|
||||
if form.is_valid() and image_formset.is_valid():
|
||||
# 修改验证逻辑,只检查表单是否有效,不强制检查图片表单集
|
||||
if form.is_valid():
|
||||
# 保存商品数据
|
||||
product = form.save(commit=False)
|
||||
product.updated_at = timezone.now()
|
||||
product.updated_by = request.user
|
||||
product.save()
|
||||
|
||||
# 保存商品图片
|
||||
for image_form in image_formset:
|
||||
if image_form.cleaned_data:
|
||||
if image_form.cleaned_data.get('DELETE'):
|
||||
if image_form.instance.pk:
|
||||
image_form.instance.delete()
|
||||
else:
|
||||
image = image_form.save(commit=False)
|
||||
image.product = product
|
||||
|
||||
# 处理图片文件
|
||||
if image.image and not image.thumbnail:
|
||||
# 生成缩略图
|
||||
thumbnail = generate_thumbnail(image.image, (300, 300))
|
||||
# 只有当图片表单集有效时才处理图片
|
||||
if image_formset.is_valid():
|
||||
# 保存商品图片
|
||||
for image_form in image_formset:
|
||||
if image_form.cleaned_data:
|
||||
if image_form.cleaned_data.get('DELETE'):
|
||||
if image_form.instance.pk:
|
||||
image_form.instance.delete()
|
||||
else:
|
||||
image = image_form.save(commit=False)
|
||||
image.product = product
|
||||
|
||||
# 保存缩略图
|
||||
thumb_name = f'thumb_{uuid.uuid4()}.jpg'
|
||||
thumb_path = f'products/thumbnails/{thumb_name}'
|
||||
thumb_file = io.BytesIO()
|
||||
thumbnail.save(thumb_file, format='JPEG')
|
||||
# 处理图片文件
|
||||
if image.image and not image.thumbnail:
|
||||
# 生成缩略图
|
||||
thumbnail = generate_thumbnail(image.image, (300, 300))
|
||||
|
||||
# 保存缩略图
|
||||
thumb_name = f'thumb_{uuid.uuid4()}.jpg'
|
||||
thumb_path = f'products/thumbnails/{thumb_name}'
|
||||
thumb_file = io.BytesIO()
|
||||
thumbnail.save(thumb_file, format='JPEG')
|
||||
|
||||
# 设置缩略图路径
|
||||
image.thumbnail = thumb_path
|
||||
|
||||
# 设置缩略图路径
|
||||
image.thumbnail = thumb_path
|
||||
|
||||
image.save()
|
||||
image.save()
|
||||
|
||||
# 更新库存预警级别
|
||||
warning_level = 10 # 设置一个默认的预警值
|
||||
if 'warning_level' in form.cleaned_data and form.cleaned_data['warning_level'] is not None:
|
||||
warning_level = form.cleaned_data['warning_level']
|
||||
|
||||
try:
|
||||
inventory = Inventory.objects.get(product=product)
|
||||
inventory.warning_level = form.cleaned_data.get('warning_level', inventory.warning_level)
|
||||
inventory.warning_level = warning_level
|
||||
inventory.save()
|
||||
except Inventory.DoesNotExist:
|
||||
Inventory.objects.create(
|
||||
product=product,
|
||||
quantity=0,
|
||||
warning_level=form.cleaned_data.get('warning_level', 5)
|
||||
warning_level=warning_level
|
||||
)
|
||||
|
||||
messages.success(request, f'商品 {product.name} 更新成功')
|
||||
return redirect('product_detail', pk=product.id)
|
||||
# 修改重定向,解决模板不存在的问题
|
||||
return redirect('product_list')
|
||||
else:
|
||||
form = ProductForm(instance=product)
|
||||
# 设置库存预警级别
|
||||
|
||||
1511
product_form.html
Normal file
1511
product_form.html
Normal file
File diff suppressed because it is too large
Load Diff
1519
product_list.html
Normal file
1519
product_list.html
Normal file
File diff suppressed because it is too large
Load Diff
1527
product_response.html
Normal file
1527
product_response.html
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user