Skip to content

Commit 3d611bb

Browse files
authored
refactor: toast (#241)
* refactor(badge): 位置和偏移量参数重命名 * refactor(Toast): 重构Toast组件,重命名为Notification
1 parent b0916dd commit 3d611bb

22 files changed

+625
-1598
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { App } from 'vue';
2+
import Notification from './src/notification';
3+
import NotificationService from './src/notification-service';
4+
export * from './src/notification-types';
5+
6+
export { Notification, NotificationService };
7+
8+
export default {
9+
title: 'Notification 全局通知',
10+
category: '反馈',
11+
status: '100%',
12+
install(app: App): void {
13+
app.component(Notification.name, Notification);
14+
app.config.globalProperties.$notificationService = NotificationService;
15+
},
16+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineComponent } from 'vue';
2+
import { Icon } from '../../icon';
3+
4+
export default defineComponent({
5+
emits: ['click'],
6+
setup(props, { emit }) {
7+
return () => (
8+
<div class='devui-notification-icon-close' onClick={(e) => emit('click', e)}>
9+
<Icon name='close' size='14px' />
10+
</div>
11+
);
12+
},
13+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { computed, defineComponent, toRefs } from 'vue';
2+
import type { PropType } from 'vue';
3+
import { NotificationType } from './notification-types';
4+
import { Icon } from '../../icon';
5+
6+
export default defineComponent({
7+
props: {
8+
type: {
9+
type: String as PropType<NotificationType>,
10+
default: 'normal',
11+
},
12+
},
13+
setup(props) {
14+
const { type } = toRefs(props);
15+
const classes = computed(() => ({
16+
'devui-notification-image': true,
17+
[`devui-notification-image-${type.value}`]: true,
18+
}));
19+
const severityIconMap = {
20+
info: 'info-o',
21+
success: 'right-o',
22+
warning: 'warning-o',
23+
error: 'error-o',
24+
};
25+
26+
return () => <span class={classes.value}>{type.value !== 'normal' && <Icon name={severityIconMap[type.value]} size='16px' />}</span>;
27+
},
28+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { reactive, createApp, onUnmounted } from 'vue';
2+
import type { App } from 'vue';
3+
import { NotificationOption, VoidFn } from './notification-types';
4+
import Notification from './notification';
5+
6+
const defaultOptions: NotificationOption = {
7+
modelValue: false,
8+
duration: 3000,
9+
type: 'normal',
10+
};
11+
12+
function initInstance(props: NotificationOption, content: string): App {
13+
const container = document.createElement('div');
14+
const app: App = createApp({
15+
setup() {
16+
onUnmounted(() => {
17+
document.body.removeChild(container);
18+
});
19+
20+
return () => (
21+
<Notification {...props} onDestroy={app.unmount}>
22+
{content}
23+
</Notification>
24+
);
25+
},
26+
});
27+
document.body.appendChild(container);
28+
app.mount(container);
29+
return app;
30+
}
31+
32+
function close(props: NotificationOption, originOnClose: VoidFn | null): void {
33+
props.modelValue = false;
34+
originOnClose?.();
35+
}
36+
37+
export default class NotificationService {
38+
static open(options: NotificationOption): void {
39+
const originOnClose: VoidFn | null = options.onClose || null;
40+
const content = options.content;
41+
let timer;
42+
delete options.content;
43+
44+
const props: NotificationOption = reactive({
45+
...defaultOptions,
46+
...options,
47+
onClose: () => {
48+
close(props, originOnClose);
49+
},
50+
});
51+
52+
initInstance(props, content);
53+
props.modelValue = true;
54+
55+
clearTimeout(timer);
56+
if (options.duration) {
57+
timer = setTimeout(props.onClose, options.duration);
58+
}
59+
}
60+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { ExtractPropTypes, PropType, h } from 'vue';
2+
3+
export type NotificationType = 'normal' | 'success' | 'error' | 'warning' | 'info';
4+
5+
export interface Message {
6+
type?: NotificationType;
7+
title?: string;
8+
content?: string | ((message: Message) => ReturnType<typeof h>);
9+
duration?: number;
10+
}
11+
12+
export const notificationProps = {
13+
modelValue: {
14+
type: Boolean,
15+
default: false,
16+
},
17+
title: {
18+
type: String,
19+
default: '',
20+
},
21+
type: {
22+
type: String as PropType<NotificationType>,
23+
default: 'normal',
24+
},
25+
duration: {
26+
type: Number,
27+
default: 3000,
28+
},
29+
onClose: {
30+
type: Function as PropType<() => void>,
31+
},
32+
};
33+
34+
export type EmitEventFn = (event: 'update:modelValue' | 'destroy', result?: unknown) => void;
35+
36+
export type VoidFn = () => void;
37+
38+
export type NotificationProps = ExtractPropTypes<typeof notificationProps>;
39+
40+
export type NotificationOption = Partial<NotificationProps> & { content?: string };
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
@import '../../style/mixins/index';
2-
@import '../../style/theme/color';
3-
@import '../../style/theme/shadow';
4-
@import '../../style/theme/corner';
5-
@import '../../style/core/_font';
6-
@import '../../style/core/animation';
7-
8-
.devui-toast {
1+
@import '../../styles-var/devui-var.scss';
2+
3+
.devui-notification {
94
position: fixed;
105
top: 50px;
116
right: 20px;
127
width: 20em;
138
word-break: normal;
149
word-wrap: break-word;
10+
z-index: 1060;
1511

1612
a {
1713
&:link,
@@ -26,35 +22,24 @@
2622
}
2723
}
2824

29-
.devui-toast-item-container {
25+
.devui-notification-item-container {
3026
position: relative;
31-
transform: translateX(100%);
32-
margin: 0 0 10px 0;
27+
margin: 0 0 8px 0;
3328
opacity: 0.95;
3429
filter: alpha(opacity=95);
3530
box-shadow: $devui-shadow-length-feedback-overlay $devui-shadow;
3631
border-radius: $devui-border-radius-feedback;
3732
color: $devui-feedback-overlay-text;
38-
transition: all $devui-animation-duration-slow $devui-animation-ease-in-out;
3933
background-color: $devui-feedback-overlay-bg;
40-
41-
&.slide-in {
42-
transform: translateX(0);
43-
}
4434
}
4535

46-
.devui-toast-item {
36+
.devui-notification-item {
4737
position: relative;
4838
display: block;
4939
padding: 12px 16px;
5040
}
5141

52-
.devui-toast-item p {
53-
padding: 0;
54-
margin: 0;
55-
}
56-
57-
.devui-toast-icon-close {
42+
.devui-notification-icon-close {
5843
position: absolute;
5944
top: 7px;
6045
right: 10px;
@@ -65,14 +50,14 @@
6550
}
6651
}
6752

68-
.devui-toast-title {
53+
.devui-notification-title {
6954
font-size: $devui-font-size-card-title;
7055
padding: 0 0 calc(0.5em - 2px) 0;
7156
display: block;
7257
font-weight: 700;
7358
}
7459

75-
.devui-toast-image {
60+
.devui-notification-image {
7661
position: absolute;
7762
display: inline-block;
7863
width: 16px;
@@ -83,46 +68,55 @@
8368
padding: 0;
8469
line-height: 1;
8570

86-
&.devui-toast-image-warn i.icon {
71+
&.devui-notification-image-warn i.icon {
8772
color: $devui-warning !important;
8873
}
8974

90-
&.devui-toast-image-info i.icon {
75+
&.devui-notification-image-info i.icon {
9176
color: $devui-info !important;
9277
}
9378

94-
&.devui-toast-image-error i.icon {
79+
&.devui-notification-image-error i.icon {
9580
color: $devui-danger !important;
9681
}
9782

98-
&.devui-toast-image-success i.icon {
83+
&.devui-notification-image-success i.icon {
9984
color: $devui-success !important;
10085
}
10186

102-
.devui-toast-image-info-path,
103-
.devui-toast-image-error-path,
104-
.devui-toast-image-success-path {
87+
.devui-notification-image-info-path,
88+
.devui-notification-image-error-path,
89+
.devui-notification-image-success-path {
10590
fill: $devui-light-text;
10691
}
10792
}
10893

109-
.devui-toast-message {
94+
.devui-notification-message {
11095
margin-left: 20px;
11196

112-
p {
113-
padding: 0 8px 0 4px;
114-
}
115-
116-
span.devui-toast-title + p {
117-
padding: 0;
97+
.devui-notification-content {
98+
font-size: $devui-font-size;
99+
margin-top: 4px;
118100
}
119101
}
120102

121-
.devui-toast-message-common .devui-toast-message {
103+
.devui-notification-message-common .devui-notification-message {
122104
margin-left: 0;
123105
}
124106

125-
.devui-toast-message p {
107+
.devui-notification-message p {
126108
font-size: $devui-font-size;
127-
margin-top: 2px;
109+
margin-top: 4px;
110+
}
111+
112+
.notification-fade {
113+
&-enter-active,
114+
&-leave-active {
115+
transition: transform $devui-animation-duration-slow $devui-animation-ease-in-out;
116+
}
117+
118+
&-enter-from,
119+
&-leave-to {
120+
transform: translateX(100%);
121+
}
128122
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { defineComponent, toRefs, Transition } from 'vue';
2+
import { notificationProps, NotificationProps } from './notification-types';
3+
import Close from './notification-icon-close';
4+
import TypeIcon from './notification-image';
5+
import { useNotification, useEvent } from './use-notification';
6+
import './notification.scss';
7+
8+
export default defineComponent({
9+
name: 'DNotification',
10+
props: notificationProps,
11+
emits: ['update:modelValue', 'destroy'],
12+
setup(props: NotificationProps, { emit, slots }) {
13+
const { modelValue, title, type } = toRefs(props);
14+
const { classes } = useNotification(props);
15+
const { interrupt, removeReset, close, handleDestroy } = useEvent(props, emit);
16+
17+
return () => (
18+
<Transition name='notification-fade' onAfterLeave={handleDestroy}>
19+
{modelValue.value && (
20+
<div class='devui-notification'>
21+
<div class={classes.value} onMouseenter={interrupt} onMouseleave={removeReset}>
22+
<div class='devui-notification-item'>
23+
<Close onClick={close} />
24+
{title.value && <TypeIcon type={type.value} />}
25+
<div class='devui-notification-message'>
26+
<span class='devui-notification-title'>{title.value}</span>
27+
<span class='devui-notification-content'>{slots.default?.()}</span>
28+
</div>
29+
</div>
30+
</div>
31+
</div>
32+
)}
33+
</Transition>
34+
);
35+
},
36+
});

0 commit comments

Comments
 (0)