This commit is contained in:
oneao committed 2025-09-12 17:32:27 +08:00
1 parent 14f9844430
commit 785bce1907
27 files changed
+1051 -175

No files matched your search

+9 -1
View File
@@ -29,12 +29,20 @@
"export default defineComponent({", "export default defineComponent({",
" name: '${1:AoComponent}',", " name: '${1:AoComponent}',",
" props: {", " props: {",
" class: {",
" type: [String, Array, Object],",
" default: '',",
" },",
" style: {",
" type: Object,",
" default: () => ({}),",
" },",
" },", " },",
" emits: [],", " emits: [],",
" setup(props, { slots, emit }) {", " setup(props, { slots, emit }) {",
" const prefixCls = getPrefixCls('${2:component}');", " const prefixCls = getPrefixCls('${2:component}');",
" return () => (", " return () => (",
" <div class={prefixCls}>", " <div class={[prefixCls, props.class]} style={{ ...props.style }}>",
" {slots.default?.()}", " {slots.default?.()}",
" </div>", " </div>",
" );", " );",
@@ -13,30 +13,48 @@ $default-color-hover: var(--ao-default-color-hover); // hover态
$default-color-focus: var(--ao-default-color-focus); // focus态,包括鼠标和键盘 $default-color-focus: var(--ao-default-color-focus); // focus态,包括鼠标和键盘
$default-color-active: var(--ao-default-color-active); // 点击态 $default-color-active: var(--ao-default-color-active); // 点击态
$default-color-disabled: var(--ao-default-color-disabled); // 禁用态 $default-color-disabled: var(--ao-default-color-disabled); // 禁用态
$default-color-light: var(--ao-default-color-light);
$default-color-light-active: var(--ao-default-color-light-active);
// 基础颜色的扩展 用于 hover / 聚焦 / 禁用 / 点击 等状态 // 基础颜色的扩展 用于 hover / 聚焦 / 禁用 / 点击 等状态
$primary-color-hover: var(--ao-primary-color-hover); // hover态 $primary-color-hover: var(--ao-primary-color-hover); // hover态
$primary-color-focus: var(--ao-primary-color-focus); // focus态,包括鼠标和键盘 $primary-color-focus: var(--ao-primary-color-focus); // focus态,包括鼠标和键盘
$primary-color-active: var(--ao-primary-color-active); // 点击态 $primary-color-active: var(--ao-primary-color-active); // 点击态
$primary-color-disabled: var(--ao-primary-color-disabled); // 禁用态 $primary-color-disabled: var(--ao-primary-color-disabled); // 禁用态
$primary-color-light: var(--ao-primary-color-light);
$primary-color-light-active: var(--ao-primary-color-light-active);
// 警告色扩展 // 警告色扩展
$warning-color-hover: var(--ao-warning-color-hover); $warning-color-hover: var(--ao-warning-color-hover);
$warning-color-focus: var(--ao-warning-color-focus); $warning-color-focus: var(--ao-warning-color-focus);
$warning-color-active: var(--ao-warning-color-active); $warning-color-active: var(--ao-warning-color-active);
$warning-color-disabled: var(--ao-warning-color-disabled); $warning-color-disabled: var(--ao-warning-color-disabled);
$warning-color-light: var(--ao-warning-color-light);
$warning-color-light-active: var(--ao-warning-color-light-active);
// 失败/错误色扩展 // 失败/错误色扩展
$danger-color-hover: var(--ao-danger-color-hover); $danger-color-hover: var(--ao-danger-color-hover);
$danger-color-focus: var(--ao-danger-color-focus); $danger-color-focus: var(--ao-danger-color-focus);
$danger-color-active: var(--ao-danger-color-active); $danger-color-active: var(--ao-danger-color-active);
$danger-color-disabled: var(--ao-danger-color-disabled); $danger-color-disabled: var(--ao-danger-color-disabled);
$danger-color-light: var(--ao-danger-color-light);
$danger-color-light-active: var(--ao-danger-color-light-active);
// 成功色扩展 // 成功色扩展
$success-color-hover: var(--ao-success-color-hover); $success-color-hover: var(--ao-success-color-hover);
$success-color-focus: var(--ao-success-color-focus); $success-color-focus: var(--ao-success-color-focus);
$success-color-active: var(--ao-success-color-active); $success-color-active: var(--ao-success-color-active);
$success-color-disabled: var(--ao-success-color-disabled); $success-color-disabled: var(--ao-success-color-disabled);
$success-color-light: var(--ao-success-color-light);
$success-color-light-active: var(--ao-success-color-light-active);
// 状态背景色
$status-base-bg-color: var(--ao-status-base-bg-color);
$status-anti-bg-color: var(--ao-status-anti-bg-color);
$status-info-bg-color: var(--ao-status-info-bg-color);
$status-success-bg-color: var(--ao-status-success-bg-color);
$status-warning-bg-color: var(--ao-status-warning-bg-color);
$status-danger-bg-color: var(--ao-status-danger-bg-color);
// 文本颜色 // 文本颜色
$text-color-base: var(--ao-text-color-base); // 色彩-文字-主要 $text-color-base: var(--ao-text-color-base); // 色彩-文字-主要
@@ -9,26 +9,43 @@
--ao-default-color-focus: #505050; --ao-default-color-focus: #505050;
--ao-default-color-active: #5c5c5c; --ao-default-color-active: #5c5c5c;
--ao-default-color-disabled: #2c2c2c; --ao-default-color-disabled: #2c2c2c;
--ao-default-color-light: #f3f3f3;
--ao-default-color-light-active: #ddd;
--ao-primary-color-hover: #2667d4; --ao-primary-color-hover: #2667d4;
--ao-primary-color-focus: #173463; --ao-primary-color-focus: #173463;
--ao-primary-color-active: #699ef5; --ao-primary-color-active: #699ef5;
--ao-primary-color-disabled: #143975; --ao-primary-color-disabled: #143975;
--ao-primary-color-light: #1b2f51;
--ao-primary-color-light-active: #173463;
--ao-warning-color-hover: #a75d2b; --ao-warning-color-hover: #a75d2b;
--ao-warning-color-focus: #582f21; --ao-warning-color-focus: #582f21;
--ao-warning-color-active: #dc7633; --ao-warning-color-active: #dc7633;
--ao-warning-color-disabled: #733c23; --ao-warning-color-disabled: #733c23;
--ao-warning-color-light: #4f2a1d;
--ao-warning-color-light-active: #582f21;
--ao-danger-color-hover: #a03f46; --ao-danger-color-hover: #a03f46;
--ao-danger-color-focus: #5e2a2d; --ao-danger-color-focus: #5e2a2d;
--ao-danger-color-active: #de6670; --ao-danger-color-active: #de6670;
--ao-danger-color-disabled: #703439; --ao-danger-color-disabled: #703439;
--ao-danger-color-light: #472324;
--ao-danger-color-light-active: #5e2a2d;
--ao-success-color-hover: #0d7a55; --ao-success-color-hover: #0d7a55;
--ao-success-color-focus: #1a4230; --ao-success-color-focus: #1a4230;
--ao-success-color-active: #43af8a; --ao-success-color-active: #43af8a;
--ao-success-color-disabled: #17533d; --ao-success-color-disabled: #17533d;
--ao-success-color-light: #193a2a;
--ao-success-color-light-active: #1a4230;
--ao-status-base-bg-color: #242424;
--ao-status-anti-bg-color: #c6cacd; // 反色,用于tooltip默认背景色
--ao-status-info-bg-color: #233448;
--ao-status-success-bg-color: #253929;
--ao-status-warning-bg-color: #453522;
--ao-status-danger-bg-color: #442927;
--ao-text-color-base: rgba(255, 255, 255, 90%); /* 主要文字 */ --ao-text-color-base: rgba(255, 255, 255, 90%); /* 主要文字 */
--ao-text-color-secondary: rgba(255, 255, 255, 55%); /* 次要文字 / 辅助信息 */ --ao-text-color-secondary: rgba(255, 255, 255, 55%); /* 次要文字 / 辅助信息 */
@@ -60,10 +77,9 @@
// 基础/下层 投影 hover 使用的组件包括:表格 / // 基础/下层 投影 hover 使用的组件包括:表格 /
--ao-shadow-surface: 0 4px 6px rgba(0, 0, 0, 6%), 0 1px 10px rgba(0, 0, 0, 8%), 0 2px 4px rgba(0, 0, 0, 12%); --ao-shadow-surface: 0 4px 6px rgba(0, 0, 0, 6%), 0 1px 10px rgba(0, 0, 0, 8%), 0 2px 4px rgba(0, 0, 0, 12%);
// 中层投影 下拉 使用的组件包括:下拉菜单 / 气泡确认框 / 选择器 / // 中层投影 下拉 使用的组件包括:下拉菜单 / 气泡确认框 / 选择器 /
--ao-shadow-popup: 0 8px 10px rgba(0, 0, 0, 12%), 0 3px 14px rgba(0, 0, 0, 10%), 0 5px 5px rgba(0, 0, 0, 16%); --ao-shadow-popup: 0 0 4px rgba(0, 0, 0, 0.04), 0 0 8px rgba(0, 0, 0, 0.03);
// 上层投影(警示/弹窗)使用的组件包括:全局提示 / 消息通知 // 上层投影(警示/弹窗)使用的组件包括:全局提示 / 消息通知
--ao-shadow-overlay: 0 16px 24px rgba(0, 0, 0, 14%), 0 6px 30px rgba(0, 0, 0, 12%), 0 8px 10px rgba(0, 0, 0, 20%); --ao-shadow-overlay: inset 0 0 0 1px hsla(0, 0%, 100%, 0.1), 0 4px 14px rgba(0, 0, 0, 0.25);
// 内投影 // 内投影
--ao-shadow-inset-top: inset 0 0.5px 0 #5e5e5e; --ao-shadow-inset-top: inset 0 0.5px 0 #5e5e5e;
--ao-shadow-inset-right: inset 0.5px 0 0 #5e5e5e; --ao-shadow-inset-right: inset 0.5px 0 0 #5e5e5e;
@@ -10,26 +10,43 @@
--ao-default-color-focus: #cfcfcf; --ao-default-color-focus: #cfcfcf;
--ao-default-color-active: #ababab; --ao-default-color-active: #ababab;
--ao-default-color-disabled: #eeeeee; --ao-default-color-disabled: #eeeeee;
--ao-default-color-light: #f3f3f3;
--ao-default-color-light-active: #ddd;
--ao-primary-color-hover: #366ef4; --ao-primary-color-hover: #366ef4;
--ao-primary-color-focus: #d9e1ff; --ao-primary-color-focus: #d9e1ff;
--ao-primary-color-active: #003cab; --ao-primary-color-active: #003cab;
--ao-primary-color-disabled: #b5c7ff; --ao-primary-color-disabled: #b5c7ff;
--ao-primary-color-light: #f2f3ff;
--ao-primary-color-light-active: #d9e1ff;
--ao-warning-color-hover: #fa9550; --ao-warning-color-hover: #fa9550;
--ao-warning-color-focus: #ffd9c2; --ao-warning-color-focus: #ffd9c2;
--ao-warning-color-active: #be5a00; --ao-warning-color-active: #be5a00;
--ao-warning-color-disabled: #ffb98c; --ao-warning-color-disabled: #ffb98c;
--ao-warning-color-light: #fff1e9;
--ao-warning-color-light-active: #ffd9c2;
--ao-danger-color-hover: #f6685d; --ao-danger-color-hover: #f6685d;
--ao-danger-color-focus: #ffd8d2; --ao-danger-color-focus: #ffd8d2;
--ao-danger-color-active: #ad352f; --ao-danger-color-active: #ad352f;
--ao-danger-color-disabled: #ffb9b0; --ao-danger-color-disabled: #ffb9b0;
--ao-danger-color-light: #fff0ed;
--ao-danger-color-light-active: #ffd8d2;
--ao-success-color-hover: #56c08d; --ao-success-color-hover: #56c08d;
--ao-success-color-focus: #c6f3d7; --ao-success-color-focus: #c6f3d7;
--ao-success-color-active: #008858; --ao-success-color-active: #008858;
--ao-success-color-disabled: #92dab2; --ao-success-color-disabled: #92dab2;
--ao-success-color-light: #e3f9e9;
--ao-success-color-light-active: #c6f3d7;
--ao-status-base-bg-color: #fff;
--ao-status-anti-bg-color: #41464c; // 反色,用于tooltip默认背景色
--ao-status-info-bg-color: #f2f3ff;
--ao-status-success-bg-color: #ecf7ec;
--ao-status-warning-bg-color: #fff8ea;
--ao-status-danger-bg-color: #fef2ed;
--ao-text-color-base: rgba(0, 0, 0, 90%); /* 主要文字 */ --ao-text-color-base: rgba(0, 0, 0, 90%); /* 主要文字 */
--ao-text-color-secondary: rgba(0, 0, 0, 60%); /* 次要文字 / 辅助信息 */ --ao-text-color-secondary: rgba(0, 0, 0, 60%); /* 次要文字 / 辅助信息 */
@@ -61,13 +78,10 @@
// 基础/下层 投影 hover 使用的组件包括:表格 / // 基础/下层 投影 hover 使用的组件包括:表格 /
--ao-shadow-surface: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%); --ao-shadow-surface: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%);
// 中层投影 下拉 使用的组件包括:下拉菜单 / 气泡确认框 / 选择器 / // 中层投影 下拉 使用的组件包括:下拉菜单 / 气泡确认框 / 选择器 /
--ao-shadow-popup: --ao-shadow-popup: 0 0 4px rgba(0, 0, 0, 0.04), 0 0 8px rgba(0, 0, 0, 0.03);
0 3px 14px 2px rgba(0, 0, 0, 5%), 0 8px 10px 1px rgba(0, 0, 0, 6%), 0 5px 5px -3px rgba(0, 0, 0, 10%);
// 上层投影(警示/弹窗)使用的组件包括:全局提示 / 消息通知
--ao-shadow-overlay:
0 6px 30px 5px rgba(0, 0, 0, 5%), 0 16px 24px 2px rgba(0, 0, 0, 4%), 0 8px 10px -5px rgba(0, 0, 0, 8%);
// 内投影 // 上层投影(警示/弹窗)使用的组件包括:全局提示 / 消息通知
--ao-shadow-overlay: 0 0 1px rgba(0, 0, 0, 0.3), 0 4px 14px rgba(0, 0, 0, 0.1);
--ao-shadow-inset-top: inset 0 0.5px 0 #dcdcdc; --ao-shadow-inset-top: inset 0 0.5px 0 #dcdcdc;
--ao-shadow-inset-right: inset 0.5px 0 0 #dcdcdc; --ao-shadow-inset-right: inset 0.5px 0 0 #dcdcdc;
--ao-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc; --ao-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc;
@@ -7,4 +7,7 @@ export { default as Input } from './input'
export { default as Popconfirm } from './popconfirm' 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 Tag } from './tag'
export { default as TagGroup } from './tag-group'
export { default as Textarea } from './textarea' export { default as Textarea } from './textarea'
export { default as Tooltip } from './tooltip'
@@ -10,3 +10,6 @@
@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 *; @use './drawer/index.scss' as *;
@use './tooltip/index.scss' as *;
@use './tag/index.scss' as *;
@use './tag-group/index.scss' as *;
@@ -21,6 +21,8 @@ function showMessageType(type: any) {
message.success({ message.success({
message: `这里是 ${type} 提示`, message: `这里是 ${type} 提示`,
type, type,
duration: 0,
theme: 'light',
}) })
} }
</script> </script>
@@ -76,6 +78,9 @@ function showMessageType(type: any) {
</div> </div>
<div class="row"> <div class="row">
<AButton status="primary" @click="showMessageType('default')">
default
</AButton>
<AButton status="primary" @click="showMessageType('info')"> <AButton status="primary" @click="showMessageType('info')">
info info
</AButton> </AButton>
@@ -85,7 +90,7 @@ function showMessageType(type: any) {
<AButton status="primary" @click="showMessageType('warning')"> <AButton status="primary" @click="showMessageType('warning')">
warning warning
</AButton> </AButton>
<AButton status="primary" @click="showMessageType('error')"> <AButton status="primary" @click="showMessageType('danger')">
error error
</AButton> </AButton>
<AButton status="primary" @click="showMessageType('loading')"> <AButton status="primary" @click="showMessageType('loading')">
@@ -47,22 +47,34 @@ $message-prefix: #{$prefix}-message;
} }
&-info { &-info {
color: $primary-color; .#{$message-prefix}-icon {
color: $primary-color;
}
} }
&-success { &-success {
color: $success-color; .#{$message-prefix}-icon {
color: $success-color;
}
} }
&-warning { &-warning {
color: $warning-color; .#{$message-prefix}-icon {
color: $warning-color;
}
} }
&-error { &-danger {
color: $danger-color; .#{$message-prefix}-icon {
color: $danger-color;
}
} }
&-question { &-question {
color: $primary-color; .#{$message-prefix}-icon {
color: $primary-color;
}
} }
&-loading { &-loading {
color: $primary-color; .#{$message-prefix}-icon {
color: $primary-color;
}
} }
&-placement { &-placement {
&-top { &-top {
@@ -128,3 +140,27 @@ $message-prefix: #{$prefix}-message;
} }
} }
} }
.#{$message-prefix}-theme-normal.#{$message-prefix} {
background-color: $status-base-bg-color;
}
.#{$message-prefix}-theme-light.#{$message-prefix}-default {
background-color: $status-base-bg-color;
}
.#{$message-prefix}-theme-light.#{$message-prefix}-info {
background-color: $status-info-bg-color;
}
.#{$message-prefix}-theme-light.#{$message-prefix}-success {
background-color: $status-success-bg-color;
}
.#{$message-prefix}-theme-light.#{$message-prefix}-warning {
background-color: $status-warning-bg-color;
}
.#{$message-prefix}-theme-light.#{$message-prefix}-danger {
background-color: $status-danger-bg-color;
}
.#{$message-prefix}-theme-light.#{$message-prefix}-loading {
background-color: $status-info-bg-color;
}
.#{$message-prefix}-theme-light.#{$message-prefix}-question {
background-color: $status-info-bg-color;
}
@@ -14,11 +14,13 @@ type Placement
| 'bottom-end' | 'bottom-end'
| 'center' | 'center'
type MessageType = 'info' | 'success' | 'warning' | 'error' | 'loading' | 'question' type MessageType = 'default' | 'info' | 'success' | 'warning' | 'danger' | 'loading' | 'question'
type MessageTheme = 'normal' | 'light'
interface MessageOptions { interface MessageOptions {
message: string | VNode | (() => VNode) message: string | VNode | (() => VNode)
type?: MessageType type?: MessageType
theme?: MessageTheme
placement?: Placement placement?: Placement
duration?: number duration?: number
showClose?: boolean showClose?: boolean
@@ -30,10 +32,11 @@ interface MessageHandle {
interface MessageFn { interface MessageFn {
(msg: string | MessageOptions): MessageHandle (msg: string | MessageOptions): MessageHandle
open: (msg: string | MessageOptions) => MessageHandle
info: (msg: string | MessageOptions) => MessageHandle info: (msg: string | MessageOptions) => MessageHandle
success: (msg: string | MessageOptions) => MessageHandle success: (msg: string | MessageOptions) => MessageHandle
warning: (msg: string | MessageOptions) => MessageHandle warning: (msg: string | MessageOptions) => MessageHandle
error: (msg: string | MessageOptions) => MessageHandle danger: (msg: string | MessageOptions) => MessageHandle
closeAll: () => void closeAll: () => void
} }
@@ -68,13 +71,13 @@ function getTransform(placement: Placement, hidden: boolean) {
export function useMessage(): MessageFn { export function useMessage(): MessageFn {
const createMessage = ( const createMessage = (
msg: string | MessageOptions, msg: string | MessageOptions,
defaultType: MessageType = 'info', defaultType: MessageType = 'default',
): MessageHandle => { ): MessageHandle => {
const opts: MessageOptions = typeof msg === 'string' const opts: MessageOptions = typeof msg === 'string'
? { message: msg, type: defaultType } ? { message: msg, type: defaultType }
: { ...msg, type: msg.type || defaultType } : { ...msg, type: msg.type || defaultType }
const { message, type, placement = 'top', duration = 3000, showClose } = opts const { message, type, theme, placement = 'top', duration = 3000, showClose } = opts
const container = getContainer(placement) const container = getContainer(placement)
let timer: number | undefined let timer: number | undefined
let handle: MessageHandle let handle: MessageHandle
@@ -83,6 +86,7 @@ export function useMessage(): MessageFn {
render() { render() {
return h(MessageItem, { return h(MessageItem, {
type, type,
theme,
message, message,
placement, placement,
showClose, showClose,
@@ -140,10 +144,11 @@ export function useMessage(): MessageFn {
} }
const api = ((msg: string | MessageOptions) => createMessage(msg)) as MessageFn const api = ((msg: string | MessageOptions) => createMessage(msg)) as MessageFn
api.open = msg => createMessage(msg, 'default')
api.info = msg => createMessage(msg, 'info') api.info = msg => createMessage(msg, 'info')
api.success = msg => createMessage(msg, 'success') api.success = msg => createMessage(msg, 'success')
api.warning = msg => createMessage(msg, 'warning') api.warning = msg => createMessage(msg, 'warning')
api.error = msg => createMessage(msg, 'error') api.danger = msg => createMessage(msg, 'danger')
api.closeAll = () => { api.closeAll = () => {
instances.slice().forEach(instance => instance.close()) instances.slice().forEach(instance => instance.close())
} }
@@ -8,7 +8,8 @@ const prefixCls = getPrefixCls('message')
export default defineComponent({ export default defineComponent({
name: 'MessageItem', name: 'MessageItem',
props: { props: {
type: { type: String, default: 'info' }, type: { type: String, default: 'default' },
theme: { type: String, default: 'normal' },
message: { 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,
@@ -23,7 +24,7 @@ export default defineComponent({
setup(props) { setup(props) {
const iconMap: Record<string, string> = { const iconMap: Record<string, string> = {
success: 'ix:success-filled', success: 'ix:success-filled',
error: 'ix:error-filled', danger: 'ix:error-filled',
warning: 'ix:warning-filled', warning: 'ix:warning-filled',
info: 'ix:info-filled', info: 'ix:info-filled',
question: 'ix:question-filled', question: 'ix:question-filled',
@@ -36,14 +37,16 @@ export default defineComponent({
} }
return () => ( return () => (
<div <div
class={`${prefixCls}`} class={`${prefixCls} ${prefixCls}-theme-${props.theme ?? 'normal'} ${prefixCls}-${props.type ?? 'default'}`}
> >
<Icon {props.type !== 'default' && (
icon={iconMap[props.type ?? 'info']} <Icon
size={20} icon={iconMap[props.type ?? 'info']}
class={`${prefixCls}-icon ${prefixCls}-${props.type ?? 'info'}`} size={20}
spin={props.type === 'loading'} class={`${prefixCls}-icon`}
/> spin={props.type === 'loading'}
/>
)}
<span class={`${prefixCls}-message`}> <span class={`${prefixCls}-message`}>
{renderMessage(props.message)} {renderMessage(props.message)}
</span> </span>
@@ -1,59 +1,85 @@
<script setup lang="ts"> <script setup lang="ts">
import { useNotification } from '@ao/ao-design' import { useNotification } from '@ao/ao-design'
import { h } from 'vue'
const notification = useNotification() const notification = useNotification()
function showCustomMessage() { function showNotification(placement: any) {
notification.success({ notification.success({
title: '自定义消息', title: `这里是 ${placement} 标题`,
message: () => message: `这里是 ${placement} 提示`,
h('div', { style: { color: 'red', position: 'relative' } }, [ placement,
h('p', {}, '这里是自定义内容,可以是任意 VNode'), })
h('strong', {}, '加粗文本'), }
// 右下角按钮容器 function closeOne() {
h(
'div', }
{
style: { function showNotificationType(type: any) {
position: 'absolute', notification.success({
bottom: '4px', theme: 'light',
right: '4px', title: `这里是 ${type} 标题`,
display: 'flex', message: `这里是 ${type} 提示`,
gap: '4px', type,
}, duration: 0,
},
[
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> </script>
<template> <template>
<div class="demo"> <div class="demo">
<AButton status="primary" @click="showCustomMessage"> <div class="row">
自定义 h 函数消息 <AButton status="primary" @click="showNotification('top-start')">
</AButton> Top Start
</AButton>
<AButton status="primary" @click="showNotification('top-end')">
Top End
</AButton>
</div>
<div class="row">
<AButton status="primary" @click="showNotification('bottom-start')">
Bottom Start
</AButton>
<AButton status="primary" @click="showNotification('bottom-end')">
Bottom End
</AButton>
</div>
<div class="row">
<AButton status="primary" @click="notification.closeAll">
Close All
</AButton>
<AButton
status="primary" @click="closeOne"
>
Show No Close
</AButton>
</div>
<div class="row">
<AButton status="primary" @click="showNotificationType('default')">
default
</AButton>
<AButton status="primary" @click="showNotificationType('info')">
info
</AButton>
<AButton status="primary" @click="showNotificationType('success')">
success
</AButton>
<AButton status="primary" @click="showNotificationType('warning')">
warning
</AButton>
<AButton status="primary" @click="showNotificationType('danger')">
error
</AButton>
<AButton status="primary" @click="showNotificationType('loading')">
loading
</AButton>
<AButton status="primary" @click="showNotificationType('question')">
question
</AButton>
</div>
</div> </div>
</template> </template>
@@ -65,4 +91,9 @@ function showCustomMessage() {
gap: 16px; gap: 16px;
margin-top: 40px; margin-top: 40px;
} }
.row {
display: flex;
gap: 12px;
}
</style> </style>
@@ -24,7 +24,6 @@ $notification-prefix: #{$prefix}-notification;
border: 0; border: 0;
margin: 0; margin: 0;
display: flex; display: flex;
background-color: #ecf7ec;
&-icon { &-icon {
text-align: center; text-align: center;
@@ -47,8 +46,6 @@ $notification-prefix: #{$prefix}-notification;
&-title { &-title {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
line-height: 1;
small { small {
margin: 0px 6px; margin: 0px 6px;
font-size: 14px; font-size: 14px;
@@ -76,26 +73,35 @@ $notification-prefix: #{$prefix}-notification;
word-break: break-all; word-break: break-all;
} }
&-footer {
}
&-info { &-info {
color: $primary-color; .#{$notification-prefix}-icon {
color: $primary-color;
}
} }
&-success { &-success {
color: $success-color; .#{$notification-prefix}-icon {
color: $success-color;
}
} }
&-warning { &-warning {
color: $warning-color; .#{$notification-prefix}-icon {
color: $warning-color;
}
} }
&-error { &-danger {
color: $danger-color; .#{$notification-prefix}-icon {
color: $danger-color;
}
} }
&-question { &-question {
color: $primary-color; .#{$notification-prefix}-icon {
color: $primary-color;
}
} }
&-loading { &-loading {
color: $primary-color; .#{$notification-prefix}-icon {
color: $primary-color;
}
} }
&-placement { &-placement {
@@ -125,3 +131,27 @@ $notification-prefix: #{$prefix}-notification;
} }
} }
} }
.#{$notification-prefix}-theme-normal.#{$notification-prefix} {
background-color: $status-base-bg-color;
}
.#{$notification-prefix}-theme-light.#{$notification-prefix}-default {
background-color: $status-base-bg-color;
}
.#{$notification-prefix}-theme-light.#{$notification-prefix}-info {
background-color: $status-info-bg-color;
}
.#{$notification-prefix}-theme-light.#{$notification-prefix}-success {
background-color: $status-success-bg-color;
}
.#{$notification-prefix}-theme-light.#{$notification-prefix}-warning {
background-color: $status-warning-bg-color;
}
.#{$notification-prefix}-theme-light.#{$notification-prefix}-danger {
background-color: $status-danger-bg-color;
}
.#{$notification-prefix}-theme-light.#{$notification-prefix}-loading {
background-color: $status-info-bg-color;
}
.#{$notification-prefix}-theme-light.#{$notification-prefix}-question {
background-color: $status-info-bg-color;
}
@@ -9,13 +9,14 @@ type Placement
| 'bottom-start' | 'bottom-start'
| 'bottom-end' | 'bottom-end'
type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'loading' | 'question' type NotificationType = 'default' | 'info' | 'success' | 'warning' | 'danger' | 'loading' | 'question'
type NotificationTheme = 'normal' | 'light'
interface NotificationOptions { interface NotificationOptions {
message: string | VNode | (() => VNode) message: string | VNode | (() => VNode)
title: string title: string
subTitle?: string subTitle?: string
theme?: NotificationTheme
type?: NotificationType type?: NotificationType
placement?: Placement placement?: Placement
duration?: number duration?: number
@@ -28,10 +29,11 @@ interface NotificationHandle {
interface MessageFn { interface MessageFn {
(msg: NotificationOptions): NotificationHandle (msg: NotificationOptions): NotificationHandle
open: (msg: NotificationOptions) => NotificationHandle
info: (msg: NotificationOptions) => NotificationHandle info: (msg: NotificationOptions) => NotificationHandle
success: (msg: NotificationOptions) => NotificationHandle success: (msg: NotificationOptions) => NotificationHandle
warning: (msg: NotificationOptions) => NotificationHandle warning: (msg: NotificationOptions) => NotificationHandle
error: (msg: NotificationOptions) => NotificationHandle danger: (msg: NotificationOptions) => NotificationHandle
closeAll: () => void closeAll: () => void
} }
@@ -65,9 +67,9 @@ function getTransform(placement: Placement, hidden: boolean) {
export function useNotification(): MessageFn { export function useNotification(): MessageFn {
const createNotification = ( const createNotification = (
opts: NotificationOptions, opts: NotificationOptions,
defaultType: NotificationType = 'info', defaultType: NotificationType = 'default',
): NotificationHandle => { ): NotificationHandle => {
const { message, title, subTitle, type = defaultType, placement = 'top-end', duration = 3000, showClose } = opts const { message, title, subTitle, type = defaultType, theme, placement = 'top-end', duration = 3000, showClose } = opts
const container = getContainer(placement) const container = getContainer(placement)
let timer: number | undefined let timer: number | undefined
@@ -77,6 +79,7 @@ export function useNotification(): MessageFn {
render() { render() {
return h(MessageItem, { return h(MessageItem, {
type, type,
theme,
title, title,
subTitle, subTitle,
message, message,
@@ -136,10 +139,11 @@ export function useNotification(): MessageFn {
} }
const api = ((msg: NotificationOptions) => createNotification(msg)) as MessageFn const api = ((msg: NotificationOptions) => createNotification(msg)) as MessageFn
api.open = msg => createNotification(msg, 'default')
api.info = msg => createNotification(msg, 'info') api.info = msg => createNotification(msg, 'info')
api.success = msg => createNotification(msg, 'success') api.success = msg => createNotification(msg, 'success')
api.warning = msg => createNotification(msg, 'warning') api.warning = msg => createNotification(msg, 'warning')
api.error = msg => createNotification(msg, 'error') api.danger = msg => createNotification(msg, 'danger')
api.closeAll = () => { api.closeAll = () => {
instances.slice().forEach(instance => instance.close()) instances.slice().forEach(instance => instance.close())
} }
@@ -8,7 +8,8 @@ const prefixCls = getPrefixCls('notification')
export default defineComponent({ export default defineComponent({
name: 'NotificationItem', name: 'NotificationItem',
props: { props: {
type: { type: String, default: 'info' }, type: { type: String, default: 'default' },
theme: { type: String, default: 'normal' },
title: { type: String, required: true }, title: { type: String, required: true },
message: { message: {
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode), type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
@@ -25,7 +26,7 @@ export default defineComponent({
setup(props) { setup(props) {
const iconMap: Record<string, string> = { const iconMap: Record<string, string> = {
success: 'ix:success-filled', success: 'ix:success-filled',
error: 'ix:error-filled', danger: 'ix:error-filled',
warning: 'ix:warning-filled', warning: 'ix:warning-filled',
info: 'ix:info-filled', info: 'ix:info-filled',
question: 'ix:question-filled', question: 'ix:question-filled',
@@ -38,15 +39,16 @@ export default defineComponent({
} }
return () => ( return () => (
<div <div
class={`${prefixCls}`} class={`${prefixCls} ${prefixCls}-theme-${props.theme ?? 'normal'} ${prefixCls}-${props.type ?? 'info'}`}
> >
<Icon {props.type !== 'default' && (
icon={iconMap[props.type ?? 'info']} <Icon
size={22} icon={iconMap[props.type ?? 'info']}
class={`${prefixCls}-icon ${prefixCls}-${props.type ?? 'info'}`} size={22}
spin={props.type === 'loading'} class={`${prefixCls}-icon`}
/> spin={props.type === 'loading'}
/>
)}
<div class={`${prefixCls}-main`}> <div class={`${prefixCls}-main`}>
<div class={`${prefixCls}-title-wrapper`}> <div class={`${prefixCls}-title-wrapper`}>
<span class={`${prefixCls}-title`}> <span class={`${prefixCls}-title`}>
@@ -23,14 +23,16 @@ $popup-prefix: '#{$prefix}-popup';
content: ''; content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
background: $bg-color-container; background-color: $bg-color-container;
transform: rotate(45deg); transform: rotate(45deg);
transform-origin: center; transform-origin: center;
} }
} }
&-content { &-content {
padding: 4px 8px; padding: 4px 8px;
} }
// 上方弹出,箭头在底部 // 上方弹出,箭头在底部
&[data-popup-placement^='top'] > &-arrow { &[data-popup-placement^='top'] > &-arrow {
bottom: -4px; bottom: -4px;
@@ -53,11 +53,11 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
popupClass: { class: {
type: [String, Array, Object], type: [String, Array, Object],
default: '', default: '',
}, },
popupStyle: { style: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
@@ -65,6 +65,8 @@ export default defineComponent({
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode), type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
default: '', default: '',
}, },
showDelay: { type: Number, default: 0 },
hideDelay: { type: Number, default: 0 },
}, },
emits: ['update:visible'], emits: ['update:visible'],
setup(props, { slots, emit }) { setup(props, { slots, emit }) {
@@ -75,6 +77,10 @@ export default defineComponent({
const arrowRef = ref<HTMLElement | null>(null) // 箭头引用 const arrowRef = ref<HTMLElement | null>(null) // 箭头引用
const localVisible = ref(props.visible) const localVisible = ref(props.visible)
const hasRendered = ref(props.visible) const hasRendered = ref(props.visible)
// 定时器
let showTimer: number | null = null
let hideTimer: number | null = null
let cleanup: (() => void) | null = null let cleanup: (() => void) | null = null
const { floatingStyles, middlewareData, placement, update } = useFloating( const { floatingStyles, middlewareData, placement, update } = useFloating(
@@ -135,43 +141,65 @@ export default defineComponent({
floatEl.style.width = `${triggerEl.offsetWidth}px` floatEl.style.width = `${triggerEl.offsetWidth}px`
} }
const clearTimers = () => {
if (showTimer) {
clearTimeout(showTimer)
showTimer = null
}
if (hideTimer) {
clearTimeout(hideTimer)
hideTimer = null
}
}
const showPopup = async () => { const showPopup = async () => {
if (!hasRendered.value) clearTimers()
hasRendered.value = true showTimer = window.setTimeout(async () => {
await nextTick() if (!hasRendered.value)
hasRendered.value = true
await nextTick()
fitFloatingWidth() fitFloatingWidth()
localVisible.value = true localVisible.value = true
setupAutoUpdate()
setupAutoUpdate() emit('update:visible', true)
emit('update:visible', true) }, props.showDelay)
} }
const hidePopup = async () => { const hidePopup = async () => {
localVisible.value = false clearTimers()
hideTimer = window.setTimeout(() => {
clearAutoUpdate() localVisible.value = false
emit('update:visible', false) clearAutoUpdate()
emit('update:visible', false)
}, props.hideDelay)
} }
const togglePopup = async () => { const togglePopup = async () => {
clearTimers()
if (!hasRendered.value) if (!hasRendered.value)
hasRendered.value = true hasRendered.value = true
await nextTick() await nextTick()
fitFloatingWidth() fitFloatingWidth()
localVisible.value = !localVisible.value if (!localVisible.value) {
await nextTick() // 需要显示
showTimer = window.setTimeout(async () => {
if (localVisible.value) { localVisible.value = true
setupAutoUpdate() await nextTick()
setupAutoUpdate()
emit('update:visible', true)
}, props.showDelay)
} }
else { else {
clearAutoUpdate() // 需要隐藏
hideTimer = window.setTimeout(() => {
localVisible.value = false
clearAutoUpdate()
emit('update:visible', false)
}, props.hideDelay)
} }
emit('update:visible', localVisible.value)
} }
// 事件绑定 // 事件绑定
@@ -306,11 +334,11 @@ export default defineComponent({
<div <div
v-show={localVisible.value} v-show={localVisible.value}
ref={floatingRef} ref={floatingRef}
class={[prefixCls, props.popupClass]} class={[prefixCls, props.class]}
style={{ style={{
...floatingStyles.value, ...floatingStyles.value,
position: 'absolute', position: 'absolute',
...props.popupStyle, ...props.style,
}} }}
data-popup-placement={placement.value} data-popup-placement={placement.value}
> >
@@ -0,0 +1,12 @@
@use '../_styles/_variables.scss' as *;
@use 'sass:color';
$tag-prefix: '#{$prefix}-tag';
$tag-group-prefix: '#{$prefix}-tag-group';
.#{$tag-group-prefix} {
> .#{$tag-prefix}:not(:last-child) {
margin-right: 8px;
}
}
@@ -0,0 +1,73 @@
import type { PropType, VNode } from 'vue'
import { cloneVNode, defineComponent, ref } from 'vue'
import { getPrefixCls } from '../_utils/getPrefixCls'
import Tag from '../tag/index'
export default defineComponent({
name: 'TagGroup',
props: {
modelValue: { type: Array as PropType<(string | number)[]>, default: () => [] },
multiple: { type: Boolean, default: true },
options: {
type: Array as PropType<Array<{ label: string, value: string | number }>>,
default: () => [],
},
checkedProps: {
type: Object as PropType<{
type?: string
status?: string
}>,
default: () => ({
type: 'solid',
status: 'primary',
}),
},
uncheckedProps: {
type: Object as PropType<{
type?: string
status?: string
}>,
default: () => ({
type: 'solid',
status: 'default',
}),
},
class: {
type: [String, Array, Object],
default: '',
},
style: {
type: Object,
default: () => ({}),
},
},
emits: [],
setup(props, { slots }) {
const prefixCls = getPrefixCls('tag-group')
const selectedValues = ref([...props.modelValue])
const renderSlotTags = () => {
const slotNodes = slots.default?.() || []
return slotNodes.map((vnode: VNode) => {
// 判断是否是 Tag 组件
if (vnode.type === Tag) {
const value = (vnode.props as any)?.value
const checked = selectedValues.value.includes(value)
return cloneVNode(vnode, {
checked,
'type': checked ? props.checkedProps.type : props.uncheckedProps.type,
'status': checked ? props.checkedProps.status : props.uncheckedProps.status,
'onUpdate:checked': (val: boolean) => handleTagClick(value, val),
})
}
return vnode
})
}
return () => (
<div class={[prefixCls, props.class]} style={{ ...props.style }}>
{slots.default?.()}
</div>
)
},
})
@@ -0,0 +1,121 @@
<script setup lang="ts">
</script>
<template>
<div class="demo">
<div class="row">
<ATag size="small">
small
</ATag>
<ATag size="medium">
medium
</ATag>
<ATag size="large">
large
</ATag>
</div>
<div class="row">
<ATag shape="round">
round
</ATag>
<ATag shape="mark">
mark
</ATag>
<ATag shape="square">
square
</ATag>
</div>
<div class="row">
<ATag type="solid">
solid
</ATag>
<ATag type="light">
light
</ATag>
<ATag type="outline">
outline
</ATag>
<ATag type="outline-light">
outline-light
</ATag>
</div>
<div class="row">
<ATag type="solid" status="primary">
solid
</ATag>
<ATag type="light" status="primary">
light
</ATag>
<ATag type="outline" status="primary">
outline
</ATag>
<ATag type="outline-light" status="primary">
outline-light
</ATag>
</div>
<div class="row">
<ATag type="solid" status="danger">
solid
</ATag>
<ATag type="light" status="danger">
light
</ATag>
<ATag type="outline" status="danger">
outline
</ATag>
<ATag type="outline-light" status="danger">
outline-light
</ATag>
</div>
<div class="row">
<ATag type="solid" status="warning">
solid
</ATag>
<ATag type="light" status="warning">
light
</ATag>
<ATag type="outline" status="warning">
outline
</ATag>
<ATag type="outline-light" status="warning">
outline-light
</ATag>
</div>
<div class="row">
<ATag type="solid" status="success">
solid
</ATag>
<ATag type="light" status="success">
light
</ATag>
<ATag type="outline" status="success">
outline
</ATag>
<ATag type="outline-light" status="success">
outline-light
</ATag>
</div>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
margin-top: 40px;
}
.row {
display: flex;
gap: 12px;
}
</style>
@@ -0,0 +1,157 @@
@use '../_styles/_variables.scss' as *;
@use 'sass:color';
$tag-prefix: '#{$prefix}-tag';
.#{$tag-prefix} {
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
display: inline-flex;
align-items: center;
flex-direction: row;
border: 1px solid transparent;
white-space: nowrap;
border-radius: $border-color;
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
&-size-large {
padding: 0 8px;
height: 24px;
font-size: 13px;
}
&-size-medium {
padding: 0 6px;
height: 22px;
font-size: 12px;
}
&-size-small {
padding: 0 4px;
height: 20px;
font-size: 12px;
}
&-shape-round {
border-radius: 12px;
}
&-shape-mark {
border-radius: 0px 12px 12px 0;
}
&-icon {
margin-right: 4px;
}
&-close {
margin-left: 4px;
cursor: pointer;
transition: filter 0.2s cubic-bezier(0.38, 0, 0.24, 1);
color: currentColor;
filter: brightness(1.2); /* 比父色亮 20% */
&:hover {
filter: brightness(0.8); /* 比父色暗 20% */
}
}
&-type-solid {
&.#{$tag-prefix}-status-default {
color: $text-color-base;
background-color: $default-color;
}
&.#{$tag-prefix}-status-primary {
color: $text-color-anti;
background-color: $primary-color;
}
&.#{$tag-prefix}-status-success {
color: $text-color-anti;
background-color: $success-color;
}
&.#{$tag-prefix}-status-warning {
color: $text-color-anti;
background-color: $warning-color;
}
&.#{$tag-prefix}-status-danger {
color: $text-color-anti;
background-color: $danger-color;
}
}
&-type-light {
&.#{$tag-prefix}-status-default {
color: $text-color-base;
background-color: $default-color-light;
}
&.#{$tag-prefix}-status-primary {
color: $primary-color;
background-color: $primary-color-light;
}
&.#{$tag-prefix}-status-success {
color: $success-color;
background-color: $success-color-light;
}
&.#{$tag-prefix}-status-warning {
color: $warning-color;
background-color: $warning-color-light;
}
&.#{$tag-prefix}-status-danger {
color: $danger-color;
background-color: $danger-color-light;
}
}
&-type-outline {
&.#{$tag-prefix}-status-default {
color: $text-color-base;
background-color: transparent;
border-color: $default-color;
}
&.#{$tag-prefix}-status-primary {
color: $primary-color;
background-color: transparent;
border-color: $primary-color;
}
&.#{$tag-prefix}-status-success {
color: $success-color;
background-color: transparent;
border-color: $success-color;
}
&.#{$tag-prefix}-status-warning {
color: $warning-color;
background-color: transparent;
border-color: $warning-color;
}
&.#{$tag-prefix}-status-danger {
color: $danger-color;
background-color: transparent;
border-color: $danger-color;
}
}
&-type-outline-light {
&.#{$tag-prefix}-status-default {
color: $text-color-base;
background-color: $default-color-light;
border-color: $default-color;
}
&.#{$tag-prefix}-status-primary {
color: $primary-color;
background-color: $primary-color-light;
border-color: $primary-color;
}
&.#{$tag-prefix}-status-success {
color: $success-color;
background-color: $success-color-light;
border-color: $success-color;
}
&.#{$tag-prefix}-status-warning {
color: $warning-color;
background-color: $warning-color-light;
border-color: $warning-color;
}
&.#{$tag-prefix}-status-danger {
color: $danger-color;
background-color: $danger-color-light;
border-color: $danger-color;
}
}
}
@@ -0,0 +1,83 @@
import type { PropType } from 'vue'
import { defineComponent, ref } from 'vue'
import { getPrefixCls } from '../_utils/getPrefixCls'
import Icon from '../icon/index'
export default defineComponent({
name: 'Tag',
props: {
shape: {
type: String as PropType<'square' | 'round' | 'mark'>,
default: 'square',
},
size: {
type: String as PropType<'small' | 'medium' | 'large'>,
default: 'medium',
},
type: {
type: String as () => 'solid' | 'light' | 'outline' | 'outline-light',
default: 'solid',
},
status: {
type: String as () => 'primary' | 'danger' | 'warning' | 'success' | 'default',
default: 'default',
},
showClose: {
type: Boolean,
default: false,
},
icon: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
class: {
type: [String, Array, Object],
default: '',
},
style: {
type: Object,
default: () => ({}),
},
},
emits: ['click', 'close'],
setup(props, { slots, emit }) {
const prefixCls = getPrefixCls('tag')
const visible = ref(true)
const handleClick = (event: MouseEvent) => {
emit('click', event)
}
const handleClose = (event: MouseEvent) => {
event.stopPropagation()
visible.value = false // 点击关闭隐藏 Tag
emit('close', event)
}
return () =>
visible.value && (
<div
class={[
prefixCls,
`${prefixCls}-size-${props.size}`,
`${prefixCls}-shape-${props.shape}`,
`${prefixCls}-type-${props.type}`,
`${prefixCls}-status-${props.status}`,
props.class,
]}
style={{ ...props.style }}
onClick={handleClick}
>
{props.icon && <Icon icon={props.icon} class={`${prefixCls}-icon`} />}
<span>{slots.default?.()}</span>
{!props.disabled && props.showClose && (
<Icon icon="ix:close" class={`${prefixCls}-close`} onClick={handleClose} />
)}
</div>
)
},
})
@@ -0,0 +1,55 @@
<script setup lang="ts">
</script>
<template>
<div class="demo">
<div class="row">
<ATooltip trigger="click" type="default" content="defaultdefaultdefaultdefaultdefaultdefaultdefaultdefaultdefaultdefaultdefaultdefaultdefaultdefaultdefault">
<AButton status="primary">
default
</AButton>
</ATooltip>
<ATooltip trigger="click" type="anti" content="anti">
<AButton status="primary">
anti
</AButton>
</ATooltip>
<ATooltip trigger="click" type="info" content="info">
<AButton status="primary">
info
</AButton>
</ATooltip>
<ATooltip trigger="click" type="success" content="success">
<AButton status="primary">
success
</AButton>
</ATooltip>
<ATooltip trigger="click" type="danger" content="danger">
<AButton status="primary">
danger
</AButton>
</ATooltip>
<ATooltip trigger="click" type="warning" content="warning">
<AButton status="primary">
warning
</AButton>
</ATooltip>
</div>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
margin-top: 40px;
}
.row {
display: flex;
gap: 12px;
}
</style>
@@ -0,0 +1,88 @@
@use '../_styles/_variables.scss' as *;
@use 'sass:color';
$popup-prefix: '#{$prefix}-popup';
$tooltip-prefix: '#{$prefix}-tooltip';
.#{$tooltip-prefix} {
margin: 0;
padding: 0;
color: $text-color-base;
&-wrapper {
background-color: $status-base-bg-color;
padding: 4px 8px;
word-break: break-all;
}
.#{$popup-prefix}-arrow::before {
background-color: $status-base-bg-color;
}
&-default {
.#{$tooltip-prefix}-wrapper {
background-color: $status-base-bg-color;
color: $text-color-base;
}
.#{$popup-prefix}-arrow::before {
background-color: $status-base-bg-color;
}
}
&-anti {
.#{$tooltip-prefix}-wrapper {
background-color: $status-anti-bg-color;
color: $text-color-anti;
}
.#{$popup-prefix}-arrow::before {
background-color: $status-anti-bg-color;
}
}
&-info {
.#{$tooltip-prefix}-wrapper {
background-color: $status-info-bg-color;
color: $text-color-base;
}
.#{$popup-prefix}-arrow::before {
background-color: $status-info-bg-color;
}
}
&-success {
.#{$tooltip-prefix}-wrapper {
background-color: $status-success-bg-color;
color: $text-color-base;
}
.#{$popup-prefix}-arrow::before {
background-color: $status-success-bg-color;
}
}
&-danger {
.#{$tooltip-prefix}-wrapper {
background-color: $status-danger-bg-color;
color: $text-color-base;
}
.#{$popup-prefix}-arrow::before {
background-color: $status-danger-bg-color;
}
}
&-warning {
.#{$tooltip-prefix}-wrapper {
background-color: $status-warning-bg-color;
color: $text-color-base;
}
.#{$popup-prefix}-arrow::before {
background-color: $status-warning-bg-color;
}
}
&-anti.#{$tooltip-prefix},
&-info.#{$tooltip-prefix},
&-success.#{$tooltip-prefix},
&-danger.#{$tooltip-prefix},
&-warning.#{$tooltip-prefix} {
box-shadow: none;
.#{$popup-prefix}-arrow::before {
box-shadow: none;
}
}
}
@@ -0,0 +1,108 @@
import type { PropType, VNode } from 'vue'
import { defineComponent } from 'vue'
import { getPrefixCls } from '../_utils/getPrefixCls'
import Popup from '../popup/index'
export default defineComponent({
name: 'Tooltip',
props: {
type: {
type: String as PropType<'default' | 'anti' | 'info' | 'success' | 'danger' | 'warning'>,
default: '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',
},
trigger: {
type: [String, Array] as PropType<
'hover' | 'click' | 'focus' | 'contextMenu' | Array<'hover' | 'click' | 'focus' | 'contextMenu'>
>,
default: 'hover',
},
showArrow: {
type: Boolean,
default: true,
},
autoFitPopupMinWidth: {
type: Boolean,
default: false,
},
autoFitPopupWidth: {
type: Boolean,
default: false,
},
destroyOnClose: {
type: Boolean,
default: false,
},
content: {
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
default: '',
},
showDelay: { type: Number, default: 0 },
hideDelay: { type: Number, default: 0 },
class: {
type: [String, Array, Object],
default: '',
},
style: {
type: Object,
default: () => ({}),
},
maxWidth: {
type: [Number, String] as PropType<number | string>,
default: '200px',
},
},
emits: [],
setup(props, { slots }) {
const prefixCls = getPrefixCls('tooltip')
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
}
const renderTooltip = () => {
return (
<div
class={[`${prefixCls}-wrapper`, props.class]}
style={{
maxWidth: props.maxWidth,
...props.style,
}}
>
{slots.content ? slots.content() : renderContent(props.content)}
</div>
)
}
return () => (
<Popup
trigger={props.trigger}
placement={props.placement}
showArrow={props.showArrow}
autoFitPopupMinWidth={props.autoFitPopupMinWidth}
autoFitPopupWidth={props.autoFitPopupWidth}
destroyOnClose={props.destroyOnClose}
content={renderTooltip}
showDelay={props.showDelay}
hideDelay={props.hideDelay}
class={`${prefixCls} ${prefixCls}-${props.type}`}
>
{{
default: () => slots.default?.(),
}}
</Popup>
)
},
})
+11 -31
View File
@@ -1,40 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { useNotification } from '@ao/ao-design'
import { h } from 'vue'
const notification = useNotification()
function showCustomMessage() {
notification.success({
title: '测试',
subTitle: '测试2222222222222222222222222222222222222222222222222',
message: () =>
h('div', { style: { color: 'red', position: 'relative' } }, [
h(
'div',
{
style: {
position: 'absolute',
bottom: '4px',
right: '4px',
display: 'flex',
gap: '4px',
},
},
),
]),
type: 'success',
showClose: true,
duration: 0,
})
}
</script> </script>
<template> <template>
<div class="demo"> <div class="demo">
<AButton status="primary" @click="showCustomMessage"> <div class="row">
自定义 h 函数消息 <ATagGroup>
</AButton> <ATag show-close>测试1</ATag>
<ATag show-close>测试2</ATag>
</ATagGroup>
</div>
</div> </div>
</template> </template>
@@ -46,4 +21,9 @@ function showCustomMessage() {
gap: 16px; gap: 16px;
margin-top: 40px; margin-top: 40px;
} }
.row {
display: flex;
gap: 12px;
}
</style> </style>
+3
View File
@@ -19,6 +19,9 @@ declare module 'vue' {
APopconfirm: typeof import('@ao/ao-design')['Popconfirm'] 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']
ATag: typeof import('@ao/ao-design')['Tag']
ATagGroup: typeof import('@ao/ao-design')['TagGroup']
ATextarea: typeof import('@ao/ao-design')['Textarea'] ATextarea: typeof import('@ao/ao-design')['Textarea']
ATooltip: typeof import('@ao/ao-design')['Tooltip']
} }
} }
+1 -13
View File
@@ -187,19 +187,7 @@
</style> </style>
</head> </head>
<body> <body>
<!-- 触发元素示例 --> <div style="text-align: center;"><table border="1"><tbody><tr><td colspan="4">Shipper: CHANNEL INTERNATIONAL FREIGHT CO.,LTD 24 FLOOR, YINYI GLOBAL CENTER,B BLOCK,NO.42 YAOHANG STREET HAISHUDISTRICT,NINGBO,CHINA TEL:0086-574-87337359 FAX:0086-574-87135310</td><td rowspan="3" colspan="4">TO:宁波坤洲国际物流有限公司 ATTN: TEL: FROM:宁波驰诺/裘珊珊 TEL:0574-87237893 QQ:1597608137 业务编号:CNNBYS230201 开航日期:2023-02-0400:00:00.0 船公司:现代商船 柜量:2X2OTKFCL运费条款:FREIGHTPREPAID 运输条款:CY-CY</td></tr><tr><td colspan="4">Consignee: NSINTERNATIONAL(THAILAND)CO.LTD 75/49 RICHMOND BLDG.,15 FL SUKHUMVIT SOI 26 KLONGTOEY,BANGKOK1011O THAILAND ATTN:IMPORT@NSINTER.COM TEL: +66-2-665-6710 FAX:+66-2-665-6494</td></tr><tr><td colspan="4">Notify Party: NS INTERNATIONAL (THAILAND) CO.,LTD 75/49 RICHMOND BLDG., 15 FL SUKHUMVIT SOI 26 KLONGTOEY,BANGKOK1011O THAILAND ATTN:IMPORT@NSINTER.COM TEL:+66-2-665-6710 FAX: +66-2-665-6494</td></tr><tr><td colspan="2">起运港(PortOf Loading):</td><td colspan="3">中转港(Port of Dischargc):</td><td colspan="3">目的港(Final Destination):</td></tr><tr><td colspan="2">NINGBO,CHINA</td><td colspan="3"></td><td colspan="3">LEAM CHA BANG</td></tr><tr><td>标志和号码</td><td colspan="2">件数和包装种类</td><td colspan="3">货名</td><td>毛重(公斤)</td><td>体积(立方米)</td></tr><tr><td>SMAU9992165 SMAU9992170</td><td colspan="2">2TANKS</td><td colspan="3">BULK LIQUID ARGON CLASS:2.2 UN:1951</td><td>44000.000KGS</td><td>50.000CBM</td></tr></tbody></table></div>
<button class="trigger" data-tooltip="顶部提示" data-direction="top">顶部</button>
<button class="trigger" data-tooltip="顶部左侧提示" data-direction="top-left">顶部左</button>
<button class="trigger" data-tooltip="顶部右侧提示" data-direction="top-right">顶部右</button>
<button class="trigger" data-tooltip="底部提示" data-direction="bottom">底部</button>
<button class="trigger" data-tooltip="底部左侧提示" data-direction="bottom-left">底部左</button>
<button class="trigger" data-tooltip="底部右侧提示" data-direction="bottom-right">底部右</button>
<button class="trigger" data-tooltip="左侧提示" data-direction="left">左侧</button>
<button class="trigger" data-tooltip="左侧顶部提示" data-direction="left-top">左侧顶</button>
<button class="trigger" data-tooltip="左侧底部提示" data-direction="left-bottom">左侧底</button>
<button class="trigger" data-tooltip="右侧提示" data-direction="right">右侧</button>
<button class="trigger" data-tooltip="右侧顶部提示" data-direction="right-top">右侧顶</button>
<button class="trigger" data-tooltip="右侧底部提示" data-direction="right-bottom">右侧底</button>
<script> <script>
class TooltipManager { class TooltipManager {