-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
475 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
const prefixStorage = '__fesd-storage'; | ||
|
||
export function getPrefixStorage(suffix: string) { | ||
return suffix ? `${prefixStorage}-${suffix}` : prefixStorage; | ||
} | ||
|
||
export type StorageType = 'local' | 'session'; | ||
|
||
export function getStorage(type: StorageType) { | ||
return type === 'local' ? localStorage : sessionStorage; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import { | ||
Teleport, | ||
Transition, | ||
computed, | ||
defineComponent, | ||
nextTick, | ||
ref, | ||
watch, | ||
} from 'vue'; | ||
import { isNumber } from 'lodash-es'; | ||
import { useStorage } from '@vueuse/core'; | ||
import getPrefixCls from '../_util/getPrefixCls'; | ||
import { CloseOutlined } from '../icon'; | ||
import { useTheme } from '../_theme/useTheme'; | ||
import { useConfig } from '../config-provider'; | ||
import { getPrefixStorage, getStorage } from '../_util/storage'; | ||
|
||
import { floatPaneProps } from './props'; | ||
import { useDrag } from './useDrag'; | ||
|
||
const prefixCls = getPrefixCls('float-pane'); | ||
const UPDATE_VISIBLE_EVENT = 'update:visible'; | ||
const AFTER_ENTER_EVENT = 'after-enter'; | ||
const AFTER_LEAVE_EVENT = 'after-leave'; | ||
|
||
const FloatPane = defineComponent({ | ||
name: 'FFloatPane', | ||
props: floatPaneProps, | ||
emits: [ | ||
UPDATE_VISIBLE_EVENT, | ||
AFTER_ENTER_EVENT, | ||
AFTER_LEAVE_EVENT, | ||
], | ||
setup(props, ctx) { | ||
useTheme(); | ||
const innerVisible = ref(false); | ||
|
||
watch( | ||
() => props.visible, | ||
() => { | ||
nextTick(() => { | ||
innerVisible.value = props.visible; | ||
}); | ||
}, | ||
{ immediate: true }, | ||
); | ||
const config = useConfig(); | ||
const getContainer = computed( | ||
() => props.getContainer || config.getContainer?.value, | ||
); | ||
|
||
function handleCancel() { | ||
ctx.emit(UPDATE_VISIBLE_EVENT, false); | ||
} | ||
|
||
function handleTransitionAfterEnter(el: Element) { | ||
ctx.emit(AFTER_ENTER_EVENT, el); | ||
} | ||
function handleTransitionAfterLeave(el: Element) { | ||
ctx.emit(AFTER_LEAVE_EVENT, el); | ||
} | ||
|
||
const hasHeader = computed(() => ctx.slots.title || props.title); | ||
|
||
const transform = props.cachePosition | ||
? useStorage<{ | ||
offsetX: number; | ||
offsetY: number; | ||
}>(getPrefixStorage('float-pane'), { | ||
offsetX: 0, | ||
offsetY: 0, | ||
}, getStorage(props.cachePosition)) | ||
: ref({ | ||
offsetX: 0, | ||
offsetY: 0, | ||
}); | ||
const styles = computed(() => { | ||
const { offsetX, offsetY } | ||
= transform.value; | ||
return { | ||
zIndex: props.zIndex, | ||
width: isNumber(props.width) ? `${props.width}px` : props.width, | ||
...props.defaultPosition, | ||
transform: `translate(${offsetX}px, ${offsetY}px)`, | ||
}; | ||
}); | ||
|
||
const { handleMouseDown, isDragging } = useDrag(transform); | ||
const handleDraggable = (event: MouseEvent) => { | ||
if (props.draggable) { | ||
handleMouseDown(event); | ||
} | ||
}; | ||
|
||
function getHeader() { | ||
const closeJsx = ( | ||
<div class={`${prefixCls}-close`} onClick={handleCancel}> | ||
<CloseOutlined /> | ||
</div> | ||
); | ||
if (!hasHeader.value) { | ||
return closeJsx; | ||
} | ||
const header = ctx.slots.title?.() || props.title; | ||
return ( | ||
<div class={[`${prefixCls}-header`, isDragging.value && `${prefixCls}-header--dragging`]} onMousedown={handleDraggable}> | ||
<div>{header}</div> | ||
{closeJsx} | ||
</div> | ||
); | ||
} | ||
|
||
const getBody = () => { | ||
return ( | ||
<div class={`${prefixCls}-body`}> | ||
{ctx.slots.default?.()} | ||
</div> | ||
); | ||
}; | ||
|
||
const showDom = computed( | ||
() => | ||
(props.displayDirective === 'if' && innerVisible.value) | ||
|| props.displayDirective === 'show', | ||
); | ||
|
||
const wrapperClass = computed(() => { | ||
return [`${prefixCls}-container`, props.contentClass].filter(Boolean); | ||
}); | ||
|
||
return () => ( | ||
<Teleport | ||
disabled={!getContainer.value?.()} | ||
to={getContainer.value?.()} | ||
> | ||
<div class={prefixCls}> | ||
<Transition | ||
name={`${prefixCls}-fade`} | ||
onAfterEnter={handleTransitionAfterEnter} | ||
onAfterLeave={handleTransitionAfterLeave} | ||
> | ||
{showDom.value && ( | ||
<div | ||
v-show={innerVisible.value} | ||
class={wrapperClass.value} | ||
style={styles.value} | ||
> | ||
{getHeader()} | ||
{getBody()} | ||
</div> | ||
)} | ||
</Transition> | ||
</div> | ||
</Teleport> | ||
); | ||
}, | ||
}); | ||
|
||
export default FloatPane; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { withInstall } from '../_util/withInstall'; | ||
import type { SFCWithInstall } from '../_util/interface'; | ||
import FloatPane from './float-pane'; | ||
|
||
type ModalType = SFCWithInstall<typeof FloatPane>; | ||
|
||
export { floatPaneProps } from './props'; | ||
export type { FloatPaneProps } from './props'; | ||
export const FFloatPane = withInstall<ModalType>(FloatPane as ModalType); | ||
|
||
export default FFloatPane; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import type { ComponentObjectPropsOptions, PropType, VNode, VNodeChild } from 'vue'; | ||
import type { ExtractPublicPropTypes } from '../_util/interface'; | ||
import type { StorageType } from '../_util/storage'; | ||
|
||
export interface PanePosition { | ||
top?: string; | ||
right?: string; | ||
bottom?: string; | ||
left?: string; | ||
} | ||
|
||
// 通用的属性 | ||
export const floatPaneProps = { | ||
visible: Boolean, | ||
displayDirective: { | ||
type: String as PropType<'show' | 'if'>, | ||
default: 'show', | ||
}, | ||
draggable: { | ||
type: Boolean, | ||
default: true, | ||
}, | ||
title: String as PropType<string | VNode | (() => VNodeChild)>, | ||
width: { | ||
type: [String, Number] as PropType<string | number>, | ||
default: 520, | ||
}, | ||
zIndex: { | ||
type: Number, | ||
default: 3000, | ||
}, | ||
defaultPosition: { | ||
type: Object as PropType<PanePosition>, | ||
default(): PanePosition { | ||
return { | ||
bottom: '50px', | ||
right: '50px', | ||
}; | ||
}, | ||
}, | ||
cachePosition: { | ||
type: String as PropType<StorageType>, | ||
default: 'local', | ||
}, | ||
getContainer: { | ||
type: Function as PropType<() => HTMLElement>, | ||
}, | ||
// 内容外层类名 | ||
contentClass: String, | ||
} as const satisfies ComponentObjectPropsOptions; | ||
|
||
export type FloatPaneProps = ExtractPublicPropTypes<typeof floatPaneProps>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
@import '../../style/themes/index'; | ||
@import '../../style/mixins/index'; | ||
|
||
@modal-prefix-cls: ~'@{cls-prefix}-float-pane'; | ||
|
||
.@{modal-prefix-cls} { | ||
.default(); | ||
.text(); | ||
|
||
&-container { | ||
position: fixed; | ||
box-sizing: border-box; | ||
background: var(--f-body-bg-color); | ||
border-radius: var(--f-border-radius-base); | ||
box-shadow: @shadow-down; | ||
} | ||
|
||
&-header { | ||
position: relative; | ||
|
||
--f-modal-header-icon-color: inherit; | ||
display: flex; | ||
align-items: center; | ||
padding: @padding-md @padding-sm; | ||
color: var(--f-head-color); | ||
font-weight: @font-weight-medium; | ||
font-size: @font-size-head; | ||
border-bottom: var(--f-border-width-base) var(--f-border-style-base) var(--f-border-color-base); | ||
|
||
&--dragging { | ||
cursor: move; | ||
} | ||
|
||
.@{modal-prefix-cls}-icon { | ||
display: flex; | ||
align-items: center; | ||
padding-right: @padding-sm; | ||
color: var(--f-modal-header-icon-color); | ||
font-size: @font-size-title; | ||
} | ||
} | ||
|
||
&-body { | ||
padding: @padding-xs 0; | ||
color: var(--f-sub-head-color); | ||
font-size: var(--f-font-size-base); | ||
} | ||
|
||
&-close { | ||
position: absolute; | ||
top: auto; | ||
right: 0; | ||
padding: 0 @padding-sm; | ||
color: var(--f-sub-head-color); | ||
font-size: @font-size-head; | ||
line-height: 0; | ||
cursor: pointer; | ||
} | ||
|
||
&-fade-leave-active, | ||
&-fade-enter-active { | ||
transition: all @animation-duration-slow @ease-base-out; | ||
} | ||
|
||
&-fade-leave-to, | ||
&-fade-enter-from { | ||
transform: scale(0); | ||
opacity: 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import '../../style'; | ||
import './index.less'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { type Ref, ref } from 'vue'; | ||
import { throttle } from 'lodash-es'; | ||
import { useEventListener } from '@vueuse/core'; | ||
|
||
export const useDrag = ( | ||
transform: Ref<{ | ||
offsetX: number; | ||
offsetY: number; | ||
}>, | ||
) => { | ||
const isDragging = ref(false); | ||
|
||
let startX: number; | ||
let startY: number; | ||
let imgOffsetX: number; | ||
let imgOffsetY: number; | ||
|
||
const handleMouseDown = (event: MouseEvent) => { | ||
// 取消默认图片拖拽的行为 | ||
event.preventDefault(); | ||
isDragging.value = true; | ||
// 存储鼠标按下的偏移量和事件发生坐标 | ||
const { offsetX, offsetY } = transform.value; | ||
startX = event.pageX; | ||
startY = event.pageY; | ||
imgOffsetX = offsetX; | ||
imgOffsetY = offsetY; | ||
}; | ||
|
||
const handleDrag = throttle((event: MouseEvent) => { | ||
// 避免移动到窗口外 | ||
if (event.clientY <= 0 || event.clientX <= 0 || event.clientX >= window.innerWidth || event.clientY >= window.innerHeight) { | ||
return; | ||
} | ||
transform.value = { | ||
...transform.value, | ||
offsetX: imgOffsetX + event.pageX - startX, | ||
offsetY: imgOffsetY + event.pageY - startY, | ||
}; | ||
}); | ||
|
||
// mousemove 事件监听 document 拖拽效果更流畅 | ||
useEventListener(document, 'mousemove', (event) => { | ||
if (!isDragging.value) { | ||
return; | ||
} | ||
handleDrag(event); | ||
}); | ||
|
||
useEventListener(document, 'mouseup', () => { | ||
if (!isDragging.value) { | ||
return; | ||
} | ||
isDragging.value = false; | ||
}); | ||
|
||
return { | ||
handleMouseDown, | ||
isDragging, | ||
}; | ||
}; |
Oops, something went wrong.