mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-09-12 15:09:43 +00:00
feat(form): group-list 标题吸顶改为按 header.sticky 显式开启
默认关闭避免长列表误吸顶,嵌套层按外层真实标题/吸底按钮高度让位;属性面板列表通过 stickyAddButton 保持原表现。
This commit is contained in:
parent
7e4546899b
commit
2a4a54a495
@ -2,7 +2,7 @@
|
||||
|
||||
## 配置类型
|
||||
|
||||
::: details 查看 ContainerCommonConfig / RowConfig / TabConfig / TabPaneConfig / FieldsetConfig / PanelConfig / StepConfig / FlexLayoutConfig / GroupListConfig / TableConfig / TableColumnConfig / TableGroupListCommonConfig 配置类型定义
|
||||
::: details 查看 ContainerCommonConfig / RowConfig / TabConfig / TabPaneConfig / FieldsetConfig / PanelConfig / StepConfig / FlexLayoutConfig / GroupListConfig / TableConfig / TableColumnConfig / TableGroupListCommonConfig / GroupListHeaderConfig 配置类型定义
|
||||
<<< @/../packages/form-schema/src/base.ts#ContainerCommonConfig{ts}
|
||||
|
||||
<<< @/../packages/form-schema/src/base.ts#RowConfig{ts}
|
||||
@ -27,6 +27,8 @@
|
||||
|
||||
<<< @/../packages/form-schema/src/base.ts#TableGroupListCommonConfig{ts}
|
||||
|
||||
<<< @/../packages/form-schema/src/base.ts#GroupListHeaderConfig{ts}
|
||||
|
||||
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||
|
||||
:::
|
||||
@ -211,6 +213,27 @@
|
||||
}]
|
||||
}]"></demo-block>
|
||||
|
||||
#### 标题吸顶
|
||||
|
||||
列表项较多、需要边滚动边看清当前编辑的是哪一项时,可以用 `header.sticky` 让卡片标题吸顶:
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'groupList',
|
||||
name: 'group',
|
||||
header: { sticky: true },
|
||||
items: [/* ... */],
|
||||
}
|
||||
```
|
||||
|
||||
吸顶默认关闭,需要逐层显式开启:外层开了不会带动内层,嵌套列表想一起吸顶就各自配一次,内层标题会自动下移让开外层标题。
|
||||
|
||||
标题让位的高度默认按 65px 计算。如果自定义了卡片标题的样式导致实际高度不同、内层标题出现重叠或空隙,用 `header.height` 告知真实高度即可(它只影响内层的让位距离,不会改变标题本身的高度)。
|
||||
|
||||
::: warning 行为变更
|
||||
1.8.0-beta.28 之前,group-list 的卡片标题在任何情况下都会吸顶。现在改为默认关闭、由 `header.sticky` 显式开启。升级后如需保持原有表现,请在对应配置上补上 `header: { sticky: true }`。
|
||||
:::
|
||||
|
||||
### table
|
||||
|
||||
<demo-block type="form" :config="[{
|
||||
|
||||
@ -17,12 +17,13 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 属性面板列表字段共用的吸底全宽「添加」按钮,展开到 group-list 配置上。
|
||||
* 属性面板列表字段共用的吸底全宽「添加」按钮与吸顶标题,展开到 group-list 配置上。
|
||||
*
|
||||
* 吸底按钮会盖住列表底部,新增的项必须同时滚进视口才看得见,两者一起给出避免漏配。
|
||||
* 吸底按钮会盖住列表底部,吸顶标题会盖住列表顶部,新增的项必须同时滚进视口才看得见。
|
||||
*/
|
||||
export const stickyAddButton = (text: string) => ({
|
||||
scrollLastItemIntoView: true as const,
|
||||
header: { sticky: true as const },
|
||||
addButtonConfig: {
|
||||
sticky: true as const,
|
||||
text,
|
||||
|
||||
@ -122,6 +122,7 @@ describe('CodeSelect', () => {
|
||||
const container = wrapper.findComponent({ name: 'MContainer' });
|
||||
const config = container.props('config') as any;
|
||||
expect(config.scrollLastItemIntoView).toBe(true);
|
||||
expect(config.header.sticky).toBe(true);
|
||||
expect(config.addButtonConfig.sticky).toBe(true);
|
||||
expect(config.addButtonConfig.text).toBe('添加');
|
||||
const codeTypeSelect = config.items[0];
|
||||
|
||||
@ -84,6 +84,7 @@ describe('DisplayConds', () => {
|
||||
expect(capturedConfig.type).toBe('groupList');
|
||||
expect(capturedConfig.defaultAdd).toEqual({ cond: [] });
|
||||
expect(capturedConfig.scrollLastItemIntoView).toBe(true);
|
||||
expect(capturedConfig.header.sticky).toBe(true);
|
||||
expect(capturedConfig.addButtonConfig.sticky).toBe(true);
|
||||
expect(capturedConfig.addButtonConfig.text).toBe('新增条件组');
|
||||
expect(capturedConfig.items[0].type).toBe('groupList');
|
||||
@ -93,6 +94,7 @@ describe('DisplayConds', () => {
|
||||
expect(capturedConfig.items[0].movable).toBe(false);
|
||||
expect(capturedConfig.items[0].flat).toBe(true);
|
||||
expect(capturedConfig.items[0].scrollLastItemIntoView).toBe(true);
|
||||
expect(capturedConfig.items[0].header.sticky).toBe(true);
|
||||
expect(capturedConfig.items[0].addButtonConfig.sticky).toBe(true);
|
||||
expect(capturedConfig.items[0].addButtonConfig.text).toBe('新增条件');
|
||||
expect(capturedConfig.items[0].items.every((item: any) => item.span === undefined)).toBe(true);
|
||||
|
||||
@ -133,11 +133,13 @@ describe('EventSelect', () => {
|
||||
expect(wrapper.find('.fake-table').exists()).toBe(false);
|
||||
expect(capturedConfig.type).toBe('group-list');
|
||||
expect(capturedConfig.scrollLastItemIntoView).toBe(true);
|
||||
expect(capturedConfig.header.sticky).toBe(true);
|
||||
expect(capturedConfig.addButtonConfig.sticky).toBe(true);
|
||||
expect(capturedConfig.addButtonConfig.text).toBe('添加事件');
|
||||
expect(capturedConfig.defaultAdd).toEqual({ name: '', actions: [] });
|
||||
expect(capturedConfig.movable).toBe(false);
|
||||
expect(capturedConfig.items[0].scrollLastItemIntoView).toBe(true);
|
||||
expect(capturedConfig.items[0].header.sticky).toBe(true);
|
||||
expect(capturedConfig.items[0].addButtonConfig.sticky).toBe(true);
|
||||
expect(capturedConfig.items[0].addButtonConfig.text).toBe('新增动作');
|
||||
});
|
||||
|
||||
@ -830,6 +830,20 @@ export interface AddButtonConfig {
|
||||
}
|
||||
// #endregion AddButtonConfig
|
||||
|
||||
// #region GroupListHeaderConfig
|
||||
/** group-list 卡片标题;仅 sticky 开启时吸顶 */
|
||||
export interface GroupListHeaderConfig {
|
||||
/** 标题吸顶 */
|
||||
sticky?: boolean;
|
||||
/**
|
||||
* 告诉嵌套列表「本层吸顶标题占了多高」,内层标题据此下移让位。
|
||||
* **不会改变标题自身的高度**,只在实际标题高度偏离默认值 65px 时才需要配。
|
||||
* 数字与纯数字字符串按 px,其余按 CSS 长度原样使用。
|
||||
*/
|
||||
height?: number | string;
|
||||
}
|
||||
// #endregion GroupListHeaderConfig
|
||||
|
||||
// #region TableGroupListCommonConfig
|
||||
export interface TableGroupListCommonConfig extends FormItem {
|
||||
type: 'table' | 'groupList' | 'group-list';
|
||||
@ -846,6 +860,8 @@ export interface TableGroupListCommonConfig extends FormItem {
|
||||
/** 新增后滚动到最后一项(group-list 形态,默认关闭) */
|
||||
scrollLastItemIntoView?: boolean;
|
||||
addButtonConfig?: AddButtonConfig;
|
||||
/** group-list 形态的标题吸顶(默认关闭);table 形态忽略 */
|
||||
header?: GroupListHeaderConfig;
|
||||
}
|
||||
// #endregion TableGroupListCommonConfig
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<div class="m-fields-group-list">
|
||||
<div
|
||||
class="m-fields-group-list"
|
||||
:class="{
|
||||
'is-header-sticky': Boolean(config.header?.sticky),
|
||||
'is-footer-sticky': isFooterSticky($slots),
|
||||
}"
|
||||
:style="headerVars"
|
||||
>
|
||||
<div v-if="config.extra" v-html="config.extra" style="color: rgba(0, 0, 0, 0.45)"></div>
|
||||
<div v-if="!displayItems.length" class="el-table__empty-block">
|
||||
<span class="el-table__empty-text t-table__empty">暂无{{ config.titlePrefix || '' }}数据</span>
|
||||
@ -34,7 +41,7 @@
|
||||
<div
|
||||
class="m-fields-group-list-footer"
|
||||
:class="{ 'is-sticky-full': Boolean(config.addButtonConfig?.sticky) }"
|
||||
v-if="!isCompare && ($slots['toggle-button'] || $slots['add-button'])"
|
||||
v-if="hasFooter($slots)"
|
||||
>
|
||||
<slot name="toggle-button"></slot>
|
||||
<div style="display: flex; justify-content: flex-end; flex: 1">
|
||||
@ -49,6 +56,7 @@ import { computed } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { ContainerChangeEventData, GroupListConfig } from '../schema';
|
||||
import { getGroupListHeaderVars } from '../utils/tableGroupList';
|
||||
|
||||
import MFieldsGroupListItem from './GroupListItem.vue';
|
||||
|
||||
@ -104,6 +112,19 @@ const onAddDiffCount = () => emit('addDiffCount');
|
||||
|
||||
const asList = (value: unknown): any[] => (Array.isArray(value) ? value : []);
|
||||
|
||||
const headerVars = computed(() => getGroupListHeaderVars(props.config.header));
|
||||
|
||||
/**
|
||||
* 取 `$slots` 而不是 `useSlots()`:宿主切换 `addable` 时插槽会增删,
|
||||
* computed 缓存不随插槽变化失效,只有在渲染期读才拿得到最新的。
|
||||
*/
|
||||
const hasFooter = (slots: Record<string, unknown>) =>
|
||||
!props.isCompare && Boolean(slots['toggle-button'] || slots['add-button']);
|
||||
|
||||
/** 只有真的渲染出吸底 footer,内层列表才需要为它让位 */
|
||||
const isFooterSticky = (slots: Record<string, unknown>) =>
|
||||
hasFooter(slots) && Boolean(props.config.addButtonConfig?.sticky);
|
||||
|
||||
const currentList = computed(() => asList(props.model[props.name]));
|
||||
|
||||
/** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */
|
||||
|
||||
@ -70,27 +70,60 @@
|
||||
flex-shrink: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
// 卡片标题吸顶;补不透明背景,避免滚动时正文透出。
|
||||
// z-index 必须低于吸底 footer,否则标题会盖住「新增」按钮。
|
||||
> .el-card__header,
|
||||
> .t-card__header {
|
||||
position: sticky;
|
||||
top: var(--m-group-list-header-sticky-top, 0px);
|
||||
z-index: 7;
|
||||
background-color: var(--m-group-list-header-bg, #fff);
|
||||
}
|
||||
}
|
||||
|
||||
// 嵌套标题叠在外层标题之下,层级更低以免盖住外层底部分隔线
|
||||
.m-fields-group-list-item .m-fields-group-list-item {
|
||||
> .el-card__header,
|
||||
> .t-card__header {
|
||||
top: calc(
|
||||
var(--m-group-list-header-sticky-top, 0px) +
|
||||
var(--m-group-list-header-height)
|
||||
// 嵌套吸底按钮叠在外层 footer 之上(如「新增条件」叠在「新增条件组」上面)。
|
||||
// 每穿过一层「自己渲染了吸底 footer」的列表才多让出一个 footer 高度,外层没有吸底按钮时
|
||||
// 内层不能凭空抬高。自定义属性不能自增,所以用 item / list 两个变量交替接力。
|
||||
> .m-fields-group-list-item {
|
||||
--m-group-list-item-footer-bottom: var(
|
||||
--m-group-list-nested-footer-bottom,
|
||||
0px
|
||||
);
|
||||
}
|
||||
|
||||
&.is-footer-sticky > .m-fields-group-list-item {
|
||||
--m-group-list-item-footer-bottom: calc(
|
||||
var(--m-group-list-nested-footer-bottom, 0px) +
|
||||
var(--m-group-list-footer-height)
|
||||
);
|
||||
}
|
||||
|
||||
.m-fields-group-list-item .m-fields-group-list {
|
||||
--m-group-list-nested-footer-bottom: var(
|
||||
--m-group-list-item-footer-bottom,
|
||||
0px
|
||||
);
|
||||
}
|
||||
|
||||
// 仅 header.sticky 时标题吸顶;直接子 item,避免外层吸顶带动未开启的内层
|
||||
&.is-header-sticky {
|
||||
// 从外层 item 接过已累加的偏移;最外层没有该变量时走 fallback
|
||||
--m-group-list-nested-sticky-top: var(--m-group-list-child-list-sticky-top);
|
||||
--m-group-list-nested-header-z: var(--m-group-list-child-list-header-z);
|
||||
|
||||
> .m-fields-group-list-item {
|
||||
--m-group-list-item-sticky-top: var(
|
||||
--m-group-list-nested-sticky-top,
|
||||
var(--m-group-list-header-sticky-top, 0px)
|
||||
);
|
||||
z-index: 6;
|
||||
--m-group-list-item-header-z: var(--m-group-list-nested-header-z, 7);
|
||||
--m-group-list-child-list-sticky-top: calc(
|
||||
var(--m-group-list-item-sticky-top) + var(--m-group-list-header-height)
|
||||
);
|
||||
--m-group-list-child-list-header-z: calc(
|
||||
var(--m-group-list-item-header-z) - 1
|
||||
);
|
||||
|
||||
// 卡片标题吸顶;补不透明背景,避免滚动时正文透出。
|
||||
// z-index 必须低于吸底 footer,否则标题会盖住「新增」按钮。
|
||||
> .el-card__header,
|
||||
> .t-card__header {
|
||||
position: sticky;
|
||||
top: var(--m-group-list-item-sticky-top);
|
||||
z-index: var(--m-group-list-item-header-z);
|
||||
background-color: var(--m-group-list-header-bg, #fff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,10 +146,10 @@
|
||||
}
|
||||
|
||||
// 吸底全宽主按钮(event-select / code-select / display-conds 外层列表)
|
||||
// z-index 高于 item header,避免吸顶标题挡住新增按钮
|
||||
// z-index 高于 item header,避免吸顶标题挡住新增按钮;多层时 bottom 由内层 list 累加
|
||||
> .m-fields-group-list-footer.is-sticky-full {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
bottom: var(--m-group-list-nested-footer-bottom, 0px);
|
||||
z-index: 7;
|
||||
margin-bottom: 0;
|
||||
padding: var(--m-group-list-footer-padding-top) 0
|
||||
@ -133,14 +166,6 @@
|
||||
height: var(--m-group-list-footer-button-height);
|
||||
}
|
||||
}
|
||||
|
||||
// 嵌套吸底按钮叠在外层 footer 之上(如「新增条件」叠在「新增条件组」上面)
|
||||
.m-fields-group-list-item
|
||||
.m-fields-group-list
|
||||
> .m-fields-group-list-footer.is-sticky-full {
|
||||
bottom: var(--m-group-list-footer-height);
|
||||
z-index: 7;
|
||||
}
|
||||
}
|
||||
|
||||
/** 最外层的groupList需要每个item需要增加空白区域界限时,增加outer-gorup_list可以实现 */
|
||||
|
||||
@ -18,7 +18,9 @@
|
||||
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { GroupListConfig, TableColumnConfig, TableConfig } from '../schema';
|
||||
import { isNumber } from '@tmagic/utils';
|
||||
|
||||
import type { GroupListConfig, GroupListHeaderConfig, TableColumnConfig, TableConfig } from '../schema';
|
||||
|
||||
/**
|
||||
* table / group-list 是同一份配置的两种展示形态,可以互相切换(`TableGroupList.vue`)。
|
||||
@ -31,6 +33,31 @@ import type { GroupListConfig, TableColumnConfig, TableConfig } from '../schema'
|
||||
/** group-list 形态的 type(兼容驼峰与中划线两种写法) */
|
||||
export const isGroupListType = (type: unknown): boolean => type === 'groupList' || type === 'group-list';
|
||||
|
||||
/**
|
||||
* 归一成带单位的 CSS 长度。
|
||||
*
|
||||
* 配置常常来自 JSON 或表单输入,`'48'` 这类无单位数字串很常见;直接写进 CSS 变量会让
|
||||
* 嵌套层的 `calc()` 整体失效、吸顶静默失灵,所以纯数字一律补 `px`。
|
||||
*/
|
||||
const toCssLength = (height: number | string | undefined): string | undefined => {
|
||||
const value = `${height ?? ''}`.trim();
|
||||
if (!value) return undefined;
|
||||
|
||||
if (isNumber(value)) return `${value}px`;
|
||||
// 走到这里还是 number 的只剩 NaN / Infinity,不写变量,交给样式默认值兜底
|
||||
return typeof height === 'number' ? undefined : value;
|
||||
};
|
||||
|
||||
/** header.sticky 开启时把 height 写成 CSS 变量,给嵌套吸顶偏移用 */
|
||||
export const getGroupListHeaderVars = (
|
||||
header: GroupListHeaderConfig | undefined,
|
||||
): Record<string, string> | undefined => {
|
||||
if (!header?.sticky) return undefined;
|
||||
|
||||
const height = toCssLength(header.height);
|
||||
return height ? { '--m-group-list-header-height': height } : undefined;
|
||||
};
|
||||
|
||||
/** 按 label 文案长度估算 label 宽度(中文按 20px、其他按 8px,最小 80px) */
|
||||
export const calcLabelWidth = (label: string): string => {
|
||||
if (!label) return '0px';
|
||||
|
||||
@ -202,6 +202,63 @@ describe('GroupList container', () => {
|
||||
).toBe(false);
|
||||
warn.mockRestore();
|
||||
});
|
||||
|
||||
test('点击删除按钮移除对应项', async () => {
|
||||
const wrapper = mountForm(compareConfig, { list: [{ text: 'a' }, { text: 'b' }] });
|
||||
await nextTick();
|
||||
|
||||
await wrapper.findAll('.delete-button')[0].trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.list).toEqual([{ text: 'b' }]);
|
||||
expect(wrapper.findAll('.m-fields-group-list-item')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('点击复制按钮在末尾追加一份副本', async () => {
|
||||
const wrapper = mountForm(compareConfig, { list: [{ text: 'a' }, { text: 'b' }] });
|
||||
await nextTick();
|
||||
|
||||
const copyButton = wrapper.findAll('button').find((btn) => btn.text().includes('复制'));
|
||||
await copyButton?.trigger('click');
|
||||
await nextTick();
|
||||
|
||||
const { list } = (wrapper.vm as any).values;
|
||||
expect(list).toHaveLength(3);
|
||||
expect(list[2]).toEqual({ text: 'a' });
|
||||
// 深拷贝,改副本不应牵动原项
|
||||
expect(list[2]).not.toBe(list[0]);
|
||||
});
|
||||
|
||||
test('点击下移 / 上移交换相邻两项', async () => {
|
||||
const wrapper = mountForm(compareConfig, { list: [{ text: 'a' }, { text: 'b' }] });
|
||||
await nextTick();
|
||||
|
||||
// 首项的「上移」与末项的「下移」只是 v-show 隐藏,仍在 DOM 里,按 item 作用域取才不会点错
|
||||
const buttonIn = (index: number, text: string) =>
|
||||
wrapper
|
||||
.findAll('.m-fields-group-list-item')
|
||||
[index].findAll('button')
|
||||
.find((btn) => btn.text().includes(text));
|
||||
|
||||
await buttonIn(0, '下移')?.trigger('click');
|
||||
await nextTick();
|
||||
expect((wrapper.vm as any).values.list).toEqual([{ text: 'b' }, { text: 'a' }]);
|
||||
|
||||
await buttonIn(1, '上移')?.trigger('click');
|
||||
await nextTick();
|
||||
expect((wrapper.vm as any).values.list).toEqual([{ text: 'a' }, { text: 'b' }]);
|
||||
});
|
||||
|
||||
test('移动越界时夹到列表两端,不丢项', async () => {
|
||||
const wrapper = mountForm(compareConfig, { list: [{ text: 'a' }, { text: 'b' }] });
|
||||
await nextTick();
|
||||
|
||||
const item = wrapper.findAllComponents({ name: 'MFormGroupListItem' })[1];
|
||||
item.vm.$emit('swap-item', 1, 5);
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.list).toEqual([{ text: 'a' }, { text: 'b' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('labelPosition 透传', () => {
|
||||
@ -501,6 +558,131 @@ describe('GroupList container', () => {
|
||||
await nextTick();
|
||||
expect(wrapper.find('.m-fields-group-list-footer.is-sticky-full').exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('添加');
|
||||
expect(wrapper.find('.m-fields-group-list').classes()).toContain('is-footer-sticky');
|
||||
});
|
||||
|
||||
// 没有吸底 footer 的层不能让内层凭空抬高
|
||||
test('未开 addButtonConfig.sticky 时根节点不带 is-footer-sticky', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ list: [{ text: 'a' }] },
|
||||
);
|
||||
await nextTick();
|
||||
expect(wrapper.find('.m-fields-group-list').classes()).not.toContain('is-footer-sticky');
|
||||
});
|
||||
|
||||
test('对比模式下不渲染 footer,也不带 is-footer-sticky', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
addButtonConfig: { sticky: true, text: '添加', props: { type: 'primary', plain: true, text: false } },
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ list: [{ text: 'a' }] },
|
||||
{ isCompare: true, lastValues: { list: [{ text: 'a' }] } },
|
||||
);
|
||||
await nextTick();
|
||||
expect(wrapper.find('.m-fields-group-list-footer').exists()).toBe(false);
|
||||
expect(wrapper.find('.m-fields-group-list').classes()).not.toContain('is-footer-sticky');
|
||||
});
|
||||
|
||||
test('header.sticky 时根节点带 is-header-sticky', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
header: { sticky: true },
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ list: [{ text: 'a' }] },
|
||||
);
|
||||
await nextTick();
|
||||
expect(wrapper.find('.m-fields-group-list.is-header-sticky').exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('未开 header.sticky 时不加吸顶 class', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ list: [{ text: 'a' }] },
|
||||
);
|
||||
await nextTick();
|
||||
expect(wrapper.find('.m-fields-group-list.is-header-sticky').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('header.height 在 sticky 开启时写入 CSS 变量', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
header: { sticky: true, height: 48 },
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ list: [{ text: 'a' }] },
|
||||
);
|
||||
await nextTick();
|
||||
const el = wrapper.find('.m-fields-group-list').element as HTMLElement;
|
||||
expect(el.style.getPropertyValue('--m-group-list-header-height')).toBe('48px');
|
||||
});
|
||||
|
||||
test('仅配置 header.height 未开 sticky 时不写 CSS 变量', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
header: { height: 48 },
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ list: [{ text: 'a' }] },
|
||||
);
|
||||
await nextTick();
|
||||
const el = wrapper.find('.m-fields-group-list').element as HTMLElement;
|
||||
expect(el.style.getPropertyValue('--m-group-list-header-height')).toBe('');
|
||||
expect(wrapper.find('.m-fields-group-list.is-header-sticky').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('嵌套列表各自按 header.sticky 决定是否吸顶', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'groups',
|
||||
header: { sticky: true },
|
||||
items: [
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'cond',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
{ groups: [{ cond: [{ text: 'a' }] }] },
|
||||
);
|
||||
await nextTick();
|
||||
const lists = wrapper.findAll('.m-fields-group-list');
|
||||
expect(lists[0].classes()).toContain('is-header-sticky');
|
||||
expect(lists[1].classes()).not.toContain('is-header-sticky');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
*/
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { getGroupListRowConfig } from '@form/utils/tableGroupList';
|
||||
import { getGroupListHeaderVars, getGroupListRowConfig } from '@form/utils/tableGroupList';
|
||||
|
||||
describe('getGroupListRowConfig', () => {
|
||||
test('把 group-list 的 labelWidth / labelPosition 复制到 row 配置', () => {
|
||||
@ -26,3 +26,47 @@ describe('getGroupListRowConfig', () => {
|
||||
expect(row.items).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGroupListHeaderVars', () => {
|
||||
test('未开 sticky 时不写样式', () => {
|
||||
expect(getGroupListHeaderVars(undefined)).toBeUndefined();
|
||||
expect(getGroupListHeaderVars({})).toBeUndefined();
|
||||
expect(getGroupListHeaderVars({ height: 48 })).toBeUndefined();
|
||||
});
|
||||
|
||||
test('sticky 但 height 为空时不写变量,走样式默认值', () => {
|
||||
expect(getGroupListHeaderVars({ sticky: true })).toBeUndefined();
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: '' })).toBeUndefined();
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: ' ' })).toBeUndefined();
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: Number.NaN })).toBeUndefined();
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: Number.POSITIVE_INFINITY })).toBeUndefined();
|
||||
});
|
||||
|
||||
test('数字 height 转成 px', () => {
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: 48 })).toEqual({
|
||||
'--m-group-list-header-height': '48px',
|
||||
});
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: 0 })).toEqual({
|
||||
'--m-group-list-header-height': '0px',
|
||||
});
|
||||
});
|
||||
|
||||
// 无单位的值会让嵌套层的 calc() 整体失效、吸顶静默失灵
|
||||
test('无单位数字字符串补 px', () => {
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: '48' })).toEqual({
|
||||
'--m-group-list-header-height': '48px',
|
||||
});
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: ' 47.5 ' })).toEqual({
|
||||
'--m-group-list-header-height': '47.5px',
|
||||
});
|
||||
});
|
||||
|
||||
test('带单位的字符串 height 原样写入', () => {
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: '4em' })).toEqual({
|
||||
'--m-group-list-header-height': '4em',
|
||||
});
|
||||
expect(getGroupListHeaderVars({ sticky: true, height: 'var(--x)' })).toEqual({
|
||||
'--m-group-list-header-height': 'var(--x)',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user