| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- <template>
- <view
- v-if="renderState"
- class="nana-toast-container"
- :style="{
- ...selectStyleType<ViewStyle, IToastPosition>(position, 'center', {
- center: {
- justifyContent: 'center',
- },
- top: {
- justifyContent: 'flex-start',
- paddingTop: theme.resolveThemeSize('ToastMarginWhenTop', 200),
- },
- bottom: {
- justifyContent: 'flex-end',
- paddingBottom: theme.resolveThemeSize('ToastMarginWhenBottom', 200),
- },
- }),
- pointerEvents: forbidClick ? 'auto' : 'none',
- ...maskStyle,
- }"
- >
- <view
- :class="[
- 'nana-toast',
- showState ? 'show' : 'hide',
- ]"
- :style="{
- pointerEvents: forbidClick ? 'auto' : 'none',
- ...themeStyles.toastStyle.value,
- ...toastStyle,
- }"
- @click="handleClick"
- >
- <ActivityIndicator
- v-if="showProps.type === 'loading'"
- :color="textStyle?.color as string || themeStyles.toastTextStyle.value.color"
- :size="themeStyles.toastIconStyle.value.size"
- />
- <Icon
- v-else-if="showProps.type !== 'text' || showProps.icon"
- :icon="showProps.icon || iconType[showProps.type!]"
- :color="textStyle?.color as string || themeStyles.toastTextStyle.value.color"
- :size="themeStyles.toastIconStyle.value.size"
- v-bind="iconProps"
- />
- <Height v-if="showProps.type !== 'text'" :size="themeStyles.toastIconStyle.value.marginBottom" />
- <slot name="content">
- <text :style="{
- ...themeStyles.toastTextStyle.value,
- ...textStyle
- }">{{ showProps.content }}</text>
- </slot>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { nextTick, ref } from 'vue';
- import ActivityIndicator from '../basic/ActivityIndicator.vue';
- import type { IconProps } from '../basic/Icon.vue';
- import { useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
- import { DynamicColor, DynamicSize, DynamicSize2, DynamicVar, selectStyleType } from '../theme/ThemeTools';
- import Icon from '../basic/Icon.vue';
- import Height from '../layout/space/Height.vue';
- export type IToastPosition = 'top'|'bottom'|'center';
- export type IToastType = 'text'|'loading'|'success'|'fail'|'info'|'offline';
- export interface ToastProps {
- /**
- * 图标自定义属性
- */
- iconProps?: IconProps;
- /**
- * 提示显示位置
- */
- position?: IToastPosition;
- /**
- * 是否禁止背景点击
- */
- forbidClick?: boolean;
- /**
- * 是否在点击后关闭
- */
- closeOnClick?: boolean;
- /**
- * 土司背景的自定义样式
- */
- maskStyle?: ViewStyle;
- /**
- * 土司提示容器的自定义样式
- */
- toastStyle?: ViewStyle;
- /**
- * 土司提示文字的自定义样式
- */
- textStyle?: TextStyle;
- }
- export interface ToastShowProps {
- /**
- * 自动关闭的延时,单位ms。
- * * 0:根据内容长度自动设置关闭时间,最小3000ms,最大15000ms
- * * -1:一直显示,直到手动关闭
- */
- duration?: number;
- /**
- * 图标类型
- */
- type?: IToastType;
- /**
- * 自定义图标
- */
- icon?: string;
- /**
- * 土司内容
- */
- content?: string;
- /**
- * 关闭后回调
- */
- onClose?: () => void;
- }
- const props = withDefaults(defineProps<ToastProps>(), {
- position: 'center',
- forbidClick: false,
- closeOnClick: true,
- })
- const theme = useTheme();
- const iconType: {
- [key: string]: string
- } = {
- success: theme.getVar('ToastIconSuccess', 'success'),
- fail: theme.getVar('ToastIconError', 'error'),
- offline: theme.getVar('ToastIconOffline', 'cry'),
- info: theme.getVar('ToastIconInfo', 'prompt'),
- };
- const themeStyles = theme.useThemeStyles({
- toastStyle: {
- backgroundColor: DynamicColor('ToastBackgroundColor', 'background.toast'),
- padding: DynamicSize2('ToastPaddingVertical', 'ToastPaddingHorizontal', 25, 30),
- borderRadius: DynamicSize('ToastRadius', 16),
- },
- toastTextStyle: {
- color: DynamicColor('ToastTextColor', 'white'),
- },
- toastIconStyle: {
- size: DynamicVar('ToastIconSize', 85),
- marginBottom: DynamicSize('ToastIconMarginBottom', 12),
- },
- })
- const renderState = ref(false);
- const showState = ref(false);
- const showProps = ref<ToastShowProps>({
- duration: 0,
- type: 'text',
- icon: '',
- content: '',
- onClose: () => {},
- });
- let showTimer = 0;
- function show(options: ToastShowProps|string) {
- if (typeof options === 'string')
- options = { content: options };
- const {
- duration = 0,
- type = 'text',
- icon = '',
- content = '',
- onClose = () => {},
- } = options;
- showState.value = false;
- renderState.value = true;
- showProps.value = {
- duration,
- type,
- icon,
- content,
- onClose,
- };
- if (showTimer)
- clearTimeout(showTimer);
- setTimeout(() => showState.value = true, 10);
- let duration2 = duration;
- if (duration2 === 0)
- duration2 = Math.min(Math.max((content.length / 10) * 1000, 3000), 15000);
- if (duration2 > 0)
- showTimer = setTimeout(hide, duration2) as unknown as number;
- }
- function showWithType(type: IToastType, options: ToastShowProps|string) {
- show({
- type,
- content: typeof options === 'string' ? options : options.content,
- ...(typeof options === 'object' ? options : {}),
- })
- }
- function updateProps(options?: ToastShowProps) {
- showProps.value = {
- ...showProps.value,
- ...options,
- }
- }
- function hide() {
- if (showTimer)
- clearTimeout(showTimer);
- showProps.value.onClose?.();
- showState.value = false;
- showTimer = 0;
- showTimer = setTimeout(() => {
- renderState.value = false;
- showTimer = 0;
- }, 500) as unknown as number;
- }
- function handleClick() {
- if (props.closeOnClick)
- hide();
- }
- export interface ToastInstance {
- show(options: ToastShowProps|string) : void;
- info(options?: ToastShowProps|string): void;
- success(options?: ToastShowProps|string): void;
- fail(options?: ToastShowProps|string): void;
- offline(options?: ToastShowProps|string): void;
- loading(options?: ToastShowProps|string): void;
- text(options?: ToastShowProps|string): void;
- hide(): void;
- close(): void;
- updateProps(options?: ToastShowProps): void;
- }
- defineExpose<ToastInstance>({
- show,
- updateProps,
- info(options?: ToastShowProps|string) { showWithType('info', options as ToastShowProps); },
- success(options?: ToastShowProps|string) { showWithType('success', options as ToastShowProps); },
- fail(options?: ToastShowProps|string) { showWithType('fail', options as ToastShowProps); },
- offline(options?: ToastShowProps|string) { showWithType('offline', options as ToastShowProps); },
- loading(options?: ToastShowProps|string) { showWithType('loading', options as ToastShowProps); },
- text(options?: ToastShowProps|string) { showWithType('text', options as ToastShowProps); },
- hide,
- close: hide,
- })
- </script>
- <style lang="scss">
- .nana-toast-container {
- position: fixed;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- z-index: 140;
- .nana-toast {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- opacity: 0;
- transition: opacity ease-in-out 0.3s;
- &.show {
- opacity: 1;
- }
- }
- }
- </style>
|