更新登录页及优化相关功能

This commit is contained in:
焦龙言
2026-10-08 14:13:05 +08:00
parent 9a14b692e3
commit 56e646bcc1
139 changed files with 77587 additions and 811 deletions
@@ -21,10 +21,14 @@ CONTROLLER_NAMES = (
'inbound',
'filling',
'shipment',
'shipment_receipt',
'usage',
'usage_fifo',
'return',
'report',
'test_batch',
'dashboard',
'tunnel',
)
TARGET_PREFIXES = (
'/master',
@@ -43,6 +47,7 @@ EXPECTED_PAGE_PERMISSIONS = frozenset(
'master:line:list',
'master:warehouse:list',
'master:pallet:list',
'master:tunnel:list',
'business:inbound:list',
'business:filling:list',
'business:shipment:list',
@@ -80,6 +85,10 @@ EXPECTED_BUTTON_PERMISSIONS = frozenset(
'master:pallet:add',
'master:pallet:edit',
'master:pallet:print',
'master:tunnel:query',
'master:tunnel:add',
'master:tunnel:edit',
'master:tunnel:remove',
'business:inbound:query',
'business:inbound:add',
'business:inbound:edit',
@@ -90,6 +99,7 @@ EXPECTED_BUTTON_PERMISSIONS = frozenset(
'business:shipment:add',
'business:shipment:edit',
'business:shipment:confirm',
'business:shipment:receive',
'business:usage:query',
'business:usage:add',
'business:return:query',
@@ -108,7 +118,10 @@ EXPECTED_PERMISSIONS = EXPECTED_PAGE_PERMISSIONS | EXPECTED_BUTTON_PERMISSIONS
EXPECTED_WEB_PERMISSIONS = frozenset(
permission
for permission in EXPECTED_PERMISSIONS
if not permission.startswith('system:testbatch:') or permission == 'system:testbatch:adopt'
if (
(not permission.startswith('system:testbatch:') or permission == 'system:testbatch:adopt')
and permission != 'master:tunnel:remove'
)
)
VUE_PAGE_PATHS = (
'src/views/master/strain/index.vue',
@@ -117,6 +130,7 @@ VUE_PAGE_PATHS = (
'src/views/master/line/index.vue',
'src/views/master/warehouse/index.vue',
'src/views/master/pallet/index.vue',
'src/views/master/tunnel/index.vue',
'src/views/business/inbound/index.vue',
'src/views/business/filling/index.vue',
'src/views/business/shipment/index.vue',
@@ -130,7 +144,10 @@ VUE_PAGE_PATHS = (
'src/views/report/palletTrace/index.vue',
)
PERMISSION_LITERAL_PATTERN = re.compile(r'(?:master|business|report|system):[a-z]+:[a-z]+')
UNGUARDED_AUTHENTICATED_ROUTES = {('/system/test-batch/options', 'GET')}
UNGUARDED_AUTHENTICATED_ROUTES = {
('/system/test-batch/options', 'GET'),
('/business/dashboard/scan-exceptions', 'GET'),
}
REMOVED_DELETE_SYMBOLS = (
'delStrain',
'delCustomer',
@@ -170,6 +187,16 @@ def _build_expected_route_permissions() -> dict[tuple[str, str], str]:
}
)
expected.update(
{
('/master/tunnel/list', 'GET'): 'master:tunnel:list',
('/master/tunnel/{tunnel_id}', 'GET'): 'master:tunnel:query',
('/master/tunnel', 'POST'): 'master:tunnel:add',
('/master/tunnel', 'PUT'): 'master:tunnel:edit',
('/master/tunnel/{tunnel_ids}', 'DELETE'): 'master:tunnel:remove',
}
)
expected.update(
{
('/master/site/{site_id}/users', 'GET'): 'master:site:assign',
@@ -195,12 +222,20 @@ def _build_expected_route_permissions() -> dict[tuple[str, str], str]:
('/business/shipment/site-options', 'GET'): 'business:shipment:query',
('/business/shipment/scan/{qr_token}', 'GET'): 'business:shipment:query',
('/business/shipment/{shipment_id}', 'GET'): 'business:shipment:query',
('/business/shipment/{shipment_id}/labels', 'GET'): 'business:shipment:query',
('/business/shipment-receipt/list', 'GET'): 'business:shipment:receive',
('/business/shipment-receipt/{shipment_id}', 'GET'): 'business:shipment:receive',
('/business/shipment-receipt/{shipment_id}/scan/{identifier}', 'GET'): 'business:shipment:receive',
('/business/shipment-receipt/{shipment_id}/confirm', 'PUT'): 'business:shipment:receive',
('/business/usage-fifo/recommendation', 'GET'): 'business:usage:query',
('/business/shipment', 'POST'): 'business:shipment:add',
('/business/shipment', 'PUT'): 'business:shipment:edit',
('/business/shipment/{shipment_ids}', 'DELETE'): 'business:shipment:list',
('/business/shipment/{shipment_id}/confirm', 'PUT'): 'business:shipment:confirm',
('/business/usage/list', 'GET'): 'business:usage:list',
('/business/usage/line-options', 'GET'): 'business:usage:query',
('/business/usage/tunnel-options', 'GET'): 'business:usage:query',
('/business/usage/enter-tunnel', 'POST'): 'business:usage:add',
('/business/usage/scan/{qr_token}', 'GET'): 'business:usage:query',
('/business/usage/{usage_id}', 'GET'): 'business:usage:query',
('/business/usage', 'POST'): 'business:usage:add',
@@ -249,6 +284,7 @@ STATIC_READS_BEFORE_DETAIL = {
),
'/business/usage/{usage_id}': (
'/business/usage/line-options',
'/business/usage/tunnel-options',
'/business/usage/scan/{qr_token}',
),
'/business/return/{return_id}': (
@@ -264,6 +300,7 @@ EXACT_QUERY_PARAMETERS = {
('/business/inbound/scan/{qr_token}', 'GET'): {'warehouseId'},
('/business/shipment/scan/{qr_token}', 'GET'): {'sourceWarehouseId', 'targetSiteId'},
('/business/usage/line-options', 'GET'): {'siteId'},
('/business/usage/tunnel-options', 'GET'): {'siteId'},
('/business/return/scan/{qr_token}', 'GET'): {'mode', 'returnId'},
}
REQUIRED_QUERY_PARAMETERS = {
@@ -286,7 +323,7 @@ def _auth_dependencies(route: APIRoute) -> list[CheckUserInterfaceAuth]:
def _assert_controller_permission_literals(test_case: unittest.TestCase, literals: list[str]) -> None:
expected = Counter(EXPECTED_ROUTE_PERMISSIONS.values())
test_case.assertEqual(85, expected.total())
test_case.assertEqual(98, expected.total())
test_case.assertEqual(expected, Counter(literals))
test_case.assertEqual(EXPECTED_PERMISSIONS, frozenset(expected))
@@ -409,7 +446,7 @@ class ApiPermissionContractTest(unittest.TestCase):
cls.app = create_app()
cls.schema = cls.app.openapi()
cls.target_routes = [
route for route in cls.app.routes if isinstance(route, APIRoute) and _is_target_path(route.path)
route for route in cls.app.routes if isinstance(route, APIRoute) and _is_target_path(route.path_format)
]
def test_target_path_selector_uses_namespace_boundaries(self) -> None:
@@ -438,11 +475,11 @@ class ApiPermissionContractTest(unittest.TestCase):
}
self.assertEqual(set(EXPECTED_ROUTE_PERMISSIONS) | UNGUARDED_AUTHENTICATED_ROUTES, actual_operations)
self.assertEqual(86, len(actual_operations))
self.assertEqual(100, len(actual_operations))
openapi_ai_paths = sorted(path for path in self.schema['paths'] if path == '/ai' or path.startswith('/ai/'))
app_ai_paths = sorted(
route.path
route.path_format
for route in self.app.routes
if getattr(route, 'path', None) == '/ai' or getattr(route, 'path', '').startswith('/ai/')
)
@@ -454,8 +491,8 @@ class ApiPermissionContractTest(unittest.TestCase):
operation_keys: list[tuple[str, str]] = []
for route in self.target_routes:
methods = route.methods & ROUTE_HTTP_METHODS
self.assertEqual(1, len(methods), route.path)
key = (route.path, next(iter(methods)))
self.assertEqual(1, len(methods), route.path_format)
key = (route.path_format, next(iter(methods)))
operation_keys.append(key)
auth_dependencies = _auth_dependencies(route)
@@ -491,19 +528,33 @@ class ApiPermissionContractTest(unittest.TestCase):
literals.append(node.args[0].value)
self.assertEqual([], malformed_calls)
self.assertEqual(18, len(EXPECTED_PAGE_PERMISSIONS))
self.assertEqual(43, len(EXPECTED_BUTTON_PERMISSIONS))
self.assertEqual(19, len(EXPECTED_PAGE_PERMISSIONS))
self.assertEqual(48, len(EXPECTED_BUTTON_PERMISSIONS))
self.assertFalse(EXPECTED_PAGE_PERMISSIONS & EXPECTED_BUTTON_PERMISSIONS)
self.assertEqual(61, len(EXPECTED_PERMISSIONS))
self.assertEqual(67, len(EXPECTED_PERMISSIONS))
_assert_controller_permission_literals(self, literals)
self.assertEqual(
{'add', 'adopt', 'assign', 'close', 'confirm', 'edit', 'list', 'print', 'purge', 'query', 'request'},
{
'add',
'adopt',
'assign',
'close',
'confirm',
'edit',
'list',
'print',
'purge',
'query',
'remove',
'request',
'receive',
},
{permission.rsplit(':', maxsplit=1)[1] for permission in literals},
)
def test_controller_ast_permission_contract_rejects_duplicate_call(self) -> None:
literals = list(EXPECTED_ROUTE_PERMISSIONS.values())
self.assertEqual(85, len(literals))
self.assertEqual(98, len(literals))
literals.append(literals[0])
with self.assertRaises(AssertionError):
@@ -521,8 +572,8 @@ class ApiPermissionContractTest(unittest.TestCase):
def test_vue_pages_have_exact_guarded_permission_contract(self) -> None:
actual_permissions = set()
self.assertEqual(17, len(VUE_PAGE_PATHS))
self.assertEqual(17, len(set(VUE_PAGE_PATHS)))
self.assertEqual(18, len(VUE_PAGE_PATHS))
self.assertEqual(18, len(set(VUE_PAGE_PATHS)))
for relative_path in VUE_PAGE_PATHS:
source = (FRONTEND_ROOT / relative_path).read_text(encoding='utf-8')
guarded = _extract_guarded_vue_permissions(source)
@@ -663,7 +714,7 @@ class ApiPermissionContractTest(unittest.TestCase):
def test_static_scan_and_option_routes_precede_dynamic_details(self) -> None:
route_order = {
route.path: index
route.path_format: index
for index, route in enumerate(self.app.routes)
if isinstance(route, APIRoute) and 'GET' in route.methods
}
@@ -705,9 +756,9 @@ class ApiPermissionContractTest(unittest.TestCase):
actual_write_routes: dict[tuple[str, str], APIRoute] = {}
for route in self.target_routes:
for method in route.methods & WRITE_METHODS:
actual_write_routes[(route.path, method)] = route
actual_write_routes[(route.path_format, method)] = route
self.assertEqual(41, len(expected_write_permissions))
self.assertEqual(46, len(expected_write_permissions))
self.assertEqual(set(expected_write_permissions), set(actual_write_routes))
for key, expected_permission in expected_write_permissions.items():
with self.subTest(operation=key):