This commit is contained in:
2026-10-21 13:22:42 +08:00
parent eb0b0f85d1
commit 2333bd8bab
114 changed files with 1766 additions and 526 deletions
+12 -4
View File
@@ -10,11 +10,15 @@
--cc-list-query-font-size: var(--admin-font-body);
font-size: var(--admin-font-body, 13px);
}
.admin-density .surface-panel, .admin-density .query-toolbar {
.admin-density .surface-panel {
border-radius: var(--admin-radius, 4px); box-shadow: none;
}
.admin-density .query-toolbar {
border-radius: var(--cc-list-query-radius, 5px); box-shadow: none;
}
.admin-density .el-table, .admin-density-dialog .el-table {
--el-table-header-bg-color: var(--admin-label-bg);
--el-table-header-bg-color: var(--cc-list-table-header-bg);
--el-table-header-text-color: var(--cc-list-table-header-text);
--el-table-border-color: var(--admin-table-border);
--el-table-row-hover-bg-color: var(--admin-row-hover);
font-size: var(--admin-font-body, 13px);
@@ -22,7 +26,7 @@
.admin-density .el-table .el-table__cell, .admin-density .data-table .el-table .el-table__cell, .admin-density-dialog .el-table .el-table__cell { height: var(--admin-row-height, 36px); padding: var(--admin-space-1, 4px) 0; }
.admin-density .el-table .cell, .admin-density-dialog .el-table .cell { line-height: 22px; }
.admin-density .el-table .el-button--small, .admin-density-dialog .el-table .el-button--small { height: var(--admin-action-height, 28px); padding: var(--admin-space-1, 4px) var(--admin-space-3, 8px); }
.admin-density .el-table__body tr.el-table__row--striped td.el-table__cell { background: var(--admin-row-stripe); }
.admin-density .el-table__body tr.el-table__row--striped:not(:where(:hover, .hover-row, .current-row)) > td.el-table__cell:not(:where(.hover-cell)) { background: var(--admin-row-stripe); }
.admin-density .data-table .data-table__pagination { min-height: 48px; padding: var(--admin-space-3, 8px) var(--admin-space-5, 12px); }
.admin-density .el-empty { padding: var(--admin-space-5, 12px) 0; }
.admin-density .el-empty__image { display: none; }
@@ -30,6 +34,10 @@
.admin-density .el-empty__description p { font-size: var(--admin-font-body, 13px); }
.admin-density-dialog .el-form-item__label { line-height: 22px; }
.admin-density-dialog .el-form--inline .el-form-item__label { align-items: center; }
:is(.admin-density, .admin-density-dialog, .admin-density-drawer) .el-table th.el-table__cell {
background: var(--cc-list-table-header-bg);
color: var(--cc-list-table-header-text);
}
.admin-density-dialog .el-descriptions__label.el-descriptions__cell { font-size: var(--admin-font-caption, 12px); background: var(--admin-label-bg); }
.admin-density-dialog .el-descriptions__content.el-descriptions__cell { font-size: var(--admin-font-body, 13px); overflow-wrap: anywhere; }
.admin-density-dialog.el-dialog.is-fullscreen { width: 100%; max-width: 100%; height: 100vh; max-height: 100vh; margin: 0; border-radius: 0; }
@@ -260,7 +268,7 @@
/* Existing native data tables keep their business column widths and sticky cells. */
:is(.admin-density, .admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-table { width: 100%; border-collapse: collapse; font-size: var(--admin-font-body); }
:is(.admin-density, .admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-table :is(th, td) { height: var(--admin-row-height); padding: var(--admin-space-1) var(--admin-space-3); border: 1px solid var(--admin-table-border); text-align: left; vertical-align: middle; overflow-wrap: anywhere; }
:is(.admin-density, .admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-table th { background: var(--admin-label-bg); color: var(--cc-muted); font-weight: 600; }
:is(.admin-density, .admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-table th { background: var(--cc-list-table-header-bg); color: var(--cc-list-table-header-text); font-weight: 600; }
:is(.admin-density, .admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-table td { background: var(--admin-surface); }
:is(.admin-density, .admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-table tbody tr:nth-child(even) td { background: var(--admin-row-stripe); }
:is(.admin-density, .admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-table tbody tr:hover td { background: var(--admin-row-hover); }
+66 -7
View File
@@ -217,6 +217,40 @@ const cssRoot = (css: string) => {
}
describe('approved Admin density ownership', () => {
it('separates shared list filters with the same 5px radius across density themes', () => {
const declarations = (source: string, selector: string) => {
const values: Record<string, string> = {}
cssRoot(source).walkRules(rule => {
if (rule.selector === selector) rule.walkDecls(decl => { values[decl.prop] = decl.value })
})
return values
}
const foundation = declarations(read('styles/foundation.css'), ':root')
expect(foundation['--cc-list-query-radius']).toBe('5px')
expect(foundation['--cc-list-query-gap']).toBe('16px')
expect(foundation['--cc-list-table-header-bg']).toBe('#edf4ff')
expect(foundation['--cc-list-table-header-text']).toBe('#284b78')
expect(declarations(shared, ':is(.admin-density, .admin-density-dialog, .admin-density-drawer) .el-table th.el-table__cell')).toEqual({
background: 'var(--cc-list-table-header-bg)',
color: 'var(--cc-list-table-header-text)',
})
const table = parse(read('shared/components/DataTable.vue')).descriptor.styles.map(style => style.content).join('\n')
expect(declarations(table, '.data-table > .data-table__filters')).toMatchObject({
'margin-bottom': 'var(--cc-list-query-gap, 16px)',
'border-radius': 'var(--cc-list-query-radius, 5px)',
border: '1px solid var(--list-card-border, var(--cc-border))',
})
const headerSelector = '.data-table > .data-table__toolbar:has(+ .data-table__filters)'
expect(declarations(table, headerSelector)['border-bottom']).toBe('0')
const fixture = document.createElement('div')
fixture.innerHTML = '<div class="data-table"><div id="with-filters" class="data-table__toolbar"></div><div class="data-table__filters"></div></div><div class="data-table"><div id="without-filters" class="data-table__toolbar"></div><div class="el-table"></div></div>'
expect(fixture.querySelector('#with-filters')!.matches(headerSelector)).toBe(true)
expect(fixture.querySelector('#without-filters')!.matches(headerSelector)).toBe(false)
expect(declarations(shared, '.admin-density .query-toolbar')['border-radius']).toBe('var(--cc-list-query-radius, 5px)')
expect(declarations(shared, '.admin-density .surface-panel')['border-radius']).toBe('var(--admin-radius, 4px)')
const query = parse(read('shared/components/QueryToolbar.vue')).descriptor.styles.map(style => style.content).join('\n')
expect(declarations(query, '.query-toolbar')['border-radius']).toBe('var(--cc-list-query-radius, 5px)')
})
it('keeps sailing cutoff controls compact and delegates editor borders to the shared opt-in', () => {
const view = parse(read('modules/sailing-calendar/pages/SailingCalendarView.vue')).descriptor
const rules = new Map<string, Record<string, string>>()
@@ -1024,7 +1058,23 @@ describe('approved Admin density ownership', () => {
rule.walkDecls(decl => { declarations[decl.prop] = decl.value })
rules.set(rule.selector, declarations)
})
expect(rules.get('.admin-density .el-table__body tr.el-table__row--striped td.el-table__cell')?.background).toBe('var(--admin-row-stripe)')
const stripeSelector = '.admin-density .el-table__body tr.el-table__row--striped:not(:where(:hover, .hover-row, .current-row)) > td.el-table__cell:not(:where(.hover-cell))'
expect(rules.get(stripeSelector)?.background).toBe('var(--admin-row-stripe)')
const stripeFixture = document.createElement('div')
stripeFixture.innerHTML = '<div class="admin-density"><table class="el-table__body"><tbody><tr class="el-table__row--striped"><td class="el-table__cell"></td></tr></tbody></table></div>'
const row = stripeFixture.querySelector('tr')!
const cell = stripeFixture.querySelector('td')!
expect(cell.matches(stripeSelector)).toBe(true)
for (const state of ['hover-row', 'current-row']) {
row.classList.add(state)
expect(cell.matches(stripeSelector)).toBe(false)
row.classList.remove(state)
}
cell.classList.add('hover-cell')
expect(cell.matches(stripeSelector)).toBe(false)
cell.classList.remove('hover-cell')
cell.innerHTML = '<table class="el-table__body"><tbody><tr><td class="el-table__cell"></td></tr></tbody></table>'
expect(cell.querySelector('td')!.matches(stripeSelector)).toBe(false)
expect(rules.get('.admin-density-dialog .el-descriptions__label.el-descriptions__cell')?.background).toBe('var(--admin-label-bg)')
expect(rules.get('.admin-density-dialog .el-form--inline .el-form-item__label')?.['align-items']).toBe('center')
expect(rules.get('.admin-density-dialog.el-dialog.admin-density-workspace > .el-dialog__body')?.flex).toBe('1 1 auto')
@@ -1215,12 +1265,14 @@ describe('approved Admin density ownership', () => {
expect(detail).not.toContain('.fee-form-grid')
expect(read('modules/overseas-warehouse/components/WarehouseTransferReviewDialog.vue')).toContain('data-tone="source"')
})
it('keeps new dispatch settings and resources on shared continuous sections with body scrolling', () => {
it('keeps dispatch planning on shared sections and cargo selection in the three-column board', () => {
const source = read('modules/overseas-warehouse/components/SmartDispatchDialog.vue')
const board = read('modules/overseas-warehouse/components/SmartDispatchCargoBoard.vue')
const fields = read('modules/overseas-warehouse/components/DispatchResourceFields.vue')
expect(source).toContain('fullscreenable')
expect(source).toContain('density-table density-table--editable dispatch-create-table')
expect(source).toContain('<DispatchResourceFields continuous')
expect(source).not.toContain('<DispatchResourceFields')
expect(source).toContain('<SmartDispatchCargoBoard')
expect(source).toContain('label-position="left" label-width="130px"')
expect(source).toContain('class="density-dialog-section dispatch-setup"')
expect(source).toContain('class="density-dialog-section dispatch-draft-toolbar"')
@@ -1228,9 +1280,12 @@ describe('approved Admin density ownership', () => {
expect(source).not.toContain('data-tone="work"')
expect(source).not.toContain('data-tone="summary"')
expect(source).not.toContain('data-tone="source"')
expect(source).toContain('class="cargo-workspace"')
expect(source).toContain('<LineClampText')
expect(source).toContain('class="dispatch-source-group"')
expect(board).toContain('class="dispatch-cargo-board"')
expect(board).toContain('class="density-table cargo-board-table"')
expect(board).toContain('aria-label="待排订单"')
expect(board).toContain('aria-label="托盘明细"')
expect(board).toContain('aria-label="车辆装载"')
expect(board).toContain('class="cargo-board-group-details"')
expect(source).not.toContain('height="420"')
expect(source).not.toContain('class="create-grid"')
expect(fields).toContain('continuous?: boolean')
@@ -1240,6 +1295,7 @@ describe('approved Admin density ownership', () => {
})
it('keeps dispatch workflow, delivery arrangements and owned evidence on shared compact sections', () => {
const workflow = read('modules/overseas-warehouse/components/DispatchWorkflowDialog.vue')
const cargo = read('modules/overseas-warehouse/components/SmartDispatchExistingCargo.vue')
const arrangement = read('modules/overseas-warehouse/components/DispatchArrangeFields.vue')
const evidence = read('modules/overseas-warehouse/components/DispatchEvidenceFiles.vue')
expect(workflow).toContain(':fullscreenable="width !== \'560px\'"')
@@ -1247,7 +1303,10 @@ describe('approved Admin density ownership', () => {
expect(workflow).toContain('density-table density-table--editable workflow-editor-table')
expect(workflow).toContain('label-position="left" label-width="130px"')
expect(workflow).toContain(':confirm-loading="busy || uploadingEvidence"')
expect(workflow).toContain('<LineClampText')
expect(workflow).not.toContain('aria-label="候选货物分页"')
expect(cargo).toContain('class="existing-cargo density-dialog-sections"')
expect(cargo).toContain('<SmartDispatchCargoBoard')
expect(cargo).toContain('aria-label="当前排车货物"')
for (const tone of ['source', 'work']) {
expect(workflow).toContain(`data-tone="${tone}"`)
expect(arrangement).toContain(`data-tone="${tone}"`)
+15 -7
View File
@@ -365,12 +365,12 @@
/* DataTable retains ownership of toolbar size, controls and query layout. */
.admin-workbench .query-toolbar.workbench-query {
border-color: var(--workbench-border);
border-radius: var(--workbench-radius);
border-radius: var(--cc-list-query-radius, 5px);
background: var(--workbench-surface);
box-shadow: var(--workbench-shadow);
}
/* Integrated filters share the table surface; never nest another query card. */
/* DataTable owns the rounded filter block; its inner toolbar stays transparent. */
.admin-workbench .data-table--integrated > .data-table__filters .query-toolbar.workbench-query {
border-radius: 0;
background: transparent;
@@ -433,7 +433,7 @@
--el-component-size: var(--admin-control-height, 32px);
min-height: var(--workbench-list-toolbar-height);
box-sizing: border-box;
padding: var(--workbench-list-toolbar-padding) var(--workbench-list-inset);
padding: var(--workbench-list-toolbar-padding) 0;
gap: 8px 12px;
flex-wrap: wrap;
}
@@ -473,7 +473,15 @@
gap: 8px 12px;
}
/* The toolbar supplies the inset; moved tabs must not add a second frame. */
.admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__pagination {
min-height: var(--workbench-list-pagination-height);
box-sizing: border-box;
padding: var(--workbench-list-pagination-padding) var(--workbench-list-inset);
gap: 8px 12px;
flex-wrap: wrap;
}
/* Toolbar content aligns with the page edge; moved tabs add no extra frame. */
.admin-workbench .data-table__toolbar-start .workbench-list-tabs {
min-width: 0;
margin: 0;
@@ -509,8 +517,8 @@
.admin-workbench .workbench-table > .data-table > .el-table,
.admin-workbench .workbench-table > .el-table {
--el-table-border-color: var(--workbench-border);
--el-table-header-bg-color: var(--workbench-surface-soft);
--el-table-header-text-color: var(--workbench-muted);
--el-table-header-bg-color: var(--cc-list-table-header-bg);
--el-table-header-text-color: var(--cc-list-table-header-text);
--el-table-row-hover-bg-color: var(--workbench-accent-soft);
--el-table-current-row-bg-color: var(--workbench-accent-soft);
--el-table-text-color: var(--workbench-text);
@@ -518,7 +526,7 @@
}
.admin-workbench .workbench-table .el-table th.el-table__cell {
color: var(--workbench-muted);
color: var(--cc-list-table-header-text);
}
.admin-workbench .el-button.density-action:not([data-tone]),
+35 -4
View File
@@ -134,7 +134,7 @@ describe('Admin workbench opt-in styles', () => {
const standalone = declarationsFor(get('#standalone'))
const integrated = declarationsFor(get('#integrated'))
expect(standalone.get('border-color')).toBe('var(--workbench-border)')
expect(standalone.get('border-radius')).toBe('var(--workbench-radius)')
expect(standalone.get('border-radius')).toBe('var(--cc-list-query-radius, 5px)')
expect(standalone.get('box-shadow')).toBe('var(--workbench-shadow)')
expect(integrated.get('background')).toBe('transparent')
expect(integrated.get('border-radius')).toBe('0')
@@ -242,6 +242,7 @@ describe('Admin workbench opt-in styles', () => {
</div>
<div class="data-table__filters"><div id="${id}-query" class="query-toolbar"><div class="query-toolbar__filters"></div></div></div>
<div id="${id}-body" class="el-table"></div>
<footer id="${id}-pagination" class="data-table__pagination"></footer>
</div>`
const get = fixture(`
<main class="admin-page admin-density admin-workbench">
@@ -253,19 +254,48 @@ describe('Admin workbench opt-in styles', () => {
const header = declarationsFor(get('#main-header'))
const query = declarationsFor(get('#main-query'))
expect(header.get('min-height')).toBe('var(--workbench-list-toolbar-height)')
expect(header.get('padding')).toBe('var(--workbench-list-toolbar-padding) var(--workbench-list-inset)')
expect(header.get('padding')).toBe('var(--workbench-list-toolbar-padding) 0')
expect(query.get('padding')).toBe('var(--workbench-list-query-padding) var(--workbench-list-inset)')
expect(header.get('flex-wrap')).toBe('wrap')
expect(header.has('height')).toBe(false)
expect(declarationsFor(get('#main-actions')).get('max-width')).toBe('100%')
const pagination = declarationsFor(get('#main-pagination'))
expect(pagination.get('min-height')).toBe('var(--workbench-list-pagination-height)')
expect(pagination.get('padding')).toBe('var(--workbench-list-pagination-padding) var(--workbench-list-inset)')
expect(pagination.get('box-sizing')).toBe('border-box')
expect(pagination.get('flex-wrap')).toBe('wrap')
expect(pagination.has('height')).toBe(false)
expect(declarationsFor(get('#main-tabs')).get('padding')).toBe('0')
expect(declarationsFor(get('#main-tabs')).get('background')).toBe('transparent')
for (const selector of ['#nested-header', '#outside-header', '#nested-query', '#outside-query', '#main-body']) {
for (const selector of ['#main-tabs', '#nested-tabs', '#outside-tabs', '#main-actions', '#main-query']) {
expect(declarationsFor(get(selector)).has('margin-inline-start')).toBe(false)
}
for (const selector of ['#nested-header', '#outside-header', '#nested-query', '#outside-query', '#nested-pagination', '#outside-pagination', '#main-body']) {
expect(declarationsFor(get(selector)).has('padding')).toBe(false)
expect(declarationsFor(get(selector)).has('height')).toBe(false)
}
})
it('keeps status tabs and adjacent toolbar content free of negative inset compensation', () => {
const get = fixture(`
<main class="admin-workbench"><div class="workbench-table is-page-surface"><div class="data-table">
<div class="data-table__toolbar"><div class="data-table__toolbar-start">
<button id="return" class="el-button">返回预约工作</button>
<nav id="status" class="workbench-list-tabs density-status-tabs"><button>装车前</button></nav>
<span id="summary" class="workbench-badge">数量</span>
<div id="radio" class="workbench-list-tabs"><input type="radio" /></div>
<div id="pills" class="workbench-list-tabs workbench-tabs"><button>库存周转</button></div>
<div class="data-table"><div class="data-table__toolbar"><div class="data-table__toolbar-start">
<nav id="inner" class="workbench-list-tabs density-status-tabs"><button>子表</button></nav>
</div></div></div>
</div></div>
</div></div></main>
`)
for (const id of ['status', 'return', 'summary', 'radio', 'pills', 'inner']) {
expect(declarationsFor(get(`#${id}`)).has('margin-inline-start')).toBe(false)
}
})
it('aligns wrapped native tables and dialog actions without repainting semantic actions', () => {
const get = fixture(`
<section class="admin-workbench is-dialog admin-density-dialog">
@@ -277,7 +307,8 @@ describe('Admin workbench opt-in styles', () => {
<div class="workbench-table"><div id="outside" class="el-table"></div></div>
`)
expect(declarationsFor(get('#native'))).toEqual(declarationsFor(get('#shared')))
expect(declarationsFor(get('#native')).get('--el-table-header-bg-color')).toBe('var(--workbench-surface-soft)')
expect(declarationsFor(get('#native')).get('--el-table-header-bg-color')).toBe('var(--cc-list-table-header-bg)')
expect(declarationsFor(get('#native')).get('--el-table-header-text-color')).toBe('var(--cc-list-table-header-text)')
expect(declarationsFor(get('#primary')).get('--action-color')).toBe('var(--workbench-primary)')
expect(declarationsFor(get('#danger')).get('--action-color')).toBeUndefined()
expect(declarationsFor(get('#outside')).size).toBe(0)
+2
View File
@@ -19,6 +19,8 @@
--workbench-list-toolbar-height: 48px;
--workbench-list-toolbar-padding: 6px;
--workbench-list-query-padding: 8px;
--workbench-list-pagination-height: 48px;
--workbench-list-pagination-padding: 6px;
--workbench-page-space: 24px;
--workbench-section-gap: 16px;
--workbench-grid-gap: 18px;
+4
View File
@@ -46,8 +46,12 @@
--cc-list-page-title-font-size: 20px;
--cc-list-page-description-font-size: 12px;
--cc-list-query-font-size: 14px;
--cc-list-query-radius: 5px;
--cc-list-query-gap: 16px;
--cc-list-table-font-size: 12px;
--cc-list-table-header-font-weight: 600;
--cc-list-table-header-bg: #edf4ff;
--cc-list-table-header-text: #284b78;
--cc-list-primary-font-size: 12px;
--cc-list-secondary-font-size: 12px;
--cc-list-status-font-size: 11px;