@@ -3381,7 +3381,7 @@ function loadBrandManagementData(page) {
if(res.code!==0){body.innerHTML='
| '+escHtml(res.msg||'加载失败')+' |
';return;}
var rows=res.data||[], total=document.getElementById('brandManageTotal'); if(total) total.textContent=res.total||0;
if(!rows.length){body.innerHTML='
| 暂无品牌 |
';document.getElementById('brandManagePagination').innerHTML='';return;}
- body.innerHTML=rows.map(function(r,i){var cat=r.category||'';var cls=cat==='国内'?'brand-category-domestic':(cat==='海外'?'brand-category-overseas':'');var select='
';return '
| '+((window._brandManagePage-1)*10+i+1)+' | '+escHtml(r.brand)+' | '+select+' | '+escHtml(r.updated_at||'-')+' | |
';}).join('');
+ body.innerHTML=rows.map(function(r,i){var cat=r.category||'';var cls=cat==='国内'?'brand-category-domestic':(cat==='海外'?'brand-category-overseas':'');var select='
';var deleteButton=window._hasPagePerm('brand_management','delete')?'
':'';return '
| '+((window._brandManagePage-1)*10+i+1)+' | '+escHtml(r.brand)+' | '+select+' | '+escHtml(r.updated_at||'-')+' | '+deleteButton+' |
';}).join('');
renderBrandManagementPagination(res.total||0,10,window._brandManagePage);
}).catch(function(){body.innerHTML='
| 网络异常,请稍后重试 |
';});
}
@@ -3391,6 +3391,7 @@ function showBrandEdit(brand){fetch('/api/brand-management/list?search='+encodeU
function showBrandModal(title,brand,category){var old=document.getElementById('brandManageModal');if(old)old.remove();var overlay=document.createElement('div');overlay.id='brandManageModal';overlay.className='modal-overlay';overlay.style.display='flex';overlay.onclick=function(e){if(e.target===overlay){}};overlay.innerHTML='
';document.body.appendChild(overlay);document.getElementById('brandManageModalCategory').value=category||'';document.getElementById('brandManageName').focus();}
function submitBrandModal(oldBrand){var name=(document.getElementById('brandManageName').value||'').trim(),category=document.getElementById('brandManageModalCategory').value||'';if(!name){alert('品牌名称不能为空');return;}var isEdit=!!oldBrand;fetch(isEdit?'/api/brand-management/update':'/api/brand-management/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(isEdit?{old_brand:oldBrand,new_brand:name,category:category}:{brand:name,category:category})}).then(function(r){return r.json();}).then(function(res){if(res.code!==0){alert(res.msg||'保存失败');return;}document.getElementById('brandManageModal').remove();loadBrandManagementData(window._brandManagePage||1);}).catch(function(){alert('网络异常,保存失败');});}
function saveBrandCategory(brand,category){fetch('/api/brand-management/update',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({old_brand:brand,new_brand:brand,category:category})}).then(function(r){return r.json();}).then(function(res){if(res.code!==0){alert(res.msg||'保存失败');}else{loadBrandManagementData(window._brandManagePage||1);}}).catch(function(){alert('网络异常,保存失败');});}
+function deleteBrand(brand){showCenteredConfirm('确认删除','确定删除品牌“'+brand+'”吗?删除后不可恢复;正在被产品使用的品牌将受到保护,无法删除。').then(function(ok){if(!ok)return;fetch('/api/brand-management/delete',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({brand:brand})}).then(function(r){return r.json();}).then(function(res){if(res.code!==0){showCenteredAlert('删除失败',res.msg||'删除失败');return;}showCenteredAlert('删除成功',res.msg||'品牌已删除');loadBrandManagementData(window._brandManagePage||1);}).catch(function(){showCenteredAlert('删除失败','网络异常,请稍后重试');});});}
// ─── 类目管理页面 ───
function loadProductCategoryPage(container) {
diff --git a/tests/test_brand_management_delete.py b/tests/test_brand_management_delete.py
new file mode 100644
index 0000000..ddae6f5
--- /dev/null
+++ b/tests/test_brand_management_delete.py
@@ -0,0 +1,61 @@
+"""品牌信息管理删除功能回归测试。"""
+
+import unittest
+from unittest import mock
+
+import test_wsgi # noqa: F401
+from app import app
+from routes import products
+
+
+class BrandManagementDeleteTest(unittest.TestCase):
+ def setUp(self):
+ app.config.update(TESTING=True)
+ self.client = app.test_client()
+ with self.client.session_transaction() as session:
+ session.update(user_id=1, username='brand-delete-test', role='super_admin')
+
+ def test_delete_manual_brand(self):
+ with mock.patch.object(products, 'operation_required', return_value=True), \
+ mock.patch.object(products, 'query', side_effect=[
+ [{'cnt': 0}],
+ [{'brand': '误添加品牌', 'category': '国内'}],
+ 0,
+ ]) as query, \
+ mock.patch.object(products, 'add_operation_log') as operation_log:
+ response = self.client.post(
+ '/api/brand-management/delete', json={'brand': ' 误添加品牌 '}
+ )
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.get_json()['code'], 0)
+ self.assertIn('DELETE FROM brand_information', query.call_args_list[2].args[0])
+ operation_log.assert_called_once()
+
+ def test_reject_delete_when_brand_is_used_by_products(self):
+ with mock.patch.object(products, 'operation_required', return_value=True), \
+ mock.patch.object(products, 'query', return_value=[{'cnt': 3}]) as query, \
+ mock.patch.object(products, 'add_operation_log') as operation_log:
+ response = self.client.post(
+ '/api/brand-management/delete', json={'brand': '在用品牌'}
+ )
+
+ body = response.get_json()
+ self.assertEqual(body['code'], 409)
+ self.assertEqual(body['product_count'], 3)
+ self.assertEqual(query.call_count, 1)
+ operation_log.assert_not_called()
+
+ def test_reject_delete_without_edit_permission(self):
+ with mock.patch.object(products, 'operation_required', return_value=False), \
+ mock.patch.object(products, 'query') as query:
+ response = self.client.post(
+ '/api/brand-management/delete', json={'brand': '无权限品牌'}
+ )
+
+ self.assertEqual(response.get_json()['code'], 403)
+ query.assert_not_called()
+
+
+if __name__ == '__main__':
+ unittest.main()