Merge remote-tracking branch 'origin/release/test' into dev
# Conflicts: # AGENTS.md # src/modules/container-loading/pages/GlobalSmartLoadingView.vue # src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
|
||||
- XLSX 批量导入使用 `BatchImportDialog.vue`;页面只提供模板下载、导入调用和成功后刷新,不复制文件校验、上传状态、错误报告或弹窗 CSS。
|
||||
- 模态框和抽屉优先使用 `AppDialog.vue`、`AppDrawer.vue`;但独立页面、弹框或抽屉的选择必须服从对应原型,不因已有组件方便而替换承载方式。
|
||||
- `AppDialog` 未提供自定义 `#header` 时保留 Element 原生 `title` 标题栏;`fullscreenable` 只在原标题栏追加全屏控件,不得仅为标题或全屏按钮再造一层 header。只有原型确有复合标题结构时才使用 `#header`。
|
||||
- 加载失败组合 `LoadErrorState.vue` 与 `ApiErrorMeta.vue`,保留重试和 `requestId`/`traceId`;状态与复制优先使用 `StatusBadge.vue`、`CopyButton.vue`。
|
||||
- 参考页面:查询和表格见 `../../modules/master-data/express-companies/pages/ExpressCompaniesView.vue`,批量导入见该页面与 `../../modules/master-data/counterparties/pages/PartnersView.vue`,抽屉见 `../../modules/downloads/pages/DownloadCenterView.vue`。
|
||||
|
||||
|
||||
@@ -8,12 +8,13 @@ import AppDialog from './AppDialog.vue'
|
||||
const ElDialogStub = defineComponent({
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
title: String,
|
||||
showClose: Boolean,
|
||||
closeOnClickModal: Boolean,
|
||||
closeOnPressEscape: Boolean,
|
||||
fullscreen: Boolean,
|
||||
},
|
||||
template: '<section data-testid="dialog"><slot name="header" /><slot /><slot name="footer" /></section>',
|
||||
template: '<section data-testid="dialog"><h2 v-if="!$slots.header" data-testid="native-title">{{ title }}</h2><slot name="header" /><slot /><slot name="footer" /></section>',
|
||||
})
|
||||
|
||||
const ElButtonStub = defineComponent({
|
||||
@@ -73,6 +74,9 @@ describe('AppDialog', () => {
|
||||
|
||||
const wrapper = mountDialog({ fullscreenable: true, defaultFullscreen: true })
|
||||
expect(wrapper.getComponent(ElDialogStub).props('fullscreen')).toBe(true)
|
||||
expect(wrapper.get('[data-testid="native-title"]').text()).toBe('编辑资料')
|
||||
expect(wrapper.find('.app-dialog__header').exists()).toBe(false)
|
||||
expect(wrapper.find('.app-dialog__native-fullscreen-control').exists()).toBe(true)
|
||||
expect(wrapper.find('button[aria-label="退出全屏"]').exists()).toBe(true)
|
||||
|
||||
await wrapper.get('button[aria-label="退出全屏"]').trigger('click')
|
||||
@@ -83,9 +87,18 @@ describe('AppDialog', () => {
|
||||
it('keeps the dialog chrome fixed while long content scrolls in the body', () => {
|
||||
const source = readFileSync(resolve(process.cwd(), 'src/shared/components/AppDialog.vue'), 'utf8')
|
||||
|
||||
expect(source).toContain(':global(.app-dialog.el-dialog) { display: flex;')
|
||||
expect(source).toContain(':global(.app-dialog.el-dialog) { position: relative; display: flex;')
|
||||
expect(source).toContain('width: 100%; max-width: 100%; height: 100vh;')
|
||||
expect(source).toContain(':global(.app-dialog .el-dialog__body) { min-height: 0; overflow-y: auto;')
|
||||
expect(source).toContain('max-height: calc(100svh - 20px)')
|
||||
})
|
||||
|
||||
it('keeps the native title bar when fullscreen is enabled without a custom header', () => {
|
||||
const source = readFileSync(resolve(process.cwd(), 'src/shared/components/AppDialog.vue'), 'utf8')
|
||||
|
||||
expect(source).toContain('<template v-if="$slots.header" #header>')
|
||||
expect(source).not.toContain('<template v-if="$slots.header || fullscreenable" #header>')
|
||||
expect(source).toContain("fullscreenable && !$slots.header")
|
||||
expect(source).toContain('app-dialog__native-fullscreen-control')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -80,22 +80,33 @@ function closeDialog() {
|
||||
:close-on-press-escape="closeOnPressEscape && !submittingLocked"
|
||||
:destroy-on-close="destroyOnClose"
|
||||
:fullscreen="fullscreenable && fullscreen"
|
||||
:class="{ 'has-native-fullscreen-control': fullscreenable && !$slots.header }"
|
||||
>
|
||||
<template v-if="$slots.header || fullscreenable" #header>
|
||||
<div v-if="fullscreenable" class="app-dialog__header">
|
||||
<template v-if="$slots.header" #header>
|
||||
<div class="app-dialog__header">
|
||||
<div class="app-dialog__header-content">
|
||||
<slot v-if="$slots.header" name="header" :fullscreen="fullscreen" :toggle-fullscreen="toggleFullscreen" />
|
||||
<span v-else>{{ title }}</span>
|
||||
<slot name="header" :fullscreen="fullscreen" :toggle-fullscreen="toggleFullscreen" />
|
||||
</div>
|
||||
<el-tooltip :content="fullscreen ? '退出全屏' : '展开全屏'" placement="bottom">
|
||||
<el-tooltip v-if="fullscreenable" :content="fullscreen ? '退出全屏' : '展开全屏'" placement="bottom">
|
||||
<el-button text :aria-label="fullscreen ? '退出全屏' : '展开全屏'" @click="toggleFullscreen">
|
||||
<Minimize2 v-if="fullscreen" :size="18" />
|
||||
<Maximize2 v-else :size="18" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<slot v-else name="header" />
|
||||
</template>
|
||||
<el-tooltip v-if="fullscreenable && !$slots.header" :content="fullscreen ? '退出全屏' : '展开全屏'" placement="bottom">
|
||||
<el-button
|
||||
class="app-dialog__native-fullscreen-control"
|
||||
:class="{ 'is-without-close': !showClose || submittingLocked }"
|
||||
text
|
||||
:aria-label="fullscreen ? '退出全屏' : '展开全屏'"
|
||||
@click="toggleFullscreen"
|
||||
>
|
||||
<Minimize2 v-if="fullscreen" :size="18" />
|
||||
<Maximize2 v-else :size="18" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<div v-loading="loading" class="app-dialog__body" :aria-busy="loading || confirmLoading">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -117,13 +128,16 @@ function closeDialog() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:global(.app-dialog.el-dialog) { display: flex; max-height: min(90vh, 900px); flex-direction: column; overflow: hidden; }
|
||||
:global(.app-dialog.el-dialog) { position: relative; display: flex; max-height: min(90vh, 900px); flex-direction: column; overflow: hidden; }
|
||||
:global(.app-dialog.el-dialog.is-fullscreen) { width: 100%; max-width: 100%; height: 100vh; max-height: 100vh; margin: 0 !important; border-radius: 0; }
|
||||
:global(.app-dialog .el-dialog__header), :global(.app-dialog .el-dialog__footer) { flex: 0 0 auto; }
|
||||
:global(.app-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
|
||||
:global(.app-dialog.has-native-fullscreen-control .el-dialog__title) { display: block; padding-right: 44px; }
|
||||
.app-dialog__header { display: flex; width: 100%; min-width: 0; align-items: center; justify-content: space-between; gap: 12px; color: var(--cc-text); font-size: 18px; font-weight: 600; line-height: 24px; }
|
||||
.app-dialog__header-content { min-width: 0; flex: 1; }
|
||||
.app-dialog__header > :deep(.el-button) { flex: 0 0 auto; margin-right: 24px; color: var(--cc-muted); }
|
||||
.app-dialog__native-fullscreen-control { position: absolute; z-index: 2; top: 10px; right: 48px; color: var(--cc-muted); }
|
||||
.app-dialog__native-fullscreen-control.is-without-close { right: 12px; }
|
||||
.app-dialog__body { min-height: 0; }
|
||||
.app-dialog__footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
|
||||
.app-dialog__footer :deep(.el-button) { margin-left: 0; }
|
||||
|
||||
Reference in New Issue
Block a user