效果
使用示例
<script setup lang="ts">
import ToMessage from "**/NaiveMessage";
function a() {
ToMessage("#a", "測試消息a", { duration: 0, closable: true });
}
function b() {
ToMessage("#b", "測試消息b", { duration: 0, closable: true });
}
</script>
<template>
<div style="display: flex">
<div
id="a"
style="width: 200px; height: 200px; border: 1px solid; margin: 10px"
>
<button @click="a()" style="padding: 10px">a+</button>
</div>
<div
id="b"
style="width: 200px; height: 200px; border: 1px solid; margin: 10px"
>
<button @click="b()" style="padding: 10px">b+</button>
</div>
</div>
</template>
副作用提示
Ctrl + f 搜索 注意副作用
掛載 App.vue
<template>
<NConfigProvider>
<div id="to-message-app-box"></div>
</NConfigProvider>
</template>
組件 */index.vue
<script lang="ts" setup>
import { ref } from "vue";
import NMessage from "./Message.vue";
const msg = ref<InstanceType<typeof NMessage>>();
defineExpose({ msg });
</script>
<template>
<n-message-provider>
<NMessage ref="msg" />
</n-message-provider>
</template>
<style lang="scss">
// 注意副作用 - 這會改變目標元素樣式
.n-message-container-target {
position: relative;
.n-message-container {
position: absolute;
}
}
</style>
組件 */Message.vue
<script lang="ts" setup>
import { useMessage } from "naive-ui";
const message = useMessage();
defineExpose({
create: message.create,
});
</script>
組件 */index.ts
import {
MessageOptions,
MessageProviderProps,
MessageReactive,
} from "naive-ui";
import { createApp, h, VNodeChild } from "vue";
import NMessage from "./index.vue";
import { App } from "vue";
const getDom = (to?: string | HTMLElement) => {
to = to || "body";
if (typeof to == "string") return document.querySelector(to) as HTMLElement;
return to as HTMLElement;
};
const addClass = (dom: HTMLElement) =>
dom.classList.add("n-message-container-target");
const removeClass = (dom: HTMLElement) =>
dom.classList.remove("n-message-container-target");
type Provider = {
target: HTMLElement;
count: number;
ref: InstanceType<typeof NMessage>;
app: App<Element>;
appDom: HTMLElement;
};
const providers: Provider[] = [];
type ToMessageType = (
props: string | MessageProviderProps,
content: string | (() => VNodeChild),
option?: MessageOptions
) => Promise<MessageReactive | undefined>;
const ToMessage: ToMessageType = (props, content, option = {}) => {
const appBox = document.querySelector("#to-message-app-box") as HTMLElement;
props = typeof props == "string" ? { to: props } : props;
props.to = getDom(props.to);
// 注意副作用 - 這會改變目標元素dom樹
const target = props.to;
return new Promise((resolve) => {
const onAfterLeave = option.onAfterLeave;
option.onAfterLeave = () => {
const item = providers.find((v) => v.target == target)!;
item.count--;
if (item.count > 0) return;
item.app.unmount();
appBox.removeChild(item.appDom);
removeClass(item.target);
providers.splice(providers.indexOf(item), 1);
onAfterLeave?.();
};
const item = providers.find((v) => v.target == target);
if (item) {
item.count++;
requestAnimationFrame(() =>
resolve(item.ref.msg?.create(content, option))
);
} else {
addClass(target);
const appDom = document.createElement("div");
appBox.appendChild(appDom);
let app: Provider["app"];
let is = true;
const getRef = (v: Provider["ref"]) => {
if (!v || !is) return;
is = false;
providers.push({ target, appDom, count: 1, ref: v, app });
requestAnimationFrame(() => resolve(v.msg?.create(content, option)));
};
const vnode = () => h(NMessage, { ...props, ref: (v: any) => getRef(v) });
app = createApp(vnode);
app.mount(appDom);
}
});
};
export default ToMessage;