Skip to content

Commit 937e1da

Browse files
committed
refactor(shared): 重构 SectionLayout 设计和实现
1 parent 1adc260 commit 937e1da

File tree

11 files changed

+388
-510
lines changed

11 files changed

+388
-510
lines changed

playground/src/pages/shared/section-layout.vue

Lines changed: 107 additions & 292 deletions
Large diffs are not rendered by default.

src/internal/bem/index.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import type { Arrayable } from 'type-fest'
2+
3+
export type BemModifiers = Record<string, boolean | undefined | null>
4+
5+
export interface BEM {
6+
namespace: string
7+
8+
b: (
9+
modifiers?: BemModifiers | Arrayable<string>,
10+
customClass?: Arrayable<string>,
11+
) => any[]
12+
e: (
13+
element: string,
14+
modifiers?: BemModifiers | Arrayable<string>,
15+
customClass?: Arrayable<string>,
16+
) => any[]
17+
m: (modifier: string) => string
18+
em: (element: string, modifier: string) => string
19+
is: (state: string) => string
20+
}
21+
22+
const baseNamespace = 'kit'
23+
24+
/**
25+
* 创建 BEM 工具函数集合
26+
* @param block 块名
27+
*/
28+
export function createBEM(block: string): BEM {
29+
const namespace = `${baseNamespace}-${block}`
30+
31+
return {
32+
namespace,
33+
34+
b(modifiers, customClass) {
35+
const classes: any[] = [namespace]
36+
37+
if (
38+
modifiers &&
39+
typeof modifiers === 'object' &&
40+
!Array.isArray(modifiers)
41+
) {
42+
// 处理修饰符
43+
Object.entries(modifiers).forEach(([key, value]) => {
44+
if (value) {
45+
classes.push(`${namespace}--${key}`)
46+
}
47+
})
48+
49+
// 处理自定义类名
50+
if (customClass) {
51+
classes.push(customClass)
52+
}
53+
}
54+
else if (modifiers) {
55+
// 直接传入自定义类名
56+
classes.push(modifiers)
57+
}
58+
59+
return classes
60+
},
61+
62+
e(element, modifiers, customClass) {
63+
const elementClass = `${namespace}__${element}`
64+
const classes: any[] = [elementClass]
65+
66+
if (
67+
modifiers &&
68+
typeof modifiers === 'object' &&
69+
!Array.isArray(modifiers)
70+
) {
71+
// 处理修饰符
72+
Object.entries(modifiers).forEach(([key, value]) => {
73+
if (value) {
74+
classes.push(`${elementClass}--${key}`)
75+
}
76+
})
77+
78+
// 处理自定义类名
79+
if (customClass) {
80+
classes.push(customClass)
81+
}
82+
}
83+
else if (modifiers) {
84+
// 直接传入自定义类名
85+
classes.push(modifiers)
86+
}
87+
88+
return classes
89+
},
90+
91+
m(modifier) {
92+
return `${namespace}--${modifier}`
93+
},
94+
95+
em(element, modifier) {
96+
return `${namespace}__${element}--${modifier}`
97+
},
98+
99+
is(state) {
100+
return `is-${state}`
101+
},
102+
}
103+
}

