This commit is contained in:
2026-09-15 09:58:15 +08:00
parent 3f998bdb88
commit 4bf66d9571
90 changed files with 1599 additions and 1388 deletions
+126 -2
View File
@@ -1,5 +1,6 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { dirname, resolve } from 'node:path'
import ts from 'typescript'
import { describe, expect, it } from 'vitest'
import { compileStyle, parse } from 'vue/compiler-sfc'
@@ -38,11 +39,54 @@ const overseasConsumers = [
'overseas-warehouse/components/SmartDispatchDialog.vue',
'overseas-warehouse/components/DeliveryDocumentsDialog.vue',
]
const overseasBusinessConsumers = [
'overseas-business/pages/PurchaseOrdersView.vue', 'overseas-business/pages/PurchaseOrderDetailView.vue',
'overseas-business/pages/PurchaseOrderEditView.vue', 'overseas-business/pages/TransportOrdersView.vue',
'overseas-business/pages/TransportOrderFormView.vue',
]
const customerServiceConsumers = [
'overseas-warehouse/pages/ExceptionOrdersView.vue', 'overseas-warehouse/pages/CustomerVisitsView.vue',
]
const financeConsumers = [
'finance/pages/FinanceCustomerAccountsView.vue', 'finance/pages/FinanceCustomerArapView.vue',
'finance/pages/FinanceCustomerStatementsView.vue', 'finance/pages/FinanceFormalSettlementPage.vue',
'finance/pages/FinanceReceivableStatisticsView.vue',
]
const generalCatalogConsumers = [
'suppliers/pages/SuppliersView.vue',
'suppliers/pages/SupplierDetailView.vue',
'suppliers/pages/SupplierFormView.vue',
'suppliers/components/SupplierLicenseDialog.vue',
'overseas-master-data/pages/ExchangeRatesView.vue',
'overseas-master-data/pages/OverseasQuotesView.vue',
'overseas-master-data/pages/VehiclesView.vue',
'overseas-master-data/pages/VehicleDetailView.vue',
'master-data/express-companies/pages/ExpressCompaniesView.vue',
'master-data/counterparties/pages/PartnersView.vue',
'fee-items/pages/FeeItemsView.vue',
'warehouses/pages/WarehousesView.vue',
'fba-pricing/carrier-rates/pages/CarrierRatesView.vue',
'fba-pricing/carrier-rates/pages/CarrierRateDetailView.vue',
'downloads/pages/DownloadCenterView.vue',
]
const notificationConsumers = [
'notifications/pages/PlatformNotificationsView.vue', 'notifications/pages/MessageManagementView.vue',
'notifications/pages/MessageTemplatesView.vue', 'notifications/components/MessageComposerDialog.vue',
'notifications/components/TemplateEditorDialog.vue',
]
const systemConsumers = ['iam/pages/InternalUsersView.vue', 'iam/pages/RolesView.vue', 'iam/pages/PermissionsView.vue', 'dashboard/pages/DashboardView.vue']
const consumers = [
...notificationConsumers,
...systemConsumers,
...generalCatalogConsumers,
...financeConsumers,
...customerServiceConsumers,
...overseasBusinessConsumers,
...overseasConsumers,
...cargoConsumers,
'customers/pages/CustomersView.vue', 'customers/pages/CustomerDetailView.vue',
'customers/pages/OverseasStorageFeesView.vue', 'customers/components/CustomerBillingRulesPanel.vue',
'unified-orders/components/InboundFeeDetailManager.vue', 'unified-orders/components/OrderDeclarationEditorDialog.vue',
'customers/CustomerOverseasStorageFeePanel.vue', 'unified-orders/pages/UnifiedOrdersView.vue',
'inbounds/components/InboundTaskList.vue', 'inbounds/measurement/InboundMeasurementDialog.vue',
'inbounds/measurement/InboundMeasurementPanel.vue', 'inbounds/review/MeasurementReviewPanel.vue',
@@ -53,7 +97,7 @@ const consumers = [
'domestic-containers/pages/DomesticContainerInventoryDetailView.vue',
'container-loading/components/ContainerSimulationCreate.vue', 'audit/components/CustomerAuditLogsPanel.vue',
]
const moduleCss = ['customers/customerDensity.css', 'unified-orders/styles/orderDensity.css', 'inbounds/measurement/measurementDensity.css', 'domestic-containers/pages/domesticContainersSummary.css']
const moduleCss = ['iam/iamDensity.css', 'finance/financeDensity.css', 'overseas-business/overseasBusinessDensity.css', 'customers/customerDensity.css', 'unified-orders/styles/orderDensity.css', 'inbounds/measurement/measurementDensity.css', 'domestic-containers/pages/domesticContainersSummary.css']
const read = (file: string) => readFileSync(resolve(process.cwd(), 'src', file), 'utf8')
const domesticPages = [
{ title: '订单列表', path: '/orders', file: 'unified-orders/pages/UnifiedOrdersView.vue' },
@@ -69,6 +113,12 @@ const customerPages = [
{ title: '海外仓阶梯费用', path: '/customers/overseas-storage-fees', file: 'customers/pages/OverseasStorageFeesView.vue' },
]
const densityPages = [...new Set([
...notificationConsumers.filter(file => file.includes('/pages/')),
...systemConsumers,
...generalCatalogConsumers.filter(file => file.includes('/pages/')),
...financeConsumers,
...customerServiceConsumers,
...overseasBusinessConsumers,
...overseasConsumers.filter(file => !file.endsWith('PalletDetailView.vue')),
...cargoConsumers.filter(file => file.includes('/pages/') || ['ContainerOverviewPanel.vue', 'ContainerCustomsPanel.vue', 'ContainerLastLegFeesPanel.vue', 'ContainerFeesPanel.vue', 'OceanBookingDocumentsPanel.vue'].some(name => file.endsWith(name))),
...domesticPages.map(page => page.file), 'loading-lists/pages/PreloadPlanDetailView.vue',
@@ -86,6 +136,7 @@ const classesOf = (node: ElementNode, preload = true, dense = preload, workbench
return flatten(value)
})
const styles = [
...parse(read('shared/components/BatchImportDialog.vue')).descriptor.styles.map(style => ({ file: 'BatchImportDialog.vue', css: style.content })),
...consumers.flatMap(file => parse(read(`modules/${file}`)).descriptor.styles.map(style => ({ file, css: style.content }))),
...moduleCss.map(file => ({ file, css: read(`modules/${file}`) })),
]
@@ -98,6 +149,78 @@ const cssRoot = (css: string) => {
}
describe('approved Admin density ownership', () => {
it('opts application-shell confirmations in even before a business page loads', () => {
const entries = ['app/layouts/AppLayout.vue', 'app/components/WorkspaceTabs.vue', 'app/router/index.ts', 'main.ts']
let count = 0
for (const file of entries) {
const source = read(file)
expect(source, file).toContain("@/styles/adminDensity.css")
const script = file.endsWith('.vue') ? parse(source).descriptor.scriptSetup!.content : source
const tree = ts.createSourceFile(file, script, ts.ScriptTarget.Latest, true)
const visit = (node: ts.Node) => {
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.expression.getText(tree) === 'ElMessageBox') {
count += 1
const options = node.arguments[2]
expect(options && ts.isObjectLiteralExpression(options), file).toBe(true)
const customClass = options && ts.isObjectLiteralExpression(options) ? options.properties.find(prop => ts.isPropertyAssignment(prop) && prop.name.getText(tree) === 'customClass') : undefined
expect(customClass && ts.isPropertyAssignment(customClass) && ts.isStringLiteral(customClass.initializer) && customClass.initializer.text, file).toBe('admin-density-confirm')
}
ts.forEachChild(node, visit)
}
visit(tree)
}
expect(count).toBe(5)
})
it('checks dialog roots along actual static and dynamic Vue imports from approved consumers', () => {
const pending = consumers.map(file => resolve(process.cwd(), 'src/modules', file))
const seen = new Set<string>()
while (pending.length) {
const file = pending.shift()!
if (seen.has(file)) continue
seen.add(file)
const descriptor = parse(readFileSync(file, 'utf8')).descriptor
const enqueue = (reference: string) => {
if (!reference.endsWith('.vue')) return
if (reference.startsWith('@/')) pending.push(resolve(process.cwd(), 'src', reference.slice(2)))
else if (reference.startsWith('.')) pending.push(resolve(dirname(file), reference))
}
for (const script of [descriptor.script, descriptor.scriptSetup]) {
if (!script) continue
const ast = ts.createSourceFile(file, script.content, ts.ScriptTarget.Latest, true)
const visitImports = (node: ts.Node) => {
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.expression.getText(ast) === 'ElMessageBox') {
expect(node.arguments[2]?.getText(ast), file + ': reachable confirmation').toContain('admin-density-confirm')
}
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) enqueue(node.moduleSpecifier.text)
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const reference = node.arguments[0]
if (reference && ts.isStringLiteral(reference)) enqueue(reference.text)
}
ts.forEachChild(node, visitImports)
}
visitImports(ast)
}
if (['AppDialog.vue', 'AppDrawer.vue', 'BatchImportDialog.vue'].some(name => file.endsWith(name))) continue
const visitTemplate = (nodes: TemplateNode[]) => {
for (const node of nodes) {
if (node.type !== 1) continue
if (['AppDialog', 'el-dialog', 'BatchImportDialog'].includes(node.tag)) expect(classesOf(node), `${file}: reachable modal`).toContain('admin-density-dialog')
if (['AppDrawer', 'el-drawer'].includes(node.tag)) expect(classesOf(node), `${file}: reachable drawer`).toContain('admin-density-drawer')
visitTemplate(node.children)
}
}
if (descriptor.template?.ast) visitTemplate(descriptor.template.ast.children)
}
})
it('keeps the supplier license dialog styled at its own teleported root', () => {
const source = read('modules/suppliers/components/SupplierLicenseDialog.vue')
expect(source).toContain('<AppDialog class="admin-density-dialog"')
expect(source).toContain('width="min(560px, calc(100vw - 24px))"')
expect(source).toContain('aria-label="营业执照信息"')
})
it('uses identical authored tab paint for native domestic and Element overseas tabs', () => {
const element = '.admin-density .density-element-tabs > .el-tabs__header .el-tabs__nav .el-tabs__item'
@@ -321,6 +444,7 @@ describe('approved Admin density ownership', () => {
it('prevents page copies of action paint, tab primitives and desktop density dimensions', () => {
for (const { file, css } of styles) cssRoot(css).walkRules(rule => {
expect(rule.selector.trim(), file + ': empty selector').not.toBe('')
expect(rule.selector, file).not.toMatch(/customer-action-button|order-action-button|\.density-action|\.density-tab-icon/)
let desktop = true
for (let parent: typeof rule.parent | ReturnType<typeof cssRoot>['parent'] = rule.parent; parent; parent = parent.parent) {