diff --git a/packages/opendesign/src/components/index.ts b/packages/opendesign/src/components/index.ts
index 049dff26ae80455ea23ef88c425f401088699bf1..a7c36024104e5d9eeb0d423cdb0d3326a50c7e88 100644
--- a/packages/opendesign/src/components/index.ts
+++ b/packages/opendesign/src/components/index.ts
@@ -33,6 +33,7 @@ export * from './dropdown';
export * from './dialog';
export * from './figure';
export * from './card';
+export * from './message';
export * from './intersection-observer';
export * from './resize-observer';
diff --git a/packages/opendesign/src/components/message/OMessage.vue b/packages/opendesign/src/components/message/OMessage.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b55cef66ebf9b6d4e35ae7a83332429c3a729ca8
--- /dev/null
+++ b/packages/opendesign/src/components/message/OMessage.vue
@@ -0,0 +1,42 @@
+
+
+
+ {{ props.content }}
+
diff --git a/packages/opendesign/src/components/message/OMessageList.vue b/packages/opendesign/src/components/message/OMessageList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0b45998fa44dfa1313083207bbd92b423166344b
--- /dev/null
+++ b/packages/opendesign/src/components/message/OMessageList.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/opendesign/src/components/message/__demo__/IndexMessage.vue b/packages/opendesign/src/components/message/__demo__/IndexMessage.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e074ad3502d98830200b3c6865e3ac8b925c5c38
--- /dev/null
+++ b/packages/opendesign/src/components/message/__demo__/IndexMessage.vue
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/packages/opendesign/src/components/message/__demo__/MessageBasic.vue b/packages/opendesign/src/components/message/__demo__/MessageBasic.vue
new file mode 100644
index 0000000000000000000000000000000000000000..651c77c8254634685fca0ea25965cad848695178
--- /dev/null
+++ b/packages/opendesign/src/components/message/__demo__/MessageBasic.vue
@@ -0,0 +1,43 @@
+
+
+
+ 基础用法
+
+ Info Message
+ Success Message
+ Warning Message
+ Danger Message
+
+
diff --git a/packages/opendesign/src/components/message/index.ts b/packages/opendesign/src/components/message/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c5bf979732b2b7a6ce03c1776251b484ab57b69e
--- /dev/null
+++ b/packages/opendesign/src/components/message/index.ts
@@ -0,0 +1,14 @@
+import type { App } from 'vue';
+
+import _OMessage from './OMessage.vue';
+import useMessage from './use-message';
+
+const OMessage = Object.assign(_OMessage, {
+ install(app: App) {
+ app.component(_OMessage.name, _OMessage);
+ },
+});
+
+export * from './types';
+
+export { OMessage, useMessage };
diff --git a/packages/opendesign/src/components/message/style/index.scss b/packages/opendesign/src/components/message/style/index.scss
new file mode 100644
index 0000000000000000000000000000000000000000..370d2754650f6ea805ff25027d020c896cc96a56
--- /dev/null
+++ b/packages/opendesign/src/components/message/style/index.scss
@@ -0,0 +1,43 @@
+@use './var.scss';
+
+.o-message {
+ display: flex;
+ align-items: center;
+ padding: var(--message-padding);
+ font-size: var(--message-text-size);
+ line-height: var(--message-text-height);
+ color: var(--message-color);
+ background-color: var(--message-bg-color);
+ border-radius: var(--message-radius);
+ border: 1px solid var(--message-bd-color);
+ width: fit-content;
+ transition: all var(--o-duration-m1) var(--o-easing-standard-out);
+}
+
+.o-message + .o-message {
+ margin-top: 16px;
+}
+
+.o-message-list {
+ position: fixed;
+ z-index: 1001;
+ left: 50%;
+ top: 32px;
+ transform: translateX(-50%);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.fade-message-enter-from {
+ opacity: 0;
+}
+
+.fade-message-enter-active,
+.fade-message-leave-active {
+ transition: all var(--o-duration-m1) var(--o-easing-standard-out);
+}
+
+.fade-message-leave-to {
+ opacity: 0;
+}
diff --git a/packages/opendesign/src/components/message/style/index.ts b/packages/opendesign/src/components/message/style/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..591b2fbd72dcd5e987362d645b561bd2fac42716
--- /dev/null
+++ b/packages/opendesign/src/components/message/style/index.ts
@@ -0,0 +1,2 @@
+import '../../style';
+import './index.scss';
diff --git a/packages/opendesign/src/components/message/style/var.scss b/packages/opendesign/src/components/message/style/var.scss
new file mode 100644
index 0000000000000000000000000000000000000000..70cb1eeb861ff42b9e7a5507724680bd2474890e
--- /dev/null
+++ b/packages/opendesign/src/components/message/style/var.scss
@@ -0,0 +1,29 @@
+.o-message {
+ --message-padding: 8px 16px;
+ --message-text-size: var(--o-font_size-text1);
+ --message-text-height: var(--o-line_height-text1);
+
+ --message-color: var(--o-color-info1);
+
+ --message-radius: var(--o-radius-control-m);
+}
+
+.o-message-normal {
+ --message-bg-color: var(--o-color-control-light);
+ --message-bd-color: var(--o-color-control1);
+}
+
+.o-message-success {
+ --message-bg-color: var(--o-color-success4-light);
+ --message-bd-color: var(--o-color-success1);
+}
+
+.o-message-warning {
+ --message-bg-color: var(--o-color-warning4-light);
+ --message-bd-color: var(--o-color-warning1);
+}
+
+.o-message-danger {
+ --message-bg-color: var(--o-color-danger4-light);
+ --message-bd-color: var(--o-color-danger1);
+}
diff --git a/packages/opendesign/src/components/message/types.ts b/packages/opendesign/src/components/message/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f7d7b190b24f3c9afbb9a8287903cc809240ab5
--- /dev/null
+++ b/packages/opendesign/src/components/message/types.ts
@@ -0,0 +1,26 @@
+import type { ExtractPropTypes, PropType } from 'vue';
+import { ColorT } from '../_shared/global';
+
+export const messageProps = {
+ color: {
+ type: String as PropType,
+ default: 'normal',
+ },
+ content: {
+ type: String,
+ default: '',
+ },
+ duration: {
+ type: Number,
+ default: 3000,
+ },
+ onClose: {
+ type: Function as PropType<() => void>,
+ },
+};
+
+export type MessagePropsT = ExtractPropTypes;
+
+export type MessagePositionT = 'top' | 'bottom';
+
+export type MessageParamsT = Partial;
diff --git a/packages/opendesign/src/components/message/use-message.ts b/packages/opendesign/src/components/message/use-message.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4df6da19a1718eb1f4545073b8f423a0e35a9480
--- /dev/null
+++ b/packages/opendesign/src/components/message/use-message.ts
@@ -0,0 +1,108 @@
+import { createVNode, render } from 'vue';
+import { isString } from '../_shared/is';
+import { MessageParamsT } from './types';
+import OMessageList from './OMessageList.vue';
+
+const DEFAULT_OPTIONS: MessageParamsT = {
+ color: 'normal',
+ position: 'top',
+ duration: 3000,
+};
+
+const instanceMap = new Map();
+
+const normalizeOptions = (params: MessageParamsT) => {
+ const options: MessageParamsT = !params || isString(params) ? { content: params } : params;
+
+ const normalized = {
+ ...DEFAULT_OPTIONS,
+ ...options,
+ };
+
+ return normalized;
+};
+
+const showMessage = (params: MessageParamsT) => {
+ const { position } = params;
+
+ const instance = instanceMap.get(position);
+ if (!instance) {
+ let wrap: HTMLDivElement | null = document.createElement('div');
+
+ const vnode = createVNode(OMessageList, {
+ position: params.position,
+ onDestory: () => {
+ if (wrap) {
+ document.body.removeChild(wrap);
+ wrap = null;
+ }
+ instanceMap.set(position, undefined);
+ },
+ });
+
+ render(vnode, wrap);
+
+ const vm = vnode.component!;
+ vm.exposed?.add(params);
+
+ instanceMap.set(position, vm);
+
+ document.body.appendChild(wrap);
+ } else {
+ instance.exposed?.add(params);
+ }
+};
+
+const normal = (params: MessageParamsT): void => {
+ const options: MessageParamsT = normalizeOptions(params);
+ showMessage({
+ ...options,
+ color: 'normal',
+ });
+};
+
+const primary = (params: MessageParamsT): void => {
+ const options: MessageParamsT = normalizeOptions(params);
+ showMessage({
+ ...options,
+ color: 'primary',
+ });
+};
+
+const success = (params: MessageParamsT): void => {
+ const options: MessageParamsT = normalizeOptions(params);
+ showMessage({
+ ...options,
+ color: 'success',
+ });
+};
+
+const warning = (params: MessageParamsT): void => {
+ const options: MessageParamsT = normalizeOptions(params);
+ showMessage({
+ ...options,
+ color: 'warning',
+ });
+};
+
+const danger = (params: MessageParamsT): void => {
+ const options: MessageParamsT = normalizeOptions(params);
+ showMessage({
+ ...options,
+ color: 'danger',
+ });
+};
+
+const Message = {
+ normal,
+ primary,
+ success,
+ warning,
+ danger,
+};
+
+const useMessage = () => {
+ return Message;
+};
+
+export default useMessage;
diff --git a/packages/portal/src/router.ts b/packages/portal/src/router.ts
index f57e6dfadd7fd9fa23a8401f07a62d8eccb37a70..4848a27a4f82a518be22ee501fcb0d33a10a395b 100644
--- a/packages/portal/src/router.ts
+++ b/packages/portal/src/router.ts
@@ -68,6 +68,12 @@ export const routes = [
label: '下拉框',
component: () => import('@components/select/__demo__/IndexSelect.vue'),
},
+ // {
+ // path: '/cascader',
+ // name: 'Cascader',
+ // label: '级联选择器',
+ // component: () => import('@components/cascader/__demo__/IndexCascader.vue'),
+ // },
{
path: '/radio',
name: 'Radio',
@@ -164,6 +170,12 @@ export const routes = [
label: '卡片',
component: () => import('@components/card/__demo__/IndexCard.vue'),
},
+ {
+ path: '/message',
+ name: 'Message',
+ label: '消息提示',
+ component: () => import('@components/message/__demo__/IndexMessage.vue'),
+ },
{
path: '/resize-observer',
name: 'ResizeObserver',