From 80a71b48ce68d17679039cdac5142cd16e73ff91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E9=BE=99=E8=A8=80?= Date: Sat, 10 Oct 2026 11:39:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=89=8D=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E5=AF=BC=E5=87=BAexcel=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module_business/dao/master_dao.py | 34 +++++++++++++++++++ .../module_business/entity/vo/master_vo.py | 7 ++++ .../test_master_excel_service.py | 19 +++++++++++ .../module_business/test_master_service.py | 24 +++++++++++++ .../tests/module_business/test_vo_contract.py | 12 +++---- .../scripts/verify-business-contract.mjs | 4 +-- .../components/MasterExcelActions/index.vue | 10 +++++- .../src/views/master/customer/index.vue | 2 +- .../src/views/master/line/index.vue | 2 +- .../src/views/master/pallet/index.vue | 2 +- .../src/views/master/site/index.vue | 2 +- .../src/views/master/strain/index.vue | 2 +- .../src/views/master/tunnel/index.vue | 2 +- .../src/views/master/warehouse/index.vue | 2 +- 14 files changed, 108 insertions(+), 16 deletions(-) diff --git a/shierfeng-fastapi-backend/module_business/dao/master_dao.py b/shierfeng-fastapi-backend/module_business/dao/master_dao.py index 25fb2cd..3910fc0 100644 --- a/shierfeng-fastapi-backend/module_business/dao/master_dao.py +++ b/shierfeng-fastapi-backend/module_business/dao/master_dao.py @@ -43,6 +43,19 @@ from utils.page_util import PageUtil class MasterDao: """菌种、客户、现场、产线、隧道和仓库主数据数据库操作层。""" + @staticmethod + def _parse_id_list(ids_str: str | None) -> list[int]: + if not ids_str: + return [] + result: list[int] = [] + for item in ids_str.split(','): + item = item.strip() + if item.isdigit(): + val = int(item) + if val > 0: + result.append(val) + return result + @staticmethod async def _paginate( db: AsyncSession, @@ -139,6 +152,9 @@ class MasterDao: conditions.append(BizStrain.strain_name.like(f'%{query_object.strain_name}%')) if query_object.status: conditions.append(BizStrain.status == query_object.status) + ids = cls._parse_id_list(getattr(query_object, 'ids', None)) + if ids: + conditions.append(BizStrain.strain_id.in_(ids)) statement = select(BizStrain).where(*conditions).order_by(BizStrain.strain_id) return await cls._paginate(db, statement, query_object, is_page) @@ -187,6 +203,9 @@ class MasterDao: conditions.append(BizCustomer.customer_name.like(f'%{query_object.customer_name}%')) if query_object.status: conditions.append(BizCustomer.status == query_object.status) + ids = cls._parse_id_list(getattr(query_object, 'ids', None)) + if ids: + conditions.append(BizCustomer.customer_id.in_(ids)) statement = select(BizCustomer).where(*conditions).order_by(BizCustomer.customer_id) return await cls._paginate(db, statement, query_object, is_page) @@ -237,6 +256,9 @@ class MasterDao: conditions.append(BizCustomerSite.site_name.like(f'%{query_object.site_name}%')) if query_object.status: conditions.append(BizCustomerSite.status == query_object.status) + ids = cls._parse_id_list(getattr(query_object, 'ids', None)) + if ids: + conditions.append(BizCustomerSite.site_id.in_(ids)) statement = select(BizCustomerSite).where(*conditions).order_by(BizCustomerSite.site_id) return await cls._paginate(db, statement, query_object, is_page) @@ -298,6 +320,9 @@ class MasterDao: conditions.append(BizProductionLine.line_name.like(f'%{query_object.line_name}%')) if query_object.status: conditions.append(BizProductionLine.status == query_object.status) + ids = cls._parse_id_list(getattr(query_object, 'ids', None)) + if ids: + conditions.append(BizProductionLine.line_id.in_(ids)) statement = select(BizProductionLine).where(*conditions).order_by(BizProductionLine.line_id) return await cls._paginate(db, statement, query_object, is_page) @@ -359,6 +384,9 @@ class MasterDao: conditions.append(BizTunnel.tunnel_name.like(f'%{query_object.tunnel_name}%')) if query_object.status: conditions.append(BizTunnel.status == query_object.status) + ids = cls._parse_id_list(getattr(query_object, 'ids', None)) + if ids: + conditions.append(BizTunnel.tunnel_id.in_(ids)) statement = select(BizTunnel).where(*conditions).order_by(BizTunnel.tunnel_id) return await cls._paginate(db, statement, query_object, is_page) @@ -418,6 +446,9 @@ class MasterDao: conditions.append(BizWarehouse.warehouse_name.like(f'%{query_object.warehouse_name}%')) if query_object.status: conditions.append(BizWarehouse.status == query_object.status) + ids = cls._parse_id_list(getattr(query_object, 'ids', None)) + if ids: + conditions.append(BizWarehouse.warehouse_id.in_(ids)) statement = select(BizWarehouse).where(*conditions).order_by(BizWarehouse.warehouse_id) return await cls._paginate(db, statement, query_object, is_page) @@ -466,6 +497,9 @@ class MasterDao: conditions.append(BizPallet.pallet_code.like(f'%{query_object.pallet_code}%')) if query_object.asset_status: conditions.append(BizPallet.asset_status == query_object.asset_status) + ids = cls._parse_id_list(getattr(query_object, 'ids', None)) + if ids: + conditions.append(BizPallet.pallet_id.in_(ids)) statement = select(BizPallet).where(*conditions).order_by(BizPallet.pallet_id) return await cls._paginate(db, statement, query_object, is_page) diff --git a/shierfeng-fastapi-backend/module_business/entity/vo/master_vo.py b/shierfeng-fastapi-backend/module_business/entity/vo/master_vo.py index 4e7542f..c24fef7 100644 --- a/shierfeng-fastapi-backend/module_business/entity/vo/master_vo.py +++ b/shierfeng-fastapi-backend/module_business/entity/vo/master_vo.py @@ -50,6 +50,7 @@ class StrainPageQueryModel(BaseModel): description='菌种名称', ) status: Literal['0', '1'] | None = Field(default=None, description='状态') + ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)') class DeleteStrainModel(BaseModel): @@ -110,6 +111,7 @@ class CustomerPageQueryModel(BaseModel): description='客户名称', ) status: Literal['0', '1'] | None = Field(default=None, description='状态') + ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)') class DeleteCustomerModel(BaseModel): @@ -167,6 +169,7 @@ class SitePageQueryModel(BaseModel): description='现场名称', ) status: Literal['0', '1'] | None = Field(default=None, description='状态') + ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)') class DeleteSiteModel(BaseModel): @@ -223,6 +226,7 @@ class LinePageQueryModel(BaseModel): description='产线名称', ) status: Literal['0', '1'] | None = Field(default=None, description='状态') + ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)') class DeleteLineModel(BaseModel): @@ -281,6 +285,7 @@ class TunnelPageQueryModel(BaseModel): description='隧道名称', ) status: Literal['0', '1'] | None = Field(default=None, description='状态') + ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)') class DeleteTunnelModel(BaseModel): @@ -342,6 +347,7 @@ class WarehousePageQueryModel(BaseModel): description='仓库名称', ) status: Literal['0', '1'] | None = Field(default=None, description='状态') + ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)') class DeleteWarehouseModel(BaseModel): @@ -416,6 +422,7 @@ class PalletPageQueryModel(BaseModel): page_size: int = Field(default=10, gt=0, le=100, description='每页记录数') pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号') asset_status: Literal['0', '1'] | None = Field(default=None, description='资产状态') + ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)') class DeletePalletModel(BaseModel): diff --git a/shierfeng-fastapi-backend/tests/module_business/test_master_excel_service.py b/shierfeng-fastapi-backend/tests/module_business/test_master_excel_service.py index 2b41bf9..a62bae5 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_master_excel_service.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_master_excel_service.py @@ -318,3 +318,22 @@ class MasterExcelServiceTest(unittest.IsolatedAsyncioTestCase): exported = sheet_rows(empty) self.assertEqual([('菌种编码', '菌种名称', '状态', '备注')], [row[:4] for row in exported]) self.assertEqual(1, len(exported)) + + async def test_export_passes_query_with_ids(self) -> None: + rows = [ + StrainModel(strainId=1, strainCode='S001', strainName='香菇', status='0', remark='甲'), + ] + query = StrainPageQueryModel(ids='1') + list_strain = AsyncMock(return_value=rows) + with patch.dict(_LISTERS, {'strain': list_strain}): + binary = await MasterExcelService.export_resource(self.db, 'strain', query, self.scope) + self.assertFalse(list_strain.await_args.kwargs['is_page']) + self.assertEqual(query, list_strain.await_args.args[1]) + self.assertEqual('1', list_strain.await_args.args[1].ids) + self.assertEqual( + [ + ('菌种编码', '菌种名称', '状态', '备注'), + ('S001', '香菇', '正常', '甲'), + ], + [row[:4] for row in sheet_rows(binary)], + ) diff --git a/shierfeng-fastapi-backend/tests/module_business/test_master_service.py b/shierfeng-fastapi-backend/tests/module_business/test_master_service.py index d85c93a..e91d487 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_master_service.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_master_service.py @@ -737,6 +737,30 @@ class TestMasterDao(unittest.IsolatedAsyncioTestCase): self.assertIs(page, result) self.assertIn(f"biz_strain.strain_code LIKE '%{strain_code}%'", compile_sql(statement)) + async def test_list_master_resources_support_ids_filtering(self) -> None: + page = PageModel(rows=[], pageNum=1, pageSize=10, total=0, hasNext=False) + cases = ( + ('strain', StrainPageQueryModel, 'biz_strain.strain_id IN (1, 2)'), + ('customer', CustomerPageQueryModel, 'biz_customer.customer_id IN (1, 2)'), + ('site', SitePageQueryModel, 'biz_customer_site.site_id IN (1, 2)'), + ('line', LinePageQueryModel, 'biz_production_line.line_id IN (1, 2)'), + ('warehouse', WarehousePageQueryModel, 'biz_warehouse.warehouse_id IN (1, 2)'), + ) + for resource, model_cls, expected_sql in cases: + with ( + self.subTest(resource=resource), + patch.object(PageUtil, 'paginate', new=AsyncMock(return_value=page)) as paginate_mock, + ): + await getattr(MasterDao, f'list_{resource}')( + self.db, + model_cls(ids='1,2,abc'), + TestBatchScope.formal(), + is_page=False, + ) + statement = paginate_mock.await_args.args[1] + sql = compile_sql(statement) + self.assertIn(expected_sql, sql) + async def test_get_queries_use_expected_table_and_primary_key(self) -> None: cases = ( ('strain', 'biz_strain', 'strain_id'), diff --git a/shierfeng-fastapi-backend/tests/module_business/test_vo_contract.py b/shierfeng-fastapi-backend/tests/module_business/test_vo_contract.py index d438435..3fb5d65 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_vo_contract.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_vo_contract.py @@ -98,7 +98,7 @@ MASTER_MODEL_FIELDS = { 'update_time', 'remark', }, - StrainPageQueryModel: {'page_num', 'page_size', 'strain_code', 'strain_name', 'status'}, + StrainPageQueryModel: {'page_num', 'page_size', 'strain_code', 'strain_name', 'status', 'ids'}, DeleteStrainModel: {'strain_ids'}, CustomerModel: { 'customer_id', @@ -111,7 +111,7 @@ MASTER_MODEL_FIELDS = { 'update_time', 'remark', }, - CustomerPageQueryModel: {'page_num', 'page_size', 'customer_code', 'customer_name', 'status'}, + CustomerPageQueryModel: {'page_num', 'page_size', 'customer_code', 'customer_name', 'status', 'ids'}, DeleteCustomerModel: {'customer_ids'}, SiteModel: { 'site_id', @@ -126,7 +126,7 @@ MASTER_MODEL_FIELDS = { 'update_time', 'remark', }, - SitePageQueryModel: {'page_num', 'page_size', 'customer_id', 'site_code', 'site_name', 'status'}, + SitePageQueryModel: {'page_num', 'page_size', 'customer_id', 'site_code', 'site_name', 'status', 'ids'}, DeleteSiteModel: {'site_ids'}, LineModel: { 'line_id', @@ -140,7 +140,7 @@ MASTER_MODEL_FIELDS = { 'update_time', 'remark', }, - LinePageQueryModel: {'page_num', 'page_size', 'site_id', 'line_code', 'line_name', 'status'}, + LinePageQueryModel: {'page_num', 'page_size', 'site_id', 'line_code', 'line_name', 'status', 'ids'}, DeleteLineModel: {'line_ids'}, WarehouseModel: { 'warehouse_id', @@ -154,7 +154,7 @@ MASTER_MODEL_FIELDS = { 'update_time', 'remark', }, - WarehousePageQueryModel: {'page_num', 'page_size', 'warehouse_code', 'warehouse_name', 'status'}, + WarehousePageQueryModel: {'page_num', 'page_size', 'warehouse_code', 'warehouse_name', 'status', 'ids'}, DeleteWarehouseModel: {'warehouse_ids'}, PalletModel: { 'pallet_id', @@ -170,7 +170,7 @@ MASTER_MODEL_FIELDS = { }, PalletCreateModel: {'pallet_code', 'capacity_packages', 'remark'}, PalletEditModel: {'pallet_id', 'capacity_packages', 'asset_status', 'remark'}, - PalletPageQueryModel: {'page_num', 'page_size', 'pallet_code', 'asset_status'}, + PalletPageQueryModel: {'page_num', 'page_size', 'pallet_code', 'asset_status', 'ids'}, DeletePalletModel: {'pallet_ids'}, UserAssignModel: {'user_ids'}, } diff --git a/shierfeng-fastapi-frontend/scripts/verify-business-contract.mjs b/shierfeng-fastapi-frontend/scripts/verify-business-contract.mjs index 5922ea5..395e50f 100644 --- a/shierfeng-fastapi-frontend/scripts/verify-business-contract.mjs +++ b/shierfeng-fastapi-frontend/scripts/verify-business-contract.mjs @@ -1856,7 +1856,7 @@ test('六个基础资料页面逐页满足 API、权限、分页和安全删除 assert.doesNotMatch(source, /\bhandle(?:Import|Export)\b|proxy\.download\(/) assert.match( masterToolbar, - new RegExp(``), + new RegExp(``), `${resource} 必须复用基础资料导入导出组件` ) assert.match( @@ -1979,7 +1979,7 @@ test('隧道资料页复用基础资料导入导出组件', async () => { const masterToolbar = source.match(/]*class="[^"]*\bmaster-toolbar\b[^"]*"[^>]*>[\s\S]*?<\/el-row>/)?.[0] || '' assert.match( masterToolbar, - // + // ) assert.match(masterToolbar, /<\/template>\s*\s*\s*(?:导入|导出)\s*<\/el-button>/) diff --git a/shierfeng-fastapi-frontend/src/components/MasterExcelActions/index.vue b/shierfeng-fastapi-frontend/src/components/MasterExcelActions/index.vue index 2fda332..eacbd6f 100644 --- a/shierfeng-fastapi-frontend/src/components/MasterExcelActions/index.vue +++ b/shierfeng-fastapi-frontend/src/components/MasterExcelActions/index.vue @@ -95,6 +95,10 @@ const props = defineProps({ queryParams: { type: Object, default: () => ({}) + }, + selectedIds: { + type: Array, + default: () => [] } }); const emit = defineEmits(["imported"]); @@ -136,9 +140,13 @@ function downloadTemplate() { } function handleExport() { + const exportParams = { ...props.queryParams }; + if (Array.isArray(props.selectedIds) && props.selectedIds.length > 0) { + exportParams.ids = props.selectedIds.join(","); + } proxy.download( `master/${props.resource}/export`, - { ...props.queryParams }, + exportParams, `${props.resource}_${new Date().getTime()}.xlsx` ); } diff --git a/shierfeng-fastapi-frontend/src/views/master/customer/index.vue b/shierfeng-fastapi-frontend/src/views/master/customer/index.vue index 1b8f8de..50a6172 100644 --- a/shierfeng-fastapi-frontend/src/views/master/customer/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/customer/index.vue @@ -32,7 +32,7 @@ 纳入测试批次 - + diff --git a/shierfeng-fastapi-frontend/src/views/master/line/index.vue b/shierfeng-fastapi-frontend/src/views/master/line/index.vue index 0f4537b..a1e3734 100644 --- a/shierfeng-fastapi-frontend/src/views/master/line/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/line/index.vue @@ -35,7 +35,7 @@ 纳入测试批次 - + diff --git a/shierfeng-fastapi-frontend/src/views/master/pallet/index.vue b/shierfeng-fastapi-frontend/src/views/master/pallet/index.vue index 0f61653..8d5db05 100644 --- a/shierfeng-fastapi-frontend/src/views/master/pallet/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/pallet/index.vue @@ -29,7 +29,7 @@ 纳入测试批次 - + diff --git a/shierfeng-fastapi-frontend/src/views/master/site/index.vue b/shierfeng-fastapi-frontend/src/views/master/site/index.vue index 9bf7d44..a73e529 100644 --- a/shierfeng-fastapi-frontend/src/views/master/site/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/site/index.vue @@ -36,7 +36,7 @@ 纳入测试批次 - + diff --git a/shierfeng-fastapi-frontend/src/views/master/strain/index.vue b/shierfeng-fastapi-frontend/src/views/master/strain/index.vue index 354c1b0..149360a 100644 --- a/shierfeng-fastapi-frontend/src/views/master/strain/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/strain/index.vue @@ -39,7 +39,7 @@ 纳入测试批次 - + diff --git a/shierfeng-fastapi-frontend/src/views/master/tunnel/index.vue b/shierfeng-fastapi-frontend/src/views/master/tunnel/index.vue index d9ab220..7567f35 100644 --- a/shierfeng-fastapi-frontend/src/views/master/tunnel/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/tunnel/index.vue @@ -35,7 +35,7 @@ 纳入测试批次 - + diff --git a/shierfeng-fastapi-frontend/src/views/master/warehouse/index.vue b/shierfeng-fastapi-frontend/src/views/master/warehouse/index.vue index b5e752c..593d84b 100644 --- a/shierfeng-fastapi-frontend/src/views/master/warehouse/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/warehouse/index.vue @@ -33,7 +33,7 @@ 纳入测试批次 - +