src/internal/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './bem'
2+
export * from './load-css'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script lang="ts" setup>
2+
import type { SectionItemProps } from '../interface'
3+
import { inject } from 'vue'
4+
import { createBEM } from '@/internal'
5+
import { SectionLayoutInjectionKey } from '../injection-keys'
6+
7+
defineOptions({
8+
name: 'SectionItem',
9+
})
10+
11+
defineProps<SectionItemProps>()
12+
13+
const { bem, config } = inject(SectionLayoutInjectionKey, {
14+
bem: createBEM('section'),
15+
config: undefined,
16+
})
17+
</script>
18+
19+
<template>
20+
<div :class="[bem.e('item'), card && [bem.m('card'), config?.cardClass]]">
21+
<slot />
22+
</div>
23+
</template>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<script lang="ts" setup>
2+
import type { SectionMainProps } from '../interface'
3+
import { toArray } from '@antfu/utils'
4+
import { useElementSize } from '@vueuse/core'
5+
import { inject, shallowRef, watch } from 'vue'
6+
import { createBEM } from '@/internal'
7+
import { SectionLayoutInjectionKey } from '../injection-keys'
8+
9+
defineOptions({
10+
name: 'SectionMain',
11+
})
12+
13+
const { onResize } = defineProps<SectionMainProps>()
14+
15+
const { bem, config } = inject(SectionLayoutInjectionKey, {
16+
bem: createBEM('section'),
17+
config: undefined,
18+
})
19+
20+
const elRef = shallowRef<HTMLDivElement>()
21+
const { width: elWidth, height: elHeight } = useElementSize(elRef)
22+
23+
watch([elWidth, elHeight], () => {
24+
if (onResize) {
25+
onResize &&
26+
toArray(onResize).forEach((fn) =>
27+
fn({
28+
width: elWidth.value,
29+
height: elHeight.value,
30+
}),
31+
)
32+
}
33+
})
34+
35+
defineExpose({
36+
width: elWidth,
37+
height: elHeight,
38+
})
39+
</script>
40+
41+
<template>
42+
<div
43+
ref="elRef"
44+
:class="[bem.e('main'), card && [bem.m('card'), config?.cardClass]]"
45+
>
46+
<slot
47+
:width="elWidth"
48+
:height="elHeight"
49+
/>
50+
</div>
51+
</template>
Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,42 @@
1-
:where(.kit-section-layout) {
2-
--gap: calc(1em * 0.8);
1+
:where(.kit-section),
2+
:where(.kit-section) [class^='kit-section__'] {
3+
box-sizing: border-box;
4+
}
5+
6+
:where(.kit-section) {
7+
--gap: 0.75rem;
38

9+
display: flex;
10+
gap: var(--gap);
411
height: 100%;
512
}
613

7-
:where(.kit-section-layout),
8-
:where(.kit-section-layout) [class^='kit-section-layout__'] {
9-
box-sizing: border-box;
14+
/* Vertical mode (default) */
15+
:where(.kit-section--vertical) {
16+
flex-direction: column;
1017
}
1118

12-
:where(.kit-section-layout),
13-
:where(.kit-section-layout) > .kit-section-layout__main {
14-
display: flex;
15-
flex-direction: column;
16-
gap: var(--gap);
19+
/* Horizontal mode */
20+
:where(.kit-section--horizontal) {
21+
flex-direction: row;
1722
}
1823

19-
:where(.kit-section-layout) > .kit-section-layout__top,
20-
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-head,
21-
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-foot,
22-
:where(.kit-section-layout) > .kit-section-layout__bottom {
24+
/* SectionItem - 固定尺寸的次要分区 */
25+
:where(.kit-section) > .kit-section__item {
2326
flex: none;
2427
}
2528

26-
:where(.kit-section-layout) > .kit-section-layout__main,
27-
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-content {
29+
/* SectionMain 和嵌套的 SectionLayout - 自动填充的主要分区 */
30+
:where(.kit-section--vertical) > :is(.kit-section, .kit-section__main) {
2831
flex: 1;
2932
min-height: 0;
33+
height: auto;
3034
overflow-y: auto;
3135
}
3236

33-
:where(.kit-section-layout) > .kit-section-layout__top:empty,
34-
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-head:empty,
35-
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-foot:empty,
36-
:where(.kit-section-layout) > .kit-section-layout__bottom:empty {
37-
display: none !important;
37+
:where(.kit-section--horizontal) > :is(.kit-section, .kit-section__main) {
38+
flex: 1;
39+
min-width: 0;
40+
width: auto;
41+
overflow-x: auto;
3842
}

src/shared/section-layout/index.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import type { SectionLayoutConfig } from './index.vue'
1+
import type { SectionLayoutConfig } from './interface'
22
import SectionLayoutImpl from './index.vue'
33

4+
export { default as SectionItem } from './components/item.vue'
5+
export { default as SectionMain } from './components/main.vue'
6+
47
export type * from './interface'
58

69
export const SectionLayout = SectionLayoutImpl as typeof SectionLayoutImpl & {
@@ -15,16 +18,20 @@ export const SectionLayout = SectionLayoutImpl as typeof SectionLayoutImpl & {
1518
clone: (config?: SectionLayoutConfig) => typeof SectionLayout
1619
}
1720

18-
SectionLayout.configure = /* @__PURE__ */ function (
19-
config: SectionLayoutConfig = {},
20-
) {
21-
this.config = Object.assign(this.config || {}, config)
22-
return this.config
23-
}
21+
SectionLayout.configure = /* @__PURE__ */ configure.bind(SectionLayout)
2422

2523
SectionLayout.clone = /* @__PURE__ */ (config: SectionLayoutConfig = {}) => {
2624
// SFC 本质上是一个普通对象,这里可以通过浅拷贝进行克隆
2725
const cloned = { ...SectionLayout } as typeof SectionLayout
26+
cloned.configure = configure.bind(cloned)
2827
cloned.configure(config)
2928
return cloned
3029
}
30+
31+
function configure(
32+
this: any,
33+
config: SectionLayoutConfig = {},
34+
): SectionLayoutConfig {
35+
this.config = Object.assign(this.config || {}, config)
36+
return this.config
37+
}

0 commit comments

Comments
 (0)