优化
This commit is contained in:
@@ -7,6 +7,7 @@ import { resolve } from 'node:path'
|
||||
import { compileStyle, parse } from 'vue/compiler-sfc'
|
||||
import AppDialog from './AppDialog.vue'
|
||||
import AppDrawer from './AppDrawer.vue'
|
||||
import DataTable from './DataTable.vue'
|
||||
|
||||
let wrapper: VueWrapper | undefined
|
||||
const styles: HTMLStyleElement[] = []
|
||||
@@ -18,10 +19,10 @@ function installStyles() {
|
||||
document.head.append(style)
|
||||
styles.push(style)
|
||||
}
|
||||
for (const name of ['base', 'el-dialog', 'el-drawer', 'el-form', 'el-form-item', 'el-button']) {
|
||||
for (const name of ['base', 'el-dialog', 'el-drawer', 'el-form', 'el-form-item', 'el-select', 'el-button']) {
|
||||
add(readFileSync(resolve(process.cwd(), `node_modules/element-plus/theme-chalk/${name}.css`), 'utf8'))
|
||||
}
|
||||
for (const [name, component] of [['AppDialog', AppDialog], ['AppDrawer', AppDrawer]] as const) {
|
||||
for (const [name, component] of [['AppDialog', AppDialog], ['AppDrawer', AppDrawer], ['DataTable', DataTable]] as const) {
|
||||
const source = readFileSync(resolve(process.cwd(), `src/shared/components/${name}.vue`), 'utf8')
|
||||
for (const style of parse(source).descriptor.styles) {
|
||||
const compiled = compileStyle({ source: style.content, filename: `${name}.vue`, scoped: style.scoped, id: component.__scopeId || 'data-v-layout' })
|
||||
@@ -37,7 +38,7 @@ function installStyles() {
|
||||
async function render(template: string) {
|
||||
installStyles()
|
||||
wrapper = mount(defineComponent({
|
||||
components: { AppDialog, AppDrawer },
|
||||
components: { AppDialog, AppDrawer, DataTable },
|
||||
setup: () => ({ open: ref(true), childOpen: ref(true), name: ref(''), valid: ref(false) }),
|
||||
template,
|
||||
}), { attachTo: document.body, global: { plugins: [ElementPlus] } })
|
||||
@@ -60,23 +61,95 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('real Element floating frame boundaries', () => {
|
||||
it.each(['workbench-table', 'workbench-toolbar'])('isolates %s toolbar actions from nested dialogs while preserving wrappers and dropdowns', async (surface) => {
|
||||
await render(`
|
||||
<main class="admin-workbench admin-density"><div class="${surface} is-page-surface">
|
||||
<DataTable :data="[]" :columns="[]" appearance="integrated" column-settings-mode="hide" :show-pagination="false">
|
||||
<template #toolbar-start><el-button class="start-action">返回</el-button></template>
|
||||
<template #toolbar-actions>
|
||||
<el-button type="primary" class="direct-action">新增</el-button>
|
||||
<div class="action-wrapper"><el-button type="primary" class="wrapped-action">批量操作</el-button>
|
||||
<el-dropdown><el-button type="primary" class="dropdown-action">更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item>导出</el-dropdown-item></el-dropdown-menu></template></el-dropdown>
|
||||
<el-dropdown split-button type="primary" class="split-actions">组合操作<template #dropdown><el-dropdown-menu><el-dropdown-item>另存</el-dropdown-item></el-dropdown-menu></template></el-dropdown>
|
||||
<el-button-group><el-button class="grouped-action">上一项</el-button><el-button>下一项</el-button></el-button-group>
|
||||
</div>
|
||||
<AppDialog v-model="open" title="新增模拟柜" class="toolbar-editor admin-density-dialog admin-density-dialog--comfortable admin-workbench is-dialog">
|
||||
<el-button type="primary" class="body-action">正文操作</el-button>
|
||||
</AppDialog>
|
||||
<DataTable :data="[]" :columns="[]" column-settings-mode="hide" :show-pagination="false">
|
||||
<template #toolbar-start><el-button type="primary" class="nested-table-action">子表操作</el-button></template>
|
||||
</DataTable>
|
||||
</template>
|
||||
<template #toolbar-end><div><el-button class="end-action">导出</el-button></div></template>
|
||||
</DataTable>
|
||||
</div></main>
|
||||
`)
|
||||
expect(element('.toolbar-editor').closest('.data-table__business-actions')).not.toBeNull()
|
||||
// Check matching against real DOM, not a source-order approximation of the cascade.
|
||||
const workbench = compileStyle({ source: readFileSync(resolve(process.cwd(), 'src/styles/adminWorkbench.css'), 'utf8'), filename: 'workbench.css', id: 'toolbar-regression' }).rawResult!.root
|
||||
const toolbarSelectors: string[] = []
|
||||
workbench.walkRules(rule => {
|
||||
if (/data-table__(toolbar|business-actions)/.test(rule.selector) && rule.selector.includes('el-button')) toolbarSelectors.push(...rule.selectors)
|
||||
})
|
||||
for (const selector of ['.direct-action', '.wrapped-action', '.dropdown-action', '.split-actions .el-button', '.grouped-action', '.start-action', '.end-action']) {
|
||||
expect(toolbarSelectors.some(rule => element(selector).matches(rule)), selector).toBe(true)
|
||||
}
|
||||
for (const button of document.querySelectorAll('.toolbar-editor .el-button, .nested-table-action')) {
|
||||
expect(toolbarSelectors.filter(rule => button.matches(rule))).toEqual([])
|
||||
}
|
||||
const table = parse(readFileSync(resolve(process.cwd(), 'src/shared/components/DataTable.vue'), 'utf8')).descriptor
|
||||
for (const style of table.styles) {
|
||||
const rules = compileStyle({ source: style.content, filename: 'DataTable.vue', scoped: style.scoped, id: DataTable.__scopeId || 'data-v-layout' }).rawResult!.root
|
||||
rules.walkRules(rule => {
|
||||
if (!/toolbar|business-actions|utility-actions/.test(rule.selector) || !rule.selector.includes('el-button')) return
|
||||
for (const button of document.querySelectorAll('.toolbar-editor .el-button, .nested-table-action')) {
|
||||
expect(rule.selectors.some(selector => button.matches(selector)), rule.selector).toBe(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
expect(computed('.toolbar-editor .app-dialog__footer > .el-button--primary').height).toBe('38px')
|
||||
expect(computed('.toolbar-editor .app-dialog__footer > .el-button--primary').boxShadow).not.toContain('4px 12px')
|
||||
})
|
||||
|
||||
it('keeps a comfortable parent from changing nested dense and native dialog chrome', async () => {
|
||||
await render(`
|
||||
<AppDialog v-model="open" title="父窗口" class="outer admin-density-dialog admin-density-dialog--comfortable admin-workbench is-dialog" fullscreenable>
|
||||
<AppDialog v-model="childOpen" title="子窗口" class="inner admin-density-dialog admin-workbench is-dialog"><p>子窗口正文</p></AppDialog>
|
||||
<el-form class="parent-form" label-position="top">
|
||||
<el-form-item label="父窗口字段"><el-select><el-option label="选项" value="one" /></el-select></el-form-item>
|
||||
<div class="parent-grid"><el-form-item label="网格字段"><el-select><el-option label="选项" value="one" /></el-select></el-form-item></div>
|
||||
</el-form>
|
||||
<AppDialog v-model="childOpen" title="子窗口" class="inner admin-density-dialog admin-workbench is-dialog">
|
||||
<el-form class="density-form"><el-form-item label="子窗口字段"><el-select><el-option label="选项" value="one" /></el-select></el-form-item></el-form>
|
||||
<el-form label-position="top"><el-form-item label="顶部标签"><el-select size="small"><el-option label="选项" value="one" /></el-select></el-form-item></el-form>
|
||||
</AppDialog>
|
||||
<el-dialog :model-value="true" title="原生预览" class="nested-native"><p>预览内容</p></el-dialog>
|
||||
</AppDialog>
|
||||
<AppDialog :model-value="true" title="独立窗口" class="reference admin-density-dialog admin-workbench is-dialog"><p>独立正文</p></AppDialog>
|
||||
<AppDialog :model-value="true" title="独立窗口" class="reference admin-density-dialog admin-workbench is-dialog">
|
||||
<el-form class="density-form"><el-form-item label="子窗口字段"><el-select><el-option label="选项" value="one" /></el-select></el-form-item></el-form>
|
||||
<el-form label-position="top"><el-form-item label="顶部标签"><el-select size="small"><el-option label="选项" value="one" /></el-select></el-form-item></el-form>
|
||||
</AppDialog>
|
||||
<el-dialog :model-value="true" title="独立预览" class="reference-native"><p>预览内容</p></el-dialog>
|
||||
`)
|
||||
expect(element('.inner').closest('.outer')).not.toBeNull()
|
||||
expect(element('.outer').classList.contains('el-dialog')).toBe(true)
|
||||
for (const field of ['.parent-form > .el-form-item', '.parent-grid > .el-form-item']) {
|
||||
expect(computed(`.outer ${field}`).marginBottom).toBe('20px')
|
||||
expect(computed(`.outer ${field} > .el-form-item__label`).marginBottom).toBe('8px')
|
||||
expect(computed(`.outer ${field} > .el-form-item__label`).fontWeight).toBe('500')
|
||||
expect(computed(`.outer ${field} .el-select__wrapper`).minHeight).toBe('40px')
|
||||
}
|
||||
for (const part of ['header', 'body', 'footer']) {
|
||||
expect(computed(`.inner > .el-dialog__${part}`).padding).toBe(computed(`.reference > .el-dialog__${part}`).padding)
|
||||
}
|
||||
expect(computed('.nested-native > .el-dialog__header').padding).toBe(computed('.reference-native > .el-dialog__header').padding)
|
||||
expect(computed('.nested-native > .el-dialog__body').overflowY).toBe(computed('.reference-native > .el-dialog__body').overflowY)
|
||||
expect(computed('.inner > .el-dialog__header > .el-dialog__title').paddingRight).toBe(computed('.reference > .el-dialog__header > .el-dialog__title').paddingRight)
|
||||
for (const form of ['.density-form', '.el-form--label-top']) {
|
||||
expect(computed(`.inner ${form} .el-form-item`).marginBottom).toBe(computed(`.reference ${form} .el-form-item`).marginBottom)
|
||||
expect(computed(`.inner ${form} .el-form-item__label`).marginBottom).toBe(computed(`.reference ${form} .el-form-item__label`).marginBottom)
|
||||
expect(computed(`.inner ${form} .el-form-item__label`).fontWeight).toBe(computed(`.reference ${form} .el-form-item__label`).fontWeight)
|
||||
expect(computed(`.inner ${form} .el-select__wrapper`).minHeight).toBe(computed(`.reference ${form} .el-select__wrapper`).minHeight)
|
||||
}
|
||||
})
|
||||
|
||||
it('isolates drawer chrome and honors an explicitly teleported nested dialog', async () => {
|
||||
|
||||
@@ -437,7 +437,10 @@ function setPage(value: number) {
|
||||
.data-table__toolbar-actions { display: flex; flex: 0 0 auto; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
|
||||
.data-table__business-actions, .data-table__utility-actions, .data-table__toolbar-end { display: flex; min-width: 0; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
|
||||
.data-table__utility-actions { flex: 0 0 auto; }
|
||||
.data-table__toolbar-actions :deep(.el-button + .el-button) { margin-left: 0; }
|
||||
.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(.el-button + .el-button),
|
||||
.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button + .el-button),
|
||||
.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button + .el-button),
|
||||
.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button + .el-button) { margin-left: 0; }
|
||||
.data-table__pagination { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 18px; border-top: 1px solid var(--cc-border); color: var(--cc-muted); font-size: var(--cc-list-pagination-font-size); }
|
||||
.data-table__pagination-left { display: flex; min-width: 0; flex: 1; align-items: center; gap: 8px; }
|
||||
.data-table__pagination-controls, .data-table__page-size, .data-table__page-buttons { display: flex; align-items: center; gap: 8px; }
|
||||
@@ -465,7 +468,10 @@ function setPage(value: number) {
|
||||
.data-table__page-ellipsis { width: 22px; height: 30px; }
|
||||
}
|
||||
|
||||
@media (max-width: 720px) { .data-table__toolbar, .data-table__toolbar.is-compact { min-height: 0; align-items: stretch; flex-direction: column; padding: 14px; } .data-table__toolbar-actions { width: 100%; align-items: flex-start; flex-wrap: wrap; justify-content: flex-start; } .data-table__toolbar-actions.has-business-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto; } .data-table__business-actions { justify-content: flex-start; } .data-table__utility-actions { align-items: flex-start; justify-content: flex-end; } .data-table__toolbar-end { justify-content: flex-start; } .data-table__toolbar-actions :deep(.el-button) { max-width: 100%; min-height: 40px; } .data-table__pagination { align-items: flex-start; flex-direction: column; } .data-table__pagination-left, .data-table__pagination-controls, .data-table__page-buttons { width: 100%; } .data-table__pagination-controls { justify-content: flex-start; } .data-table__page-buttons { flex-wrap: wrap; } .data-table__page-button { width: 44px; min-width: 44px; height: 44px; } .data-table__page-ellipsis { height: 44px; } }
|
||||
@media (max-width: 720px) { .data-table__toolbar, .data-table__toolbar.is-compact { min-height: 0; align-items: stretch; flex-direction: column; padding: 14px; } .data-table__toolbar-actions { width: 100%; align-items: flex-start; flex-wrap: wrap; justify-content: flex-start; } .data-table__toolbar-actions.has-business-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto; } .data-table__business-actions { justify-content: flex-start; } .data-table__utility-actions { align-items: flex-start; justify-content: flex-end; } .data-table__toolbar-end { justify-content: flex-start; } .data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(.el-button),
|
||||
.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button),
|
||||
.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button),
|
||||
.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button) { max-width: 100%; min-height: 40px; } .data-table__pagination { align-items: flex-start; flex-direction: column; } .data-table__pagination-left, .data-table__pagination-controls, .data-table__page-buttons { width: 100%; } .data-table__pagination-controls { justify-content: flex-start; } .data-table__page-buttons { flex-wrap: wrap; } .data-table__page-button { width: 44px; min-width: 44px; height: 44px; } .data-table__page-ellipsis { height: 44px; } }
|
||||
/* Opt-in list surface: header, filters and results share one continuous card. */
|
||||
.data-table--integrated {
|
||||
--list-card-border: #e8edf5;
|
||||
@@ -494,19 +500,31 @@ function setPage(value: number) {
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.data-table--integrated .data-table__toolbar-actions :deep(.el-button) {
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(.el-button),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button) {
|
||||
min-height: 36px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.data-table--integrated .data-table__utility-actions :deep(.el-button > span) { gap: 6px; }
|
||||
.data-table--integrated .data-table__business-actions :deep(.el-button--primary:not(:disabled)),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__utility-actions > :deep(.el-button > span),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__utility-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button > span),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__utility-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button > span),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__utility-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button > span) { gap: 6px; }
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(.el-button--primary:not(:disabled)),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled)),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled)),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled)),
|
||||
.data-table--integrated .data-table__page-button[aria-current="page"]:not(:disabled) {
|
||||
border-color: transparent;
|
||||
background: var(--list-card-accent);
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 12px rgb(102 126 234 / 20%);
|
||||
}
|
||||
.data-table--integrated .data-table__business-actions :deep(.el-button--primary:hover:not(:disabled)),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(.el-button--primary:hover:not(:disabled)),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:hover:not(:disabled)),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:hover:not(:disabled)),
|
||||
.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:hover:not(:disabled)),
|
||||
.data-table--integrated .data-table__page-button[aria-current="page"]:hover:not(:disabled) { filter: brightness(1.06); }
|
||||
.data-table--integrated > .data-table__filters {
|
||||
min-width: 0;
|
||||
|
||||
Reference in New Issue
Block a user