u
This commit is contained in:
1 parent
b24015fbae
commit
6d4f1ee402
19 files changed
+782
-108
No files matched your search
+2
-2
@@ -49,9 +49,9 @@
|
|||||||
"@use '../_styles/_variables.scss' as *;",
|
"@use '../_styles/_variables.scss' as *;",
|
||||||
"@use 'sass:color';",
|
"@use 'sass:color';",
|
||||||
"",
|
"",
|
||||||
"$${1:component}-prefix: #{$prefix}-${1:component};",
|
"$${1:component}-prefix: '#{$$prefix}-${1:component}';",
|
||||||
"",
|
"",
|
||||||
".#{${1:component}-prefix} {",
|
".#{$${1:component}-prefix} {",
|
||||||
"}"
|
"}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed, defineComponent } from 'vue'
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
import Icon from '../icon'
|
import Icon from '../icon'
|
||||||
@@ -19,6 +20,7 @@ export default defineComponent({
|
|||||||
iconPosition: { type: String as () => 'left' | 'right', default: 'left' },
|
iconPosition: { type: String as () => 'left' | 'right', default: 'left' },
|
||||||
size: { type: String as () => 'small' | 'medium' | 'large', default: 'medium' },
|
size: { type: String as () => 'small' | 'medium' | 'large', default: 'medium' },
|
||||||
block: { type: Boolean, default: false },
|
block: { type: Boolean, default: false },
|
||||||
|
onClick: { type: Function as PropType<(event: MouseEvent) => void> }, // 注意这里不要加 ?
|
||||||
},
|
},
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const prefixCls = getPrefixCls('btn')
|
const prefixCls = getPrefixCls('btn')
|
||||||
@@ -39,7 +41,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return (
|
return (
|
||||||
<button class={cls.value} disabled={props.disabled || props.loading}>
|
<button
|
||||||
|
class={cls.value}
|
||||||
|
disabled={props.disabled || props.loading}
|
||||||
|
onClick={props.onClick}
|
||||||
|
>
|
||||||
{props.iconPosition === 'left' && (props.loading || props.icon) && (
|
{props.iconPosition === 'left' && (props.loading || props.icon) && (
|
||||||
<Icon
|
<Icon
|
||||||
icon={props.loading ? 'mingcute:loading-line' : props.icon}
|
icon={props.loading ? 'mingcute:loading-line' : props.icon}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
export { default as ConfigProvider } from './_config'
|
export { default as ConfigProvider } from './_config'
|
||||||
export { default as Button } from './button'
|
export { default as Button } from './button'
|
||||||
|
export { default as Drawer } from './drawer'
|
||||||
export { default as Group } from './group'
|
export { default as Group } from './group'
|
||||||
export { default as Icon } from './icon'
|
export { default as Icon } from './icon'
|
||||||
export { default as Input } from './input'
|
export { default as Input } from './input'
|
||||||
|
export { default as Popconfirm } from './popconfirm'
|
||||||
export { default as Popup } from './popup'
|
export { default as Popup } from './popup'
|
||||||
export { default as Spin } from './spin'
|
export { default as Spin } from './spin'
|
||||||
export { default as Textarea } from './textarea'
|
export { default as Textarea } from './textarea'
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
@use '../_styles/_variables.scss' as *;
|
||||||
|
|
||||||
|
$drawer-prefix: '#{$prefix}-drawer';
|
||||||
|
|
||||||
|
.#{$drawer-prefix} {
|
||||||
|
z-index: 1500;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&-mask {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
transition:
|
||||||
|
transform 0.25s ease,
|
||||||
|
opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 46px;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-color-base;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.#{$drawer-prefix}-close {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
color: $text-color-assist;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $default-color-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-footer {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
text-align: left;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: $bg-color-control;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
&-left {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
&-top {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
&-bottom {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-open {
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
.#{$drawer-prefix}-mask {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$drawer-prefix}-right .#{$drawer-prefix}-wrapper {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
&.#{$drawer-prefix}-left .#{$drawer-prefix}-wrapper {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
&.#{$drawer-prefix}-top .#{$drawer-prefix}-wrapper {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
&.#{$drawer-prefix}-bottom .#{$drawer-prefix}-wrapper {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right .#{$drawer-prefix}-wrapper {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
&-left .#{$drawer-prefix}-wrapper {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
&-top .#{$drawer-prefix}-wrapper {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
&-bottom .#{$drawer-prefix}-wrapper {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
|
import { defineComponent, onMounted, ref, Teleport } from 'vue'
|
||||||
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
|
import Button from '../button/index'
|
||||||
|
import Icon from '../icon/index'
|
||||||
|
|
||||||
|
type DrawerSize = 'small' | 'medium' | 'large'
|
||||||
|
type SizeType = number | string | DrawerSize
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Drawer',
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String as PropType<'top' | 'bottom' | 'left' | 'right'>,
|
||||||
|
default: 'right',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String] as PropType<SizeType>,
|
||||||
|
default: 'medium',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
showClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
confirmBtn: {
|
||||||
|
type: null,
|
||||||
|
default: () => ({ label: '确认', type: 'solid', status: 'primary' }),
|
||||||
|
},
|
||||||
|
cancelBtn: {
|
||||||
|
type: null,
|
||||||
|
default: () => ({ label: '取消', type: 'solid', status: 'default' }),
|
||||||
|
},
|
||||||
|
asyncConfirm: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
maskClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
appendTo: {
|
||||||
|
type: [String, Object] as PropType<string | HTMLElement>,
|
||||||
|
default: 'body',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['update:visible', 'confirm', 'cancel'],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
|
const prefixCls = getPrefixCls('drawer')
|
||||||
|
const confirmLoading = ref<boolean>(false)
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit('update:visible', false)
|
||||||
|
confirmLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMaskClick = () => {
|
||||||
|
if (props.maskClose) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveSize = (value: SizeType, defaultPx = 300) => {
|
||||||
|
if (typeof value === 'number')
|
||||||
|
return `${value}px`
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const presetMap: Record<DrawerSize, string> = {
|
||||||
|
small: '300px',
|
||||||
|
medium: '500px',
|
||||||
|
large: '700px',
|
||||||
|
}
|
||||||
|
if (presetMap[value as DrawerSize])
|
||||||
|
return presetMap[value as DrawerSize]
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return `${defaultPx}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
if (props.asyncConfirm) {
|
||||||
|
confirmLoading.value = true
|
||||||
|
emit('confirm', close, () => {
|
||||||
|
confirmLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
close()
|
||||||
|
emit('confirm')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
close()
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
// 挂载目标位置
|
||||||
|
const targetEl = ref<HTMLElement>(document.body)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.appendTo instanceof HTMLElement) {
|
||||||
|
targetEl.value = props.appendTo
|
||||||
|
}
|
||||||
|
else if (typeof props.appendTo === 'string') {
|
||||||
|
const el = document.querySelector(props.appendTo)
|
||||||
|
if (el)
|
||||||
|
targetEl.value = el as HTMLElement
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 判断是否是 body
|
||||||
|
const isBody = props.appendTo === 'body'
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Teleport to={targetEl.value}>
|
||||||
|
<div
|
||||||
|
class={[prefixCls, `${prefixCls}-${props.placement}`, props.visible && `${prefixCls}-open`]}
|
||||||
|
style={{
|
||||||
|
position: isBody ? 'fixed' : 'absolute',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class={`${prefixCls}-mask`} onClick={handleMaskClick}></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={[`${prefixCls}-wrapper`]}
|
||||||
|
style={{
|
||||||
|
width: ['left', 'right'].includes(props.placement)
|
||||||
|
? resolveSize(props.size)
|
||||||
|
: '100%',
|
||||||
|
height: ['top', 'bottom'].includes(props.placement)
|
||||||
|
? resolveSize(props.size)
|
||||||
|
: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class={`${prefixCls}-header`}>
|
||||||
|
{slots.header
|
||||||
|
? slots.header()
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<span>{props.title}</span>
|
||||||
|
{props.showClose && (
|
||||||
|
<Icon
|
||||||
|
class={`${prefixCls}-close`}
|
||||||
|
icon="ix:close"
|
||||||
|
onClick={() => emit('update:visible', false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={`${prefixCls}-body`}>
|
||||||
|
{slots.default ? slots.default() : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={`${prefixCls}-footer`}>
|
||||||
|
{slots.footer
|
||||||
|
? slots.footer()
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<Button {...props.cancelBtn} block onClick={handleCancel}>
|
||||||
|
{props.cancelBtn.label}
|
||||||
|
</Button>
|
||||||
|
<Button loading={confirmLoading.value} block {...props.confirmBtn} onClick={handleConfirm}>
|
||||||
|
{props.confirmBtn.label}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
@use './textarea/index.scss' as *;
|
@use './textarea/index.scss' as *;
|
||||||
@use './group/index.scss' as *;
|
@use './group/index.scss' as *;
|
||||||
@use './popup/index.scss' as *;
|
@use './popup/index.scss' as *;
|
||||||
|
@use './popconfirm/index.scss' as *;
|
||||||
@use './icon/index.scss' as *;
|
@use './icon/index.scss' as *;
|
||||||
@use './message/index.scss' as *;
|
@use './message/index.scss' as *;
|
||||||
@use './notification/index.scss' as *;
|
@use './notification/index.scss' as *;
|
||||||
|
@use './drawer/index.scss' as *;
|
||||||
@@ -3,29 +3,28 @@
|
|||||||
|
|
||||||
$message-prefix: #{$prefix}-message;
|
$message-prefix: #{$prefix}-message;
|
||||||
|
|
||||||
|
.#{$message-prefix}-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 6000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.#{$message-prefix} {
|
.#{$message-prefix} {
|
||||||
&-wrapper {
|
display: inline-flex;
|
||||||
position: fixed;
|
align-items: center;
|
||||||
z-index: 6000;
|
outline: 0;
|
||||||
display: flex;
|
width: fit-content;
|
||||||
align-items: center;
|
vertical-align: middle;
|
||||||
gap: 8px;
|
border-radius: $border-radius;
|
||||||
}
|
padding: 6px 12px;
|
||||||
&-item {
|
background-color: $bg-color-container;
|
||||||
display: inline-flex;
|
color: $text-color-base;
|
||||||
align-items: center;
|
box-shadow: $shadow-overlay;
|
||||||
outline: 0;
|
box-sizing: border-box;
|
||||||
width: fit-content;
|
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
vertical-align: middle;
|
&-message {
|
||||||
border-radius: $border-radius;
|
|
||||||
padding: 6px 12px;
|
|
||||||
background-color: $bg-color-container;
|
|
||||||
color: $text-color-base;
|
|
||||||
box-shadow: $shadow-overlay;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
&-content {
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
@@ -40,8 +39,8 @@ $message-prefix: #{$prefix}-message;
|
|||||||
color: $text-color-assist;
|
color: $text-color-assist;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 16px;
|
width: 18px;
|
||||||
height: 16px;
|
height: 18px;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $default-color-hover;
|
background-color: $default-color-hover;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { VNode } from 'vue'
|
||||||
import { createApp, h } from 'vue'
|
import { createApp, h } from 'vue'
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
import MessageItem from './item'
|
import MessageItem from './item'
|
||||||
@@ -16,7 +17,7 @@ type Placement
|
|||||||
type MessageType = 'info' | 'success' | 'warning' | 'error' | 'loading' | 'question'
|
type MessageType = 'info' | 'success' | 'warning' | 'error' | 'loading' | 'question'
|
||||||
|
|
||||||
interface MessageOptions {
|
interface MessageOptions {
|
||||||
message: string
|
message: string | VNode | (() => VNode)
|
||||||
type?: MessageType
|
type?: MessageType
|
||||||
placement?: Placement
|
placement?: Placement
|
||||||
duration?: number
|
duration?: number
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { PropType } from 'vue'
|
import type { PropType, VNode } from 'vue'
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
import Icon from '../icon/index'
|
import Icon from '../icon/index'
|
||||||
@@ -9,7 +9,10 @@ export default defineComponent({
|
|||||||
name: 'MessageItem',
|
name: 'MessageItem',
|
||||||
props: {
|
props: {
|
||||||
type: { type: String, default: 'info' },
|
type: { type: String, default: 'info' },
|
||||||
message: { type: String, required: true },
|
message: {
|
||||||
|
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
placement: { type: String, required: true },
|
placement: { type: String, required: true },
|
||||||
showClose: { type: Boolean, default: false },
|
showClose: { type: Boolean, default: false },
|
||||||
onClose: {
|
onClose: {
|
||||||
@@ -26,10 +29,14 @@ export default defineComponent({
|
|||||||
question: 'ix:question-filled',
|
question: 'ix:question-filled',
|
||||||
loading: 'mingcute:loading-line',
|
loading: 'mingcute:loading-line',
|
||||||
}
|
}
|
||||||
|
const renderMessage = (message: string | VNode | (() => VNode)) => {
|
||||||
|
if (typeof message === 'function')
|
||||||
|
return message()
|
||||||
|
return message
|
||||||
|
}
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
class={`${prefixCls}-item`}
|
class={`${prefixCls}`}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon={iconMap[props.type ?? 'info']}
|
icon={iconMap[props.type ?? 'info']}
|
||||||
@@ -37,7 +44,9 @@ export default defineComponent({
|
|||||||
class={`${prefixCls}-icon ${prefixCls}-${props.type ?? 'info'}`}
|
class={`${prefixCls}-icon ${prefixCls}-${props.type ?? 'info'}`}
|
||||||
spin={props.type === 'loading'}
|
spin={props.type === 'loading'}
|
||||||
/>
|
/>
|
||||||
<span class={`${prefixCls}-content`}>{props.message}</span>
|
<span class={`${prefixCls}-message`}>
|
||||||
|
{renderMessage(props.message)}
|
||||||
|
</span>
|
||||||
{props.showClose
|
{props.showClose
|
||||||
? (
|
? (
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useNotification } from '@ao/ao-design'
|
||||||
|
import { h } from 'vue'
|
||||||
|
|
||||||
|
const notification = useNotification()
|
||||||
|
|
||||||
|
function showCustomMessage() {
|
||||||
|
notification.success({
|
||||||
|
title: '自定义消息',
|
||||||
|
message: () =>
|
||||||
|
h('div', { style: { color: 'red', position: 'relative' } }, [
|
||||||
|
h('p', {}, '这里是自定义内容,可以是任意 VNode'),
|
||||||
|
h('strong', {}, '加粗文本'),
|
||||||
|
// 右下角按钮容器
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '4px',
|
||||||
|
right: '4px',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '4px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h(
|
||||||
|
'button',
|
||||||
|
{
|
||||||
|
style: { fontSize: '12px', padding: '2px 6px' },
|
||||||
|
onClick: () => alert('Button A clicked'),
|
||||||
|
},
|
||||||
|
'A',
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
'button',
|
||||||
|
{
|
||||||
|
style: { fontSize: '12px', padding: '2px 6px' },
|
||||||
|
onClick: () => alert('Button B clicked'),
|
||||||
|
},
|
||||||
|
'B',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
type: 'success',
|
||||||
|
showClose: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="demo">
|
||||||
|
<AButton status="primary" @click="showCustomMessage">
|
||||||
|
自定义 h 函数消息
|
||||||
|
</AButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.demo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,7 @@ $notification-prefix: #{$prefix}-notification;
|
|||||||
z-index: 6000;
|
z-index: 6000;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$notification-prefix} {
|
.#{$notification-prefix} {
|
||||||
@@ -54,7 +54,7 @@ $notification-prefix: #{$prefix}-notification;
|
|||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
color: $text-color-assist;
|
color: $text-color-assist;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 50%;
|
border-radius: $border-radius;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -62,7 +62,7 @@ $notification-prefix: #{$prefix}-notification;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-content {
|
&-message {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $text-color-secondary;
|
color: $text-color-secondary;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type Placement
|
|||||||
type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'loading' | 'question'
|
type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'loading' | 'question'
|
||||||
|
|
||||||
interface NotificationOptions {
|
interface NotificationOptions {
|
||||||
content: string | VNode | (() => VNode)
|
message: string | VNode | (() => VNode)
|
||||||
|
|
||||||
title: string
|
title: string
|
||||||
subTitle?: string
|
subTitle?: string
|
||||||
@@ -63,7 +63,7 @@ export function useNotification(): MessageFn {
|
|||||||
opts: NotificationOptions,
|
opts: NotificationOptions,
|
||||||
defaultType: NotificationType = 'info',
|
defaultType: NotificationType = 'info',
|
||||||
): NotificationHandle => {
|
): NotificationHandle => {
|
||||||
const { content, title, subTitle, type = defaultType, placement = 'top-end', duration = 3000, showClose } = opts
|
const { message, title, subTitle, type = defaultType, placement = 'top-end', duration = 3000, showClose } = opts
|
||||||
|
|
||||||
const container = getContainer(placement)
|
const container = getContainer(placement)
|
||||||
let timer: number | undefined
|
let timer: number | undefined
|
||||||
@@ -75,7 +75,7 @@ export function useNotification(): MessageFn {
|
|||||||
type,
|
type,
|
||||||
title,
|
title,
|
||||||
subTitle,
|
subTitle,
|
||||||
content,
|
message,
|
||||||
placement,
|
placement,
|
||||||
showClose,
|
showClose,
|
||||||
style: {
|
style: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PropType, VNode } from 'vue'
|
import type { PropType, VNode } from 'vue'
|
||||||
import { defineComponent, h } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
import Icon from '../icon/index'
|
import Icon from '../icon/index'
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
type: { type: String, default: 'info' },
|
type: { type: String, default: 'info' },
|
||||||
title: { type: String, required: true },
|
title: { type: String, required: true },
|
||||||
content: {
|
message: {
|
||||||
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
@@ -31,10 +31,10 @@ export default defineComponent({
|
|||||||
question: 'ix:question-filled',
|
question: 'ix:question-filled',
|
||||||
loading: 'mingcute:loading-line',
|
loading: 'mingcute:loading-line',
|
||||||
}
|
}
|
||||||
const renderContent = (content: string | VNode | (() => VNode)) => {
|
const renderMessage = (message: string | VNode | (() => VNode)) => {
|
||||||
if (typeof content === 'function')
|
if (typeof message === 'function')
|
||||||
return content()
|
return message()
|
||||||
return content
|
return message
|
||||||
}
|
}
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
@@ -67,8 +67,8 @@ export default defineComponent({
|
|||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={`${prefixCls}-content`}>
|
<div class={`${prefixCls}-message`}>
|
||||||
{renderContent(props.content)}
|
{renderMessage(props.message)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
@use '../_styles/_variables.scss' as *;
|
||||||
|
@use 'sass:color';
|
||||||
|
|
||||||
|
$popconfirm-prefix: '#{$prefix}-popconfirm';
|
||||||
|
|
||||||
|
.#{$popconfirm-prefix}-wrapper {
|
||||||
|
padding: 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$popconfirm-prefix} {
|
||||||
|
&-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
&-icon {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
max-width: 320px;
|
||||||
|
color: $text-color-base;
|
||||||
|
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
&-description {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $text-color-secondary;
|
||||||
|
}
|
||||||
|
&-footer {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 16px;
|
||||||
|
> * + * {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-info {
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
&-success {
|
||||||
|
color: $success-color;
|
||||||
|
}
|
||||||
|
&-warning {
|
||||||
|
color: $warning-color;
|
||||||
|
}
|
||||||
|
&-error {
|
||||||
|
color: $danger-color;
|
||||||
|
}
|
||||||
|
&-question {
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
&-loading {
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import type { PropType, VNode } from 'vue'
|
||||||
|
import { defineComponent, ref, watch } from 'vue'
|
||||||
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
|
import Button from '../button/index'
|
||||||
|
import Icon from '../icon/index'
|
||||||
|
import Popup from '../popup/index'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Popconfirm',
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String as PropType<'info' | 'success' | 'error' | 'warning' | 'question' | 'loading'>,
|
||||||
|
default: 'info',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
asyncConfirm: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false, // 默认同步关闭,不显示 loading
|
||||||
|
},
|
||||||
|
confirmBtn: {
|
||||||
|
type: null,
|
||||||
|
default: () => ({ label: '确认', size: 'small', type: 'solid', status: 'primary' }),
|
||||||
|
},
|
||||||
|
cancelBtn: {
|
||||||
|
type: null,
|
||||||
|
default: () => ({ label: '取消', size: 'small', type: 'text', status: 'default' }),
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String as PropType<
|
||||||
|
'top' | 'bottom' | 'left' | 'right'
|
||||||
|
| 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'
|
||||||
|
| 'left-start' | 'left-end' | 'right-start' | 'right-end'
|
||||||
|
>,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
showArrow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['confirm', 'cancel'],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
|
const prefixCls = getPrefixCls('popconfirm')
|
||||||
|
|
||||||
|
const popVisible = ref<boolean>(false)
|
||||||
|
|
||||||
|
const confirmLoading = ref<boolean>(false)
|
||||||
|
|
||||||
|
const iconMap: Record<string, string> = {
|
||||||
|
success: 'ix:success-filled',
|
||||||
|
error: 'ix:error-filled',
|
||||||
|
warning: 'ix:warning-filled',
|
||||||
|
info: 'ix:info-filled',
|
||||||
|
question: 'ix:question-filled',
|
||||||
|
loading: 'mingcute:loading-line',
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderNode = (val: any) => {
|
||||||
|
if (typeof val === 'function')
|
||||||
|
return val()
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
if (props.asyncConfirm) {
|
||||||
|
confirmLoading.value = true
|
||||||
|
|
||||||
|
emit('confirm', () => { // resolve
|
||||||
|
popVisible.value = false
|
||||||
|
confirmLoading.value = false
|
||||||
|
}, () => { // reject
|
||||||
|
confirmLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
popVisible.value = false
|
||||||
|
emit('confirm')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
popVisible.value = false
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染内容块
|
||||||
|
const renderContent = () => {
|
||||||
|
return (
|
||||||
|
<div class={`${prefixCls}-wrapper`}>
|
||||||
|
<div class={`${prefixCls}-main`}>
|
||||||
|
{slots.icon
|
||||||
|
? (
|
||||||
|
<span class={`${prefixCls}-icon`}>{slots.icon()}</span>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<Icon
|
||||||
|
icon={iconMap[props.type ?? 'info']}
|
||||||
|
size={22}
|
||||||
|
class={`${prefixCls}-icon ${prefixCls}-${props.type ?? 'info'}`}
|
||||||
|
spin={props.type === 'loading'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div class={`${prefixCls}-title-wrapper`}>
|
||||||
|
<span class={`${prefixCls}-title`}>
|
||||||
|
{slots.title ? slots.title() : renderNode(props.title)}
|
||||||
|
</span>
|
||||||
|
<span class={`${prefixCls}-description`}>
|
||||||
|
{slots.description
|
||||||
|
? (
|
||||||
|
<div class={`${prefixCls}-description`}>{slots.description()}</div>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
props.description && <div class={`${prefixCls}-description`}>{renderNode(props.description)}</div>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{slots.footer
|
||||||
|
? (
|
||||||
|
slots.footer()
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div class={`${prefixCls}-footer`}>
|
||||||
|
<Button {...props.cancelBtn} onClick={handleCancel}>
|
||||||
|
{props.cancelBtn.label}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button loading={confirmLoading.value} {...props.confirmBtn} onClick={handleConfirm}>
|
||||||
|
{props.confirmBtn.label}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Popup
|
||||||
|
v-model:visible={popVisible.value}
|
||||||
|
placement={props.placement}
|
||||||
|
showArrow={props.showArrow}
|
||||||
|
content={renderContent}
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: () => slots.default?.(),
|
||||||
|
}}
|
||||||
|
</Popup>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -7,7 +7,6 @@ $popup-prefix: '#{$prefix}-popup';
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: $bg-color-container;
|
background-color: $bg-color-container;
|
||||||
color: $text-color-base;
|
color: $text-color-base;
|
||||||
padding: 4px 8px;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@@ -29,7 +28,9 @@ $popup-prefix: '#{$prefix}-popup';
|
|||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&-content {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
// 上方弹出,箭头在底部
|
// 上方弹出,箭头在底部
|
||||||
&[data-popup-placement^='top'] > &-arrow {
|
&[data-popup-placement^='top'] > &-arrow {
|
||||||
bottom: -4px;
|
bottom: -4px;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { PropType } from 'vue'
|
import type { PropType, VNode } from 'vue'
|
||||||
import { arrow, autoUpdate, flip, offset, useFloating } from '@floating-ui/vue'
|
import { arrow, autoUpdate, flip, offset, useFloating } from '@floating-ui/vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -17,6 +17,8 @@ import { getPrefixCls } from '../_utils/getPrefixCls'
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'APopup',
|
name: 'APopup',
|
||||||
props: {
|
props: {
|
||||||
|
visible: { type: Boolean, default: false },
|
||||||
|
|
||||||
trigger: {
|
trigger: {
|
||||||
type: [String, Array] as PropType<
|
type: [String, Array] as PropType<
|
||||||
'hover' | 'click' | 'focus' | 'contextMenu' | Array<'hover' | 'click' | 'focus' | 'contextMenu'>
|
'hover' | 'click' | 'focus' | 'contextMenu' | Array<'hover' | 'click' | 'focus' | 'contextMenu'>
|
||||||
@@ -33,7 +35,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
showArrow: {
|
showArrow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: false,
|
||||||
},
|
},
|
||||||
destroyOnClose: {
|
destroyOnClose: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -60,18 +62,19 @@ export default defineComponent({
|
|||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
type: String,
|
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, { slots }) {
|
emits: ['update:visible'],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
const prefixCls = getPrefixCls('popup')
|
const prefixCls = getPrefixCls('popup')
|
||||||
|
|
||||||
const triggerRef = ref<HTMLElement | null>(null) // 触发 引用
|
const triggerRef = ref<HTMLElement | null>(null) // 触发 引用
|
||||||
const floatingRef = ref<HTMLElement | null>(null) // float 引用
|
const floatingRef = ref<HTMLElement | null>(null) // float 引用
|
||||||
const arrowRef = ref<HTMLElement | null>(null) // 箭头引用
|
const arrowRef = ref<HTMLElement | null>(null) // 箭头引用
|
||||||
const visible = ref(false)
|
const localVisible = ref(props.visible)
|
||||||
const hasRendered = ref(false)
|
const hasRendered = ref(props.visible)
|
||||||
let cleanup: (() => void) | null = null
|
let cleanup: (() => void) | null = null
|
||||||
|
|
||||||
const { floatingStyles, middlewareData, placement, update } = useFloating(
|
const { floatingStyles, middlewareData, placement, update } = useFloating(
|
||||||
@@ -132,25 +135,43 @@ export default defineComponent({
|
|||||||
floatEl.style.width = `${triggerEl.offsetWidth}px`
|
floatEl.style.width = `${triggerEl.offsetWidth}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 准备浮动框
|
const showPopup = async () => {
|
||||||
const prepareFloating = async () => {
|
|
||||||
if (!hasRendered.value)
|
if (!hasRendered.value)
|
||||||
hasRendered.value = true
|
hasRendered.value = true
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
fitFloatingWidth()
|
fitFloatingWidth()
|
||||||
|
localVisible.value = true
|
||||||
|
|
||||||
|
setupAutoUpdate()
|
||||||
|
emit('update:visible', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const showPopup = async () => {
|
const hidePopup = async () => {
|
||||||
await prepareFloating()
|
localVisible.value = false
|
||||||
visible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const hidePopup = () => {
|
clearAutoUpdate()
|
||||||
visible.value = false
|
emit('update:visible', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const togglePopup = async () => {
|
const togglePopup = async () => {
|
||||||
await prepareFloating()
|
if (!hasRendered.value)
|
||||||
visible.value = !visible.value
|
hasRendered.value = true
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
fitFloatingWidth()
|
||||||
|
|
||||||
|
localVisible.value = !localVisible.value
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
if (localVisible.value) {
|
||||||
|
setupAutoUpdate()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clearAutoUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:visible', localVisible.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 事件绑定
|
// 事件绑定
|
||||||
@@ -181,29 +202,57 @@ export default defineComponent({
|
|||||||
const triggerEl = getDOM(triggerRef.value)
|
const triggerEl = getDOM(triggerRef.value)
|
||||||
const floatingEl = getDOM(floatingRef.value)
|
const floatingEl = getDOM(floatingRef.value)
|
||||||
if (!triggerEl?.contains(target) && !floatingEl?.contains(target)) {
|
if (!triggerEl?.contains(target) && !floatingEl?.contains(target)) {
|
||||||
visible.value = false
|
localVisible.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 Teleport 挂载目标
|
// 获取 Teleport 挂载目标
|
||||||
const getTeleportTarget = () => {
|
const targetEl = ref<HTMLElement>(document.body)
|
||||||
if (props.appendTo instanceof HTMLElement)
|
|
||||||
return props.appendTo
|
onMounted(() => {
|
||||||
if (typeof props.appendTo === 'string') {
|
if (props.appendTo instanceof HTMLElement) {
|
||||||
|
targetEl.value = props.appendTo
|
||||||
|
}
|
||||||
|
else if (typeof props.appendTo === 'string') {
|
||||||
const el = document.querySelector(props.appendTo)
|
const el = document.querySelector(props.appendTo)
|
||||||
if (el)
|
if (el)
|
||||||
return el
|
targetEl.value = el as HTMLElement
|
||||||
}
|
}
|
||||||
return 'body'
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染 trigger slot
|
// 渲染 trigger slot
|
||||||
const renderTriggerSlot = () => {
|
const renderTriggerSlot = () => {
|
||||||
const slotContent = slots.reference?.() || slots.default?.() || []
|
const slotContent = slots.reference?.() || slots.default?.() || []
|
||||||
if (slotContent.length === 1)
|
// 只有一个子节点时
|
||||||
return cloneVNode(slotContent[0], { ref: triggerRef, ...eventProps })
|
if (slotContent.length === 1) {
|
||||||
|
const vnode = slotContent[0]
|
||||||
|
return cloneVNode(vnode, {
|
||||||
|
...eventProps,
|
||||||
|
ref: (el: any) => {
|
||||||
|
// 使用 getDOM 解析真实 DOM
|
||||||
|
triggerRef.value = getDOM(el)
|
||||||
|
|
||||||
|
// 保留用户传入的 ref
|
||||||
|
const originalRef = (vnode as any).ref
|
||||||
|
if (typeof originalRef === 'function') {
|
||||||
|
originalRef(el)
|
||||||
|
}
|
||||||
|
else if (originalRef && typeof originalRef === 'object') {
|
||||||
|
;(originalRef as any).value = el
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多个子节点时,包一层 span
|
||||||
return (
|
return (
|
||||||
<span ref={triggerRef} style="display:inline-flex;" {...eventProps}>
|
<span
|
||||||
|
style="display:inline-flex;"
|
||||||
|
{...eventProps}
|
||||||
|
ref={(el) => {
|
||||||
|
triggerRef.value = getDOM(el)
|
||||||
|
}}
|
||||||
|
>
|
||||||
{slotContent.map(v => cloneVNode(v))}
|
{slotContent.map(v => cloneVNode(v))}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -218,24 +267,29 @@ export default defineComponent({
|
|||||||
clearAutoUpdate()
|
clearAutoUpdate()
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(visible, async (val) => {
|
watch(() => props.visible, (val) => {
|
||||||
if (val) {
|
localVisible.value = val
|
||||||
setupAutoUpdate()
|
}, { immediate: true })
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearAutoUpdate()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const renderContent = (content: string | VNode | (() => VNode)) => {
|
||||||
|
if (typeof content === 'function')
|
||||||
|
return content()
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
return <div class={`${prefixCls}-content`}>{content}</div>
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
// 懒加载
|
// 懒加载
|
||||||
const shouldRenderPopup = props.destroyOnClose ? visible.value : hasRendered.value
|
const shouldRenderPopup = props.destroyOnClose
|
||||||
|
? localVisible.value // 需要销毁时,只在显示时渲染
|
||||||
|
: localVisible.value || hasRendered.value // 不销毁时保持已渲染
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderTriggerSlot()}
|
{renderTriggerSlot()}
|
||||||
|
|
||||||
<Teleport to={getTeleportTarget()}>
|
<Teleport to={targetEl.value}>
|
||||||
{shouldRenderPopup && (
|
{shouldRenderPopup && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -250,7 +304,7 @@ export default defineComponent({
|
|||||||
appear
|
appear
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-show={visible.value}
|
v-show={localVisible.value}
|
||||||
ref={floatingRef}
|
ref={floatingRef}
|
||||||
class={[prefixCls, props.popupClass]}
|
class={[prefixCls, props.popupClass]}
|
||||||
style={{
|
style={{
|
||||||
@@ -260,7 +314,7 @@ export default defineComponent({
|
|||||||
}}
|
}}
|
||||||
data-popup-placement={placement.value}
|
data-popup-placement={placement.value}
|
||||||
>
|
>
|
||||||
{slots.content ? slots.content() : props.content}
|
{slots.content ? slots.content() : renderContent(props.content)}
|
||||||
{props.showArrow && (
|
{props.showArrow && (
|
||||||
<span
|
<span
|
||||||
class={`${prefixCls}-arrow`}
|
class={`${prefixCls}-arrow`}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const notification = useNotification()
|
|||||||
function showCustomMessage() {
|
function showCustomMessage() {
|
||||||
notification.success({
|
notification.success({
|
||||||
title: '自定义消息',
|
title: '自定义消息',
|
||||||
content: () =>
|
message: () =>
|
||||||
h('div', { style: { color: 'red', position: 'relative' } }, [
|
h('div', { style: { color: 'red', position: 'relative' } }, [
|
||||||
h('p', {}, '这里是自定义内容,可以是任意 VNode'),
|
h('p', {}, '这里是自定义内容,可以是任意 VNode'),
|
||||||
h('strong', {}, '加粗文本'),
|
h('strong', {}, '加粗文本'),
|
||||||
@@ -23,29 +23,11 @@ function showCustomMessage() {
|
|||||||
gap: '4px',
|
gap: '4px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[
|
|
||||||
h(
|
|
||||||
'button',
|
|
||||||
{
|
|
||||||
style: { fontSize: '12px', padding: '2px 6px' },
|
|
||||||
onClick: () => alert('Button A clicked'),
|
|
||||||
},
|
|
||||||
'A',
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
'button',
|
|
||||||
{
|
|
||||||
style: { fontSize: '12px', padding: '2px 6px' },
|
|
||||||
onClick: () => alert('Button B clicked'),
|
|
||||||
},
|
|
||||||
'B',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
duration: 0,
|
|
||||||
showClose: true,
|
showClose: true,
|
||||||
|
duration: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AButton: typeof import('@ao/ao-design')['Button']
|
AButton: typeof import('@ao/ao-design')['Button']
|
||||||
AConfigProvider: typeof import('@ao/ao-design')['ConfigProvider']
|
AConfigProvider: typeof import('@ao/ao-design')['ConfigProvider']
|
||||||
|
ADrawer: typeof import('@ao/ao-design')['Drawer']
|
||||||
AGroup: typeof import('@ao/ao-design')['Group']
|
AGroup: typeof import('@ao/ao-design')['Group']
|
||||||
AIcon: typeof import('@ao/ao-design')['Icon']
|
AIcon: typeof import('@ao/ao-design')['Icon']
|
||||||
AInput: typeof import('@ao/ao-design')['Input']
|
AInput: typeof import('@ao/ao-design')['Input']
|
||||||
ALoading: typeof import('@ao/ao-design')['Loading']
|
ALoading: typeof import('@ao/ao-design')['Loading']
|
||||||
AMessage: typeof import('@ao/ao-design')['Message']
|
AMessage: typeof import('@ao/ao-design')['Message']
|
||||||
|
APopconfirm: typeof import('@ao/ao-design')['Popconfirm']
|
||||||
APopup: typeof import('@ao/ao-design')['Popup']
|
APopup: typeof import('@ao/ao-design')['Popup']
|
||||||
ASpin: typeof import('@ao/ao-design')['Spin']
|
ASpin: typeof import('@ao/ao-design')['Spin']
|
||||||
ATextarea: typeof import('@ao/ao-design')['Textarea']
|
ATextarea: typeof import('@ao/ao-design')['Textarea']
|
||||||
|
|||||||
Reference in new issue
Block a user