如何使用 primeVue toast 创建可重用的 toastService?
How to create reusable toastService with primeVue toast?
我想知道是否可以通过 primevue toast 函数调用创建可重用的 scritp/class/service,这样我就不需要在每个组件中直接调用 primevue toast 函数。
到目前为止,我一直在尝试创建一个 ToastService.ts,如下所示:
import { useToast } from 'primevue/usetoast';
const toast = useToast();
export function addMsgSuccess(): void {
toast.add({ severity: 'success', summary: 'Test', detail: 'Test', life: 3000 });
}
但是此代码使我的应用程序崩溃并且出现以下错误:
Uncaught Error: No PrimeVue Toast provided!at useToast
(usetoast.esm.js?18cb:8:1) eval (ToastService.ts?73ba:3:1)
Module../src/shared/service/ToastService.ts (app.js:1864:1)
webpack_require (app.js:849:30) fn (app.js:151:20) eval (cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/ts-loader/index.js?!./node_modules/eslint-loader/index.js?!./src/views/cadastro-plano/CadastroPlano.ts?vue&type=script&lang=ts:31:87)
Module../node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/ts-loader/index.js?!./node_modules/eslint-loader/index.js?!./src/views/cadastro-plano/CadastroPlano.ts?
有谁知道如何解决这个问题,或者创建调用 add() 的函数,这样我就不需要每次都调用它了?
也许以下解决方案适合您:
在 App.vue 中添加一个 Toast 并添加一个手表来检查来自商店的消息
<template>
<router-view />
<Toast position="bottom-right" group="br" />
</template>
<script>
import { watch } from "vue";
import { useStore } from "vuex";
import { useToast } from "primevue/usetoast";
export default {
setup() {
const store = useStore();
const toast = useToast();
watch(
() => store.getters.getErrorMessage,
(message, prevMessage) => {
console.log("error message", message);
if (message) {
toast.add({
severity: "error",
summary: "Error",
detail: message,
group: "br",
life: 6000,
});
}
}
);
},
};
</script>
商店
import { createStore } from "vuex";
export default createStore({
state: { errorMessage: "" },
mutations: {
setErrorMessage(state, payload) {
state.errorMessage = payload;
},
},
actions: {},
modules: {},
getters: {
getErrorMessage: (state) => state.errorMessage,
},
});
然后,在任何其他组件中只需更新消息
store.commit("setErrorMessage", error.message);
这个解决方案对我有用,但我不确定它是不是一个好的解决方案。
首先:从 main.ts
导出应用
// main.ts
import {createApp} from 'vue';
import App from '@/App.vue';
import PrimeVue from 'primevue/config';
import ToastService from 'primevue/toastservice';
export const app = createApp(App);
app.use(PrimeVue);
app.use(ToastService);
app.mount('#app')
其次:导入应用程序并使用 app.config.globalProperties
的 toast 服务
// myToastService.ts
import {ToastSeverity} from 'primevue/api';
import {app} from '@/main';
const lifeTime = 3000;
export function info(title: string = 'I am title', body: string = 'I am body'): void {
app.config.globalProperties.$toast.add({severity: ToastSeverity.INFO, summary: title, detail: body, life: lifeTime});
};
export function error(title: string = 'I am title', body: string = 'I am body'): void {
app.config.globalProperties.$toast.add({severity: ToastSeverity.ERROR, summary: title, detail: body, life: lifeTime});
};
第三:在你的组件中导入 myToastService。
// myTestToastComponent.vue
<script setup lang="ts">
import {info, error} from '@/components/myToastService';
info();
</script>
玩了一会儿之后,我找到了一个我认为很优雅的解决方案,尽管它使用的是 Pinia。使用此方法,您也可以在辅助函数中调用toasts,并且函数的复用非常简单。
main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import Toast from "primevue/toast";
createApp(App)
.use(router)
.use(PrimeVue)
.use(createPinia())
.use(ToastService)
.component("Toast", Toast)
.mount("#app");
interfaces.ts
export interface Message {
severity: string;
summary: string;
detail: string;
}
useNotifications.ts
import { defineStore } from "pinia";
import { Message } from "@interfaces";
interface State {
info: Message;
notify: Message;
confirm: Message;
}
const useNotifications = defineStore({
id: "notificationStore",
// Might be better to only have one piece of state, but this is the solution I went with
// info for basic notifs, notify for sticky notifs, confirm for notifs that need confirmation
state: (): State => ({
info: {
severity: "",
summary: "",
detail: "",
},
notify: {
severity: "",
summary: "",
detail: "",
},
confirm: {
severity: "",
summary: "",
detail: "",
},
}),
});
export default useNotifications;
App.vue
<script setup lang="ts">
import { useToast } from "primevue/usetoast";
import { useNotifications } from "@store";
import { Message } from "@interfaces";
const notificationStore = useNotifications();
const toast = useToast();
// Watches changes on notificationStore throughout the app
notificationStore.$subscribe((mutation, state) => {
// Checks which part of the state has been mutated, and updates that state based on those conditions
// mutation.events.key will throw a TypeScript error, but it will still work (until build time where another solution should be found)
const key = mutation.events.key;
if (key === "info") {
const { severity, summary, detail } = state.info;
toast.add({ severity, summary, detail, life: 3000, group: "br" });
} else if (key === "notify") {
const { severity, summary, detail } = state.notify;
toast.add({ severity, summary, detail, group: "br" });
} else if (key === "confirm") {
const { severity, summary, detail } = state.confirm;
toast.add({ severity, summary, detail, group: "bc" });
}
});
// Use provide to make Toast functionality easily injectable
provide("toastConfirm", (args: Message) => {
const { severity, summary, detail } = args;
notificationStore.confirm = { severity, summary, detail };
});
provide("toastNotify", (args: Message) => {
const { severity, summary, detail } = args;
notificationStore.notify = { severity, summary, detail };
});
provide("toastInfo", (args: Message) => {
const { severity, summary, detail } = args;
notificationStore.info = { severity, summary, detail };
});
const denyToast = () => {
toast.removeGroup("bc");
};
// Have not figured out how to make this function useable
const acceptToast = () => {
toast.removeGroup("bc");
};
</script>
<template>
<!-- This group will represent the toasts I do not wish to have to confirm -->
<Toast position="bottom-right" group="br" />
<!-- The 'confirmable' toast template is basically copy pasted from PrimeVue docs -->
<!-- This Toast will appear in the bottom center -->
<Toast position="bottom-center" group="bc">
<template #message="slotProps">
<div>
<div>
<i class="pi pi-exclamation-triangle" />
<h4>{{ slotProps.message.summary }}</h4>
<p>{{ slotProps.message.detail }}</p>
</div>
<div>
<div>
<Button class="p-button-danger" label="No" @click="denyToast" />
<Button class="p-button-success" label="Yes" @click="acceptToast" />
</div>
</div>
</div>
</template>
</Toast>
</template>
AnyChildComponent.vue
<script setup lang="ts">
import { inject } from "vue";
import { Message } from "@interface";
const notifyMe = inject("toastNotify", (args: Message) => {});
const handleClick = () => {
notifyMe({
severity: "success",
summary: "success!",
detail: "this is from child component",
});
};
</script>
<template>
<Button @click="handleClick" />
</template>
exampleOfUsageInHelperFunction.ts
import { useNotifications } from "@store";
// Database helper function
const send = async (channel: string, data?: Object) => {
const notificationStore = useNotifications();
if (data) data = JSON.parse(JSON.stringify(data));
// response will return something like: { message: "Query failed", error: error } or { message: "Query succeeded", success?: returnData }
const response = await window.electron.ipcRenderer.invoke(channel, data);
if (response.error) {
console.log(response.error);
notificationStore.notify = {
severity: "danger",
summary: "Error",
detail: response.message,
};
} else {
notificationStore.info = {
severity: "success",
summary: "Success",
detail: response.message,
};
if (response.success) return response.success;
}
};
export default send;
另一种选择是在单个位置处理 toasts (App.vue
),然后通过消息总线接收它们。
首先在main.js
设置公交车:
import mitt from 'mitt'
const toastBus = mitt()
const app = createApp(App)
// Provide the bus for injection for use by components
app.provide('toastBus', toastBus)
// Export the bus for use by 'non-component' scripts
export { toastBus }
然后将总线注入App.vue,监听事件并生成toasts:
<template>
<div id="app"></div>
<Toast />
</template>
<script>
import { inject } from 'vue'
import { useToast } from 'primevue/usetoast'
const toastBus = inject('toastBus')
const toast = useToast()
toastBus.on('*', (type, args) => {
if (type === 'add') {
toast.add(args)
} else if (type === 'remove') {
toast.remove(args)
}
})
然后从组件发送消息框:
let toastBus = inject('toastBus')
toastBus.emit('add', { severity: 'success', summary: 'Test', detail: 'Test', life: 3000 })
或来自non-component代码:
import { toastBus } from './main'
toastBus.emit('add', { severity: 'success', summary: 'Test', detail: 'Test', life: 3000 })
答案: @match 对于那些正在努力的人,将 App.vue 更改为:
<template>
<div id="app"></div>
<Toast />
</template>
<script>
import { inject } from 'vue'
import { useToast } from 'primevue/usetoast'
export default {
setup() {
const toastBus = inject('toastBus')
const toast = useToast()
toastBus.on('*', (type, args) => {
if (type === 'add') {
toast.add(args)
} else if (type === 'remove') {
toast.remove(args)
}
})
}
}
</script>
我想知道是否可以通过 primevue toast 函数调用创建可重用的 scritp/class/service,这样我就不需要在每个组件中直接调用 primevue toast 函数。
到目前为止,我一直在尝试创建一个 ToastService.ts,如下所示:
import { useToast } from 'primevue/usetoast';
const toast = useToast();
export function addMsgSuccess(): void {
toast.add({ severity: 'success', summary: 'Test', detail: 'Test', life: 3000 });
}
但是此代码使我的应用程序崩溃并且出现以下错误:
Uncaught Error: No PrimeVue Toast provided!at useToast (usetoast.esm.js?18cb:8:1) eval (ToastService.ts?73ba:3:1) Module../src/shared/service/ToastService.ts (app.js:1864:1) webpack_require (app.js:849:30) fn (app.js:151:20) eval (cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/ts-loader/index.js?!./node_modules/eslint-loader/index.js?!./src/views/cadastro-plano/CadastroPlano.ts?vue&type=script&lang=ts:31:87) Module../node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/ts-loader/index.js?!./node_modules/eslint-loader/index.js?!./src/views/cadastro-plano/CadastroPlano.ts?
有谁知道如何解决这个问题,或者创建调用 add() 的函数,这样我就不需要每次都调用它了?
也许以下解决方案适合您:
在 App.vue 中添加一个 Toast 并添加一个手表来检查来自商店的消息
<template>
<router-view />
<Toast position="bottom-right" group="br" />
</template>
<script>
import { watch } from "vue";
import { useStore } from "vuex";
import { useToast } from "primevue/usetoast";
export default {
setup() {
const store = useStore();
const toast = useToast();
watch(
() => store.getters.getErrorMessage,
(message, prevMessage) => {
console.log("error message", message);
if (message) {
toast.add({
severity: "error",
summary: "Error",
detail: message,
group: "br",
life: 6000,
});
}
}
);
},
};
</script>
商店
import { createStore } from "vuex";
export default createStore({
state: { errorMessage: "" },
mutations: {
setErrorMessage(state, payload) {
state.errorMessage = payload;
},
},
actions: {},
modules: {},
getters: {
getErrorMessage: (state) => state.errorMessage,
},
});
然后,在任何其他组件中只需更新消息
store.commit("setErrorMessage", error.message);
这个解决方案对我有用,但我不确定它是不是一个好的解决方案。
首先:从 main.ts
导出应用// main.ts
import {createApp} from 'vue';
import App from '@/App.vue';
import PrimeVue from 'primevue/config';
import ToastService from 'primevue/toastservice';
export const app = createApp(App);
app.use(PrimeVue);
app.use(ToastService);
app.mount('#app')
其次:导入应用程序并使用 app.config.globalProperties
// myToastService.ts
import {ToastSeverity} from 'primevue/api';
import {app} from '@/main';
const lifeTime = 3000;
export function info(title: string = 'I am title', body: string = 'I am body'): void {
app.config.globalProperties.$toast.add({severity: ToastSeverity.INFO, summary: title, detail: body, life: lifeTime});
};
export function error(title: string = 'I am title', body: string = 'I am body'): void {
app.config.globalProperties.$toast.add({severity: ToastSeverity.ERROR, summary: title, detail: body, life: lifeTime});
};
第三:在你的组件中导入 myToastService。
// myTestToastComponent.vue
<script setup lang="ts">
import {info, error} from '@/components/myToastService';
info();
</script>
玩了一会儿之后,我找到了一个我认为很优雅的解决方案,尽管它使用的是 Pinia。使用此方法,您也可以在辅助函数中调用toasts,并且函数的复用非常简单。
main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import Toast from "primevue/toast";
createApp(App)
.use(router)
.use(PrimeVue)
.use(createPinia())
.use(ToastService)
.component("Toast", Toast)
.mount("#app");
interfaces.ts
export interface Message {
severity: string;
summary: string;
detail: string;
}
useNotifications.ts
import { defineStore } from "pinia";
import { Message } from "@interfaces";
interface State {
info: Message;
notify: Message;
confirm: Message;
}
const useNotifications = defineStore({
id: "notificationStore",
// Might be better to only have one piece of state, but this is the solution I went with
// info for basic notifs, notify for sticky notifs, confirm for notifs that need confirmation
state: (): State => ({
info: {
severity: "",
summary: "",
detail: "",
},
notify: {
severity: "",
summary: "",
detail: "",
},
confirm: {
severity: "",
summary: "",
detail: "",
},
}),
});
export default useNotifications;
App.vue
<script setup lang="ts">
import { useToast } from "primevue/usetoast";
import { useNotifications } from "@store";
import { Message } from "@interfaces";
const notificationStore = useNotifications();
const toast = useToast();
// Watches changes on notificationStore throughout the app
notificationStore.$subscribe((mutation, state) => {
// Checks which part of the state has been mutated, and updates that state based on those conditions
// mutation.events.key will throw a TypeScript error, but it will still work (until build time where another solution should be found)
const key = mutation.events.key;
if (key === "info") {
const { severity, summary, detail } = state.info;
toast.add({ severity, summary, detail, life: 3000, group: "br" });
} else if (key === "notify") {
const { severity, summary, detail } = state.notify;
toast.add({ severity, summary, detail, group: "br" });
} else if (key === "confirm") {
const { severity, summary, detail } = state.confirm;
toast.add({ severity, summary, detail, group: "bc" });
}
});
// Use provide to make Toast functionality easily injectable
provide("toastConfirm", (args: Message) => {
const { severity, summary, detail } = args;
notificationStore.confirm = { severity, summary, detail };
});
provide("toastNotify", (args: Message) => {
const { severity, summary, detail } = args;
notificationStore.notify = { severity, summary, detail };
});
provide("toastInfo", (args: Message) => {
const { severity, summary, detail } = args;
notificationStore.info = { severity, summary, detail };
});
const denyToast = () => {
toast.removeGroup("bc");
};
// Have not figured out how to make this function useable
const acceptToast = () => {
toast.removeGroup("bc");
};
</script>
<template>
<!-- This group will represent the toasts I do not wish to have to confirm -->
<Toast position="bottom-right" group="br" />
<!-- The 'confirmable' toast template is basically copy pasted from PrimeVue docs -->
<!-- This Toast will appear in the bottom center -->
<Toast position="bottom-center" group="bc">
<template #message="slotProps">
<div>
<div>
<i class="pi pi-exclamation-triangle" />
<h4>{{ slotProps.message.summary }}</h4>
<p>{{ slotProps.message.detail }}</p>
</div>
<div>
<div>
<Button class="p-button-danger" label="No" @click="denyToast" />
<Button class="p-button-success" label="Yes" @click="acceptToast" />
</div>
</div>
</div>
</template>
</Toast>
</template>
AnyChildComponent.vue
<script setup lang="ts">
import { inject } from "vue";
import { Message } from "@interface";
const notifyMe = inject("toastNotify", (args: Message) => {});
const handleClick = () => {
notifyMe({
severity: "success",
summary: "success!",
detail: "this is from child component",
});
};
</script>
<template>
<Button @click="handleClick" />
</template>
exampleOfUsageInHelperFunction.ts
import { useNotifications } from "@store";
// Database helper function
const send = async (channel: string, data?: Object) => {
const notificationStore = useNotifications();
if (data) data = JSON.parse(JSON.stringify(data));
// response will return something like: { message: "Query failed", error: error } or { message: "Query succeeded", success?: returnData }
const response = await window.electron.ipcRenderer.invoke(channel, data);
if (response.error) {
console.log(response.error);
notificationStore.notify = {
severity: "danger",
summary: "Error",
detail: response.message,
};
} else {
notificationStore.info = {
severity: "success",
summary: "Success",
detail: response.message,
};
if (response.success) return response.success;
}
};
export default send;
另一种选择是在单个位置处理 toasts (App.vue
),然后通过消息总线接收它们。
首先在main.js
设置公交车:
import mitt from 'mitt'
const toastBus = mitt()
const app = createApp(App)
// Provide the bus for injection for use by components
app.provide('toastBus', toastBus)
// Export the bus for use by 'non-component' scripts
export { toastBus }
然后将总线注入App.vue,监听事件并生成toasts:
<template>
<div id="app"></div>
<Toast />
</template>
<script>
import { inject } from 'vue'
import { useToast } from 'primevue/usetoast'
const toastBus = inject('toastBus')
const toast = useToast()
toastBus.on('*', (type, args) => {
if (type === 'add') {
toast.add(args)
} else if (type === 'remove') {
toast.remove(args)
}
})
然后从组件发送消息框:
let toastBus = inject('toastBus')
toastBus.emit('add', { severity: 'success', summary: 'Test', detail: 'Test', life: 3000 })
或来自non-component代码:
import { toastBus } from './main'
toastBus.emit('add', { severity: 'success', summary: 'Test', detail: 'Test', life: 3000 })
答案:
<template>
<div id="app"></div>
<Toast />
</template>
<script>
import { inject } from 'vue'
import { useToast } from 'primevue/usetoast'
export default {
setup() {
const toastBus = inject('toastBus')
const toast = useToast()
toastBus.on('*', (type, args) => {
if (type === 'add') {
toast.add(args)
} else if (type === 'remove') {
toast.remove(args)
}
})
}
}
</script>