Skip to content

Commit 9186095

Browse files
committed
feat(shared): 新增分区布局组件
1 parent bfeb7c8 commit 9186095

File tree

6 files changed

+326
-0
lines changed

6 files changed

+326
-0
lines changed

src/internal/load-css/index.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { getCurrentInstance } from 'vue'
2+
3+
const cssMap = new WeakMap<any, string>()
4+
5+
export function loadCSS(): void {
6+
const componentOptions = getCurrentInstance()?.proxy?.$options
7+
if (
8+
!componentOptions ||
9+
cssMap.has(componentOptions) ||
10+
!componentOptions.__cKitStaticCSS
11+
) {
12+
return
13+
}
14+
15+
const style = resolveStyleElement()
16+
style.textContent += `${componentOptions.__cKitStaticCSS}\n`
17+
cssMap.set(componentOptions, componentOptions.__cKitStaticCSS)
18+
}
19+
20+
function resolveStyleElement(id = 'cKit_loadCSS_style'): HTMLStyleElement {
21+
let style = document.getElementById(id) as HTMLStyleElement | null
22+
if (!style) {
23+
style = document.createElement('style')
24+
style.id = id
25+
document.head.appendChild(style)
26+
}
27+
return style
28+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
:where(.kit-section-layout) {
2+
--gap: calc(1em * 0.8);
3+
}
4+
5+
:where(.kit-section-layout),
6+
:where(.kit-section-layout) [class^='kit-section-layout__'] {
7+
box-sizing: border-box;
8+
}
9+
10+
:where(.kit-section-layout),
11+
:where(.kit-section-layout) > .kit-section-layout__main {
12+
display: flex;
13+
flex-direction: column;
14+
gap: var(--gap);
15+
}
16+
17+
:where(.kit-section-layout) > .kit-section-layout__top,
18+
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-head,
19+
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-foot,
20+
:where(.kit-section-layout) > .kit-section-layout__bottom {
21+
flex: none;
22+
}
23+
24+
:where(.kit-section-layout) > .kit-section-layout__main,
25+
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-content {
26+
flex: 1;
27+
min-height: 0;
28+
overflow-y: auto;
29+
}
30+
31+
:where(.kit-section-layout) > .kit-section-layout__top:empty,
32+
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-head:empty,
33+
:where(.kit-section-layout) > .kit-section-layout__main > .kit-section-layout__main-foot:empty,
34+
:where(.kit-section-layout) > .kit-section-layout__bottom:empty {
35+
display: none !important;
36+
}

src/shared/section-layout/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { SectionLayoutConfig } from './index.vue'
2+
import SectionLayoutImpl from './index.vue'
3+
4+
export type * from './interface'
5+
6+
export const SectionLayout = SectionLayoutImpl as typeof SectionLayoutImpl & {
7+
/**
8+
* 全局配置
9+
*/
10+
configure: (config?: SectionLayoutConfig) => SectionLayoutConfig
11+
}
12+
13+
SectionLayout.configure = /* @__PURE__ */ (
14+
config: SectionLayoutConfig = {},
15+
) => {
16+
SectionLayout.config = Object.assign(SectionLayout.config || {}, config)
17+
return SectionLayout.config
18+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<script lang="ts" setup>
2+
import type {
3+
SectionLayoutCardArea,
4+
SectionLayoutProps,
5+
SectionLayoutSlots,
6+
} from './interface'
7+
import { toArray, useElementSize } from '@vueuse/core'
8+
import { computed, getCurrentInstance, shallowRef, watch } from 'vue'
9+
import { loadCSS } from '@/internal/load-css'
10+
import css from './index.css?raw'
11+
12+
defineOptions({
13+
name: 'SectionLayout',
14+
__cKitStaticCSS: css,
15+
})
16+
17+
const {
18+
card,
19+
height = '100%',
20+
onMainContentResize,
21+
} = defineProps<SectionLayoutProps>()
22+
23+
defineSlots<SectionLayoutSlots>()
24+
25+
loadCSS()
26+
27+
export interface SectionLayoutConfig {
28+
cardClass?: any
29+
}
30+
31+
const { config } = getCurrentInstance()!.proxy!.$options as {
32+
config?: SectionLayoutConfig
33+
}
34+
35+
const ns = 'kit-section-layout'
36+
37+
const cardAreas = computed(() => {
38+
const set = new Set(toArray(card))
39+
return {
40+
has(area: SectionLayoutCardArea) {
41+
return card === true || set.has(area)
42+
},
43+
}
44+
})
45+
46+
const mainContentRef = shallowRef<HTMLDivElement>()
47+
const mainContentSize = useElementSize(mainContentRef)
48+
watch([mainContentSize.width, mainContentSize.height], () => {
49+
onMainContentResize &&
50+
toArray(onMainContentResize).forEach((fn) =>
51+
fn({
52+
width: mainContentSize.width.value,
53+
height: mainContentSize.height.value,
54+
}),
55+
)
56+
})
57+
58+
defineExpose({
59+
mainContentRef,
60+
mainContentSize: {
61+
width: mainContentSize.width,
62+
height: mainContentSize.height,
63+
},
64+
})
65+
</script>
66+
67+
<template>
68+
<div
69+
:class="ns"
70+
:style="{ height }"
71+
>
72+
<div
73+
:style="topStyle"
74+
:class="[
75+
`${ns}__top`,
76+
cardAreas.has('top') && ['is-card', config?.cardClass],
77+
topClass,
78+
]"
79+
>
80+
<slot name="top" />
81+
</div>
82+
83+
<div
84+
:style="mainStyle"
85+
:class="[
86+
`${ns}__main`,
87+
cardAreas.has('main') && ['is-card', config?.cardClass],
88+
mainClass,
89+
]"
90+
>
91+
<slot name="main">
92+
<div
93+
:style="mainHeadStyle"
94+
:class="[`${ns}__main-head`, mainHeadClass]"
95+
>
96+
<slot name="main-head" />
97+
</div>
98+
99+
<div
100+
ref="mainContentRef"
101+
:style="mainContentStyle"
102+
:class="[`${ns}__main-content`, mainContentClass]"
103+
>
104+
<component
105+
:is="$slots['main-content'] || $slots.default"
106+
v-if="$slots['main-content'] || $slots.default"
107+
:width="mainContentSize.width.value"
108+
:height="mainContentSize.height.value"
109+
/>
110+
</div>
111+
112+
<div
113+
:style="mainFootStyle"
114+
:class="[`${ns}__main-foot`, mainFootClass]"
115+
>
116+
<slot name="main-foot" />
117+
</div>
118+
</slot>
119+
</div>
120+
121+
<div
122+
:style="bottomStyle"
123+
:class="[
124+
`${ns}__bottom`,
125+
cardAreas.has('bottom') && ['is-card', config?.cardClass],
126+
bottomClass,
127+
]"
128+
>
129+
<slot name="bottom" />
130+
</div>
131+
</div>
132+
</template>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { Arrayable } from 'type-fest'
2+
import type { StyleValue, VNodeChild } from 'vue'
3+
4+
interface ElementSize {
5+
width: number
6+
height: number
7+
}
8+
9+
export type SectionLayoutCardArea = 'top' | 'main' | 'bottom'
10+
11+
export interface SectionLayoutProps {
12+
// Outer sections
13+
topStyle?: StyleValue
14+
topClass?: any
15+
16+
mainStyle?: StyleValue
17+
mainClass?: any
18+
19+
bottomStyle?: StyleValue
20+
bottomClass?: any
21+
22+
// Inner sections within main
23+
mainHeadStyle?: StyleValue
24+
mainHeadClass?: any
25+
26+
mainContentStyle?: StyleValue
27+
mainContentClass?: any
28+
29+
mainFootStyle?: StyleValue
30+
mainFootClass?: any
31+
32+
// Card-like wrappers by area
33+
card?: boolean | Arrayable<SectionLayoutCardArea>
34+
35+
/**
36+
* 布局高度
37+
* @default '100%'
38+
*/
39+
height?: string
40+
41+
/**
42+
* 主内容区域尺寸变化回调(main-content)
43+
*/
44+
onMainContentResize?: Arrayable<(data: ElementSize) => void>
45+
}
46+
47+
export interface SectionLayoutSlots {
48+
// Outer
49+
'top'?: () => VNodeChild
50+
'main'?: () => VNodeChild
51+
'bottom'?: () => VNodeChild
52+
53+
// Inner within main
54+
'main-head'?: () => VNodeChild
55+
'main-content'?: (data: ElementSize) => VNodeChild
56+
'main-foot'?: () => VNodeChild
57+
58+
/**
59+
* default 作为 main-content 的别名插槽
60+
*/
61+
'default'?: (data: ElementSize) => VNodeChild
62+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { FunctionalComponent, StyleValue, VNodeChild } from 'vue'
2+
3+
interface SelectionProps {
4+
rootClass: string
5+
render: (() => VNodeChild) | undefined
6+
7+
leftStyles: StyleValue
8+
leftClass: any
9+
leftRender: (() => VNodeChild) | undefined
10+
11+
rightStyles: StyleValue
12+
rightClass: any
13+
rightRender: (() => VNodeChild) | undefined
14+
}
15+
16+
export const Selection: FunctionalComponent<SelectionProps> = ({
17+
rootClass,
18+
render,
19+
20+
leftStyles,
21+
leftClass,
22+
leftRender,
23+
24+
rightStyles,
25+
rightClass,
26+
rightRender,
27+
}) => {
28+
render ||= () => {
29+
return (
30+
<>
31+
<div
32+
style={leftStyles}
33+
class={[`${rootClass}__left`, leftClass]}
34+
>
35+
{leftRender}
36+
</div>
37+
<div
38+
style={rightStyles}
39+
class={[`${rootClass}__right`, rightClass]}
40+
>
41+
{rightRender}
42+
</div>
43+
</>
44+
)
45+
}
46+
47+
return <div>{render}</div>
48+
}
49+
50+
Selection.props = ['rootClass']

0 commit comments

Comments
 (0)